动手写一个基于Linux内核的网络数据包拦截扩展
ahcoder 2025-05-24 11:52 18 浏览
操作系统: ubuntu虚拟机
编程语言: Linux C
(1). 安装源更新
ubuntu系统默认的安装源是ubuntu官方源,从国内访问速度较慢, 这里先要将其替换成国内的安装源, 找到: /etc/apt/sources.list文件, 先将其备份, 然后将里面的内容替换成以下内容:
deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse
替换完成后, 运行以下命令对软件包进行更新:
$ sudo apt update
(2).环境配置
假设登录系统账号为:suntiger, 代码工程保存在目录:
/home/linux-kernel-security-module, 在命令行下需要输入以下提权命令:
# chown -R suntiger /home/linux-kernel-security-module
执行该命令后, 在VScode中便可以正常创建代码文件。
在开发过程中, 需要使用Linux具体版本的内核头文件包, 通过以下命令进行安装:
# apt install linux-headers-$(uname -r)
还需要安装一些基本的开发工具, 例如:build-essential, 它包含了编译内核模块所需的编译器和其它工具, 安装命令如下:
# apt install build-essential
# add-apt-repository ppa:ubuntu-toolchain-r/test
# apt update
# apt install gcc-12 g++-12
开发一个简单内核模块扩展
首先在自己的工程目录新建一个代码文件:main.c, 然后写入以下代码:
#include <linux/init.h>
#include <linux/module.h>
static int __init construct(void) {
pr_info("First kernel module: Hello World!\n");
return 0;
}
static void __exit destruct(void) {
pr_info("First kernel module has been removed\n");
}
module_init(construct);
module_exit(destruct);
MODULE_LICENSE("GPL");
这段代码是一个简单的Linux内核模块开发实例, 以下部分将对这段代码的含义进行解读。
首先是头文件:
#include <linux/init.h>
#include <linux/module.h>
这些头文件包含了内核模块开发所需的函数和宏定义, 例如:
- linux/init.h:提供了模块初始化和退出的宏。
- linux/module.h:提供了内核模块的必要定义和函数。
下面来看这段代码:
static int __init construct(void) {
pr_info("First kernel module: Hello World!\n");
return 0;
}
这段代码是模块的初始化函数, 它在模块加载时被调用。函数名前面的__init是一个宏, 用于将函数标记为初始化代码。在模块加载时, 内核会调用这个函数。
- pr_info是一个内核打印函数, 类似于用户空间的printf。它用于在内核日志中打印消息。
- 该函数返回0, 表示初始化成功。
下面看另一段代码:
static void __exit destruct(void) {
pr_info("First kernel module has been removed\n");
}
这是模块的清理函数, 它在模块卸载时被调用。函数名前面的__exit是一个宏, 用于将函数标记为退出代码, 在模块卸载时,内核会调用这个函数。
看最后的三行代码:
module_init(construct);
module_exit(destruct);
MODULE_LICENSE("GPL");
前两行宏代码用于注册初始化和清理函数,其中:
- module_init:宏注册了模块的初始化函数construct。
- module_exit宏注册了模块的清理函数destruct。
最后一句宏定义了模块的许可证为GPL(GNU General Public License)。这表明该模块遵循GPL许可证。使用GPL许可证声明是强烈推荐的, 特别是当你要将模块发布为开源代码时。内核模块如果没有指定许可证, 内核将认为它是非GPL的, 并且可能限制某些符号的使用。
下面编写一个Makefile来尝试编译一下代码, 内容如下:
obj-m += main.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
注意: Makefile的内容对格式有严格要求,make前面的空格必须是一个table空格,否则会引发编译错误。
第一句:obj-m += main.o定义了一个目标模块main.o。obj-m是一个内核构建系统变量, 用于列出要构建的模块对象文件。通过将main.o添加到obj-m中, 这是为了告诉内核构建系统, 我们要编译main.c并生成main.o内核模块。
第二句:
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
- all是一个目标,它表示默认目标。如果在命令行中仅输入make, 这个目标就会被执行。
- make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules是一个命令,用于调用内核构建系统来编译模块。
- -C /lib/modules/$(shell uname -r)/build这个参数将工作目录切换到内核构建目录。$(shell uname -r)返回当前内核的版本号,例如:6.2.0-26-generic。因此这个命令切换到/lib/modules/6.2.0-26.generic/build目录,该目录包含了当前内核版本的构建环境。
- M=$(PWD):这个参数告诉内核构建系统模块源代码所在的目录是当前目录, 表示当前工作目录。
- modules: 这个目标指示内核构建系统生成模块。
下面切换到当前的工作目录, 在命令行中直接输入Make命令开始编译,编译过程如下:
如果编译成功, 在当前的工作目录会看到一个名为main.ko的内核模块,如图:
下面, 使用以下命令将编写的模块加载到内核中, 命令如下:
# insmod main.ko
接着输入lsmod命令查看已加载的内核模块,如图:
可以看到自己编写的内核模块已加载成功。
接着输入命令:dmesg | grep main -A1来查看模块的内核日志,如图:
可以看到加载的内核扩展模块日志成功打印, 说明已成功加载运行。
接下来, 尝试输入命令:rmmod main来卸载内核模块, 并使用命令dmesg | grep main -A2来查看内核日志, 如图:
从日志内容中可以发现卸载模块信息被打印, 说明内核扩展卸载成功, 使用命令lsmod再次查看内核模块,如图:
main内核扩展已完全卸载。
内核扩展中使用日志级别
在内核扩展中, 也可以打印不同日志级别的信息, 看下面的代码:
#include <linux/init.h>
#include <linux/module.h>
static int __init construct(void) {
printk(KERN_INFO "INFO Level message\n");
pr_info("Another INFO Level message\n");
printk(KERN_WARNING "WARNING Level message\n");
pr_warn("Another WARNING Level message\n");
printk(KERN_ERR "ERROR Level message\n");
pr_err("Another ERROR Level message\n");
printk(KERN_CONT "This " );
pr_cont("message ");
printk(KERN_CONT "is " );
pr_cont("single ");
printk(KERN_CONT "line\n");
return 0;
}
static void __exit destruct(void) {
printk(KERN_INFO "The module has been removed\n");
}
module_init(construct);
module_exit(destruct);
MODULE_LICENSE("GPL");
在上面的代码中, 主要是用了printk()和pr_xxx系列函数来打印不同日志级别的信息, 两者含义差不多。
- printk()函数是内核中最基础的日志打印函数, 可以用于打印各种级别的日志信息,在使用时需要指定日志级别, 例如:KERN_INFO、KERN_WARNING、KERN_ERR。
- pr_xxx系列函数是printk()的简化宏, 专门用于打印特定级别的日志信息, 主要简化了代码, 不需要显示指定日志级别。
编译该内核扩展并加载后, 可以使用以下命令查看错误级别的日志:
# dmesg --level err | grep message
返回信息如图:
开发网络数据包拦截内核扩展
有了上面的基础, 下面开发一个网络数据包拦截的内核扩展, 代码如下:
#include <linux/module.h> // included for all kernel modules
#include <linux/kernel.h> // included for KERN_INFO
#include <linux/init.h> // included for __init and __exit macros
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netdevice.h>
#include <linux/vmalloc.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A Simple Hello Packet Module");
enum { NF_IP_PRE_ROUTING,
NF_IP_LOCAL_IN,
NF_IP_FORWARD,
NF_IP_LOCAL_OUT,
NF_IP_POST_ROUTING,
NF_IP_NUMHOOKS };
static struct nf_hook_ops in_nfho; //net filter hook option struct
static struct nf_hook_ops out_nfho; //net filter hook option struct
static void dump_addr(unsigned char *iphdr)
{
int i;
for(i=0; i<4; i++){
printk("%d.", *(iphdr+12+i));
}
printk(" -> ");
for(i=0; i<4; i++){
printk("%d.", *(iphdr+16+i));
}
printk("\n");
}
unsigned int my_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
printk("Hello packet! ");
//printk("from %s to %s\n", in->name, out->name);
unsigned char *iphdr = skb_network_header(skb);
if(iphdr){
dump_addr(iphdr);
}
return NF_ACCEPT;
// return NF_DROP;//会导致上不了网
}
static int __init init_func(void)
{
//NF_IP_PRE_ROUTING hook
in_nfho.hook = my_hook;
in_nfho.hooknum = NF_IP_LOCAL_IN;
in_nfho.pf = PF_INET;
in_nfho.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &in_nfho);
//NF_IP_LOCAL_OUT hook
out_nfho.hook = my_hook;
out_nfho.hooknum = NF_IP_LOCAL_OUT;
out_nfho.pf = PF_INET;
out_nfho.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &out_nfho);
return 0;
}
static void __exit exit_func(void)
{
nf_unregister_net_hook(&init_net, &in_nfho);
nf_unregister_net_hook(&init_net, &out_nfho);
printk(KERN_INFO "Cleaning up Hello_Packet module.\n");
}
module_init(init_func);
module_exit(exit_func);
在上面的代码中, 注册并设置了两个Netfilter钩子:
- in_nfho钩子拦截进入本地网络栈的数据包(NF_INET_LOCAL_IN)
- out_nfho钩子拦截离开本地网络栈的数据包(NF_INET_LOCAL_OUT)
最终通过dump_addr函数打印了数据包中的源IP地址和目的IP地址。
将代码按照上面的方法进行编译并加载, 模块加载完成后, 输入命令:
dmesg | tail
可以看到打印出来的源IP地址和目的IP地址,如图:
这里192.168.30.57是源IP地址,也就是我本地工作电脑地址; 192.168.201.204就是服务器地址。
相关推荐
- KaOS 2025.05版本发布:全面拥抱Qt6,彻底告别Qt5
-
KaOSLinux2025.05版本重磅发布:全面拥抱Qt6,开启KDE生态新篇章继2025.03版本发布两个月后,专注于KDE桌面环境、采用XFS文件系统的滚动发行版Li...
- 基于FIMC接口的CMOS摄像头驱动分析与设计
-
摘要:目前的嵌入式系统中,USB摄像头使用比较普遍,但其应用会受到传输速度的限制。本文采用一款高速CMOS摄像头,其驱动利用S3C6410内置的FIMC接口技术,采用DMA和ping-pong缓冲...
- 没错是微软 推出基于Linux的交换机系统
-
2015-09-2205:59:59作者:郑伟你没看错,为了提升自身Azure云数据中心内网络设备的兼容性及开放性,微软也开始推出基于Linux的网络交换机系统了。这个被称为AzureCloud...
- Linus Torvalds 宣布首个 Linux 内核 6.16 候选版本
-
Linux内核负责人兼创始人LinusTorvalds宣布关闭合并窗口,该窗口用于将主要新功能添加到内核中,并开始发布Linux6.16候选版本,从候选版本1(Linux6.16-r...
- Linux内核漏洞将影响Haswell架构服务器
-
在infoq网站上,GilTene最近报告一个十分重要,但并不为人知Linux内核补丁,特别对采用Haswell架构的Linux系统用户和管理员应该特别关注。报告提醒RedHat发行版的用户(包括...
- 关于Linux性能调优中网络I/O的一些笔记
-
写在前面和小伙伴分享一些Linux网络优化的笔记,内容很浅,可以用作入门博文内容结合《Linux性能优化》读书笔记整理涉及内容包括常用的优化工具(mii-tool,ethtool,ifconfig,i...
- 国产操作系统- Veket Linux(国产操作系统之光银河麒麟阅读理解)
-
VeketLinux是一个随身的可装在U盘的Linux操作系统。主要面向桌面用户。它的设计重点是提供简单易用且稳定的操作系统,同时保持更新和开发。它具有强大的功能集和广泛的用户基础,可满足...
- AlmaLinux 9.6发布:升级工具、初步支持IBM Power虚拟化技术
-
IT之家5月21日消息,科技媒体linuxiac昨日(5月20日)发布博文,报道称代号为SageMargay的AlmaLinux9.6发行版已上线,距上一版本9.5发...
- 跟老韩学Linux运维架构师系列,vim与view的基本使用
-
下面是vim和view的10个实例:用vim打开一个新文件:vimnewfile.txt这个命令将会在vim编辑器中打开一个新文件。在vim中移动光标:使用方向键或h、j、k、l键来移动光标。在v...
- malloc底层原理剖析——ptmalloc内存池
-
malloc底层为什么是内存池malloc大家都用过,其是库函数。我们都知道库函数在不同的操作系统中其实执行的是系统调用,那么malloc在Linux上执行的是哪个系统调用呢?brk()和mmap()...
- Zen 6架构首秀Linux,AMD加速下一代处理器布局
-
IT之家5月15日消息,科技媒体Phoronix昨日(5月14日)发布博文,报道称AMD已经开始为下一代“Zen6”处理器做准备,已为该构架向Linux内核提交了首个补丁,...
- 为何越来越多企业转向安卓/Linux工业平板电脑?答案在这里
-
在工业领域,设备的稳定性至关重要,尤其是工业平板电脑,常年运行在高温、粉尘、潮湿等复杂环境下,一旦系统崩溃或者卡顿,可能会影响整个生产流程。那么,为什么越来越多的企业选择安卓/Linux工业平板电脑,...
- 从3ms到0.8ms:ARM+Linux如何重塑工业控制实时性标杆
-
在智能制造领域,产线控制系统对实时性的要求越来越高。根据行业调研数据,超过65%的工业现场出现过因系统响应延迟导致的故障停机,平均每次停机造成的直接损失高达2-8万元。传统x86架构搭配Windows...
- 看Linux如何"挖坑种树"
-
写在前面,有人看我的Linux文章说技术难度不深,笔者不是不想写深,笔者是觉得Linux难就难在入门,入门之后你就知道如何上网查询你所要要解决的Linux需求。如果你已入门,此文已对你无用,请略过此...
- AlmaLinux 9.6 发布,新增功能亮点纷呈!
-
距离上一版本AlmaLinux9.5发布六个月后,基于5.14内核的AlmaLinux正式宣布其企业级Linux发行版的9.x系列第六个更新——AlmaLinux9.6(Sag...
- 一周热门
- 最近发表
-
- KaOS 2025.05版本发布:全面拥抱Qt6,彻底告别Qt5
- 基于FIMC接口的CMOS摄像头驱动分析与设计
- 没错是微软 推出基于Linux的交换机系统
- Linus Torvalds 宣布首个 Linux 内核 6.16 候选版本
- Linux内核漏洞将影响Haswell架构服务器
- 关于Linux性能调优中网络I/O的一些笔记
- 国产操作系统- Veket Linux(国产操作系统之光银河麒麟阅读理解)
- AlmaLinux 9.6发布:升级工具、初步支持IBM Power虚拟化技术
- 跟老韩学Linux运维架构师系列,vim与view的基本使用
- malloc底层原理剖析——ptmalloc内存池
- 标签列表
-
- linux 远程 (37)
- u盘 linux (32)
- linux 登录 (34)
- linux 路径 (33)
- linux 文件命令 (35)
- linux 是什么 (35)
- linux 界面 (34)
- 查看文件 linux (35)
- linux 语言 (33)
- linux代码 (32)
- linux 查看命令 (33)
- 关闭linux (34)
- root linux (33)
- 删除文件 linux (35)
- linux 主机 (34)
- linux与 (33)
- linux 函数 (35)
- linux .ssh (35)
- cpu linux (35)
- 查看linux 系统 (32)
- linux 防火墙 (33)
- linux 手机 (32)
- linux 镜像 (34)
- linux ip地址 (34)
- linux 用户查看 (33)