Linux下使用Netfilter框架编写内核模块(统计协议层ping特定地址丢包数)

2023-05-16

一、linux内核中neitfilter的处理过程

1.5个HOOK点的执行点说明:
数据包从进入系统,进行IP校验以后,首先经过第一个HOOK函数NF_IP_PRE_ROUTING进行处理;
然后就进入FORWARD路由匹配,其决定该数据报是需要转发还是发给本机的;
若该数据包是发给本机的,则该数据经过HOOK函数NF_IP_LOCAL_IN处理以后然后传递给上层协议;
若该数据报应该被转发则它被NF_IP_FORWARD处理;
经过转发的数据报经过最后一个HOOK函数NF_IP_POST_ROUTING处理以后,再传输到网络上。
本地产生的数据经过HOOK函数NF_IP_LOCAL_OUT 处理后,进行路由选择处理,然后经过NF_IP_POST_ROUTING处理后发送出去。
在这里插入图片描述
2.nf_hooks结构:
在这里插入图片描述
3.协议头字段

#include <linux/netfilter.h>

/* only for userspace compatibility */

#include <limits.h> /* for INT_MIN, INT_MAX */

/* IP Cache bits. */
/* Src IP address. */
#define NFC_IP_SRC		0x0001
/* Dest IP address. */
#define NFC_IP_DST		0x0002
/* Input device. */
#define NFC_IP_IF_IN		0x0004
/* Output device. */
#define NFC_IP_IF_OUT		0x0008
/* TOS. */
#define NFC_IP_TOS		0x0010
/* Protocol. */
#define NFC_IP_PROTO		0x0020
/* IP options. */
#define NFC_IP_OPTIONS		0x0040
/* Frag & flags. */
#define NFC_IP_FRAG		0x0080

/* Per-protocol information: only matters if proto match. */
/* TCP flags. */
#define NFC_IP_TCPFLAGS		0x0100
/* Source port. */
#define NFC_IP_SRC_PT		0x0200
/* Dest port. */
#define NFC_IP_DST_PT		0x0400
/* Something else about the proto */
#define NFC_IP_PROTO_UNKNOWN	0x2000

二、源码解析:

1.通过源码make编译,生成simple_nf_test.ko,加载进内核使用;
2.使用rmmod卸载模块,将内核计算数据保存进user file;
3.使用od -tu /root/netfilter.log查看保存的file数据。
#Makefile
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
obj-m += simple_nf_test.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

[root@test02 netfilter-test]# insmod simple_nf_test.ko
[root@test02 netfilter-test]# ping 114.114.114.114
PING 114.114.114.114 (114.114.114.114) 56(84) bytes of data.
^C
--- 114.114.114.114 ping statistics ---
64 packets transmitted, 0 received, 100% packet loss, time 64552ms

[root@test02 netfilter-test]# rmmod simple_nf_test
#dmesg日志
[22038.705777] ----------------------------------------------------------
[22038.705779] 114.114.114.114 is denied by netfilter!
[22038.705780] drop 114.114.114.114  packets count is : 20
[22039.575591] ----------------------------------------------------------
[22039.575602] # source address is 63baf772 , 114.247.186.99
[22039.575607] # dest address is bc14a8c0 , 192.168.20.188
[22039.575609] ----------------------------------------------------------
[22039.575611] ...packet pass filter...
[22039.575613] [simple_nf_test] packet_filter. end.
[22039.729706] ----------------------------------------------------------
[22039.729762] # source address is 72727272 , 114.114.114.114
[22039.729767] # dest address is bc14a8c0 , 192.168.20.188
[22039.729770] ----------------------------------------------------------
[22039.729772] 114.114.114.114 is denied by netfilter!
[22039.729774] drop 114.114.114.114  packets count is : 21
[22040.753807] ----------------------------------------------------------
[22040.753822] # source address is 72727272 , 114.114.114.114
[22040.753826] # dest address is bc14a8c0 , 192.168.20.188
[22040.753829] ----------------------------------------------------------
[22040.753830] 114.114.114.114 is denied by netfilter!
[22040.753832] drop 114.114.114.114  packets count is : 22
...
[22088.630236] ----------------------------------------------------------
[22088.630237] ...packet pass filter...
[22088.630239] [simple_nf_test] packet_filter. end.
[22089.055705] ----------------------------------------------------------
[22089.055722] # source address is 63baf772 , 114.247.186.99
[22089.055727] # dest address is bc14a8c0 , 192.168.20.188
[22089.055729] ----------------------------------------------------------
[22089.055731] ...packet pass filter...
[22089.055733] [simple_nf_test] packet_filter. end.
[22089.060209] packet fillter capture count: 64
[22089.060212] begin to write to netfilter.log
[22089.060258] record file write done .
[22089.060470] [simple_nf_test] remove hook lkm success!

