单机多网卡互通——问题跟踪+工具分析

news2024/11/26 6:21:39

一、背景

想搭建soft ROCE(RXE)与实体ROCE设备互联的测试环境,为了节省机器以及使用方便,预想在配备ROCE卡的主机上,用另一个网卡绑定soft ROCE,然后互通。

[ETH1 + ROCE] <--------------------> [ETH2 + RXE]

二、问题跟踪

ROCE向RXE发送send only,两端都没有收到cqe。

1、tcpdump抓取ETH2网卡,抓获了ROCE v2的send only报文,没有ack报文。

也就是说数据包已经成功环回,外部链路正常;RXE的接受处理路径出现异常,导致没有响应。

2、使用funcgraph抓取,RXE注册的隧道接收处理函数

perf-tools funcgraph rxe_udp_encap_recv

没有捕获到,这跟预想的不一样,这说明:

数据包在进入RXE的处理之前,已经被丢掉了。需要看数据包的底层接收路径。

3、然后开启了将近一天的四处碰壁阶段。

由于过往对网络的内核处理路径了解很少,理了一下rxe_udp_encap_recv的调用路径,推测应该是通过udp_unicast_rcv_skb接口调用的,其余还有mcast/broadcast两个接口,但我们这个数据包不应该是多播和广播。

然后trace了一下udp_unicast_rcv_skb接口,还是没有捕获到!

这段代码是这个样子的:

int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,
		   int proto)
{
    /* .............*/
	sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable);
	if (sk)
		return udp_unicast_rcv_skb(sk, skb, uh);
    /* .............*/
}

4、本着就近原则,trace了一下udp4_lib_lookup接口,发现可以trace 到!!!那问题不就出在这里了么???

所以接下来用bpftrace跟踪了下udp4_lib_lookup的返回值,确实是0,没有拿到socket,所以报文被丢了!

5、为什么拿不到socket

bpftrace跟踪了upd4_lib_lookup中的入参,源目端口,源目地址,数值都是正确的!那为啥拿不到socket?RXE里监听这个端口的socket创建有问题???

试图梳理一下socket的创建、查找,短时间不好搞定,未果。

6、udp_rcv没有执行

这时候想要么funcgraph抓一下udp报文的处理入口,udp_rcv,看一下他的执行路径,在哪个环节跟正常的报文不一样?也可能前置出错,引发的问题。

结果发现,udp_rcv没有捕获到!!!离了个大谱!!

7、bpftrace跟踪udp_lib_lookup的调用栈

那谁调用了udp_lib_lookup呢?把它的调用栈打印出来看一下。

咦,是在udp_gro_receive接口中调用的。

#!/usr/bin/env bpftrace

#include <linux/netdevice.h>
#include <linux/udp.h>
#include <linux/skbuff.h>
#include <linux/netdev_features.h>


kprobe:__udp4_lib_lookup {
    printf("saddr=0x%x,sport=0x%x,daddr=0x%x,dport=0x%x\n",arg1, arg2, arg3, arg4);
    @[kstack]=count();
}

kretprobe:udp4_lib_lookup2 {
    printf("sock=0x%x\n",retval);
    if(retval != 0) {
        printf("encap_recv = 0x%x, gro_receive= 0x%x, gro_enabled=%d\n", ((struct udp_sock *)retval)->encap_rcv, ((struct udp_sock *)retval)->gro_receive, ((struct udp_sock *)retval)->gro_enabled);
    }
}

看到是udp4_gro_receive接口调用了udp4_lib_lookup,然后没有查询到socket。

[root@localhost leiyanjie]# bpftrace udp.bt
Attaching 3 probes...
saddr=0x3231a8c0,sport=0x8,daddr=0x7e33a8c0,dport=0xb712
sock=0x0
sock=0xa7ef9b00
encap_recv = 0xc071a520, gro_receive= 0x0, gro_enabled=0
skb->dev=0xffff9280187d7000, skb->dev->features = 0x10000134829, GRO_FEATURE_BIT=57
^C

