绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
Netfilter 地址转换的实现(1)
2020-05-26 15:22:14

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


网络地址转换(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函数。  


文章来源CU社区:Netfilter 地址转换的实现


分享好友

分享这个小栈给你的朋友们,一起进步吧。

内核源码
创建时间:2020-05-18 13:36:55
内核源码精华帖内容汇总
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • 飘絮絮絮丶
    专家
戳我,来吐槽~