天天看點

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 系統網絡架構一. 基本結構 二. 注網過程 三. 網絡優選/評分 四. 網絡政策/防火牆五. 無法上網原因

各個狀态的描述:

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,

                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 系統網絡架構一. 基本結構 二. 注網過程 三. 網絡優選/評分 四. 網絡政策/防火牆五. 無法上網原因

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之間的資料流向圖:

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等。

 [email protected]:/ # 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 [email protected]:wpa_wlan0

    interface [email protected]::ISupplicant default

    interface [email protected]::ISupplicant default

    socket wpa_wlan0 dgram 660 wifi wifi

    class main

    disabled

    oneshot

wpa_supplicant和Framework通信:

Android 系統網絡架構一. 基本結構 二. 注網過程 三. 網絡優選/評分 四. 網絡政策/防火牆五. 無法上網原因

二. 注網過程

Android系統網絡注冊過程很複雜,涉及到的子產品也非常多。主要可以分為以下幾個步驟:

WiFi熱點掃描,擷取掃描結果

配置WiFi驗證資訊,已配置完可忽略

資料鍊路層L2連接配接(包含Associate、FourWay-Handshake、Group-Handshake等過程)

DHCP通過UDP的方式擷取IP、Gateway、DNS等網絡資訊

配置Interafce、IP、DNS、Router到netd

2.1 WiFi鍊路連接配接

以自動連接配接為例:

掃描流程:

Android 系統網絡架構一. 基本結構 二. 注網過程 三. 網絡優選/評分 四. 網絡政策/防火牆五. 無法上網原因

在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 系統網絡架構一. 基本結構 二. 注網過程 三. 網絡優選/評分 四. 網絡政策/防火牆五. 無法上網原因

Android WiFi的連接配接過程主要分為 鍊路連接配接 和 DHCP擷取IP 兩個過程。(如果使用的是靜态IP則不需要進行DHCP)

WiFi鍊路連接配接:

Android 系統網絡架構一. 基本結構 二. 注網過程 三. 網絡優選/評分 四. 網絡政策/防火牆五. 無法上網原因

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

Android 系統網絡架構一. 基本結構 二. 注網過程 三. 網絡優選/評分 四. 網絡政策/防火牆五. 無法上網原因

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.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來進行雙向通信的,這裡不再贅述。

注網流程圖:

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個網絡相關的廣播比較重要:

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";

網絡有效性檢測的原理:

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

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

Android 系統網絡架構一. 基本結構 二. 注網過程 三. 網絡優選/評分 四. 網絡政策/防火牆五. 無法上網原因

3.2 評分機制

CS中注冊的網絡可能不隻一種,同時,CS也能夠向Data和WiFi提供的NetworkFactory請求鍊路網絡。多種網絡共存時,就存在優先選擇的問題,CS通過分數統計的方式來進行網絡擇優。評分的影響因素有:

鍊路網絡存在初始分數:WiFi預設為60分,Data預設為50分

鍊路網絡根據信号衰減rssi更新初始分數

使用者強行選擇的網絡(不可上網但使用者主動連接配接)預設100分

網絡是否可以上網,不可上網則減去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:

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

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張表:

包過濾子子產品:對應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目錄下:

[email protected]:/ # 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

#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;

        }

        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);

        if (errno == EAGAIN)

                goto retry;

        return NULL;

}

當然,native層并不需要這麼複雜的去操作ip tables,這些都已經被iptables工具封裝好了。系統中如netd這些native程序甚至我們在root shell下使用iptables指令就可以操作,如使用"iptables -t filter -L"檢視filter表資訊:

[email protected]:/ # 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       udp  --  anywhere             anywhere             udp dpt:1900

DROP       udp  --  anywhere             anywhere             udp dpt:1900

DROP       udp  --  anywhere             anywhere             udp dpt:1900

DROP       udp  --  anywhere             anywhere             udp dpt:1900

DROP       udp  --  anywhere             anywhere             udp dpt:1900

DROP       udp  --  anywhere             anywhere             udp dpt:1900

DROP       udp  --  anywhere             anywhere             udp dpt:1900

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名單:

[email protected]:/ # 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名單保持一緻:

[email protected]:/ # 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):

[email protected]:/ # 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模式

[email protected]:/ # dumpsys deviceidle force-idle deep

Now 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

--------------------- 

作者:ulangch 

來源:CSDN 

原文:https://blog.csdn.net/qq_14978113/article/details/89182253 

版權聲明:本文為部落客原創文章,轉載請附上博文連結!

引言:這篇文章以WiFi舉例,介紹了Android系統網絡架構。其内容包含:網絡鍊路的連接配接和注冊、網絡有效性檢測和網絡優選、Android系統網絡防火牆和幾種場景下的網絡政策等,文章的最後也列舉了幾種常見的無法上網原因供大家參考。