本篇主要从三层协议栈调用函数NF_HOOK说起,不断深入,分析某个钩子点中所有钩子函数的调用流程,但是本文不包含规则介绍和核心的规则匹配流程,后续文章将继续分析;
NF_HOOK函数先调用了nf_hook继续执行调用钩子函数处理,处理之后,如果接受,则调用输入的回调函数okfn,继续数据包的下一步处理;
1 static inline int 2 NF_HOOK(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk, struct sk_buff *skb, 3 struct net_device *in, struct net_device *out, 4 int (*okfn)(struct net *, struct sock *, struct sk_buff *)) 5 { 6 /* 先执行钩子函数 */ 7 int ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn); 8 9 /* 返回成功,则继续执行成功回调 */ 10 if (ret == 1) 11 ret = okfn(net, sk, skb); 12 return ret; 13 }
NF_HOOK_COND增加了一个输入掉价cond,当不满足条件的时候,直接调用okfn,满足条件的时候,才会继续调用nf_hook进行后续的钩子函数调用流程,如果nf_hook返回1,则调用okfn;
1 static inline int 2 NF_HOOK_COND(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk, 3 struct sk_buff *skb, struct net_device *in, struct net_device *out, 4 int (*okfn)(struct net *, struct sock *, struct sk_buff *), 5 bool cond) 6 { 7 int ret; 8 9 if (!cond || 10 ((ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn)) == 1)) 11 ret = okfn(net, sk, skb); 12 return ret; 13 }
nf_hook函数首先找到钩子点函数入口,如果有钩子函数,则进一步初始化nf_hook_state结构,然后调用nf_hook_slow进入钩子函数调用流程;
1 static inline int nf_hook(u_int8_t pf, unsigned int hook, struct net *net, 2 struct sock *sk, struct sk_buff *skb, 3 struct net_device *indev, struct net_device *outdev, 4 int (*okfn)(struct net *, struct sock *, struct sk_buff *)) 5 { 6 struct nf_hook_entry *hook_head; 7 int ret = 1; 8 9 #ifdef HAVE_JUMP_LABEL 10 if (__builtin_constant_p(pf) && 11 __builtin_constant_p(hook) && 12 !static_key_false(&nf_hooks_needed[pf][hook])) 13 return 1; 14 #endif 15 16 rcu_read_lock(); 17 /* 找到钩子点 */ 18 hook_head = rcu_dereference(net->nf.hooks[pf][hook]); 19 if (hook_head) { 20 struct nf_hook_state state; 21 22 /* 初始化nf_hook_state结构 */ 23 nf_hook_state_init(&state, hook, pf, indev, outdev, 24 sk, net, okfn); 25 26 /* 执行钩子函数 */ 27 ret = nf_hook_slow(skb, &state, hook_head); 28 } 29 rcu_read_unlock(); 30 31 return ret; 32 }
在分析nf_hook_slow之前,我们先看下该函数中涉及到的钩子函数执行结果的返回值字段的含义;
1 /* Responses from hook functions. */ 2 #define NF_DROP 0 /* 丢包,不再传输 */ 3 #define NF_ACCEPT 1 /* 接受数据包,继续正常传输 */ 4 #define NF_STOLEN 2 /* 数据包已经被接管,回调函数处理该包,NF不再处理 */ 5 #define NF_QUEUE 3 /* 将数据包交给用户空间的进程处理 */ 6 #define NF_REPEAT 4 /* 再次调用钩子函数 */ 7 #define NF_STOP 5 /* Deprecated, for userspace nf_queue compatibility. */ 8 #define NF_MAX_VERDICT NF_STOP
nf_hook_slow会遍历当前钩子点上的钩子函数,通过函数nf_hook_entry_hookfn调用钩子函数,并根据返回值判断如何进行下一步处理;
1 int nf_hook_slow(struct sk_buff *skb, struct nf_hook_state *state, 2 struct nf_hook_entry *entry) 3 { 4 unsigned int verdict; 5 int ret; 6 7 do { 8 /* 调用钩子函数 */ 9 verdict = nf_hook_entry_hookfn(entry, skb, state); 10 switch (verdict & NF_VERDICT_MASK) { 11 case NF_ACCEPT: 12 /* 获取下一个钩子函数 */ 13 entry = rcu_dereference(entry->next); 14 break; 15 case NF_DROP: 16 /* 丢包 */ 17 kfree_skb(skb); 18 ret = NF_DROP_GETERR(verdict); 19 if (ret == 0) 20 ret = -EPERM; 21 return ret; 22 case NF_QUEUE: 23 /* 通过netfilter_queue交给应用层nf_queue处理 */ 24 ret = nf_queue(skb, state, &entry, verdict); 25 if (ret == 1 && entry) 26 continue; 27 return ret; 28 default: 29 /* Implicit handling for NF_STOLEN, as well as any other 30 * non conventional verdicts. 31 */ 32 return 0; 33 } 34 35 /* 继续执行下一个钩子函数 */ 36 } while (entry); 37 38 return 1; 39 }
nf_hook_entry_hookfn函数调用当前钩子函数结构entry中的钩子函数hook,返回执行结果;
1 static inline int 2 nf_hook_entry_hookfn(const struct nf_hook_entry *entry, struct sk_buff *skb, 3 struct nf_hook_state *state) 4 { 5 return entry->hook(entry->priv, skb, state); 6 }
我们暂且看一下filter表的钩子函数,可见,其核心流程为ipt_do_table,也就是进入filter表的规则匹配流程,ipt_do_table函数后续文章我们单独分析;
1 static unsigned int 2 iptable_filter_hook(void *priv, struct sk_buff *skb, 3 const struct nf_hook_state *state) 4 { 5 /* LOCAL_OUT && (数据长度不足ip头 || 实际ip头部长度不足最小ip头),在使用raw socket */ 6 if (state->hook == NF_INET_LOCAL_OUT && 7 (skb->len < sizeof(struct iphdr) || 8 ip_hdrlen(skb) < sizeof(struct iphdr))) 9 /* root is playing with raw sockets. */ 10 return NF_ACCEPT; 11 12 /* 核心规则匹配流程 */ 13 return ipt_do_table(skb, state, state->net->ipv4.iptable_filter); 14 }