Android 16 开发板 eth0 静态 IP 配置实战与避坑指南
我在嵌入式开发里和 Android 系统打交道的时间不短最近手头有一台基于 Android 16 工程固件的开发板遇到一个很典型的需求要把有线网口 eth0 的 IP 固定下来方便和上位机通信。老实说如果在普通 Linux 服务器上一条ip addr add敲下去网络立刻生效收工。但在 Android 上完全不是这么回事。你可能会遇到三种让人抓狂的情况刚设置好的 IP 过几秒被系统悄悄改回原来的 DHCP 地址或者明明用 root 执行命令却报Operation not permitted又或者重启之后一切恢复原样之前的所有操作都白费。这篇文章我会完整梳理在 Android 16 上修改 eth0 IP 的可行方案包括临时生效的命令、开机自启动的持久化配置、SELinux 权限问题的解法以及我实际踩过的各种坑。不管你是做嵌入式、工控设备还是在车机或者智能硬件项目里折腾网络配置这篇文章应该能帮你省下不少时间。1. 先从 Android 16 的网络栈说起改 IP 前需要知道的几个事实很多从 Linux 转过来的开发者第一反应就是把 Android 当成一个精简版 Linux拿着ifconfig或ip命令直接操作网络接口。但 Android 在网络管理上早就自成一派了尤其是到了 Android 16 这个版本单纯在 shell 里敲命令的行为越来越受限。1.1 netd、ConnectivityService、EthernetService 三者的分工Android 系统里真正管理网络底层的是 netdNetwork Daemon它是一个以 root 身份运行的守护进程负责路由表、防火墙规则、带宽控制、DNS 解析这些底层操作。你在 shell 里敲的ip route、ip rule有很多命令最终会和 netd 的操作冲突因为 netd 会持续维护它自己的网络状态。再往上是 ConnectivityService它是上层网络的总指挥。Wi-Fi、移动数据、以太网这些网络请求都会汇总到它这里由它决定当前哪个网络可用、哪个网络优先。而以太网接口 eth0 的控制权则掌握在 EthernetService 手中它负责监听网卡插拔事件并在检测到网线连接后去配置网络。这三者的关系可以粗浅理解成EthernetService 发现有网线插进来了于是向 netd 发起配置请求netd 真正去改内核里的地址和路由ConnectivityService 则在中间做协调和决策。如果你绕过 EthernetService直接在 shell 里用ip addr add设置地址等于是在绕过系统顶层管理者的思路来改底层配置。1.2 为什么手动设置的 IP 会被抢走这是大家最容易踩的坑。你把 eth0 的地址手动改成 192.168.1.100过了几秒钟再ip addr show eth0发现地址又变回了原来的 192.168.1.65甚至变成了 0.0.0.0。原因就在于 EthernetService 并不知道你已经手动设置了地址它检测到网线存在后按照默认逻辑发起 DHCP 请求拿到新的地址后通过 netd 把整个接口重新配置了一遍。你之前手动加的那些地址自然就被清掉了。所以在 Android 上修改 eth0 的 IP关键不是命令行怎么敲而是要让系统的网络管理框架认可你的静态 IP 配置。理解了这一点后面所有方案就都说得通了。1.3 Android 16 带来的新变化Android 16 在权限隔离和模块化方面比之前又严格了一些。普通应用早就没有直接操作网络接口的权限了就算你拿到 shell 的 root 权限SELinux 也会把大多数网络配置操作挡在门外。另外系统分区、vendor 分区的隔离更彻底想在 system 分区写文件、改配置没有 userdebug 或 eng 版本的固件基本不可能。所以在进行任何操作之前先确认三件事设备是否已经解锁、当前固件是不是 userdebug/eng 版本、adb 是否能够 root。这三个条件不满足后面的大多数方案都跑不起来。2. 临时改 IP 的三种命令行路线从 ip 命令到 ndc如果你只是想在调试阶段快速把 eth0 改到某个网段验证一下硬件链路是否通、上位机能不能 ping 通板子那没必要上来就动系统配置用命令行临时改是最快的。2.1 环境准备确认 root 身份和构建类型先用 adb 连接设备并确认能拿到 root shelladb root adb shell进入 shell 后看一下构建类型getprop ro.build.type如果是userdebug或eng那一切都好说如果是user版本adb root通常会被拒绝你后面想改系统分区也没权限。很多开发板出厂默认给的是 user 固件如果遇到这种情况得先联系板卡厂商拿一份 userdebug 固件或者自己在 AOSP 源码上编一个。2.2 路线一ip 命令直接操作内核这是 Linux 上最正统的方式Android 16 的系统 shell 里通常自带ip命令它通过 netlink 直接和内核通信地址设置是立刻生效的。# 先看当前 eth0 的状态 ip addr show eth0 # 清掉当前接口上的所有 IPv4 地址包括 DHCP 分配的 ip -4 addr flush dev eth0 # 添加静态地址 ip addr add 192.168.1.100/24 dev eth0 # 确保接口是 up 状态 ip link set eth0 up # 配置默认网关 ip route add default via 192.168.1.1 dev eth0这几条命令敲完之后ip addr show eth0应该能看到地址已经变成 192.168.1.100/24 了。有两点要特别提醒如果你的设备上已经通过 DHCP 获得了一个地址直接用ip addr add添加新地址旧地址不会自动消失接口上会同时存在两个 IP容易造成混乱。所以先flush一下是很有必要的。Android 的路由管理是策略路由默认网关不一定添加在 main 表里就会真正生效。如果加完网关后 ping 不通可以用ip route show table all和ip rule show看看策略规则和路由表必要时再用ip rule add from all lookup main pref 1把流量引导到 main 表。2.3 路线二ndc 命令让配置和 netd 保持同步如果你不想让 netd 在上层搞纠正可以尝试走 ndc 这条路径。ndc 是 netd 的命令行前端它做的事情和 netd 内部逻辑是一致的相当于告诉 netd我要把 eth0 配成静态地址。# 设置接口地址和掩码注意 24 是前缀长度 ndc interface setcfg eth0 192.168.1.100 24 # 添加默认路由 ndc interface route add eth0 0 0.0.0.0/0 192.168.1.1这个方案的好处是配置变更会同步到 netd 的管理状态里上层框架能感知到被抢走的概率比纯ip命令小。不过不同版本、不同厂商固件对 ndc 子命令的支持情况略有差异建议先执行ndc interface help确认一下参数格式。2.4 路线三ifconfig 命令还能不能用很多老教程里会写ifconfig eth0 192.168.1.100 netmask 255.255.255.0 up这套方式在早期 Android 上还能跑但到了 Android 16如果固件自带的是 toybox 版本的工具ifconfig要么不存在要么参数支持不完整。我的建议是直接用ip命令别在ifconfig上浪费时间。三种临时配置方案的对比方案底层机制是否与上层框架同步适用场景ip 命令netlink 直接操作内核不同步可能被 DHCP 或框架覆盖快速验证链路、临时调试ndc 命令走 netd 框架和系统网络管理一致同步临时修改且希望上层感知ifconfig老式 ioctl 接口不同步且工具支持不完整老设备兼容脚本新版本不推荐临时改完之后可以用ping -c 3 192.168.1.1验证网关是否可达。如果通说明链路和 IP 配置都没有问题可以进入持久化阶段了。3. 持久化配置让 eth0 的固定 IP 重启后依然存在临时配置只对当前运行状态有效重启之后一切烟消云散。如果板子需要长期以固定 IP 运行你得把静态 IP 配置写进系统启动流程或者交给以太网服务去管理。下面是我实际用过的四种持久化方案按照从简单到复杂的顺序来梳理。3.1 方案一init 脚本触发的静态配置Android 启动过程中init 进程会解析 init.rc 文件我们可以在这里加一段触发逻辑在系统启动完成后去执行一个脚本把 eth0 的地址设置好。假设你在/system/etc/下放了一个脚本static_ip.sh内容如下#!/system/bin/sh IP_ADDR192.168.1.100 PREFIX24 GW192.168.1.1 DEVeth0 ip -4 addr flush dev $DEV ip addr add $IP_ADDR/$PREFIX dev $DEV ip link set $DEV up ip route add default via $GW dev $DEV然后在 init.rc 或者厂商的 rc 文件里加上触发条件on property:sys.boot_completed1 exec u:r:system_shell:s0 -- /system/bin/sh /system/etc/static_ip.sh这个方案的优点是不需要改 framework 代码只要有 userdebug 固件用 adb remount 把文件放进去就能用。缺点是时机很不好把握如果脚本执行太晚EthernetService 可能已经发起过 DHCP把地址又改回去了如果太早eth0 设备可能还没注册到系统里脚本执行后会报Device not found。实际调试时我会在脚本开头加一个循环等待确保网络接口已经存在while [ ! -e /sys/class/net/eth0 ]; do sleep 0.5 done不过即便如此和 EthernetService 的 DHCP 还是存在竞争关系。所以这个方案更适合板子上没有其他网络管理干扰的场景比如纯 Linux 内核启动的 Android 容器环境。3.2 方案二通过 cmd ethernet 让系统框架管理静态 IPAndroid 以太网服务内部本来就是支持静态 IP 配置的与其在外面和它抢时间不如直接告诉它eth0 用静态地址。在 Android 16 的 userdebug 固件上可以尝试用 cmd 命令调用以太网服务adb shell cmd ethernet help不同固件支持的子命令不完全一样我的开发板上可以用这样几类命令# 查看当前接口配置 adb shell cmd ethernet get-interface-config eth0 # 设置静态 IP adb shell cmd ethernet set-static-ip eth0 192.168.1.100 24 # 如果以后想改回 DHCP adb shell cmd ethernet set-ip-assignment eth0 dhcp这个方案的优势非常明显地址配置交给框架来管理上层网络状态和内核实际地址保持一致不会出现内核里是静态 IP、框架里还以为是 DHCP这种分裂状态重启之后框架也会自动应用静态配置。需要注意的一点是cmd ethernet在部分厂商固件里可能没有完整实现或者子命令命令被裁剪掉了。这时候先用cmd ethernet help看看有哪些可用功能没有的话再退回其他方案。3.3 方案三修改系统源码在 EthernetTracker 里写死静态 IP如果你是产品量产场景几百上千台设备都要用同一个固定 IP那命令行和脚本都太脆弱了。最可靠的做法是回到源码里在以太网服务初始化的时候直接给 eth0 指定 StaticIpConfiguration。在 AOSP 的 EthernetTracker 或 EthernetNetworkFactory 代码中可以修改以太网接口的默认配置也可以直接用资源 overlay 来覆盖以太网相关配置项。这类方案对没有源码权限的开发者不太友好但如果你是板卡方案商或者手里有完整 BSP这才是量产的标准做法。另外还有一个思路如果设备要接入的网络环境是可控的比如都是同一个局域网、同一个路由器那完全可以不在设备端做任何配置直接去路由器或者 DHCP 服务器上做 MAC 地址静态绑定。这样设备无论以什么状态启动拿到的 IP 都永远是固定的设备端零改动省掉所有麻烦。3.4 方案四把配置做到应用层Android 系统应用可以通过 ConnectivityManager 的 API 来设置某个网络的静态 IP 配置但前提是该应用必须有系统签名和MANAGE_ETHERNET权限。这种做法适合那些把网络配置做进设置界面的系统应用对第三方应用开发者来说门槛太高这里就不展开代码了。四种持久化方案的对比方案难度重启持久性推荐场景init 脚本触发中可以保持但时机难控制工程调试、快速测试cmd ethernet 设置静态 IP低可以保持由框架统一管理小批量设备调试源码级修改/overlay高稳定产品量产DHCP 服务器 MAC 绑定低不依赖设备网络环境可控4. SELinux 与权限root 之后依旧报 Permission denied 的真相在 Android 16 上折腾网络配置绕不开 SELinux。很多开发者已经用adb root拿到了 root shell执行ip addr add依然报Operation not permitted第一反应是root 不完整或者固件有坑。其实真正的原因是 SELinux 在把关。4.1 从 dmesg 里定位 AVC 拦截当 SELinux 拦截某个操作时内核会记录一条 AVC denied 日志。你可以这样查adb shell dmesg | grep avc | tail -n 50典型的日志长这样avc: denied { add } for pid1234 commip scontextu:r:shell:s0 tcontextu:r:shell:s0 tclassnetlink_route_socket permissive0看到这样的日志就说明你的 shell 域没有权限去创建或操作 netlink_route_socket所以ip命令底层的 netlink 操作被 SELinux 拦了。4.2 临时绕过的操作setenforce 0如果只是为了快速调试可以直接把 SELinux 切换到 permissive 模式adb shell setenforce 0这个命令一秒生效所有权限拦截都会暂时放开ip、ndc、ifconfig之类的命令都能正常跑。要注意两点一是设备重启后 SELinux 会回到 enforcing 状态二是 setenforce 权限不够的话同样会被阻止此时需要 root 身份执行。临时调试时用setenforce 0是最高效的选择但这不是产品化的正确做法。如果一个功能必须在 disabling SELinux 的情况下才能跑那只能说明你的配置方案还没有正确落位。4.3 永久解决方案在 sepolicy 里加 allow 规则如果要在 enforcing 模式下让 shell 域能够操作网络接口需要在源码的 sepolicy 配置里添加对应规则。比如给 shell 域增加 netlink_route_socket 和 tcp/udp 相关权限allow shell self:netlink_route_socket { bind read write create getattr setattr }; allow shell self:udp_socket { read write bind create connect getattr setattr }; allow shell self:tcp_socket { read write bind create connect getattr setattr };修改完 sepolicy 文件后重新编译 boot.img 和 system 相关镜像烧录进去生效。这个方法只适用于手里有完整 AOSP 或厂商 BSP 源码的开发者。4.4 更实用的做法用 init 的 exec 执行上下文绕过权限问题修改 sepolicy 需要编译镜像很多人没这个条件。另外一个思路是不要用 shell 域去执行网络配置脚本而是让 init 进程以更高权限的 domain 来执行。在 init.rc 里exec命令可以指定 SELinux 上下文。比如让脚本以 vendor_init 域运行on boot exec u:r:vendor_init:s0 -- /vendor/bin/static_ip.shvendor_init 域通常被授予了操作网络接口的权限尤其是厂商自己的网络初始化脚本走这条路不需要额外改 sepolicy。实际操作中很多方案厂商都会在 vendor 分区预留这种脚本的位置你可以直接复用省心不少。说到底Android 16 的 SELinux 策略不是拦着你改 IP而是拦着你用不受信任的方式改 IP。顺应它的规则去配置反而比你硬碰硬要简单得多。5. 完整实测把开发板的 eth0 静态 IP 从零配到重启不丢这一章我整理了一个完整实操记录场景是我最近调试的一台 Android 16 开发板。硬件由某方案公司提供SoC 是嵌入式常见平台固件是 userdebug 版本adb 可以 root需要把 eth0 固定为 192.168.1.100/24网关是 192.168.1.1用于和上位机通信。5.1 确认设备状态连接设备后先确认固件类型和当前接口状态$ adb devices List of devices attached 0123456789ABCDEF device $ adb root restarting adbd as root $ adb shell # getprop ro.build.type userdebug # ip addr show eth0 3: eth0: BROADCAST,MULTICAST,UP,LOWER_UP mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 00:11:22:33:44:55 brd ff:ff:ff:ff:ff:ff inet 192.168.1.65/24 brd 192.168.1.255 scope global eth0可以看到 eth0 已经通过 DHCP 获得了 192.168.1.65 这个地址。如果我们直接手动改成 .100大概率会在一段时间后被 DHCP 重新覆盖。5.2 临时修改并验证链路先做临时修改验证链路本身没问题# ip -4 addr flush dev eth0 # ip addr add 192.168.1.100/24 dev eth0 # ip link set eth0 up # ip route add default via 192.168.1.1 dev eth0 # ip addr show eth0 3: eth0: BROADCAST,MULTICAST,UP,LOWER_UP mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 inet 192.168.1.100/24 brd 192.168.1.255 scope global eth0再 ping 网关# ping -c 3 192.168.1.1 PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data. 64 bytes from 192.168.1.1: icmp_seq1 ttl64 time0.420 ms 64 bytes from 192.168.1.1: icmp_seq2 ttl64 time0.388 ms 64 bytes from 192.168.1.1: icmp_seq3 ttl64 time0.401 ms网络链路正常网关可达。这时候问题就变成了如何让这个配置在重启之后依然生效。5.3 用 cmd ethernet 配置静态 IP在尝试了 init 脚本方案之后我发现以太网服务在启动早期就会执行 DHCP脚本执行时机不好把握于是改用系统以太网服务来配置# 先看支持哪些子命令 # cmd ethernet help # 设置静态 IP # cmd ethernet set-static-ip eth0 192.168.1.100 24 # 确认接口配置 # cmd ethernet get-interface-config eth0设置完成之后我又把网线拔掉重插了一下触发了重新配置流程地址保持为 192.168.1.100。这个动作很关键有些版本的以太网服务只有在接口重连时才会重新应用配置。5.4 重启验证设置完静态 IP 后重启设备$ adb reboot # 等系统起来后重新连接 $ adb shell ip addr show eth0 3: eth0: BROADCAST,MULTICAST,UP,LOWER_UP mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 inet 192.168.1.100/24 brd 192.168.1.255 scope global eth0重启后地址稳定在 192.168.1.100和预期一致。到此这个开发板的静态 IP 配置就算彻底搞定了。5.5 实测中的其他发现在这轮操作中我还注意到几个细节cmd ethernet set-static-ip参数顺序在不同版本固件里可能有差异一定要先看 help 确认别照抄别人的命令。如果系统里同时配置了多个网络接口比如 eth0 和 wlan0 同时在网路由优先级可能会冲突需要额外用ip rule调整策略。在 userdebug 固件上adb remount后往 /system 分区写脚本是可行的但量产固件不要这么干改动是临时的恢复出厂就丢了。6. 改完就失效、ping 不通、命令不存在常见陷进排查思路最后整理几个高频问题基本覆盖了我见过的各种翻车现场。这些问题看似是运气不好其实背后都有明确的逻辑。6.1 地址设置后过几秒被重置这是最常见的坑。表现是ip addr add执行后地址正常但过几秒再看地址又变回 DHCP 分配的地址或者是 0.0.0.0。排查步骤# 看以太网服务状态 adb shell dumpsys ethernet # 看哪些属性涉及 dhcp adb shell getprop | grep -i dhcp # 看日志里以太网和 DHCP 客户端的行为 adb shell logcat -d | grep -iE ethernet|dhcp根本原因就是 DHCP 客户端或者 EthernetService 一直在管理这个接口手动改的地址不被认可。解决办法是走cmd ethernet或ndc让框架感知而不是单纯用ip命令。6.2 地址已经设置成功但 ping 不通网关可能的原因有很多优先看路由表ip route show ip route show table all ip rule show很多情况下地址加好了但默认路由没有进到正确的路由表或者策略路由规则没有把流量引导到 main 表。处理方式是在设置完地址后立刻用ip route add default via 192.168.1.1 dev eth0添加默认路由并用ip rule确认策略优先级。如果还是不通再看cat /proc/net/arp确认网关的 ARP 是否解析成功。6.3 网卡状态是 DOWN执行完ip addr add后ip addr show eth0显示state DOWN说明网卡没有启用ip link set eth0 up如果设置 up 之后又被拉低检查网线有没有插好或者板子的 PHY 芯片有没有正确驱动。硬件问题不用在系统层排查。6.4 ifconfig 命令找不到Android 16 很多固件里已经移除了ifconfig或者在 toybox 里实现得不完整。不要纠结于用老命令直接用ip addr和ip link系列命令即可功能完全覆盖ifconfig的日常用途。6.5 手动加的路由表被 wlan0 抢走当 eth0 和 wlan0 同时存在时Android 会根据网络评分和优先级来判断默认网络走哪个接口。即使你手动给 eth0 加了默认路由系统也可能在几秒后把默认路由切回 wlan0。这种情况下的检查思路是# 看系统当前认定的默认网络 adb shell dumpsys connectivity # 看所有路由规则 adb shell ip rule show如果需要强制让 eth0 成为默认网络要么上层禁用 Wi-Fi要么用ndc配合调整网络优先级。在命令行层面硬改路由通常是打不过 ConnectivityService 的。6.6 静态 IP 配好后 DNS 解析不了固定 IP 通常意味着不走 DHCP那 DNS 也不会自动拿到需要手动设置ndc resolver setnetdns eth0 192.168.1.1或者简单一点直接设置系统属性setprop net.eth0.dns1 192.168.1.1然后再ping一个域名测试解析是否正常。改完 eth0 的 IP 再回头看Android 16 上的网络管理和传统 Linux 确实是两套思路。Linux 让你直接操作内核和设备Android 则要求你顺着框架的规则来。临时调试用ip命令没毛病但要想长期稳定运行早点切换到cmd ethernet或者源码级配置才是少走弯路的关键。同一套方法我也在另外几块不同平台的板子上试过只要确认固件是 userdebug 且以太网服务完整基本都能跑通。如果你也在折腾 Android 新版本上的网络配置建议先看看cmd ethernet help支持哪些操作很多问题都能少折腾。