很长时间没上来,一时兴起只来剪草,却正巧赶上开博一周年,天意如此,我且顺了天,附上点近期所学东西。
连接跟踪对nat实现的辅助
一个新建连接的数据包从物理层传递到网络层后,连接跟踪会对其进行跟踪,记录下其连接状态和重要信息,从指定的内置的处理函数的优先级enum nf_ip_hook_priorities{}中可以看出连接跟踪函数的优先级是在nat处理之前的,是辅助完成nat转换的,但其不对数据包进行任何实质性处理,所以修改数据包信息的工作还是由nat完成。
连接跟踪就是获取数据报的数据结构sk_buff中ip头和协议头的信息(源ip、端口,目的ip、端口、传输协议和网络协议),存放于连接跟踪特定的多元组tuple中,tuple的成员dir还对数据包的方向进行了记录,用来区分是进来的包还是发出的包。内核中对本地接受和发送的数据包的信息分别用两个多元组original tuple和reply tuple分开存储,这个就相当理论所说的napt映射表。original tuple多元组的信息全从sk_buff中获取,回包的时候只需对tuple中的源ip和目的ip,源端口和目的端口的值使用函数nf_ct_invert_tuple进行交换处理,赋值给reply tuple这个多元组中的成员,连接跟踪的任务便完成了,根据前面的原理描述,我们知道网络地址转换主要是对sk_buff中ip地址和端口号动手脚来实现,这些工作还是由nat模块完成。
连接跟踪是nat转换的基础,下面针对在nat转换中起关键作用的几个函数进行分析。以接收tcp包为例。
1. 函数nf_ct_get_tuple从sk_buff中获取源ip、端口,目的ip、端口、传输协议和网络协议信息,之后赋值给多元组tuple。
bool nf_ct_get_tuple(const struct sk_buff *skb,unsigned int nhoff,unsigned int dataoff,u_int16_t l3num,u_int8_t protonum,struct nf_conntrack_tuple *tuple, const struct nf_conntrack_l3proto *l3proto,const struct nf_conntrack_l4proto *l4proto)
{
memset(tuple, 0, sizeof(*tuple));
/*协议类型赋给tuple成员*/
tuple->src.l3num = l3num;
if (l3proto->pkt_to_tuple(skb, nhoff, tuple) == 0)
return false;
/*网络协议赋值*/
tuple->dst.protonum = protonum;
/*获取传输方向*/
tuple->dst.dir = IP_CT_DIR_ORIGINAL;
/*获取l4层信息*/
return l4proto->pkt_to_tuple(skb, dataoff, tuple);
}
EXPORT_SYMBOL_GPL(nf_ct_get_tuple);
获取sk_buff网络层信息的具体操作如下:
struct nf_conntrack_l3proto nf_conntrack_l3proto_ipv4 __read_mostly = {
.l3proto = PF_INET,
.name = "ipv4",
.pkt_to_tuple = ipv4_pkt_to_tuple,
.invert_tuple = ipv4_invert_tuple,
.print_tuple = ipv4_print_tuple,
.get_l4proto = ipv4_get_l4proto,
… …
};
ipv4_pkt_to_tuple所做的工作是从ip首部自动的源地址开始,读取两个32字节的变量,分别赋给tuple中的源地址和目的地址。
获取sk_buff传输层信息的具体操作如下:
struct nf_conntrack_l4proto nf_conntrack_l4proto_tcp4 __read_mostly =
{
.l3proto = PF_INET,
.l4proto = IPPROTO_TCP,
.name = "tcp",
.pkt_to_tuple = tcp_pkt_to_tuple,
.invert_tuple = tcp_invert_tuple,
.print_tuple = tcp_print_tuple,
.print_conntrack = tcp_print_conntrack,
.packet = tcp_packet,
.new = tcp_new,
.error = tcp_error,
… …
};
EXPORT_SYMBOL_GPL(nf_conntrack_l4proto_tcp4);
函数tcp_pkt_to_tuple从传输头首地址开始,读取两个8字节的变量,分别赋给tuple中的源端口和目的端口。
static bool tcp_pkt_to_tuple(const struct sk_buff *skb, unsigned int dataoff,
struct nf_conntrack_tuple *tuple)
{
const struct tcphdr *hp;
struct tcphdr _hdr;
hp = skb_header_pointer(skb, dataoff, 8, &_hdr);
if (hp == NULL)
return false;
tuple->src.u.tcp.port = hp->source;
tuple->dst.u.tcp.port = hp->dest;
return true;
}
2. 最后一个重要的函数莫过于实现交换处理的函数nf_ct_invert_tuple(),由original tuple经过层层处理从而得到reply tuple,以达到最终目的。
nf_ct_invert_tuple(struct nf_conntrack_tuple *inverse,
const struct nf_conntrack_tuple *orig,
const struct nf_conntrack_l3proto *l3proto,
const struct nf_conntrack_l4proto *l4proto)
{ memset(inverse, 0, sizeof(*inverse));
inverse->src.l3num = orig->src.l3num;
if (l3proto->invert_tuple(inverse, orig) == 0)
return false;
inverse->dst.dir = !orig->dst.dir;
inverse->dst.protonum = orig->dst.protonum;
return l4proto->invert_tuple(inverse, orig);
}
EXPORT_SYMBOL_GPL(nf_ct_invert_tuple);
从代码中可以清楚地看到同样是l3和l4层分开交换处理,与获取sk_buff信息的处理步骤基本一致,此处不再赘述。仅附l4处理代码。
static bool tcp_invert_tuple(struct nf_conntrack_tuple *tuple,
const struct nf_conntrack_tuple *orig)
{
tuple->src.u.tcp.port = orig->dst.u.tcp.port;
tuple->dst.u.tcp.port = orig->src.u.tcp.port;
return true;
}