net.ipv4.icmp_errors_use_inbound_ifaddr内核代码参数详解
net.ipv4.icmp_errors_use_inbound_ifaddr内核代码参数详解
Joshuanet.ipv4.icmp_errors_use_inbound_ifaddr内核代码参数详解
学习icmp相关的内核参数的时候,遇到了这个内核参数,看了看官方文档的描述,感觉它描写的云里雾里的,问了问朋友,朋友也看不懂文档的描述,互联网上对这个参数的解读大多又都是直接从内核文档这里复制过来的。既然如此,那就很有必要读一读内核代码,直接从代码层面解决问题了。
📄文档描述
这里先贴出官方文档对这个参数的解释:
icmp_errors_use_inbound_ifaddr - BOOLEAN
If zero, icmp error messages are sent with the primary address of the exiting interface.
If non-zero, the message will be sent with the primary address of the interface that received the packet that caused the icmp error. This is the behaviour many network administrators will expect from a router. And it can make debugging complicated network layouts much easier.
Note that if no primary address exists for the interface selected, then the primary address of the first non-loopback interface that has one will be used regardless of this setting.
Default: 0
首先文档说,这个参数接受两种情况:
- 如果是0,icmp的错误信息会从exiting interface带着primary address发出
- 如果非0,错误信息会从接收到引起这个错误的包的接口带着primary address发出
首先这里我们遇到了两个陌生的词汇exiting interface和primary address。
在网络术语中,exiting interface通常指的是数据包离开网络设备的那个网络接口。
至于什么是primary address我们来看看代码吧。
❓前提知识:什么是primary address
源码
primary address中文翻译是首要地址,在内核中你还可以见到secondary address,这个指的是次要地址。
一个地址是不是secondary address会被记录在struct in_ifaddr
地址结构体中的ifa_flags
中。这个flags会在地址插入的时候就决定好,因此我们来看看Linux地址插入的关键代码(下文中文部分为自己添加的注释)。
// defined in https://github.com/torvalds/linux/blob/2594faafeee2f4406ff82790604e4e3f55037d60/net/ipv4/devinet.c#L476 |
补充一下scope
enum rt_scope_t {
RT_SCOPE_UNIVERSE=0, // 等于global
/* User defined values */
RT_SCOPE_SITE=200,
RT_SCOPE_LINK=253,
RT_SCOPE_HOST=254,
RT_SCOPE_NOWHERE=255
};
结论
看完代码我们可以给出结论了:
- primary address:当前网卡上不存在与要插入地址是同一个子网的IP,则新插入地址为primary address。
- secondary address:当前网卡上已存在与插入地址是同一个子网的IP,后插入的地址为secondary address
我们还可以看出in_device中地址链表的分布方式了:
若此时我想插入一个127.0.2.1/24,结果将如下:
此时如果插入一个192.168.2.3/24,结果将如下:
🔎 研究icmp_errors_use_inbound_ifaddr代码
知道了什么是primary address以后,我们再来看看这个参数的内核代码
源码
// defined in https://github.com/torvalds/linux/blob/2594faafeee2f4406ff82790604e4e3f55037d60/net/ipv4/icmp.c#L587C1-L589C2 |
inet_select_addr
// defined in https://github.com/torvalds/linux/blob/994d5c58e50e91bb02c7be4a91d5186292a895c8/net/ipv4/devinet.c#L1325 |
icmp_route_lookup
这里我们主要关注上文中的saddr是怎么赋值到最终的作为发出结构体flow4上的即可
// defined in https://github.com/torvalds/linux/blob/815fb87b753055df2d9e50f6cd80eb10235fe3e9/net/ipv4/icmp.c#L476 |
✅结论
我们理一下逻辑:
-
只有当一个输入的包,是转发包时(目的地非本机),该参数才会起作用
-
如果开启了该参数,只会影响saddr的值的选择,saddr的值会被被设置为输入接口的相关地址,其中:
-
如果输入地址是primary ip
- 那么遇到第一个和输入包的源地址是同一个子网的primary ip时会立即选择它为saddr
- 如果没有那就是输入接口的地址链表中最后的一个primary ip,这个ip的scope是所有primary ip中最大的
-
会忽略所有的secondary ip
-
如果在当前接口上找不到任何primary ip
- 判断当前设备是否是VRF的从设备或主设备,如果是就尝试先获取VRF主设备的地址,如果主设备没有合法地址,会尝试获取同一个VRF下面其它子设备是否有合法地址
- 如果当前设备不是VRF设备,会尝试遍历所有不属于VRF设备的其它设备,从他们上面获取一个合法IP,由于遍历的链表的第一个元素是lo接口,所以在此情况下,是符合文档中的,找不到任何地址,会用第一个回环地址的地址来做为源地址
-
-
如果没开启参数,saddr的值为0
- 依靠路由系统根据构造出包的ip destination address来确定saddr的值
- 路由系统确定出的值,一般就是匹配到的路由,后面的src标记,如
default via 192.168.114.1 dev wlp0s20f3 proto dhcp src 192.168.114.110 metric 20600
中就是192.168.114.110
- 路由系统确定出的值,一般就是匹配到的路由,后面的src标记,如
- 依靠路由系统根据构造出包的ip destination address来确定saddr的值
所以开不开这个参数,只会对转发包有影响。在一般情况下,我们完全没有必要开启这个函数,只有我们有很复杂的路由规则的时候,对于来自同一个地址的ip,输入和输出时的路由不一致,导致从不同接口出去的时候,这才会带来影响。