arp_ignore背后的rp_filter与arp_filter

简介:
操作系统:Debian 3
机器和网络配置:
测试机器eth0:
inet addr:192.168.1.82
当事机配置:
eth0:
inet addr:192.168.1.247/HWaddr 00:15:17:F4:9A:E0 
eth1:
inet addr:192.168.1.246/HWaddr 00:15:17:F4:9A:E1
eth0和eth1插于同一台交换机上,配置同一网段ip,路由如下:
192.168.1.0 * 255.255.255.0 eth0
192.168.1.0 * 255.255.255.0 eth1
测试过程:
1.将当事机的内核参数net.ipv4.conf.XXX.arp_ignore设置为0
2.清除测试机的arp缓存
3.在测试机上ping当事机的eth0或者eth1
期望结果:
既然arp_ignore设置成了0,那么如果在测试机上抓取arp的回复包的话,应该有两条回复,分别来自当事机的eth0和eth1:
15:20:34.665840 arp reply 192.168.1.247 is-at 00:15:17:f4:9a:e1
15:20:34.665856 arp reply 192.168.1.247 is-at 00:15:17:f4:9a:e0
然后测试机取晚到的那一个作为自己的arp缓存项。可是只抓取到了一个arp回复包,并且在当事机的eth1上抓取arp请求包,已经抓到了,只是eth1没有回复:
15:20:34.665856 arp reply 192.168.1.247 is-at 00:15:17:f4:9a:e0
很显然只有eth0回复了,而eth1没有回复,这是怎么回事呢?查看当事机的路由,发现始终是:
192.168.1.0 * 255.255.255.0 eth0
192.168.1.0 * 255.255.255.0 eth1
...
即使将eth1 down掉然后再起来路由也依然如此,并没有交换位置,而arp回复的恰恰就是eth0的mac地址,直观感觉和路由有关系,再看arp处理的源代码,发现核心功能全在arp_process,而最最核心的莫非下面的小段:
if (arp->ar_op == htons(ARPOP_REQUEST) && [0]ip_route_input(skb, tip, sip, 0, dev) == 0) {
    rt = (struct rtable*)skb->dst;
    addr_type = rt->rt_type;
    if (addr_type == RTN_LOCAL) { //查找本机的ip地址对应的mac,路由结果必然是local的
        n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
        if (n) {
            int dont_send = 0;
            if (!dont_send) //ignore判断,太熟悉了,略过
                [2]dont_send |= arp_ignore(in_dev,dev,sip,tip);
            [1]if (!dont_send && IN_DEV_ARPFILTER(in_dev)) //filter判断,本质上也是在确保arp回复包路由结果的出口设备和arp请求的入口设备相一致
                dont_send |= arp_filter(sip,tip,dev); 
            if (!dont_send)
                arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);
            neigh_release(n);
...
可见影响arp回复的是上述的[0],[1]和[2],而不仅仅是[2],因此肯定是[0]或者[1]中使得arp回复没能发送,先看[0],那就是一个路由选择,注意,一般情况下,路由选择仅仅根据目的ip地址进行查找,一般不管出入口设备信息的,因此,根据当事机上的路由表情况,如果发现要有包去往192.168.1.0/24网段,那么肯定会选择第一个找到的路由,就是eth0出口的路由,在ip_route_input的内部如果路由的结果是本机的话(对于一般的arp请求,这是肯定的),那么会调用fib_validate_source(细节在rfc1812):
if (res.type == RTN_LOCAL) {
    int result;
    result = fib_validate_source(saddr, daddr, tos,
                loopback_dev.ifindex,
                dev, &spec_dst, &itag);
正是这个fib_validate_source使得本来能成功的路由查找失败了:
int fib_validate_source(...)
{
    struct in_device *in_dev;
    //将目的地址和源地址反转,验证如此的路由出口是否和正方向的入口一致。比如如果一个包的源地址是s1,目的地址是d1,从e1进入,那么在开启源验证的情况下,源为d1,目的为s1的路由出口必须是e1,正所谓从哪里进入,从哪里出去
    struct flowi fl = { .nl_u = { .ip4_u =
                      { .daddr = src,
                    .saddr = dst,
                    .tos = tos } },
                .iif = oif };
    ...
    in_dev = __in_dev_get(dev);
    if (in_dev) {
        no_addr = in_dev->ifa_list == NULL;
        rpf = IN_DEV_RPFILTER(in_dev); //是否启用源地址验证,这是通过内核参数net.ipv4.conf.eth0.rp_filter的值来决定的
    }
    ...
    if (fib_lookup(&fl, &res))
        goto last_resort;
    ...
    if (FIB_RES_DEV(res) == dev)
    {
        //...如果反方向向的路由出口设备和正方向的入口设备一致,那么不会有问题,也是期望的
        return ret;
    }
    ...
    if (rpf) //如果开启了源地址验证,而反方向的出口又和正方向的入口不一致,那么出错!
        goto e_inval;
    ...
}
现在由于eth1已经抓到了arp请求包,并且ip_route_input也可以路由:
192.168.1.0 * 255.255.255.0 eth1
可是却没有发送arp回复,根据上面的理论分析看一下net.ipv4.conf.eth0.rp_filter这个值,果然在当事机上该值为1,很显然是在fib_validate_source失败了,现在将其改为0,再次进行上述测试,和期望的一样,得到了两条arp回复,内核文档Documentation/networking/ip-sysctl.txt中有rp_filter的条目,其最后:
Default value is 0. Note that some distributions enable it in startup scripts.
因此我们知道,Debian 3就是这里的one of 'some distributions'。

     除了这个rp_filter之外,另一个影响arp回复的就是arp_filter,net.ipv4.conf.XXX.arp_filter这个值得默认值是0,也就是不做检查,如果将之设置成1,即使rp_filter为0(停用源地址验证),arp回复也是不会发送的,看arp_filter的代码,发现其和fib_validate_source的实现很类似,只是简单很多。既然rp_filter已经能搞定出口入口相一致的问题,为何要在arp模块中再次存在arp_filter呢?这是一个层次的问题,rp_filter是对整个路由系统起作用的,而arp_filter仅仅针对arp系统,二者的共存旨在解决路由系统和arp系统的配置策略不一致的问题。


 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1271138


相关文章
|
2月前
|
人工智能 关系型数据库 Linux
Xshell、MobaXterm 之外的新选择:uniTerm 开源终端软件,不到 10MB,覆盖 20+ 协议
uniTerm是一款不到10MB的开源跨平台终端工具(Apache 2.0),集成SSH/Telnet/RDP/VNC/SFTP/SMB/MySQL/Redis等20+协议,内置AI Agent、分屏、云同步与中文支持,一站式替代Xshell、Navicat等多款客户端。
736 7
|
缓存 Kubernetes API
K8S 性能优化 - K8S APIServer 调优
K8S 性能优化 - K8S APIServer 调优
|
域名解析 编解码 负载均衡
【域名解析DNS专栏】域名解析中的EDNS扩展:提升DNS协议灵活性
【5月更文挑战第27天】EDNS(Extension Mechanisms for DNS)是为了解决传统DNS协议在复杂网络环境下的灵活性和扩展性问题而诞生的技术。它允许在DNS请求和响应中添加额外选项,提高查询效率,支持更大数据范围,增强安全性,并实现负载均衡和故障转移。通过在DNS消息中包含EDNS部分,客户端和服务器能交换更多信息,实现更复杂的逻辑。EDNS的使用示例代码展示了如何在Python中创建和处理EDNS选项。随着技术进步,EDNS将在域名解析领域扮演更重要角色。
1432 1
|
监控 NoSQL Java
几种主流的分布式定时任务,你知道哪些?
几种主流的分布式定时任务,你知道哪些?
|
自然语言处理 达摩院 索引
Elasticsearch 中文分词器
在使用Elasticsearch 进行搜索中文时,Elasticsearch 内置的分词器会将所有的汉字切分为单个字,对用国内习惯的一些形容词、常见名字等则无法优雅的处理,此时就需要用到一些开源的分词器,以下分别介绍几种常见的中文分词器
11495 2
Elasticsearch 中文分词器
|
Kubernetes 安全 Linux
在k8S中,Calico网络组件实现原理是什么?
在k8S中,Calico网络组件实现原理是什么?
|
Ubuntu Linux Shell
深入理解Linux命令 - autoconf
`autoconf`是Linux软件开发中的自动化配置工具,它根据`configure.ac`模板生成`configure`脚本,检测系统特性并创建适应性的Makefile。工作流程包括读取模板、执行宏、生成配置脚本及运行配置。安装`autoconf`后,编写`configure.ac`,运行`autoconf`生成`configure`,再执行`./configure`以配置项目。此工具简化了跨平台编译的复杂性。
|
安全 UED
麒麟的版本 V10 (Lance) V10 (Tercel) 有什么区别
【6月更文挑战第26天】麒麟的版本 V10 (Lance) V10 (Tercel) 有什么区别
14330 2
|
网络协议 安全 网络安全
解析IPv6报文结构
【7月更文挑战第1天】IPv6报文结构包括基本头和可选的扩展头,基本头固定40字节,含8个字段,不支持分片,提升了处理效率。扩展头灵活处理选项,长度为8字节的倍数,可包含如路由、分片、认证和安全封装等信息。多个扩展头按特定顺序排列,目的选项头可出现两次。