记录:Android 下 USB 网卡修改 MAC 后,接口名从 eth1 变成 usb0 的原因分析

关键词:usbnet、cdc_ether、接口命名、MAC 地址、U/L 位、Android GKI

一、问题现象

调试一款 USB 网卡(基于 usbnet/cdc_ether 框架的驱动),接入 Android 系统:

  • 默认状态:网卡识别为 eth1,一切正常;
  • 修改 MAC 地址后重新上电:网卡名称变成了 usb0,Android 以太网服务不再正常管理它(系统默认只管理匹配 eth\d 的接口,usb0 会被当成 USB 网络共享设备处理)。

修改前后的 MAC:

状态MAC 地址
修改前dc:xx:xx:xx:xx:xx
修改后ee:xx:xx:xx:xx:xx

同一台设备、同一个驱动,只改了 MAC,接口命名规则就变了,说明命名和 MAC 地址本身有关

二、根因:usbnet_probe() 的命名启发式

usbnet 框架的 USB 网卡(cdc_ether、cdc_ncm 及各类厂商衍生驱动),接口名在 drivers/net/usb/usbnet.cusbnet_probe() 中决定,逻辑分两步:

第 1 步:默认命名 usb%d

net = alloc_etherdev(sizeof(*dev));
...
strcpy (net->name, "usb%d");

第 2 步:启发式判断是否改名为 eth%d(旧版内核代码)[1]

// heuristic:  "usb%d" for links we know are two-host,
// else "eth%d" when there's reasonable doubt.  userspace
// can rename the link if it knows better.
if ((dev->driver_info->flags & FLAG_ETHER) != 0 &&
    ((dev->driver_info->flags & FLAG_POINTTOPOINT) == 0 ||
     (net->dev_addr [0] & 0x02) == 0))
    strcpy (net->name, "eth%d");

cdc_ether 类驱动的 flags 是 FLAG_ETHER | FLAG_POINTTOPOINT,于是判定就落在 MAC 地址第一字节的 0x02 位(U/L 位) 上:

  • U/L 位 = 0(全局管理地址,带合法 OUI)→ 改名 eth%d
  • U/L 位 = 1(本地管理地址)→ 保持 usb%d

新版内核(5.15/6.x GKI,含 2024 年 12 月补丁)逻辑等价,写得更直白[2]

static bool usbnet_needs_usb_name_format(struct usbnet *dev, struct net_device *net)
{
	/* Point to point devices which don't have a real MAC address
	 * (or report a fake local one) have historically used the usb%d
	 * naming. Preserve this..
	 */
	return (dev->driver_info->flags & FLAG_POINTTOPOINT) != 0 &&
		(is_zero_ether_addr(net->dev_addr) ||
		 is_local_ether_addr(net->dev_addr));
}

即:点对点设备,如果驱动没给 MAC(全零)或者给的是本地管理 MAC,保持历史惯例用 usb%d 命名。

三、为什么我的 MAC 踩坑了:U/L 位分析

MAC 第一字节的两个关键标志位:

  • bit0(0x01,I/G 位):0 = 单播,1 = 组播(网卡 MAC 必须为 0,否则 register_netdev 直接报 invalid MAC);
  • bit1(0x02,U/L 位):0 = 全局管理地址,1 = 本地管理地址。

逐位分析两个 MAC 的第一字节:

MAC 第一字节二进制bit0 (I/G)bit1 (U/L)判定结果接口名
dc1101 110000全局地址eth1
ee1110 111001本地管理地址usb0

我随手编的 ee:...,第二字符是 E(二进制 1110),正好把 U/L 位置成了 1,命中了 is_local_ether_addr() 的例外分支。

速记规律:MAC 第一字节的第二个十六进制字符是 2/3/6/7/A/B/E/F → U/L 位为 1(本地地址);是 0/1/4/5/8/9/C/D → 全局地址。手工编 MAC 时务必选后者。

四、验证方法

# 1. 看当前 MAC 第一字节是否落在 x2/x6/xA/xE 上
cat /sys/class/net/usb0/address

# 2. 确认绑定的是哪个驱动(是否走的 cdc_ether/usbnet 框架)
readlink /sys/class/net/usb0/device/driver

# 3. 看 probe 时驱动读到的 MAC 和注册日志
dmesg | grep -i register

把 MAC 改成 ec:3d:4e:00:32:12(只把 ee 换成 ec)后重新上电,接口名恢复 eth1,验证通过。

五、延伸:这段命名逻辑的"回归窗口期"

这段代码近几年被反复改过,不同版本内核行为不完全一致,跨版本调试时要注意:

时间提交行为变化
~2022 前(原始逻辑)默认 usb%dFLAG_ETHER 且(非点对点 U/L=0)→ eth%d
2022 左右bab8eb0dd4cb(usbnet: modern method to get random MAC)随机 MAC 分配时机改变,命名检查时读到的是清零地址,U/L 检查恒"通过"——带 FLAG_ETHER 的设备不管 MAC 是什么都叫 eth%d(第一次回归)
2024.108a7d12d674ac(fix name regression,随后回合各 stable)条件放宽为"MAC 非零即 eth%d",本地地址也会变成 eth%d,反而把一批依赖 usb0 名字的 LTE 模组/OpenWrt 配置搞挂(第二次回归)
2024.12net: usb: usbnet: restore usb%d name exception for local mac addresses(主线及 stable 回合,如 5.4.292)恢复"点对点 + 本地/全零 MAC → 保持 usb%d"的旧行为[2-1][3]

结论:内核从来不保证接口名稳定(Greg KH 原话:"Device names have NEVER been stable"),不要把产品逻辑建立在 eth1/usb0 这类默认名字上。

六、解决方案汇总

  1. 改 MAC 时保证第一字节为合法全局单播地址:bit0=0 且 bit1=0,例如保留原 OUI 前缀 dc:04:5a:xx:xx:xx,或把 ee 改成 ec——最省事的做法;
  2. 用户态改名ip link set usb0 down && ip link set usb0 name eth1;Android 上可用 udev 规则按 MAC 固定名称(如 system/core/rootdir/etc/udev/rules.d/70-persistent-net.rules);
  3. 驱动层固定命名:在驱动的 bind() 里显式 strcpy(net->name, "eth%d"),跳过启发式判断;
  4. 模块参数:usbnet 提供 eth_device_name 参数(默认 "eth%d")可改改名用的模式串,注意它只影响"改名后叫什么",不影响"是否改名"的判断。

七、经验总结

  1. USB 网卡在 Linux/Android 下的接口名不是随机的,是 usbnet 框架根据驱动 flags + MAC 地址属性决定的;
  2. 手工编 MAC 牢记两个约束:第一字节 bit0=0(单播)、bit1=0(全局),即第二个十六进制字符取 0/1/4/5/8/9/C/D
  3. 遇到"名字变了"类问题,先看 dmesg 和驱动绑定关系,再想是不是 MAC 合法性/属性影响了内核的命名/注册路径;
  4. 产品化时接口名要用 MAC/设备路径绑定(udev 规则或驱动固定),别依赖内核默认命名。

参考资料


  1. USB NCM usbnet 枚举流程代码分析(含 usbnet_probe 旧版命名启发式源码)- CSDN↩︎

  2. net: usb: usbnet: restore usb%d name exception for local mac addresses - patchwork↩︎↩︎

  3. Linux 5.4.292 ChangeLog(restore usb%d 补丁回合 stable)↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值