#netfilter.log记录(8byte)
[root@test02 netfilter-test]# od -tu /root/netfilter.log
0000000         64          0
0000010

#simple_nf_test.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
 
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

//count变量用于统计filter hook的拦截次数
unsigned long int count = 0L;
//内核数据写入本地文件方法
static int  _fileWrite(unsigned long int count)
{
    unsigned long int  buf[]= { count };
    printk("packet fillter capture count: %ld \n",count);
    struct file *fp;
    mm_segment_t fs;
    loff_t pos;
    printk("begin to write to netfilter.log \n");
    //打开本地文件句柄
    fp =filp_open("/root/netfilter.log",O_RDWR|O_CREAT,0644);
    if (IS_ERR(fp)){
        printk("open file error\n");
        return -1;
    }
    //保护内核空间,地址参数检查
    fs =get_fs();
    //扩大空间限制到内核地址空间,从而可以在内核空间中使用文件系统调用
    set_fs(KERNEL_DS);
    //ssize_t kernel_write(struct file *file, const void *buf, size_t count, loff_t *pos)
    //内核数据写入用户态文件API,注意buf如果为Int类型,将以ascii码的方式,保存进文件。所以从文件中获取Int数据时,需要使用od -tu filename命令。
    pos =0;
    kernel_write(fp,buf, sizeof(buf), &pos);
    //关闭文件句柄
    filp_close(fp,NULL);
    //关闭内核空间的文件系统调用
    set_fs(fs);
    return 0;
}


unsigned int packet_filter(unsigned int hooknum, struct sk_buff *skb,const struct net_device *in, const struct net_device *out,int (*okfn)(struct sk_buff *))
{      
        //ip协议头部
        struct iphdr *iph;
        //tcp协议头部
        struct tcphdr *tcph;
        //udp协议头部
        struct udphdr *udph;
       //如socket buffer指针为空,则当前hook点允许该数据包继续在协议栈中流转。 
       //define NF_ACCEPT 1 
        if(skb == NULL)
        return NF_ACCEPT;
        //如skb不为空,则从skb获取ip头,使用内核API ip_hdr()
        iph = ip_hdr(skb);
        //如ip头为空,则当前hook点允许该数据包继续在协议栈中流转。
        if(iph == NULL)
        return NF_ACCEPT;
        //打印经过hook的包信息
        printk("----------------------------------------------------------");
        printk("# source address is %x , %pI4 \n",iph->saddr,&iph->saddr);	
        printk("# dest address is %x , %pI4 \n",iph->daddr,&iph->daddr);
        printk("----------------------------------------------------------");	
	    //示例增加对IP地址114.114.114.114(十六进制表示:0x72727272)的地址过滤
	    if(iph->saddr == 0x72727272){
           printk("114.114.114.114 is denied by netfilter!\n");
           //命中计数器统计
           count++;
	       printk("drop 114.114.114.114  packets count is : %ld \n",count);
	       // 丢弃该数据包
	       return NF_DROP;
	         /*
			#include\uapi\linux\netfilter.h
			/* Responses from hook functions. */
			#define NF_DROP 0 // 丢弃该数据包
			#define NF_ACCEPT 1 // 当前hook点,允许该数据包继续在协议栈中流转
			#define NF_STOLEN 2 // 让netfilter框架忽略该数据包的处理
			#define NF_QUEUE 3 // 该数据包加入到用户队列,供用户程序处理
			#define NF_REPEAT 4
			#define NF_STOP 5	/* Deprecated, for userspace nf_queue compatibility. */
			#define NF_MAX_VERDICT NF_STOP
           */
	     }
	    printk("...packet pass filter...");
        //协议层逻辑
        switch(iph->protocol)
        {
                case IPPROTO_TCP:
                        tcph = (struct tcphdr *)(skb->data + (iph->ihl * 4));
                        //printk("#tcp source port is [%d].\n",ntohs(tcph->source));
                        //printk("#tcp dest port is [%d].\n",ntohs(tcph->dest));
			            //if TCP then do something
                        break;
 
                case IPPROTO_UDP:
                        udph = (struct udphdr *)(skb->data + (iph->ihl * 4));
                        //printk("#udp source port is [%d].\n",ntohs(udph->source));
                        //printk("#udp dest port is [%d].\n",ntohs(udph->source));
                        //if UDP then do someting 
			break;
 
                default :
                        //放行数据包
                        return NF_ACCEPT;
        }
        printk("[simple_nf_test] %s. end.\n", __func__);
        return NF_ACCEPT;
 
};
/*
//hook结构体
struct nf_hook_ops {							 
	struct list_head 	list;	 /* 标准链表—由于链接所有钩子点 */ 
	/* User fills in from here down. */
	nf_hookfn		*hook;	/* 钩子函数回调指针 */
	struct net_device	*dev;    /* 网络设备指针 */
	void			*priv;		/* 私有数据,在钩子回调函数中使用 */
	u_int8_t		pf;				/* 协议族 */
	unsigned int		hooknum;		/* hook点   */
	/* Hooks are ordered in ascending priority. */
	int			priority;			/* 优先级 */
};
*/
//初始化hook参数
static struct nf_hook_ops packet_simple_nf_opt = {
				//hook函数packet_filter
        .hook = (nf_hookfn *)packet_filter,
        //协议指定为ip协议
        .pf = NFPROTO_INET,
        //hook点 NF_INET_PRE_ROUTING 优先级最高
        .hooknum = NF_INET_PRE_ROUTING,
        .priority = NF_IP_PRI_FIRST,
        /*
		//hook支持的协议
		enum {
			NFPROTO_UNSPEC =  0,
			NFPROTO_INET   =  1,        
			NFPROTO_IPV4   =  2,
			NFPROTO_ARP    =  3,
			NFPROTO_NETDEV =  5,
			NFPROTO_BRIDGE =  7,
			NFPROTO_IPV6   = 10,
			NFPROTO_DECNET = 12,
			NFPROTO_NUMPROTO,
		};
		*/
		/*
		内核中注册的5个HOOK点如下:
		
		enum nf_inet_hooks {
		NF_INET_PRE_ROUTING,
		NF_INET_LOCAL_IN,
		NF_INET_FORWARD,
		NF_INET_LOCAL_OUT,
		NF_INET_POST_ROUTING,
		NF_INET_NUMHOOKS
		};
		*/
		/*
		enum nf_ip_hook_priorities {
			NF_IP_PRI_FIRST = INT_MIN,
			NF_IP_PRI_RAW_BEFORE_DEFRAG = -450,
			NF_IP_PRI_CONNTRACK_DEFRAG = -400,
			NF_IP_PRI_RAW = -300,
			NF_IP_PRI_SELINUX_FIRST = -225,
			NF_IP_PRI_CONNTRACK = -200,
			NF_IP_PRI_MANGLE = -150,
			NF_IP_PRI_NAT_DST = -100,
			NF_IP_PRI_FILTER = 0,
			NF_IP_PRI_SECURITY = 50,
			NF_IP_PRI_NAT_SRC = 100,
			NF_IP_PRI_SELINUX_LAST = 225,
			NF_IP_PRI_CONNTRACK_HELPER = 300,
			NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
			NF_IP_PRI_LAST = INT_MAX,
		};
		*/

};

static int simple_nf_init(void)
{   
    //注册函数钩子,内核版本大于4.3.0的内核函数API为nf_register_net_hook,否则为nf_register_hook
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,3,0) 
    //int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg)
    //网络系统在初始化的时候会初始化一个初始网络命名空间,即init_net命名空间。后续创建的net namespace命名空间会和init_net一起通过list项组织起来,且每个网络设备都对应一个命名空间
        nf_register_net_hook(&init_net, &packet_simple_nf_opt);
    #else
        nf_register_hook(&packet_simple_nf_opt);
    #endif
        printk("[simple_nf_test] network hooks success.\n");
        return 0;
 
};

static void simple_nf_exit(void)
{
	//write capute packets count  to user file.
	_fileWrite(count);
	printk("record file write done.\n");
	//卸载hook函数
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,3,0)
        nf_unregister_net_hook(&init_net, &packet_simple_nf_opt);
    #else
        nf_unregister_hook(&packet_simple_nf_opt);
    #endif
	printk("[simple_nf_test] remove hook lkm success!\n");
};
//内核模块加载和卸载时,执行的函数
module_init(simple_nf_init);
module_exit(simple_nf_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jeb");

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux下使用Netfilter框架编写内核模块(统计协议层ping特定地址丢包数) 的相关文章

  • Windows10安装或重装ubuntu18.04双系统教程(平民教程)

    一 引言 1 电脑配置 操作系统 xff1a Win10专业版机型 xff1a Dell G3 15 3500显卡 xff1a NVIDIA GeForce GTX 1660Ti内存 xff1a 32G硬盘 xff1a 双硬盘 xff08
  • 不同国家的日期写法

    题目描述 对于年 月 日的写法 xff0c 不同的国家有不同的描述形式 请按年 月 日的顺序读入日期 xff0c 然后分别输出中国式写法 xff08 年 月 日 xff09 xff0c 英国式写法 xff08 日 月 年 xff09 和美国
  • 二维数组最大值及位置

    题目描述 有一个3 4的矩阵 xff0c 要求编程求出其中值最大的那个元素 xff0c 以及其所在的行号和列号 xff08 如果最大数有多个 xff0c 则显示第1个出现的数据的信息 xff09 输入要求 从键盘输入12个数字组成一个3 4
  • 比较两个字符串的大小

    题目描述 设计函数 xff0c 比较两个字符串的大小 每个字符串长度不超过50 输入要求 从键盘分别读入两个字符串 xff0c 每个字符串以换行符结束 输出要求 比较两个串的大小 xff0c 输出相应的结果 输入样例 Hello hi 输出
  • 心形曲线(java)

    心形曲线java简易表示法 span class token keyword import span span class token namespace java span class token punctuation span awt
  • c++运行不输出结果怎么办

    C C 43 43 运行不出结果怎么调试 main函数中可在各个地方插入return 0提前结束程序 xff0c 直到有结果出现 xff0c 问题就出在return 0的下方 如果在自定义函数内部 xff0c 则需要使用exit xff08
  • 如何解决VS2019控制台输出中文乱码问题

    情况一 xff1a 下载插件 xff0c 将VS的输出编码更改为UTF 8 情况二 xff1a 如果已经装了UTF 8插件但是控制台输出的中文仍然是乱码 则按以下步骤进行 xff08 1 xff09 打开电脑的控制面板 xff0c 然后打开
  • 如何解决vs2019 scanf报错问题

    1 在程序最前面加 xff1a define CRT SECURE NO DEPRECATE 2 在程序最前面加 xff1a pragma warning disable 4996 3 将scanf改为scanf s 4 无需在程序前面加那
  • 函数曲线的绘制

    初等函数曲线的简易绘制 span class token macro property span class token directive keyword include span span class token string lt s
  • 花里胡哨的IDEA 2021启动界面

    一 前言 作为一个花里胡哨的男人 xff0c 总是在不停的研究各种花里胡哨的东西 xff0c 每次上机第一件事 xff0c 肯定是打开开发神器 xff1a Intellij IDEA 2021 xff0c 每次打开都是一个图片 xff0c
  • java中常见排序算法

    一 冒泡排序 span class token comment 64 author liyong 64 date 2021年12月02日 23 33 span span class token keyword public span spa
  • linux下栈空间大小(ulimit)

    linux下栈空间大小 第一次写博客 xff0c 很多地方写的不好请多见谅 xff0c 希望这篇文章对大家有帮助 首先说下为什么会写linux下栈空间大小这个内容 在评审同事代码的时候发现代码中有两个函数互相调用 xff0c 且无法退出导致
  • 解决npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.

    解决办法 https github com npm cli issues 4980 issuecomment 1145334203 解决步骤 xff08 1 xff09 找到装 node js 路径下的 npm cmd xff08 2 xf
  • 1. 两数之和【return new int[]{i, j}、hashtable.containsKey()、get、put】

    1 两数之和 给定一个整数数组 nums 和一个整数目标值 target xff0c 请你在该数组中找出 和为目标值 target 的那 两个 整数 xff0c 并返回它们的数组下标 你可以假设每种输入只会对应一个答案 但是 xff0c 数
  • yarn的安装和使用(极其详细)

    一 yarn的简介 xff1a Yarn是facebook发布的一款取代npm的包管理工具 二 yarn的特点 xff1a 速度超快 Yarn 缓存了每个下载过的包 xff0c 所以再次使用时无需重复下载 同时利用并行下载以最大化资源 利用
  • Java构造方法(与类名相同的方法)、类方法、类变量、实例方法、实例变量

    目录 一 构造方法1 构造方法的特点 xff1a 2 构造方法分类3 构造方法的重载4 构造方法中的 this 二 类方法 类变量 实例方法 实例变量三 类方法 xff08 静态方法 xff0c 用static修饰的方法 xff09 四 类
  • WIFI接入之Authentication和Association流程梳理

    目录 1 Authentication 2 Association 3 总结 在Wifi与AP进行四次握手前 xff0c 需要进行Authentication xff08 认证 xff09 和Association xff08 关联 xff
  • VSCode 快捷键、快捷指令

    一 快捷键设置 xff08 1 xff09 切换块注释 xff1a 默认是 ctrl 43 alt 43 A xff08 2 xff09 切换行注释 xff1a 默认是 ctrl 43 添加键绑定 xff08 3 xff09 光标移到行尾
  • nodejs启动mqtt服务报错SchemaError: Expected `schema` to be an object or boolean问题解决

    一 问题描述 xff1a xff08 1 xff09 nodejs中使用mqtt服务 xff0c 运行命令node mqttserver js xff0c 发现报错 缺少mosca包 xff08 2 xff09 那就装一个 xff0c np
  • 【解决】ERROR in [eslint] ESLint is not a constructor

    问题描述 xff1a ERROR in eslint ESLint is not a constructorERROR in Error Child compilation failed eslint ESLint is not a con

随机推荐