当前位置: 首页 > news >正文

Linux内核Netfilter使用实战案例分析

一、NF_DROP演示

 该代码是演示案例,丢弃ICMP包并打印,可以用ping命令查看。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/icmp.h>

// 定义钩子回调函数
static unsigned int drop_icmp_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) {
    struct iphdr *iph;
    struct icmphdr *icmph;

    if (!skb)
        return NF_ACCEPT;

    iph = ip_hdr(skb);
    if (iph->protocol == IPPROTO_ICMP) {
        icmph = icmp_hdr(skb);
        // 丢弃 ICMP 数据包
        printk(KERN_INFO "Dropping ICMP packet\n");
        return NF_DROP;
    }
    return NF_ACCEPT;
}

static struct nf_hook_ops drop_icmp_ops = {
    //该字段是一个函数指针,指向实际的钩子回调函数。
    .hook = drop_icmp_hook,
    //指定协议族,即钩子函数要处理的数据包所属的协议类型。NFPROTO_IPV4 表示处理 IPv4 协议的数据包。如果要处理 IPv6 协议的数据包,则应使用 NFPROTO_IPV6。
    .pf = NFPROTO_IPV4,
    //指定钩子函数要挂载的 Netfilter 挂接点。
    .hooknum = NF_INET_PRE_ROUTING,
    //指定钩子函数的优先级。在同一个挂接点上可能会挂载多个钩子函数,内核会根据优先级来决定这些钩子函数的执行顺序。
    .priority = NF_IP_PRI_FIRST,
};

static int __init drop_icmp_init(void) {
    int ret;
    ret = nf_register_net_hook(&init_net, &drop_icmp_ops);
    if (ret) {
        printk(KERN_ERR "Failed to register netfilter hook\n");
        return ret;
    }
    printk(KERN_INFO "Netfilter hook registered successfully\n");
    return 0;
}

static void __exit drop_icmp_exit(void) {
    nf_unregister_net_hook(&init_net, &drop_icmp_ops);
    printk(KERN_INFO "Netfilter hook unregistered\n");
}

module_init(drop_icmp_init);
module_exit(drop_icmp_exit);
MODULE_LICENSE("GPL");

Makefile

obj-m:=NF_HOOK.o	

CURRENT_PAHT:=$(shell pwd) 
LINUX_KERNEL:=$(shell uname -r)   

LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
all:

	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PAHT) modules

clean:

	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PAHT) clean

编译make

 当加载模块之前可以正常ping 8.8.8.8

插入模块之后 无法ping通

卸载模块

二、NF_STOLEN演示

    实现了在 NF_INET_PRE_ROUTING 挂接点捕获数据包并进行复制分析,同时阻止原数据包继续传输的功能。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/icmp.h>

// 模拟数据包分析函数
static void monitor_packet(struct sk_buff *skb) {
    struct iphdr *iph;
    struct tcphdr *tcph;
    struct udphdr *udph;
    struct icmphdr *icmph;

    if (!skb)
        return;

    iph = ip_hdr(skb);
    if (!iph)
        return;

    printk(KERN_INFO "Captured packet: Src IP %pI4, Dst IP %pI4, Protocol %u\n",
           &iph->saddr, &iph->daddr, iph->protocol);

    switch (iph->protocol) {
        case IPPROTO_TCP:
            tcph = tcp_hdr(skb);
            if (tcph)
                printk(KERN_INFO "  TCP: Src Port %u, Dst Port %u\n",
                       ntohs(tcph->source), ntohs(tcph->dest));
            break;
        case IPPROTO_UDP:
            udph = udp_hdr(skb);
            if (udph)
                printk(KERN_INFO "  UDP: Src Port %u, Dst Port %u\n",
                       ntohs(udph->source), ntohs(udph->dest));
            break;
        case IPPROTO_ICMP:
            icmph = icmp_hdr(skb);
            if (icmph)
                printk(KERN_INFO "  ICMP: Type %u, Code %u\n",
                       icmph->type, icmph->code);
            break;
        default:
            break;
    }
}

// 钩子回调函数
static unsigned int steal_pkt_for_monitor(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) {
    struct sk_buff *new_skb;
    new_skb = skb_clone(skb, GFP_ATOMIC);  // 克隆数据包
    if (new_skb) {
        // 对 new_skb 进行分析(如解析协议内容)
        monitor_packet(new_skb);
        kfree_skb(new_skb);  // 释放克隆的数据包
        return NF_STOLEN;  // 原数据包不再继续传输
    }
    return NF_ACCEPT;
}

static struct nf_hook_ops drop_icmp_ops = {
    .hook = steal_pkt_for_monitor,
    .pf = NFPROTO_IPV4,
    .hooknum = NF_INET_PRE_ROUTING,
    .priority = NF_IP_PRI_FIRST,
};

static int __init monitor_init(void) {
    int ret;
    ret = nf_register_net_hook(&init_net, &drop_icmp_ops);
    if (ret) {
        printk(KERN_ERR "Failed to register netfilter hook\n");
        return ret;
    }
    printk(KERN_INFO "Netfilter hook registered successfully\n");
    return 0;
}

static void __exit monitor_exit(void) {
    nf_unregister_net_hook(&init_net, &drop_icmp_ops);
    printk(KERN_INFO "Netfilter hook unregistered\n");
}

module_init(monitor_init);
module_exit(monitor_exit);
MODULE_LICENSE("GPL");

Makefile

obj-m:=NF_STOLEN.o	

CURRENT_PAHT:=$(shell pwd) 
LINUX_KERNEL:=$(shell uname -r)   

LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
all:

	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PAHT) modules

clean:

	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PAHT) clean

编译

插入内核

 运行

        发现ping已经被拦截

        反而被内核自定义模块截获,打印信息:

 

三、NF_QUEUE演示

        将数据包送入用户空间队列,由用户空间程序(如 iptables 配合 libnetfilter_queue 库)进一步处理。

NF_QUEUE.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>

// 钩子回调函数,将数据包入队
static unsigned int queue_pkt_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) {
    return NF_QUEUE;  // 数据包入队,交用户空间处理
}

static struct nf_hook_ops queue_pkt_ops = {
    .hook = queue_pkt_hook,
    .pf = NFPROTO_IPV4,
    .hooknum = NF_INET_PRE_ROUTING,
    .priority = NF_IP_PRI_FIRST,
};

static int __init nf_queue_init(void) {
    int ret;
    ret = nf_register_net_hook(&init_net, &queue_pkt_ops);
    if (ret) {
        printk(KERN_ERR "Failed to register netfilter hook\n");
        return ret;
    }
    printk(KERN_INFO "Netfilter hook registered successfully\n");
    return 0;
}

static void __exit nf_queue_exit(void) {
    nf_unregister_net_hook(&init_net, &queue_pkt_ops);
    printk(KERN_INFO "Netfilter hook unregistered\n");
}

module_init(nf_queue_init);
module_exit(nf_queue_exit);
MODULE_LICENSE("GPL");

 nf_queue_user.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <arpa/inet.h>  //包含 IP 地址转换函数(如 inet_ntoa)
#include <netinet/ip.h> //定义 IP 头部结构体 iphdr

// 手动定义 NF_ACCEPT 为 1,避免因包含内核头文件 linux/netfilter.h 引发的宏冲突
#define NF_ACCEPT 1
//libnetfilter_queue/libnetfilter_queue.h:引入 Netfilter 队列操作的头文件,用于与内核空间的队列交互。
#include <libnetfilter_queue/libnetfilter_queue.h>

// 处理数据包的回调函数
/*
qh:队列句柄。队列句柄是一个指向 struct nfq_q_handle 的指针,它代表了用户空间程序与内核中某个 Netfilter 队列(如队列 0)的连接。每个队列句柄唯一对应内核中的一个队列,通过它可以对该队列进行专属操作(如设置数据包裁决、获取数据包等)
msg:Netfilter 消息头。
nfa:数据包元数据。
data:用户自定义数据(未使用)。
*/
static int process_packet(struct nfq_q_handle *qh, struct nfgenmsg *msg, struct nfq_data *nfa, void *data) {
    struct nfqnl_msg_packet_hdr *ph;  //指向数据包头部信息的指针
    struct iphdr *iph;  //指向 IP 头部的指针
    u_int32_t id;  //数据包 ID
    unsigned char *buf;  //存储数据包有效负载
    int ret;  //函数返回值或数据长度

    // 从 nfa 中提取数据包头部信息
    ph = nfq_get_msg_packet_hdr(nfa);
    if (!ph) {
        fprintf(stderr, "无法获取数据包头部\n");
        return -1;
    }
    //将数据包 ID 从网络字节序(大端)转换为主机字节序(小端)
    id = ntohl(ph->packet_id);

    // 打印数据包信息
    printf("收到数据包 ID: %u\n", id);

    // 解析 IP 头部
    //nfq_get_payload(nfa, &buf):获取数据包的有效负载(即 IP 数据包内容),并将长度存入 ret
    ret = nfq_get_payload(nfa, &buf);
    if (ret >= (int)sizeof(struct iphdr)) {
        iph = (struct iphdr *)buf;
        printf("  Src IP: %s\n", inet_ntoa(*(struct in_addr *)&iph->saddr));
        printf("  Dst IP: %s\n", inet_ntoa(*(struct in_addr *)&iph->daddr));
    }

    // 放行数据包
    /*
    nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL):
    对数据包做出裁决:NF_ACCEPT 表示放行数据包。
    参数依次为队列句柄、数据包 ID、裁决动作、数据长度、额外数据(NULL)。
    函数返回 0 表示处理成功。
    */
    nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
    return 0;
}

