nf_register_sockop

Posted Li-Yongjun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了nf_register_sockop相关的知识,希望对你有一定的参考价值。

简介

netfilter 提供了 nf_register_sockopt() 和 nf_unregister_sockopt() 来动态登记或取消 sockopt 命令字。
打开一个网络 socket 后可以使用 set/getsockopt(2) 实现用户空间与内核的通信,本质和 ioctl 差不多,区别在于set/getsockopt 不用新建设备,直接利用系统已有的 socket 类型就可以进行可用setsockopt() 函数向内核写数据,用 getsockopt() 从内核读数据

示例环境

系统:Ubuntu 20.04
kernel:5.15.0-52-generic

源码

sockopt.c

#include "sockopt.h"
#include <linux/init.h>
#include <linux/module.h>
#include <linux/netfilter.h>

#define BUFFER_LEN_MAX 1024
static char buffer[BUFFER_LEN_MAX];

/* setsockopt 回调处理函数 */
int setsockopt_handler(struct sock *sk, int optval, void __user *user, unsigned int len)

	switch (optval) 
	case SOCKOPT_SET_BUFFER:
		if (copy_from_user((void *)&buffer, user, len) != 0) 
			return -EFAULT;
		

		break;
	default:
		printk(KERN_INFO "invalid setsockopt opt : %d\\n", optval);
		return -EFAULT;
	

	printk(KERN_INFO "buffer[]: %s\\n", buffer);
	return 0;


/* getsockopt 回调处理函数 */
int getsockopt_handler(struct sock *sk, int optval, void __user *user, int *len)

	unsigned int cpy_len;

	cpy_len = *len > BUFFER_LEN_MAX ? BUFFER_LEN_MAX : *len;
	switch (optval) 
	case SOCKOPT_GET_BUFFER:
		if (copy_to_user(user, (void *)&buffer[0], cpy_len) != 0) 
			printk(KERN_INFO "getsockopt_handler fail \\n");
			return -EFAULT;
		

		break;
	default:
		printk(KERN_INFO "unrecognized getsockopt optvalue : %d\\n", optval);
		return -EFAULT;
	

	return 0;


/* 定义nf_sockopt_ops结构体 */
static struct nf_sockopt_ops sockopt_ops_test = 
	.pf 			= PF_INET,
	.set_optmin 	= SOCKOPT_SET_MIN,
	.set_optmax 	= SOCKOPT_SET_MAX,
	.set 			= setsockopt_handler,
	.get_optmin 	= SOCKOPT_GET_MIN,
	.get_optmax 	= SOCKOPT_GET_MAX,
	.get 			= getsockopt_handler,
;

static __init int sockopt_test_init(void)

	int result;
	/* 注册sockopt */
	result = nf_register_sockopt(&sockopt_ops_test);
	if (result != 0) 
		printk("register sockopt error!\\n");
	

	printk("sockopt_test register success !\\n");
	return 0;


static __exit void sockopt_test_exit(void)

	/* 注销sockopt */
	nf_unregister_sockopt(&sockopt_ops_test);
	printk("sockopt_test unregister\\n");


