ip rule
0: from all lookup local
1000: from all lookup main
10000: from all fwmark 0xc0000/0xd0000 lookup legacy_system
11000: from all iif lo oif dummy0 uidrange 0-0 lookup dummy0
11000: from all iif lo oif eth1 uidrange 0-0 lookup eth1
16000: from all fwmark 0x10063/0x1ffff iif lo lookup local_network
16000: from all fwmark 0x10064/0x1ffff iif lo lookup eth1
17000: from all iif lo oif dummy0 lookup dummy0
17000: from all iif lo oif eth1 lookup eth1
18000: from all fwmark 0x0/0x10000 lookup legacy_system
19000: from all fwmark 0x0/0x10000 lookup legacy_network
20000: from all fwmark 0x0/0x10000 lookup local_network
23000: from all fwmark 0x64/0x1ffff iif lo lookup eth1
31000: from all fwmark 0x0/0xffff iif lo lookup eth1
32000: from all unreachable
规则优先级大致是:
• 16000 EXPLICIT_NETWORK:mark 里的 netId 匹配,且 explicitlySelected=1。bindSocket / bindProcessToNetwork 走这里。
• 17000 OUTPUT_INTERFACE:SO_BINDTODEVICE / IP_PKTINFO,按出口接口选表,不看 netId。
• 23000 IMPLICIT_NETWORK:netId 匹配且 explicitlySelected=0。connect() 时打上的 mark 走这里,用来把这条连接钉死在当时的网络上。
• 31000 DEFAULT_NETWORK:只匹配 netId == 0(NETID_UNSET)再加 permission。PhysicalNetwork::addAsDefault() → addInterfaceToDefaultNetwork() 装的就是这条。

当前上网走哪条
普通 app 上网(没 bindSocket、没 SO_BINDTODEVICE)走 23000。
modifyImplicitNetworkRule() 的注释把原因写明了:connect 时绑上当时的网络,默认网络以后切换,这条已连接的 socket 仍然留在原网络,不会被新默认网络抢走。
如果 connect 时不打 mark,包会掉到 31000 那条 netId==0 的规则上,每次查路由都跟着当前默认网络走。默认网络一切换,已有连接的下一包就会换出口,TCP 直接断。
先写成 mDefaultNetId 且 explicitlySelected=0,命中的是 23000 的隐式规则,连接被钉住;同时因为 explicit 位是 0,local network(20000)、legacy(18000/19000)这些更低优先级的规则还能兜住局域网和老的 requestRouteToHost()。
有一个例外:secure VPN 不在这里写它自己的 netId。它靠 13000 那条不看 mark 的高优先级规则把流量卷走。注释里说,这样 VPN 拆掉以后,socket 上留的仍是默认网络 netId,连接还能继续。bypassable VPN 优先级太低,不写它的 netId 就根本匹配不到,所以那种才会把 VPN netId 写进 mark。