int main() {
    struct nfq_handle *h;
    struct nfq_q_handle *qh;
    int fd;
    int rv;
    //buf:存储接收到的数据包数据,__attribute__ ((aligned)) 确保内存对齐
    char buf[4096] __attribute__ ((aligned));

    // 初始化 Netfilter 队列
    h = nfq_open();
    if (!h) {
        fprintf(stderr, "无法打开 Netfilter 队列\n");
        return -1;
    }

    // buf:存储接收到的数据包数据,__attribute__ ((aligned)) 确保内存对齐
    qh = nfq_create_queue(h, 0, &process_packet, NULL);
    if (!qh) {
        fprintf(stderr, "无法创建队列\n");
        nfq_close(h);
        return -1;
    }

    /*
    nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff):
    设置队列模式为 NFQNL_COPY_PACKET,表示将完整的数据包拷贝到用户空间。
    0xffff 表示拷贝数据包的最大长度(65535 字节)
    */
    if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) {
        fprintf(stderr, "无法设置队列模式\n");
        return -1;
    }

    // 处理数据包,获取 Netfilter 队列的文件描述符 fd,用于后续的 recv 操作
    fd = nfq_fd(h);
    while ((rv = recv(fd, buf, sizeof(buf), rv >= 0))) {
        //将接收到的数据传递给 Netfilter 库处理,触发回调函数 process_packet
        nfq_handle_packet(h, buf, rv);
    }

    // 清理
    nfq_destroy_queue(qh);
    nfq_close(h);
    return 0;
}

Makefile

obj-m:= NF_QUEUE.o	

CURRENT_PAHT:=$(shell pwd) 
LINUX_KERNEL:=$(shell uname -r)   

LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
all:

	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PAHT) modules

clean:

	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PAHT) clean

编译

        安装库文件

sudo apt update
sudo apt install libnetfilter-queue-dev

        开始编译内核模块 

         编译用户代码

        gcc nf_queue_user.c -o nf_queue_user -lnetfilter_queue

插入模块并运行

 

相关文章:

  • 利用labelme进行图片标注
  • Redis BitMap 用户签到
  • numpy学习笔记12:实现数组的归一化(0-1范围)
  • 力扣 797. 所有可能的路径 解析JS、Java、python、Go、c++
  • 第2章:容器核心原理:深入理解Namespace、Cgroup与联合文件系统
  • 自动化测试框架pytest+requests+allure
  • Lambda 表达式的语法:
  • 【STL】string类用法介绍及部分接口的模拟实现
  • SpringBoot整合LangChain4j操作AI大模型实战详解
  • 自研实时内核稳定性问题 - I2C总线 - UAF内存异常问题
  • 计算斜着椭圆内某个点到边距离(验证ok)
  • SpringSecurity——基于角色权限控制和资源权限控制
  • 使用`plot_heatmap`绘制热力图时
  • Android之悬浮窗实现
  • 如何设计一个 RPC 框架?需要考虑哪些点?
  • 结合基于标签置信度的特征选择方法用于部分多标签学习-简介版
  • C++ —— 线程同步(互斥锁)
  • vue 中常用操作数组的方法
  • Minecraft命令总结(持续更新)
  • Deal - DbC、检查Python 值、异常和副作用
  • 中国一重集团有限公司副总经理陆文俊被查
  • 民生访谈|今年上海还有哪些重要演出展览?场地配套如何更给力?
  • 纪录片《中国》原班人马打造,《船山先生》美学再升级
  • 绿城约13.93亿元竞得西安浐灞国际港港务片区地块,区内土地楼面单价首次冲破万元
  • 对话|蓬皮杜策展人布莱昂:抽象风景中的中国审美
  • 明星站台“胖都来”背后:百元起录视频,20万可请顶流