深刻理解Android系统网络架构

引言:这篇文章以WiFi举例,介绍了Android系统网络架构。其内容包含:网络链路的连接和注册、网络有效性检测和网络优选、Android系统网络防火墙和几种场景下的网络策略等,文章的最后也列举了几种常见的无法上网原因供大家参考。

一. 基本结构

1.1 类图

深刻理解Android系统网络架构

1.2 WifiService

WifiManager中公开API的具体实现,提供了WiFi打开与关闭、配置和扫描、连接和断开等方法,其中也包含了对调用者的权限检查,如开关WiFi需要"Manifest.permission.CHANGE_WIFI_STATE"权限等。外部调用方式为:

WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);

1.3 WifiStateMachine

状态机: 状态机是一种用于表示有限个状态以及在这些状态之间转移和动作行为的数学模型。状态机描述了对象在它生命周期内所经历的状态序列,以及在不同状态下如何响应外部事件。使用状态机可以省去代码中一堆的"if-else"判断,这样不仅易于管理,同时也使代码结构更加清晰,易于阅读。

WifiStateMachine是一个状态机,用于管理WiFi驱动加载、扫描、连接、获取IP、漫游等各个状态。基本的状态如图:("→"的起始端为父状态,终端为子状态;外部消息可以在子→父状态中流动,子状态不处理的消息交由父状态处理)

深刻理解Android系统网络架构

各个状态的描述:

StateDescription
DefaultState初始状态,WiFi 开关没有打开,驱动没有加载。当处于其他状态时,消息由子状态上行可用于日志打印。
ConnectModeStateConnectModeStateWiFi开关已经打开,驱动已经加载,native中wpa_supplicant已经启动。此时可以进行扫描和连接的操作。

进入该状态时发送 “android.net.wifi.WIFI_STATE_CHANGED” 广播。

L2ConnectedStateL2是"Level 2"的意思,代表OSI网络模型中的2层即数据链路层。这个状态代表数据链路已经建立完成。

进入该状态时发送"android.net.wifi.STATE_CHANGE"广播,连接状态是CONNECTING。

ObtainingIpStateDHCP获取IP过程时的状态。
ConnectedState已连接状态,当链路建立完成且DHCP配置IP完成后会进入该状态。

进入该状态时发送"android.net.wifi.STATE_CHANGE"广播,连接状态是CONNECTED。

RoamingState漫游状态。如果附近的两个热点名字(ssid)相同,且网络质量达到一定差异化时,系统就会进入漫游状态,连接到另一个热点。
DisconnectingState断开中状态。从断开发起到断开成功,处于该状态。

进入该状态会发送 “android.net.wifi.STATE_CHANGE” 广播,连接状态是DISCONNECTING。

DisconnectedState已断开状态。进入该状态时会发送"android.net.wifi.STATE_CHANGE" 广播,连接状态是DISCONNECTED。

adb连接状态下可以使用 “adb shell dumpsys wifi” 来查看连接WiFi的信息和连接的详细过程,如:

上一次扫描结果:

Latest scan results:BSSID              Frequency  RSSI    Age      SSID                                 Flags80:8d:b7:62:da:12       5765    -52    1.330+   Bytedance Inc                     [WPA2-EAP-CCMP][ESS]80:8d:b7:62:da:15       5765    -53   94.471    Bytedance AD                      [WPA2-EAP-CCMP][ESS]80:8d:b7:62:da:14       5765    -53   94.471    jiyunhudong                       [WPA2-EAP-CCMP][ESS]80:8d:b7:62:da:13       5765    -53   94.471    Bytedance 2.4G                    [WPA2-EAP-CCMP][ESS]80:8d:b7:63:12:d4       5805    -71   94.471    jiyunhudong                       [WPA2-EAP-CCMP][ESS]80:8d:b7:63:12:d5       5805    -71   94.469    Bytedance AD                      [WPA2-EAP-CCMP][ESS]80:8d:b7:63:12:d2       5805    -71   94.469+   Bytedance Inc                     [WPA2-EAP-CCMP][ESS]80:8d:b7:63:12:d3       5805    -71   94.469    Bytedance 2.4G                    [WPA2-EAP-CCMP][ESS]80:8d:b7:62:da:02       2412    -54   94.469    Bytedance 2.4G                    [WPA2-EAP-CCMP][ESS]80:8d:b7:63:43:d2       5260    -81   94.469+   Bytedance Inc                     [WPA2-EAP-CCMP][ESS]80:8d:b7:63:43:d3       5260    -81   94.468    Bytedance 2.4G                    [WPA2-EAP-CCMP][ESS]80:8d:b7:63:43:d0       5260    -81   94.468                                      [WPA2-PSK-CCMP][ESS]80:8d:b7:63:43:d4       5260    -81   94.468    jiyunhudong                       [WPA2-EAP-CCMP][ESS]80:8d:b7:63:43:d5       5260    -81   94.468    Bytedance AD                      [WPA2-EAP-CCMP][ESS]80:8d:b7:62:df:73       5300    -87   94.468    Bytedance 2.4G                    [WPA2-EAP-CCMP][ESS]80:8d:b7:60:26:50       5260    -87   94.468                                      [WPA2-PSK-CCMP][ESS]80:8d:b7:60:26:52       5260    -86   94.468+   Bytedance Inc                     [WPA2-EAP-CCMP][ESS]80:8d:b7:60:26:54       5260    -86   94.467    jiyunhudong                       [WPA2-EAP-CCMP][ESS]80:8d:b7:60:26:53       5260    -86   94.467    Bytedance 2.4G                    [WPA2-EAP-CCMP][ESS]80:8d:b7:60:26:55       5260    -86   94.467    Bytedance AD                      [WPA2-EAP-CCMP][ESS]80:8d:b7:62:da:11       5765    -53   94.467    Bytedance Guest                   [ESS]80:8d:b7:63:12:d1       5805    -71   94.467    Bytedance Guest                   [ESS]80:8d:b7:62:da:01       2412    -53   94.467    Bytedance Guest                   [ESS]80:8d:b7:63:43:d1       5260    -80   94.467    Bytedance Guest                   [ESS]80:8d:b7:63:05:22       2437    -68   94.467    Bytedance Guest                   [ESS]80:8d:b7:60:26:51       5260    -87   94.466    Bytedance Guest                   [ESS]80:8d:b7:63:38:f3       5200    -73   94.466    Bytedance 2.4G                    [WPA2-EAP-CCMP][ESS]

