引言:這篇文章以WiFi舉例,介紹了Android系統網絡架構。其內容包含:網絡鏈路的連接和注冊、網絡有效性檢測和網絡優選、Android系統網絡防火牆和幾種場景下的網絡策略等,文章的最后也列舉了幾種常見的無法上網原因供大家參考。
一. 基本結構
1.1 類圖
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、漫游等各個狀態。基本的狀態如圖:("→"的起始端為父狀態,終端為子狀態;外部消息可以在子→父狀態中流動,子狀態不處理的消息交由父狀態處理)
各個狀態的描述:
State | Description |
---|---|
DefaultState | 初始狀態,WiFi 開關沒有打開,驅動沒有加載。當處於其他狀態時,消息由子狀態上行可用於日志打印。 |
ConnectModeState | ConnectModeStateWiFi開關已經打開,驅動已經加載,native中wpa_supplicant已經啟動。此時可以進行掃描和連接的操作。進入該狀態時發送 “android.net.wifi.WIFI_STATE_CHANGED” 廣播。 |
L2ConnectedState | L2是"Level 2"的意思,代表OSI網絡模型中的2層即數據鏈路層。這個狀態代表數據鏈路已經建立完成。進入該狀態時發送"android.net.wifi.STATE_CHANGE"廣播,連接狀態是CONNECTING。 |
ObtainingIpState | DHCP獲取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 Flags
80: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_EVENT
rt=34877/34863 29 0 SSID: Bytedance Inc BSSID: 00:00:00:00:00:00 nid: 1 state: ASSOCIATING
rec[35]: time=04-08 21:36:05.910 processed=ConnectModeState org=DisconnectedState dest=<null> what=147462(0x24006) !SUPPLICANT_STATE_CHANGE_EVENT
rt=34976/34962 30 0 SSID: Bytedance Inc BSSID: 00:00:00:00:00:00 nid: 1 state: ASSOCIATED
rec[38]: time=04-08 21:36:06.055 processed=ConnectModeState org=DisconnectedState dest=<null> what=147462(0x24006) !SUPPLICANT_STATE_CHANGE_EVENT
rt=35121/35108 48 0 SSID: Bytedance Inc BSSID: 80:8d:b7:62:da:12 nid: 1 state: FOUR_WAY_HANDSHAKE
rec[39]: time=04-08 21:36:06.062 processed=ConnectModeState org=DisconnectedState dest=<null> what=147462(0x24006) !SUPPLICANT_STATE_CHANGE_EVENT
rt=35128/35114 49 0 SSID: Bytedance Inc BSSID: 80:8d:b7:62:da:12 nid: 1 state: GROUP_HANDSHAKE
rec[40]: time=04-08 21:36:06.066 processed=ConnectModeState org=DisconnectedState dest=ObtainingIpState what=147459(0x24003) !NETWORK_CONNECTION_EVENT
rt=35132/35118 1 0 80:8d:b7:62:da:12 nid=1 "Bytedance Inc"-WPA_EAP
rec[41]: time=04-08 21:36:06.096 processed=ConnectModeState org=ObtainingIpState dest=<null> what=147462(0x24006) !SUPPLICANT_STATE_CHANGE_EVENT
rt=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單向通信:
對於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進行雙向通信:
1.7 NetworkMonitor
在鏈路網絡注冊到CS,並且所有網絡配置信息都已經向netd完成了配置,此時就會開始進行網絡診斷,具體診斷的任務交給NetworkMonitor。
NetworkMonitor也是一個狀態機,包含以下幾種基本狀態:
State | Description |
---|---|
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);
網絡策略的基本目的:
- 在收費網絡的情況下省流量
- 最大可能性的省電
- 防止危險流量進入
網絡策略中幾個重要的名單:
NameList | Description |
---|---|
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: trueRestrict background: falseRestrict power: falseDevice idle: falseMetered 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=2147483648 limitBytes=-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=2147483648 limitBytes=-1 lastWarningSnooze=-1 lastLimitSnooze=-1 lastRapidSnooze=-1 metered=true inferred=true}power save whitelist (except idle) app ids: UID=1000: true UID=1001: true UID=2000: true UID=10006: true UID=10008: true UID=10013: true UID=10021: truePower save whitelist app ids: UID=1000: true UID=1001: true UID=2000: true UID=10013: true UID=10021: trueDefault restrict background whitelist uids: UID=10013 UID=10021 UID=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之間的數據流向圖:
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/vold root 330 1 30600 2884 binder_thr b6c47ac8 S /system/bin/netd radio 332 1 59132 11124 __sys_trac b6dba824 S /system/bin/rild radio 566 1 57844 11024 __sys_trac b6e9a824 S /system/bin/rild system 2048 348 1925344 248952 sys_epoll_ b6ca999c S system_server
init.svc.netd進程由init進程啟動,netd.rc 如下:
service netd /system/bin/netd class main socket netd stream 0660 root system socket dnsproxyd stream 0660 root inet socket mdns stream 0660 root system socket fwmarkd stream 0660 root inet onrestart restart zygote onrestart 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_wlan0 interface android.hardware.wifi.supplicant@1.0::ISupplicant default interface android.hardware.wifi.supplicant@1.1::ISupplicant default socket wpa_wlan0 dgram 660 wifi wifi class main disabled oneshot
wpa_supplicant和Framework通信:
二. 注網過程
Android系統網絡注冊過程很復雜,涉及到的模塊也非常多。主要可以分為以下幾個步驟:
- WiFi熱點掃描,獲取掃描結果
- 配置WiFi驗證信息,已配置完可忽略
- 數據鏈路層L2連接(包含Associate、FourWay-Handshake、Group-Handshake等過程)
- DHCP通過UDP的方式獲取IP、Gateway、DNS等網絡信息
- 配置Interafce、IP、DNS、Router到netd
2.1 WiFi鏈路連接
以自動連接為例:
掃描流程:
在Android系統中,WiFi掃描的方式主要有三種:
- 前台掃描:亮屏狀態下且在WiFi Settings頁面,每10s發起一次掃描
- 后台掃描:亮屏狀態下且不在WiFi Settings頁面,掃描間隔呈二進制指數退避,退避:interval * (2^n),最短間隔為20s,最長間隔為160s
- PNO掃描:滅屏狀態下只掃描已保存的網絡。最小間隔min=20s,最大間隔max=20s*3
在Android 8.0 以后,為了解決多種掃描類型帶來的冗雜,Google推出了 WifiScanningService,在其中維護了3個狀態機分別應用上述3種掃描:WifiSingleScanStateMachine 、WifiBackgroundScanStateMachine、WifiPnoScanStateMachine。
AP選擇流程:
假如設備中保存了多個可以上網的網絡,並且當前都可以被掃描到,系統如何保證連接上最佳(網絡質量最高、用戶最想要連接)的網絡呢?
Ans:WifiNetworkSelector提供了AP優選的能力,影響優選的因素有:
- 是否被用戶由於無法上網而UnWanted,進入了禁止自動連接的黑名單
- 信號是否過弱,2.4GHz下不低於-80dBm,5GHz下不低於-77dBm
- 其他因素一致情況下,5GHz比2.4GHz享有 40 分加成
- 上次用戶主動選擇的AP享有最高 480 分加成,根據時長遞減
- 根據信號衰減值(rssi)計算信號分加成(rssi + 85)* 4
- 與當前連接的AP一致享有 24 分加成
- 非開放網絡享有 80 分加成
連接流程:
Android WiFi的連接過程主要分為 鏈路連接 和 DHCP獲取IP 兩個過程。(如果使用的是靜態IP則不需要進行DHCP)
WiFi鏈路連接:
(圖片來自:https://blog.csdn.net/QQ474111624/article/details/86620579 )
-
認證:對於WPA-PSK、WPA2-PSK類型網絡使用密碼(Pre-shared key)進行認證;對於EAP類型(PEAP、TTLS、PWD、TLS)則根據具體的加密方法需要身份、密碼、證書等進行認證。
-
關聯:由STA向AP發出關聯請求,AP回應關聯請求。STA和AP建立關聯后,后續數據報文的收發則只能與關聯的AP進行。
注:對於開放類型的網絡,這時候鏈路就已經連理成功了。
-
四路握手:PTK(Pairwise Transient Key,成對臨時密鑰,用於加密單播數據流的加密密鑰)的生成、交換、安裝。
-
組握手:GTK(Group Temporal Key, 組臨時密鑰,用於加密廣播和組播數據流的加密密鑰)的生成、交換、安裝。
DHCP獲取IP: 動態主機設置協議(Dynamic Host Configuration Protocol,DHCP)是一個局域網內的網絡協議,使用UDP協議工作,主要用於內部網或網絡服務供應商自動分配IP地址。DHCP流程:
- DHCP DISCOVER:DHCP客戶機發送有限廣播請求IP。(0.0.0.0:68 → 255.255.255.255:67)
- DHCP OFFER:DHCP服務器響應。在收到客戶機的DHCP請求后,DHCP服務器從IP地址池中找出合法可用的IP地址填入DHCP OFFER報文中並發送有限廣播給客戶機。(192.168.1.1:67 → 255.255.255.255:68)
- DHCP REQUEST:DHCP客戶機選擇IP。DHCP客戶機從接收到的DHCP OFFER消息中選擇IP地址,並發送DHCP REQUEST有限廣播到所有的DHCP服務器,表明它接受提供的內容。(0.0.0.0:68 → 255.255.255.255:67)
- DHCP ACK:DHCP服務器確認租約。(192.168.1.1:67 → 255.255.255.255:68)
Android系統中為DHCP創建的協議族為IPPROTO_UDP的Socket:
// DhcpClient.javaprivate 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: 68 Os.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來進行雙向通信的,這里不再贅述。
注網流程圖:
網絡屬性設置到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個網絡相關的廣播比較重要:
Class | Broadcast | Description |
---|---|---|
WifiManager.java | android.net.wifi.WIFI_STATE_CHANGED | WiFi開關狀態的改變(打開、打開中、關閉、關閉中) |
WifiManager.java | android.net.wifi.STATE_CHANGE | WiFi連接狀態的改變(已連接、連接中、已斷開、斷開中),“已連接” 不代表此時可以上網。 |
ConnectivityManager.java | android.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.javaprivate 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";
網絡有效性檢測的原理:
- DNS驗證:使用"Network.getAllByName(host)"進行DNS解析,成功則驗證通過,拋出"UnknownHostException"異常則說明驗證失敗。
- 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",無法上網
網絡有效性檢測的主要流程: (下圖描述了某個需要二次登錄認證網絡的有效性檢測過程)
3.2 評分機制
CS中注冊的網絡可能不只一種,同時,CS也能夠向Data和WiFi提供的NetworkFactory請求鏈路網絡。多種網絡共存時,就存在優先選擇的問題,CS通過分數統計的方式來進行網絡擇優。評分的影響因素有:
- 鏈路網絡存在初始分數:WiFi默認為60分,Data默認為50分
- 鏈路網絡根據信號衰減rssi更新初始分數
- 用戶強行選擇的網絡(不可上網但用戶主動連接)默認100分
- 網絡是否可以上網,不可上網則減去40分
// NetworkAgentInfo.javaprivate 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:
- score > 50:保持使用WiFi,如果Data連接着且沒有"針對性"(NetworkRequest中存在"TRANSPORT_CELLULAR"選項)請求,則斷開Data網絡
- 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的工作流程:
(圖片來自: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這兩個入口和出口設置數據包檢測?
A:一方面,這兩個階段處於網絡層(IP層)協議棧中,這時候不會拆解TCP/UDP等傳輸層協議的頭部信息,如果需要對更上層協議內容(如端口等)進行過濾,在這兩個階段顯然不行;
另一方面,這兩個階段協議棧不知道這個數據包是需要轉發給誰,是轉發到下一跳還是傳遞給上層協議棧,如果是需要傳遞給上層應用,就更不知道需要傳遞給哪個應用了。但這些信息在LOCAL_IN和LOCAL_OUT這兩個階段是明確的(明確了傳輸層協議類型、源IP/目的IP、源端口/目的端口,確定了一條連接),這樣過濾應用的報文就成為了可能。
Netfilter主要有3個模塊和3張表:
- 包過濾子模塊:對應filter表,能夠對數據包進行過濾,DROP/REJECT/RETURN/ACCEPT
- NAT子模塊:對應nat表,能夠實現網絡地址轉換(這個在運營商服務主機中很常用,路由器中其實也運用了該功能,如你手機的外網IP是120.52.148.57,但內網IP是192.168.1.100,這個時候就需要進行網絡地址轉換)
- 數據報修改和跟蹤模塊:對應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 ip6tableslrwxr-xr-x root shell u:object_r:system_file:s0 ip6tables-restore -> ip6tableslrwxr-xr-x root shell u:object_r:system_file:s0 ip6tables-save -> ip6tables-rwxr-xr-x root shell u:object_r:system_file:s0 iptableslrwxr-xr-x root shell u:object_r:system_file:s0 iptables-restore -> iptableslrwxr-xr-x root shell u:object_r:system_file:s0 iptables-save -> iptables
iptables和Netfilter通信使用的是sockopt的系統調用方式,通過setsockopt和getsockopt在參數中傳遞對應命令值來進行修改和查詢:
(圖片來自: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.cstruct 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 -LChain INPUT (policy ACCEPT)target prot opt source destinationbw_INPUT all -- anywhere anywherefw_INPUT all -- anywhere anywheretc_limiter all -- anywhere anywhereChain FORWARD (policy ACCEPT)target prot opt source destinationoem_fwd all -- anywhere anywherefw_FORWARD all -- anywhere anywherebw_FORWARD all -- anywhere anywherenatctrl_FORWARD all -- anywhere anywhereChain OUTPUT (policy ACCEPT)target prot opt source destinationwmsctrl_OUTPUT tcp -- anywhere anywhereDROP 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 anywherefw_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 netdroot 9071 569 13040 2788 0 0 S iptables-restore
4.2 前后台網絡策略
前面介紹NetworkPolicyManagerService時提到了一個mUidFirewallStandbyRules數組名單,這里面緩存了后台需要限制上網的uid黑名單。
NameList | Description |
---|---|
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_managementUID firewall standby chain enabled: trueUID 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_standbyChain fw_standby (2 references)target prot opt source destinationDROP all -- anywhere anywhere owner UID match u0_a55DROP all -- anywhere anywhere owner UID match u0_a104DROP all -- anywhere anywhere owner UID match u0_a108DROP all -- anywhere anywhere owner UID match u0_a111DROP all -- anywhere anywhere owner UID match u0_a116DROP all -- anywhere anywhere owner UID match u0_a123DROP all -- anywhere anywhere owner UID match u0_a125DROP all -- anywhere anywhere owner UID match u0_a126DROP all -- anywhere anywhere owner UID match u0_a127RETURN 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_dozableChain fw_dozable (0 references)target prot opt source destinationRETURN all -- anywhere anywhere owner UID match 0-9999RETURN all -- anywhere anywhere owner UID match radioRETURN all -- anywhere anywhere owner UID match finddeviceRETURN all -- anywhere anywhere owner UID match u0_a0RETURN all -- anywhere anywhere owner UID match u0_a1RETURN all -- anywhere anywhere owner UID match u0_a2RETURN all -- anywhere anywhere owner UID match u0_a3RETURN all -- anywhere anywhere owner UID match u0_a4RETURN 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 destinationRETURN all -- anywhere anywhere owner UID match 0-9999RETURN all -- anywhere anywhere owner UID match radioRETURN all -- anywhere anywhere owner UID match finddeviceRETURN all -- anywhere anywhere owner UID match u0_a0RETURN all -- anywhere anywhere owner UID match u0_a1RETURN all -- anywhere anywhere owner UID match u0_a2RETURN 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 deepNow forced in to deep idle mode
五. 無法上網原因
最后,簡單羅列幾種可能導致無法上網的原因:
- WiFi網絡未驗證(portal網絡),訪問時路由器會重定向到二次登錄網址
- 運營商服務器或代理服務器問題,無法連接到外網
- DNS服務器問題,導致DNS解析失敗
- 系統時間不正常,導致證書失效,SSL/TLS握手失敗,HTTPS無法上網
- TCP連接長時間無數據收發,達到NAT超時時間,網絡運營商切斷TCP連接,導致長連接失效(push心跳間隔應小於NAT超時時間)
- 應用進入了后台且在mUidFirewallStandbyRules黑名單中,數據包被DROP
- 系統進入省電模式且應用不在mUidFirewallPowerSaveRules白名單中,數據包被DROP
- 系統進入Doze且應用不在mUidFirewallDozableRules白名單中,數據包被DROP