免费注册 查看新帖 |

Chinaunix

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

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

论坛徽章:
0
跳转到指定楼层
1 [收藏(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 编辑 ]

论坛徽章:
8
羊年新春福章
日期:2015-03-19 02:03:312015亚冠之北京国安
日期:2015-06-16 22:04:45程序设计版块每日发帖之星
日期:2015-06-23 22:20:00每日论坛发贴之星
日期:2015-06-23 22:20:002015亚冠之首尔
日期:2015-06-24 19:18:072015亚冠之广州恒大
日期:2015-08-06 10:29:442015亚冠之柏太阳神
日期:2015-11-02 11:21:0515-16赛季CBA联赛之辽宁
日期:2015-12-09 15:05:02
22 [报告]
发表于 2014-10-12 00:09 |只看该作者
有一个地方没看懂? 对于回来的报文在ipt_nat_packet里面
ct->status 应该被设置为IPS_DST_NAT;
这个是在哪里设置的呢?

论坛徽章:
8
羊年新春福章
日期:2015-03-19 02:03:312015亚冠之北京国安
日期:2015-06-16 22:04:45程序设计版块每日发帖之星
日期:2015-06-23 22:20:00每日论坛发贴之星
日期:2015-06-23 22:20:002015亚冠之首尔
日期:2015-06-24 19:18:072015亚冠之广州恒大
日期:2015-08-06 10:29:442015亚冠之柏太阳神
日期:2015-11-02 11:21:0515-16赛季CBA联赛之辽宁
日期:2015-12-09 15:05:02
21 [报告]
发表于 2014-10-11 21:53 |只看该作者
感谢作者分享,
ip_nat_initialized 这个函数的解释不对
不是检测这个包是否被修改了, 而是这个包对应的ct被修改了.

论坛徽章:
0
20 [报告]
发表于 2012-05-07 16:20 |只看该作者
正想了解这方面的资料,非常感谢!

论坛徽章:
0
19 [报告]
发表于 2011-08-09 00:09 |只看该作者
持续学习中...

论坛徽章:
0
18 [报告]
发表于 2011-03-16 15:21 |只看该作者
好贴,正在做类似的东西,正好可以借鉴{:2_170:}

论坛徽章:
0
17 [报告]
发表于 2010-12-09 22:49 |只看该作者
绝对是大作啊,虽然这么晚才看到,必须要顶一下。

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
16 [报告]
发表于 2010-12-08 12:47 |只看该作者
根据九贱兄的分析,自己结合代码分析一下吧。这正好是自我提高的过程。

论坛徽章:
0
15 [报告]
发表于 2010-12-08 12:24 |只看该作者
在linux 2.6.36版本里看到,还在local_in和local_out链上添加了相应处理。楼主能不能讲下为啥?

论坛徽章:
0
14 [报告]
发表于 2009-05-25 20:33 |只看该作者
很强大!
  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP