深入理解Android系統網絡架構

引言:這篇文章以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: 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=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: true
Power save whitelist app ids:
  UID=1000: true
  UID=1001: true
  UID=2000: true
  UID=10013: true
  UID=10021: true
Default 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系統網絡註冊過程很複雜,涉及到的模塊也非常多。主要可以分爲以下幾個步驟:

  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系統中,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 WiFi的連接過程主要分爲 鏈路連接 和 DHCP獲取IP 兩個過程。(如果使用的是靜態IP則不需要進行DHCP)

WiFi鏈路連接:

(圖片來自: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流程:

  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: 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.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",無法上網

網絡有效性檢測的主要流程: (下圖描述了某個需要二次登錄認證網絡的有效性檢測過程)

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的工作流程:

(圖片來自: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在參數中傳遞對應命令值來進行修改和查詢:

(圖片來自: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             anywhere

Chain 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             anywhere

Chain 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黑名單。

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_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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章