作者:gfree.wind@gmail.com
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
======================================================================================================
在前面的NAT学习中,发现NAT是建立在connection track的基础上的,那么还是需要有一些connection track的概念为好。
关键函数nf_conntrack_in
- unsigned int
-
nf_conntrack_in(struct net *net, u_int8_t pf, unsigned int hooknum,
-
struct sk_buff *skb)
-
{
-
struct nf_conn *ct, *tmpl = NULL;
-
enum ip_conntrack_info ctinfo;
-
struct nf_conntrack_l3proto *l3proto;
-
struct nf_conntrack_l4proto *l4proto;
-
unsigned int dataoff;
-
u_int8_t protonum;
-
int set_reply = 0;
-
int ret;
/*
这个条件判断不是很明白。为什么可以直接忽略以前的nfct。
看了后面的代码后,skb->nfcth会在后面的代码中重新得到,再配合这里的注释大概明白了用意。
当skb->nfct为有效值时,即意味着该skb已经经过了conn track,再次落到conn track时。
如注释所说,比如发往回环的时候,会有这个情况。
*/
-
if (skb->nfct) {
-
/* Previously seen (loopback or untracked)? Ignore. */
-
tmpl = (struct nf_conn *)skb->nfct;
-
if (!nf_ct_is_template(tmpl)) {
-
NF_CT_STAT_INC_ATOMIC(net, ignore);
-
return NF_ACCEPT;
-
}
-
skb->nfct = NULL;
-
}
-
-
/* rcu_read_lock()ed by nf_hook_slow */
- /*
- 根据proto family,来得到l3的conn track的target。
- 这里对于netfilter来说,它将conn track的具体工作下发到具体的L3层的target处理。
- 这样无论是ipv4还是ipv6,netfilter的处理可以保持一致。甚至其可以支持更多的L3协议。
对于ipv4来说,l3proto就是nf_conntrack_l3proto_ipv4。
- */
-
l3proto = __nf_ct_l3proto_find(pf);
- /*
- 将L3的报文头的偏移即数据包skb传给l3->get_l4proto来得到L4的协议号即L4的起始位置。
- 这时L3的报文头完全由具体的L3层的proto负责解析
- */
-
ret = l3proto->get_l4proto(skb, skb_network_offset(skb),
-
&dataoff, &protonum);
-
if (ret <= 0) {
-
pr_debug("not prepared to track yet or error occured\n");
-
NF_CT_STAT_INC_ATOMIC(net, error);
-
NF_CT_STAT_INC_ATOMIC(net, invalid);
-
ret = -ret;
-
goto out;
-
}
/*
与L3类似,同样是通过L4的协议号得到具体的L4 proto的target。
实现与具体协议的解耦。
如nf_conntrack_l4proto_tcp4, nf_conntrack_l4proto_udp4等等
*/
-
l4proto = __nf_ct_l4proto_find(pf, protonum);
-
-
/* It may be an special packet, error, unclean...
-
* inverse of the return code tells to the netfilter
-
* core what to do with the packet. */
-
if (l4proto->error != NULL) {
- /* 对L4数据包进行检查,由具体的L4协议负责 */
-
ret = l4proto->error(net, tmpl, skb, dataoff, &ctinfo,
-
pf, hooknum);
-
if (ret <= 0) {
-
NF_CT_STAT_INC_ATOMIC(net, error);
-
NF_CT_STAT_INC_ATOMIC(net, invalid);
-
ret = -ret;
-
goto out;
-
}
-
}
/* 根据L3和L4层的信息,得到conn track和其信息。后面会学习resolve_normal_ct */
-
ct = resolve_normal_ct(net, tmpl, skb, dataoff, pf, protonum,
-
l3proto, l4proto, &set_reply, &ctinfo);
-
if (!ct) {
-
/* Not valid part of a connection */
-
NF_CT_STAT_INC_ATOMIC(net, invalid);
-
ret = NF_ACCEPT;
-
goto out;
-
}
-
-
if (IS_ERR(ct)) {
-
/* Too stressed to deal. */
-
NF_CT_STAT_INC_ATOMIC(net, drop);
-
ret = NF_DROP;
-
goto out;
-
}
-
-
NF_CT_ASSERT(skb->nfct);
/*
将数据包传递给具体的L4 target进行特定的操作。
如nf_conntrack_l4proto_udp4,会update连接conn track的age,保证不ageout
对于nf_conntrack_l4proto_tcp4,会有更复杂的操作。
*/
-
ret = l4proto->packet(ct, skb, dataoff, ctinfo, pf, hooknum);
-
if (ret <= 0) {
-
/* Invalid: inverse of the return code tells
-
* the netfilter core what to do */
-
pr_debug("nf_conntrack_in: Can't track with proto module\n");
-
nf_conntrack_put(skb->nfct);
-
skb->nfct = NULL;
-
NF_CT_STAT_INC_ATOMIC(net, invalid);
-
if (ret == -NF_DROP)
-
NF_CT_STAT_INC_ATOMIC(net, drop);
-
ret = -ret;
-
goto out;
-
}
/* 设置conn track的事件,不明白这里为什么叫做cache */
-
if (set_reply && !test_and_set_bit(IPS_SEEN_REPLY_BIT, &ct->status))
-
nf_conntrack_event_cache(IPCT_REPLY, ct);
-
out:
-
if (tmpl)
-
nf_ct_put(tmpl);
-
-
return ret;
- }
- /* On success, returns conntrack ptr, sets skb->nfct and ctinfo */
-
static inline struct nf_conn *
-
resolve_normal_ct(struct net *net, struct nf_conn *tmpl,
-
struct sk_buff *skb,
-
unsigned int dataoff,
-
u_int16_t l3num,
-
u_int8_t protonum,
-
struct nf_conntrack_l3proto *l3proto,
-
struct nf_conntrack_l4proto *l4proto,
-
int *set_reply,
-
enum ip_conntrack_info *ctinfo)
-
{
-
struct nf_conntrack_tuple tuple;
-
struct nf_conntrack_tuple_hash *h;
-
struct nf_conn *ct;
-
u16 zone = tmpl ? nf_ct_zone(tmpl) : NF_CT_DEFAULT_ZONE;
/* 通过具体的L3和L4 target得到tuple */
-
if (!nf_ct_get_tuple(skb, skb_network_offset(skb),
-
dataoff, l3num, protonum, &tuple, l3proto,
-
l4proto)) {
-
pr_debug("resolve_normal_ct: Can't get tuple\n");
-
return NULL;
-
}
-
-
/* look for tuple match */
- /* 通过tuple找到正确的conn track*/
-
h = nf_conntrack_find_get(net, zone, &tuple);
-
if (!h) {
- /* 没有conn track的话,就创建一个新的conn track,并添加到hash表中 */
-
h = init_conntrack(net, tmpl, &tuple, l3proto, l4proto,
-
skb, dataoff);
-
if (!h)
-
return NULL;
-
if (IS_ERR(h))
-
return (void *)h;
-
}
- /* 从hash 节点获得conn track */
-
ct = nf_ct_tuplehash_to_ctrack(h);
-
-
/* It exists; we have (non-exclusive) reference. */
-
if (NF_CT_DIRECTION(h) == IP_CT_DIR_REPLY) {
- /* tuple为REPLY方向,那么ctinfo为establish+reply */
-
*ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
-
/* Please set reply bit if this packet OK */
-
*set_reply = 1;
-
} else {
- /* tuple为original方向,即初始的发送方向 */
-
/* Once we've had two way comms, always ESTABLISHED. */
-
if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
- /* 之前收到过REPLY,那么ctinfo为established*/
-
pr_debug("nf_conntrack_in: normal packet for %p\n", ct);
-
*ctinfo = IP_CT_ESTABLISHED;
-
} else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {
-
pr_debug("nf_conntrack_in: related packet for %p\n",
-
ct);
- /* 表明这是一个相关的conn track。如ICMP error或者FTP的data session? */
-
*ctinfo = IP_CT_RELATED;
-
} else {
- /* 表明这是一个新的conn track*/
-
pr_debug("nf_conntrack_in: new packet for %p\n", ct);
-
*ctinfo = IP_CT_NEW;
-
}
-
*set_reply = 0;
-
}
-
skb->nfct = &ct->ct_general;
-
skb->nfctinfo = *ctinfo;
-
return ct;
- }
发现Netfilter的conn track还是有点意思的。那么我后面会继续学习netfilter的conn track的。