linux内核网络之Netfilter详解

简介

Netfilter是Linux 2.4内核的一个子系统,Netfiler使得诸如数据包过滤、网络地址转换(NAT)以及网络连接跟踪等技巧成为可能,这些功能仅通过使用内核网络代码提供的各式各样的hook既可以完成。这些hook位于内核代码中,要么是静态链接的,要么是以动态加载的模块的形式存在。可以为指定的网络事件注册相应的回调函数,数据包的接收(ip_rcv)就是这样一个例子。

源代码文件 路径
Netfilter主文件 net/core/netfilter.c
Netfilter主头文件 include/linux/netfilter.h
ipv4 c文件 net/ipv4/netfilter/*.c
ipv4 头文件 include/linux/netfilter_ipv4/*.h
include/linux/netfilter_ipv4.h
ipv4协议栈主C文件 ip_input.c,ip_forward.c,ip_output.c,ip_fragment.c

Netfilter hook及其用法

Netfilter实现

Netfilter主要通过表、链实现规则,可以这么说,Netfilter是表的容器,表是链的容器,链是规则的容器,最终形成对数据报处理规则的实现。

数据在协议栈里的发送过程中,从上至下依次是“加头”的过程,每到达一层数据就被会加上该层的头部;与此同时,接受数据方就是个“剥头”的过程,从网卡收上包来之后,在往协议栈的上层传递过程中依次剥去每层的头部,最终到达用户那儿的就是裸数据了。

Netfilter是Linux 2.4.x引入的一个子系统,它作为一个通用的、抽象的框架,提供一整套的hook函数的管理机制,使得诸如数据包过滤、网络地址转换(NAT)和基于协议类型的连接跟踪成为了可能。Netfilter在内核中位置如下图所示:

这幅图很直观的反应了用户空间的iptables和内核空间的基于Netfilterip_tables模块之间的关系和其通讯方式,以及Netfilter在这其中所扮演的角色

Netfilter对IPv4的hook

Netfilter中定义了五个关于IPv4的hook,对这些符号的声明可以在linux/netfilter_ipv4.h中找到。这些hook列在下面的表中:

Hook 调用的时机
NF_IP_PRE_ROUTING 刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验和等检测), 目的地址转换在此点进行
NF_IP_LOCAL_IN 经路由查找后,送往本机的通过此检查点,INPUT包过滤在此点进行
NF_IP_FORWARD 要转发的包通过此检测点,FORWORD包过滤在此点进行
NF_IP_LOCAL_OUT 本机进程发出的包通过此检测点,OUTPUT包过滤在此点进行
NF_IP_POST_ROUTING 所有马上便要通过网络设备出去的包通过此检测点,内置的源地址转换功能(包括地址伪装)在此点进行;

在每个hook点上,已经预先注册了回调函数(称之为“钩子函数”),形成一条链。NF_IP_PRE_ROUTING 这个hook是数据包被接收到之后调用的第一个hook,这个hook既是稍后将要描述的模块所用到的。当然,其它的hook同样非常有用,但是在这里,我们的焦点是在 NF_IP_PRE_ROUTING 这个hook上。
在hook函数完成了对数据包所需的任何的操作之后,它们必须返回下列预定义的Netfilter返回值中的一个:

返回值 含义
NF_DROP 丢弃该数据包
NF_ACCEPT 保留该数据包
NF_STOLEN 忘掉该数据包
NF_QUEUE 将该数据包插入到用户空间
NF_REPEAT 再次调用该hook函数

每个hook对应的表

对于每种类型的协议,数据包都会依次按照hook点的方向进行传输,每个hook点上Netfilter又按照优先级挂了很多hook函数。这些hook函数就是用来处理数据包用的。

每张表的含义

Filter:从名字就可以看出,这个表里面的rule主要用来过滤数据,用来控制让哪些数据可以通过,哪些数据不能通过,它是最常用的表。

NAT:里面的rule都是用来处理网络地址转换的,控制要不要进行地址转换,以及怎样修改源地址或目的地址,从而影响数据包的路由,达到连通的目的,这是家用路由器必备的功能。

Mangle:里面的rule主要用来修改IP数据包头,比如修改TTL值,同时也用于给数据包添加一些标记,从而便于后续其它模块对数据包进行处理(这里的添加标记是指往内核skb结构中添加标记,而不是往真正的IP数据包上加东西)

Raw:在netfilter里面有一个叫做connection tracking的功能(后面会介绍到),主要用来追踪所有的连接,而raw表里的rule的功能是给数据包打标记,从而控制哪些数据包不被connection tracking所追踪

NF_HOOK

Netfilter使用NF_HOOK(include/linux/netfilter.h)宏在协议栈内部切入到Netfilter框架中。定义如下:

#define NF_HOOK(pf, hook, skb, indev, outdev,okfn) 
         NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN)

关于宏NF_HOOK各个参数的解释说明:
1) pf:协议族名,Netfilter架构同样可以用于IP层之外,因此这个变量还可以有诸如PF_INET6,PF_DECnet等名字。
2) hook:HOOK点的名字,对于IP层,就是取上面的五个值;
3) skb:网络设备数据缓存区;
4) indev:数据包进来的设备,以struct net_device结构表示;
5) outdev:数据包出去的设备,以struct net_device结构表示;
(后面可以看到,以上五个参数将传递给nf_register_hook中注册的处理函数。)
6) okfn:是个函数指针,当所有的该HOOK点的所有登记函数调用完后,转而走此流程。

我们发现NF_HOOK_THRESH只增加了一个thresh参数,这个参数就是用来指定通过该宏去遍历钩子函数时的优先级,同时,该宏内部又调用了nf_hook_thresh函数:

static inline int
NF_HOOK_THRESH(uint8_t pf, unsigned int hook, struct sk_buff *skb,
           struct net_device *in, struct net_device *out,
           int (*okfn)(struct sk_buff *), int thresh)
{
    int ret = nf_hook_thresh(pf, hook, skb, in, out, okfn, thresh);
    if (ret == 1)
        ret = okfn(skb);
    return ret;
}
static inline int nf_hook_thresh(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 thresh)
{
    if (nf_hooks_active(pf, hook))
        return nf_hook_slow(pf, hook, skb, indev, outdev, okfn, thresh);
    return 1;
}

nf_hook_slow函数用来遍历钩子函数okfn(按照优先级从小到大一次执行)

所以IPv4的钩子挂载点为nf_hooks[2][0-4]

挂载点 nf_hooks
PRE_ROUTING nf_hooks [2] [0]
LOCAL_IN nf_hooks [2] [1]
FORWARD nf_hooks [2] [2]
LOCAL_OUT nf_hooks [2] [3]
POST_ROUTING nf_hooks [2] [4]

NF_IP_PRE_ROUTING (0)

数据报在进入路由代码被处理之前,数据报在IP数据报接收函数ip_rcv()(位于net/ipv4/ip_input.c)的最后,也就是在传入的数据报被处理之前经过这个HOOK。在ip_rcv()中挂接这个HOOK之前,进行的是一些与类型、长度、版本有关的检查。
经过这个HOOK处理之后,数据报进入ip_rcv_finish()(位于net/ipv4/ip_input.c),进行查路由表的工作,并判断该数据报是发给本地机器还是进行转发。
在这个HOOK上主要是对数据报作报头检测处理,以捕获异常情况。

涉及功能(优先级顺序):Conntrack(-200)、mangle(-150)、DNAT(-100)

NF_IP_LOCAL_IN(1)

目的地为本地主机的数据报在IP数据报本地投递函数ip_local_deliver()(位于net/ipv4/ip_input.c)的最后

经过这个HOOK。经过这个HOOK处理之后,数据报进入ip_local_deliver_finish()(位于net/ipv4/ip_input.c)

这样,IPTables模块就可以利用这个HOOK对应的INPUT规则链表来对数据报进行规则匹配的筛选了。防火墙一般建立在这个HOOK上。

涉及功能:mangle(-150)、filter(0)、SNAT(100)、Conntrack(INT_MAX-1)

NF_IP_FORWARD (2)

目的地非本地主机的数据报,包括被NAT修改过地址的数据报,都要在IP数据报转发函数ip_forward()(位于net/ipv4/ip_forward.c)的最后经过这个HOOK。
经过这个HOOK处理之后,数据报进入ip_forward_finish()(位于net/ipv4/ip_forward.c)
另外,在net/ipv4/ipmr.c中的ipmr_queue_xmit()函数,最后也会经过这个HOOK。(ipmr为多播相关,估计是在需要通过路由转发多播数据时的处理)
这样,IPTables模块就可以利用这个HOOK对应的FORWARD规则链表来对数据报进行规则匹配的筛选了。

涉及功能:mangle(-150)、filter(0)

NF_IP_LOCAL_OUT (3)

本地主机发出的数据报在IP数据报构建/发送函数ip_queue_xmit()(位于net/ipv4/ip_output.c)、以及ip_build_and_send_pkt()(位于net/ipv4/ip_output.c)的最后经过这个HOOK。(在数据报处理中,前者最为常用,后者用于那些不传输有效数据的SYN/ACK包)
经过这个HOOK处理后,数据报进入ip_queue_xmit2()(位于net/ipv4/ip_output.c)
另外,在ip_build_xmit_slow()(位于net/ipv4/ip_output.c)和ip_build_xmit()(位于net/ipv4/ip_output.c)中用于进行错误检测;在igmp_send_report()(位于net/ipv4/igmp.c)的最后也经过了这个HOOK,进行多播时相关的处理。
这样,IPTables模块就可以利用这个HOOK对应的OUTPUT规则链表来对数据报进行规则匹配的筛选了。

涉及功能:Conntrack(-200)、mangle(-150)、DNAT(-100)、filter(0)

NF_IP_POST_ROUTING (4)

所有数据报,包括源地址为本地主机和非本地主机的,在通过网络设备离开本地主机之前,在IP数据报发送函数ip_finish_output()(位于net/ipv4/ip_output.c)的最后经过这个HOOK。
经过这个HOOK处理后,数据报进入ip_finish_output2()(位于net/ipv4/ip_output.c,Line160)另外,在函数ip_mc_output()(位于net/ipv4/ip_output.c)中在克隆新的网络缓存skb时,也经过了这个HOOK进行处理。

涉及功能:mangle(-150)、SNAT(100)、Conntrack(INT_MAX)

总结

在内核的IP协议栈里,从协议栈正常的流程切入到Netfilter框架中,然后顺序、依次去调用每个HOOK点所有的钩子函数的相关操作有如下几处:
1)、net/ipv4/ip_input.c里的ip_rcv函数。该函数主要用来处理网络层的IP报文的入口函数,它到Netfilter框架的切入点为:NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,ip_rcv_finish)
根据前面的理解,这句代码意义已经很直观明确了。那就是:如果协议栈当前收到了一个IP报文(PF_INET),那么就把这个报文传到NetfilterNF_IP_PRE_ROUTING过滤点,去检查在那个过滤点(nf_hooks [2] [0])是否已经有人注册了相关的用于处理数据包的钩子函数。如果有,则挨个去遍历链表nf_hooks [2] [0]去寻找匹配的match和相应的target,根据返回到Netfilter框架中的值来进一步决定该如何处理该数据包(由钩子模块处理还是交由ip_rcv_finish函数继续处理)。刚才说到所谓的“检查”。其核心就是nf_hook_slow()函数。该函数本质上做的事情很简单,根据优先级查找双向链表nf_hooks [] [],找到对应的回调函数来处理数据包:

2)、net/ipv4/ip_forward.c中的ip_forward函数,它的切入点为:
NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev,rt->u.dst.dev,ip_forward_finish);
在经过路由抉择后,所有需要本机转发的报文都会交由ip_forward函数进行处理。这里,该函数由NF_IP_FOWARD过滤点切入到Netfilter框架,在nf_hooks [2] [2]过滤点执行匹配查找。最后根据返回值来确定ip_forward_finish函数的执行情况。

3)、net/ipv4/ip_output.c中的ip_output函数,它切入Netfilter框架的形式为:
NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING, skb,NULL, dev,ip_finish_output,
!(IPCB(skb)->flags & IPSKB_REROUTED));
这里我们看到切入点从无条件宏NF_HOOK改成了有条件宏NF_HOOK_COND,调用该宏的条件是:如果协议栈当前所处理的数据包skb中没有重新路由的标记,数据包才会进入Netfilter框架。否则直接调用ip_finish_output函数走协议栈去处理。除此之外,有条件宏和无条件宏再无其他任何差异。
如果需要陷入Netfilter框架则数据包会在nf_hooks [2] [4]过滤点去进行匹配查找。

4)、还是在net/ipv4/ip_input.c中的ip_local_deliver函数。该函数处理所有目的地址是本机的数据包,其切入函数为:
NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev,NULL,ip_local_deliver_finish);
发给本机的数据包,首先全部会去nf_hooks [2] [1]过滤点上检测是否有相关数据包的回调处理函数,如果有则执行匹配和动作,最后根据返回值执行ip_local_deliver_finish函数。

5)、net/ipv4/ip_output.c中的ip_push_pending_frames函数。该函数是将IP分片重组成完整的IP报文,然后发送出去。进入Netfilter框架的切入点为:
NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL,skb->dst->dev, dst_output);
对于所有从本机发出去的报文都会首先去Netfilter的nf_hooks [2] [3]过滤点去过滤。一般情况下来来说,不管是路由器还是PC终端,很少有人限制自己机器发出去的报文。因为这样做的潜在风险也是显而易见的,往往会因为一些不恰当的设置导致某些服务失效,所以在这个过滤点上拦截数据包的情况非常少。当然也不排除真的有特殊需求的情况。

整个Linux内核中Netfilter框架的HOOK机制可以概括如下:

剑气纵横三万里

“为什么要努力?” “想去的地方很远,想要的东西很贵,喜欢的人很优秀,父母的白发,朋友的约定,周围人的嘲笑,以及,天生傲骨。”

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐

暂无内容!