1. hook && conntrack && nat
下副图,我们可以很明确的看到:用于实现连接跟踪入口的hook函数以较高的优先级分别被注册到了netfitler的NF_IP_PRE_ROUTING和NF_IP_LOCAL_OUT两个hook点上;用于实现连接跟踪出口的hook函数以非常低的优先级分别被注册到了netfilter的NF_IP_LOCAL_IN和NF_IP_POST_ROUTING两个hook点上。
而DNAT操作和SNAT操作则是在NAT中。
2. 代码跟踪
下面以TCP包为例,整理一下连接跟踪的建立。前面已经讲了大致的流程,这里主要分析数据包到达ip_conntrack_in和ip_confirm的处理,借此了解连接跟踪状态的转换。
(1)发送TCP SYN包,由本地客户端发送给远程服务端
1)NF_IP_LOCAL_OUT这个Hook点,进入跟踪连接模块,调用ipv4_conntrack_local
,然后调用nf_conntrack_in,nf_conntrack_in中首先获取L3(IP层)和L4(传输层)协议,然后调用resolve_normal_ct
2)
resolve_normal_ct将查询连接记录中是否有记录该数据包,该函数里首先使用nf_ct_get_tuple通过skb参数获得数据包对应的tuple,通过调用nf_conntrack_find_get查找全局变量nf_conntrack_tuple_hash表中是否有该tuple
a) 有匹配的tuple,则将该返回该tuple对应的struct nf_conntrack_tuple_hash结构。
b) 如果没有匹配的,则通过init_conntrack创建一个struct nf_conntrack_tuple_hash结构。由于我们这里是有c发送的SYN包。所以应该查不到匹配的tuple。因此要创建一个nf_conn . 该函数里完成了SYN对应的正向tuple和方向tuple的计算,nf_conn 结构的初始化等等。重要的是给数组tuplehash[0]和tuplehash[1]的赋值,以及对成员conntrack->infos的赋值即
conntrack->infos.master=&conntrack->ct_general;
这样将新建的conntrack结构体的首地址保存在了 conntrack->infos.master。
该函数也返回一个struct nf_conntrack_tuple_hash结构
c) 处理完数据包是否有匹配的tuple之后,继续函数resolve_normal_ct的执行。接着NF_CT_DIRECTION判断该tuple_hash结构h的方向。很显然该包是一个新建立的包,所以
*ctinfo = IP_CT_NEW;
*set_reply= 0;
最后将 skb->nfct = &ct->ct_general;
skb->nfctinfo = *ctinfo;
相当于把当前的连接跟踪结构体的地址保存在了skb中。至此,resolve_normal_ct函数执行结束。该函数返回的是一个nf_conn 结构体ct,对应当前数据的连接跟踪记录。
d) 接着nf_conntrack_in的处理。对ct进行指针检查之后,进入对应协议的packet函数处理。由于这里是TCP协议,所以l4proto->packet函数指针指向了tcp_packet(nf_conntrack_proto_tcp.c). 该函数对SYN包进行的相应处理,这里我们只需要知道
conntrack->proto.tcp.state= TCP_CONNTRACK_SYN_SENT
e) ip_conntrack_in函数执行完毕,数据包接着被其他Hook函数处理。
3) 对数据包进行ip_conntrack_local处理之后,最后就等着数据包离开本机了。数据包离开本机之前,要经过NF_IP_POSTROUTING点,钩子函数为ipv4_confirm(ip_confirm).
该函数首先调用
ipv4_confirm-> nf_conntrack_confirm-> __nf_conntrack_confirm。__ip_conntrack_confirm函数最终对数据包进行实际的处理。
__nf_conntrack_confirm首先CTINFO2DIR检查数据包的方向,这里本地发出的包,应该为IP_CT_DIR_ORIGINAL(当且仅当使用REJECT target时,会出现不是ORIGINAL的情形,其它非ORIGINAL数据包在nf_conntrack_confirm函数中被返回NF_ACCEPT,不会进入到该函数的处理中),然后计算该数据包的正反两个方向tuple的hash值。如果nf_conntrack_tuple_hash静态表中没有这两个tuple,则__nf_conntrack_hash_insert加入进去。并add_timer将超时处理挂到time_list标上,同时atomic_inc将nf_conn 结构的ct_general成员加1,并set_bit将nf_conn 结构的status的第IPS_CONFIRMED_BIT=3位置位. 随后返回NF_ACCEPT,将该数据包发送出去。
至此,SYN包的连接记录已经建立,连接记录的超时处理函数也挂在了全局timer_list里面。
(2)接收TCP SYN包,由本地客户端接收远程服务端
1)NF_IP_PRE_ROUTING
这个Hook点,进入跟踪连接模块,调用ipv4_conntrack_in,然后调用nf_conntrack_in,nf_conntrack_in中首先获取L3(IP层)和L4(传输层)协议,然后调用resolve_normal_ct
2)resolve_normal_ct将查询连接记录中是否有记录该数据包,该函数里首先使用nf_ct_get_tuple通过skb参数获得数据包对应的tuple,通过调用nf_conntrack_find_get查找全局变量nf_conntrack_tuple_hash表中是否有该tuple
a) 有匹配的tuple,则将该返回该tuple对应的struct nf_conntrack_tuple_hash结构。
b) 如果没有匹配的,则通过init_conntrack创建一个struct nf_conntrack_tuple_hash结构。由于我们这里是有c发送的SYN包。所以应该查不到匹配的tuple。因此要创建一个nf_conn . 该函数里完成了SYN对应的正向tuple和方向tuple的计算,nf_conn 结构的初始化等等。重要的是给数组tuplehash[0]和tuplehash[1]的赋值,以及对成员conntrack->infos的赋值即
conntrack->infos.master=&conntrack->ct_general;
这样将新建的conntrack结构体的首地址保存在了 conntrack->infos.master。
该函数也返回一个struct nf_conntrack_tuple_hash结构
c) 处理完数据包是否有匹配的tuple之后,继续函数resolve_normal_ct的执行。接着NF_CT_DIRECTION判断该tuple_hash结构h的方向。很显然该包方向为IP_CT_DIR_REPLY,所以
*ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
/* Please set reply bit if this packet OK */
*set_reply = 1;
最后将 skb->nfct = &ct->ct_general;
skb->nfctinfo = *ctinfo;
相当于把当前的连接跟踪结构体的地址保存在了skb中。至此,resolve_normal_ct函数执行结束。该函数返回的是一个nf_conn 结构体ct,对应当前数据的连接跟踪记录。
d) 接着nf_conntrack_in的处理。对ct进行指针检查之后,进入对应协议的packet函数处理。由于这里是TCP协议,所以l4proto->packet函数指针指向了tcp_packet(nf_conntrack_proto_tcp.c). 该函数对SYN包进行的相应处理
e) ip_conntrack_in函数执行完毕,数据包接着被其他Hook函数处理。
3) 对数据包的
nf_conntrack_in处理之后,就等着数据包进入本机的Hook点,NF_IP_LOACL_IN。连接跟踪在这里注册的函数是ipv4_confirm(ip_confirm),该函数是调用
nf_conntrack_confirm,这里应该直接返回NF_ACCEPT。
至此,由于有相应连接记录的存在,这里只对连接记录的若干状态进行修改,然后就接收到了本地主机上。这里也表明了连接跟踪将相关的连接都归于同一条连接记录上
3. 如何修改数据包
阅读(1521) | 评论(0) | 转发(1) |