内核模块编写

下面是一个简单的内核模块示例,满足您的需求。该模块在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");

测试用例

  1. 编译和加载模块

    • 将上述代码保存为icmp_stat.c
    • 使用make编译模块(需要一个适当的Makefile)。
    • 加载模块:sudo insmod icmp_stat.ko packet_size=200
  2. 查看统计信息

    • 查看 /proc/icmp_stat 文件的内容:cat /proc/icmp_stat
  3. 测试

    • 发送ICMP请求:ping -s 200 <target_ip>
    • 检查统计信息:cat /proc/icmp_stat

注意:在测试之前,请确保您有适当的权限并了解对系统的潜在影响。

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

说明

  1. 定义模块名称

    • obj-m := icmp_stat.o:定义模块名称为 icmp_stat,这是默认名称。只需更改这行即可更改模块名称。
  2. 内核构建目录

    • KDIR 变量指定了内核构建目录,它通常位于 /lib/modules/$(uname -r)/build
  3. 当前目录

    • PWD 变量自动获取当前工作目录的路径。
  4. all 目标

    • 使用 make -C $(KDIR) M=$(PWD) modules 命令调用内核构建系统来构建模块。
  5. clean 目标

    • 使用 make -C $(KDIR) M=$(PWD) clean 命令清理模块生成的文件。
  6. 用户可修改模块名称

    • module_name 变量用于指定模块的名称。如果需要更改模块名称,只需在 Makefile 中修改 module_name 的值,然后 obj-m 会自动更新。

使用方法

  1. 编译模块

    • 运行 make 命令来编译模块。
  2. 清理生成的文件

    • 运行 make clean 命令来删除生成的文件。
  3. 更改模块名称

    • 修改 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");

编译和加载模块

  1. 编写 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
    
  2. 编译模块

    make
    
  3. 加载模块

    sudo insmod packet_logger_proc.ko
    
  4. 查看 /proc 文件

    cat /proc/packet_info
    

解释

  • proc_create:用于创建 /proc 文件。
  • proc_show:展示文件内容的函数,包括时间戳和源 IP 地址。
  • single_opensingle_release:用于处理打开和关闭 /proc 文件的操作。

这样,你可以通过访问 /proc/packet_info 文件来查看最近捕获的网络包的时间戳和源地址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值