一次L2连接成功的过程:

 rec[34]: time=04-08 21:36:05.811 processed=ConnectModeState org=DisconnectedState dest=<null> what=147462(0x24006) !SUPPLICANT_STATE_CHANGE_EVENTrt=34877/34863 29 0 SSID: Bytedance Inc BSSID: 00:00:00:00:00:00 nid: 1 state: ASSOCIATINGrec[35]: time=04-08 21:36:05.910 processed=ConnectModeState org=DisconnectedState dest=<null> what=147462(0x24006) !SUPPLICANT_STATE_CHANGE_EVENTrt=34976/34962 30 0 SSID: Bytedance Inc BSSID: 00:00:00:00:00:00 nid: 1 state: ASSOCIATEDrec[38]: time=04-08 21:36:06.055 processed=ConnectModeState org=DisconnectedState dest=<null> what=147462(0x24006) !SUPPLICANT_STATE_CHANGE_EVENTrt=35121/35108 48 0 SSID: Bytedance Inc BSSID: 80:8d:b7:62:da:12 nid: 1 state: FOUR_WAY_HANDSHAKErec[39]: time=04-08 21:36:06.062 processed=ConnectModeState org=DisconnectedState dest=<null> what=147462(0x24006) !SUPPLICANT_STATE_CHANGE_EVENTrt=35128/35114 49 0 SSID: Bytedance Inc BSSID: 80:8d:b7:62:da:12 nid: 1 state: GROUP_HANDSHAKErec[40]: time=04-08 21:36:06.066 processed=ConnectModeState org=DisconnectedState dest=ObtainingIpState what=147459(0x24003) !NETWORK_CONNECTION_EVENTrt=35132/35118 1 0 80:8d:b7:62:da:12 nBytedance Inc"-WPA_EAPrec[41]: time=04-08 21:36:06.096 processed=ConnectModeState org=ObtainingIpState dest=<null> what=147462(0x24006) !SUPPLICANT_STATE_CHANGE_EVENTrt=35163/35149 52 0 SSID: Bytedance Inc BSSID: 80:8d:b7:62:da:12 nid: 1 state: COMPLETED

1.4 ConnectivityService

ConnectivityService(简称CS)是Android系统中的网络连接大管家,所有类型(如WiFi、Telephony、Ethernet等)的网络都需要注册关联到CS并提供链路请求接口。CS主要提供了以下几个方面的功能:

  • 网络有效性检测(NetworkMonitor)
  • 网络评分与选择(NetworkFactory、NetworkAgent、NetworkAgentInfo)
  • 网口、路由、DNS等参数配置(netd)
  • 向系统及三方提供网络申请接口(ConnectivityManager)

启动方式:

// SystemServer.java
try {connectivity = new ConnectivityService(context, networkManagement, networkStats, networkPolicy);ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity,/* allowIsolated= */ false,DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);networkStats.bindConnectivityManager(connectivity);networkPolicy.bindConnectivityManager(connectivity);
} catch (Throwable e) {reportWtf("starting Connectivity Service", e);
}

外部调用方式:

ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);

adb连接状态下可以通过"adb shell dumpsys connectivity"来查看系统当前所有的网络信息以及网络检测等关键日志:

Current Networks:NetworkAgentInfo{ ni{[type: WIFI[], state: CONNECTED/CONNECTED, reason: (unspecified), extra: "Bytedance Inc", roaming: false, failover: false, isAvailable: true]}  network{100}lp{{InterfaceName: wlan0 LinkAddresses: [fe80::5a44:98ff:fef8:74e2/64,10.95.43.48/21,]  Routes: [fe80::/64 -> :: wlan0,10.95.40.0/21 -> 0.0.0.0 wlan0,0.0.0.0/0 -> 10.95.40.1 wlan0,]DnsAddresses: [10.2.0.2,10.1.0.2,] Domains: bytedance.net MTU: 0 TcpBufferSizes: 524288,1048576,2097152,262144,524288,1048576 HttpProxy: [10.95.40.10] 8888 xl= }}nc{[ Transports: WIFI Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VPN&VALIDATED LinkUpBandwidth>=1048576Kbps LinkDnBandwidth>=1048576Kbps SignalStrength: -54]}Score{60}  everValidated{true}  lastValidated{true}  created{true} lingering{false} explicitlySelected{false} acceptUnvalidated{false} everCaptivePortalDetected{false}lastCaptivePortalDetected{false} }Requests:NetworkRequest [ id=1, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VPN] ]NetworkRequest [ id=3, legacyType=-1, [] ]NetworkRequest [ id=4, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]NetworkRequest [ id=6, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]NetworkRequest [ id=7, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]NetworkRequest [ id=8, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]NetworkRequest [ id=9, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]Lingered:Network Requests:Listen from uid/pid:10126/8357 for NetworkRequest [ id=7, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]Request from uid/pid:1000/2048 for NetworkRequest [ id=1, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VPN] ]Listen from uid/pid:1000/3122 for NetworkRequest [ id=3, legacyType=-1, [] ]Listen from uid/pid:10126/8357 for NetworkRequest [ id=9, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]Listen from uid/pid:10126/8357 for NetworkRequest [ id=8, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]Listen from uid/pid:10126/7970 for NetworkRequest [ id=6, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]Listen from uid/pid:10126/7873 for NetworkRequest [ id=4, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]

1.5 NetworkFactory

系统中的网络工厂,也是CS向链路网络请求的统一接口。Android系统启动之初,数据和WiFi就通过WifiNetworkFactory和TelephonyNetworkFactory将自己注册到CS中,方便CS迅速响应网络请求。

NetworkFactory继承自Handler,并通过AsyncChannel(对Messenger的一种包装,维护了连接的状态,本质上使用Messenger)建立了CS和WifiStateMachine之间的单向通信:

// NetworkFactory.java
public void register() {if (DBG) log("Registering NetworkFactory");if (mMessenger == null) {// 创建以自己为Handler的Messenger并传递给CS// 之后CS就能够使用Messenger通过Binder的形式与WifiStateMachine线程通信mMessenger = new Messenger(this);ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger, LOG_TAG);}
}

CS通过NetworkFactory和WifiStateMachine单向通信:

深刻理解Android系统网络架构

对于AsyncChannel可以参考之前整理的一篇博客:

https://blog.csdn.net/qq_14978113/article/details/80701588

1.6 NetworkAgent

链路网络的代理,是CS和链路网络管理者(如WifiStateMachine)之间的信使,在L2连接成功后创建。通过NetworkAgent,WifiStateMachine可以向CS:

  • 更新网络状态 NetworkInfo(断开、连接中、已连接等)
  • 更新链路配置 LinkProperties(本机网口、IP、DNS、路由信息等)
  • 更新网络能力 NetworkCapabilities(信号强度、是否收费等)

CS可以向WifiStateMachine:

  • 更新网络有效性(即NetworkMonitor的网络检测结果)
  • 禁止自动连接
  • 由于网络不可上网等原因主动断开网络

因此,NetworkAgent提供了CS和WifiStateMachine之间双向通信的能力。原理类似NetworkFactory,也是使用了AsyncChannel和Messenger。

CS和WifiStateMachine通过NetworkAgent进行双向通信:

深刻理解Android系统网络架构

1.7 NetworkMonitor

在链路网络注册到CS,并且所有网络配置信息都已经向netd完成了配置,此时就会开始进行网络诊断,具体诊断的任务交给NetworkMonitor。

NetworkMonitor也是一个状态机,包含以下几种基本状态:

深刻理解Android系统网络架构

StateDescription
DefaultState初始状态。接收CS网络诊断命令消息后触发诊断;接收用户登录网络消息
MaybeNotifyState通知用户登录。接收诊断后发送的"CMD_LAUNCH_CAPTIVE_PORTAL_APP"消息,startActivity显示登录页面
EvaluatingState诊断状态。进入时发送"CMD_REEVALUATE"消息,接收 “CMD_REEVALUATE” 消息并执行网络诊断过程
CaptivePortalState登录状态。进入时发送"CMD_LAUNCH_CAPTIVE_PORTAL_APP"消息显示登录页面,

发送10分钟延迟的"CMD_CAPTIVE_PORTAL_RECHECK"消息进行再次诊断

ValidatedState已验证状态。进入时发送"EVENT_NETWORK_TESTED"通知CS网络诊断完成。
EvaluatingPrivateDnsState私密DNS验证状态。Android Pie验证私密DNS推出。

1.8 NetworkPolicyManagerService

NetworkPolicyManagerService(简称NPMS)是Android系统的网络策略管理者。NPMS会监听网络属性变化(是否收费,metered)、应用前后台、系统电量状态(省电模式)、设备休眠状态(Doze),在这些状态发生改变时,为不同名单内的网络消费者配置不同的网络策略。

启动方式:

// SystemServer.java
try {networkPolicy = new NetworkPolicyManagerService(context, mActivityManagerService,networkManagement);ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
} catch (Throwable e) {reportWtf("starting NetworkPolicy Service", e);
}

外部调用方式:

NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(this);

网络策略的基本目的:

  • 在收费网络的情况下省流量
  • 最大可能性的省电
  • 防止危险流量进入

网络策略中几个重要的名单:

NameListDescription
mUidFirewallStandbyRules黑名单,针对前后台应用。此名单中的APP默认REJECT,可配置ALLOW。
mUidFirewallDozableRules白名单,针对Doze。此名单中的APP在Doze情况下默认ALLOW。
mUidFirewallPowerSaveRules白名单,针对省电模式(由Battery服务提供)。此名单中的APP在省电模式下默认ALLOW,但在Doze情况下仍然REJECT。

NPMS对网络策略进行统一管理和记录,并配合netd和iptables/ip6tables工具,达到网络限制的目的。

adb连接状态下可以使用"adb shell dumpsys netpolicy"来查看当前的网络策略:

System ready: true
Restrict background: false
Restrict power: false
Device idle: false
Metered ifaces: {}
Network policies:NetworkPolicy{template=NetworkTemplate: matchRule=MOBILE, matchSubscriberIds=[460078...] cycleRule=RecurrenceRule{start=2018-01-11T00:00+08:00[Asia/Shanghai] end=null period=P1M} warningBytes=2147483648limitBytes=-1 lastWarningSnooze=-1 lastLimitSnooze=-1 lastRapidSnooze=-1 metered=true inferred=true}NetworkPolicy{template=NetworkTemplate: matchRule=MOBILE, matchSubscriberIds=[460021...] cycleRule=RecurrenceRule{start=2018-01-11T00:00+08:00[Asia/Shanghai] end=null period=P1M} warningBytes=2147483648limitBytes=-1 lastWarningSnooze=-1 lastLimitSnooze=-1 lastRapidSnooze=-1 metered=true inferred=true}
power save whitelist (except idle) app ids:UID=1000: trueUID=1001: trueUID=2000: trueUID=10006: trueUID=10008: trueUID=10013: trueUID=10021: true
Power save whitelist app ids:UID=1000: trueUID=1001: trueUID=2000: trueUID=10013: trueUID=10021: true
Default restrict background whitelist uids:UID=10013UID=10021UID=12810021

1.9 NetworkManagementService

Android SystemServer不具备直接配置和操作网络的能力,所有的网络参数(网口、IP、DNS、Router等)配置,网络策略执行都需要通过netd这个native进程来实际执行或者传递给Kernel来执行。

而NetworkManagementService(简称NMS)就是SystemServer中其他服务连接netd的桥梁。

NMS和netd之间通信的方式有两种:Binder 和 Socket。为什么不全使用Binder?原因在于Android老版本上像 vold、netd 这种native进程和SystemServer通信的方式都是使用的Socket,目前高版本上也在慢慢的Binder化,提升调用速度。

SystemServer和netd之间的数据流向图:

深刻理解Android系统网络架构

adb连接状态下可以使用 “adb shell dumpsys network_management” 查看NMS和netd之前通过socket传递的信息记录:

04-09 15:09:25.609 - SND -> {1331 network create 101}
04-09 15:09:25.609 - RCV <- {200 1331 success}
04-09 15:09:25.610 - SND -> {1332 network interface add 101 wlan0}
04-09 15:09:25.616 - SND -> {1333 traffic wmmer enable}
04-09 15:09:25.701 - RCV <- {200 1332 success}
04-09 15:09:25.702 - SND -> {1334 network route add 101 wlan0 fe80::/64}
04-09 15:09:25.706 - RCV <- {200 1333 command succeeeded}
04-09 15:09:25.707 - SND -> {1335 traffic limitter enable}
04-09 15:09:25.707 - RCV <- {200 1334 success}
04-09 15:09:25.708 - SND -> {1336 network route add 101 wlan0 10.95.40.0/21}
04-09 15:09:25.757 - RCV <- {200 1335 command succeeeded}
04-09 15:09:25.757 - SND -> {1337 traffic updatewmm 10014 1}
04-09 15:09:25.757 - RCV <- {200 1336 success}
04-09 15:09:25.758 - SND -> {1338 network route add 101 wlan0 0.0.0.0/0 10.95.40.1}
04-09 15:09:25.758 - RCV <- {200 1337 command succeeeded}
04-09 15:09:25.759 - SND -> {1339 traffic whitelist 10014 add}
04-09 15:09:25.759 - RCV <- {200 1338 success}
04-09 15:09:25.761 - RCV <- {200 1339 command succeeeded}
04-09 15:09:25.762 - SND -> {1340 resolver setnetdns 101 bytedance.net 10.2.0.2 10.1.0.2 240c::6666 114.114.114.114}

1.10 netd

为了保障各个功能的正常运行,Android系统中有非常多的守护进程(Daemon)。为了保证系统起来后各项功能都已经ready,这些daemon进程跟随系统的启动而启动,而且一般比system_server进程先启动。如存储相关的vold、电话相关的rild、以及网络相关netd等。

 root@virgo:/ # ps |grep -E "netd|vold|rild|system_server"root      253   1     10268  2464  __sys_trac b6d0a824 S /system/bin/voldroot      330   1     30600  2884  binder_thr b6c47ac8 S /system/bin/netdradio     332   1     59132  11124 __sys_trac b6dba824 S /system/bin/rildradio     566   1     57844  11024 __sys_trac b6e9a824 S /system/bin/rildsystem    2048  348   1925344 248952 sys_epoll_ b6ca999c S system_server

init.svc.netd进程由init进程启动,netd.rc 如下:

service netd /system/bin/netdclass mainsocket netd stream 0660 root systemsocket dnsproxyd stream 0660 root inetsocket mdns stream 0660 root systemsocket fwmarkd stream 0660 root inetonrestart restart zygoteonrestart restart zygote_secondary

netd作为Android系统的网络守护者,主要有以下方面的职能:

  • 处理接收来自Kernel的UEvent消息(包含网络接口、带宽、路由等信息),并传递给Framework
  • 提供防火墙设置、网络地址转换(NAT)、带宽控制、网络设备绑定(Tether)等接口
  • 管理和缓存DNS信息,为系统和应用提供域名解析服务

1.11 wpa_supplicant

与netd一样,也是Android系统的一个daemon进程,与netd不同的是,它只有在WiFi开启的情况下才会启动,在WiFi关闭的时候会随之关闭。wpa_supplicant向Framework提供了WiFi配置、连接、断开等接口。

wpa_supplicant比Android的历史要早,在很多其他平台上也被广泛利用,他增加了对更多RFC协议的支持,这也是Google最初选择它的原因。但从Android近几个版本来看,Google还是希望弱化wpa_supplicant,并将其功能迁移至Framework或者其他daemon进程中。Android 8.0发生的几个改变:

  • 与system_server的通信从原来的Socket通信改成了HIDL,提高了速度、便于system分区自升级
  • 扫描的功能迁移到了system/wificond中,弱化wpa_supplicant

启动方式:

service wpa_supplicant /system/vendor/bin/hw/wpa_supplicant -g@android:wpa_wlan0interface android.hardware.wifi.supplicant@1.0::ISupplicant defaultinterface android.hardware.wifi.supplicant@1.1::ISupplicant defaultsocket wpa_wlan0 dgram 660 wifi wificlass maindisabledoneshot

wpa_supplicant和Framework通信:

深刻理解Android系统网络架构

二. 注网过程

Android系统网络注册过程很复杂,涉及到的模块也非常多。主要可以分为以下几个步骤:

  1. WiFi热点扫描,获取扫描结果
  2. 配置WiFi验证信息,已配置完可忽略
  3. 数据链路层L2连接(包含Associate、FourWay-Handshake、Group-Handshake等过程)
  4. DHCP通过UDP的方式获取IP、Gateway、DNS等网络信息
  5. 配置Interafce、IP、DNS、Router到netd

2.1 WiFi链路连接

以自动连接为例:

扫描流程:

深刻理解Android系统网络架构

在Android系统中,WiFi扫描的方式主要有三种:

  1. 前台扫描:亮屏状态下且在WiFi Settings页面,每10s发起一次扫描
  2. 后台扫描:亮屏状态下且不在WiFi Settings页面,扫描间隔呈二进制指数退避,退避:interval * (2^n),最短间隔为20s,最长间隔为160s
  3. PNO扫描:灭屏状态下只扫描已保存的网络。最小间隔min=20s,最大间隔max=20s*3

在Android 8.0 以后,为了解决多种扫描类型带来的冗杂,Google推出了 WifiScanningService,在其中维护了3个状态机分别应用上述3种扫描:WifiSingleScanStateMachine 、WifiBackgroundScanStateMachine、WifiPnoScanStateMachine。

AP选择流程:

假如设备中保存了多个可以上网的网络,并且当前都可以被扫描到,系统如何保证连接上最佳(网络质量最高、用户最想要连接)的网络呢?

Ans:WifiNetworkSelector提供了AP优选的能力,影响优选的因素有:

  1. 是否被用户由于无法上网而UnWanted,进入了禁止自动连接的黑名单
  2. 信号是否过弱,2.4GHz下不低于-80dBm,5GHz下不低于-77dBm
  3. 其他因素一致情况下,5GHz比2.4GHz享有 40 分加成
  4. 上次用户主动选择的AP享有最高 480 分加成,根据时长递减
  5. 根据信号衰减值(rssi)计算信号分加成(rssi + 85)* 4
  6. 与当前连接的AP一致享有 24 分加成
  7. 非开放网络享有 80 分加成

连接流程:

深刻理解Android系统网络架构

Android WiFi的连接过程主要分为 链路连接 和 DHCP获取IP 两个过程。(如果使用的是静态IP则不需要进行DHCP)

WiFi链路连接:

深刻理解Android系统网络架构

(图片来自:https://blog.csdn.net/QQ474111624/article/details/86620579 )

  1. 认证:对于WPA-PSK、WPA2-PSK类型网络使用密码(Pre-shared key)进行认证;对于EAP类型(PEAP、TTLS、PWD、TLS)则根据具体的加密方法需要身份、密码、证书等进行认证。

  2. 关联:由STA向AP发出关联请求,AP回应关联请求。STA和AP建立关联后,后续数据报文的收发则只能与关联的AP进行。

    注:对于开放类型的网络,这时候链路就已经连理成功了。

  3. 四路握手:PTK(Pairwise Transient Key,成对临时密钥,用于加密单播数据流的加密密钥)的生成、交换、安装。

  4. 组握手:GTK(Group Temporal Key, 组临时密钥,用于加密广播和组播数据流的加密密钥)的生成、交换、安装。

DHCP获取IP: 动态主机设置协议(Dynamic Host Configuration Protocol,DHCP)是一个局域网内的网络协议,使用UDP协议工作,主要用于内部网或网络服务供应商自动分配IP地址。DHCP流程:

深刻理解Android系统网络架构

  1. DHCP DISCOVER:DHCP客户机发送有限广播请求IP。(0.0.0.0:68 → 255.255.255.255:67)

  2. DHCP OFFER:DHCP服务器响应。在收到客户机的DHCP请求后,DHCP服务器从IP地址池中找出合法可用的IP地址填入DHCP OFFER报文中并发送有限广播给客户机。(192.168.1.1:67 → 255.255.255.255:68)

  3. DHCP REQUEST:DHCP客户机选择IP。DHCP客户机从接收到的DHCP OFFER消息中选择IP地址,并发送DHCP REQUEST有限广播到所有的DHCP服务器,表明它接受提供的内容。(0.0.0.0:68 → 255.255.255.255:67)

  4. DHCP ACK:DHCP服务器确认租约。(192.168.1.1:67 → 255.255.255.255:68)

Android系统中为DHCP创建的协议族为IPPROTO_UDP的Socket:

// DhcpClient.java
private boolean initUdpSocket() {final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_DHCP);try {// UDP数据报mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);Os.setsockoptIfreq(mUdpSock, SOL_SOCKET, SO_BINDTODEVICE, mIfaceName);// 广播Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0);// Inet4Address.ANY: 0.0.0.0// DhcpPacket.DHCP_CLIENT: 68Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT);NetworkUtils.protectFromVpn(mUdpSock);} catch(SocketException|ErrnoException e) {Log.e(TAG, "Error creating UDP socket", e);return false;} finally {TrafficStats.setThreadStatsTag(oldTag);}return true;
}

2.2 WiFi注网

WiFi、Data、Ethernet等类型链路网络注册到CS,并将Interface、IP、DNS、Router等网络属性设置到netd中的过程称为Android系统的注网过程。

前面已经提到WifiStateMachine和CS之间是通过WifiNetworkAgent使用AsyncChannel来进行双向通信的,这里不再赘述。

注网流程图:

深刻理解Android系统网络架构

网络属性设置到netd的Socket通信记录可以通过 “adb shell dumpsys network_management” 来查看。如前文所说,Framework和netd通信有Socket和Binder两种方式,在网络注册这个过程中,DNS的设置在Android O版本前后发生了变化,O以前的版本使用Socket,而O以后的版本使用的是Binder,如下所示:

Android M:

04-09 15:09:25.609 - SND -> {1331 network create 101}
04-09 15:09:25.609 - RCV <- {200 1331 success}
04-09 15:09:25.610 - SND -> {1332 network interface add 101 wlan0}
04-09 15:09:25.701 - RCV <- {200 1332 success}
04-09 15:09:25.702 - SND -> {1334 network route add 101 wlan0 fe80::/64}
04-09 15:09:25.707 - RCV <- {200 1334 success}
04-09 15:09:25.708 - SND -> {1336 network route add 101 wlan0 10.95.40.0/21}
04-09 15:09:25.757 - RCV <- {200 1336 success}
04-09 15:09:25.758 - SND -> {1338 network route add 101 wlan0 0.0.0.0/0 10.95.40.1}
04-09 15:09:25.759 - RCV <- {200 1338 success}
// 使用socket的方式,在"dumpsys network_management"中有该记录
04-09 15:09:25.762 - SND -> {1340 resolver setnetdns 101 bytedance.net 10.2.0.2 10.1.0.2 240c::6666 114.114.114.114}

Android O:

2019-04-09T17:38:48.692 - SND -> {149271 network create 314}
2019-04-09T17:38:48.692 - RCV <- {200 149271 success}
2019-04-09T17:38:48.878 - SND -> {149277 network interface add 314 wlan0}
2019-04-09T17:38:48.963 - RCV <- {200 149277 success}
2019-04-09T17:38:48.965 - SND -> {149278 network route add 314 wlan0 fe80::/64}
2019-04-09T17:38:48.966 - RCV <- {200 149278 success}
2019-04-09T17:38:48.968 - SND -> {149279 network route add 314 wlan0 10.95.48.0/21}
2019-04-09T17:38:48.969 - RCV <- {200 149279 success}
2019-04-09T17:38:48.969 - SND -> {149280 network route add 314 wlan0 0.0.0.0/0 10.95.48.1}
2019-04-09T17:38:48.970 - RCV <- {200 149280 success}
// 没有setnetdns的记录,因为这个过程使用了Binder的通信方式

2.3 连接状态及相关广播

以WiFi举例,对于应用开发者来说,有3个网络相关的广播比较重要:

ClassBroadcastDescription
WifiManager.javaandroid.net.wifi.WIFI_STATE_CHANGEDWiFi开关状态的改变(打开、打开中、关闭、关闭中)
WifiManager.javaandroid.net.wifi.STATE_CHANGEWiFi连接状态的改变(已连接、连接中、已断开、断开中),“已连接” 不代表此时可以上网。
ConnectivityManager.javaandroid.net.conn.CONNECTIVITY_CHANGE网络(不只是WiFi)连接状态的改变(连接、断开),“已连接” 代表此时可以上网。

这三个广播都是粘性广播,通过Context.sendStickyBroadcast来发送,因此应用在注册该广播时,如果之前有发送过该广播,就一定会收到一次广播通知。

Note:不要通过 “android.net.wifi.STATE_CHANGE” 来判断是否可以上网,因为链路成功后是不能代表此时可以支持上网的,需要等待CS配置完成并发送 “android.net.conn.CONNECTIVITY_CHANGE” 的广播后才可能上网。

三. 网络优选/评分

3.1 网络有效性检测

每种类型的链路网络在L2连接上并注册到CS中时,CS都会为其匹配一个NetworkMonitor,用于进行网络有效性检测。NetworkMonitor将检测结果反馈给CS,CS会根据结果进行以下过程:

  • 标记和提示用户网络有效性状态
  • 提示用户进入网络二次登录操作(针对Portal类型网络,如机场WiFi)
  • 为网络进行评分,并进行网络切换,提供最优网络

触发网络有效性检测的时机:

  • 链路连接上并且Interface/IP/Router/DNS等配置成功后触发检测
  • 网络检测不通过时延时触发检测
  • Portal类型网络登录后触发检测
  • 三方APP通过CS的reportNetworkConnectivity接口反馈网络有效性触发检测

检测方式实际就是通过HTTP请求下面域名进行的:

// NetworkMonitor.java
private static final String DEFAULT_HTTPS_URL     = "https://www.google.com/generate_204";
private static final String DEFAULT_HTTP_URL      ="http://connectivitycheck.gstatic.com/generate_204";
private static final String DEFAULT_FALLBACK_URL  = "http://www.google.com/gen_204";
private static final String DEFAULT_OTHER_FALLBACK_URLS ="http://play.googleapis.com/generate_204";

网络有效性检测的原理:

  1. DNS验证:使用"Network.getAllByName(host)"进行DNS解析,成功则验证通过,抛出"UnknownHostException"异常则说明验证失败。

  2. HTTP验证:使用HttpURLConnection访问generate_204网站(访问成功会返回204的response code),该网站一般使用Google提供的"http://connectivitycheck.gstatic.com/generate_204",各大手机厂商也会进行定制,防止被墙导致诊断失误。HTTP验证会有3种结果,根据response code确定:

  • code=204:返回值由generate_204网站返回,网络验证通过
  • 200<=code<=399:返回值由路由器网关返回,一般会携带redirect url,网络需要登录
  • code不在上述范围内:无法上网
  • 抛出"IOException",无法上网

网络有效性检测的主要流程: (下图描述了某个需要二次登录认证网络的有效性检测过程)

深刻理解Android系统网络架构

3.2 评分机制

CS中注册的网络可能不只一种,同时,CS也能够向Data和WiFi提供的NetworkFactory请求链路网络。多种网络共存时,就存在优先选择的问题,CS通过分数统计的方式来进行网络择优。评分的影响因素有:

  1. 链路网络存在初始分数:WiFi默认为60分,Data默认为50分
  2. 链路网络根据信号衰减rssi更新初始分数
  3. 用户强行选择的网络(不可上网但用户主动连接)默认100分
  4. 网络是否可以上网,不可上网则减去40分
// NetworkAgentInfo.java
private int getCurrentScore(boolean pretendValidated) {if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {// 用户主动选择,一般是摄像机、车载WiFi等设备// 直接返回100分return ConnectivityConstants.MAXIMUM_NETWORK_SCORE;}// currentScore为链路网络的初始分数,受rssi影响int score = currentScore;if (!lastValidated && !pretendValidated && !ignoreWifiUnvalidationPenalty()) {// 不可上网,减去40分score -= ConnectivityConstants.UNVALIDATED_SCORE_PENALTY;}if (score < 0) score = 0;return score;
}

当前的分数影响到网络选择,以WiFi和Data举例,

如果当前连接Data:由于score小于WiFI的默认分数60,向WifiNetworkFactory请求网络,并保持当前网络直到WiF连接重新触发网络验证、评分和网络选择。

如果当前连接WiFi:

  1. score > 50:保持使用WiFi,如果Data连接着且没有"针对性"(NetworkRequest中存在"TRANSPORT_CELLULAR"选项)请求,则断开Data网络
  2. score < 50:保持使用WiFi,并向TelephonyNetworkFactory请求Data网络,Data连上后重新触发网络验证、评分和网络选择

注明:Android O上开始在CS中默认保留一个 “TRANSPORT_CELLULAR” 的mDefaultMobileDataRequest,除非用户主动关闭数据网络,否则将一直保持数据链路的连接状态,方便在WiFi状态不佳时进行WiFi和Data之间的快速切换。

四. 网络策略/防火墙

为了达到省电/省流量/拦截等目的,Android系统会在多种场景下(Doze、Powersave、前后台等)根据配置进行网络流量限制。

4.1 Netfilter和iptables

Android基于Linux内核,而Linux则使用Netfilter这个"钩子"在内核的IP协议栈中去hook各个阶段的数据包,根据预先制定的包过滤规则,定义哪些数据包可以接收,哪些数据包需要丢弃或者拒绝。

iptables/ip6tables:iptables/ip6tables是用户层的一个工具,用户层使用iptables/ip6tables通过socket的系统调用方式(setsockopt、getsockopt)获取和修改Netfilter需要的包过滤规则,是用户层和内核层Netfilter之间交互的工具。(iptables用于IPv4,ip6tables用于IPv6)

Netfilter和iptables是Linux网络防火墙中重要的组成部分。Netfilter的工作流程:

深刻理解Android系统网络架构

(图片来自:http://blog.chinaunix.net/uid-23069658-id-3160506.html )

收到的每个数据包都从(1)进来,经过路由判决,如果是发送给本机的就经过(2),然后往协议栈的上层继续传递;否则,如果该数据包的目的地不是本机,那么就经过(3),然后顺着(5)将该包转发出去。Netfilter在 PRE_ROUTING、LOCAL_IN、LOCAL_OUT、FORWARD、POST_ROUTING 这5个阶段分别设置回调函数(hook函数),对每一个进出的数据包进行检测。

q:为什么不只在PRE_ROUTING和POST_ROUTING这两个入口和出口设置数据包检测?

ans:一方面,这两个阶段处于网络层(IP层)协议栈中,这时候不会拆解TCP/UDP等传输层协议的头部信息,如果需要对更上层协议内容(如端口等)进行过滤,在这两个阶段显然不行;

另一方面,这两个阶段协议栈不知道这个数据包是需要转发给谁,是转发到下一跳还是传递给上层协议栈,如果是需要传递给上层应用,就更不知道需要传递给哪个应用了。但这些信息在LOCAL_IN和LOCAL_OUT这两个阶段是明确的(明确了传输层协议类型、源IP/目的IP、源端口/目的端口,确定了一条连接),这样过滤应用的报文就成为了可能。

Netfilter主要有3个模块和3张表:

  1. 包过滤子模块:对应filter表,能够对数据包进行过滤,DROP/REJECT/RETURN/ACCEPT
  2. NAT子模块:对应nat表,能够实现网络地址转换(这个在运营商服务主机中很常用,路由器中其实也运用了该功能,如你手机的外网IP是120.52.148.57,但内网IP是192.168.1.100,这个时候就需要进行网络地址转换)
  3. 数据报修改和跟踪模块:对应mangle表,能够对数据包打上或者判断mark标记,也可以修改数据报中的其它内容(如IP协议头部的tos等)。

应用层通过iptables工具修改filter、nat和mangle这三张表来控制Netfilter的行为。

iptables和Netfilter交互方式:

iptables的源码在/external/iptables目录下,编译完成后,iptables在系统中是一个可执行的bin文件,位于/system/bin目录下:

root@virgo:/ # ls -lZ system/bin |grep -E "iptables|ip6tables"
-rwxr-xr-x root     shell             u:object_r:system_file:s0 ip6tables
lrwxr-xr-x root     shell             u:object_r:system_file:s0 ip6tables-restore -> ip6tables
lrwxr-xr-x root     shell             u:object_r:system_file:s0 ip6tables-save -> ip6tables
-rwxr-xr-x root     shell             u:object_r:system_file:s0 iptables
lrwxr-xr-x root     shell             u:object_r:system_file:s0 iptables-restore -> iptables
lrwxr-xr-x root     shell             u:object_r:system_file:s0 iptables-save -> iptables

iptables和Netfilter通信使用的是sockopt的系统调用方式,通过setsockopt和getsockopt在参数中传递对应命令值来进行修改和查询:

深刻理解Android系统网络架构

(图片来自:http://blog.chinaunix.net/uid-23069658-id-3160506.html)

内核中定义了iptables sockopt的相关命令值:

// ./include/uapi/linux/netfilter_ipv4/ip_tables.h
/** New IP firewall options for [gs]etsockopt at the RAW IP level.* Unlike BSD Linux inherits IP options so you don't have to use a raw* socket for this. Instead we check rights in the calls.** ATTENTION: check linux/in.h before adding new number here.*/
#define IPT_BASE_CTL        64// 修改ip tables规则
#define IPT_SO_SET_REPLACE  (IPT_BASE_CTL)
// 加入流量计数器
#define IPT_SO_SET_ADD_COUNTERS (IPT_BASE_CTL + 1)
#define IPT_SO_SET_MAX      IPT_SO_SET_ADD_COUNTERS// 获取ip tables某种类型的表信息
#define IPT_SO_GET_INFO         (IPT_BASE_CTL)
// 获取ip tables规则信息
#define IPT_SO_GET_ENTRIES      (IPT_BASE_CTL + 1)
#define IPT_SO_GET_REVISION_MATCH   (IPT_BASE_CTL + 2)
#define IPT_SO_GET_REVISION_TARGET  (IPT_BASE_CTL + 3)
#define IPT_SO_GET_MAX          IPT_SO_GET_REVISION_TARGET

以iptables获取某个表的规则信息为例:

// ./external/iptables/libiptc/libiptc.c
struct xtc_handle *TC_INIT(const char *tablename)
{// 表所有信息数据结构,包含info和规则等struct xtc_handle *h;// 表基本信息数据结构STRUCT_GETINFO info;unsigned int tmp;socklen_t s;int sockfd;
retry:iptc_fn = TC_INIT;//...sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW);//...s = sizeof(info);// 把tablename复制到info中,用于告知Netfilter查询的是哪张表strcpy(info.name, tablename);// 使用getsockopt的系统调用方式,其中IPT命令为SO_GET_INFO,对应内核// 中定义的IPT_SO_GET_INFO,调用完成后,表信息通过info参数返回if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0) {close(sockfd);return NULL;}if ((h = alloc_handle(info.name, info.size, info.num_entries))== NULL) {close(sockfd);return NULL;}/* Initialize current state */h->sockfd = sockfd;h->info = info;h->entries->size = h->info.size;tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size;// 使用getsockopt的系统调用方式,其中IPT命令为SO_GET_ENTRIES,对应内核// 中定义的IPT_SO_GET_ENTRIES,调用完成后,规则信息通过h->entries参数返回if (getsockopt(h->sockfd, TC_IPPROTO, SO_GET_ENTRIES, h->entries,&tmp) < 0)goto error;if (parse_table(h) < 0)goto error;CHECK(h);return h;error:TC_FREE(h);/* A different process changed the ruleset size, retry */if (errno == EAGAIN)goto retry;return NULL;
}

当然,native层并不需要这么复杂的去操作ip tables,这些都已经被iptables工具封装好了。系统中如netd这些native进程甚至我们在root shell下使用iptables命令就可以操作,如使用"iptables -t filter -L"查看filter表信息:

root@virgo:/ # iptables -t filter -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
bw_INPUT   all  --  anywhere             anywhere
fw_INPUT   all  --  anywhere             anywhere
tc_limiter  all  --  anywhere             anywhereChain FORWARD (policy ACCEPT)
target     prot opt source               destination
oem_fwd    all  --  anywhere             anywhere
fw_FORWARD  all  --  anywhere             anywhere
bw_FORWARD  all  --  anywhere             anywhere
natctrl_FORWARD  all  --  anywhere             anywhereChain OUTPUT (policy ACCEPT)
target     prot opt source               destination
wmsctrl_OUTPUT  tcp  --  anywhere             anywhere
DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
DROP       udp  --  anywhere             anywhere             udp dpt:1900 /* Drop SSDP on WWAN */
oem_out    all  --  anywhere             anywhere
fw_OUTPUT  all  --  anywhere             anywhere

这个过程其实就是fork了iptables子进程并执行了其main函数,并且携带了"-t filter -L"等args参数。

注明:Google、各大ODM及手机厂商都会配置很多包过滤规则来进行定制化,因此iptables的操作会很频繁,每次fork都会占用比较大的时间资源;并且为了保证并发访问修改内核的ip tables规则时的安全性,iptables中其实是有文件锁(#define XT_LOCK_NAME "/system/etc/xtables.lock")存在的,这样就又存在排队等待。这个过程比较耗时甚至可能还会引起上层的系统watchdog。

Google在Android O上做了优化:netd中fork出一个iptables-restore进程并且保持它的存活,每次需要时都通过socket的方式将命令发送给该子进程,并且在执行连续执行命令时做了优化,尽可能保证一次查询一次修改。大大优化了效率。如下是系统中的iptables-restore进程,他的父进程是netd:

HWEML:/ $ ps -A |grep -E "iptables|netd"
root           569     1 2163632   4320 0                   0 S netd
root          9071   569   13040   2788 0                   0 S iptables-restore

4.2 前后台网络策略

前面介绍NetworkPolicyManagerService时提到了一个mUidFirewallStandbyRules数组名单,这里面缓存了后台需要限制上网的uid黑名单。

NameListDescription
mUidFirewallStandbyRules黑名单,针对前后台应用。此名单中的APP默认REJECT,可配置ALLOW。
mUidFirewallDozableRules白名单,针对Doze。此名单中的APP在Doze情况下默认ALLOW。
mUidFirewallPowerSaveRules白名单,针对省电模式(由Battery服务提供)。此名单中的APP在省电模式下默认ALLOW,但在Doze情况下仍然REJECT。

可以使用"adb shell dumpsys network_management"来查看mUidFirewallStandbyRules名单:

root@virgo:/ # dumpsys network_management
UID firewall standby chain enabled: true
UID firewall standby rule: [10055:2,10104:2,10108:2,10111:2,10116:2,10123:2,10125:2,10126:2,10127:2]

前后台网络策略最终通过filter表中的fw_standby这个名单来控制,该名单与mUidFirewallStandbyRules名单保持一致:

root@virgo:/ # iptables -t filter -L fw_standby
Chain fw_standby (2 references)
target     prot opt source               destination
DROP       all  --  anywhere             anywhere             owner UID match u0_a55
DROP       all  --  anywhere             anywhere             owner UID match u0_a104
DROP       all  --  anywhere             anywhere             owner UID match u0_a108
DROP       all  --  anywhere             anywhere             owner UID match u0_a111
DROP       all  --  anywhere             anywhere             owner UID match u0_a116
DROP       all  --  anywhere             anywhere             owner UID match u0_a123
DROP       all  --  anywhere             anywhere             owner UID match u0_a125
DROP       all  --  anywhere             anywhere             owner UID match u0_a126
DROP       all  --  anywhere             anywhere             owner UID match u0_a127
RETURN     all  --  anywhere             anywhere

fw_stanby这条chain是黑名单,Netfilter会将数据包的信息与该名单规则(UID匹配)一条条匹配,匹配到就会执行DROP操作,也就是丢弃数据包;如果所有的名单规则都未匹配,则匹配最后一条没有限定条件的规则,执行RETURN操作,也就是放行数据包。"2 references"表示被另外两条chain(fw_INPUT和fw_OUTPUT)引用,只有链接到Netfilter直接操作的chain上时该名单才能够生效。

注明:只有在非充电情况下fw_standy这条chain才会生效,也就是被fw_INPUT和fw_OUTPUT这两条chain引用,否则fw_standy就会显示"0 references"。可以通过 “adb shell dumpsys battery unplug” 来取消USB充电,然后使用 “adb shell iptables -t filter -L fw_standby” 来查看。

4.3 Doze下网络策略

Doze下的网络策略由NetworkPolicyManagerService中的mUidFirewallDozableRules控制,对应filter表中的fw_dozable chain,这是个白名单,符合名单中任何一条UID规则的数据包都会被放行,否则匹配到最后一条默认规则,被丢弃。这个白名单也是可配置的,将一些关键应用(如微信、QQ等需要在休眠时也能接收消息)配置在其中,防止Doze情况下这些应用无法上网,影响用户使用。

正常情况下,fw_dozable这条chain不会被使用(0 references):

root@virgo:/ # iptables -t filter -L fw_dozable
Chain fw_dozable (0 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere             owner UID match 0-9999
RETURN     all  --  anywhere             anywhere             owner UID match radio
RETURN     all  --  anywhere             anywhere             owner UID match finddevice
RETURN     all  --  anywhere             anywhere             owner UID match u0_a0
RETURN     all  --  anywhere             anywhere             owner UID match u0_a1
RETURN     all  --  anywhere             anywhere             owner UID match u0_a2
RETURN     all  --  anywhere             anywhere             owner UID match u0_a3
RETURN     all  --  anywhere             anywhere             owner UID match u0_a4
RETURN     all  --  anywhere             anywhere             owner UID match u0_a5

当系统进入Doze模式时,fw_dozable就会被使用并且Add到fw_INPUT和fw_OUTPUT中(2 references):

Chain fw_dozable (2 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere             owner UID match 0-9999
RETURN     all  --  anywhere             anywhere             owner UID match radio
RETURN     all  --  anywhere             anywhere             owner UID match finddevice
RETURN     all  --  anywhere             anywhere             owner UID match u0_a0
RETURN     all  --  anywhere             anywhere             owner UID match u0_a1
RETURN     all  --  anywhere             anywhere             owner UID match u0_a2
RETURN     all  --  anywhere             anywhere             owner UID match u0_a3
....
DROP       all  --  anywhere             anywhere

Netfilter将数据包与fw_dozable中的名单一条条匹配,当UID符合规则时,则RETURN,也就是放行;如果数据包的归属者UID都不满足fw_dozable中的规则,则执行最后一条默认的DROP规则,数据包被丢弃。

注:可以使用 "adb shell dumpsys deviceidle force-idle deep"来进入Doze模式

root@virgo:/ # dumpsys deviceidle force-idle deep
Now forced in to deep idle mode

五. 无法上网原因

最后,简单罗列几种可能导致无法上网的原因:

  1. WiFi网络未验证(portal网络),访问时路由器会重定向到二次登录网址
  2. 运营商服务器或代理服务器问题,无法连接到外网
  3. DNS服务器问题,导致DNS解析失败
  4. 系统时间不正常,导致证书失效,SSL/TLS握手失败,HTTPS无法上网
  5. TCP连接长时间无数据收发,达到NAT超时时间,网络运营商切断TCP连接,导致长连接失效(push心跳间隔应小于NAT超时时间)
  6. 应用进入了后台且在mUidFirewallStandbyRules黑名单中,数据包被DROP
  7. 系统进入省电模式且应用不在mUidFirewallPowerSaveRules白名单中,数据包被DROP
  8. 系统进入Doze且应用不在mUidFirewallDozableRules白名单中,数据包被DROP