免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123下一页
最近访问板块 发新帖
查看: 27188 | 回复: 21

Netfilter 地址转换的实现 [复制链接]

论坛徽章:
0
发表于 2007-01-27 21:28 |显示全部楼层
要过年了,发个贴庆祝新年。

作者:九贱
内核版本:2.6.12
个人主页:www.skynet.org.cn

欢迎大家讨论,错误之处,敬请指正。转贴请注明作者及出处。

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

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

模块初始化

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

  1. static int init_or_cleanup(int init)
  2. {
  3.         int ret = 0;

  4.         need_ip_conntrack();

  5.         if (!init) goto cleanup;

  6.         /*初始化nat规则*/
  7.         ret = ip_nat_rule_init();
  8.         if (ret < 0) {
  9.                 printk("ip_nat_init: can't setup rules.\n");
  10.                 goto cleanup_nothing;
  11.         }
  12. /*初始化nat所需要重要数据结构*/
  13.         ret = ip_nat_init();
  14.         if (ret < 0) {
  15.                 printk("ip_nat_init: can't setup rules.\n");
  16.                 goto cleanup_rule_init;
  17.         }
  18. /*注册Hook*/
  19.         ret = nf_register_hook(&ip_nat_in_ops);
  20.         ……
  21.         /*卸载各个注册的模块,释放初始化时申请的资源*/
  22. cleanup:
  23.         ……
  24. return ret;
  25. }
复制代码


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


ip_nat_rule_init工作很简单,注册NAT表和两个target:源地址转换(SNAT)和目的地址转换(DNAT):
  1. int __init ip_nat_rule_init(void)
  2. {
  3.         int ret;
  4.         /*注册NAT表*/
  5.         ret = ipt_register_table(&nat_table, &nat_initial_table.repl);
  6.         if (ret != 0)
  7.                 return ret;
  8.         /*注册SNAT Target */
  9.         ret = ipt_register_target(&ipt_snat_reg);
  10.         if (ret != 0)
  11.                 goto unregister_table;
  12.         /*注册DNAT Target*/
  13.         ret = ipt_register_target(&ipt_dnat_reg);
  14.         if (ret != 0)
  15.                 goto unregister_snat;

  16.         return ret;
  17. }
复制代码


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

规则初始化之外的动作,是在ip_nat_init函数中完成的,这个初始化函数同连接跟踪的初始化非常相似:
  1. int __init ip_nat_init(void)
  2. {
  3.         size_t i;

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

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

  10.         /* 初始化内建协议 */
  11.         WRITE_LOCK(&ip_nat_lock);
  12.         for (i = 0; i < MAX_IP_NAT_PROTO; i++)
  13.                 ip_nat_protos[i] = &ip_nat_unknown_protocol;
  14.         ip_nat_protos[IPPROTO_TCP] = &ip_nat_protocol_tcp;
  15.         ip_nat_protos[IPPROTO_UDP] = &ip_nat_protocol_udp;
  16.         ip_nat_protos[IPPROTO_ICMP] = &ip_nat_protocol_icmp;
  17.         WRITE_UNLOCK(&ip_nat_lock);

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

  22.         /* FIXME: Man, this is a hack.  <SIGH> */
  23.         IP_NF_ASSERT(ip_conntrack_destroyed == NULL);
  24.         ip_conntrack_destroyed = &ip_nat_cleanup_conntrack;

  25.         /* Initialize fake conntrack so that NAT will skip it */
  26.         ip_conntrack_untracked.status |= IPS_NAT_DONE_MASK;
  27.         return 0;
  28. }
复制代码


Hook的注册,我们已经反复地遇到了,NAT也不例外,它需要在各个关键的Hook点上注册自己的Hook,我们仍假设Linux做为一个网关型设备,关心它在PREROUTING和POSTROUTING两个Hook点上注册的Hook,因为它们完成了最重要的源地址转换和目的地址转换:
  1. /* 目的地址转换的Hook,在filter包过滤之前进行 */
  2. static struct nf_hook_ops ip_nat_in_ops = {
  3.         .hook                = ip_nat_in,
  4.         .owner                = THIS_MODULE,
  5.         .pf                = PF_INET,
  6.         .hooknum        = NF_IP_PRE_ROUTING,
  7.         .priority        = NF_IP_PRI_NAT_DST,
  8. };

  9. /*源地址转换,在filter包过滤之后*/
  10. static struct nf_hook_ops ip_nat_out_ops = {
  11.         .hook                = ip_nat_out,
  12.         .owner                = THIS_MODULE,
  13.         .pf                = PF_INET,
  14.         .hooknum        = NF_IP_POST_ROUTING,
  15.         .priority        = NF_IP_PRI_NAT_SRC,
  16. };
