开始之前
关于本教程
目前,信息要通过管道(也就是网络)传输,需要花很多时间封装在数据包中。在本教程 中,我们将在这些数据包传输过程中捕获它们,捕获数据包所采用的平台是 Linux。
大多数网络应用程序――从虚拟专用网(VPN)到路由程序,再到嗅探器――都具有某种 数据包捕获机制。因此,编写此类软件的任何人都可以从本教程中受益。
由于我们将要研究的几种数据包过滤机制都是内核模块,所以还将简要地介绍这些模块以 及内核编译。
我们还将回顾一些我没有成功使用的机制:我只能获得数据包的一个副本而无法截获最初 的数据包。这里的讨论不仅能省去您重复我所做工作的麻烦,而且还对编写网络应用程序(如嗅探器) 很有用。
除大致熟悉不同的数据包捕获机制(如防火墙钩子、divert socket 和 netfilter)外; 读者还应具备有关 Linux 网络和 TCP/IP 协议栈方面的知识。开始之前最好了解一些源代 码的知识。
回页首
前提条件
本教程最适合那些以前在系统编程、Linux 网络和 Linux 内核模块方面 至少有一些经验的读者。但是,本教程尽可能简单地给出一些概念,并在适当的时候给 予详细解释,因此,即使读者缺少一种或多种这方面的知识,也能从本文的讨论中获益。
简介
数据包捕获
TCP/IP 协议栈是 Linux 网络的重要支柱,其体系结构设计得非常优美。数据包 是 TCP/IP 协议栈 中信息流的载体。在 Linux 网络编程中,许多有意义的工作都包括了捕获这些信息丰富的数据包、提取或操作这些数据包所包含的信息。
数据包捕获 对我们而言意味着某种机制,即获取一个数据包,直到达到所要求的目 的后才将其释放,以便该数据包能够按照常规路径通过其余的任何处理。其他相同或相似 操作的术语有 数据包过滤、数据包嗅探、网络嗅探 以及 数据包 或 网络监视。
数据包过滤是许多网络软件的基础,这些软件有网络监视工具、VPN、网络分析程序、路由 程序、入侵监测系统(Intrusion Detection Systems,IDS)和嗅探器。
Linux 提供了多处可以捕获数据包的位置,既包括用户空间,又包括内核空间。下图展示了网络数据包流过 TCP/IP 协议栈的路径:
图 1. 网络数据包通过协议栈的路径
如上图所示,流入的数据包通过以太网层、网络层、TCP 协议栈并穿过套接字层, 然后才能被复制到用户空间中。在所有这些断点处,数据包都很容易被捕获到。本教程讨 论的方法大多在网络层和用户空间中有效。
然而,由于 Linux 和 Linux 内核在不断发展,这里讨论的某些数据包捕获方法仅在特定 的内核中有效。例如,divert socket 对打过补丁的 2.2.12 内核有效,而 netfilter 对 内核 2.4.x 和 2.6.x 有效。在讨论各种方法时始终要注意这些细节。
Linux 可加载的内核模块(LKM)概述
捕获数据包的模块
本教程探讨的大多数数据包捕获机制都是以 Linux 内核模块的方式工作的,因此我们现在简 要讨论一下这些模块。如果您有很强的 LKM 背景,则可以跳至下一节 编译内核 。
回页首
什么是 LKM?
可加载内核模块 是内核的扩展,可以在需要时附加到内核中,或从内核中删除。LKM 基本上是设备驱动程序的软件实现,它与真实的或者虚拟的硬件设备协同工作。当 LKM 加 载到内核中时,它们监听和处理对设备的任何请求。由于只加载所需的 LKM,因此使 用 LKM 可使内核变得轻便、模块化、灵活和可伸缩。
回页首
LKM 体系结构
编写 Linux 内核模块的体系结构如下:
init_module
cleanup_module
回页首
编译 LKM
编写 LKM 后,可以采用下列命令编译它:
gcc -c -g |
该命令产生一个名为
insmod -f |
-f
选项意味着强制加载。)
如果在加载模块时遇到任何内核版本问题,可以通过在加载 LKM 时包含这个特定内核 的头文件来解决问题。因此,在编译内核时,请使用 -I/usr/src/linux/include
。
还可以使用 Makefile 来解决此类版本问题,但这超出了本教程的范围。在本教程的末 尾,您还可以在 参考资源 中找到有关 LKM 编程的更多信息。
回页首
卸载 LKM
一旦模块加载到内核中,它将开始执行预定的功能。使用命令 lsmod
可 以看到当前所有加载的 LKM 的列表。
可以使用 rmmod
从内核中加载或删除模块。
编译内核
回顾
本教程讨论的许多机制都要求设置某些内核选项,然后重新编译内核。因此在开始前,我 们将复习内核编译的步骤。如果您已经了解内核编译,则可以跳至下一 节 数据包截获:防火墙钩子 ,在那里开始复习数据包捕获机制。
回页首
步骤
重新编译内核:
make xconfig
make menuconfig
或 make config
;这只是个人偏好问题。)make dep
make bzImage
make install
make modules
make modules_install
接着,您应将关于最近编译内核的信息添加到 /etc/lilo.conf(如果不在该位置的 话),然后重新启动机器。您会在重新启动过程的开始看到列出的新内核选项。
数据包截获:防火墙钩子
概述
防火墙钩子(Firewall hook) 已引入到 2.2.16 内核中,它是 2.2.x 内核运行的数据包截获方法。 防火墙钩子在 TCP/IP 协议栈的 IP 层截获数据包。
防火墙钩子在功能上充当 Linux 可加载内核模块(LKM)或伪设备驱动程序,可以根据 需要从内核中加载或卸载。
要使用防火墙钩子,请在内核编译期间启用 firewalling 选项 (在 Networking 选项下列出)。
您可以使用该数据包截获机制来开发路由程序、VPN、数据包嗅探器或位于网络边 缘且要求实时捕获数据包的任何其他网络应用程序。
回页首
填充结构
内核定义的 firewall_ops 结构是防火墙钩子的基础。可以用这些结构来指定各种 数据包策略,这些策略可以是非常特殊的,也可以是很普通的。
firewall_ops 结构位于 /usr/src/linux/include/linux/firewall.h 中,类似如下:
struct firewall_ops { struct firewall_ops next; int (*fw_forward)(struct firewall_ops *this, int pf, struct device *dev, void *phdr, void *arg, struct sk_buff **pskb); int (*fw_input)(struct firewall_ops *this, int pf, struct device *dev, void *phdr, void *arg, struct sk_buff **pskb); int (*fw_output)(struct firewall_ops *this, int pf, struct device *dev, void *phdr, void *arg, struct sk_buff **pskb); int fw_pf; int fw_priority; }; |
这种机制是作为 LKM 来实现的。同样,它需要 init_module
(用于初始 化模块)、cleanup_module
和某些特定处理的函数:
next |
指向下一个钩子的指针 |
fw_forward |
转发数据包的函数指针 |
fw_input |
流入数据包的函数指针 |
fw_output |
流出数据包的函数指针 |
fw_pf |
协议簇 |
fw_priority |
所选防火墙的优先权 |
这些都是实际的函数指针;相应的函数在下一个屏中定义。
回页首
函数
为了处理数据包,需要定义三个函数:
流入的数据包处理:
static int fw_input(struct firewall_ops *this,int pf,struct device *dev,void *phdr,void *arg,struct sk_buff **pskb) |
对所有流入的数据包都要调用该函数。如果需要对流入的数据包进行任何处理(也称为 "mangling"),如添加额外的字段,可以在此进行处理。
流出的数据包处理:
static int fw_output(struct firewall_ops *this,int pf,struct device *dev,void *phdr,void *arg,struct sk_buff **pskb) |
对所有流出的数据包(源自主机)调用该函数。可以在这里对此类数据包进行任何额外处理。
转发数据包处理:
static int fw_forward(struct firewall_ops *this,int pf,struct device *dev,void *phdr,void *arg,struct sk_buff **pskb) |
对所有转发的数据包调用该函数。
下面是在上述函数中传递参数的详细信息。
*this |
指向防火墙钩子结构的指针 |
pf |
代表协议簇 |
*dev |
指向以太网卡设备结构的指针 card |
*phdr |
指向 IP 地址缓冲区的指针 |
*arg |
额外的参数,在需要时传递给函数 function |
**pskb |
指向 TCP_IP 协议栈的 sk_buff 层的指针 |
对于上述每个函数,数据包的返回值(或对数据包的操作)可以是:
FW_ACCEPT
宏完成。它接受数据包并且数据包遵循正常的 TCP/IP 协议栈路径。FW_REJECT
宏完成。它拒绝所有数据包,既没有网络流量流出, 又没有网络流量流入。FW_REDIRECT
宏完成。它将数据包重定向到特定的主机。回页首
函数(续)
现在,我们将把上一屏中定义的函数赋值给 firewall_ops 结构中的指针。一旦赋值 完成,firewall_ops 结构将被填充,函数将由系统本身调用――这就是术语 回调函 数。
下面是要填充的字段:
struct firewall_ops * next; /*Pointer to the next firewall hook structure */ int fw_pf; /* Protocol family */ int fw_priority; /* Priority of chosen firewalls */ |
现在,防火墙结构与下面的代码类似:
/* Filling the firewall_ops structure */ static struct firewall_ops myOps = {NULL, fw_forward, fw_input, fw_output, PF_INET, /* Protocol family */ 1 /* Priority of chosen firewalls*/ }; |
回页首
Kernel 注册
现在,您需要将 firewall_ops 结构注册到内核:
register_firewall(protocol family, struct firewall_ops *); |
在 LKM 的 init_module
代码中进行注册。
回页首
取消注册 firewall_ops 结构
当卸载 LKM 时,cleanup_module
函数应包含
unregister_firewall(protocol family, struct firewall_ops *); |
对 LKM 进行编码、编译并加载到内核后,您的数据包拦截器就准备工作了。
完成在接下来的两屏中给出的这个 LKM 的源代码和使用说明。
回页首
源代码:防火墙钩子程序
/* This is an insertable module that uses the firewall hooks mechanism on 2.2.16 to intercept a packet */ /* gcc -O -c NetFWHook.c -I/usr/src/linux/include*/ /* No one can then telnet,ftp,ping your machine */ /*NetFWHook.c*/ #define MODULE #define __KERNEL__ #include |
回页首
使用防火墙钩子程序
gcc -c -O NetFWHook.c -I/usr/src/linux/include/ |
/sbin/insmod -f NetFWHook.o |
dmesg
命令。数据包截获:Netfilter
概述
Netfilter 是由内核 2.4.x 和 2.6.x 提供的数据包截获机制,它替代了内核 2.2.x 中 使用的 ipchains、防火墙钩子和其他方法。Netfilter 也可以作为 LKM 获得。
要使用 netfilter,在内核编译时设置 Packet Filtering 选项。
可以对采用防火墙钩子机制的同类应用程序使用 netfilter 机制,这些应 用程序有:路由程序、数据包嗅探器和其他位于网络边缘并访问通信流的实体。
回页首
使用 Netfilter
Netfilter 可以在通过 TCP/IP 协议栈的路径中的几个定义良好的点上捕获数据包:
NF_IP_PRE_ROUTING
NF_IP_LOCAL_IN
NF_IP_FORWARD
NF_IP_LOCAL_OUT
NF_IP_POST_ROUTING
当数据包穿过 TCP/IP 协议栈后,协议调用带有数据包和钩子号的 netfilter 框架。钩子也 可以指派优先级。
函数的返回值包括:
NF_ACCEPT
NF_DROP
NF_STOLEN
NF_QUEUE
NF_REPEAT
回页首
步骤
Netfilter 的工作方式与防火墙钩子非常相似。作为 LKM 注册到内核的结构 调用特定于进程的函数。任何基于 netfilter 的数据包拦截器都必须遵循开发 LKM 所采 用的步骤。
回页首
特定于进程的函数
特定于进程的函数(或钩子)的原型如下所示:
static unsigned int packet_interceptor_hook(unsigned int hook, struct sk_buff **pskb, const struct net_device *indev, const struct net_device *outdev, int (*okfn) (struct sk_buff *)) |
您可以将字段定义为:
hook
NF_IP_LOCAL_OUT
、NF_IP_LOCAL_IN
、NF_IP_ FORWARD
等。**pskb
*indev & *outdev
(*okfn) (struct sk_buff *)
回页首
netfilter 结构
核心 netfilter 结构在 /usr/src/include/linux/netfilter.h 中定义,类似如下:
struct nf_hook_ops { struct list_head list; /* User fills in from here down. */ nf_hookfn *hook; int pf; int hooknum; /* Hooks are ordered in ascending priority. */ int priority; }; |
参数是:
list
{ NULL, NULL }
。hook
NF_ACCEPT
、NF_DROP
或 NF_QUEUE
。如 果返回 NF_ACCEPT
,则下一个钩子将被附加到将要调用的点。如 果返回 NF_DROP
,则数据包被丢弃。如果返回 NF_QUEUE
,则对数据包 进行排队。sk_buff 指针被传递到该函数中,并用数据包信息如 IP 报头、TCP 报头等进 行填充。您可以使用 sk_buff 结构指针来操作或删除数据包(要删除数据包,只需将 skb 指 针设置为空即可)。pf
PF_INET
。hooknum
NF_IP_LOCAL_IN
等。回页首
内核注册
在 LKM 的 init_module
函数中,需要注册在内核中填充的结构:
int nf_register_hook(struct nf_hook_ops *req); |
这里,nf_hook_ops 是 netfilter 操作结构。
一旦该结构注册到内核中,Linux 将调用这里定义的函数来处理数据包。
回页首
取消注册 netfilter 结构
卸载 LKM 时,netfilter 结构需要从内核中取消注册。这一操作在 cleanup_module
函数中完成:
void nf_unregister_hook(struct nf_hook_ops *req); |
nf_hook_ops 也是 netfilter 操作结构。
数据包截获:Divert socket
概述
2.2.12 内核的其中一个补丁引入了一种新类型的原始套接字(raw socket),称 为 divert socket,该套接字按照防火墙规范过滤数据包,并将其发送到用户空 间。数据包从这里开始进行处理或简单地再放回到 TCP/IP 协议栈中。
Divert socket 是一种特殊类型的原始套接字,与任何其他套接字一样,通过该套接字 可以接收和发送数据包。Divert socket 使用 Linux 内核的 firewalling 功能:可以 将 Linux firewalling 设置为对流入、流出和转发数据包应用受限的策略,然后将其重 定向到指定的端口。Divert socket 将在该端口进行监听,然后进一步重定向数据包。
使用 divert socket 的主要缺点是将数据包复制到用户空间的开销,这会占用时间 和资源,从而降低了网络性能。
任何没有对数据包实时处理施加硬性限制的联网应用程序(如嗅探 器),您都可以使用 divert socket。
回页首
内核编译选项
Linux firewalling 是使用 divert socket 的先决条件,因此应在内核编译时启用该 选项。某些选项已专门为 divert socket 引入到内核中,并应在编译时设置它们:
ipchains 控制网络数据包传输的路径。要将 ipchains 与 divert socket 一起使用, 应下载、编译和安装打了补丁的 ipchains 版本 ipchains-1.3.9。
在下一屏对 ipchains 进行一般性介绍。
回页首
ipchains 简介
转发的数据包按以下顺序穿过链:
数据包截获:内核改动
概述
在此,我们将操作内核源代码以扩展内核本地数据包截获能力。
内核源文件位于 /usr/src/linux,要修改的两个主要文件 是 ip_input.c 和 ip_output。这两个文件位于 /usr/src/linux/net/ipv4/ 目录中。
回页首
源文件修改:ip_input.c
ip_input.c 包含这两个函数,可以为所有流入数据包调用它们: incoming packets:
ip_local_deliver(struct sk_buff, struct device,struct packet_type) |
为所有的流入数据包执行而不考虑目的地
ip_rcv(struct sk_buff, struct device,struct packet_type) |
为目的地是本地机器的所有数据包进行调用
sk_buff
device
packet_type
ip_rcv
执行 IP 层所需的几个任务,包括重新装配、校验和计算等。
由于 ip_rcv
处理所有流入的 IP 数据包,因此这是一个添 加要对流入数据包执行任何附加代码的好地方。其他选项是用于编写自己 的 ip_rcv
例程的。
回页首
源文件修改:ip_output.c
与 ip_output 一样,ip_output.c 位于 /usr/src/linux/net/ipv4/ 目录中,它处理 流出的数据包。它的四个函数是:
ip_build_and_sent_pkt(struct sk_buff,struct sock,u32 saddr,u32 daddr, struct ip_options) |
将 IP 报头添加到 sk_buff 并将其发送出去
ip_queue_xmit(struct sk_buff) |
对要发送的数据包排队
ip_build_xmit_slow() |
构建和传送数据包
ip_build_xmit() |
“快速”构建和传送数据包,该选项只用于无需碎片整理的选项。
只有 ip_build_xmit_slow
和 ip_build_xmit
处理所有 流出的 IP 数据包,因此任何额外处理均可在任何阶段完成。对 于 ip_input.c,其他选项用于编写自己的例程,然后从这些例程中调用最初的函数。您 选择的策略依赖于您要实现的功能。
回页首
使用 Makefiles 修改源代码
可以使用 Makefiles 动态修改内核源代码;Makefile 脚本确保对源代码做必需的 修改。可以使用一条命令(make install
或 make uninstall
)添加或删除数据包拦截器,然后重新编译内核并重新启动机器。
由于该机制要求修改 Linux 内核源代码,所以快速调试可能非常困难。而且,由于该 方法要求重新编译内核,所以这个过程是冗长、复杂且不灵活的。
数据包截获:创建数据包的副本
概述
某些数据包捕获机制仅提供数据包的副本而非数据包本身。但是,我们前面提到过,在开发 网络嗅探器应用程序时,数据包的副本仍然是很有用的。
回页首
协议处理程序
协议处理程序是另一种充当 LKM 的机制。
协议处理程序对处理通过以太网卡传来的数据包。为此,协议处理程序必须注册到网络设备中;在本例中,应该注册到以太网卡的设备结构中。
网卡设备结构还包含一个指向 init 函数的指针,该函数初始化设备。
回页首
实现协议处理程序
实现协议处理程序的步骤:
现在,通过以太网接口的任何数据包都能得到正确处理。对 数据包的副本 而非最初 的数据包进行处理。不可能通过 TCP/IP 协议栈转移来自最初路径的数据包,因此只有数据包的 副本到达协议处理程序本身。
回页首
中断处理程序
顾名思义,该机制是以中断处理作为基础的,也可以作为 LKM 来实现。不论何时,只 要有数据包到达以太网卡,就会生成中断。通过编写自己的中断处理程序,您就可以捕获数据包 并对其进行任何必要的处理。
幸运的是,无论何时只要有数据包穿过 TCP/IP 协议栈,就会生成中断,这会设置以太网卡中的标志,表 示数据包是流入的还是流出的。这个中断通常是中断 9,因此,您可以编写中断处理程序 来处理中断 9 的信号,然后处理数据包。
在这种情况下,由于有了协议处理程序,只有 数据包的副本 发送给中断处理程序,而 最初的数据包未受任何阻碍,通过 TCP/IP 协议栈正常传输。
结束语和参考资料
数据包截获机制的总结
机制 | 模块? | 可伸缩? | 性能(TCP/IP 吞吐量) | 可插入/可删除? |
防火墙钩子 | 是,可以将其作为 LKM 插入 | 是,可以通过更改 LKM 的源代码并重新编译来增强该性 能 | 降低大约 50% | 是,使用 insmod 命令 |
Netfilter | 是,可以作为 LKM 插入它 | 是,可以通过更改 LKM 的源代码并重新编译来增强该功能 | 下降大约 50% | 是,使用 insmod 命令 |
Divert socket | 否,divert socket 数据包必须直接应用到内核代码 | 冗长乏味,因为整个内核需要编译 | 太慢而无法度量 | 否,因为它不是模块 |
内核改动 | 否,内核数据包拦截器代码必须直接集成到内核源代码中 | 冗长乏味,因为整个内核需要编译 | 通常快于 LKM 方法(如果正确实现) | 是,如果将 Makefile 用于安装和卸载 |
防火墙钩子和 netfilter 是模块化的、灵活的和动态加载的(可删除) ,它们只对系统的性能产生适度的影响。
对于预计流量很大且实时处理时有硬性限制的应用程序而言,最适合使用内核改动方法。
如果数据包处理没有计时问题,则可以使用 Divert socket。Divert socket 在用户空间中是一种分 析和控制网络流量的非常有用的方法。
回页首
反馈
请让我们知道本教程是否对您有帮助,以及我们如何才能做得更好。我们 还希望了解您想要阅读的其他教程主题。
如果对本教程的内容有疑问,请通过 [email protected] 与作者 Ashish Chaurasia 联系。
参考资料
Linux Device Drivers, 2nd Edition ,作者:Alessandro Rubini 和 Jonathan Corbet(O'Reilly & Associates,2001),该书讨论了设备驱动程序编程;还讨论了 使用 Makefiles 来解决版本问题(这已在 Linux 可加载的内核模块(LKM)概述 中讨论了)。
Linux Kernel Internals ,作者:Michael Beck、Harold Bohme、Ulrich Kunitz、Robert Magnus、Mirko Dziadzka 和 Dirk Verworner(ACM,1996),该书阐述了 Linux 的内部机制,内容包括从进程 调度到内存管理和文件系统,还讨论了内核本身。
Di vert Sockets mini-HOWTO,作者:Ilia Baldine(2000 年 2 月),给出了这种数据包截获方法的大量信息。
有关 Netfilter 的更多信息,请 参阅 netfilter Web site 或 Netfilter summary of information(作者:V.R. Sundar 和 Karthik Dantu)。另请参阅 Linux netfilter Hacking HOWTO。
对于较新的内核,则请参考 “Linux 2.4 Packet Filtering HOWTO”,即 Rusty's Remarkably Unreliable Guides 之一(其他包括 Networking Concepts HOWTO、Linux 2.4 NAT HOWTO 和 上面列出的 Netfilter Hacking HOWTO)。不过要注意,它们比系列名称所暗示的那样更可靠。
“Linux iptables HOWTO”介绍 如何使用 iptable 来过滤出 Linux 内核 2.3.15 和更高版本的损坏数据包。Linux iptables HOWTO: using iptables 也很有用。
Linux 2.4 Advanced Routing HOWTO 介绍了 iproute2 的使用――需要隧道时,选择它就很好。
Guide to IP Layer Network Administration with Linux 中的 Links to Documentation 一节很精彩,介绍从一般联网到 iproute2、netfilter、 ipchains 以及更多内容。它们的 Links to Software 也非常精彩。
Phrack 杂志介绍了从安全问题到攻击无线广 播和世界新闻等主题,以及有关 Linux 内核和内核模块的许多很精采的文章。
诗句 “If a packet hits a pocket on a socket on a port” 摘自 A Grandchild's Guide to Using Grandpa's Computer。
有关 LKM 的更多信息,请参阅论文 “The Linux Kernel Module Programming Guide”(Peter Jay Salzman 和 Ori Pomerantz,2001) 和 “Linux Loadable Kernel Module HOWTO”(Bryan Henderson,2004)。
Fairly Fast Packet Filter (FFPF) 最大限度减少了数据包复制,准备用于替代 netfilter。
Linux Device Drivers, 2nd Edition 作者:Alessandro Rubini & Jonathan Corbet(O'Reilly & Associates,2001) 可在 O'Reilly 的 xml.com 上在线获得。本书的所有章节都很有用;特别 是 Chapter 9: Interrupt Handling 更是如此。
Linux 开发人员可以在 developerWorks Linux 专区 获得更多资源。
在 Developer Bookstore 的 Linux 区,可以找到很多有关 Linux 的书籍。