@[
    __udp4_lib_lookup+1
    udp4_gro_receive+402
    inet_gro_receive+675
    dev_gro_receive+1574
    napi_gro_receive+96
    receive_buf+911
    virtnet_poll+331
    net_rx_action+309
    __softirqentry_text_start+188
    asm_call_sysvec_on_stack+15
    do_softirq_own_stack+55
    irq_exit_rcu+208
    common_interrupt+120
    asm_common_interrupt+30
    cpuidle_enter_state+214
    cpuidle_enter+41
    do_idle+452
    cpu_startup_entry+25
    start_secondary+276
    secondary_startup_64_no_verify+176
]: 1

8、跟踪udp4_gro_receive的执行路径

发现,怎么这个接口接口这么简单就退出了呀,是不是没有接收完,把报文丢掉了?!!

# ad perf-tools funcgraph udp4_gro_receive
Tracing "udp4_gro_receive"... Ctrl-C to end.
   1)               |  udp4_gro_receive() {
   1)   0.197 us    |    irq_enter_rcu();
   1)               |    __sysvec_irq_work() {
   1)               |      __wake_up() {
   1)               |        __wake_up_common_lock() {
   1)   0.644 us    |          _raw_spin_lock_irqsave();
   1)               |          __wake_up_common() {
   1)               |            autoremove_wake_function() {
   1)               |              default_wake_function() {
   1)               |                try_to_wake_up() {
   1)   0.801 us    |                  _raw_spin_lock_irqsave();
   1)               |                  select_task_rq_fair() {
   1)   0.365 us    |                    available_idle_cpu();
   1)   0.364 us    |                    cpus_share_cache();
   1)   1.268 us    |                    update_cfs_rq_h_load();
   1)               |                    select_idle_sibling() {
   1)   0.374 us    |                      available_idle_cpu();
   1)   0.560 us    |                    }
   1)   0.086 us    |                    rcu_read_unlock_strict();
   1)   3.776 us    |                  }
   1)               |                  ttwu_queue_wakelist() {
   1)               |                    __smp_call_single_queue() {
   1)   0.569 us    |                      send_call_function_single_ipi();
   1)   1.396 us    |                    }
   1)   1.776 us    |                  }
   1)   0.097 us    |                  _raw_spin_unlock_irqrestore();
   1)   7.576 us    |                }
   1)   8.340 us    |              }
   1)   8.544 us    |            }
   1)   9.832 us    |          }
   1)   0.094 us    |          _raw_spin_unlock_irqrestore();
   1) + 11.127 us   |        }
   1) + 11.301 us   |      }
   1) + 12.007 us   |    }
   1)               |    irq_exit_rcu() {
   1)   0.087 us    |      idle_cpu();
   1)               |      tick_nohz_irq_exit() {
   1)   0.102 us    |        ktime_get();
   1)   0.287 us    |      }
   1)   0.647 us    |    }
   1)               |    __udp4_lib_lookup() {
   1)   0.700 us    |      udp4_lib_lookup2();
   1)               |      udp4_lib_lookup2() {
   1)   0.227 us    |        compute_score();
   1)   0.252 us    |        compute_score();
   1)   1.306 us    |      }
   1)   2.482 us    |    }
   1)   0.243 us    |    udp_gro_receive();
   1)   0.086 us    |    rcu_read_unlock_strict();
   1) + 20.713 us   |  }
^C
Ending tracing...

然后掉进了一个大坑,查看udp_gro_receive的代码如下:

	if (!sk || !udp_sk(sk)->gro_receive) {
		if (skb->dev->features & NETIF_F_GRO_FRAGLIST)
			NAPI_GRO_CB(skb)->is_flist = sk ? !udp_sk(sk)->gro_enabled : 1;

		if ((!sk && (skb->dev->features & NETIF_F_GRO_UDP_FWD)) ||
		    (sk && udp_sk(sk)->gro_enabled) || NAPI_GRO_CB(skb)->is_flist)
			return call_gro_receive(udp_gro_receive_segment, head, skb);

		/* no GRO, be sure flush the current packet */
		goto out;
	}

那么udp_gro_receive_segment应该是要被执行的,可实际没有执行。为什么?

因为我们的网卡使能了GRO,而RXE只提供了encap_rcv接口,实际也需要注册自己的gro_receive么?!

	tnl_cfg.encap_type = 1;
	tnl_cfg.encap_rcv = rxe_udp_encap_recv;

	/* Setup UDP tunnel */
	setup_udp_tunnel_sock(net, sock, &tnl_cfg);

void setup_udp_tunnel_sock(struct net *net, struct socket *sock,
			   struct udp_tunnel_sock_cfg *cfg)
{
	struct sock *sk = sock->sk;

	/* Disable multicast loopback */
	inet_sk(sk)->mc_loop = 0;

	/* Enable CHECKSUM_UNNECESSARY to CHECKSUM_COMPLETE conversion */
	inet_inc_convert_csum(sk);

	rcu_assign_sk_user_data(sk, cfg->sk_user_data);

	udp_sk(sk)->encap_type = cfg->encap_type;
	udp_sk(sk)->encap_rcv = cfg->encap_rcv;
	udp_sk(sk)->encap_err_rcv = cfg->encap_err_rcv;
	udp_sk(sk)->encap_err_lookup = cfg->encap_err_lookup;
	udp_sk(sk)->encap_destroy = cfg->encap_destroy;
	udp_sk(sk)->gro_receive = cfg->gro_receive;
	udp_sk(sk)->gro_complete = cfg->gro_complete;

	udp_tunnel_encap_enable(sock);
}

RXE是标准内核驱动随着内核版本更新升级的,理论上不应该不支持开启了GRO的网卡?

9、bpfrrace跟踪这段代码的参数

因为不熟悉,所以全都靠猜:是不是我们的网卡置位NETIF_F_GRO_FRAGLIST这个标识,就可以执行后续的call_gro_receive?

# bpftrace udp.bt
Attaching 3 probes...
saddr=0x3231a8c0,sport=0x8,daddr=0x7e33a8c0,dport=0xb712
sock=0x0
sock=0xa7ef9b00
encap_recv = 0xc071a520, gro_receive= 0x0, gro_enabled=0
skb->dev=0xffff9280187d7000, skb->dev->features = 0x10000134829, GRO_FEATURE_BIT=57

10、增加GRO_FRAGLIST属性

man ethtool

ethtool -k eth2 查询网卡属性

ethtool -K eth2 **** on/off 设置网卡属性

# ethtool -k eth2 | grep gro
rx-gro-hw: off [fixed]
rx-gro-list: off

# thtool -K eth2 rx-gro-list on 

然后bpftrace发现,这个标识确实在netdev->features开启成功了,但并还是没有进入后续的segment收包流程。

再次进入死胡同。

三、问题跟踪2

网络搜索了一下udp_gro_receive的相关流程,也不能够帮助在短时间内建立非常清晰的理解。

比较幸运的是,udp的报文并不多,所以我们在udp_gro_receive接口中轻易的抓到了报文,而且从sk_buff结构中拿到了netdev的地址。

1、bpftrace根据参数过滤eth2的ip_rcv接口

所以,尝试抓取ip_rcv接口,数据有没有进入ip_rcv接口呢?

eth0的IP报文是很多的,咋筛选出eth2的报文呢?既然我们拿到了netdev的地址,就可以根据netdev精确过滤出eth2的报文。

kprobe:ip_rcv {
    if(arg1 == 0xffff9ab4a371a000) {
        printf("get eth2 packet\n");
        @[kstack]=count();
    }
}

捕获到了get eth2 packet!

这说明之前推测的GRO丢包是错误的,其实报文已经送到了IP层,没有进入UDP层。是在IP层丢掉的。

saddr=0x3231a8c0,sport=0x8,daddr=0x8734a8c0,dport=0xb712
sock=0x0
sock=0x1680bf00
encap_recv = 0xc055d520, gro_receive= 0x0, gro_enabled=0
skb->dev=0xffff9ab4a371a000, skb->dev->features = 0x200010000134829, GRO_FEATURE_BIT=57
get eth2 packet

本来用funcgraph跟踪一下ip_rcv接口会更直观,看报文丢弃在了哪个环节,但因为funcgraph无法传入参数过滤,eth0的IP报文也比较多,就没有采用这种方式。

2、ip_rcv_finish接口返回失败

ip_rcv接口比较简单,很容易就跟踪到是ip_rcv_finish接口返回失败,导致报文没有进一步上送。

kretprobe:ip_rcv_finish {
    printf("ret=0x%x\n",retval);
}

3、tracepoint跟踪kfree_skb_reason

分析了ip_rcv_finish_core接口,看到有多个位置可能导致异常返回,异常返回都会调用kfree_skb_reason这个接口传递释放sk_buff,而且会把错误码(drop_reason)传入。

kfree_skb_reason在我安装的5.10内核版本上实际测试,没有查询到bpftrace入口。

但是在我5.15的内核代码中,是有trace点的,可以把reason打印出来。

void kfree_skb_reason(struct sk_buff *skb, enum skb_drop_reason reason)
{
	if (!skb_unref(skb))
		return;

	trace_kfree_skb(skb, __builtin_return_address(0), reason);
	__kfree_skb(skb);
}

所以开始研究使用tracepoint。/sys/kernel/debug/tracing/available_events中记录了所有的可trace事件。可以看到

/sys/kernel/debug/tracing # cat available_events | grep kfree_skb
skb:kfree_skb

那么如何使能这个事件?进入events/skb/kfree_skb目录,有如下接口文件:

/sys/kernel/debug/tracing/events/skb/kfree_skb # ls
enable  filter  format  id  trigger

enable输入1,就可以使能trace,不需要其他开关。

filter是可以输入过滤条件的,比如过滤eth2的报文?这个需要后面学习一下。

format是显示trace日志格式的。

实际的trace日志,还可以显示pid、cpu和时间,挺好的。结果如下:

因为我的机器上的内核版本是5.10,所以看不到reason,这是个让人伤心的故事。从某个patch来看,确实是后面的版本合入的kfree_skb_reason。原来正常的skb释放也是走这个路径,所以trace kfree_skb有特别多的日志,无法区分。

4、tracepoint跟踪kfree_skb_reasonconsume_skb

在高版本内核中,正常的skb释放走consume_skb路径,drop的skb释放走kfree_skb_reason路径,就可以区分出来了。

5、bpftrace打桩跟踪执行路径

既然看不出来drop reason,那其实直接用bpftrace在ip_recv_finish_core的各个接口中打桩是可以看到执行路径的。

因为我们也不是大流量的环境,网络报文的密度比较小。所以抓到udp报文后,紧跟着的打印可以认为是同一条报文的处理路径中的。

最终确认是在ip_route_input_slow接口,路由失败引发丢包的。

kprobe:ip_rcv {
    if(arg1 == 0xffff9ab4a371a000) {
        printf("get eth2 packet\n");
        @[kstack]=count();
    }
}

kprobe:ip_rcv_finish {
    if(((struct sk_buff *)arg2)->dev == 0xffff9ab4a371a000) {
        printf("get eth2 packet\n");
        @[kstack]=count();
    }
}

kretprobe:ip_route_use_hint {
    printf("ip_route_use_hind, ret=0x%x\n", retval);
}

kretprobe:udp_v4_early_demux {
    printf("udp_v4_early_demux, ret=0x%x\n", retval);
}

kretprobe:ip_route_input_noref {
    printf("ip_route_input_noref, ret=%d\n", retval);
}

kretprobe:ip_rcv_finish {
    printf("ret=0x%x\n",retval);
}

kretprobe:ip_route_input_slow {
    printf("ip_route_input_slow, ret=%d\n", retval);
}

6、为什么会路由失败??

我简单尝试了一下,用eth1 ping eth2,发现也是无法ping通的。这台机器上的3个网卡都无法互相ping通。

所以此时意识到,同一台机器上的两个网卡之间是不是就是不能通信的?

然后开始资料查询,重点针对一台机器上的多个网卡。

有几篇文章都作了说明,但原理又都不清楚,只是说linux对此的支持不友好。

反而是最终在自己疯狂的尝试中,误打误撞解决了问题。

sysctl -w /proc/sys/net/ipv4/conf/all/accep_local=1
sysctl -w /proc/sys/net/ipv4/conf/all/rp_filter=2

IP路由的相关知识后续学习补充。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1897826.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【qt】如何获取网卡的信息?

网卡不只一种,有有线的,有无线的等等 我们用QNetworkInterface类的静态函数allInterfaces() 来获取所有的网卡 返回的是一个网卡的容器. 然后我们对每个网卡来获取其设备名称和硬件地址 可以通过静态函数humanReadableName() 来获取设备名称 可以通过静态函数**hardwareAddre…

TC3xx NvM小细节解读

目录 1.FlsLoader Driver和FlsDmu Driver 2. FlsLoader小细节 3.小结 大家好&#xff0c;我是快乐的肌肉&#xff0c;今天聊聊TC3xx NvM相关硬件细节以及MCAL针对NvM的驱动。 1.FlsLoader Driver和FlsDmu Driver 在最开始做标定的时候&#xff0c;认为标定数据既然是数据&…

Java传引用问题

本文将介绍 Java 中的引用传递&#xff0c;包括其定义、实现方式、通过引用修改原来指向的内容和通过引用修改当前引用的指向的区别 目录 1、引用传递的概念 2、引用传递的实现方式 3、传引用会发生的两种情况&#xff1a; 通过引用修改当前引用的指向 通过引用修改原来指…

C++基础(七):类和对象(中-2)

上一篇博客学的默认成员函数是类和对象的最重要的内容&#xff0c;相信大家已经掌握了吧&#xff0c;这一篇博客接着继续剩下的内容&#xff0c;加油&#xff01; 目录 一、const成员&#xff08;理解&#xff09; 1.0 引入 1.1 概念 1.2 总结 1.2.1 对象调用成员函数 …