module_init(sockopt_test_init);
module_exit(sockopt_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Stone");

sockopt.h

#ifndef __SOCKOPT_TEST_H_
#define __SOCKOPT_TEST_H_
 
#define SOCKOPT_BASE                 (10240)
#define SOCKOPT_SET_MIN              ((SOCKOPT_BASE) + 1)
#define SOCKOPT_SET_BUFFER           ((SOCKOPT_BASE) + 1)
#define SOCKOPT_SET_MAX              ((SOCKOPT_BASE) + 2)

#define SOCKOPT_GET_MIN              ((SOCKOPT_BASE) + 1)
#define SOCKOPT_GET_BUFFER           ((SOCKOPT_BASE) + 1)
#define SOCKOPT_GET_MAX              ((SOCKOPT_BASE) + 2)
 
#endif

Makefile

obj-m:=sockopt.o

CURRENT_PATH:=$(shell pwd)
VERSION_NUM:=$(shell uname -r)
LINUX_PATH:=/usr/src/linux-headers-$(VERSION_NUM)

all :
	make -C $(LINUX_PATH) M=$(CURRENT_PATH) modules
clean:
	make -C $(LINUX_PATH) M=$(CURRENT_PATH) clean

测试代码
test.c

#include <stdio.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "sockopt.h"

#define USAGE          \\
	"./sockopt get \\n" \\
	"./sockopt set buffer \\n"

#define BUFFER_LEN_MAX 1024
static char buffer[BUFFER_LEN_MAX];

static int getsockopt_handler(void)

	int fd, ret, size;

	size = sizeof(buffer);
	memset(buffer, '\\0', size);
	fd = socket(PF_INET, SOCK_DGRAM, 0);
	if (fd == -1) 
		printf("socket error, errno : %d\\n", errno);
		return -1;
	

	ret = getsockopt(fd, IPPROTO_IP, SOCKOPT_GET_BUFFER, buffer, &size);
	if (ret == -1) 
		printf("getsockopt fail, errno : %d\\n", errno);
	 else 
		printf("getsockopt return buffer : %s\\n", buffer);
	
	close(fd);
	return 0;


static int setsockopt_handler(char *arg)

	int fd, ret, cpy_len;
	unsigned int size;

	size = sizeof(buffer);
	cpy_len = size > strlen(arg) ? strlen(arg) : size;
	memset(buffer, '\\0', size);
	memcpy(buffer, arg, cpy_len);
	fd = socket(PF_INET, SOCK_DGRAM, 0);
	if (fd == -1) 
		printf("socket error, errno : %d\\n", errno);
		return -1;
	

	ret = setsockopt(fd, IPPROTO_IP, SOCKOPT_SET_BUFFER, buffer, size);
	if (ret == -1) 
		printf("setsockopt fail, errno : %d\\n", errno);
	

	close(fd);
	return 0;


int main(int argc, char **argv)

	int direction;
	int index;
	int value = 9;

	if (argc < 2) 
		goto FAIL;
	

	if (strcmp(argv[1], "get") == 0) 
		return getsockopt_handler();
	 else if (strcmp(argv[1], "set") == 0) 
		if (argc != 3) 
			goto FAIL;
		

		return setsockopt_handler(argv[2]);
	

FAIL:
	printf(USAGE);
	exit(EXIT_FAILURE);

编译

报错

$ make
make -C /usr/src/linux-headers-5.15.0-52-generic M=/home/liyongjun/project/c/C_study/kernel/netfilter/2 modules
make[1]: 进入目录“/usr/src/linux-headers-5.15.0-52-generic”
  CC [M]  /home/liyongjun/project/c/C_study/kernel/netfilter/2/sockopt.o
/home/liyongjun/project/c/C_study/kernel/netfilter/2/sockopt.c:61:35: error: initialization of ‘int (*)(struct sock *, int,  sockptr_t,  unsigned int)aka ‘int (*)(struct sock *, int,  struct <anonymous>,  unsigned int) from incompatible pointer type ‘int (*)(struct sock *, int,  void *, unsigned int)[-Werror=incompatible-pointer-types]
   61 |         .set                    = setsockopt_handler,
      |                                   ^~~~~~~~~~~~~~~~~~
/home/liyongjun/project/c/C_study/kernel/netfilter/2/sockopt.c:61:35: note: (near initialization for ‘sockopt_ops_test.set’)
cc1: some warnings being treated as errors
make[2]: *** [scripts/Makefile.build:297:/home/liyongjun/project/c/C_study/kernel/netfilter/2/sockopt.o] 错误 1
make[1]: *** [Makefile:1900:/home/liyongjun/project/c/C_study/kernel/netfilter/2] 错误 2
make[1]: 离开目录“/usr/src/linux-headers-5.15.0-52-generic”
make: *** [Makefile:9:all] 错误 2

出错原因是,从内核 5.9 版本开始,struct nf_sockopt_ops 中 set 函数指针的定义由

int (*set)(struct sock *sk, int optval, void __user *user, unsigned int len);

改为了

int (*set)(struct sock *sk, int optval, sockptr_t arg, unsigned int len);

所以,我们也要将 setsockopt_handler 实现改为如下,① 第三个参数改为 sockptr_t 类型,② copy_from_user() 改为 copy_from_sockptr()。

int setsockopt_handler(struct sock *sk, int optval, sockptr_t user, unsigned int len)

	switch (optval) 
	case SOCKOPT_SET_BUFFER:
		if (copy_from_sockptr((void *)&buffer, user, len) != 0) 
			return -EFAULT;
		

		break;
	default:
		printk(KERN_INFO "invalid setsockopt opt : %d\\n", optval);
		return -EFAULT;
	

	printk(KERN_INFO "buffer[]: %s\\n", buffer);
	return 0;

测试

$ sudo insmod sockopt.ko 
$ ./a.out set hello
$ ./a.out get
getsockopt return buffer : hello

以上是关于nf_register_sockop的主要内容,如果未能解决你的问题,请参考以下文章