Linux 中的网络数据包捕获

您所在的位置:网站首页 deviate和divert Linux 中的网络数据包捕获

Linux 中的网络数据包捕获

2023-01-07 00:19| 来源: 网络整理| 查看: 265

开始之前

关于本教程

目前,信息要通过管道(也就是网络)传输,需要花很多时间封装在数据包中。在本教程 中,我们将在这些数据包传输过程中捕获它们,捕获数据包所采用的平台是 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  这个 LKM 初始化函数是强制执行的。当 LKM 加载到内核时触发该函数,因此 所有初始化工作都是在这个函数中完成的。 处理特定的函数  这些函数执行实际的工作,如读、写、打开、关闭设备,等等。 cleanup_module  当从内核中删除 LKM 时,调用 LKM 的这个强制执行函数。因此,所有清除工作 如释放内存等都应在这个函数中执行。

回页首

编译 LKM

编写 LKM 后,可以采用下列命令编译它:

gcc -c -g .c -I/usr/src/linux/include

该命令产生一个名为 .o 的文件,它就是 LKM,可以使用下列命令将它加载到 内核中:

insmod -f .o ( -f  选项意味着强制加载。)

如果在加载模块时遇到任何内核版本问题,可以通过在加载 LKM 时包含这个特定内核 的头文件来解决问题。因此,在编译内核时,请使用 -I/usr/src/linux/include。

还可以使用 Makefile 来解决此类版本问题,但这超出了本教程的范围。在本教程的末 尾,您还可以在 参考资源 中找到有关 LKM 编程的更多信息。

回页首

卸载 LKM

一旦模块加载到内核中,它将开始执行预定的功能。使用命令 lsmod 可 以看到当前所有加载的 LKM 的列表。

可以使用 rmmod  从内核中加载或删除模块。

编译内核

回顾

本教程讨论的许多机制都要求设置某些内核选项,然后重新编译内核。因此在开始前,我 们将复习内核编译的步骤。如果您已经了解内核编译,则可以跳至下一 节 数据包截获:防火墙钩子 ,在那里开始复习数据包捕获机制。

回页首

步骤

重新编译内核:

make xconfig  允许设置不同的内核选项。(也可以使用 make menuconfig 或 make config;这只是个人偏好问题。) make dep  解决内核编译的文件依赖性。 make bzImage  编译(make)内核映像并将其存储在 vmlinuz 中。 make install  将 vmlinuz 复制到 /boot 目录中,并将二进制文件复制到正确的位置。 make modules  编译所有内核模块。 make modules_install  在正确的位置安装模块(通常位于 /lib/modules/ )。

接着,您应将关于最近编译内核的信息添加到 /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 层的指针

对于上述每个函数,数据包的返回值(或对数据包的操作)可以是:

Accept  该操作由 FW_ACCEPT 宏完成。它接受数据包并且数据包遵循正常的 TCP/IP 协议栈路径。 Reject  该操作由 FW_REJECT 宏完成。它拒绝所有数据包,既没有网络流量流出, 又没有网络流量流入。 Redirect  该操作由 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //Function for forwarded packets static int fw_forward(struct firewall_ops *this,int pf,struct device *dev,void *phdr,void *arg,struct sk_buff **pskb) { struct iphdr *hdr = (struct iphdr *)(*pskb)->h.ipiph; printk("\n\tfw_forward)() called..."); printk("\n\t\tThe source of this packet is: %s",in_ntoa(hdr->saddr)); return FW_ACCEPT; } /*Function for incoming packets*/ static int fw_input(struct firewall_ops *this,int pf,struct device *dev,void *phdr,void *arg,struct sk_buff **pskb) { struct iphdr *iph; iph = (struct iphdr*)(*pskb)->h.ipiph; printk("\n\tfw_input() called..."); printk("\n\t\tThe source of this packet is: %s",in_ntoa(iph->saddr)); return FW_ACCEPT; } /*Function for outgoing packets*/ static int fw_output(struct firewall_ops *this,int pf,struct device *dev,void *phdr,void *arg,struct sk_buff **pskb) { struct iphdr *iph; iph = (struct iphdr*)(*pskb)->h.ipiph; printk("\n\tfw_output)() called..."); printk("\n\tThis packet is destined for: %s",in_ntoa(iph->daddr)); return FW_ACCEPT; } /*Filling the firewall_ops structure*/ static struct firewall_ops myOps = {NULL,fw_forward,fw_input,fw_output,PF_INE int fw_pf; /* Protocol family*/,int fw_priority; /* Priority of chosen firewalls */T,1}; /*First function to be called at the time of loading of module*/ int init_module(void) { /*registering the firewall_ops structure*/ if(register_firewall(PF_INET,&myOps) < 0) { printk("\n\n\tERROR...firewall main aag lag gayee!!!"); return -1; } else { printk("\n\n\tFirewall registered"); } return 0; } /*Function that is called when the module is unloaded*/ void cleanup_module(void) { /*Unregistering the firewall_ops structure*/ if(unregister_firewall(PF_INET,&myOps)


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3