人工智能系列-NumPy(二)

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 链接数组 anp.array([[1,2],[3,4]]) print(第一个数组&#xff1a;) print(a) print(\n) bnp.array([[5,6],[7,8]]) print(第二个数组&#xff1a;) print(b) print(\n) print…

web基础与HTTP协议(企业网站架构部署与优化)

补充&#xff1a;http服务首页文件在/var/www/html下的&#xff0c;一定是index.html命名的文件。才会显示出来。 如果该路径下没有相应的文件&#xff0c;会显示/usr/share/httpd/noindex下的index.html文件。 如果/usr/share/httpd/noindex没有index.html文件&#xff0c;会…

使用Llama3/Qwen2等开源大模型,部署团队私有化Code Copilot和使用教程

目前市面上有不少基于大模型的 Code Copilot 产品&#xff0c;部分产品对于个人开发者来说可免费使用&#xff0c;比如阿里的通义灵码、百度的文心快码等。这些免费的产品均通过 API 的方式提供服务&#xff0c;因此调用时均必须联网、同时需要把代码、提示词等内容作为 API 的…

2024年亚太中文赛数学建模竞赛B题 洪水灾害的数据分析与预测详细思路解析

2024年亚太中文赛数学建模竞赛B题 洪水灾害的数据分析与预测详细思路解析 解题方法&#xff1a; 首先就是对数据进行数据的预处理包括缺失值和异常值处理&#xff0c;之后就是分析哪些指标与洪水的发生有着密切的关联&#xff0c;可以使用相关性分析&#xff08;建议使用斯皮尔…

WMS,OMS,TMS三者之间是什么关系?

WMS、OMS 和 TMS 是供应链管理中的三个重要系统&#xff0c;它们分别管理仓库、订单和运输的不同方面。 三者的功能&#xff1a; 1、WMS (Warehouse Management System) - 仓库管理系统&#xff1a; 1):主要负责仓库内部的操作和管理&#xff0c;包括库存管理、仓储空间优化、…

4K Tokkit Pro for Mac:轻松管理TikTok的利器

在TikTok的海洋中畅游&#xff0c;你是否想有一个得力助手来帮你高效管理你的账号&#xff1f;4K Tokkit Pro for Mac正是你的不二之选&#xff01; 这款专为Mac用户打造的TikTok管理工具&#xff0c;拥有简洁的界面和强大的功能&#xff0c;让你轻松下载、管理和分享喜欢的Ti…

【算法笔记自学】入门篇(2)——算法初步

4.1排序 自己写的题解 #include <stdio.h> #include <stdlib.h>void selectSort(int A[], int n) {for(int i 0; i < n - 1; i) { // 修正索引范围int k i;for(int j i 1; j < n; j) { // 修正索引范围if(A[j] < A[k]) {k j;}}if (k ! i) { // 仅在…

[SAP ABAP] 版本管理

版本管理是指软件开发过程中各种程序代码、配置文件以及说明文档等文件变更的管理 生成版本 版本管理 对比版本 点击上述版本管理即可进行版本对比操作 补充扩展 我们可以使用事务码SE10对传输请求进行创建、修改、删除、合并以及更改所有者等操作 使用事务码SCC1进行不同cl…

微观特征轮廓尺寸测量:光学3D轮廓仪、共焦显微镜与台阶仪的应用

随着科技进步&#xff0c;显微测量仪器以满足日益增长的微观尺寸测量需求而不断发展进步。多种高精度测量仪器被用于微观尺寸的测量&#xff0c;其中包括光学3D表面轮廓仪&#xff08;白光干涉仪&#xff09;、共聚焦显微镜和台阶仪。有效评估材料表面的微观结构和形貌&#xf…

免费代理 IP 如何泄露您的个人信息?

互联网时代&#xff0c;信息安全和隐私保护成为人们关注的焦点。很多用户出于各种需要&#xff0c;使用代理服务器浏览网页或进行其他网络活动&#xff0c;其中免费代理IP因其免费的特点而受到广泛青睐。然而&#xff0c;免费代理IP并不总是一个安全可靠的选择&#xff0c;它们…

新架构下服务建模,关键在这6步!

随着AUTOSAR、SOA、以太网通讯等新技术、新理念的成熟化&#xff0c;面向软件、硬件、网络、电气等多领域的电子电气系统经历了多代架构方法论的迭代。如何跟随新技术和新理念的浪潮&#xff0c;构建起新型的汽车电子电气架构平台&#xff0c;以实现新老技术的快速更替和融合&a…

塑料颗粒烘干机相关制作技术

网盘 https://pan.baidu.com/s/1urgMadWbneDT-HNOZFwZOw?pwd5idr 再生塑料颗粒用烘干装置.pdf 塑料制品加工用颗粒烘干装置.pdf 塑料颗粒烘干机.pdf 塑料颗粒生产加工用循环烘干装置.pdf 塑料颗粒用高效率烘干装置.pdf 颗粒物料烘干机.pdf

windows启动Docker闪退Docker desktop stopped

Windows启动Docker闪退-Docker desktop stopped 电脑上很早就安装有Docker了&#xff0c;但是有一段时间都没有启动了&#xff0c;今天想启动启动不起来了&#xff0c;打开没几秒就闪退&#xff0c;记录一下解决方案。仅供参考 首先&#xff0c;参照其他解决方案&#xff0c;本…

Servlet与Servlet容器

什么是Servlet? Servlet是Java EE&#xff08;现称Jakarta EE&#xff09;中的一个组件&#xff0c;通常用于创建动态Web内容。Servlet是运行在Web服务器上的Java程序&#xff0c;它处理客户端的请求并生成响应。Servlet的核心功能是处理HTTP请求和响应。下面是一个servlet例…

imx6ull/linux应用编程学习(11)CAN应用编程基础

关于裸机的can通信&#xff0c;会在其他文章发&#xff0c;这里主要讲讲linux上的can通信。 与I2C,SPI等同步通讯方式不同&#xff0c;CAN通讯是异步通讯&#xff0c;也就是没有时钟信号线来保持信号接收同步&#xff0c;也就是所说的半双工&#xff0c;无法同时发送与接收&…