ethtool查询virtio网卡异常的问题

一、背景

用户反馈,虚机内使用ethtool查询网卡信息,显示如下:

而以前显示并没有这个奇怪的字符串,n/a就是正常显示为0的。比如,查看主机上的网卡:

[root@instance-dn0b13yt ethtool]# ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX:             0
TX:             0
Other:          0
Combined:       4
Current hardware settings:
RX:             0
TX:             0
Other:          0
Combined:       4

因为虚机的网卡是VFIO透传我们的VF网卡,所以怀疑可能是VF工作有一些异常。首先从我们自身的设计和VFIO的工作原理来看,PF和VF应该不会在队列个数上有什么差异。所以综合分析推测,跟工作环境有关系(内核版本、ethtool版本),所以向客户要了相应的信息,Centos8.4及centos发布的4.18内核。ethtoool --version查看版本号是5.8。

相比于主机在用的centos7镜像和内部自研的4.18内核,版本都是有差异的。主机的ethtool --version查看版本号是4.8。

二、定位过程

定位过程大概记录一下

1、在拿到客户的版本号之前,自己启动了一个虚机验证,虚机内ethtool显示正常(后面都会用正常和异常来表示,能看懂即可)。也说明大概率跟VF本身没有关系。

看了一下也是centos7的镜像,内核3.10,ethtool4.8。

2、去下载了ethtool的源码,有其他博客写明了下载地址,可以下载到所有版本的源码,很赞。

但短时间内也没有头绪和时间去读代码,所以就strace跟踪了一下ethtool的调用路径。基础不牢的自己艰难的分析了一下调用路径,看到在最后打印结果前,有一个ioctl接口。但在源码里搜索没有看到这个接口。

是不是可以用gdb --args加断点看一下呢,实际测试确实可行。只执行过一次ioctl,而且可以bt看调用路径,能在源码里找到上层接口,这样也找到了源码里的突破口;再加上源码里的打印位置,很快梳理出了整体逻辑。

ethtool 4.8是通过ioctl()接口从内核中获取chanel信息的,然后通过%u打印。内核里的接口也确认过,virtio-net驱动里将combined设置为virtio queue pair个数,rx tx设置为0。所以前几个数值打印就是0。这跟我自己机器上的正常打印日志是相符的。

3、这时候用户反馈了他们的版本信息,并联系测试同学那里拿到了一台跟用户相同的虚机。就方便多了。

将他们的ethool工具直接拷贝到其他机器上,分别是5.10的内核和内部4.18内核,发现在5.10内核上也是显示异常,内部4.18内核显示正常。整理一下结论:

定制4.18内核centos 4.18内核定制5.10内核
ethtool 4.8正常正常异常
ethtool 5.8正常异常异常

这个结果看起来有点混乱,只能认为,不是只跟内核或ethtool版本之一有关,可能是二者配合甚至还有其它元素导致的结果。

4、下载ethtool5.8的源码

查看5.8源码里channel的获取方式,跟ethtool 5.8没有任何区别呀。都是ioctl接口从内核获取数据后,通过%u打印出来。那怎么可能出现ethtool4.8和ethtool 5.8在同一个内核里的行为表现不同呢!!!

这里实在是困惑了一会儿。

5、跟踪ethtool5.8的执行路径

后来还是通过strace跟踪了一下ethtool5.8的执行路径,发现:没有ioctl,在打印结果前,执行了sendto和recv接口!!!然后通过gdb跟踪了一下,原来ethtool5.8是执行了netlink接口呀。

然后就结合strace和gdb辛苦的抠源码,主要是基础不够,对netlink只限于听说没有使用过更不了解原理,分析起来比较吃力。源码细节没有完全理顺,不过整体思路清晰了:

ethtool5.8里增加了一套netlink接口,会优先使用netlink接口,如果发现netlink接口访问内核的路径失败了,则使用ioctl接口。这样可以解释为什么ethtool5.8在不同内核版本上有差异,更高版本的内核对ethtool访问netlink的支持更好,则使用了netlink接口;centos的4.18内核大概率也是回合了内核后来的patch增加了对ethtool访问netlink的支持,自研内核没有做这件事。

另一个关键的点是,为什么netlink访问会显示n/a。

netlink路径中,不再是ioctl结构体的二进制形式,而是返回一个attr[]的array,每个attr里包含[参数名,数值]的信息,而对于数值为0的项目,内核直接过滤掉了。不会放到skb里,也就不会回传给ethtool。

