Linux内核网络源码走读之Netfilter

本文走读内核网络?.NETfilter子系统相关的源码 。源码基于kernel 4.14版本 。
Netfilter子系统包含数据包选择、过滤、修改,连接跟踪,网络地址转换(NAT)等内容 。
Netfilter挂载点在上篇《linux内核源码走读之IPv4及IPv6》文章中,我们在IPv4和IPv6的接收和发送路径中,看到过这些挂载点 。

  • NF_INET_PRE_ROUTING: 在IPv4中,这个挂载点位于方法ip_rcv()中 。这是所有入站数据包遇到的第一个挂载点,它处在路由选择之前 。
  • NF_INET_LOCAL_IN: 在IPv4中,这个挂载点位于方法ip_local_deliver中 。对于所有发给当前主机的入站数据包,经过挂载点NF_INET_PRE_ROUTING和路由选择子系统之后,都将到达这个挂载点 。
  • NF_INET_FORWARD: 在IPv4中,这个挂载点位于方法ip_forward()中 。对于所有要转发的数据包,经过挂载点NF_INET_PRE_ROUTING和路由选择子系统之后,都将到达这个挂载点 。
  • NF_INET_POST_ROUTING: 在IPv4中,这个挂载点位于方法ip_output()中 。所有要转发的数据包,都在经过挂载点NF_INET_FORWARD后到达这个挂载点 。另外,当前主机生成的数据包经过挂载点NF_INET_LOCAL_OUT后将到达这个挂载点 。
  • NF_INET_LOCAL_OUT: 在IPv4中,这个挂载点位于方法__ip_local_out中 。当前主机生成的所有出站数据包都在经过路由查找和此挂载点之后,到达挂载点NF_INET_POST_ROUTING 。
内核网络代码中,一般通过宏NF_HOOK来调用在挂载点中注册的钩子函数 。
static inline intNF_HOOK(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk, struct sk_buff *skb,struct net_device *in, struct net_device *out,int (*okfn)(struct net *, struct sock *, struct sk_buff *)){int ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn);if (ret == 1)ret = okfn(net, sk, skb);return ret;}//nf_hook并不调用okfn回调函数,NF_HOOK宏判断nf_hook返回值=1(表示允许包通过)调用okfnstatic inline int nf_hook(u_int8_t pf, unsigned int hook, struct net *net,struct sock *sk, struct sk_buff *skb,struct net_device *indev, struct net_device *outdev,int (*okfn)(struct net *, struct sock *, struct sk_buff *))switch (pf)case NFPROTO_IPV4:hook_head = rcu_dereference(net->nf.hooks_ipv4[hook]);struct nf_hook_state state;nf_hook_state_init(&state, hook, pf, indev, outdev, sk, net, okfn);ret = nf_hook_slow(skb, &state, hook_head, 0);return retint nf_hook_slow(struct sk_buff *skb, struct nf_hook_state *state,const struct nf_hook_entries *e, unsigned int s)for (; s < e->num_hook_entries; s++)//依次执行注册的hook函数,如果返回值是NF_ACCEPT,则表示调用者可进一步执行okfnverdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state);return entry->hook(entry->priv, skb, state);Netfilter钩子回调函数返回值必须是下述五个值之一,这些值被称为netfilter verdicts(netfilter判决)
  • NF_DROP: 默默丢弃数据包
  • NF_ACCEPT: 数据包继续在内核协议栈中传输
  • NF_STOLEN: 数据包不继续传输,由钩子方法进行处理
  • NF_QUEUE: 将数据包排序,供用户空间使用
  • NF_REPEAT: 再次调用钩子函数
注册Netfilter钩子回调函数注册Netfilter钩子回调函数的方法有两个nf_register_net_hook和nf_register_net_hooks 。4.13之前的内核版本还有两个注册接口nf_register_hook和nf_register_hooks,从4.13版本开始内核删除了这两个接口,这两个接口最终也是调用nf_register_net_hook,下面看下nf_register_net_hook:
int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg)__nf_register_net_hook(net, reg->pf, reg)struct nf_hook_entries *p, *new_hooks;struct nf_hook_entries __rcu **pp;pp = nf_hook_entry_head(net, pf, reg->hooknum, reg->dev)return net->nf.hooks_ipv4 + hooknum; //以pf==NFPROTO_IPV4为例 。钩子挂载点保存在struct net对象中p = nf_entry_dereference(*pp);new_hooks = nf_hook_entries_grow(p, reg); //将新的nf_hook_ops按照优先级插入到hook entries中我们看到nf_register_net_hook一个入参是结构体struct nf_hook_ops,看下这个结构体:
typedef unsigned int nf_hookfn(void *priv,struct sk_buff *skb,const struct nf_hook_state *state);struct nf_hook_ops {/* User fills in from here down. */nf_hookfn*hook; //要注册的钩子回调函数struct net_device*dev;void*priv;u_int8_tpf; //协议簇,对于IPv4来说,它为NFPROTO_IPV4; IPV6, NFPROTO_IPV6boolnat_hook;unsigned inthooknum; //netfilter的5个挂载点之一/* Hooks are ordered in ascending priority. */intpriority; //按优先级升序排列回调函数,priority值越小回调函数越先被调用};连接跟踪现代网络中,仅根据L4和L3报头来过滤流量还不够,还应考虑基于会话对包进行处理 。连接跟踪能够让内核跟踪会话,连接跟踪的主要目标是为NAT打下基础 。


推荐阅读