Chinaunix首页 | 论坛 | 博客
  • 博客访问: 570564
  • 博文数量: 146
  • 博客积分: 5251
  • 博客等级: 大校
  • 技术积分: 1767
  • 用 户 组: 普通用户
  • 注册时间: 2006-11-10 15:58
文章分类
文章存档

2010年(12)

2008年(129)

2007年(5)

我的朋友

分类: LINUX

2008-02-03 14:14:20

网络地址转换的实现初步分析

网络地址转换(NAT),从本质上来讲,是通过修改IP数据首部中的地址,以实现将一个地址转换成另一个地址的技术。当然,在某些情况下,修改的不仅仅是IP首部的来源或目的地址,还包括其它要素。
随着接入Internet的计算机数量的不断猛增,IP地址资源也就愈加显得捉襟见肘。目前NAT技术更多地被使用在将一个私网IP地址网段,转换为一个或几个公网IP地址,以实现私网与Internet的互相通讯。
Netfilter在连接跟踪的基础上,实现了两种类型的地址转换:源地址转换和目的地址转换。顾名思义,源地址转换就是修改IP包中的源地址(或许还有源端口),而目的地址转换,就是修改IP包中的目的地址(同样,或许还有目的端口)。前者通常用于将内网主机私网地址转换为公网地址,访问Internet,后者通常用于将公网IP地址转换为一个或几个私网地址,实现向互联网提供服务。

模块初始化

NAT模块对应的源文件是ip_nat_standard.c,同样地,init_or_cleanup是它的初始化函数:

CODE:
static int init_or_cleanup(int init)
{
        int ret = 0;

        need_ip_conntrack();

        if (!init) goto cleanup;

        /*初始化nat规则*/
        ret = ip_nat_rule_init();
        if (ret < 0) {
                printk("ip_nat_init: can't setup rules.\n");
                goto cleanup_nothing;
        }
/*初始化nat所需要重要数据结构*/
        ret = ip_nat_init();
        if (ret < 0) {
                printk("ip_nat_init: can't setup rules.\n");
                goto cleanup_rule_init;
        }
/*注册Hook*/
        ret = nf_register_hook(&ip_nat_in_ops);
        ……
        /*卸载各个注册的模块,释放初始化时申请的资源*/
cleanup:
        ……
return ret;
}

函数主要完成四个工作:

  • NAT规则表的初始化;
  • NAT所需的重要的数据结构的初始化;
  • 注册Hook;
  • 完成各清除工作;


ip_nat_rule_init工作很简单,注册NAT表和两个target:源地址转换(SNAT)和目的地址转换(DNAT):

CODE:
int __init ip_nat_rule_init(void)
{
        int ret;
        /*注册NAT表*/
        ret = ipt_register_table(&nat_table, &nat_initial_table.repl);
        if (ret != 0)
                return ret;
        /*注册SNAT Target */
        ret = ipt_register_target(&ipt_snat_reg);
        if (ret != 0)
                goto unregister_table;
        /*注册DNAT Target*/
        ret = ipt_register_target(&ipt_dnat_reg);
        if (ret != 0)
                goto unregister_snat;

        return ret;
}

这些函数在filter表中已经详细分析过了,读者可以对应结构成员的赋值,自行分析。需要注意的是对SNAT和DNAT两个模块的注册,它们的target处理函数分别是ipt_snat_target和ipt_dnat_target,这个target与包过滤中的target没有质的区别,都是规则的动作部份,只是完成的功能不同罢了——它们的工作是地址转换,而包过滤中的target是拦截、放行之类的。

规则初始化之外的动作,是在ip_nat_init函数中完成的,这个初始化函数同连接跟踪的初始化非常相似:

CODE:
int __init ip_nat_init(void)
{
        size_t i;

        /* 设置nat的hash表的大小 */
        ip_nat_htable_size = ip_conntrack_htable_size;

        /*同连接跟踪一样,nat的hash表也要维护一个list_head结构的hash链表*/
        bysource = vmalloc(sizeof(struct list_head) * ip_nat_htable_size);
        if (!bysource)
                return -ENOMEM;

        /* 初始化内建协议 */
        WRITE_LOCK(&ip_nat_lock);
        for (i = 0; i < MAX_IP_NAT_PROTO; i++)
                ip_nat_protos[i] = &ip_nat_unknown_protocol;
        ip_nat_protos[IPPROTO_TCP] = &ip_nat_protocol_tcp;
        ip_nat_protos[IPPROTO_UDP] = &ip_nat_protocol_udp;
        ip_nat_protos[IPPROTO_ICMP] = &ip_nat_protocol_icmp;
        WRITE_UNLOCK(&ip_nat_lock);

        /*初始化hash表*/
        for (i = 0; i < ip_nat_htable_size; i++) {
                INIT_LIST_HEAD(&bysource[i]);
        }

        /* FIXME: Man, this is a hack.   */
        IP_NF_ASSERT(ip_conntrack_destroyed == NULL);
        ip_conntrack_destroyed = &ip_nat_cleanup_conntrack;

        /* Initialize fake conntrack so that NAT will skip it */
        ip_conntrack_untracked.status |= IPS_NAT_DONE_MASK;
        return 0;
}

Hook的注册,我们已经反复地遇到了,NAT也不例外,它需要在各个关键的Hook点上注册自己的Hook,我们仍假设Linux做为一个网关型设备,关心它在PREROUTING和POSTROUTING两个Hook点上注册的Hook,因为它们完成了最重要的源地址转换和目的地址转换:

CODE:
/* 目的地址转换的Hook,在filter包过滤之前进行 */
static struct nf_hook_ops ip_nat_in_ops = {
        .hook                = ip_nat_in,
        .owner                = THIS_MODULE,
        .pf                = PF_INET,
        .hooknum        = NF_IP_PRE_ROUTING,
        .priority        = NF_IP_PRI_NAT_DST,
};

/*源地址转换,在filter包过滤之后*/
static struct nf_hook_ops ip_nat_out_ops = {
        .hook                = ip_nat_out,
        .owner                = THIS_MODULE,
        .pf                = PF_INET,
        .hooknum        = NF_IP_POST_ROUTING,
        .priority        = NF_IP_PRI_NAT_SRC,
};

源地址转换
源地址转换注册在NF_IP_POST_ROUTING,数据包在包过滤之后,会进入ip_nat_out函数。在分析这个函数之前,我们需要理解的是,源地址转换如何进行?
        源地址的转换最终要做的工作,就是修改IP包中的源地址,将其替换为iptables添加规则时指定的“转换后地址”,对于绝大多数应用而言,一般是将私网IP地址修改为公网IP地址,然后将数据包发送出去。但是,很自然地,这样修改后,回来的应答数据包没有办法知道它转换之前的样子,也就是不知道真实的来源主机(对于回应包,也就是不知道把数据应答给谁),数据包将被丢弃,所以有必要,维护一张地址转换表,详细记录数据包的转换情况,以使NAT后的数据能交互地传输。
        对于许多无状态检测功能的NAT技术,这个记录转换情况的表就是一张NAT会话表,对于Netfilter而言,已经为进出数据包建立了一张状态跟踪表,自然也就没有必要重新多维护一张表了,也就是,合理地利用状态跟踪表,实现对NAT状态的跟踪和维护。
回忆一下连接跟踪的情况,当数据包进入连接跟踪后,会建立一个tuple以及相应的replay tuple,而应答的数据包,会查找与之匹配的repaly tuple,——对于源地址转换而言,应答包中的目的地址,将是转换后的地址,而不是真实的地址,所以,为了让应答的数据包能找到对应的replay tuple,很自然地,NAT模块应该修改replaly tuple中的目的地址,以使应答数据包能找到属于自己的replay,如下图。

所以,源地址转换的主要步骤大致如下:
1.        数据包进入Hook函数后,进行规则匹配;
2.        如果所有match都匹备,则进行SNAT模块的动作,即snat 的target模块;
3.        源地址转换的规则一般是…… -j SNAT –to X.X.X.X,SNAT用规则中预设的转换后地址X.X.X.X,修改连接跟踪表中的replay tuple;
4.        接着,修改数据包的来源的地址,将它替换成replay tuple中的相应地址,即规则中预设的地址,将其发送出去;
5.        对于回来的数据包,应该能在状态跟踪表中,查找与之对应的replay tuple,也就能顺藤摸瓜地找到原始的tuple中的信息,将应答包中的目的地址改回来,这样,整个数据传送就得以顺利转发了;

当然,这些只是最精简单的步骤,因为有许多重要的因素没有考虑到,比如:
1.        上面的步骤只是对应一个来源IP的一个连接,但是一台主机在同时可能发出N个连接,所以应该有相应的解决办法;
2.        同样的,转换后地址可能是一段地址池;
3.        动态协议,如FTP,应该有相应的NAT方法;

我们还是从最简单的开始,ip_nat_out是SNAT的Hook函数。
ip_nat_out

CODE:
static unsigned int
ip_nat_out(unsigned int hooknum,
           struct sk_buff **pskb,
           const struct net_device *in,
           const struct net_device *out,
           int (*okfn)(struct sk_buff *))
{
        /* root is playing with raw sockets. */
        if ((*pskb)->len < sizeof(struct iphdr)
            || (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr))
                return NF_ACCEPT;

        if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
                *pskb = ip_ct_gather_frags(*pskb, IP_DEFRAG_NAT_OUT);

                if (!*pskb)
                        return NF_STOLEN;
        }

        return ip_nat_fn(hooknum, pskb, in, out, okfn);
}

在进行了IP包的长度较验和分片检查之后,函数进入ip_nat_fn,它是整个地址转换的核心函数之一:

CODE:
static unsigned int
ip_nat_fn(unsigned int hooknum,
          struct sk_buff **pskb,
          const struct net_device *in,
          const struct net_device *out,
          int (*okfn)(struct sk_buff *))
{
        struct ip_conntrack *ct;
        enum ip_conntrack_info ctinfo;
        struct ip_nat_info *info;
        /* maniptype通过调用HOOK2MAINIP宏判断Hook点,指明转换类型是源地址转换还是目的地址转换,为0(IP_NAT_MANIP_SRC)表示源地址转换,为1(IP_NAT_MANIP_DST)表示目的地址转换 */
        enum ip_nat_manip_type maniptype = HOOK2MANIP(hooknum);

        /* 前面函数中已经处理过分片的情况,这里应该不会再出现分片包了. */
        IP_NF_ASSERT(!((*pskb)->nh.iph->frag_off
                       & htons(IP_MF|IP_OFFSET)));

/*因为地址转换会修改数据包,所以这里先初始化将其设置为“未修改”标志,后面进行数据包修改时再来重置这个标志*/
        (*pskb)->nfcache |= NFC_UNKNOWN;

        /* If we had a hardware checksum before, it's now invalid */
        if ((*pskb)->ip_summed == CHECKSUM_HW)
                if (skb_checksum_help(*pskb, (out == NULL)))
                        return NF_DROP;
       
/*取得数据包的连接状态*/
        ct = ip_conntrack_get(*pskb, &ctinfo);
        /* 如果找不到对应连接,则应该直接放行它,而不再对其进行转换处理,特别地,ICMP重定向报文将会被丢弃*/
        if (!ct) {
                /* Exception: ICMP redirect to new connection (not in
                   hash table yet).  We must not let this through, in
                   case we're doing NAT to the same network. */
                if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
                        struct icmphdr _hdr, *hp;

                        hp = skb_header_pointer(*pskb,
                                                (*pskb)->nh.iph->ihl*4,
                                                sizeof(_hdr), &_hdr);
                        if (hp != NULL &&
                            hp->type == ICMP_REDIRECT)
                                return NF_DROP;
                }
                return NF_ACCEPT;
        }

/*判断连接状态,调用相应的处理函数*/
        switch (ctinfo) {
        case IP_CT_RELATED:
        case IP_CT_RELATED+IP_CT_IS_REPLY:
                if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
                        if (!icmp_reply_translation(pskb, ct, maniptype,
                                                    CTINFO2DIR(ctinfo)))
                                return NF_DROP;
                        else
                                return NF_ACCEPT;
                }
                /* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */
        case IP_CT_NEW:
                info = &ct->nat.info;

                /* 观察这个新建封包是否已经被NAT模块修改过了,如果没有,进一步调用ip_nat_rule_find函数*/
                if (!ip_nat_initialized(ct, maniptype)) {
                        unsigned int ret;

                        /* LOCAL_IN hook doesn't have a chain!  */
                        if (hooknum == NF_IP_LOCAL_IN)
                                ret = alloc_null_binding(ct, info, hooknum);
                        else
                                ret = ip_nat_rule_find(pskb, hooknum,
                                                       in, out, ct,
                                                       info);

                        if (ret != NF_ACCEPT) {
                                return ret;
                        }
                } else
                        DEBUGP("Already setup manip %s for ct %p\n",
                               maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST",
                               ct);
                break;

        default:
                /* ESTABLISHED */
                IP_NF_ASSERT(ctinfo == IP_CT_ESTABLISHED
                             || ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY));
                info = &ct->nat.info;
        }

        IP_NF_ASSERT(info);
        return nat_packet(ct, ctinfo, hooknum, pskb);
}

我们假设这是一个刚刚进入Linux的数据包,它是一个新建状态的连接,首先调用ip_nat_initialized函数判断它是否已经被地址转换例程修改过,即是否已经设置了相应转换类型的标志位:
static inline int ip_nat_initialized(struct ip_conntrack *conntrack,
                                     enum ip_nat_manip_type manip)
{
        /*如果是源地址转换,即测试源地址转换位,否则,测试目的地址转换位*/
if (manip == IP_NAT_MANIP_SRC)
                return test_bit(IPS_SRC_NAT_DONE_BIT, &conntrack->status);
        return test_bit(IPS_DST_NAT_DONE_BIT, &conntrack->status);
}

对于一个中转没有被修改过的包,ip_nat_rule_find函数将会被调用,以作进一步处理。

ip_nat_rule_find

ip_nat_rule_find 函数,从名称上我们就可以看出,它的含义是“NAT 规则查找”,它是一个规则匹配函数,其最重要的工作,就是调用ipt_do_table函数进行规则的检测,这个函数我们在包过滤一章已经对它进行了详细的分析,当ipt_do_table函数发现数据包匹配一条源地址转换的规则时,则会调用SNAT模块的target函数。

CODE:
int ip_nat_rule_find(struct sk_buff **pskb,
                     unsigned int hooknum,
                     const struct net_device *in,
                     const struct net_device *out,
                     struct ip_conntrack *ct,
                     struct ip_nat_info *info)
{
        int ret;

        /*NAT规则匹配*/
        ret = ipt_do_table(pskb, hooknum, in, out, &nat_table, NULL);

        if (ret == NF_ACCEPT) {
                if (!ip_nat_initialized(ct, HOOK2MANIP(hooknum)))
                        /* NUL mapping */
                        ret = alloc_null_binding(ct, info, hooknum);
        }
        return ret;
}

前面我们已经讨论过,SNAT注册的的target函数是ipt_snat_target,这个函数,首先取得规则中“转换后地址”的信息,然后将工作交给ip_nat_setup_info进一步处理:

CODE:
/* Source NAT */
static unsigned int ipt_snat_target(struct sk_buff **pskb,
                                    const struct net_device *in,
                                    const struct net_device *out,
                                    unsigned int hooknum,
                                    const void *targinfo,
                                    void *userinfo)
{
        struct ip_conntrack *ct;
        enum ip_conntrack_info ctinfo;
        /*取得规则中的target部份*/
        const struct ip_nat_multi_range_compat *mr = targinfo;

        IP_NF_ASSERT(hooknum == NF_IP_POST_ROUTING);
       
/*取得数据包的连接*/
        ct = ip_conntrack_get(*pskb, &ctinfo);

        /* Connection must be valid and new. */
        IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED
                            || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));
        IP_NF_ASSERT(out);

        return ip_nat_setup_info(ct, &mr->range[0], hooknum);
}

ip_nat_setup_info

ip_nat_setup_info 函数是地址转换中又一个非常重要的函数:

CODE:
unsigned int
ip_nat_setup_info(struct ip_conntrack *conntrack,                /*数据包对应的连接*/
                  const struct ip_nat_range range,                                /*转换后的地址池*/
                  unsigned int hooknum)                                        /*Hook点*/
{
        struct ip_conntrack_tuple curr_tuple, new_tuple;
        struct ip_nat_info *info = &conntrack->nat.info;
        int have_to_hash = !(conntrack->status & IPS_NAT_DONE_MASK);
        enum ip_nat_manip_type maniptype = HOOK2MANIP(hooknum);

        IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING
                     || hooknum == NF_IP_POST_ROUTING
                     || hooknum == NF_IP_LOCAL_IN
                     || hooknum == NF_IP_LOCAL_OUT);
        BUG_ON(ip_nat_initialized(conntrack, maniptype));

        /* What we've got will look like inverse of reply. Normally
           this is what is in the conntrack, except for prior
           manipulations (future optimization: if num_manips == 0,
           orig_tp =
           conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple) */
        invert_tuplepr(&curr_tuple,
                       &conntrack->tuplehash[IP_CT_DIR_REPLY].tuple);

        get_unique_tuple(&new_tuple, &curr_tuple, range, conntrack, maniptype);

        if (!ip_ct_tuple_equal(&new_tuple, &curr_tuple)) {
                struct ip_conntrack_tuple reply;

                /* Alter conntrack table so will recognize replies. */
                invert_tuplepr(&reply, &new_tuple);
                ip_conntrack_alter_reply(conntrack, &reply);

                /* Non-atomic: we own this at the moment. */
                if (maniptype == IP_NAT_MANIP_SRC)
                        conntrack->status |= IPS_SRC_NAT;
                else
                        conntrack->status |= IPS_DST_NAT;
        }

        /* Place in source hash if this is the first time. */
        if (have_to_hash) {
                unsigned int srchash
                        = hash_by_src(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL]
                                      .tuple);
                WRITE_LOCK(&ip_nat_lock);
                list_add(&info->bysource, &bysource[srchash]);
                WRITE_UNLOCK(&ip_nat_lock);
        }

        /* It's done. */
        if (maniptype == IP_NAT_MANIP_DST)
                set_bit(IPS_DST_NAT_DONE_BIT, &conntrack->status);
        else
                set_bit(IPS_SRC_NAT_DONE_BIT, &conntrack->status);

        return NF_ACCEPT;
}

这个函数转换来转换去,让人头大,慢慢抽丝拨茧先。首先调用invert_tuplepr取得一个当前数据包对应的replay tuple,然后对其取反得到一个curr_tuple,接着调用get_unique_tuple函数:

CODE:
static void
get_unique_tuple(struct ip_conntrack_tuple *tuple,
                 const struct ip_conntrack_tuple *orig_tuple,
                 const struct ip_nat_range *range,
                 struct ip_conntrack *conntrack,
                 enum ip_nat_manip_type maniptype)
{
        struct ip_nat_protocol *proto
                = ip_nat_find_proto(orig_tuple->dst.protonum);

        /* 1) If this srcip/proto/src-proto-part is currently mapped,
           and that same mapping gives a unique tuple within the given
           range, use that.

           This is only required for source (ie. NAT/masq) mappings.
           So far, we don't do local source mappings, so multiple
           manips not an issue.  */
        if (maniptype == IP_NAT_MANIP_SRC) {
                if (find_appropriate_src(orig_tuple, tuple, range)) {
                        DEBUGP("get_unique_tuple: Found current src map\n");
                        if (!ip_nat_used_tuple(tuple, conntrack))
                                return;
                }
        }

        /* 2) Select the least-used IP/proto combination in the given
           range. */
        *tuple = *orig_tuple;
        find_best_ips_proto(tuple, range, conntrack, maniptype);

        /* 3) The per-protocol part of the manip is made to map into
           the range to make a unique tuple. */

        /* Only bother mapping if it's not already in range and unique */
        if ((!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED)
             || proto->in_range(tuple, maniptype, &range->min, &range->max))
            && !ip_nat_used_tuple(tuple, conntrack))
                return;

        /* Last change: get protocol to try to obtain unique tuple. */
        proto->unique_tuple(tuple, range, maniptype, conntrack);
}

