1 Netfilter/iptables
1.1 Netfilter/iptables概述
Netfilter实际上为linux开发的第三代网络防火墙,其之前的版本与linux内核版本对应的关系如表1所示:
内核版本 | Linux 防火墙版本 |
Kernel-2.0.x | Ipfwadm |
Kernel-2.2.x | Ipchains |
Kernel-2.4.x/Kernel-2.6.x | Netfilter/Iptables |
表1:linux内核与linux防火墙之间的关系
Netfilter的实现采用高度的模块化结构,内核开发者可以很容易的基于Netfilter提供的架构实现定制模块,完成需要的功能。2.4之后netfilter开发了iptables用户控制工具,用户通过iptables连接到内核态的netfilter处理架构,完成网络数据包的过滤、修改、NAT及其他复杂的功能。
Netfilter源码结构如表2所示:(linux内核版本:2.6.32)
项目名称 | 位置 |
Netfilter主文件 | /net/netfilter/ |
Netfilter主头文件 | /linux/net/netfilter/ |
Netfilter Ipv4相关文件 | /net/ipv4/netfilter/ |
Netfilter Ipv6相关文件 | /net/ipv6/netfilter/ |
Linux2.6.14之后,Netfilter在架构设计上做了比较的改变,其希望Netfilter的模块与协议是无关的。其实现了部分模块与协议无关,这些模块存放在/net/netfilter/目录下,一般以xt_前缀开头。
1.2 Netfilter/iptables总体架构
Netfilter/iptables 通过一系列的表、链实现规则的管理,表、链相当于Netfilter规则的数据库,Netfilter通过该数据库的规则完成数据包的匹配、修改、NAT等功能。Iptables功能模块通过修改内存中的表、链中的规则已完成其对内核中Netfilter的控制。
Netfilter/iptables总体架构主要分为以下几部分:
Ø Netfilter HOOK
Ø Netfilter /Iptables 基础模块
Ø 基于Netfilter实现具体功能模块
1.2.1 Netfilter hook
Netfilter最为核心的机制就是对外提供了五个Hook点,分别为PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING,其实现与具体协议无关。具体功能模块一般都讲处理模块挂载到某个或某几个Hook点上完成相应数据包的处理工作。例如,iptables filter内核功能模块,该模块分别挂载到了INPUT、OUTPUT、FORWARD三个hook点上,Netfilter通过该模块配合iptables工具下发的管理规则完成数据包的具体处理工作。
图1展示了Netfilterhook在linux网络协议栈的具体位置
图1 netfilter hook挂载位置
对照图1,简单介绍一下数据包在netfilter中各个HOOK点的处理流程。
数据报从进入系统,进行IP校验以后,首先经过第一个HOOK点 NF_IP_PRE_ROUTING进行处理;然后就进入路由代码,其决定该数据报是需要转发还是发给本机的;若该数据报是发被本机的,则该数据经过HOOK点NF_IP_LOCAL_IN处理以后然后传递给上层协议;若该数据报应该被转发则它被NF_IP_FORWARD处理;经过转发的数据报经过最后一个HOOK点NF_IP_POST_ROUTING处理以后,再传输到网络上。本地产生的数据经过HOOK点NF_IP_LOCAL_OUT 处理后,进行路由选择处理,然后经过NF_IP_POST_ROUTING处理后发送出去。
1.2.2 Netfilter/iptables 基础模块
基于Netfilter提供的基础架构,iptables在内核中实现了四种模块分别为:filter、mangle、nat、raw。其主要完成hook处理函数的实现和注册工作。Linux-2.6.x内核中其主要对应于iptable_filter.c、iptables_mangle.c、iptables_nat.c、iptables_raw.c四个文件。
1.2.3具体功能模块
Linux -2.6.32 下Netfilter/iptables提供的功能模块主要如下:
Ø 数据包过滤模块(filter)
Ø 数据包修改模块(mangle)
Ø 网络地址转换模块(nat)
Ø 数据包穿越防火墙加速模块(raw)
Ø 链接跟踪模块(connection track)
Ø … …
1.3 Netfilter 模块扩展方式
Netfilter实现的基础架构是协议和功能无关的,其主要就是在linux网络协议栈中挂载了五个HOOK处理点。开发人员可以根据具体的功能需求,编写不同功能HOOK处理函数,然后将其挂载到Netfilter相应的HOOK点上,当数据包流经改HOOK点时,相应的HOOK函数就会被回调并执行一系列的定义好的处理流程。
下面主要介绍一下netfilter模块扩展的基本方式:
1.3.1 nf_hook_ops
首先介绍一下netfilter hook机制的核心数据结构:nf_hook_ops(/include/linux/netfilter.h)
|
下面对nf_hook_ops成员具体介绍一下:
l hooknum 代表netfilter中五个hook点,其定义在(/include/linux/netfilter.h)中
|
l nf_hookfn *hook,为hook处理函数的的指针,其原型定义在(/include/linux/netfilter.h)
|
Ø struct sk_buff *skb sk_buff表示linux内核中数据包的缓冲结构,网卡在接收到一个数据包之后,就会将数据缓存到sk_buff结构中,然后将其递送给网络协议栈,之后在协议栈的整个处理流程中基本上都是围绕sk_buff展开的。该结构具体定义在(/include/linux/skbuff.h)中。
Ø const struct net_device *in,表示输入设备,即网络数据包进入网络协议栈时的网卡设备。
Ø const struct net_device *out, 表示输出设别,即网络数据包离开网络协议栈输出时的网卡设备。
Hook函数完成处理后,必须返回特定的值,该值定义在(/include/linux/netfilter.h)
|
Ø NF_DROP(0):丢弃此数据报,禁止包继续传递,不进入此后的处理流程;
Ø NF_ACCEPT(1):接收此数据报,允许包继续传递,直至传递到链表最后,而进入okfn函数;以上两个返回值最为常见
Ø NF_STOLEN(2):数据报被筛选函数截获,禁止包继续传递,但并不释放数据报的资源,这个数据报及其占有的sk_buff仍然有效(e.g. 将分片的数据报一一截获,然后将其装配起来再进行其他处理);
Ø NF_QUEQUE(3):将数据报加入用户空间队列,使用户空间的程序可以直接进行处理;
Ø NF_REPEAT(4):再次调用当前这个HOOK的筛选函数,进行重复处理。
Ø NF_STOP(5):2.6内核中的NF动作增加了NF_STOP,功能和NF_ACCEPT类似但强于NF_ACCEPT,一旦挂接链表中某个hook节点返回NF_STOP,该skb包就立即结束检查而接受,不再进入链表中后续的hook节点,而NF_ACCEPT则还需要进入后续hook点检查。
l priority,该值越小,优先级越高,其定义在(/include/linux/netfilter_ipv4.h)
|
1.3.2 hook的注册和注销
Netfilter提供了hook函数的注册和注销的函数,(/include/linux/netfilter.h)分别定义如下:
l int nf_register_hook(structnf_hook_ops *reg);
intnf_register_hooks(struct nf_hook_ops *reg, unsigned int n);//完成多个nf_hook_ops注册
l void nf_unregister_hook(structnf_hook_ops *reg);
voidnf_unregister_hooks(struct nf_hook_ops *reg, unsigned int n);//完成多个nf_hook_ops注销
1.4 Netfilter 示例模块
下面简单编写了一个hook处理模块,完成的功能是:禁止ping命令。该hook函数首先会解析数据包的协议类型,如果为imcp数据包就会将其丢弃。
l hook函数定义如下:
|
l nf_hook_ops结构定义如下:
|
l Makefile
|
l 源文件见压缩包
2 Netlinksocket
Netlinksocket 为内核空间与用户空间之间的通信 IPC机制, 其经常被用来完成IP 网络相关的设置工作,RFC3485对其实现进行详细的介绍。
Netlinksocket 能够利用标准的sockets APIs完成从socket 打开、关闭、传输、接收的操作。例如,系统调用socket:
|
我们可以通过man socket获得非常详细的关于TCP/IP下的socket系统调用的参数的说明信息。
对于netlinksocket,socket系统调用的三个参数的定义如下:
l domain: PF_NETLINK
l type:SOCK_DGRAM
l protocol:可以是自定义的或系统提供的,其定义在(include/linux/netlink.h)
|
在使用Netlink socket时,通信端点之间通过进程ID进行识别,特别的内核的标识为0。Netfilter socket可以完成消息的单播和多播发送:发送的目的地可以是一个进程PID,或者一个组ID,或者两者之间的结合体。内核定义了一些列的广播组,其目的是为一些特殊的事件发送通知,用户空间的程序可以关注其感兴趣的组ID,内核中这些组的定义位于/include/linux/rtnetlink.h.
相比于ioctl netlink的优势为:内核可以初始化一个传输过程,完成其与用户空间的通信。而ioctl只能被动的回应用户空间的请求。
2.1 Netlink 使用方式
下面通过使用netlink实现的用户空间与内核空间通信的简单例子介绍netlink相关接口函数的使用方式。
该测试demo完成的功能如下:首先,用户空间向内核空间发送一条信息“Hello kernel”,内核在收到以后会向用户空间发送“Hello user ”。
下面介绍用户空间和内核空间程序的编写步骤:
2.1.1 用户空间
l 创建netlink socket
|
其中,NETLINK_TEST是自定义的,前面介绍了系统定义的一些其他的类型。
l 初始化本地sockaddr_nl结构
|
l 将创建的socket_fd与本地socketaddr_nl绑定
|
l 初始化对端socketaddr_nl结构
|
l 初始化netlink消息头
|
l 初始化消息体
memcpy(NLMSG_DATA(nlh), "Hello Kernel", 12);
iov.iov_base = (void*)nlh;
iov.iov_len = nlh->nlmsg_len;
msg.msg_name = (void*)&dest_addr;
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
l 发送消息,使用的是sendmsg,也已可以使用sendto
sendmsg(sock_fd, &msg, 0) |
l 接收内核发送的消息,使用的是recvform,也可以使用recvmsg
|
2.1.2 内核空间
内核空间比用户空间的实现稍微附在一些,主要分为模块初始化函数、netlink消息接收处理函数、消息发送函数、模块退出函数。
l 模块初始化函数
Netlink内核socket的创建比较复杂,主要是通过netlink_kernel_create()函数完成,其原型如下:
|
Netlink_kernel_create函数在内核的各个版本中的定义稍有不同,在2.6.32中其参数的个数为6个,其中第一个参数net一般使用的系统全局的变量,我们不需要定义。input函数指针表示netlink收到消息后调用的回调函数,我们主要在里面完成消息的解析处理工作。
其返回指向struct sock结构的指针,该结构相当于用户空间的socket文件句柄。
l 消息接收处理函数
|
该函数主要完成的工作就是解析netlink 消息,保存用户空间的进程pid,之后向用户空间发送消息。
l 消息发送函数
|
该函数主要完成发送消息的组装,然后利用netlink_unicast函数将消息发送到进程号为user_pid的用户进程。
l 模块退出函数
退出模块主要完成一些资源清理、释放的工作,最后释放内核netlink通信socket
|
3 内核模块编译
Linux2.6内核中,由于采用了新的“kbuild”构建系统,现在构建系统模块相比于以前容易了很多。构建过程的第一步就是决定在哪里管理模块源码。一般有两种选择:放在内核源码树中,或者作为一个补丁或者最终把代码合并到内核源码树中;放在内核源码树之外构建、维护你的模块代码。
3.1 内核源码树中构建
首先,我们需要明确我们的模块代码应该放在哪个内核目录下面,设备驱动程序一般放置在/drivers目录下,在其内部,设备驱动程序被进一步按照类别、类型或特殊驱动程序等更有序的方式组织在一起。如字符设备存在于/drivers/char 目录下,块设备存在于/drivers/block/目录下等。
假如你编写一个netfilter模块文件,而且希望将他存放于/net/netfilter/目录下,那么要注意,在该目录下存在大量的.c源码文件。如果你的模块文件仅仅只有一两个源文件,你可以直接将其放在该目录下,如果你的模块包含的源文件比较多的话,也许你应该建立一个单独的文件夹,用于专门维护你的模块程序源文件。假如创建一个目录名为:mynetfilter/子目录。接下来需要修改/net/netfilter/目录下的Makefile文件:
Obj-m += mynetfilter/ |
这行编译指令告诉模块构建系统,在编译模块时需要进入mynetfilter/子目录。如果你的模块程序依赖于一个特殊的配置选项。比如,CONFIG_ MYNETFILTER_TEST(该选项在编译内核时,执行make menuconfig命令时用于配置该模块的编译选项),你需要修改/net/netfilter/目录下的Kconfig文件
config “MYNETFILTER_TEST” tristate “netfilter test module” |
编译内核时,执行make menucofnig之后,我们会在配置菜单上看到此选项:
随之,需要修改Makefile文件,用下面的指令替换之前的:
Obj-$(CONFIG_MYNETFILTER_TEST) += mynetfilter/ |
最后,在/net/netfilter/mynetfilter/目录下添加一个Makefile文件,其中需要添加下面的指令:
Obj –m += mynetfilter.o |
准备就绪了,现在构建系统会进入到mynetfilter/目录下,将mynetfilter.c编译为mynetfilter.ko模块。
3.2 内核源码树之外构建
如果将模块代码放在内核源码树之外单独构建的话,你只需要在你的模块目录下创建一个Makefile文件,添加一行指令:
Obj-m := mynetfilter.o |
如果你有多个源文件只需添加另一行指令:
mynetfilter-objs := mynetfiler-init.o mynetfiler-exit.o |
木块在内核内和内核外构建的最大的区别在于构建过程。当模块在内核源代码树之外构建时,你必须告诉make如何找到内核源代码文件和基础Makefile文件。通过下面的指令完成上述功能:
make –c /kernel/source/location SUBDIRS=$PWD modules |
其中,/kernel/source/location/ 即为你配置的内核源代码树的位置。注意不要将你的内核源码树放在/usr/src/linux 目录下,而是放在/home目录某个方便访问的地方。