Netfilter是Linux 2.4.x 引入的一个子系统,它作为一个通用的、抽象的框架,提供一整套的hook函数的管理机制,使得诸如数据包过滤(filter)、网络地址转换(NAT)和基于协议类型的链接跟踪成为了可能。这些功能仅通过使用内核网络代码提供的各式各样的hook函数完成。
对于数据在网络协议栈中的传送过程,前面我们花了较大篇幅,从原理到内核源码剖析了数据包在整个协议栈的传送过程,包括发送和接收,主要是数据包的封装与解包,在用户层是裸数据,然后层层往下逐步添加对应层头部,返回来是逐步去掉头部。
但是,netfilter对数据包还设置了一些关卡,看下图:
对于收到的每个数据包,都是从 PRE_ROUTING 进来,经过路由判决,如果是发送给本机的数据包就经过 LOCAL_IN ,然后往协议栈的上层继续传递,否则,如果该数据包的目的地不是本机,那么就经过 FORWARD,然后顺着 POST_ROUTING 将该包转发出去;
对于发送的每个数据包,首先也有一个路由判决,以确定该包是从哪个接口出去,然后经过 LOCAL_OUT,最后也是顺着 POST_ROUTING 将该包发送出去。
Netfilter 的架构就是在整个网络流程的上面这些位置放置一些检测点(HOOK,或者说回调函数),而在每个检测点上登记(callback)了一些处理函数进行处理(如包过滤、NAT等,甚至可以是用户自定义的功能)。
这些hook点就是 netfilter hook,在这里,数据包可以被分析并且选择是保留还是放弃。
Netfilter 中定义了五个关于ipv4的hook类型(这里我们只讨论ipv4,对于ipv6也是可以对应的的 hook类型,这些符号声明在 linux/netfilter_ipv4.h中。
PS:大家需要知道的是,在高版本中,内核已经做出了修改,下面的ipv4符号均修改为NF_INET形式。所以大家编程的时候要把IP换为INET
NF_IP_POST_ROUTING
所有马上便要通过网络设备出去的包通过此检测点,内置的源地址转换功能(包括地址伪装)在此点进行 ;
在上面几个关键点,有很多已经按照优先级预先注册了的钩子函数,形成了一条链。对于每个到来的数据包会依次被这些钩子函数处理一番,然后视情况是将其放行还是丢弃,所以必须得将处理结果返回给 netfilter。
Netfilter 的返回值:
下面我们学习下 Netfilter hook 的注册和注销
注册和注销Netfilter hook
注册一个 hook 函数是围绕 nf_hook_ops 数据结构的一个非常简单的操作。
为了和编程环境内核源码树版本保持一致,这里我们分析的源码均是 Linux kernel 3.14
(linux/netfilter.h)
struct nf_hook_ops {
struct list_head list;//维护Netfilter hook的列表
/* 以下的值由用户填充 */
nf_hookfn *hook;//指向nf_hookfn类型的函数指针
//该函数是这个hook被调用时执行的函数
struct module *owner;//模块的所有者
void *priv;//不知道
u_int8_t pf;//用于指定协议族
unsigned int hooknum;//用于指定安装的这个函数对应的具体的hook类型
//就是前面说到的五个值,钩子函数在哪个位置设关卡
/* Hooks are ordered in ascending priority. */
int priority;//用于指定在执行的顺序中,这个hook函数应当被放在什么地方
};
//这里只是定义了一个函数指针
struct sk_buff;
struct nf_hook_ops;
typedef unsigned int nf_hookfn(const struct nf_hook_ops *ops,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *));
对应的hook类型和协议类型以及优先级的定义:
uapi/linux/netfilter.h (linux kernel 3.14)
uapi/linux/netfilter_ipv4.h
enum nf_inet_hooks {
NF_INET_PRE_ROUTING,
NF_INET_LOCAL_IN,
NF_INET_FORWARD,
NF_INET_LOCAL_OUT,
NF_INET_POST_ROUTING,
NF_INET_NUMHOOKS
};
enum {
NFPROTO_UNSPEC = 0,
NFPROTO_INET = 1,
NFPROTO_IPV4 = 2,
NFPROTO_ARP = 3,
NFPROTO_BRIDGE = 7,
NFPROTO_IPV6 = 10,
NFPROTO_DECNET = 12,
NFPROTO_NUMPROTO,
};
enum nf_ip_hook_priorities {
NF_IP_PRI_FIRST = INT_MIN,
NF_IP_PRI_CONNTRACK_DEFRAG = -400,
NF_IP_PRI_RAW = -300,
NF_IP_PRI_SELINUX_FIRST = -225,
NF_IP_PRI_CONNTRACK = -200,
NF_IP_PRI_MANGLE = -150,
NF_IP_PRI_NAT_DST = -100,
NF_IP_PRI_FILTER = 0,
NF_IP_PRI_SECURITY = 50,
NF_IP_PRI_NAT_SRC = 100,
NF_IP_PRI_SELINUX_LAST = 225,
NF_IP_PRI_CONNTRACK_HELPER = 300,
NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
NF_IP_PRI_LAST = INT_MAX,
};
struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
hook函数就保存在上面的二维数组中,通过协议类型和hook类型定位,并且每个hook函数具备对应的优先级,如上图所示。
**注册一个 Netfilter hook 需要调用 nf_register_hook() ,以及用到一个 nf_hook_ops 数据结构。
注销则是用nf_unregister_hook() ;**
int nf_register_hook(struct nf_hook_ops *reg);//注册
void nf_unregister_hook(struct nf_hook_ops *reg);//注销
ok,有了前面的小小铺垫,下篇我们将给出一个小小的测试案例。
talk is cheap, show me your code.
参考资料:《深入Linux网络核心堆栈》