🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ### **Netfilter的钩子** netfilter在内核的网络栈中注册了五个钩子(Hook),分别是(参考[此文](https://wiki.aalto.fi/download/attachments/69901948/netfilter-paper.pdf),有没有发现这五个钩子的名字和iptables的五条链的名字很像) * NF_IP_PRE_ROUTING * NF_IP_POST_ROUTING * NF_IP_FILTER * NF_IP_LOCAL_IN * NF_IP_LOCAL_OUT 数据包根据流向不同会经过不同的钩子,如下 * 入包:`NF_IP_PRE_ROUTING` -》 `NF_IP_LOCAL_IN` * 出包:`NF_IP_LOCAL_OUTPUT` -》 `NF_IP_POST_ROUTING` * 经过:`NF_IP_PRE_ROUTING` -》`NF_IP_FORWARD` -》`NF_IP_POST_ROUTING` ### **回调函数** netfilter的其他模块比如conntrack、nat在上面的钩子点注册自己的回调函数(也叫钩子函数),当数据包到达某个钩子点时,就会依次调用该钩子点注册的回调函数。 每一个注册的回调函数都会有一个优先级,优先级越高,越先调用。 每一个回调函数都会返回一个结果,结果的取值如下: * NF_ACCEPT:接收该包,交由下一个回调函数处理 * NF_DROP:丢弃该包 * ... ### **连接追踪** 连接追踪模块只对数据包进行跟踪,可能会丢弃数据包,但是不会修改数据包。修改数据包是NAT模块做的事。 连接追踪模块在`NF_IP_PRE_ROUTING`与`NF_IP_LOCAL_OUTPUT`处注册了回调函数`nf_conntrack_in()`, 在`NF_IP_POST_ROUTING`与 `NF_IP_LOCAL_IN`处注册了回调函数`nf_conntrack_confirm()`。 连接追踪模块有四张表,其中两张比较重要的表为unconfirmed表与confirmed表。 ##### **`nf_conntrack_in() net/netfilter/nf_conntrack_core.c`** 我们以经过的数据包为例。当数据包经过`NF_IP_PRE_ROUTING`时,内核会调用`nf_conntrack_in()`函数进行处理。 首先,该函数会查找该数据包是否属于某个连接(`nf_conn`),如果找到了连接,则把该连接的信息保存在数据包的`_nfct`字段中,这样数据包就与某个连接关联起来了。如果没有找到对应的连接,则根据该数据包生成一个连接, ``` unsigned int nf_conntrack_in(struct net *net, u_int8_t pf, unsigned int hooknum, struct sk_buff *skb) { const struct nf_conntrack_l4proto *l4proto; struct nf_conn *ct, *tmpl; enum ip_conntrack_info ctinfo; u_int8_t protonum; int dataoff, ret; tmpl = nf_ct_get(skb, &ctinfo); # by pshizh: 此处返回null if (tmpl || ctinfo == IP_CT_UNTRACKED) { /* Previously seen (loopback or untracked)? Ignore. */ if ((tmpl && !nf_ct_is_template(tmpl)) || ctinfo == IP_CT_UNTRACKED) { NF_CT_STAT_INC_ATOMIC(net, ignore); return NF_ACCEPT; } skb->_nfct = 0; } /* rcu_read_lock()ed by nf_hook_thresh */ dataoff = get_l4proto(skb, skb_network_offset(skb), pf, &protonum); if (dataoff <= 0) { pr_debug("not prepared to track yet or error occurred\n"); NF_CT_STAT_INC_ATOMIC(net, error); NF_CT_STAT_INC_ATOMIC(net, invalid); ret = NF_ACCEPT; goto out; } 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) { ret = l4proto->error(net, tmpl, skb, dataoff, pf, hooknum); if (ret <= 0) { NF_CT_STAT_INC_ATOMIC(net, error); NF_CT_STAT_INC_ATOMIC(net, invalid); ret = -ret; goto out; } /* ICMP[v6] protocol trackers may assign one conntrack. */ if (skb->_nfct) goto out; } repeat: ret = resolve_normal_ct(net, tmpl, skb, dataoff, pf, protonum, l4proto); if (ret < 0) { /* Too stressed to deal. */ NF_CT_STAT_INC_ATOMIC(net, drop); ret = NF_DROP; goto out; } ct = nf_ct_get(skb, &ctinfo); if (!ct) { /* Not valid part of a connection */ NF_CT_STAT_INC_ATOMIC(net, invalid); ret = NF_ACCEPT; goto out; } ret = l4proto->packet(ct, skb, dataoff, ctinfo); 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(&ct->ct_general); skb->_nfct = 0; NF_CT_STAT_INC_ATOMIC(net, invalid); if (ret == -NF_DROP) NF_CT_STAT_INC_ATOMIC(net, drop); /* Special case: TCP tracker reports an attempt to reopen a * closed/aborted connection. We have to go back and create a * fresh conntrack. */ if (ret == -NF_REPEAT) goto repeat; ret = -ret; goto out; } if (ctinfo == IP_CT_ESTABLISHED_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; } ```