复制代码


源地址转换
源地址转换注册在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函数。

[ 本帖最后由 独孤九贱 于 2007-1-27 21:44 编辑 ]

论坛徽章:
0
发表于 2007-01-27 21:32 |显示全部楼层
ip_nat_out

  1. static unsigned int
  2. ip_nat_out(unsigned int hooknum,
  3.            struct sk_buff **pskb,
  4.            const struct net_device *in,
  5.            const struct net_device *out,
  6.            int (*okfn)(struct sk_buff *))
  7. {
  8.         /* root is playing with raw sockets. */
  9.         if ((*pskb)->len < sizeof(struct iphdr)
  10.             || (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr))
  11.                 return NF_ACCEPT;

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

  14.                 if (!*pskb)
  15.                         return NF_STOLEN;
  16.         }

  17.         return ip_nat_fn(hooknum, pskb, in, out, okfn);
  18. }
复制代码


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

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

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

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

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

  32.                         hp = skb_header_pointer(*pskb,
  33.                                                 (*pskb)->nh.iph->ihl*4,
  34.                                                 sizeof(_hdr), &_hdr);
  35.                         if (hp != NULL &&
  36.                             hp->type == ICMP_REDIRECT)
  37.                                 return NF_DROP;
  38.                 }
  39.                 return NF_ACCEPT;
  40.         }

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

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

  58.                         /* LOCAL_IN hook doesn't have a chain!  */
  59.                         if (hooknum == NF_IP_LOCAL_IN)
  60.                                 ret = alloc_null_binding(ct, info, hooknum);
  61.                         else
  62.                                 ret = ip_nat_rule_find(pskb, hooknum,
  63.                                                        in, out, ct,
  64.                                                        info);

  65.                         if (ret != NF_ACCEPT) {
  66.                                 return ret;
  67.                         }
  68.                 } else
  69.                         DEBUGP("Already setup manip %s for ct %p\n",
  70.                                maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST",
  71.                                ct);
  72.                 break;

  73.         default:
  74.                 /* ESTABLISHED */
  75.                 IP_NF_ASSERT(ctinfo == IP_CT_ESTABLISHED
  76.                              || ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY));
  77.                 info = &ct->nat.info;
  78.         }

  79.         IP_NF_ASSERT(info);
  80.         return nat_packet(ct, ctinfo, hooknum, pskb);
  81. }
复制代码


我们假设这是一个刚刚进入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函数。
  1. int ip_nat_rule_find(struct sk_buff **pskb,
  2.                      unsigned int hooknum,
  3.                      const struct net_device *in,
  4.                      const struct net_device *out,
  5.                      struct ip_conntrack *ct,
  6.                      struct ip_nat_info *info)
  7. {
  8.         int ret;

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

  11.         if (ret == NF_ACCEPT) {
  12.                 if (!ip_nat_initialized(ct, HOOK2MANIP(hooknum)))
  13.                         /* NUL mapping */
  14.                         ret = alloc_null_binding(ct, info, hooknum);
  15.         }
  16.         return ret;
  17. }
复制代码


前面我们已经讨论过,SNAT注册的的target函数是ipt_snat_target,这个函数,首先取得规则中“转换后地址”的信息,然后将工作交给ip_nat_setup_info进一步处理:
  1. /* Source NAT */
  2. static unsigned int ipt_snat_target(struct sk_buff **pskb,
  3.                                     const struct net_device *in,
  4.                                     const struct net_device *out,
  5.                                     unsigned int hooknum,
  6.                                     const void *targinfo,
  7.                                     void *userinfo)
  8. {
  9.         struct ip_conntrack *ct;
  10.         enum ip_conntrack_info ctinfo;
  11.         /*取得规则中的target部份*/
  12.         const struct ip_nat_multi_range_compat *mr = targinfo;

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

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

  21.         return ip_nat_setup_info(ct, &mr->range[0], hooknum);
  22. }
复制代码