第一个值得关注的是find_appropriate_src函数,它主要是在一个以bysource为首的链表中进行遍历查找,这是做什么?我们初始化NAT的时候,对bysource进行过处始化,所以,到目前为止,这个链表还是空的,我们暂时跳过对这个函数的讨论,函数接下来执行:

CODE:
/*将curr_tuple赋值给的new_tuple*/
*tuple = *orig_tuple;
        find_best_ips_proto(tuple, range, conntrack, maniptype);

range中包含了规则中转换后地址的信息,find_best_ips_proto函数用转换后地址修改new_tuple:

CODE:
static void
find_best_ips_proto(struct ip_conntrack_tuple *tuple,
                    const struct ip_nat_range *range,
                    const struct ip_conntrack *conntrack,
                    enum ip_nat_manip_type maniptype)
{
        u_int32_t *var_ipp;
        /* Host order */
        u_int32_t minip, maxip, j;

        if (!(range->flags & IP_NAT_RANGE_MAP_IPS))
                return;
       
        if (maniptype == IP_NAT_MANIP_SRC)
                var_ipp = &tuple->src.ip;
        else
                var_ipp = &tuple->dst.ip;

        /* Fast path: only one choice. */
        if (range->min_ip == range->max_ip) {
                *var_ipp = range->min_ip;
                return;
        }

        minip = ntohl(range->min_ip);
        maxip = ntohl(range->max_ip);
        j = jhash_2words(tuple->src.ip, tuple->dst.ip, 0);
        *var_ipp = htonl(minip + j % (maxip - minip + 1));
}

如果是源地址转换,var_ipp指针指向tuple中的源地址:

CODE:
var_ipp = &tuple->src.ip;

然后,用range中的转换后地址替换它:

CODE:
        if (range->min_ip == range->max_ip) {
                *var_ipp = range->min_ip;
                return;
        }

这样,再返回至ip_nat_setup_info函数时,我们已经得到了一个根据规则中转换后地址修改过的new_tuple,接着,一个小判断,以确定确实是被修改过,然后,用它替换连接表中数据包的replay tuple:

CODE:
if (!ip_ct_tuple_equal(&new_tuple, &curr_tuple)) {
                struct ip_conntrack_tuple reply;

                /* 根据new tuple取反,得到转换后地址的reply tuple */
                invert_tuplepr(&reply, &new_tuple);
                /*修改连接跟踪表中的reply tuple*/
                ip_conntrack_alter_reply(conntrack, &reply);

                /* 设置状态标志位 */
                if (maniptype == IP_NAT_MANIP_SRC)
                        conntrack->status |= IPS_SRC_NAT;
                else
                        conntrack->status |= IPS_DST_NAT;
        }

我们忽略了太多的细节,如指定协议后的端口转换、转换后地址为地址池的情况等等,但是,这并不影响我们了解整个NAT的全过程,还是继续我们的例子:一个192.186.0.1至100.100.100.100的WEB访问,被修改为100.100.100.1的连接跟随表被修改的流程如下图所示:
Click here to open new window
CTRL+Mouse wheel to zoom in/out
在修改了数据包对应的连接跟踪表后,函数将返回至ip_nat_fn中,在函数的最后一句,调用了nat_packet修改数据包的来源地址:

CODE:
/* Do packet manipulations according to ip_nat_setup_info. */
unsigned int nat_packet(struct ip_conntrack *ct,
                        enum ip_conntrack_info ctinfo,
                        unsigned int hooknum,
                        struct sk_buff **pskb)
{
        enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
        unsigned long statusbit;
        enum ip_nat_manip_type mtype = HOOK2MANIP(hooknum);
        /*根据转换类型,设置状态标位位*/
        if (mtype == IP_NAT_MANIP_SRC)
                statusbit = IPS_SRC_NAT;
        else
                statusbit = IPS_DST_NAT;

        /* 应答的情况,暂时不考虑它 */
        if (dir == IP_CT_DIR_REPLY)
                statusbit ^= IPS_NAT_MASK;

