公司低端产品使用openwrt定制的mips架构设备,在一个项目中出现了网卡大量丢包问题。使用ethtool -S eth0 查看详细统计,发现 rx_checksum_errors 大量上涨。
用户业务比较特殊,使用了私有协议。报文二层ethernet的type是0x0800,表示3层是ip协议,但是三层报文不是ip报文,而是用户的私有协议。
初步推断是网卡将私有协议当作ip协议进行校验,校验值错误从而将报文丢弃。
为了验证问题,使用 ethtool -K eth0 rx-checksum off 将网卡的收报校验关闭,发现问题依然存在。
难道是推断错误?不太可能,明明统计的就是 rx_checksum_errors。
突然想到,是不是 网卡没有支持rx-checksum开关,导致 ethtool -K eth0 rx-checksum off 命令没有生效?
ethtool工具在内核中是通过 dev_ioctl–>dev_ethtool–>通过ethcmd 来进行对应处理。
为了快速确认 ethtool -K eth0 rx-checksum off 具体使用了哪个ethcmd,我使用funchook 模块,在dev_ethtool 函数前后增加了debug信息,编译并加载hook ko后,在测试centos机器上执行 ethtool -K eth0 rx-checksum off,
通过log发现,该命令调用了多个 ethcmd,但实际使用的是 ETHTOOL_SFEATURES 来设置开关。
ethtool_set_features–>__netdev_update_features–>
if (dev->netdev_ops->ndo_set_features)
err = dev->netdev_ops->ndo_set_features(dev, features);
else
err = 0;
查到这里发现,如果网卡设备没有定义 ndo_set_features 函数的话,则直接返回0。从结果上看好像成功了,但是网卡什么操作没有做。
查看网卡驱动代码,找到 fe_netdev_ops 定义,发现确实没有定义 ndo_set_features 函数。这是ethtool 关闭rx-checksum无效的根本原因。
static const struct net_device_ops fe_netdev_ops = {
.ndo_init = fe_init,
.ndo_uninit = fe_uninit,
.ndo_open = fe_open,
.ndo_stop = fe_stop,
.ndo_start_xmit = fe_start_xmit,
.ndo_set_mac_address = fe_set_mac_address,
.ndo_validate_addr = eth_validate_addr,
.ndo_do_ioctl = fe_do_ioctl,
.ndo_change_mtu = fe_change_mtu,
.ndo_tx_timeout = fe_tx_timeout,
.ndo_get_stats64 = fe_get_stats64,
.ndo_vlan_rx_add_vid = fe_vlan_rx_add_vid,
.ndo_vlan_rx_kill_vid = fe_vlan_rx_kill_vid,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = fe_poll_controller,
#endif
};
查阅了驱动的相关代码,写了个 ndo_set_features 的实现。
static int fe_set_features(struct net_device *dev,
netdev_features_t features)
{
struct fe_priv *priv = netdev_priv(dev);
const struct of_device_id *match = of_match_device(of_fe_match, priv->device);
struct fe_soc_data *soc = (struct fe_soc_data *)match->data;
netdev_features_t changed = features ^ dev->features;
if (!(changed & NETIF_F_RXCSUM))
return 0;
dev->features = features; //更新features
soc->fwd_config(priv); //使用fwd_config回调函数来更新设置
return 0;
}
更新后的驱动,可以通过 ethtool -K eth0 rx-checksum on/off 来开启/关闭网卡的rx-checksum功能。
如果遇到类似问题,想打patch的话,可以使用以下patch文件。
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
old mode 100644
new mode 100755
index 8c71a92..770cd6b
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -1392,6 +1392,26 @@ static int fe_change_mtu(struct net_device *dev, int new_mtu)
return fe_open(dev);
}
+static int fe_set_features(struct net_device *dev,
+ netdev_features_t features)
+{
+ struct fe_priv *priv = netdev_priv(dev);
+ const struct of_device_id *match = of_match_device(of_fe_match, priv->device);
+ struct fe_soc_data *soc = (struct fe_soc_data *)match->data;
+ netdev_features_t changed = features ^ dev->features;
+
+ if (!(changed & NETIF_F_RXCSUM))
+ return 0;
+
+ dev->features = features;
+ soc->fwd_config(priv);
+
+ return 0;
+}
+
+
static const struct net_device_ops fe_netdev_ops = {
.ndo_init = fe_init,
.ndo_uninit = fe_uninit,
@@ -1409,6 +1429,7 @@ static const struct net_device_ops fe_netdev_ops = {
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = fe_poll_controller,
#endif
+ .ndo_set_features = fe_set_features,
};
static void fe_reset_pending(struct fe_priv *priv)