ip_nat_setup_info

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

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

  10.         IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING
  11.                      || hooknum == NF_IP_POST_ROUTING
  12.                      || hooknum == NF_IP_LOCAL_IN
  13.                      || hooknum == NF_IP_LOCAL_OUT);
  14.         BUG_ON(ip_nat_initialized(conntrack, maniptype));

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

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

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

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

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

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

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

  48.         return NF_ACCEPT;
  49. }
复制代码


这个函数转换来转换去,让人头大,慢慢抽丝拨茧先。首先调用invert_tuplepr取得一个当前数据包对应的replay tuple,然后对其取反得到一个curr_tuple,接着调用get_unique_tuple函数:
  1. static void
  2. get_unique_tuple(struct ip_conntrack_tuple *tuple,
  3.                  const struct ip_conntrack_tuple *orig_tuple,
  4.                  const struct ip_nat_range *range,
  5.                  struct ip_conntrack *conntrack,
  6.                  enum ip_nat_manip_type maniptype)
  7. {
  8.         struct ip_nat_protocol *proto
  9.                 = ip_nat_find_proto(orig_tuple->dst.protonum);

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

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

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

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

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

  34.         /* Last change: get protocol to try to obtain unique tuple. */
  35.         proto->unique_tuple(tuple, range, maniptype, conntrack);
  36. }
复制代码

论坛徽章:
0
发表于 2007-01-27 21:44 |显示全部楼层
第一个值得关注的是find_appropriate_src函数,它主要是在一个以bysource为首的链表中进行遍历查找,这是做什么?我们初始化NAT的时候,对bysource进行过处始化,所以,到目前为止,这个链表还是空的,我们暂时跳过对这个函数的讨论,函数接下来执行:

  1. /*将curr_tuple赋值给的new_tuple*/
  2. *tuple = *orig_tuple;
  3.         find_best_ips_proto(tuple, range, conntrack, maniptype);
复制代码


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

  1. static void
  2. find_best_ips_proto(struct ip_conntrack_tuple *tuple,
  3.                     const struct ip_nat_range *range,
  4.                     const struct ip_conntrack *conntrack,
  5.                     enum ip_nat_manip_type maniptype)
  6. {
  7.         u_int32_t *var_ipp;
  8.         /* Host order */
  9.         u_int32_t minip, maxip, j;

  10.         if (!(range->flags & IP_NAT_RANGE_MAP_IPS))
  11.                 return;
  12.        
  13.         if (maniptype == IP_NAT_MANIP_SRC)
  14.                 var_ipp = &tuple->src.ip;
  15.         else
  16.                 var_ipp = &tuple->dst.ip;

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

  22.         minip = ntohl(range->min_ip);
  23.         maxip = ntohl(range->max_ip);
  24.         j = jhash_2words(tuple->src.ip, tuple->dst.ip, 0);
  25.         *var_ipp = htonl(minip + j % (maxip - minip + 1));
  26. }
复制代码


如果是源地址转换,var_ipp指针指向tuple中的源地址:
  1. var_ipp = &tuple->src.ip;
复制代码

然后,用range中的转换后地址替换它:
  1.         if (range->min_ip == range->max_ip) {
  2.                 *var_ipp = range->min_ip;
  3.                 return;
  4.         }
复制代码


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

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

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

  7.                 /* 设置状态标志位 */
  8.                 if (maniptype == IP_NAT_MANIP_SRC)
  9.                         conntrack->status |= IPS_SRC_NAT;
  10.                 else
  11.                         conntrack->status |= IPS_DST_NAT;
  12.         }
复制代码


我们忽略了太多的细节,如指定协议后的端口转换、转换后地址为地址池的情况等等,但是,这并不影响我们了解整个NAT的全过程,还是继续我们的例子:一个192.186.0.1至100.100.100.100的WEB访问,被修改为100.100.100.1的连接跟随表被修改的流程如下图所示:

在修改了数据包对应的连接跟踪表后,函数将返回至ip_nat_fn中,在函数的最后一句,调用了nat_packet修改数据包的来源地址:
  1. /* Do packet manipulations according to ip_nat_setup_info. */
  2. unsigned int nat_packet(struct ip_conntrack *ct,
  3.                         enum ip_conntrack_info ctinfo,
  4.                         unsigned int hooknum,
  5.                         struct sk_buff **pskb)
  6. {
  7.         enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
  8.         unsigned long statusbit;
  9.         enum ip_nat_manip_type mtype = HOOK2MANIP(hooknum);
  10.         /*根据转换类型,设置状态标位位*/
  11.         if (mtype == IP_NAT_MANIP_SRC)
  12.                 statusbit = IPS_SRC_NAT;
  13.         else
  14.                 statusbit = IPS_DST_NAT;

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

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

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


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

  1. static int
  2. manip_pkt(u_int16_t proto,
  3.           struct sk_buff **pskb,
  4.           unsigned int iphdroff,
  5.           const struct ip_conntrack_tuple *target,
  6.           enum ip_nat_manip_type maniptype)
  7. {
  8.         struct iphdr *iph;
  9.        
  10.         /*修改数据包,置相应标志位*/
  11.         (*pskb)->nfcache |= NFC_ALTERED;
  12.         if (!skb_ip_make_writable(pskb, iphdroff + sizeof(*iph)))
  13.                 return 0;
  14.         /*取得IP首部*/
  15.         iph = (void *)(*pskb)->data + iphdroff;

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

  20.         iph = (void *)(*pskb)->data + iphdroff;
  21.        
  22.         /*源地址转换,修改IP包中的来源地址*/
  23.         if (maniptype == IP_NAT_MANIP_SRC) {
  24.                 iph->check = ip_nat_cheat_check(~iph->saddr, target->src.ip,
  25.                                                 iph->check);
  26.                 iph->saddr = target->src.ip;
  27.         } else {
  28.                 iph->check = ip_nat_cheat_check(~iph->daddr, target->dst.ip,
  29.                                                 iph->check);
  30.                 iph->daddr = target->dst.ip;
  31.         }
  32.         return 1;
  33. }
复制代码


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

应答的包

对于应答的数据包,同样会进入ip_nat_fn函数,当判断了该数据包的连接状态,除了ICMP协议的应答需要特需处理外,数据包同样会进入nat_packet函数:
  1. switch (ctinfo) {
  2. ……
  3. }
  4. ……
  5. return nat_packet(ct, ctinfo, hooknum, pskb);
复制代码


函数取得对应连接跟踪表的tuple,因为连接表中有转换前的地址信息,所以这里取反,用取反的tuple中的目的地址(即原来的来源地址)修改数据包。这样,整个NAT就建立起来了:
  1. /* Do packet manipulations according to ip_nat_setup_info. */
  2. unsigned int nat_packet(struct ip_conntrack *ct,
  3.                         enum ip_conntrack_info ctinfo,
  4.                         unsigned int hooknum,
  5.                         struct sk_buff **pskb)
  6. {
  7.         enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
  8.         unsigned long statusbit;
  9.         enum ip_nat_manip_type mtype = HOOK2MANIP(hooknum);

  10.         if (mtype == IP_NAT_MANIP_SRC)
  11.                 statusbit = IPS_SRC_NAT;
  12.         else
  13.                 statusbit = IPS_DST_NAT;

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

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

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

  22.                 if (!manip_pkt(target.dst.protonum, pskb, 0, &target, mtype))
  23.                         return NF_DROP;
  24.         }
  25.         return NF_ACCEPT;
  26. }
复制代码


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

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

九贱将在下一篇文章,来分析地址转换的这些高级内容。

[ 本帖最后由 独孤九贱 于 2007-1-27 21:57 编辑 ]

论坛徽章:
0
发表于 2007-01-27 23:52 |显示全部楼层
谢谢九贱了,呵呵,最近内功修炼的不错嘛^_^

论坛徽章:
0
发表于 2007-01-29 09:47 |显示全部楼层
好!!!!
多谢九贱兄啦

论坛徽章:
0
发表于 2007-01-29 15:38 |显示全部楼层
好!!!!
顶,九贱!

论坛徽章:
0
发表于 2007-01-29 16:15 |显示全部楼层
好文一定要顶
收藏,有时间细细看一下

论坛徽章:
0
发表于 2007-04-27 13:36 |显示全部楼层
不错,谢谢! 共享!!

论坛徽章:
0
发表于 2008-01-23 15:57 |显示全部楼层
有拜读了你的一篇大作,感觉非常不错,继续关注你其它的帖子!!!!!!!!!!

论坛徽章:
0
发表于 2008-02-23 11:50 |显示全部楼层
深入浅出, 简单明了!
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP