19、USB网络共享(RNDIS/ECM):RNDIS与ECM协议原理、Android端USB网络共享实现

各位好,欢迎来到第19章。今天聊一个很实用的功能——USB网络共享。说白了,就是把手机的4G/5G网络通过USB线分享给电脑用。这个功能我调试过不下十次,每次遇到新硬件平台,总得跟它打交道。嗯,咱们今天就把它的底细摸清楚。

19.1 RNDIS与ECM协议原理

先说说这两个协议是干嘛的。USB网络共享的核心,就是让USB线扮演网线的角色。但USB本身不是为网络设计的,所以需要一套协议来“伪装”成网卡。

19.1.1 RNDIS(Remote Network Driver Interface Specification)

RNDIS是微软搞出来的。说白了,它把USB设备模拟成一个以太网适配器。Windows系统原生支持,插上就能用。我早期做项目时,Windows端基本没出过问题。

RNDIS的工作流程是这样的:

  • USB设备端(手机)枚举成一个“通信设备类”的复合设备
  • 通过批量传输端点(Bulk IN/OUT)收发网络数据包
  • 数据包头部加了一个RNDIS消息头,用来标识包类型

这里有个坑——RNDIS是微软私有协议,Linux和macOS虽然也能支持,但体验不如Windows那么顺滑。我曾经在某个Linux发行版上折腾了半天,才发现是内核模块没加载。

19.1.2 ECM(Ethernet Control Model)

ECM是USB-IF组织定义的官方标准,属于CDC(通信设备类)的子类。它比RNDIS更通用,Linux和macOS原生支持得很好。

ECM的原理其实更简单:

  • 它直接模拟一个以太网接口
  • 数据包就是标准的以太网帧,不加额外头部
  • 控制端点用来配置MAC地址、设置过滤等

我个人习惯在Android设备上优先用ECM,因为Linux内核支持更干净。但Windows对ECM的支持需要额外驱动,这点要注意。

19.1.3 协议对比

特性 RNDIS ECM
标准化 微软私有 USB-IF标准
Windows支持 原生 需驱动
Linux支持 需模块 原生
macOS支持 需驱动 原生
数据包格式 RNDIS消息头+以太网帧 纯以太网帧

核心结论:如果你的目标平台是Windows,RNDIS最省心;如果是Linux/macOS,ECM更干净。Android端两个都支持,但ECM的代码更简洁。

19.2 Android端USB网络共享实现

Android系统从早期版本就支持USB网络共享。实现方式分两种:系统设置里点几下,或者通过代码控制。咱们先看系统设置,再深入代码。

19.2.1 系统设置操作

这个大家可能都用过:

  1. 打开“设置” → “网络和互联网” → “热点和网络共享”
  2. 打开“USB网络共享”开关
  3. 用USB线连接电脑
  4. 电脑端会自动识别并获取IP

嗯,就这么简单。但底层发生了什么?我告诉你:

  • Android系统会启动一个叫netd的守护进程
  • 它会在USB gadget上配置一个网络接口(比如usb0
  • 然后启动DHCP服务器,给电脑分配IP
  • 最后配置NAT(网络地址转换),让电脑能通过手机上网

我曾经遇到一个奇怪的问题:打开USB网络共享后,电脑能获取IP但上不了网。查了半天,发现是iptables规则没配好。这个后面会细说。

19.2.2 代码控制实现

如果你要写一个App来控制USB网络共享,核心API在ConnectivityManager里。不过要注意,这些API是系统级权限,普通App用不了。

先看一个简单的示例:

// 检查USB网络共享状态
ConnectivityManager cm = (ConnectivityManager) 
    getSystemService(Context.CONNECTIVITY_SERVICE);
boolean isUsbTetheringActive = 
    cm.getTetherableUsbInterfaces().length > 0;

// 开启USB网络共享(需要系统权限)
String[] ifaces = cm.getTetherableUsbInterfaces();
if (ifaces.length > 0) {
    cm.startTethering(TETHERING_USB, 
        new OnStartTetheringCallback() {
            @Override
            public void onTetheringStarted() {
                // 成功回调
            }
            @Override
            public void onTetheringFailed() {
                // 失败处理
            }
        });
}

// 关闭USB网络共享
cm.stopTethering(TETHERING_USB);

提示:在Android 10及以上版本,startTethering方法需要android.permission.TETHER_PRIVILEGED权限。这个权限只有系统应用或签名应用才能获取。普通App建议通过Settings的Intent跳转到系统设置页面。

如果你在开发系统级应用,还可以直接操作netd。我记得有一次需要自定义IP地址段,就是通过NetworkManagementService的隐藏API实现的。

19.3 IP地址分配与路由配置

USB网络共享建立后,IP地址怎么分?路由怎么走?这里面的门道不少。

19.3.1 IP地址分配

Android默认使用192.168.42.0/24这个网段。手机端是192.168.42.129,电脑端通过DHCP获取192.168.42.xxx范围内的地址。

为什么是这个网段?我猜是Google工程师随手选的,反正不跟常见内网冲突就行。但如果你要改,可以修改/system/etc/dhcpcd/dhcpcd.conf或者通过netd的接口配置。

来看一下实际的IP配置:

# 手机端(usb0接口)
usb0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
    inet 192.168.42.129  netmask 255.255.255.0  broadcast 192.168.42.255

# 电脑端(通过DHCP获取)
usb0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
    inet 192.168.42.100  netmask 255.255.255.0  broadcast 192.168.42.255

19.3.2 路由与NAT配置

IP分配好了,但电脑怎么通过手机上外网?这里需要NAT。Android使用iptables来做这件事。

核心规则就两条:

  1. 允许转发:echo 1 > /proc/sys/net/ipv4/ip_forward
  2. 配置NAT:iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE

其中wlan0是手机的WiFi或移动网络接口。这样,从usb0过来的数据包,源地址会被替换成手机的外网地址,实现上网。

注意:如果你修改了iptables规则,记得在关闭USB网络共享时清理掉。否则下次开启时可能规则冲突。我曾经因为规则没清理干净,导致网络共享时断时续,排查了半天才发现是重复规则的问题。

19.4 实战:通过USB共享4G/5G网络

好了,理论说完了,咱们来实战。假设你有一台Android手机(已root),想通过USB线把5G网络共享给电脑。

19.4.1 准备工作

  • 手机开启5G网络(确保能正常上网)
  • 用USB线连接电脑
  • 手机端开启“USB网络共享”(系统设置里操作)

电脑端会自动获取IP。如果没获取到,可以手动配置:

# Linux/macOS
sudo ifconfig usb0 192.168.42.100 netmask 255.255.255.0 up
sudo route add default gw 192.168.42.129

# Windows(管理员CMD)
netsh interface ip set address "以太网" static 192.168.42.100 255.255.255.0 192.168.42.129

19.4.2 验证网络

配置完成后,在电脑上ping一下外网:

ping 8.8.8.8
# 如果能通,说明网络共享成功

# 再测试DNS解析
ping www.google.com
# 如果域名也能解析,说明DNS也正常

如果ping不通,检查以下几点:

  • 手机端是否开启了移动数据
  • iptables规则是否正确
  • 电脑端防火墙是否阻止了转发

19.4.3 性能测试

我实际测试过,USB 3.0接口下,5G网络共享的吞吐量能达到800Mbps以上。但要注意,USB 2.0的瓶颈在480Mbps,实际可能只有300Mbps左右。

影响性能的因素:

  • USB接口版本(3.0 > 2.0)
  • 数据线质量(劣质线会掉速)
  • 手机SoC的USB控制器性能
  • 网络信号强度

经验之谈:如果你发现速度上不去,先换一根好点的USB线试试。我遇到过好几次,换了线之后速度翻倍。另外,手机发热也会降频,影响USB控制器性能。

19.5 知识体系总览

最后,我用一张图来总结本章的核心逻辑。这张图展示了从协议选择到最终上网的完整链路。

USB网络共享知识体系 协议选择 RNDIS / ECM 根据平台选择 Android端实现 系统设置操作 代码控制API netd守护进程 IP分配 + 路由配置 + NAT转发 电脑通过USB共享上网

这张图把整个流程串起来了。从协议选择开始,到Android端的实现,再到IP分配和NAT配置,最后电脑就能上网了。你想想看,其实核心就三步:选协议、配接口、设路由。

好了,本章的内容就到这里。USB网络共享虽然看起来简单,但涉及的知识点不少。从协议原理到系统实现,再到网络配置,每一步都有坑。希望今天的分享能帮你少走弯路。

最后提醒:如果你在生产环境中部署USB网络共享,记得做好异常处理。比如USB线突然断开、网络切换、电源管理等场景,都需要考虑。我见过不少设备因为没处理好这些边界情况,导致用户体验很差。

公众号:蓝海资料掘金营,微信deep3321