NetFilter在2.4.x内核中引入,成为linux平台下进行网络应用的主要扩展,不仅包括防火墙的实现,还包括报文的处理(如报文加密、报文分类统计等)等。
NetFilter数据结构 勾子struct nf_hook_ops[net\filter\core.c]struct nf_hook_ops { struct list_head list; /* User fills in from here down. */ nf_hookfn *hook; struct module *owner; u_int8_t pf; unsigned int hooknum; /* Hooks are ordered in ascending priority. */ int priority; };成员list用于链入全局勾子数组nf_hooks中,它一定在第一位,保证&nf_hook_ops->list的值与&nf_hook_ops相同,稍后在使用时会用到这一技巧;
enum { NFPROTO_UNSPEC = 0, NFPROTO_IPV4 = 2, NFPROTO_ARP = 3, NFPROTO_BRIDGE = 7, NFPROTO_IPV6 = 10, NFPROTO_DECNET = 12, NFPROTO_NUMPROTO, };NF_MAX_HOOKS表示勾子应用的位置,可选值在每个协议模块内部定义,这些值代表了勾子函数在协议流程中应用的位置(稍后会以bridge为例详细说明),大致上都有以下值:
NF_XXX_PRE_ROUTING, NF_XXX_LOCAL_IN, NF_XXX_FORWARD, NF_XXX_LOCAL_OUT, NF_XXX_POST_ROUTING, NF_XXX_NUMHOOKS
int nf_register_hook(struct nf_hook_ops *reg) { struct nf_hook_ops *elem; int err; err = mutex_lock_interruptible(&nf_hook_mutex); if (err < 0) return err; list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) { if (reg->priority < elem->priority) break; } list_add_rcu(®->list, elem->list.prev); mutex_unlock(&nf_hook_mutex); return 0; }这个函数很简单,从指定pf&hooknum的nf_hooks队列遍历,按priority从小到大顺序,将reg插入相应位置,完成勾子函数的注册。
void nf_unregister_hook(struct nf_hook_ops *reg) { mutex_lock(&nf_hook_mutex); list_del_rcu(®->list); mutex_unlock(&nf_hook_mutex); synchronize_net(); }这个函数更简单,从nf_hooks中删除reg。
int nf_hook_slow(u_int8_t pf, unsigned int hook, struct sk_buff *skb, struct net_device *indev, struct net_device *outdev, int (*okfn)(struct sk_buff *), int hook_thresh) { struct list_head *elem; unsigned int verdict; int ret = 0; /* We may already have this, but read-locks nest anyway */ rcu_read_lock(); elem = &nf_hooks[pf][hook]; next_hook: verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev, outdev, &elem, okfn, hook_thresh); if (verdict == NF_ACCEPT || verdict == NF_STOP) { ret = 1; } else if (verdict == NF_DROP) { kfree_skb(skb); ret = -EPERM; } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) { if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn, verdict >> NF_VERDICT_BITS)) goto next_hook; } rcu_read_unlock(); return ret; }nf_hook_slow()从nf_hooks中找出到执行的勾子队列,依次执行,然后根据返回值决定是否继续(由nf_iterate()完成)。参数中的pf和hook代表了注册勾子函数时给的参数PF和HOOKNUM,它们共同决定勾子函数要插入的nf_hook的哪个队列中。
#define NF_DROP 0 #define NF_ACCEPT 1 #define NF_STOLEN 2 #define NF_QUEUE 3 #define NF_REPEAT 4 #define NF_STOP 5先以nf_iterate()函数为例,elem->hook()表示执行勾子函数,执行结构为verdict;
unsigned int nf_iterate(……) { unsigned int verdict; list_for_each_continue_rcu(*i, head) { struct nf_hook_ops *elem = (struct nf_hook_ops *)*i; if (hook_thresh > elem->priority) continue; verdict = elem->hook(hook, skb, indev, outdev, okfn); if (verdict != NF_ACCEPT) { if (verdict != NF_REPEAT) return verdict; *i = (*i)->prev; } } return NF_ACCEPT; }根据nf_iterate()返回,会有以下情况:
if (verdict == NF_ACCEPT || verdict == NF_STOP) { ret = 1; } else if (verdict == NF_DROP) { kfree_skb(skb); ret = -EPERM; } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) { if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn, verdict >> NF_VERDICT_BITS)) goto next_hook; }最后还是以bridge来说明下hooks参数的意义,上面已经讲过,它决定了在协议流程的何处调用勾子函数;因为使用NetFilter的目的是在内核态处理报文,而哪些地方可以处理报文只能是内核已经定义好的。一般来说,内核会在报文发送和接收的关键位置添加勾子函数处理,查找代码中NF_HOOK即可知。下面以bridge,为例,来看下在哪些地方用到了,以及这些值的含义: