【kernel】从 /proc/sys/net/ipv4/ip_forward 参数看如何玩转 procfs 内核参数
本文的开篇,我们先从 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_dev
的 cnf
结构体成员的 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
这个变量的定义。
其中关键字段的含义为:
const char* procname; // 参数文件名
void* data; // 参数文件值
int maxlen; // 参数大小
mode_t mode; // 文件或目录权限
proc_handler* proc_handler // 处理读写请求的回调函数
具体解释为:当前文件名为“ip_forward”;参数值绑定为ipv4_devconf
的data[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/
三个目录。如下图。
实际上,__devnet_sysctl_register()
最终调用的也是 register_net_sysctl()
函数,完成 sysctl 目录的注册。
register_net_sysctl()
函数在 sysctl_net.c
文件中最终调用 __register_sysctl_table()
接口真正去注册一个 sysctl table 子项。
/proc/sys/net/ipv4/ip_forward 如何被读写?
我们再回到 ctl_table
的结构定义:
其中一个非常重要的函数 devinet_sysctl_forward()
就是 ctl_table
结构的读写回调函数。也就是说,当 /proc/sys/net/ipv4/ip_forward
文件被读或写时,会触发这个函数的调用。
我们来详细看一下这个函数的实现:
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
中,如下:
在写入 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】从 /proc/sys/net/ipv4/ip_forward 参数看如何玩转 procfs 内核参数的更多相关文章
- /proc/sys/net/ipv4/ip_forward
ip地址分公有地址和私有地址,public address是由INIC(internet network information center)负责,这些ip地址分配给注册并向INIC提出申请的组织机 ...
- cat /proc/sys/net/ipv4/ip_forward 0 解决办法
[root@localhost java]# cat /proc/sys/net/ipv4/ip_forward 出于安全考虑,Linux系统默认是禁止数据包转发的. 所谓转发即当主机拥有多于一块的网 ...
- [转帖]/proc/sys/net/ipv4/ 下参数理解
/proc/sys/net/ipv4/ 下参数理解,方便服务器优化 2017年06月02日 16:52:27 庞叶蒙 阅读数 3065 https://blog.csdn.net/pangyemeng ...
- /proc/sys/net/ipv4/
/proc/sys/net/ipv4/icmp_timeexceed_rate这个在traceroute时导致著名的"Solaris middle star".这个文件控制发送IC ...
- /proc/sys/net/ipv4/下各项的意义
/proc/sys/net/ipv4/icmp_timeexceed_rate这个在traceroute时导致著名的“Solaris middle star”.这个文件控制发送ICMP Tim ...
- 通过/proc/sys/net/ipv4/优化Linux下网络性能
通过/proc/sys/net/ipv4/优化Linux下网络性能 /proc/sys/net/ipv4/优化1) /proc/sys/net/ipv4/ip_forward该文件表示是否打 ...
- 关于 /proc/sys/net/ipv4/下 文件的详细解释
关于 /proc/sys/net/ipv4/下 文件的详细解释: 1) /proc/sys/net/ipv4/ip_forward 该文件表示是否打开IP转发. 0,禁止 1,转 ...
- IPv4的核心管理功能/proc/sys/net/ipv4/*
I /proc/sys/net/ipv4/tcp_syncookies SYN Cookies模块可以在系统随机端口(1024:65535)即将用完时自动启动,用来应对Dos攻击.当启动SYN Coo ...
- /proc/sys/net/ipv4/ip_conntrack_max
Things to know (best practices and “issues”) READ IT !!! — uWSGI 2.0 documentationhttps://uwsgi-docs ...
- /proc/sys/net/ipv4/下各参数含义
net.ipv4.tcp_tw_reuse = 0 表示开启重用.允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭 net.ipv4.tcp_tw_recycle = ...
随机推荐
- 算法学习-CDQ分治
对于二维偏序,为普通的求逆序对,只需要先排序一遍,然后树状数组或双指针即可 而三位偏序甚至更高,则需要用 CDQ 分治,简单来说,就是将树状数组和双指针结合 操作步骤如下: 1.开始将数组按第一维排序 ...
- Servlet——执行流程、生命周期、方法介绍、体系结构
执行流程 生命周期 对象的生命周期是指一个对象从被创建到被销毁的整个过程 Servlet运行在Servlet容器(web服务器)中,其生命周期由容器管理,分为四个阶段: 1. 加载和实例 ...
- 系统编程-进程-fork深度理解、vfork简介
1. fork基本使用 #include <unistd.h> #include <stdio.h> int main(){ pid_t pid = fork(); if(pi ...
- 技术储备--SPI接口硬件协议栈芯片W5500使用
整体思路: 移植官方软件包代码, 配置好底层的SPI收发函数以及片选脚操作, 至于临界区操作函数,根据实际情况进行添加,也可以不加. 这就移植好了,就可以在我们自己的板子上跑官方的示例程序了. 官方软 ...
- 【异常处理】Assistive Technology not found: com.sun.java.accessibility.AccessBridge
十一回来之后,工作电脑上的抓包工具Charles突然启动不起来了,双击图标后,一闪而过,就没动静了. 不知道是不是因为之前安装了什么工具.软件引起的. 打开CMD命令行,跳转到目录下启动,提示:Ass ...
- WiFi基础(六):天线基础知识
liwen01 2024.10.01 前言 麦克斯韦预言了电磁波的存在,赫兹通过实验证实了麦克斯韦的预言,马可尼基于无线电磁波的原理发明了无线电报系统,从此人类进入无线通信系统时代. 天线是通信系统中 ...
- 云原生周刊:一文读懂 Pod 网络 | 2023.4.10
文章推荐 一文读懂 Pod 网络 这篇文章旨在帮助读者理解 Pod 网络的概念和原理.Pod 网络是 Kubernetes 中的一个重要概念,它描述了如何在一个集群中部署和运行应用程序. Pod 网络 ...
- 一次彻底讲清如何处理mysql 的死锁问题
MySQL 死锁 是指两个或多个事务互相等待对方持有的锁,从而导致所有事务都无法继续执行的现象.在 InnoDB 存储引擎中,死锁是通过锁机制产生的,特别是在并发较高.业务逻辑复杂的情况下,更容易发生 ...
- C语言数据类型和变量
目录 1.数据类型介绍 1.1字符型 1.2整形 1.3浮点型 1.4布尔类型 1.5各种数据类型长度 1.5.1sizeof操作符 1.5.2数据类型长度 1.5.3 sizeof中表达式不计算 2 ...
- [python]多线程快速入门
前言 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.由于CPython的GIL限制,多线程实际为单线程,大多只用来处理IO密集型任务. Python一般用标准库 ...