        /* Non-atomic: these bits don't change. */
        if (ct->status & statusbit) {
                struct ip_conntrack_tuple target;

                /* 取得修改后replay tuple,并取反,对于源地址转换,就应该中target中的源地址替换IP包中的源地址 */
                invert_tuplepr(&target, &ct->tuplehash[!dir].tuple);
                /*根据replay tuple中的地址信息,修改数据包*/
                if (!manip_pkt(target.dst.protonum, pskb, 0, &target, mtype))
                        return NF_DROP;
        }
        return NF_ACCEPT;
}

数据包的修改工作,是在mainip_pkt中完成的:

CODE:
static int
manip_pkt(u_int16_t proto,
          struct sk_buff **pskb,
          unsigned int iphdroff,
          const struct ip_conntrack_tuple *target,
          enum ip_nat_manip_type maniptype)
{
        struct iphdr *iph;
       
        /*修改数据包,置相应标志位*/
        (*pskb)->nfcache |= NFC_ALTERED;
        if (!skb_ip_make_writable(pskb, iphdroff + sizeof(*iph)))
                return 0;
        /*取得IP首部*/
        iph = (void *)(*pskb)->data + iphdroff;

        /* 高层协议部份暂时不考虑 */
        if (!ip_nat_find_proto(proto)->manip_pkt(pskb, iphdroff,
                                                 target, maniptype))
                return 0;

        iph = (void *)(*pskb)->data + iphdroff;
       
        /*源地址转换,修改IP包中的来源地址*/
        if (maniptype == IP_NAT_MANIP_SRC) {
                iph->check = ip_nat_cheat_check(~iph->saddr, target->src.ip,
                                                iph->check);
                iph->saddr = target->src.ip;
        } else {
                iph->check = ip_nat_cheat_check(~iph->daddr, target->dst.ip,
                                                iph->check);
                iph->daddr = target->dst.ip;
        }
        return 1;
}

这样,转换后的数据就被发送出去了。但是,这只是出去的数据包,绝大多数情况下,回来的数据包将再次进入地址转换模块。

应答的包

对于应答的数据包,同样会进入ip_nat_fn函数,当判断了该数据包的连接状态,除了ICMP协议的应答需要特需处理外,数据包同样会进入nat_packet函数:

CODE:
switch (ctinfo) {
……
}
……
return nat_packet(ct, ctinfo, hooknum, pskb);

函数取得对应连接跟踪表的tuple,因为连接表中有转换前的地址信息,所以这里取反,用取反的tuple中的目的地址(即原来的来源地址)修改数据包。这样,整个NAT就建立起来了:

CODE:
/* Do packet manipulations according to ip_nat_setup_info. */
unsigned int nat_packet(struct ip_conntrack *ct,
                        enum ip_conntrack_info ctinfo,
                        unsigned int hooknum,
                        struct sk_buff **pskb)
{
        enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
        unsigned long statusbit;
        enum ip_nat_manip_type mtype = HOOK2MANIP(hooknum);

        if (mtype == IP_NAT_MANIP_SRC)
                statusbit = IPS_SRC_NAT;
        else
                statusbit = IPS_DST_NAT;

        /* Invert if this is reply dir. */
        if (dir == IP_CT_DIR_REPLY)
                statusbit ^= IPS_NAT_MASK;

        /* Non-atomic: these bits don't change. */
        if (ct->status & statusbit) {
                struct ip_conntrack_tuple target;

                /* We are aiming to look like inverse of other direction. */
                invert_tuplepr(&target, &ct->tuplehash[!dir].tuple);

                if (!manip_pkt(target.dst.protonum, pskb, 0, &target, mtype))
                        return NF_DROP;
        }
        return NF_ACCEPT;
}

目的地址转换的流程和原理与源地址转换是一样的。呵呵。

当然,这只是最基本,最简单的流程,做为一个完整的地址转换,存在一个地址对应多条连接(在查块初始化的时候,遇到过初始化hash表,呵呵,就是拿来做这个的),另外,如九贱在连接跟踪的实现中,叙述过动态协议的相关内容,NAT也要对此做相应的处理等等。
阅读(1500) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~