static int channels_fill_reply(struct sk_buff *skb,
			       const struct ethnl_req_info *req_base,
			       const struct ethnl_reply_data *reply_base)
{
	const struct channels_reply_data *data = CHANNELS_REPDATA(reply_base);
	const struct ethtool_channels *channels = &data->channels;

	if ((channels->max_rx &&
	     (nla_put_u32(skb, ETHTOOL_A_CHANNELS_RX_MAX,
			  channels->max_rx) ||
	      nla_put_u32(skb, ETHTOOL_A_CHANNELS_RX_COUNT,
			  channels->rx_count))) ||
	    (channels->max_tx &&
	     (nla_put_u32(skb, ETHTOOL_A_CHANNELS_TX_MAX,
			  channels->max_tx) ||
	      nla_put_u32(skb, ETHTOOL_A_CHANNELS_TX_COUNT,
			  channels->tx_count))) ||
	    (channels->max_other &&
	     (nla_put_u32(skb, ETHTOOL_A_CHANNELS_OTHER_MAX,
			  channels->max_other) ||
	      nla_put_u32(skb, ETHTOOL_A_CHANNELS_OTHER_COUNT,
			  channels->other_count))) ||
	    (channels->max_combined &&
	     (nla_put_u32(skb, ETHTOOL_A_CHANNELS_COMBINED_MAX,
			  channels->max_combined) ||
	      nla_put_u32(skb, ETHTOOL_A_CHANNELS_COMBINED_COUNT,
			  channels->combined_count))))
		return -EMSGSIZE;

	return 0;
}

ethtool在输出结果的时候,netlink路径和ioctl路径也是不一样的,netlink是从返回的结果中解析每一项attr(内核里称为struct nlattr),比如RX没有解析到,就会打印n/a。

static inline void show_u32(const struct nlattr *attr, const char *label)
{
	if (attr)
		printf("%s%u\n", label, mnl_attr_get_u32(attr));
	else
		printf("%sn/a\n", label);
}

int channels_reply_cb(const struct nlmsghdr *nlhdr, void *data)
{
	const struct nlattr *tb[ETHTOOL_A_CHANNELS_MAX + 1] = {};
	DECLARE_ATTR_TB_INFO(tb);
	struct nl_context *nlctx = data;
	bool silent;
	int err_ret;
	int ret;

	silent = nlctx->is_dump || nlctx->is_monitor;
	err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
	if (ret < 0)
		return err_ret;
	nlctx->devname = get_dev_name(tb[ETHTOOL_A_CHANNELS_HEADER]);
	if (!dev_ok(nlctx))
		return err_ret;

	if (silent)
		putchar('\n');
	printf("Channel parameters for %s:\n", nlctx->devname);
	printf("Pre-set maximums:\n");
	show_u32(tb[ETHTOOL_A_CHANNELS_RX_MAX], "RX:\t\t");
	show_u32(tb[ETHTOOL_A_CHANNELS_TX_MAX], "TX:\t\t");
	show_u32(tb[ETHTOOL_A_CHANNELS_OTHER_MAX], "Other:\t\t");
	show_u32(tb[ETHTOOL_A_CHANNELS_COMBINED_MAX], "Combined:\t");
	printf("Current hardware settings:\n");
	show_u32(tb[ETHTOOL_A_CHANNELS_RX_COUNT], "RX:\t\t");
	show_u32(tb[ETHTOOL_A_CHANNELS_TX_COUNT], "TX:\t\t");
	show_u32(tb[ETHTOOL_A_CHANNELS_OTHER_COUNT], "Other:\t\t");
	show_u32(tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT], "Combined:\t");

	return MNL_CB_OK;
}

所以虽然一直称呼n/a为异常显示,实际上它是一种高版本ethtool的一种正常显示行为。

6、为了印证自己的论点,也更好的说服没有时间了解详细原理的同学,在一台通用网卡的机器上更新了5.10的内核,搭配ethtool5.8使用,也成功显示了n/a字样。

7、编译ethtool源码

因为gdb直接执行/usr/sbin里带的ethtool,是找不到debug信息的,很多东西看不到。所以在第5步整个分析和梳理的过程中,还是自己编译了ethtool5.8。记录一下编译步骤。

编译ethtool的方式基本参考了下面的博客,很赞:

移植ethtool至ARM板_ethtool 移植_丿没有蜡笔的小鑫的博客-CSDN博客

依赖于最新的libmnl库,centos7的repo对应的libmnl不够新。所以需要下载新版的源码编译libmnl库,然后编译ethtool。上面的文章里也都有写。另外libmnl最新的版本也是近几年的,自己机器上的gcc7.5看起来版本不够,最终使用gcc-8.2编译的。

实际编译ethtool的步骤

1、auto_gen.sh

2、./configure --prefix=/home/**/kernel/ethtool-5.8/build  CC=/opt/compiler/gcc-8.2/bin/gcc MNL_CFLAGS="-I/home/**/kernel/ethtool-5.8/libmnl/include -I/usr/include" MNL_LIBS="-L/home/leiyanjie/**/ethtool-5.8/libmnl/lib -lmnl"

3、make

4、make install

就可以美美的使用自己编译出的ethtool了,gdb用起来。

小结

这篇文章并没有深刻的原理,算是工具使用过程中遇到的问题。对于通用的netlink也没有做深入的研究(后续需要了解一下)。

之所以记录,是因为一向没有动手能力的自己,下载了ethtool源码、centos内核官方源码,用到了strace、gdb、ethtool开源代码的本地编译,很开心。


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值