下面是一个简单的内核模块示例,满足您的需求。该模块在NF_INET_PRE_ROUTING
钩子处监控ICMP报文,统计符合大小条件的ICMP包,并通过proc
文件系统提供统计信息。
内核模块代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/string.h>
#define MODULE_NAME "icmp_stat"
#define MAX_PACKET_SIZE 1000
static unsigned int packet_size = 0;
static unsigned int packet_count = 0;
static unsigned long long last_received_time = 0;
static char src_ip[16] = {0};
static struct proc_dir_entry *proc_entry;
static unsigned int hook_func(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct iphdr *ip_header;
struct icmphdr *icmp_header;
unsigned int icmp_data_size;
if (!skb)
return NF_ACCEPT;
ip_header = ip_hdr(skb);
if (ip_header->protocol != IPPROTO_ICMP)
return NF_ACCEPT;
icmp_header = (struct icmphdr *)(skb_network_header(skb) + ip_header->ihl * 4);
icmp_data_size = ntohs(ip_header->tot_len) - (ip_header->ihl * 4) - sizeof(struct icmphdr);
if (icmp_data_size == packet_size) {
packet_count++;
last_received_time = jiffies;
snprintf(src_ip, sizeof(src_ip), "%pI4", &ip_header->saddr);
}
return NF_ACCEPT;
}
static int proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "Packet Count: %u\n", packet_count);
seq_printf(m, "Last Received Time: %llu\n", last_received_time);
seq_printf(m, "Source IP Address: %s\n", src_ip);
return 0;
}
static int proc_open(struct inode *inode, struct file *file)
{
return single_open(file, proc_show, NULL);
}
static const struct file_operations proc_fops = {
.owner = THIS_MODULE,
.open = proc_open,
.read = seq_read,
.release = single_release,
};
static struct nf_hook_ops nfho = {
.hook = hook_func,
.pf = PF_INET,
.hooknum = NF_INET_PRE_ROUTING,
.priority = NF_IP_PRI_FIRST,
};
static int __init icmp_stat_init(void)
{
int ret;
if (packet_size < 100 || packet_size > 1000) {
pr_err("Invalid packet size. Must be between 100 and 1000 bytes.\n");
return -EINVAL;
}
ret = nf_register_net_hook(&init_net, &nfho);
if (ret) {
pr_err("Failed to register netfilter hook.\n");
return ret;
}
proc_entry = proc_create(MODULE_NAME, 0, NULL, &proc_fops);
if (!proc_entry) {
nf_unregister_net_hook(&init_net, &nfho);
pr_err("Failed to create proc entry.\n");
return -ENOMEM;
}
pr_info("Module initialized.\n");
return 0;
}
static void __exit icmp_stat_exit(void)
{
nf_unregister_net_hook(&init_net, &nfho);
proc_remove(proc_entry);
pr_info("Module exited.\n");
}
module_init(icmp_stat_init);
module_exit(icmp_stat_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("ICMP Packet Statistics Module");
测试用例
-
编译和加载模块
- 将上述代码保存为
icmp_stat.c
。 - 使用
make
编译模块(需要一个适当的Makefile
)。 - 加载模块:
sudo insmod icmp_stat.ko packet_size=200
- 将上述代码保存为
-
查看统计信息
- 查看
/proc/icmp_stat
文件的内容:cat /proc/icmp_stat
- 查看
-
测试
- 发送ICMP请求:
ping -s 200 <target_ip>
- 检查统计信息:
cat /proc/icmp_stat
- 发送ICMP请求:
注意:在测试之前,请确保您有适当的权限并了解对系统的潜在影响。
makefile
要编写一个适用于内核模块的 Makefile
文件,确保它在模块名称发生变化时的修改最少,可以使用下面的模板:
Makefile
# 定义模块名称
obj-m := icmp_stat.o
# 内核构建目录
KDIR := /lib/modules/$(shell uname -r)/build
# 当前目录
PWD := $(shell pwd)
all:
@echo "Building module..."
make -C $(KDIR) M=$(PWD) modules
clean:
@echo "Cleaning module..."
make -C $(KDIR) M=$(PWD) clean
# 允许用户修改模块名称
module_name = icmp_stat
# 当文件名变化时修改该变量
obj-m := $(module_name).o
说明
-
定义模块名称:
obj-m := icmp_stat.o
:定义模块名称为icmp_stat
,这是默认名称。只需更改这行即可更改模块名称。
-
内核构建目录:
KDIR
变量指定了内核构建目录,它通常位于/lib/modules/$(uname -r)/build
。
-
当前目录:
PWD
变量自动获取当前工作目录的路径。
-
all
目标:- 使用
make -C $(KDIR) M=$(PWD) modules
命令调用内核构建系统来构建模块。
- 使用
-
clean
目标:- 使用
make -C $(KDIR) M=$(PWD) clean
命令清理模块生成的文件。
- 使用
-
用户可修改模块名称:
module_name
变量用于指定模块的名称。如果需要更改模块名称,只需在Makefile
中修改module_name
的值,然后obj-m
会自动更新。
使用方法
-
编译模块:
- 运行
make
命令来编译模块。
- 运行
-
清理生成的文件:
- 运行
make clean
命令来删除生成的文件。
- 运行
-
更改模块名称:
- 修改
module_name
变量的值,然后运行make
来编译新名称的模块。
- 修改
通过这种方式,您可以尽量减少对 Makefile
的修改,以适应不同的模块名称。
最新版本
如果你希望通过 /proc
文件系统来查看内核模块记录的网络包信息,可以在内核模块中创建一个 /proc
文件。以下是一个示例,展示了如何在内核模块中实现这个功能:
修改内核模块以支持 /proc
文件系统
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/inet.h>
#include <linux/timekeeping.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#define PROC_NAME "packet_info"
static struct proc_dir_entry *proc_entry;
static struct timeval last_tv;
static unsigned int last_src_ip;
// Hook function to log packet info
unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
struct ethhdr *eth;
struct iphdr *ip_header;
struct timeval tv;
if (!skb)
return NF_ACCEPT;
// Get timestamp
ktime_get_ts64(&tv);
eth = eth_hdr(skb);
if (eth->h_proto == htons(ETH_P_IP)) {
ip_header = ip_hdr(skb);
last_tv = tv;
last_src_ip = ip_header->saddr;
}
return NF_ACCEPT;
}
// Show function for /proc file
static int proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "Last Packet Info:\n");
seq_printf(m, "Time: %lu.%06lu\n", last_tv.tv_sec, last_tv.tv_usec);
seq_printf(m, "Src IP: %pI4\n", &last_src_ip);
return 0;
}
// Open function for /proc file
static int proc_open(struct inode *inode, struct file *file)
{
return single_open(file, proc_show, NULL);
}
static const struct file_operations proc_fops = {
.owner = THIS_MODULE,
.open = proc_open,
.read = seq_read,
.release = single_release,
};
// Module initialization
static int __init mymodule_init(void)
{
proc_entry = proc_create(PROC_NAME, 0, NULL, &proc_fops);
if (!proc_entry) {
pr_err("Failed to create /proc entry\n");
return -ENOMEM;
}
pr_info("Module loaded\n");
return 0;
}
// Module cleanup
static void __exit mymodule_exit(void)
{
remove_proc_entry(PROC_NAME, NULL);
pr_info("Module unloaded\n");
}
module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple packet logging module with /proc interface");
编译和加载模块
-
编写 Makefile(假设模块文件名为
packet_logger_proc.c
):obj-m += packet_logger_proc.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
-
编译模块:
make
-
加载模块:
sudo insmod packet_logger_proc.ko
-
查看
/proc
文件:cat /proc/packet_info
解释
proc_create
:用于创建/proc
文件。proc_show
:展示文件内容的函数,包括时间戳和源 IP 地址。single_open
和single_release
:用于处理打开和关闭/proc
文件的操作。
这样,你可以通过访问 /proc/packet_info
文件来查看最近捕获的网络包的时间戳和源地址。