首页 > 其他分享 >【kernel】从 /proc/sys/net/ipv4/ip_forward 参数看如何玩转 procfs 内核参数

【kernel】从 /proc/sys/net/ipv4/ip_forward 参数看如何玩转 procfs 内核参数

时间:2024-12-06 18:45:56浏览次数:7  
标签:kernel sysctl ip sys 参数 ipv4 table net proc

本文的开篇,我们先从 sysctl 这个命令开始。

sysctl 使用

sysctl 是一个 Linux 系统工具,后台实际上是 syscall,它允许用户查看和动态修改内核参数。

# 查看当前设置的所有内核参数
sysctl -a
# 查看特定参数的值
sysctl net.ipv4.conf.all.forwarding
# 临时修改内核参数
sysctl net.ipv4.conf.all.forwarding=1
# 重新加载配置文件,默认是 /etc/sysctl.conf
sysctl -p

修改 sysctl 的三种方式:

1)sysctl 命令直接修改(重启后失效)

2)echo 1 > /proc/sys/net/ipv4/ip_forward (重启后失效)

3)vim /etc/sysctl.conf,手动加入,sysctl -p 重新加载(永久生效)

到这里,实际上可以给出一个结论:这几种方式,在原理上,都直接或间接更改了 Linux 中 /proc 文件系统下面的 /proc/sys/net/ipv4/ip_forward 文件。

那么,/proc 文件系统下的文件是如何影响到内核参数的?我们以 ip_forward 参数为例,来追踪一下。

ip_forward 参数

这个参数是内核 ip 报文转发开关。

这个参数有 2 个开关(ipv4 为例,ipv6 同理):

1 - /proc/sys/net/ipv4/ip_forward
2 - /proc/sys/net/ipv4/conf/=={all/default/enp8s0}==/forwarding

有几条规则:

1)/proc/sys/net/ipv4/ip_forward 等价于 /proc/sys/net/ipv4/conf/all/forwarding

可以验证,设置 sysctl net.ipv4.conf.all.forwarding=1 后,查看这两个值:

2)实际真正控制网卡启用 ip 转发的,是网卡对应的 forwarding 参数:/proc/sys/net/ipv4/conf/enp8s0/forwarding

3)对于新创建的网卡设备,会启用 default/forwarding 参数来配置:/proc/sys/net/ipv4/conf/default/forwarding

4)conf/all/forwarding

可以配置当前所有设备,例如将 all 参数配置从 0 修改为 1,则包括 default 在内的所有 forwarding 配置都将被改成 1。要注意的是 all 配置只有在值被修改时才有效,重复写入 all 当前值不会对其他 forwarding 配置产生任何影响。

5)all/forwarding

配置只对当前 net namespace 生效,每个 netns 有自己的独立配置。

ipforward 参数如何影响 ip 转发?

关键内核函数在 ip_route_input_slow()<以下内核版本为 4.18>

这个函数中,会根据当前网络设备 in_dev 的 forwarding 参数,来决定是继续转发,还是跳转到 ip_error。

内核通过一个宏定义 IN_DEV_FORWARD(in_dev) 来判断设备 in_dev 是否开启了转发属性。

这个宏定义在 include/linux/inetdevice.h 文件中,指向了一个 IN_DEV_CONF_GET() 宏。后者继续指向了一个 ipv4_devconf_get() 函数。

在同文件中,ipv4_devconf_get() 函数给出了以下定义:

实际上是获取了这个网络设备 in_devcnf 结构体成员的 data 数组。传入的 index 实际上是字符串 IPV4_DEVCONF_FORWARDING 的拼接。

我们来看一下这个 data 数组的结构:

include/uapi/linux/ip.h 中,定义了 ipv4_devconf 结构体的 data 变量 index

最后,总结来看,内核是通过 IN_DEV_CONF_GET 宏来获取网卡设备的 forward 参数的。

pforward 参数如何被设置的

首先,我们都知道,/proc/sys 目录实际上是一个虚拟文件系统,里面保存了实时生效的内核参数。这个机制允许我们实时查看和修改内核的参数,从而影响系统的运行行为。

和 ipv4 网络相关的参数位于 /proc/sys/net/ipv4 目录下, 如下(5.10 内核):

如何修改?上文已经说了,可以通过直接 echo,或者 sysctl 系统调用,亦或修改 /etc/sysctl.conf 配置文件,即可在不同的级别使他们生效。

/proc/sys/net/ipv4 目录下保存着很多全局变量,例如全局的 ip_forward。和具体网卡设备相关的变量保存在了其子目录 conf/ 下。

内核中的 ctl_table

其中,每一个目录代表当前系统的一个网络设备。当一个新的网络设备被注册或除名时,该目录下也会随之调整。

在内核中,/proc/sys/ 中的文件和目录都是以 ctl_table 结构定义的。下面是 devinet.c 文件中对于 /proc/sys/net/ipv4/ip_forward 这个变量的定义。

image

其中关键字段的含义为:

const char*   procname;    // 参数文件名
void*         data;        // 参数文件值
int           maxlen;      // 参数大小
mode_t        mode;        // 文件或目录权限
proc_handler* proc_handler // 处理读写请求的回调函数

具体解释为:当前文件名为“ip_forward”;参数值绑定为ipv4_devconfdata[0]的位置;644 代表root可读写,其他只读;最后,为这个参数文件绑定了一个读写回调函数 devinet_sysctl_forward

目录定义的 ctl_table 和文件的不太一样,多了个 child 字段:

{
	.procname	= "dev",
	.mode		= 0555,
	.child		= dev_table,
}

/proc/sys/net/ipv4/ip_forward 如何被创建的?

上一节我们了解了,例如 /proc/sys/net/ipv4/ip_forward 文件,在内核中实际上是一个 ctl_table 结构。

ctl_table 的创建,在 fs/proc/proc_sysctl.c 文件的 __register_sysctl_table() 中完成。其函数注释如下:

/**
 * __register_sysctl_table - register a leaf sysctl table
 * @set: Sysctl tree to register on
 * @path: The path to the directory the sysctl table is in.
 * @table: the top-level table structure
 *
 * Register a sysctl table hierarchy. @table should be a filled in ctl_table
 * array. A completely 0 filled entry terminates the table.
 */
 
struct ctl_table_header *__register_sysctl_table(
    struct ctl_table_set *set,
    const char *path, 
    struct ctl_table *table
) {...}

该函数的操作过程大体可以概述为:

  • 寻找 ctl_table 合适的目录,
  • 然后将其插入。

关于这个函数,本文不再赘述了,可以去相关文件中详细了解。下面我们来看 /proc/sys/net/ipv4/ip_forward 的创建过程。

网络设备初始化函数 devinet_init 执行时,将调用 register_pernet_subsys 函数,传入 devinet_ops 结构,并执行其 init 函数。devinet_ops 结构体绑定了 init 和 exit 两个函数,其 init 函数为 devinet_init_net。当他最终被调用执行时,会依次唤起 __devnet_sysctl_register()register_net_sysctl() 分别创建 all/default/ 以及 net/ipv4/ 三个目录。如下图。

image

实际上,__devnet_sysctl_register() 最终调用的也是 register_net_sysctl() 函数,完成 sysctl 目录的注册。

image

register_net_sysctl() 函数在 sysctl_net.c 文件中最终调用 __register_sysctl_table() 接口真正去注册一个 sysctl table 子项。

/proc/sys/net/ipv4/ip_forward 如何被读写?

我们再回到 ctl_table 的结构定义:

image

其中一个非常重要的函数 devinet_sysctl_forward() 就是 ctl_table 结构的读写回调函数。也就是说,当 /proc/sys/net/ipv4/ip_forward 文件被读或写时,会触发这个函数的调用。

我们来详细看一下这个函数的实现:

image

devinet_sysctl_forward() 接收几个参数,重要的,write表示当前操作:1 代表写,0 代表读;后面几个代表用户空间缓冲区,用于传递数据(buffer:缓冲区地址,lenp:缓冲区大小,ppos:文件偏移量)。

/proc/sys/net/ipv4/ip_forward 内核变量类型为一个整数,因此其默认的读写函数为 proc_dointvec()。类似的,字符串内核变量读写函数为 proc_dostring(),整数数组读写函数为 proc_dointvec_jiffies() 等等。这些函数的具体定义在 kernel/sysctl.c 中,如下:

image

在写入 ip_forward 变量时,不仅仅要调用 proc_dointvec() 来写入具体 proc 文件,还需要写入所有网卡设备 cnf 的 data 数组,我们在上文中给出了这部分的接口和介绍。

具体流程详见上面的伪代码,当写入 ip_forward 变量时,最终会遍历所有网卡设备,并调用 IN_DEV_CONF_SET() 宏执行写入操作。

总结:网卡设备配置参数

网卡设备的结构体 in_device 中有一个配置属性 ipv4_devconf,后者的结构中定义了一个 data[] 数组,里面存储了当前网卡的配置参数实际值。

内核中读写这个 data[] 数组,一般会用到 IN_DEV_CONF_GET()IN_DEV_CONF_SET()

如何在 proc/sys/net/ 中自定义一个参数文件?

我们来实战一下,从现在起,下文基于 kos5.8,kernel-5.10.134。

题目,通过编写一个内核模块,实现以下功能:

1)该模块加载时,在 /proc/sys/net/ 目录下创建一个文件 flag,卸载时该文件也随之移除。
2)flag 作为一个内核参数,其参数类型为 int,所有用户可对其读写。
3)当 flag 参数被写入时,向 messages 中打印一条日志。

代码样例:

#include <linux/module.h> 
#include <linux/kernel.h> 
#include <linux/init.h> 
#include <linux/sysctl.h> 
#include <linux/proc_fs.h> 

static int flag = 0; // 用于存储 flag 的值 

// 自定义的 proc_handler 函数 
static int flag_handler(struct ctl_table *table, int write, void __user *buffer, size_t *lenp, loff_t *ppos) { 
	int ret; 
	loff_t pos = *ppos; 
	
	// 使用 proc_dointvec 处理实际的读取/写入操作 
	ret = proc_dointvec(table, write, buffer, lenp, ppos); 
	
	// 当执行写操作时 
	if (write) { 
		// 打印日志,指示写操作发生 
		printk(KERN_INFO "Writing to /proc/sys/net/flag, new value: %s\n", (char *)buffer); 
	} 
	
	return ret; 
} 

// 定义 sysctl 的控制表 
static struct ctl_table sysctl_table[] = { 
	{ 
		.procname = "flag",           // 创建的 sysctl 路径 
		.data = &flag,                // 要处理的内核变量 
		.maxlen = sizeof(flag),       // 数据的最大长度 
		.mode = 0666,                 // 权限设置 
		.proc_handler = flag_handler, // 使用自定义的 proc_handler 
	}, 
	{ } // 结束符 
};

// 定义 sysctl 目录 
static struct ctl_table_header *header; 

static int __init proc_flag_init(void) { 
	printk(KERN_INFO "Initializing proc_flag_sysctl module...\n"); 
	
	// 使用 register_sysctl 创建 proc 文件 
	header = register_sysctl("net", sysctl_table); 
	
	// 在 /proc/sys/net/ 目录下创建 flag 文件 
	if (!header) { 
		printk(KERN_ERR "Unable to register sysctl table\n"); 
		return -ENOMEM; 
	} 
	
	printk(KERN_INFO "Proc file /proc/sys/net/flag created successfully\n"); 
	return 0; 
} 

static void __exit proc_flag_exit(void) { 
	// 卸载 sysctl 表 
	unregister_sysctl_table(header); 
	printk(KERN_INFO "Sysctl table for /proc/sys/net/flag removed\n"); 
} 

module_init(proc_flag_init); 
module_exit(proc_flag_exit); 

MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("Hong"); 
MODULE_DESCRIPTION("A simple kernel module for flag using custom handler and sysctl");

标签:kernel,sysctl,ip,sys,参数,ipv4,table,net,proc
From: https://www.cnblogs.com/lianyihong/p/18591291

相关文章

  • 洛谷 P11362 [NOIP 2024] 遗失的赋值
    题目传送门如果没有其他限制,那么一个二元限制可能出现的方案数为\(v^2\)。考虑\(\{x_n\}\)的一个区间,设其中能放\(t\)个二元限制,它的左右端点有一元限制,求这\(t\)个限制的方案数。设这个数为\(f(t)\)。如果第一个二元限制的\(a\)与左端点\(i\)处的\(x\)值相同,那......
  • Springboot请求参数解密
    新建一个 HttpServletRequestWrapper实现类,在Filter中使用该类包装原requestimportcn.hutool.core.collection.CollUtil;importcn.hutool.core.io.IoUtil;importcn.hutool.core.map.MapUtil;importcn.hutool.core.net.URLDecoder;importcn.hutool.core.text.CharSeq......
  • 来学习typescript 吧! --7 内置对象
    来学习typescript吧!--7内置对象JavaScript中有很多内置对象,它们可以直接在TypeScript中当做定义好了的类型。ECMAScript的内置对象有:Array、Object、Function、Boolean、Stringletb:Boolean=newBoolean(1);letd:Date=newDate();leterr:Error=newError('E......
  • Azure Pipelines 监听文件改动时自动发布 .Net Core或者.Net Framework package到arti
    示例项目:https://dev.azure.com/guorj/_git/PackDemo因为一些需求,在代码有改动以后需要更新响应的package,以供其他项目来使用,但是每次手动打包比较麻烦,就想着给自动化了,可怜自动化出来这么久都没有用过。代码托管在微软的Azure上,所以使用的是AzurePipelines功能。懒得细写了,包......
  • commonjs exports require module 参数
    functionrequire(modulePath){//根据传递的模块路径获取模块idvarmoduleId=getModuleId(modulePath);if(caches[moduleId]){returncaches[moduleId];}function_require(exports,require,module,__filename,__dirname){//目标......
  • JavaScript查找数组中某个元素的位置
    indexOf:在JavaScript中,你可以使用indexOf()方法来查找数组中元素的位置。如果元素不存在于数组中,indexOf()会返回-1。letindex=array.indexOf('x')if(index!=-1){//...}findIndex:如果你需要查找的是复杂对象数组,你可能需要自定义一个查找函数,使用findIndex()letobj......
  • Mysql8.0修改配置参数lower_case_table_names
    现象今天在配置一个环境的数据库,所使用的系统要求该数据库lower_case_table_names=1(对数据库表明、列名大小写不敏感)我看了一下,在Windows上,默认值为1。在macOS上,默认值是2。在Linux上,不支持值2;服务器会将该值设置为0。那0是不符合我们需求的,于是我打开my.cnf进......
  • 大模型输出参数说明
    参数名含义取值范围engine大模型后台推理引擎,目前可选的推理引擎有基础模型,古文模型,对话模型和翻译模型‘base_10B’:基础模型translate’:翻译模型‘dialog’:对话模型'‘rhythm_poems’:古文模型temprature模拟退火温度参数。值越大,使得概率分布越尖锐,模型的创造......
  • 无插件H5播放器EasyPlayer.js网页直播/点播播放器应该怎么使用JavaScript初始化?
    JavaScript可以用来控制播放器的基本功能,如播放、暂停、停止、快进、快退等。通过监听播放器的事件,JavaScript可以响应用户的操作,实现交互式控制。使用JavaScript,开发者可以创建自定义的播放器界面,而不是使用浏览器默认的控件。这可以通过操作DOM来实现,比如显示播放进度条、音量控......
  • 代理IP与人工智能的融合发展
    目录一、代理IP简介二、人工智能简介三、代理IP在人工智能中的应用数据采集数据处理模型训练四、案例分析五、未来展望结语在科技日新月异的今天,代理IP与人工智能(AI)正以前所未有的速度融合发展,为我们的网络生活带来了翻天覆地的变化。这两者看似不相关的技术,实则在......