(九)洞悉linux下的Netfilter&iptables:网络地址转换原理之DNAT

网络地址转换:NAT服务器

     NetfitlerNAT在内核中维护了一张名为nat的表,用来处理全部和地址映射相关的操做。诸如filternatmangle抑或raw这些在用户空间所认为的“表”的概念,在内核中有的是以模块的形式存在,如filter;有的是以子系统方式存在的,如nat,但它们都具备“表”的性质。所以,内核在处理它们时有很大一部操做都是相同的,例如表的初始化数据、表的注册、钩子函数的注册等等。关于NAT表的初始化模板数据和表的注册流程并非本文的重点,你们能够对照第四篇博文中filter表的相关分析来研究。本文仍是侧重于从总体上对整个NAT子系统的设计思想和原理进行,固然,有时间我仍是会简单和你们分析一NAT表的东西。由于最近确实太忙了,原本想着在四月份结束这个系列,无奈一转眼就晃到了五月份,作IT的娃,都不容易啊!网络

     经过前面的几篇文章咱们已经知道,NAT的设计是独立于链接跟踪系统的,即链接跟踪是NAT的基础框架,咱们还了解到链接跟踪不会修改数据包,它只是负责维护数据包和其所属的业务会话或数据链接状态的相关信息而已。链接跟踪最终是被iptables模块所使用的,它所定义的那些状态信息如NEWESTABLISHEDRELEATED等等,NAT通通不用关心。框架

     根据前面的hook函数挂载图咱们能够清晰的知道,对于那些须要被本机转发的数据包,注册在NF_IP_PRE_ROUTING点的ip_nat_in ()函数完成对其目的地址转换的操做,注册在NF_IP_POST_ROUTING点的ip_nat_out()函数完成源地址转换任务。若是在编译Linux内核源码时打开了CONFIG_IP_NF_NAT_LOCAL选项,则注册在NF_IP_LOCAL_OUTNF_IP_LOCAL_IN点的hook函数就会工做,最多见的用法的是用NF_IP_LOCAL_OUT点的ip_nat_local_fn()函数来改变本机发出报文的目的地址。至于注册在NF_IP_LOCAL_IN点的ip_nat_fn()函数通常不会起做用,只是当数据包到达该HOOK点后会例行被调用一下。由于,NAT的全部规则只可能被配置到nat表的PREROUTINGPOSTROUTINGOUTPUT三个点上,通常也不多有人去修改那些路由给本机的报文的源地址。函数

         NAT 的分类以下图所示:

    相信你们在看iptables用户指南都见过这么一句解释:this

    只有每条链接的第一个数据包才会通过nat表,而属于该链接的后续数据包会按照第一个数据包则会按照第一个报所执行的动做进行处理,再也不通过nat表。Netfilter为何要作这个限制?有什么好处?它又是如何实现的?咱们在接下来的分析中,将一一和你们探讨这些问题。atom

    ip_nat_rule.c文件中定义了nat表的初始化数据模板nat_table,及相应的target实体:SNATDNAT,并将其挂在到全局xt[PF_INET].target链表中。关于NAT所注册的几个hook函数,其调用关系咱们在前几篇博文中也见过:spa

所以,咱们的核心就集中在ip_nat_in()上。也就是说,当咱们弄明白了ip_nat_fn()函数,你就差很少已经掌握了nat的精髓。ip_nat_in()函数定义定在ip_nat_standalone.c文件里。链接跟踪做为NAT的基础,而创建在链接跟踪基础上的状态防火墙一样服务于NAT系统。设计

    关于 ip_nat_fn() 函数 咱们仍是先梳理总体流程,以便你们对其有一个宏观总体的把握,而后咱们再来分析其实现细节。这里须要你们对链接跟踪的状态跃迁有必定了了解。

    从流程图能够看出,牵扯到的几个关键函数都土黄色标注出来了。ip_nat_setup_info()函数主要是完成对数据包的链接跟踪记录ip_conntrack对象中相应成员的修改和替换,而manip_pkt()中才是真正对skb里面源/目的地址,端口以及数据包的校验和字段修改的地方。代理


目的地址转换:DNATorm

         DNAT 主要适用于将内部私有地址的服务发布到公网的情形。情形以下:

    服务器上架设了Web服务,其私有地址是B,代理防火墙服务器有一个公网地址A。想在须要经过A来访问B上的Web服务,此时就须要DNAT出马才行。根据前面的流程图,咱们立刻杀入内核。

client经过Internet访问公网地址A时,经过配置在防火墙上的DNAT将其映射到了对于私网内服务器B的访问。接下来咱们就分析一下当在clientserver的交互过程当中架设在防火墙上NAT是如何工做。

仍是看一下hook函数在内核中的挂载分布图。

 

    在 PREROUTING 点当一个 skb 被链接跟踪事后,那么 skb->ctinfo skb->nfct 两个字段均被设置了值。在接下来的分析中,对那些梢枝末节的代码咱们都将不予理睬。盗个图:

这里牵扯到一个变量ip_conntrack_untracked,以前咱们见过,可是还没讨论过。该变量定义在ip_conntrack_core.c文件里,并在ip_conntrack_init()函数进行部分初始化:

atomic_set(&ip_conntrack_untracked.ct_general.use, 1);

set_bit(IPS_CONFIRMED_BIT, &ip_conntrack_untracked.status);

同时,在ip_nat_core.c文件里的ip_nat_init()函数中又有以下设置:

ip_conntrack_untracked.status |= IPS_NAT_DONE_MASK;

该变量又是何意呢?咱们知道iptables维护的实际上是四张表,有一张raw不是很经常使用。该表以-300的优先级在PREROUTINGLOCAL_OUT点注册了ipt_hook函数,其优先级要高于链接跟踪。当每一个数据包到达raw表时skb->nfct字段缺省都被设置成了ip_conntrack_untracked,因此当该skb还没被链接踪的话,其skb->nfct就一直是ip_conntrack_untracked。对于没有被链接跟踪处理过的skb是不能进行NAT的,所以遇到这种状况代码中直接返回ACCEPT

从上面的流程图能够看出,不管是alloc_null_binding_confirmed()alloc_null_binding()仍是ip_nat_rule_find()函数其本质上最终都调用了ip_nat_setup_info()函数。

 

ip_nat_setup_info()函数:

该函数中主要完成了对链接跟踪记录ip_conntrack.status字段的设置,同时根据可能配置在nat表中的DNAT规则对链接跟踪记录里的响应tuple进行修改,最后将该ip_conntrack实例挂载到全局双向链表bysource里。

    在链接跟踪系统里根据 skb 的源 / 目的 IP 分别已经构建生成初始 tuple 和响应 tuple ,咱们经过一个简单的示意图来回顾一下其流程,并加深对 ip_nat_setup_info() 函数执行过程的理解。

在图1中,根据skb的源、目的IP生成了其链接跟踪记录的初始和响应tuple

在图2中,以初始tuple为输入数据,根据DNAT规则来修改将要被改变的地址。这里是当咱们访问目的地址是A的公网地址时,DNAT规则将其改为对私网地址B的访问。而后,计算被DNAT以后的数据包新的响应。最后用新的响应tuple替换ip_conntrack实例中旧的响应tuple,由于数据包的目的地址已经被改变了,因此其响应tuple也必须跟着变。

在图3中,会根据初始tuple计算一个hash值出来,而后以ip_conntrack结构中的nat.info字段会被组织成一个双向链表,将其插入到全局链表bysource里。

最后,将ip_conntrack.status字段的IPS_DST_NATIPS_DST_NAT_DONE_BIT位均置为1

这里必须明确一点:在ip_nat_setup_info()函数中仅仅是对ip_conntrack结构实例中相关字段进行了设置,并无修改原始数据包skb里的源、目的IP或任何端口。

 

ip_nat_packet()函数的核心是调用manip_pkt()函数:

manip_pkt()里主要完成对数据包skb结构中源/目的IP和源/目的端口的修改,而且修改了IP字段的校验和。从mainip_pkt()函数中返回就回到了ip_nat_in()函数中(节选)

ret = ip_nat_fn(hooknum, pskb, in, out, okfn);

if (ret != NF_DROP && ret != NF_STOLEN&& daddr != (*pskb)->nh.iph->daddr) {

         dst_release((*pskb)->dst);

         (*pskb)->dst = NULL;

}

return ret;

    正常状况下返回值ret通常都为NF_ACCEPT,所以会执行if条件语句,清除skb原来的路由信息,而后在后面协议栈的ip_rcv_finish()函数中从新计算该数据包的路由。

在数据包即将离开NAT框架时,还有一个名为ip_nat_adjust()的函数。参见hook函数的挂载示意图。该函数主要是对那些执行了NAT的数据包的序列号进行适当的调整。若是调整出错,则丢弃该skb;不然,将skb继续向后传递,即将到达链接跟踪的出口ip_confirm()。至于,ip_confirm()函数的功能说明咱们在链接跟踪章节已经深刻讨论了,想不起来的童鞋能够回头复习一下链接跟踪的知识点。

 

前面咱们仅分析了从client发出的第一个请求报文到server服务器时,防火墙的处理工做。紧接着咱们顺着前面的思路继续分析,当server收到该数据包后回应时防火墙的处理状况。

server收到数据包时,该skb的源地址(记为X)从未变化,但目的地址被防火墙从A改为了Bserver在响应这个请求时,它发出的回应报文目的地址是X,源地址是本身的私有地址B。你们注意到这个源、目的地址恰好匹配被DNAT以后的那个响应tuple

当该回应报文到达防火墙后,首先是被链接跟踪系统处理。显而易见,在全局的链接跟踪表ip_conntrack_hash[]中确定能够找到这个tuple所属的链接跟踪记录ip_conntrack实例。关于状态的变迁参见博文八。

而后,该回应报文到达 NAT 框架的 ip_nat_in() 函数,流程和前面同样,但处理方式确定不一样。咱们仍是先看一下截止到目前为止,这条链接跟踪结构图:

直接跳到ip_nat_packet()函数里,当netlink通知机制将链接跟踪状态由NEW变为REPLY后,此时dir=1,那么根据初始tuple求出原来的响应tuple:源地址为A,目的之为X此时,server的响应报文,源地址为私有网段B,目的地址为X。路由寻址是以目的地址为依据,防火墙上有直接到client的路由,因此响应报文是能够被client正确收到的。可是,但但是,蛋炒西红柿,对于UDP来讲client收到这样的回复没有任何问题的,可是对于TCPU而言确实不行的。这就引出咱们接下来将要讨论的SNAT

    未完,待续
相关文章
相关标签/搜索