标签:
本节主要是分析NAT模块相关的hook函数与target函数,主要是理清NAT模块实现的原理等。
NAT模块主要是在NF_IP_PREROUTING、NF_IP_POSTROUTING、NF_IP_LOCAL_OUT、NF_IP_LOCAL_IN四个节点上进行NAT操作,在上一节中我们知道nat表中只有PREROUTING、POSTROUTING、LOCAL_OUT三条链,而没有NF_IP_LOCAL_IN链,所以不能创建在LOCAL_IN hook点的SNAT操作。
而NAT模块在注册hook函数时又在LOCAL_IN点注册了hook函数,且hook函数也调用了NAT转换的通用处理函数,难道也要对LOCAL_IN的数据包进行NAT转换吗?
其实,在LOCAL_IN注册hook函数主要不是为了进行NAT转换,因为在系统为一个源ip为A的转发数据包进行了SNAT后,可能会对源端口获取一个随机的值,这时如果源ip为A的数据包要发送给网关时,可能源端口就是刚才NAT转换的那个源端口,此时为了保证连接跟踪项的原始方向的tuple变量的唯一性,就需要在LOCAL_IN的hook点通过调用NAT转换的通用处理函数,改变源端口值,重新获取一个新的唯一的且未被使用的tuple变量。这应该就是LOCAL_IN也需要hook回调函数的原因吧。
这个函数是NAT模块在PRE_ROUTING hook点上注册的回调函数,该函数主要是实现DNAT功能,该函数的定义如下,主要实现如下两个功能:
1. 调用函数ip_nat_fn实现DNAT转换
2.当转换后数据包的目的ip地址改变后,需要调用dst_release,将skb对dst_entry的引用减一,然后将skb->dst置为NULL
该函数主要是通过调用nf_nat_fn,该函数是一个通用NAT转换函数,待会着重分析这个函数
这个函数是NAT模块在POST_ROUTING hook点的hook回调函数,该函数实现如下功能:
1. 调用函数ip_nat_fn实现SNAT转换
这个函数同样是调用函数nf_nat_fn实现SNAT转换
这个函数是NAT模块在OUTPUT hook点的hook回调函数,该函数实现如下功能:
功能:实现DNAT转换功能
1. 调用函数ip_nat_fn实现DNAT转换
2.调用ip_route_me_harder,重新进行路由操作(与PRE_ROUTING不同的是,对于 OUTPUT的hook回调函数,当目的地址改变后,需要在该函数里调用ip_route_me_harder重新查找路由,而在PRE_ROUTING链中,则是将skb->dst置为空, 然后在数据包往下执行时会自行重新查找路由。OUTPUT链接收的数据均是已经路由 的数据包,且后续调用函数中不会再有查找路由的操作,所以要nf_nat_out里实现路由 查找。)。
这个函数其实也是调用函数nf_nat_fn实现NAT转换的。
NAT模块在NF_LOCAL_IN的hook回调函数就是直接调用nf_nat_fn,此处需要注意以下信息:
对于NF_LOCAL_IN链来说,因为nat表中并没有INPUT链,所以对于NF_LOCAL_IN点来说,并不会修改数据包的ip地址,也就是调用alloc_null_binding实现NAT转换,最大的可能就是修改数据包的源端口号,以实现数据连接跟踪项的reply的nf_conntrack_tuple变量是唯一的,且没有被其他连接跟踪项使用。
这也就是为什么需要在NF_LOCAL_IN HOOK点注册HOOK回调函数而又没有在nat表中注册INPUT链的原因。
对于通用NAT转换函数,最主要的就是函数nf_nat_fn,而nf_nat_fn的实现中涉及了许多的函数,此处我们依依分析之。
该函数主要功能就是实现数据的NAT操作(包括SNAT与DNAT),具体来说,就是对一个数据流对应的连接跟踪项仅执行一次SNAT、DNAT,而当数据流对应的连接跟踪项的NAT操作执行完成以后,对于后续的数据包,则直接根据连接跟踪项的reply方向的nf_conntrac_tuple变量的值进行NAT转换,然后将数据再交给协议栈处理。
下面分析这个函数:
功能:实现NAT功能(包括SNAT/DNAT功能)
1.首先判断数据包是否符合要求(必须不是分段的),数据包对应的连接跟踪项是否符合转换要求等
2.对于期望连接来说,对于icmp报文,需要对报文进行NAT转换
3.只对new状态的且未进行NAT转换的连接跟踪项,且不是NF_LOCAL_IN hook点时,调用nf_nat_rule_find进行连接跟踪
项的NAT转换
4.进行了上述3的操作后,则会调用nf_nat_packet对数据包进行NAT转换操作。
连接跟踪项的NAT转换只会发生在连接跟踪项刚被创建且还没有进行confirm时,且每个NAT类型只会执行一次NAT转换。
这个函数主要涉及了函数nf_nat_initialized、alloc_null_binding_confirmed、alloc_null_binding、nf_nat_rule_find、nf_nat_packet,下面我们开始分析这些函数。
这个函数主要是判断传递的连接跟踪项,有没有进行过manip类型的NAT转换。
若manip的值为IP_NAT_MANIP_SRC,则判断连接跟踪项的status的
IPS_SRC_NAT_DONE_BIT位是否为1,若为1,则说明该连接跟踪项已经进行了SNAT转换,不需要再次转换;
对于DNAT的判断与上述SNAT的判断类似,根据这个函数,就可以避免多次对一个连接跟踪项进行SNAT或者DNAT操作。
这个是针对连接跟踪项已经确认,但是其NAT操作还没有进行的情况。
按照我们的逻辑来说,对连接跟踪项的NAT操作是在连接跟踪项创建之后,且连接跟踪项被确认之前的。
那怎么会出现连接跟踪项已经确认,但是NAT转换还没有进行的呢?
当连接跟踪模块已经加载并且已经工作一段时间后,才加载NAT模块,就会导致这种情况出现。
那既然NAT模块是后面加载的,那还有必要对先前已经确认的连接跟踪项进行NAT转换吗?
是这样的,对于先前已经确认的连接跟踪项,虽然已经确认了,但是由于NAT模块没有加载,使其没有添加到by_source[]数组相对应的链表上,而NAT模块在对连接跟踪项进行转换时,是通过将转换后的nf_conntrack_tuple变量,与连接跟踪项上所有的nf_conntrack_tuple变量进行对比来确保转换后的连接跟踪项的唯一性。基于这个原理,如果不把先前已经确认的连接跟踪项通过NAT转换并添加到by_source[]链表上的话,则可能出现已经转换后的连接跟踪项的tuple变量与先前已经确认的连接跟踪项冲突,所以需要将先前已经的确认的连接跟踪项也进行NAT操作,不过这次NAT转换不会修改ip地址,最大的可能就是对源或目的端口进行微调。
这个函数还是比较简单的,跟踪hook的类型,确认NAT转换的类型,然后就将range中的ip地址就设置reply方向的nf_conntrack_tuple的对应的ip地址(从这也能看出,没有修改ip地址)。 然后就是调用函数nf_nat_setup_info实现对连接跟踪项的NAT转换操作。
函数nf_nat_setup_info就是最终实现NAT转换的最最重要的函数,后面的alloc_null_binding、 masquerade_target,、ipt_snat_target、ipt_dnat_target最终都是调用这个函数实现连接跟踪项的NAT转换,有必要单独拿出来讲这个函数。
在函数nf_nat_fn里,只有对于LOCAL_IN 的hook点上,才会调用该函数进行NAT转换的,因为nat表没有LOCAL_IN链,所以在LOCAL_IN链肯定不会匹配NAT转换规则的。但是内核绝不会把一个没用的代码放在那里一直不改的,对于LOCAL_IN 的hook点,虽然该数据流没有进行nat,但是存在其他三层ip相同数据流进行nat时,将该不需NAT数据流的四层端口号给占用的情况,这就好导致数据连接跟踪项的冲突。为了解决这个问题,就需要调用nf_nat_setup_info为当前不需NAT数据流找到一个唯一的tuple变量(新的唯一tuple变量的值有两种:原来的tuple变量即是唯一的;修改原来tuple变量的四层协议相关的关键字, 得到一个新的唯一的tuple变量。),并将该连接跟踪项添加到by_source[]相对应的链表上,这样在其他数据连接跟踪项进行转换时,就会先将转换后的nf_conntrac_tuple变量与连接跟踪项的确认链表中的值进行比较,在没有冲突的情况下再进行NAT转换。
这个函数的执行流程与alloc_null_binding_confirmed类似,且最终也是调用nf_nat_setup_info进行连接跟踪项的转换。
当连接跟踪项不是以上1.5.1.2、1.5.1.3这两个类型,且是NEW、RELATED、RELATED+REPLY状态的连接跟踪项,则会调用函数nf_nat_rule_find,遍历nat表中对应的规则链:
若找到nat规则,则调用相应的target函数(ipt_snat_target或者ipt_dnat_target)实现连接跟踪项的转换;
若没找到nat规则,则调用 alloc_null_binding确保连接跟踪项的reply方向的nf_conntrack_tuple变量的唯一性,并添加到by_source[]相应的链表中,实现的功能与1.5.1.2、1.5.1.3中介绍的大致类似。
功能:实现对数据包关联的连接跟踪项的NAT转换操作。
1.调用ipt_do_table,查找nat表中有没有匹配该连接跟踪项的nat规则,若有则根据NAT类型调用相应的target实现对连接跟踪项的NAT操作(SNAT target 、DNAT target),且将该连接跟踪项的status值中设置已进行NAT转换标志(关于ipt_do_table函数的分析,请参考如下文章Linux netfilter 学习笔记 之五 ip层netfilter的table中规则的匹配检查)。
2.在调用完ipt_do_table后,该连接跟踪项还没有进行NAT转换,则调用alloc_null_binding进行NAT转换。alloc_null_binding并不会修改连接跟踪项的reply方向的tuple变量的三层ip地址,只有在该连接跟踪项使用的tuple变量值不唯一时,则更新连接跟踪项的reply方向的tuple变量的四层协议相关的关键字(也就是端口号之类的)即可。
在nf_nat_fn的最后,会调用函数nf_nat_packet对数据包进行nat转换。
当一个连接跟踪项已经被NAT转换后,后续的数据包则直接进入函数nf_nat_packet,对数据包中的ip地址、端口等进行NAT转换操作。
当一个连接跟踪项刚被被NAT转换后,则其第一个数据包也要接进入函数nf_nat_packet,对数据包中的ip地址、端口等进行NAT转换操作。
/* Do packet manipulations according to nf_nat_setup_info. */
/*
功能:实现数据包的NAT操作:
当为SNAT操作,且是reply方向的PREROUTING时,经过下面的异或后同样可以调用manip_pkt,而因为此时为DNAT,因此就实现了De-SNAT;
当为DNAT操作,且是reply方向的PREROUTING时,经过下面的异或后同样可以调用manip_pkt,而因为此时为SNAT因此就实现了De-DNAT;
当为SNAT操作,且是original方向的POSTROUTING时,则调用manip_pkt执行SNAT操作;
当为DNAT操作,且是original方向的PREROUTING/OUTPUT时,则调用manip_pkt执行DNAT操作。
1.根据hook点设置statusbit的值
2.对于reply方向,需要执行异或操作
3.当连接跟踪项的status变量与statusbit进行位与的结果不为0时:
调用函数manip_pkt根据NAT类型修改数据包的ip地址。
以上把函数nf_nat_fn相关的函数都分析了,现在就分析最最重要的NAT转换函数nf_nat_setup_info
这个函数会对4个hook点进来的连接跟踪项进行NAT转换,所以这个函数至此SNAT、DNAT转换,根据HOOK点的类型能够决定转换的类型。该函数最精髓的地方就是调用函数get_unique_tuple,获取一个唯一的且未被其他已经进行NAT转换的连接跟踪项使用的nf_conntrack_tuple变量。当转换成功后,置标记位。
该函数执行的步骤如下:
1.判断传入的hook点是否是NAT相关的hook点,NAT只在PRE_ROUTING、POST_ROUTING、LOCAL_OUT、LOCAL_IN这四个hook点起作用
2.若此时连接跟踪项的status变量中的 IPS_SRC_NAT_DONE_BIT或者IPS_DST_NAT_DONE_BIT位已经被置位了,则打印bug信息,并调用kernel panic
3.根据reply方向的nf_conntrack_tuple结构的变量,获取其反方向的nf_conntrack_tuple结构的变量
4. 调用get_unique_tuple,根据传递的tuple变量,获取一个新的且经过NAT转换的tuple变量,其方向依然是原始方向
5.当新的tuple变量的值与当前的原始方向的tuple变量的值不相等时,进行NAT转换(因为只有在两个值不同时才需要NAT操作):
a)对传递过来的新的tuple变量的值,调用get_unique_tuple,获取该tuple变量反方向的 tuple变量值,即新的reply方向的值
b)调用nf_conntrack_alter_reply将连接跟踪项的reply方向的tuplehash[IP_CT_DIR_REPLY].tuple替换为a)中得到的reply方向的tuple变量,当连接跟踪项不是期望连接项,且还没有创建期望连接时,对根据新的reply反向的tuple变量,在helpers链表中查找新的符合要求的helper变量,并替换调用连接跟踪项中的原来的nf_conntrack_helper变量
c)根据连接跟踪项的NAT类型,设置连接跟踪项的status中相应位(IPS_SRC_NAT/IPS_DST_NAT)
6.若连接跟踪项的当前status变量的IPS_DST_NAT_DONE 与 IPS_SRC_NAT_DONE位均没有置位,则需要将经过NAT操作后的连接跟踪项添加到bysource[]相应的链表中去(调用hash_by_src根据传入的原始方向的tuple变量计算hash值,根据该hash值获取相应的链表bysourece[hash])
7. 根据NAT类型,将连接跟踪项的status变量的IPS_DST_NAT_DONE或者IPS_SRC_NAT_DONE位置位。
在函数的最后有个置位IPS_DST_NAT_DONE_BIT、IPS_SRC_NAT_DONE_BIT的操作,这就是为了保证一个数据连接跟踪项
在某一个NAT转换类型(SNAT、DNAT)上只能初始化一次。
nf_ct_invert_tuplepr主要是根据输入的nf_conntrack_tuple变量,获取其反方向的nf_conntrack_tuple变量。
该函数根据传递的orig_tuple与range变量,得到一个新的tuple,此tuple的ip地址或者端口号已经进行了NAT转换。
函数的执行步骤如下:
1.当为SNAT,且通过find_appropriate_src在bysource[]相应链表上得到了一个符合要求的原始tuple变量,且在连接跟踪项的确认链表中,没有与该tuple变量相关的连接跟踪项,则可以使用该tuple变量作为SNAT的依据。
2.进入此步,则说明需要对传入的连接跟踪项进行NAT,则调用find_best_ips_proto设置传入tuple的源或者目的ip地址
3.调用__nf_nat_proto_find查看在nf_nat_protos数组中有没有注册与连接跟踪项的四层协议相关的 nf_nat_protocol变量
4.当range支持IP_NAT_RANGE_PROTO_RANDOM时,则需要调用四层协议nf_nat_protocol类型变量的unique_tuple,随机选择一个新的四层协议号
5.当range不支持IP_NAT_RANGE_PROTO_SPECIFIED,则不需要修改四层协议相关的端口号或者其他信息,程序返回
6.当range支持IP_NAT_RANGE_PROTO_SPECIFIED,且发现tuple的四层协议相关的端口号或者其他信息已经在range变量对应的
四层相关值的范围之内,且该tuple的相反tuple与连接跟踪项的reply方向的tuple变量不等,则无需再进行 四层协议相关的端口号或者其他值的转换,程序返回
7.若以上5与6均不满足,则调用四层协议nf_nat_protocol类型变量的unique_tuple,对tuple的四层协议相关的端口号等信息进行NAT转换
*/
这个函数里有两个重要函数find_appropriate_src与nf_nat_used_tuple
这个函数只会被SNAT转换调用。此处是在已进行NAT的bysource[]相应的链表中,查找是否有原始tuple值的src ip、src port、l4proto的值与给定的tuple的src ip、src port、l4proto的值相等的连接跟踪项:
若有,则说明我们大概可以可以用这个已经经过SNAT的连接跟踪项的reply方向的tuple值的取反后的tuple变量作为SNAT的依据,且将该tuple的dst替换成传入tuple变量的dst值。
功能:给定一个tuple变量,判断在已进行SNAT转换,且其nf_conn_nat变量已经在bysource[]的连接跟踪项,是否存在连接跟踪项的原始方向tuple 是否与传入tuple相等:
若有,则说明找到了一个合适的经NAT后的原始tuple项,并存放在result中,程序返回
1.通过hash_by_src获取原始方向tuple的hash值为h(此处为假设)
2. 在bysource[h]链表中查找已进行NAT操作的
same_src的功能是判断传入tuple的源ip及源端口号是否与传入连接跟踪项的原始方向的tuple变量的源ip及源端口号相等。此处只是对原始方向的tuple变量的原始方向的ip地址、原始方向的端口号等以及四层协议号相等,即认为相同,而不管目的ip地址与目的端口号是否相等。
in_range的作用是判断一个tuple中的源ip地址是否在range范围内。
1. 若nf_nat_range结构 的range变量,支持ip地址的NAT取值范围功能,若tuple中的源地址小于range变量的最小ip地址,或者大于range变量的最大ip地址,则返回0,说明tuple没有在range范围内。
2.进入此步骤后,则说明tuple的源ip地址已经在range范围内了,此时判断range是否增加了协议识别标签:
若没有添加协议识别标签,则返回tuple值满足range的要求,返回1;
若有添加协议识别标签,且调用四层协议的in_range函数后,也返回1,则说明tuple满足range要求,程序返回1;
若不满足以上两种case中的一个,则返回0.
*/
该函数主要是判断已传入tuple取反获取到的reply tuple变量,是否已经被其他连接跟踪项使用了:
若已经被其他连接跟踪项使用了,则返回TRUE;
若没有被其他连接跟踪项使用,则返回FALSE
主要是通过函数 nf_conntrack_tuple_taken实现的。
nf_conntrack_tuple_taken是判断连接跟踪项的确认链表中,是否存在连接跟踪项不等于ignored_conntrack,且连接跟踪项的某个tuple值与传入值相等,若存在,则返回TRUE,则不能用传入的tuple变量作为NAT的依据;若不存在,返回FALSE,说明可以用传入的tuple变量作为NAT的依据
而函数__nf_conntrack_find才是最终的函数,该函数查找连接跟踪的确认链表中,是否存在连接跟踪项不等于ignored_conntrack,且连接跟踪项的某一个tuple变量与传入的tuple变量相等的情况,若有则说明传入的tuple变量不能作为NAT转换的依据。
至此分析完了函数get_unique_tuple,这个函数真的很重要,考虑到了多种case。
1.5.2.2 nf_conntrack_alter_reply
当获取了一个唯一的nf_conntrack_tuple变量后,就可以调用该函数修改连接跟踪项的reply方向的nf_conntrack_tuple变量了。
功能:根据传递的nf_conntrack_tuple变量,修改ct的reply方向的nf_conntrack_tuple值以及helper的值
1.修改输入连接跟踪项的reply方向的nf_conntrack_tuple变量值
2.根据新的reply方向的nf_conntrack_tuple变量,修改连接跟踪项项的helper变量
因为我们知道在创建连接跟踪项时,就是根据reply方向的nf_conntrack_tuple变量,在helpers链表中查找的helper变量;当reply方向的nf_conntrack_tuple变量修改后,则肯定需要再次查找helpers,用以找到新的符合条件的helper变量。因此必须在NAT转换完成以后,才能调用连接跟踪模块的helpe相关的回调函数。
这也是为什么SNAT的HOOK回调函数的优先级高于连接跟踪项的ipv4_conntrack_help hook回调函数的原因。
至此将nf_nat_setup_info分析完了。下面看下target的定义,其实都差不多,都是调用函数nf_nat_setup_info实现的,还是简单分析一下。
功能:实现SNAT功能
1.调用nf_ct_get,获取传入数据包关联的nf_conn变量
2.此处进行SNAT只是设置连接跟踪项中的reply方向的nf_conntrack_tuple变量,因此:
对于主连接,仅设置连接跟踪项的状态为NEW的SNAT操作,因为对于状态不为NEW的连接跟踪项,其reply方向的nf_conntrack_tuple结构的变量的目的地址和端口号已经修改过了,不需要再次修改了;
对于期望连接来说,当期望连接刚建立时,其状态仅为IP_CT_RELATED或者IP_CT_RELATED+IP_CT_IS_REPLY,所以
也只对这两种情况的期望连接,进行SNAT操作。
3.调用nf_nat_setup_info,根据targinfo中的地址范围与端口值修改连接跟踪项的reply方向的nf_conntrack_tuple
变量中的值。
执行这个target只是修改了数据包对应的连接跟踪项的reply方向的tuple变量,并没有修改数据包的ip地址,而修改数据包的ip地址是nat模块的hook函数中执行的(在执行了target操作后才会执行,调用函数nf_nat_packet实现)。
(疑问:为什么是期望连接时,状态为IP_CT_RELATED或者IP_CT_RELATED+IP_CT_IS_REPLY都认为是起始状态呢?
状态为IP_CT_RELATED时,认为是新创建的连接跟踪项,我是能理解的,但是IP_CT_RELATED+IP_CT_IS_REPLY也
做为新创建的连接跟踪项的依据,我没有搞懂? 而且我感觉不会出现状态为IP_CT_RELATED+IP_CT_IS_REPLY的
连接跟踪项
)
功能:实现DNAT功能
1.调用nf_ct_get,获取传入数据包关联的nf_conn变量
2.此处进行DNAT只是设置连接跟踪项中的reply方向的nf_conntrack_tuple变量,因此:
对于主连接,仅设置连接跟踪项的状态为NEW的DNAT操作,因为对于状态不为NEW的连接跟踪项,其reply方向的nf_conntrack_tuple结构的变量的目的地址和端口号已经修改过了,不需要再次修改了;
对于期望连接来说,当期望连接刚建立时,其状态仅为IP_CT_RELATED,才进行DNAT操作。
3.调用nf_nat_setup_info,根据targinfo中的地址范围与端口值修改连接跟踪项的reply方向的nf_conntrack_tuple变量中的值。
执行这个target只是修改了数据包对应的连接跟踪项的reply方向的tuple变量,并没有修改数据包的ip地址,而修改数据包的ip地址是nat模块的hook函数中执行的(在执行了target操作后才会执行)。
static struct xt_target masquerade = {
.name = "MASQUERADE",
.family = AF_INET,
.target = masquerade_target,
.targetsize = sizeof(struct ip_nat_multi_range_compat),
.table = "nat",
.hooks = 1 << NF_IP_POST_ROUTING,
.checkentry = masquerade_check,
.me = THIS_MODULE,
};
这个target也是实现SNAT转换,但是比SNAT更智能,不需要输入SNAT转换后的源ip地址,可以根据出口设备与下一跳网关ip,找到要转换到的源ip地址,然后再调用nf_nat_setup_info函数,实现连接跟踪项的SNAT转换。
与ipt_snat_target相比增加了获取要转换到的源ip地址,主要是根据出口设备与下一跳网关地址,通过调用函数inet_select_addr(关于这个函数,可参看Linux inet_select_addr分析),获取ip地址的。
至此,将NAT转换相关的所有主要的函数都分析完了。下面进行实例分析下。
对于nat相关的函数,我们都分析完了,那我们就分别以SNAT与DNAT两种情况,来分析下数据包在网关中是如何实现地址转换的。
这个就是典型的路由器工作机制,路由器的lan侧设备需要访问到互联网,而又只有路由器上的wan连接存在一个公网地址,此时lan侧pc发来的数据就需要进行SNAT转换。
lan1 pc ip:192.168.1.123
route wan ip为115.22.112.12
需要访问的外网的地址为ip 14.17.88.99
网关通过iptables做了SNAT,命令如下:
iptables -t nat -A POSTROUTING -s 192.168.1.132/32 -o wan0 -j SNAT --to-source 115.22.112.12
(iptables-t nat -A POSTROUTING -s 192.168.1.0/24 -o wan0 -j MASQUERADE真实的规则应该是这一个,因为真实的网关中,其wan侧ip可能会经常改变。此处分析SNAT时就以上面的命令为准)
当第一个lan侧数据进入到路由器的wan接口时,在 PRE_ROUTING 创建一个nf_conn和两个nf_conntrack_tuple(origin 与reply)
其中origin tuple.src=192.168.1.123 origin tuple.dst=14.17.88.99 reply tuple.src=14.17.88.99 reply tuple.dst=192.168.1.3
当查找路由成功,要转发该数据包时,进入到POST_ROUTING链时,进入到NAT的hook函数时,查看到有SNAT的规则,经过SNAT后,会将tuple里的值修改如下:
其中origin tuple.src=192.168.1.123 origin tuple.dst=14.17.88.99 reply tuple.src=14.17.88.99 reply tuple.dst=115.22.112.12
当服务器14.17.88.99回复了一个数据包后(src=14.17.88.99 dst=115.22.112.12),进入到wan侧接口的PRE_ROUTING链时,则在调用其nat相关的hook函数后,会调用函数ip_nat_packet获取到origin tuple值,然后再根据origin tuple,计算出反方向的tuple,即为new_tuple.src = 14.17.88.99 new_tuple.dst = 192.168.1.123,然后就会根据这个新的tuple修改其目的ip地址,修改后的数据包的目的地址即为192.168.1.123 。然后再查找路由,将数据发送到正常的lan口。这就是nat的De-SNAT
即路由器的lan侧设备中,有一个设备要作为server使用,这时候就需要使用dnat了。
lan1 pc ip:192.168.1.183
route wan ip为115.22.123.12(外网看到的server的ip地址)
外网的地址为ip 14.17.88.22
iptables -t nat -A PREROUTING -i wan0 -j DNAT --to-destination 192.168.1.183
当外网client发送一个到server的请求数据。(其src ip 14.17.88.22 dst 115.22.123.12)
当数据到达路由器的wan0口,进入到PRE_ROUTING时,会先建立一个nf_conn结构,和两个nf_conntrack_tuple(origin 与reply)
其中origin tuple.src=14.17.88.22 origin tuple.dst=115.22.123.12 reply tuple.src=115.22.123.12 reply tuple.dst=14.17.88.22;然后又会进入到PRE_ROUTING的hook点的nat hook中,然后调用nat hook,查找nat表的DNAT规则,刚好找到了我们上面创建的规则,接着就会修改reply tuple。将reply tuple.src=115.22.123.12 reply tuple.dst=14.17.88.22修改为reply tuple.src=192.168.1.183 reply tuple.dst=14.17.88.22,然后再根据修改后的reply tuple,取反获取到新的tuple,即new_tuple.src=14.17.88.22,new_tuple.dst=192.168.1.183,然后就会根据这个tuple值将数据包的目的地址修改为192.168.1.183,接着查找路由,将数据包发送给lan侧server。
当lan侧发送一个回应的报文时(数据包的src为192.168.1.183 dst为14.17.88.22),然后当数据进入wan0的PRE_ROUTING链时,由于查找到的nf_conn没有SNAT标志,
则会继续查找路由,然后forward这个数据包;当数据包到达POST_ROUTING时,根据nf_conn的flag置位为DNAT,且为reply方向,就会查找origin tuple,然后根据origin
tuple的值,取反得到新的tuple:new_tuple.src=115.22.123.12, new_tuple.dst=14.17.88.22,然后根据这个新的tuple,修改数据包的src地址,修改后的数据包的地址
为src=115.22.123.12,dst=14.17.88.22,这就是nat的De-DNAT功能。
至此,将NAT转换的大致内容分析完了,在分析的过程中,自己学到了很多,通过这次分析后,再次使用iptables命令,发现比以前熟悉多了,看来理解一件事情的来龙去脉后,就能从一个高度上进行整体的分析了,有了更全面的视角。通过分析netfilter,让我明白了,能在netfilter里实现的功能,尽量不要通过修改协议栈的代码来实现,以保证协议栈的稳定性,对于一个网络程序员来说,尽量少的修改协议栈代码,应尽量使用netfilter来完成大多数的过滤、转换、限制等功能,netfilter真的是太强大了。
Linux netfilter 学习笔记 之十二 ip层netfilter的NAT模块代码分析
标签:
原文地址:http://www.cnblogs.com/foonsun/p/5737936.html