Skip to content

Chapter 35: Networking and Connectivity

Android's networking stack is one of the most sophisticated subsystems in AOSP, spanning from high-level Java framework APIs down through native daemons and into the Linux kernel's networking primitives. This chapter traces the complete path a network packet takes, examines the key services and modules that manage connectivity, and explores how Android handles everything from Wi-Fi association to DNS resolution to VPN tunneling.


35.1 Networking Architecture Overview

35.1.1 The Big Picture

Android networking is organized in layers that mirror the classic operating-system model but with Android-specific additions for modularity, security, and updatability. At the highest level, applications use APIs like ConnectivityManager and WifiManager. These APIs communicate via Binder IPC to system services running inside system_server. Those services, in turn, talk to native daemons (netd, the DNS resolver) and the Linux kernel through Netlink sockets, iptables/nftables commands, and BPF programs.

graph TD
    subgraph "Application Layer"
        APP["Application"]
        CM["ConnectivityManager"]
        WM["WifiManager"]
    end

    subgraph "Framework Layer (system_server)"
        CS["ConnectivityService"]
        WS["WifiService"]
        TS["TetheringService"]
        VS["VpnService"]
    end

    subgraph "Network Providers"
        NA_WIFI["Wi-Fi NetworkAgent"]
        NA_CELL["Cellular NetworkAgent"]
        NA_ETH["Ethernet NetworkAgent"]
        NA_VPN["VPN NetworkAgent"]
    end

    subgraph "Native Layer"
        NETD["netd"]
        DNSR["DnsResolver"]
        WPA["wpa_supplicant"]
    end

    subgraph "Kernel Layer"
        NF["Netfilter / nftables"]
        TC["Traffic Control"]
        BPF["eBPF Programs"]
        NETLINK["Netlink"]
        DRIVER["Network Drivers"]
    end

    APP --> CM
    APP --> WM
    CM -->|Binder| CS
    WM -->|Binder| WS
    CS <-->|Binder| NA_WIFI
    CS <-->|Binder| NA_CELL
    CS <-->|Binder| NA_ETH
    CS <-->|Binder| NA_VPN
    WS --> WPA
    CS -->|Binder| NETD
    CS -->|Binder| DNSR
    NETD --> NF
    NETD --> TC
    NETD --> BPF
    NETD --> NETLINK
    NF --> DRIVER
    TC --> DRIVER
    DRIVER -->|"Physical/Radio"| EXT["External Network"]

35.1.2 Key Components Summary

Component Type Location Role
ConnectivityService Java system service packages/modules/Connectivity/service/ Central network management
NetworkAgent Java framework class packages/modules/Connectivity/framework/ Bearer-to-CS communication
NetworkFactory Java framework class packages/modules/Connectivity/staticlibs/ Creates NetworkAgents
netd Native daemon (C++) system/netd/ Kernel network configuration
DnsResolver Native module (C++) packages/modules/DnsResolver/ DNS resolution, DoT/DoH
Wi-Fi Service Java system service packages/modules/Wifi/service/ Wi-Fi management
NetworkStack Mainline module packages/modules/NetworkStack/ DHCP, network validation
Tethering Mainline module packages/modules/Connectivity/Tethering/ USB/Wi-Fi/BT tethering

35.1.3 Mainline Modularization

Starting with Android 10 (API 29), Google began extracting networking components into independently updatable Mainline modules. This was a pivotal architectural decision: it decoupled critical networking code from the slower platform OTA cadence, allowing Google to push security patches and feature updates through the Play Store.

The key networking Mainline modules are:

  1. Connectivity module (packages/modules/Connectivity/): Contains ConnectivityService, the tethering subsystem, and related framework code.
  2. NetworkStack module (packages/modules/NetworkStack/): Handles DHCP client, network validation (captive portal detection), and IP provisioning.
  3. Wi-Fi module (packages/modules/Wifi/): The entire Wi-Fi subsystem including WifiService, ClientModeImpl, and scanning logic.
  4. DnsResolver module (packages/modules/DnsResolver/): The native DNS resolver with DoT and DoH support.

Each module ships as an APEX package, providing a self-contained update unit with its own versioning, signing, and rollback capability.

Android 17 pushes this trend further by carrying native networking binaries inside the modules themselves rather than only on the read-only system and vendor partitions. Two examples documented later in this chapter are the mainline supplicant (/apex/com.android.wifi/bin/wpa_supplicant_mainline, a wpa_supplicant build shipped inside the Wi-Fi APEX, Section 35.27) and the multi-proxy PAC handler services that the Connectivity APEX binds to as APEX-resident apps (Section 35.26). Both let Google update security-sensitive networking code through Play System Updates without an OEM build.

35.1.4 Network IDs and Routing

Every active network in Android is assigned a unique network ID (netId), an integer in the range 100--65535. This ID is fundamental: it ties together routes, DNS configuration, iptables rules, and socket binding. When an application opens a socket, the kernel uses the netId (applied via an fwmark on the socket) to select the correct routing table.

From system/netd/server/NetworkController.cpp:

// Keep these in sync with ConnectivityService.java.
const unsigned MIN_NET_ID = 100;
const unsigned MAX_NET_ID = 65535;

The framework manages netId allocation through NetIdManager:

// Source: packages/modules/Connectivity/service/src/com/android/server/NetIdManager.java

35.1.5 The Data Path: From App to Wire

When an application sends data, the following sequence occurs:

sequenceDiagram
    participant App as Application
    participant Socket as Socket Layer
    participant Kernel as Linux Kernel
    participant BPF as eBPF Programs
    participant NF as Netfilter
    participant Driver as Network Driver

    App->>Socket: write(fd, data)
    Socket->>Kernel: Socket marked with fwmark (netId + permission)
    Kernel->>BPF: Evaluate cgroup/eBPF programs
    Note over BPF: UID-based traffic accounting<br/>Bandwidth metering<br/>Firewall rules
    BPF->>NF: Pass to iptables chains
    Note over NF: bw_OUTPUT (bandwidth)<br/>fw_OUTPUT (firewall)<br/>NAT (tethering)
    NF->>Kernel: Route lookup via netId routing table
    Kernel->>Driver: Transmit packet
    Driver-->>App: (async) Completion

The fwmark mechanism is central to Android's per-network routing. Each socket is tagged with a 32-bit mark whose bit layout is defined by the Fwmark union in system/netd/include/Fwmark.h:

  • The network ID (bits 0--15)
  • Whether the network was explicitly selected (bit 16)
  • Whether the socket is protected from the VPN (bit 17)
  • Permission bits (bits 18--19)

The FwmarkServer in netd is responsible for applying these marks. It is a SocketListener on a UNIX domain socket: clients hand it a socket file descriptor, and it stamps the computed mark onto that socket with setsockopt(SOL_SOCKET, SO_MARK, ...) (it is not a BPF program or a cgroup hook):

// Source: system/netd/server/FwmarkServer.cpp:314-319
    fwmark.permission = permission;

    if (setsockopt(*socketFd, SOL_SOCKET, SO_MARK, &fwmark.intValue,
                   sizeof(fwmark.intValue)) == -1) {
        return -errno;
    }

35.2 ConnectivityService

35.2.1 Overview

ConnectivityService is the central nervous system of Android networking. At 16,000+ lines of Java code, it is one of the largest and most critical services in system_server. It manages the lifecycle of all networks, satisfies application network requests, handles network scoring and selection, and coordinates with native daemons for routing and DNS configuration.

Source file: packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java

// Source: packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java
@TargetApi(Build.VERSION_CODES.S)
public class ConnectivityService extends IConnectivityManager.Stub
        implements BroadcastReceiveHelper.Delegate {
    private static final String TAG = ConnectivityService.class.getSimpleName();
    // ...

    // Default URL for captive portal detection
    private static final String DEFAULT_CAPTIVE_PORTAL_HTTP_URL =
            "http://connectivitycheck.gstatic.com/generate_204";

    // How long to wait before switching back to a radio's default network
    private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000;

    // Default to 30s linger time-out, and 5s for nascent network
    private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
    private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
    private static final int DEFAULT_NASCENT_DELAY_MS = 5_000;

    // The maximum number of network requests allowed per uid
    static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
    // ...
}

35.2.2 The Handler Thread Model

ConnectivityService processes nearly all of its work on a single handler thread. This is deliberate: a single-threaded model eliminates the need for complex locking across the many data structures that track networks, requests, and callbacks. Messages are dispatched through an internal InternalHandler that processes events such as:

  • Network agent registration and unregistration
  • Network capability changes
  • Link property updates
  • Network score changes
  • Validation results from NetworkMonitor
  • Application network requests and callbacks
graph LR
    subgraph "External Threads"
        BINDER["Binder Threads<br/>(App requests)"]
        AGENTS["NetworkAgent<br/>Messages"]
        MONITOR["NetworkMonitor<br/>Callbacks"]
    end

    subgraph "ConnectivityService Handler Thread"
        HANDLER["InternalHandler"]
        REMATCH["rematchAllNetworksAndRequests()"]
        NOTIFY["notifyNetworkCallbacks()"]
        NETD_CMD["Configure netd"]
    end

    BINDER -->|"post to handler"| HANDLER
    AGENTS -->|"post to handler"| HANDLER
    MONITOR -->|"post to handler"| HANDLER
    HANDLER --> REMATCH
    HANDLER --> NOTIFY
    HANDLER --> NETD_CMD

35.2.3 NetworkAgent

NetworkAgent is the bridge between a network transport (Wi-Fi, cellular, Ethernet, VPN) and ConnectivityService. Each active network connection is represented by exactly one NetworkAgent instance. The agent communicates bidirectionally with ConnectivityService through an asynchronous message channel.

Source file: packages/modules/Connectivity/framework/src/android/net/NetworkAgent.java

// Source: packages/modules/Connectivity/framework/src/android/net/NetworkAgent.java
@SystemApi
public abstract class NetworkAgent {
    @Nullable
    private volatile Network mNetwork;

    @Nullable
    private volatile INetworkAgentRegistry mRegistry;

    private final Handler mHandler;

    public static final int MIN_LINGER_TIMER_MS = 2000;

    // Message constants for communication with ConnectivityService
    public static final int CMD_SUSPECT_BAD = BASE;
    public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1;
    public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2;
    public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3;
    public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4;
    public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7;
    public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11;
    public static final int CMD_STOP_SOCKET_KEEPALIVE = BASE + 12;
    // ...
}

The lifecycle of a NetworkAgent is:

stateDiagram-v2
    [*] --> Created: new NetworkAgent
    Created --> Registered: register
    Registered --> Connecting: Agent sends capabilities
    Connecting --> Connected: markConnected
    Connected --> Connected: Update caps/LP/score
    Connected --> Lingering: No more requests
    Lingering --> Connected: New request matches
    Lingering --> Disconnected: Linger timeout
    Connected --> Disconnected: unregister
    Disconnected --> [*]

Key methods a transport must implement:

Method When Called Purpose
onNetworkUnwanted() CS no longer needs the network Transport should disconnect
onBandwidthUpdateRequested() CS needs updated throughput Transport should refresh
onValidationStatus() Network validated or failed Transport may adjust behavior
onSignalStrengthThresholdsUpdated() Thresholds changed Adjust signal monitoring
onStartSocketKeepalive() App requests keepalive Offload to hardware if possible
onStopSocketKeepalive() Keepalive no longer needed Stop hardware offload
onSaveAcceptUnvalidated() User accepts unvalidated Remember preference

35.2.4 NetworkFactory

While NetworkAgent represents an active network, NetworkFactory represents the capability to create networks. Each transport registers a factory with ConnectivityService, declaring what kinds of networks it can provide.

Source file: packages/modules/Connectivity/staticlibs/device/android/net/NetworkFactory.java

// Source: packages/modules/Connectivity/staticlibs/device/android/net/NetworkFactory.java
public class NetworkFactory {
    static final boolean DBG = true;

    final NetworkFactoryShim mImpl;

    public NetworkFactory(Looper looper, Context context, String logTag,
            @Nullable final NetworkCapabilities filter) {
        LOG_TAG = logTag;
        if (isAtLeastS()) {
            mImpl = new NetworkFactoryImpl(this, looper, context, filter);
        } else {
            mImpl = new NetworkFactoryLegacyImpl(this, looper, context, filter);
        }
    }

    public static final int CMD_REQUEST_NETWORK = 1;
    public static final int CMD_CANCEL_REQUEST = 2;
    // ...
}

When an application files a NetworkRequest, ConnectivityService evaluates all registered factories. If a factory's declared capabilities match the request, ConnectivityService sends it a CMD_REQUEST_NETWORK message. The factory then decides whether to bring up a new network (create a NetworkAgent) or ignore the request.

sequenceDiagram
    participant App as Application
    participant CS as ConnectivityService
    participant WF as WifiNetworkFactory
    participant CF as CellularNetworkFactory
    participant WA as Wi-Fi NetworkAgent

    App->>CS: requestNetwork(request)
    CS->>WF: CMD_REQUEST_NETWORK
    CS->>CF: CMD_REQUEST_NETWORK
    WF->>WA: Create and register agent
    WA->>CS: register()
    CS->>CS: rematchAllNetworksAndRequests()
    CS->>App: onAvailable(network)

35.2.5 NetworkRequest and NetworkCapabilities

Applications express their networking requirements through NetworkRequest objects, which wrap NetworkCapabilities constraints.

Source file: packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java

A NetworkRequest specifies:

  • Required capabilities: What the network must provide (e.g., NET_CAPABILITY_INTERNET)
  • Forbidden capabilities: What the network must not have (e.g., NET_CAPABILITY_NOT_METERED forbidden means metered is OK)
  • Transport types: Which bearers are acceptable (Wi-Fi, cellular, etc.)
  • Network specifier: For targeting specific networks (e.g., a particular Wi-Fi SSID)

NetworkCapabilities is the richest descriptor in the system, encoding dozens of attributes about a network:

Source file: packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java

Key capability constants include:

Capability Meaning
NET_CAPABILITY_INTERNET Network has general Internet access
NET_CAPABILITY_VALIDATED System confirmed Internet connectivity
NET_CAPABILITY_NOT_METERED Network does not bill by usage
NET_CAPABILITY_NOT_VPN Network is not a VPN
NET_CAPABILITY_NOT_ROAMING Not on a roaming network
NET_CAPABILITY_NOT_CONGESTED Network is not congested
NET_CAPABILITY_NOT_SUSPENDED Network is not suspended
NET_CAPABILITY_CAPTIVE_PORTAL Behind a captive portal
NET_CAPABILITY_PARTIAL_CONNECTIVITY Limited connectivity
NET_CAPABILITY_MMS MMS capable
NET_CAPABILITY_ENTERPRISE Enterprise network
NET_CAPABILITY_LOCAL_NETWORK Local network (e.g., Thread)

Transport types include:

Transport Description
TRANSPORT_CELLULAR Mobile data (LTE, 5G)
TRANSPORT_WIFI Wi-Fi
TRANSPORT_BLUETOOTH Bluetooth PAN
TRANSPORT_ETHERNET Wired Ethernet
TRANSPORT_VPN Virtual Private Network
TRANSPORT_WIFI_AWARE Wi-Fi Aware (NAN)
TRANSPORT_LOWPAN Low-power WAN (LoWPAN)
TRANSPORT_TEST Test networks
TRANSPORT_SATELLITE Satellite connectivity
TRANSPORT_THREAD Thread mesh networking

35.2.6 Network Scoring and Selection

When multiple networks can satisfy a request, ConnectivityService must choose the best one. The network selection algorithm has evolved significantly over Android's history:

  1. Legacy scoring (pre-Android 12): Simple integer scores. Higher wins. Wi-Fi defaulted to 60, cellular to 50.

  2. Modern scoring (Android 12+): A policy-based NetworkScore that encodes multiple dimensions:

// From NetworkAgent.java
public static final int WIFI_BASE_SCORE = 60;

The rematchAllNetworksAndRequests() method is the heart of network selection. It runs on every significant network change and iterates through all active requests, finding the best network for each:

flowchart TD
    TRIGGER["Trigger: Network change<br/>(connect, disconnect, score change,<br/>capability change)"]
    REMATCH["rematchAllNetworksAndRequests()"]
    ITERATE["For each NetworkRequest"]
    FIND["Find best satisfying network"]
    CHECK_CAPS["Network capabilities<br/>satisfy request?"]
    CHECK_SCORE["Better score than<br/>current satisfier?"]
    ASSIGN["Assign network to request"]
    NOTIFY["Notify app callbacks"]
    LINGER["Start linger timer on<br/>previous network if unneeded"]

    TRIGGER --> REMATCH
    REMATCH --> ITERATE
    ITERATE --> FIND
    FIND --> CHECK_CAPS
    CHECK_CAPS -->|Yes| CHECK_SCORE
    CHECK_CAPS -->|No| ITERATE
    CHECK_SCORE -->|Yes| ASSIGN
    CHECK_SCORE -->|No| ITERATE
    ASSIGN --> NOTIFY
    ASSIGN --> LINGER
    LINGER --> ITERATE

The scoring considers multiple policies:

  • Transport primary: Prefers the transport's primary network
  • Validated over unvalidated: Prefers networks that passed validation
  • Metered vs unmetered: Prefers unmetered when available
  • User preference: Respects user network selection
  • VPN: VPNs are handled specially with their own scoring rules

35.2.7 LinkProperties

LinkProperties describes the IP-level configuration of a network:

  • IP addresses (both IPv4 and IPv6)
  • DNS servers
  • Routing table entries
  • Interface name (e.g., wlan0, rmnet0)
  • MTU
  • HTTP proxy settings
  • NAT64 prefix (for IPv6-only networks)

When a NetworkAgent updates its LinkProperties, ConnectivityService pushes the corresponding routes and DNS configuration to netd:

sequenceDiagram
    participant NA as NetworkAgent
    participant CS as ConnectivityService
    participant NETD as netd
    participant DNSR as DnsResolver
    participant KERNEL as Kernel

    NA->>CS: sendLinkProperties(lp)
    CS->>CS: Compare with previous LP
    CS->>NETD: networkAddRoute(netId, route)
    CS->>NETD: networkSetDefault(netId)
    CS->>DNSR: setResolverConfiguration(netId, servers)
    NETD->>KERNEL: RTM_NEWROUTE (Netlink)
    NETD->>KERNEL: ip rule add fwmark (routing policy)

35.2.8 Network Lifecycle Events and Callbacks

Applications receive network state changes through registered callbacks. ConnectivityService supports a rich set of callback events:

// From ConnectivityService.java import block
import static android.net.ConnectivityManager.CALLBACK_AVAILABLE;
import static android.net.ConnectivityManager.CALLBACK_BLK_CHANGED;
import static android.net.ConnectivityManager.CALLBACK_CAP_CHANGED;
import static android.net.ConnectivityManager.CALLBACK_IP_CHANGED;
import static android.net.ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED;
import static android.net.ConnectivityManager.CALLBACK_LOSING;
import static android.net.ConnectivityManager.CALLBACK_LOST;
import static android.net.ConnectivityManager.CALLBACK_PRECHECK;
import static android.net.ConnectivityManager.CALLBACK_SUSPENDED;
import static android.net.ConnectivityManager.CALLBACK_RESUMED;

The callback lifecycle for a typical network connection:

sequenceDiagram
    participant App as Application
    participant CS as ConnectivityService
    participant Net as Network

    App->>CS: registerNetworkCallback(request, callback)
    Note over CS: Network connects and validates
    CS->>App: onAvailable(network)
    CS->>App: onCapabilitiesChanged(network, caps)
    CS->>App: onLinkPropertiesChanged(network, lp)
    Note over CS: Network quality degrades
    CS->>App: onCapabilitiesChanged(network, caps)
    Note over CS: Better network appears
    CS->>App: onLosing(oldNetwork, maxMs)
    CS->>App: onAvailable(newNetwork)
    Note over CS: Old network lingers, then disconnects
    CS->>App: onLost(oldNetwork)

35.2.9 BPF-Based Traffic Control

Modern Android increasingly uses eBPF (extended Berkeley Packet Filter) programs for traffic control, replacing traditional iptables rules. BPF programs are attached to cgroup hooks to enforce per-UID traffic policies.

// Source: packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java
// BPF program attachment points
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_GETSOCKOPT;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET4_BIND;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET4_CONNECT;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET6_BIND;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET6_CONNECT;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_EGRESS;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_INGRESS;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_SOCK_CREATE;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_SOCK_RELEASE;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_SETSOCKOPT;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP4_RECVMSG;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP4_SENDMSG;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP6_RECVMSG;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP6_SENDMSG;

These BPF programs provide:

  • UID-based accounting: Track bytes sent/received per UID
  • Firewall enforcement: Block/allow traffic per UID and chain
  • Socket marking: Apply fwmarks at socket creation time
  • Data saver: Restrict background data for metered networks
  • Bandwidth control: Enforce per-interface quotas

The BpfNetMaps class in the Connectivity module manages these maps, replacing many of the traditional iptables-based mechanisms:

// Source: packages/modules/Connectivity/service/src/com/android/server/BpfNetMaps.java

35.2.10 Frozen App Handling

ConnectivityService has sophisticated handling for frozen (cached) applications. When an app is frozen by the ActivityManager, its network callbacks are queued rather than delivered, avoiding unnecessary wake-ups:

// Source: ConnectivityService.java import block
import static com.android.server.connectivity.ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS;

When the app is unfrozen, queued callbacks are delivered in order, ensuring the app has an accurate view of the current network state.


35.3 Wi-Fi Framework

35.3.1 Architecture Overview

The Wi-Fi framework in AOSP is a complex subsystem that manages Wi-Fi radio operations, network scanning, connection management, SoftAP (hotspot), Wi-Fi Direct (P2P), and Wi-Fi Aware (NAN). Since Android 12, the entire Wi-Fi stack ships as a Mainline module.

Module root: packages/modules/Wifi/

graph TD
    subgraph "Application Layer"
        WIFIMGR["WifiManager API"]
        P2PMGR["WifiP2pManager API"]
    end

    subgraph "Wi-Fi Service (system_server)"
        WIFISVC["WifiServiceImpl"]
        AMWM["ActiveModeWarden"]
        CMM["ConcreteClientModeManager"]
        CMI["ClientModeImpl"]
        SAM["SoftApManager"]
        WFACT["WifiNetworkFactory"]
        WSEL["WifiNetworkSelector"]
        WCFG["WifiConfigManager"]
    end

    subgraph "HAL Layer"
        WNATIVE["WifiNative"]
        HALDEV["HalDeviceManager"]
        SUPPLICANT["SupplicantStaIfaceHal"]
        HOSTAPD["HostapdHal"]
        WCHIP["WifiChip (AIDL HAL)"]
    end

    subgraph "Native Layer"
        WPA["wpa_supplicant"]
        HAPD["hostapd"]
    end

    subgraph "Kernel"
        NL80211["nl80211 / cfg80211"]
        WDRIVER["Wi-Fi Driver"]
        FIRMWARE["Wi-Fi Firmware"]
    end

    WIFIMGR -->|Binder| WIFISVC
    P2PMGR -->|Binder| WIFISVC
    WIFISVC --> AMWM
    AMWM --> CMM
    AMWM --> SAM
    CMM --> CMI
    CMI --> WNATIVE
    CMI --> WFACT
    CMI --> WSEL
    CMI --> WCFG
    SAM --> WNATIVE
    WNATIVE --> HALDEV
    WNATIVE --> SUPPLICANT
    WNATIVE --> HOSTAPD
    HALDEV --> WCHIP
    SUPPLICANT --> WPA
    HOSTAPD --> HAPD
    WPA --> NL80211
    HAPD --> NL80211
    NL80211 --> WDRIVER
    WDRIVER --> FIRMWARE

35.3.2 WifiServiceImpl

WifiServiceImpl is the Binder-facing service that implements IWifiManager. It handles all public API calls from applications and delegates work to internal components.

Source file: packages/modules/Wifi/service/java/com/android/server/wifi/WifiServiceImpl.java

// Source: packages/modules/Wifi/service/java/com/android/server/wifi/WifiServiceImpl.java
// WifiServiceImpl handles dozens of Wi-Fi manager APIs including:
// - Scan management (IScanResultsCallback)
// - Network suggestions (ISuggestionConnectionStatusListener)
// - SoftAP control (ISoftApCallback)
// - P2P operations
// - Traffic state monitoring (ITrafficStateCallback)
// - Verbose logging control
// - DPP (Device Provisioning Protocol)
// - TWT (Target Wake Time)

Key responsibilities include:

  • Permission enforcement (location, Wi-Fi state change, etc.)
  • API parameter validation
  • Delegation to ActiveModeWarden for mode changes
  • Broadcasting Wi-Fi state changes
  • Managing local-only hotspot requests

35.3.3 ClientModeImpl: The Wi-Fi State Machine

ClientModeImpl is the workhorse of Wi-Fi connectivity. It extends StateMachine and manages the complete lifecycle of a Wi-Fi connection: from scanning and authentication through DHCP and full connectivity.

Source file: packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java

// Source: packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java
public class ClientModeImpl extends StateMachine implements ClientMode {
    // Roles for this client mode interface
    // ROLE_CLIENT_PRIMARY - the main STA interface
    // ROLE_CLIENT_LOCAL_ONLY - local-only connection
    // ROLE_CLIENT_SECONDARY_LONG_LIVED - persistent secondary
    // ROLE_CLIENT_SECONDARY_TRANSIENT - temporary secondary (MBB)
    // ROLE_CLIENT_SCAN_ONLY - scan-only mode
    // ...
}

The state machine contains the following key states:

stateDiagram-v2
    [*] --> DefaultState
    DefaultState --> ConnectableState: Wi-Fi enabled

    state ConnectableState {
        [*] --> DisconnectedState
        DisconnectedState --> L2ConnectingState: Connect command
        L2ConnectingState --> L2ConnectedState: Association success
        L2ConnectingState --> DisconnectedState: Association failure

        state L2ConnectedState {
            [*] --> WaitBeforeL3ProvisioningState
            WaitBeforeL3ProvisioningState --> L3ProvisioningState: Ready
            L3ProvisioningState --> L3ConnectedState: DHCP success
            L3ProvisioningState --> DisconnectedState: DHCP failure

            state L3ConnectedState {
                [*] --> ConnectedState
                ConnectedState --> RoamingState: Roaming
                RoamingState --> ConnectedState: Roam complete
            }
        }
        L2ConnectedState --> DisconnectedState: Disconnect
    }

State descriptions:

State Description
DefaultState Wi-Fi is off or initializing
ConnectableState Wi-Fi is on and ready to connect
DisconnectedState Not associated with any AP
L2ConnectingState Attempting 802.11 association
L2ConnectedState Associated at L2, waiting for L3
WaitBeforeL3ProvisioningState Brief pause before IP provisioning
L3ProvisioningState Running DHCP or static IP config
L3ConnectedState Full IP connectivity established
ConnectedState Stable connected state
RoamingState Transitioning between APs

35.3.4 WifiNative: The HAL Bridge

WifiNative serves as the interface between the Java Wi-Fi framework and the native Wi-Fi HAL (Hardware Abstraction Layer). It manages hardware interfaces, delegates supplicant operations, and handles scan results.

Source file: packages/modules/Wifi/service/java/com/android/server/wifi/WifiNative.java

// Source: packages/modules/Wifi/service/java/com/android/server/wifi/WifiNative.java
// WifiNative creates and manages different interface types:
// HDM_CREATE_IFACE_STA - Station (client) interface
// HDM_CREATE_IFACE_AP - Access Point interface
// HDM_CREATE_IFACE_AP_BRIDGE - Bridged AP (dual-band)
// HDM_CREATE_IFACE_P2P - Wi-Fi Direct interface
// HDM_CREATE_IFACE_NAN - Wi-Fi Aware interface

The HAL communication path:

graph LR
    WN["WifiNative"]
    HDM["HalDeviceManager"]
    CHIP["WifiChip<br/>(AIDL HAL)"]
    SIFACE["SupplicantStaIfaceHal"]
    WPA["wpa_supplicant"]

    WN --> HDM
    WN --> SIFACE
    HDM --> CHIP
    SIFACE --> WPA

35.3.5 wpa_supplicant Integration

Android uses the industry-standard wpa_supplicant for 802.11 authentication. The SupplicantStaIfaceHal class communicates with wpa_supplicant through an AIDL interface, handling:

  • WPA/WPA2/WPA3 authentication
  • 802.1X enterprise authentication (EAP-TLS, EAP-TTLS, EAP-PEAP, EAP-SIM, etc.)
  • FILS (Fast Initial Link Setup) for reduced connection time
  • OWE (Opportunistic Wireless Encryption) for open networks
  • SAE (Simultaneous Authentication of Equals) for WPA3
  • DPP (Device Provisioning Protocol) for easy onboarding

SupplicantStaIfaceHal does not bind to one fixed transport. Its factory createStaIfaceHalMockable() in packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java picks the first available backend in a strict preference order: the AIDL mainline implementation (a supplicant binary shipped inside the Wi-Fi APEX, covered in Section 35.27), then the AIDL vendor implementation (the supplicant on the vendor partition), then the legacy HIDL implementation. Each candidate exposes an isServiceAvailable() / serviceDeclared() probe; the first that answers yes wins. The same three-tier selection exists for Wi-Fi Direct in SupplicantP2pIfaceHal.

35.3.6 Network Selection

The WifiNetworkSelector evaluates available scan results and selects the best network to connect to. It considers:

  1. Saved network priority: User-configured preferences
  2. Signal strength (RSSI): Weighted by band (2.4 GHz, 5 GHz, 6 GHz)
  3. Security type: Prefers stronger security
  4. Past performance: Historical connection quality metrics
  5. Network suggestions: App-suggested networks
  6. Enterprise policies: Device admin restrictions
  7. Blocked networks: Temporarily disabled due to failures

35.3.7 SoftAP (Mobile Hotspot)

SoftApManager handles the mobile hotspot functionality, managing the AP interface lifecycle through its own state machine.

Source file: packages/modules/Wifi/service/java/com/android/server/wifi/SoftApManager.java

// Source: packages/modules/Wifi/service/java/com/android/server/wifi/SoftApManager.java
public class SoftApManager implements ActiveModeManager {
    private static final String TAG = "SoftApManager";
    // SoftAP manages the AP mode lifecycle:
    // - Interface creation (via WifiNative/HalDeviceManager)
    // - Channel selection (considering coexistence)
    // - Client connection/disconnection tracking
    // - Bridged AP mode (dual-band simultaneous)
    // - Idle timeout management
    // ...
}

SoftAP features include:

  • Dual-band support: Bridged AP mode provides simultaneous 2.4 GHz and 5 GHz
  • Client management: Track connected clients, enforce max client limits
  • Coexistence: CoexManager handles interference with cellular bands
  • Auto-shutdown: Configurable idle timeout when no clients are connected
  • WPA3-SAE: Support for WPA3 security in hotspot mode
  • MAC randomization: Privacy-preserving MAC for AP BSSID

35.3.8 Wi-Fi Direct (P2P)

Wi-Fi Direct allows devices to connect directly without an access point. The implementation lives in packages/modules/Wifi/service/java/com/android/server/wifi/p2p/ and is managed by WifiP2pServiceImpl.

The P2P connection flow:

sequenceDiagram
    participant DevA as Device A
    participant P2PA as P2P Service A
    participant P2PB as P2P Service B
    participant DevB as Device B

    DevA->>P2PA: discoverPeers()
    P2PA->>P2PB: Probe Request/Response
    P2PA->>DevA: onPeersAvailable(peers)
    DevA->>P2PA: connect(peer)
    P2PA->>P2PB: GO Negotiation Request
    P2PB->>P2PA: GO Negotiation Response
    P2PA->>P2PB: GO Negotiation Confirm
    Note over P2PA,P2PB: Group Owner elected
    P2PA->>P2PB: Provision Discovery
    P2PB->>P2PA: WPS Exchange
    Note over P2PA,P2PB: Group formed
    P2PA->>DevA: onConnectionInfoAvailable()
    P2PB->>DevB: onConnectionInfoAvailable()

Modern AOSP includes support for Wi-Fi 7 (802.11be) features, including Multi-Link Operation. The MloLink class in the Wi-Fi framework represents individual links in an MLO connection:

// From ClientModeImpl.java imports
import android.net.wifi.MloLink;

MLO enables simultaneous transmission across multiple channels and bands, significantly improving throughput and latency.


35.4 netd (Network Daemon)

35.4.1 Overview

netd is the native daemon responsible for configuring the Linux kernel's networking subsystem on behalf of the Android framework. It runs as a privileged process and is the primary pathway for routing table manipulation, firewall rule management, bandwidth control, and interface configuration.

Source directory: system/netd/

graph TD
    subgraph "Framework (Java)"
        CS["ConnectivityService"]
        TS["TetheringService"]
        NMS["NetworkManagementService"]
    end

    subgraph "netd (C++)"
        NNS["NetdNativeService<br/>(AIDL Binder)"]
        NC["NetworkController"]
        BC["BandwidthController"]
        FC["FirewallController"]
        RC["RouteController"]
        TC_CTRL["TetherController"]
        IC["InterfaceController"]
        XC["XfrmController"]
        IT["IdletimerController"]
        WC["WakeupController"]
        SC["StrictController"]
    end

    subgraph "Kernel"
        IPTABLES["iptables / ip6tables"]
        NFTABLES["nftables"]
        NETLINK_K["Netlink Socket"]
        XFRM["IPsec / XFRM"]
        ROUTING["Routing Tables"]
    end

    CS -->|Binder IPC| NNS
    TS -->|Binder IPC| NNS
    NMS -->|Binder IPC| NNS
    NNS --> NC
    NNS --> BC
    NNS --> FC
    NNS --> RC
    NNS --> TC_CTRL
    NNS --> IC
    NNS --> XC
    NNS --> IT
    NNS --> WC
    NNS --> SC
    NC --> ROUTING
    BC --> IPTABLES
    FC --> IPTABLES
    RC --> NETLINK_K
    TC_CTRL --> IPTABLES
    TC_CTRL --> NFTABLES
    IC --> NETLINK_K
    XC --> XFRM

35.4.2 NetdNativeService: The Binder Interface

NetdNativeService exposes netd's functionality via AIDL Binder. It is the sole entry point for framework callers.

Source file: system/netd/server/NetdNativeService.h

// Source: system/netd/server/NetdNativeService.h
class NetdNativeService : public BinderService<NetdNativeService>, public BnNetd {
  public:
    NetdNativeService();
    static status_t start();
    static char const* getServiceName() { return "netd"; }

    // Firewall commands
    binder::Status firewallReplaceUidChain(const std::string& chainName,
                                           bool isAllowlist,
                                           const std::vector<int32_t>& uids,
                                           bool* ret) override;
    binder::Status firewallSetFirewallType(int32_t firewallType) override;
    binder::Status firewallSetInterfaceRule(const std::string& ifName,
                                            int32_t firewallRule) override;
    binder::Status firewallSetUidRule(int32_t childChain, int32_t uid,
                                      int32_t firewallRule) override;

    // Bandwidth control commands
    binder::Status bandwidthEnableDataSaver(bool enable, bool *ret) override;
    binder::Status bandwidthSetInterfaceQuota(const std::string& ifName,
                                              int64_t bytes) override;
    binder::Status bandwidthSetGlobalAlert(int64_t bytes) override;
    binder::Status bandwidthAddNaughtyApp(int32_t uid) override;
    binder::Status bandwidthAddNiceApp(int32_t uid) override;

    // Network and routing commands
    binder::Status networkCreate(const NativeNetworkConfig& config) override;
    binder::Status networkDestroy(int32_t netId) override;
    binder::Status networkAddInterface(int32_t netId,
                                       const std::string& iface) override;
    binder::Status networkAddRoute(int32_t netId, const std::string& ifName,
                                   const std::string& destination,
                                   const std::string& nextHop) override;
    binder::Status networkSetDefault(int32_t netId) override;

    // Socket operations
    binder::Status socketDestroy(const std::vector<UidRangeParcel>& uids,
                                 const std::vector<int32_t>& skipUids) override;
    // ...
};

35.4.3 NetworkController: Routing and Networks

The NetworkController manages the creation and configuration of network abstractions within netd. Each network (physical, virtual, local, or unreachable) gets its own routing table and policy rules.

Source file: system/netd/server/NetworkController.cpp

// Source: system/netd/server/NetworkController.cpp
// THREAD-SAFETY
// The methods in this file are called from multiple threads (from
// CommandListener, FwmarkServer and DnsProxyListener). So, all accesses
// to shared state are guarded by a lock.

class NetworkController::DelegateImpl : public PhysicalNetwork::Delegate {
  public:
    explicit DelegateImpl(NetworkController* networkController);

    [[nodiscard]] int modifyFallthrough(unsigned vpnNetId,
                                        const std::string& physicalInterface,
                                        Permission permission, bool add);
  // ...
};

Network types in netd:

Type Class Purpose
Physical PhysicalNetwork Wi-Fi, cellular, Ethernet
Virtual VirtualNetwork VPN networks
Local LocalNetwork localhost and local interfaces
Unreachable UnreachableNetwork Block traffic (reject routes)
Dummy DummyNetwork Test/placeholder network

35.4.4 iptables Chain Architecture

netd organizes iptables rules into carefully ordered chains. The ordering is critical for correct operation and is explicitly documented in the source.

Source file: system/netd/server/Controllers.cpp

// Source: system/netd/server/Controllers.cpp
// ORDERING IS CRITICAL, AND SHOULD BE TRIPLE-CHECKED WITH EACH CHANGE.

static const std::vector<const char*> FILTER_INPUT = {
    OEM_IPTABLES_FILTER_INPUT,
    BandwidthController::LOCAL_INPUT,     // "bw_INPUT"
    FirewallController::LOCAL_INPUT,      // "fw_INPUT"
};

static const std::vector<const char*> FILTER_FORWARD = {
    OEM_IPTABLES_FILTER_FORWARD,
    FirewallController::LOCAL_FORWARD,    // "fw_FORWARD"
    BandwidthController::LOCAL_FORWARD,   // "bw_FORWARD"
    TetherController::LOCAL_FORWARD,      // tethering forwarding
};

static const std::vector<const char*> FILTER_OUTPUT = {
    OEM_IPTABLES_FILTER_OUTPUT,
    FirewallController::LOCAL_OUTPUT,     // "fw_OUTPUT"
    StrictController::LOCAL_OUTPUT,       // cleartext enforcement
    BandwidthController::LOCAL_OUTPUT,    // "bw_OUTPUT"
};

static const std::vector<const char*> RAW_PREROUTING = {
    IdletimerController::LOCAL_RAW_PREROUTING,
    BandwidthController::LOCAL_RAW_PREROUTING,
    TetherController::LOCAL_RAW_PREROUTING,
};

static const std::vector<const char*> MANGLE_POSTROUTING = {
    OEM_IPTABLES_MANGLE_POSTROUTING,
    BandwidthController::LOCAL_MANGLE_POSTROUTING,
    IdletimerController::LOCAL_MANGLE_POSTROUTING,
};

static const std::vector<const char*> MANGLE_INPUT = {
    CONNMARK_MANGLE_INPUT,
    WakeupController::LOCAL_MANGLE_INPUT,
    RouteController::LOCAL_MANGLE_INPUT,
};

The chain execution order for an incoming packet:

graph TD
    PKT["Incoming Packet"] --> RAW["raw/PREROUTING"]
    RAW --> |"idletimer<br/>bw_raw_PREROUTING<br/>tether_raw_PREROUTING"| MANGLE_PRE["mangle/PREROUTING"]
    MANGLE_PRE --> NAT_PRE["nat/PREROUTING"]
    NAT_PRE --> ROUTE["Routing Decision"]
    ROUTE -->|"Local"| MANGLE_IN["mangle/INPUT"]
    ROUTE -->|"Forward"| MANGLE_FWD["mangle/FORWARD"]

    MANGLE_IN -->|"connmark<br/>wakeup<br/>route"| FILTER_IN["filter/INPUT"]
    FILTER_IN -->|"OEM<br/>bw_INPUT<br/>fw_INPUT"| LOCAL["Local Process"]

    MANGLE_FWD --> FILTER_FWD["filter/FORWARD"]
    FILTER_FWD -->|"OEM<br/>fw_FORWARD<br/>bw_FORWARD<br/>tether"| MANGLE_POST["mangle/POSTROUTING"]
    MANGLE_POST -->|"OEM<br/>bw_mangle_POST<br/>idletimer"| NAT_POST["nat/POSTROUTING"]
    NAT_POST --> OUT["Network Interface"]

35.4.5 BandwidthController

The BandwidthController implements data usage tracking and enforcement using iptables quota rules and BPF programs.

Source file: system/netd/server/BandwidthController.cpp

// Source: system/netd/server/BandwidthController.cpp
const char BandwidthController::LOCAL_INPUT[] = "bw_INPUT";
const char BandwidthController::LOCAL_FORWARD[] = "bw_FORWARD";
const char BandwidthController::LOCAL_OUTPUT[] = "bw_OUTPUT";
const char BandwidthController::LOCAL_RAW_PREROUTING[] = "bw_raw_PREROUTING";
const char BandwidthController::LOCAL_MANGLE_POSTROUTING[] = "bw_mangle_POSTROUTING";
const char BandwidthController::LOCAL_GLOBAL_ALERT[] = "bw_global_alert";

Bandwidth control features:

  • Per-interface quotas: Limit data usage on specific interfaces
  • Global alerts: Notify when total usage exceeds a threshold
  • Naughty apps: UIDs blocked from using metered networks (data saver)
  • Nice apps: UIDs exempt from data saver restrictions
  • Shared costly chain: Global quota across all metered interfaces
// Source: system/netd/server/BandwidthController.cpp
// Comments explaining the rule structure:
//  * global quota for all costly interfaces uses a single costly chain:
//   . initial rules
//     iptables -N bw_costly_shared
//     iptables -I bw_INPUT -i iface0 -j bw_costly_shared
//     iptables -I bw_OUTPUT -o iface0 -j bw_costly_shared
//     iptables -I bw_costly_shared -m quota \! --quota 500000 \
//         -j REJECT --reject-with icmp-net-prohibited
//     iptables -A bw_costly_shared -j bw_penalty_box
//     iptables -A bw_penalty_box -j bw_happy_box

35.4.6 FirewallController

The FirewallController manages per-UID network access rules, implementing Android's firewall chains for doze mode, battery saver, and app standby.

Source file: system/netd/server/FirewallController.cpp

// Source: system/netd/server/FirewallController.cpp
const char FirewallController::TABLE[] = "filter";
const char FirewallController::LOCAL_INPUT[] = "fw_INPUT";
const char FirewallController::LOCAL_OUTPUT[] = "fw_OUTPUT";
const char FirewallController::LOCAL_FORWARD[] = "fw_FORWARD";

// ICMPv6 types that are required for any form of IPv6 connectivity to work.
const char* const FirewallController::ICMPV6_TYPES[] = {
    "packet-too-big",
    "router-solicitation",
    "router-advertisement",
    "neighbour-solicitation",
    "neighbour-advertisement",
    "redirect",
};

The firewall supports two modes:

  • Denylist (default): All traffic is allowed unless explicitly denied
  • Allowlist: All traffic is blocked unless explicitly allowed

The interface-scoped fw_INPUT/fw_OUTPUT/fw_FORWARD chains above are still iptables chains owned by FirewallController. The per-policy power-saving chains, however, are no longer iptables child chains: as §35.2.9 describes, they were moved into BPF and are now enforced by BpfNetMaps in the Connectivity module. They are identified by the FIREWALL_CHAIN_* constants (ConnectivityManager / packages/modules/Connectivity/.../BpfNetMapsConstants.java) rather than by FirewallController chain names, but the names and semantics map one-to-one:

BPF firewall chain Purpose Mode
FIREWALL_CHAIN_DOZABLE Doze mode allowlist Allowlist
FIREWALL_CHAIN_STANDBY App standby denylist Denylist
FIREWALL_CHAIN_POWERSAVE Battery saver allowlist Allowlist
FIREWALL_CHAIN_RESTRICTED Background restriction allowlist Allowlist
FIREWALL_CHAIN_LOW_POWER_STANDBY Low-power standby allowlist Allowlist
FIREWALL_CHAIN_BACKGROUND Background network access allowlist Allowlist

35.4.7 RouteController

The RouteController manages Linux routing tables and policy rules. Each network gets its own routing table, identified by the netId. Policy routing rules use fwmarks to direct packets to the correct table.

The routing architecture:

graph TD
    SOCKET["Socket with fwmark"] --> PR["Policy Routing Rules"]
    PR --> |"fwmark = netId 100"| RT100["Table 100<br/>(Wi-Fi routes)"]
    PR --> |"fwmark = netId 101"| RT101["Table 101<br/>(Cellular routes)"]
    PR --> |"fwmark = netId 102"| RT102["Table 102<br/>(VPN routes)"]
    PR --> |"no mark / default"| RTMAIN["Main Table<br/>(default network)"]

    RT100 --> IF_WLAN["wlan0"]
    RT101 --> IF_RMNET["rmnet0"]
    RT102 --> IF_TUN["tun0"]
    RTMAIN --> IF_DEFAULT["Default Interface"]

35.4.8 XfrmController: IPsec

The XfrmController manages Linux XFRM (IPsec transform) operations for VPN and other encrypted tunnel needs:

Source file: system/netd/server/XfrmController.cpp

It handles:

  • Security Association (SA) creation and deletion
  • Security Policy (SP) configuration
  • Tunnel interface management
  • ESP (Encapsulating Security Payload) configuration
  • SPI (Security Parameter Index) allocation

35.4.9 FwmarkServer

The FwmarkServer is a UNIX domain socket server within netd that handles socket tagging. When a socket is created, the C library (bionic) connects to the FwmarkServer, which applies the appropriate fwmark based on the process's UID, the default network, and any explicit network binding.

Source file: system/netd/server/FwmarkServer.cpp

This mechanism ensures that every socket is automatically routed through the correct network without application intervention.


35.5 DNS Resolver

35.5.1 Architecture

The DNS resolver runs as a module within the netd process (linked as a shared library) but is maintained as a separate Mainline module for independent updatability.

Module root: packages/modules/DnsResolver/

graph TD
    subgraph "Application"
        APP["App calls getaddrinfo()"]
    end

    subgraph "Bionic"
        BIONIC["DNS client in libc"]
    end

    subgraph "DnsResolver Module"
        DPL["DnsProxyListener<br/>(UNIX socket)"]
        RESOLV["Resolver Core"]
        CACHE["DNS Cache<br/>(per-network)"]
        DOT["DnsTlsTransport<br/>(DNS-over-TLS)"]
        DOH["DoH Engine<br/>(DNS-over-HTTPS)"]
        PDNS["PrivateDnsConfiguration"]
    end

    subgraph "External"
        DNS53["DNS Server<br/>(port 53)"]
        DNS853["DoT Server<br/>(port 853)"]
        DNS443["DoH Server<br/>(port 443)"]
    end

    APP --> BIONIC
    BIONIC -->|"UNIX socket"| DPL
    DPL --> RESOLV
    RESOLV --> CACHE
    RESOLV -->|"Plaintext"| DNS53
    RESOLV -->|"TLS"| DOT
    RESOLV -->|"HTTPS"| DOH
    DOT --> DNS853
    DOH --> DNS443
    PDNS --> DOT
    PDNS --> DOH

35.5.2 Initialization

The resolver is initialized when netd starts, through the resolv_init() function:

Source file: packages/modules/DnsResolver/DnsResolver.cpp

// Source: packages/modules/DnsResolver/DnsResolver.cpp
bool resolv_init(const ResolverNetdCallbacks* callbacks) {
    android::base::InitLogging(/*argv=*/nullptr);
    LOG(INFO) << __func__ << ": Initializing resolver";
    const bool isDebug = isDebuggable();
    resolv_set_log_severity(isDebug
        ? android::base::INFO
        : android::base::WARNING);
    doh_init_logger(isDebug
        ? DOH_LOG_LEVEL_INFO
        : DOH_LOG_LEVEL_WARN);

    using android::net::gApiLevel;
    gApiLevel = getApiLevel();
    using android::net::gResNetdCallbacks;
    gResNetdCallbacks.check_calling_permission =
        callbacks->check_calling_permission;
    gResNetdCallbacks.get_network_context =
        callbacks->get_network_context;
    gResNetdCallbacks.log = callbacks->log;
    if (gApiLevel >= 30) {
        gResNetdCallbacks.tagSocket = callbacks->tagSocket;
        gResNetdCallbacks.evaluate_domain_name =
            callbacks->evaluate_domain_name;
    }
    android::net::gDnsResolv = android::net::DnsResolver::getInstance();
    return android::net::gDnsResolv->start();
}

The DnsResolver::start() method launches two key components:

  1. DnsProxyListener: Listens for DNS queries on a UNIX domain socket
  2. DnsResolverService: AIDL Binder interface for configuration
// Source: packages/modules/DnsResolver/DnsResolver.cpp
bool DnsResolver::start() {
    if (!verifyCallbacks()) {
        LOG(ERROR) << __func__ << ": Callback verification failed";
        return false;
    }
    if (mDnsProxyListener.startListener()) {
        PLOG(ERROR) << __func__ << ": Unable to start DnsProxyListener";
        return false;
    }
    binder_status_t ret;
    if ((ret = DnsResolverService::start()) != STATUS_OK) {
        LOG(ERROR) << __func__
                   << ": Unable to start DnsResolverService: " << ret;
        return false;
    }
    return true;
}

35.5.3 DNS Query Flow

When an application calls InetAddress.getByName() or getaddrinfo(), the query follows this path:

sequenceDiagram
    participant App as Application
    participant Bionic as Bionic libc
    participant DPL as DnsProxyListener
    participant Cache as DNS Cache
    participant Private as PrivateDnsConfig
    participant DoT as DnsTlsTransport
    participant DoH as DoH Engine
    participant Server as DNS Server

    App->>Bionic: getaddrinfo("example.com")
    Bionic->>DPL: Send query via UNIX socket
    DPL->>Cache: Check cache (per-network)
    alt Cache hit
        Cache-->>DPL: Return cached result
    else Cache miss
        DPL->>Private: Check private DNS mode
        alt Private DNS enabled (DoT)
            Private->>DoT: Forward query
            DoT->>Server: TLS-encrypted query (port 853)
            Server-->>DoT: Response
            DoT-->>Private: Decrypted response
        else Private DNS enabled (DoH)
            Private->>DoH: Forward query
            DoH->>Server: HTTPS query (port 443)
            Server-->>DoH: Response
            DoH-->>Private: Decrypted response
        else Plaintext DNS
            DPL->>Server: UDP query (port 53)
            Server-->>DPL: Response
        end
        DPL->>Cache: Store result
    end
    DPL-->>Bionic: Return addresses
    Bionic-->>App: InetAddress[]

35.5.4 DNS-over-TLS (DoT)

The DnsTlsTransport class implements DNS-over-TLS (RFC 7858) for encrypted DNS queries on port 853.

Source file: packages/modules/DnsResolver/DnsTlsTransport.cpp

// Source: packages/modules/DnsResolver/DnsTlsTransport.cpp
namespace {
// Make a DNS query for the hostname
// "<random>-dnsotls-ds.metric.gstatic.com".
// This is used for DoT validation probing.
std::vector<uint8_t> makeDnsQuery() {
    static const char kDnsSafeChars[] =
            "abcdefhijklmnopqrstuvwxyz"
            "ABCDEFHIJKLMNOPQRSTUVWXYZ"
            "0123456789";
    // ... builds a DNS query with random prefix for validation
}
}  // namespace

The DoT implementation features:

  • Session caching: Reuses TLS sessions to reduce handshake overhead
  • Connection reuse: Multiplexes queries over persistent connections
  • Validation: Probes DoT servers before activating them
  • Failover: Falls back to plaintext DNS if DoT fails

Key classes in the DoT stack:

Class File Role
DnsTlsTransport DnsTlsTransport.cpp Connection management
DnsTlsSocket DnsTlsSocket.cpp TLS socket wrapper
DnsTlsDispatcher DnsTlsDispatcher.cpp Query routing
DnsTlsQueryMap DnsTlsQueryMap.cpp Query/response matching
DnsTlsSessionCache DnsTlsSessionCache.cpp TLS session reuse
DnsTlsServer DnsTlsServer.cpp Server representation

35.5.5 DNS-over-HTTPS (DoH)

DoH support was added in Android 13 and provides DNS encryption over HTTPS (RFC 8484). The DoH engine is implemented in Rust for memory safety and performance.

Source file: packages/modules/DnsResolver/PrivateDnsConfiguration.cpp

// Source: packages/modules/DnsResolver/PrivateDnsConfiguration.cpp
FeatureFlags makeDohFeatureFlags() {
    const Experiments* const instance = Experiments::getInstance();
    const auto getTimeout = [&](const std::string_view key,
                                 int defaultValue) -> uint64_t {
        static constexpr int kMinTimeoutMs = 1000;
        uint64_t timeout = instance->getFlag(key, defaultValue);
        if (timeout < kMinTimeoutMs) {
            timeout = kMinTimeoutMs;
        }
        return timeout;
    };

    return FeatureFlags{
        .probe_timeout_ms = getTimeout("doh_probe_timeout_ms",
            PrivateDnsConfiguration::kDohProbeDefaultTimeoutMs),
        .idle_timeout_ms = getTimeout("doh_idle_timeout_ms",
            PrivateDnsConfiguration::kDohIdleDefaultTimeoutMs),
        .use_session_resumption =
            instance->getFlag("doh_session_resumption", 0) == 1,
        .enable_early_data =
            instance->getFlag("doh_early_data", 0) == 1,
    };
}

DoH feature flags allow server-side control over:

  • Probe timeout: How long to wait for DoH validation
  • Idle timeout: How long to keep idle connections open
  • Session resumption: TLS 1.3 session resumption (0-RTT)
  • Early data: TLS 1.3 early data for reduced latency

35.5.6 Private DNS Configuration

The PrivateDnsConfiguration class manages the lifecycle of private DNS (DoT/DoH) servers, including validation and failover.

Source file: packages/modules/DnsResolver/PrivateDnsConfiguration.cpp

// Source: packages/modules/DnsResolver/PrivateDnsConfiguration.cpp
// Returns the sorted (sort IPv6 before IPv4) servers.
std::vector<std::string> sortServers(
        const std::vector<std::string>& servers) {
    std::vector<std::string> out = servers;
    std::sort(out.begin(), out.end(),
        [](std::string a, std::string b) {
            return IPAddress::forString(a) > IPAddress::forString(b);
        });
    return out;
}

Private DNS modes:

  1. Off: All DNS queries are plaintext
  2. Opportunistic (default): Try DoT/DoH, fall back to plaintext
  3. Strict: Force DoT/DoH; fail if unavailable

The validation state machine:

stateDiagram-v2
    [*] --> Unknown: Server configured
    Unknown --> InProgress: Start validation
    InProgress --> Success: Probe successful
    InProgress --> Fail: Probe failed
    Success --> InProgress: Re-validation needed
    Fail --> InProgress: Retry with backoff
    Success --> [*]: Server removed
    Fail --> [*]: Server removed

35.5.7 Dns64 and NAT64

The Dns64Configuration class handles DNS64 prefix discovery for IPv6-only networks. When a network has no IPv4 connectivity, DNS64 synthesizes AAAA records from A records, and NAT64 (handled by clatd in the connectivity module) translates the packets.

Source file: packages/modules/DnsResolver/Dns64Configuration.cpp

35.5.8 Per-Network DNS Cache

The resolver maintains separate DNS caches per network ID. This prevents DNS cache poisoning across networks and ensures that responses are appropriate for the network context (e.g., captive portal responses are not cached for the global DNS).

Key cache behaviors:

  • TTL-based expiry: Respects DNS record TTL values
  • Network isolation: Separate cache per netId
  • Negative caching: Caches NXDOMAIN responses
  • Cache flushing: Triggered on network changes

35.5.9 DNS Query Logging

The DnsQueryLog class provides diagnostic logging for DNS queries:

Source file: packages/modules/DnsResolver/DnsQueryLog.cpp

This enables debugging via dumpsys dnsresolver and metrics collection for DNS performance monitoring.


35.6 VPN Framework

35.6.1 Architecture Overview

Android's VPN framework supports multiple VPN types: third-party VPN apps (VpnService API), platform-managed IKEv2 VPNs, and legacy PPTP/L2TP VPNs. The central implementation resides in the Vpn class.

Source file: frameworks/base/services/core/java/com/android/server/connectivity/Vpn.java

graph TD
    subgraph "Application"
        VPNAPP["VPN App<br/>(extends VpnService)"]
        IKEV2["Platform VPN<br/>(IKEv2 profile)"]
    end

    subgraph "Framework"
        VPN["Vpn.java"]
        VPNSVC["VpnService API"]
        IKESESS["IkeSession"]
        NA_VPN["VPN NetworkAgent"]
        CS["ConnectivityService"]
    end

    subgraph "Kernel"
        TUN["TUN/TAP Interface"]
        IPSEC["IPsec (XFRM)"]
        ROUTING_VPN["VPN Routing Table"]
    end

    VPNAPP -->|"Bind"| VPNSVC
    VPNSVC --> VPN
    IKEV2 --> IKESESS
    IKESESS --> VPN
    VPN --> NA_VPN
    NA_VPN --> CS
    VPN -->|"Configure"| TUN
    VPN -->|"Configure"| IPSEC
    VPN -->|"Configure"| ROUTING_VPN

35.6.2 The Vpn Class

The Vpn class is one of the most complex classes in the connectivity stack, handling both third-party VPN apps and platform-managed VPNs.

// Source: frameworks/base/services/core/java/com/android/server/connectivity/Vpn.java
public class Vpn {
    private static final String NETWORKTYPE = "VPN";
    private static final String TAG = "Vpn";

    // VPN launch idle allowlist duration
    private static final long VPN_LAUNCH_IDLE_ALLOWLIST_DURATION_MS = 60 * 1000;

    // IKEv2 retry delays with exponential backoff
    private static final long[] IKEV2_VPN_RETRY_DELAYS_MS =
            {1_000L, 2_000L, 5_000L, 30_000L, 60_000L, 300_000L, 900_000L};

    // Maximum VPN profile size (128 KB)
    static final int MAX_VPN_PROFILE_SIZE_BYTES = 1 << 17;

    // VPN network score
    private static final int VPN_DEFAULT_SCORE = 101;

    // Data stall recovery delays
    private static final long[] DATA_STALL_RECOVERY_DELAYS_MS =
            {1000L, 5000L, 30000L, 60000L, 120000L, 240000L, 480000L, 960000L};

    // Maximum MOBIKE recovery attempts
    private static final int MAX_MOBIKE_RECOVERY_ATTEMPT = 2;

    // Automatic keepalive interval
    public static final int AUTOMATIC_KEEPALIVE_DELAY_SECONDS = 30;
    // ...
}

35.6.3 VPN Types

Android supports three VPN implementation approaches:

1. Third-Party VPN (VpnService API)

Applications extend VpnService and request a TUN interface from the kernel. All traffic matching the VPN's routing rules is redirected through this interface, where the app encrypts and tunnels it.

sequenceDiagram
    participant App as VPN App
    participant FW as VpnService Framework
    participant VPN as Vpn.java
    participant CS as ConnectivityService
    participant Kernel as Kernel

    App->>FW: prepare()
    FW->>VPN: establish()
    VPN->>Kernel: Create TUN interface
    VPN->>Kernel: Configure routing
    VPN->>CS: Register NetworkAgent
    CS->>CS: Remap UID routing to VPN
    Note over Kernel: All matching traffic<br/>now flows through TUN
    App->>Kernel: Read from TUN fd
    App->>App: Encrypt + tunnel
    App->>Kernel: Send via underlying network

2. Platform VPN (IKEv2)

For IKEv2 VPNs, the framework manages the entire connection lifecycle:

// Source: frameworks/base/services/core/java/com/android/server/connectivity/Vpn.java
// IKE session management imports
import android.net.ipsec.ike.IkeSession;
import android.net.ipsec.ike.IkeSessionCallback;
import android.net.ipsec.ike.IkeSessionConfiguration;
import android.net.ipsec.ike.IkeSessionParams;
import android.net.ipsec.ike.IkeTunnelConnectionParams;

The platform handles:

  • IKE negotiation (IKEv2 with EAP or certificate authentication)
  • IPsec SA management
  • MOBIKE for seamless network switching
  • Automatic retry with exponential backoff
  • Data stall detection and recovery

3. Legacy VPN (PPTP/L2TP)

Deprecated but still supported through the LegacyVpnInfo and VpnProfile classes.

35.6.4 Per-App VPN

ConnectivityService can restrict VPN traffic to specific applications or exclude specific applications. This is implemented through UID ranges:

// Source: Vpn.java imports
import android.net.UidRangeParcel;

The UID ranges are configured on the VPN's network via netd's networkAddUidRanges(). Packets from included UIDs are fwmarked for the VPN routing table.

35.6.5 Always-On VPN

The always-on VPN feature ensures that a VPN is always active. If the connection drops, traffic is either blocked (lockdown mode) or allowed to flow through the underlying network (without lockdown).

// Source: Vpn.java
private static final String LOCKDOWN_ALLOWLIST_SETTING_NAME =
        Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST;

Lockdown VPN implementation:

  1. ConnectivityService blocks all traffic for the VPN's UID ranges using BLOCKED_REASON_LOCKDOWN_VPN
  2. Only traffic through the VPN interface is permitted
  3. Certain essential system apps can be allowlisted

35.6.6 VPN Network Agent

The VPN registers a NetworkAgent with ConnectivityService, advertising capabilities that include:

  • TRANSPORT_VPN
  • Capabilities inherited from underlying networks (metered, not-roaming, etc.)
  • Underlying network information for proper routing
graph TD
    subgraph "VPN Network"
        VPN_NA["VPN NetworkAgent<br/>TRANSPORT_VPN<br/>NET_CAPABILITY_INTERNET"]
    end

    subgraph "Underlying Networks"
        WIFI_NA["Wi-Fi NetworkAgent<br/>TRANSPORT_WIFI"]
        CELL_NA["Cellular NetworkAgent<br/>TRANSPORT_CELLULAR"]
    end

    VPN_NA -->|"Underlying"| WIFI_NA
    VPN_NA -.->|"Fallback"| CELL_NA

35.6.7 IKEv2 Data Stall Recovery

The Vpn class implements sophisticated data stall recovery for IKEv2 VPNs:

// Source: Vpn.java
// Data stall recovery timers: 1s, 5s, 30s, 1m, 2m, 4m, 8m, 16m
private static final long[] DATA_STALL_RECOVERY_DELAYS_MS =
        {1000L, 5000L, 30000L, 60000L, 120000L, 240000L, 480000L, 960000L};
// Maximum attempts to perform MOBIKE when the network is bad
private static final int MAX_MOBIKE_RECOVERY_ATTEMPT = 2;

Recovery strategy:

  1. First 2 attempts: Try MOBIKE (IKEv2 Mobility and Multihoming) to migrate the session to a different path
  2. Subsequent attempts: Full session restart with exponential backoff
  3. If all recovery attempts are exhausted, repeat the last interval

35.7 Tethering

35.7.1 Architecture Overview

The Tethering module allows Android devices to share their Internet connection with other devices via USB, Wi-Fi hotspot, Bluetooth, Ethernet, or Wi-Fi Direct.

Module root: packages/modules/Connectivity/Tethering/

graph TD
    subgraph "Tethering Module"
        TM["TetheringManager<br/>(public API)"]
        TETHER["Tethering.java<br/>(main coordinator)"]
        IPS["IpServer<br/>(per-interface)"]
        UNM["UpstreamNetworkMonitor"]
        BPF_COORD["BpfCoordinator"]
        IPV6_COORD["IPv6TetheringCoordinator"]
        RAD["RouterAdvertisementDaemon"]
        DHCP_S["DHCP Server"]
    end

    subgraph "External Components"
        CS["ConnectivityService"]
        NETD["netd"]
        WIFI["Wi-Fi Service"]
        USB["USB Service"]
        BT["Bluetooth Service"]
    end

    subgraph "Kernel"
        NAT["NAT (iptables)"]
        BPF_K["BPF Offload"]
        FORWARDING["IP Forwarding"]
    end

    TM -->|Binder| TETHER
    TETHER --> IPS
    TETHER --> UNM
    TETHER --> BPF_COORD
    TETHER --> IPV6_COORD
    IPS --> RAD
    IPS --> DHCP_S
    UNM -->|"Monitor"| CS
    TETHER --> NETD
    TETHER --> WIFI
    TETHER --> USB
    TETHER --> BT
    IPS --> NAT
    BPF_COORD --> BPF_K
    IPS --> FORWARDING

35.7.2 The Tethering Class

Tethering.java is the central coordinator for all tethering operations. It manages the lifecycle of tethered interfaces and coordinates between upstream (Internet-providing) and downstream (client-facing) networks.

Source file: packages/modules/Connectivity/Tethering/src/com/android/networkstack/tethering/Tethering.java

// Source: packages/modules/Connectivity/Tethering/src/com/android/networkstack/tethering/Tethering.java
// Supported tethering types:
// TETHERING_WIFI      - Wi-Fi hotspot
// TETHERING_USB       - USB tethering (RNDIS/NCM)
// TETHERING_BLUETOOTH - Bluetooth PAN
// TETHERING_WIFI_P2P  - Wi-Fi Direct tethering
// TETHERING_NCM       - USB NCM (Network Control Model)
// TETHERING_ETHERNET  - Ethernet tethering
// TETHERING_WIGIG     - WiGig (60 GHz)
// TETHERING_VIRTUAL   - Virtual tethering

35.7.3 Tethering Types

Type Interface Transport Use Case
Wi-Fi wlan0/1 802.11 Mobile hotspot
USB RNDIS rndis0 USB Wired to PC
USB NCM ncm0 USB Modern USB networking
Bluetooth bt-pan BT PAN Low-speed sharing
Ethernet eth0 Ethernet Automotive, TV
Wi-Fi P2P p2p0 Wi-Fi Direct Direct device sharing
WiGig wigig0 802.11ad High-speed 60 GHz

35.7.4 IpServer: Per-Interface Management

Each tethered interface is managed by an IpServer instance that runs its own state machine.

Source file: packages/modules/Connectivity/Tethering/src/android/net/ip/IpServer.java

stateDiagram-v2
    [*] --> InitialState
    InitialState --> LocalHotspotState: LOCAL_ONLY request
    InitialState --> TetheredState: TETHERING request
    LocalHotspotState --> InitialState: Stop
    TetheredState --> InitialState: Stop
    TetheredState --> TetheredState: Upstream change

    state TetheredState {
        [*] --> ConfigureInterface
        ConfigureInterface --> RunDHCP: Start DHCP server
        RunDHCP --> SetupNAT: Configure NAT rules
        SetupNAT --> Active: Ready
        Active --> UpdateUpstream: Upstream changes
        UpdateUpstream --> Active: Reconfigure
    }

35.7.5 BPF Offload

The BpfCoordinator manages eBPF-based tethering offload, which bypasses the Linux networking stack for forwarded packets, dramatically improving throughput and reducing CPU usage.

Source file: packages/modules/Connectivity/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java

// Source: packages/modules/Connectivity/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
// BPF maps used for tethering offload:
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
import com.android.net.module.util.bpf.TetherStatsValue;

The BPF tethering offload works by installing forwarding rules in eBPF maps:

graph LR
    subgraph "Without BPF Offload"
        A1["Downstream Packet"] --> B1["Kernel IP Stack"]
        B1 --> C1["iptables NAT"]
        C1 --> D1["Routing"]
        D1 --> E1["Upstream"]
    end

    subgraph "With BPF Offload"
        A2["Downstream Packet"] --> B2["BPF Program<br/>(TC ingress)"]
        B2 -->|"Lookup BPF map<br/>NAT + forward"| E2["Upstream"]
        B2 -.->|"Miss"| C2["Kernel IP Stack<br/>(slow path)"]
    end

The BPF maps contain:

  • Tether4Key/Tether4Value: IPv4 connection tracking entries
  • TetherStatsValue: Per-interface traffic statistics
  • Downstream/Upstream keys: Direction-specific forwarding rules

35.7.6 IPv6 Tethering

The IPv6TetheringCoordinator manages IPv6 prefix delegation for tethered clients:

Source file: packages/modules/Connectivity/Tethering/src/com/android/networkstack/tethering/IPv6TetheringCoordinator.java

IPv6 tethering uses:

  • RouterAdvertisementDaemon: Sends Router Advertisements to clients
  • DadProxy: Handles Duplicate Address Detection for tethered devices
  • NeighborPacketForwarder: Forwards neighbor discovery messages
sequenceDiagram
    participant Client as Tethered Client
    participant IPS as IpServer
    participant RAD as RA Daemon
    participant Upstream as Upstream Network

    Upstream->>IPS: IPv6 prefix delegated
    IPS->>RAD: Configure prefix for advertisement
    RAD->>Client: Router Advertisement (prefix, DNS)
    Client->>Client: SLAAC: Generate IPv6 address
    Client->>IPS: IPv6 traffic
    IPS->>Upstream: Forward (BPF or kernel)

35.7.7 DHCP Server

The tethering module includes its own DHCP server for assigning IPv4 addresses to tethered clients:

// Source: packages/modules/Connectivity/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java

The DHCP server provides:

  • IPv4 address assignment from a configured pool
  • Default gateway (the tethering device)
  • DNS server configuration (forwarded from upstream)
  • Lease management and renewal

35.7.8 Upstream Network Monitor

The UpstreamNetworkMonitor tracks available upstream networks and selects the best one for providing Internet to tethered clients. It registers network callbacks with ConnectivityService and responds to network changes.

Selection priority (typical):

  1. DUN (Dedicated Upstream Network) capable cellular
  2. Wi-Fi
  3. Regular cellular
  4. Ethernet

35.7.9 NAT Configuration

For IPv4 tethering, netd configures Network Address Translation (NAT) rules:

graph LR
    CLIENT["Tethered Client<br/>192.168.49.x"] -->|"src: 192.168.49.2"| TETHER["Tethering Device"]
    TETHER -->|"NAT: src -> WAN IP"| UPSTREAM["Internet"]
    UPSTREAM -->|"NAT: dst -> 192.168.49.2"| TETHER
    TETHER -->|"dst: 192.168.49.2"| CLIENT

The NAT is configured through netd's tethering controller, which sets up iptables MASQUERADE rules in the nat/POSTROUTING chain.


35.8 Network Security Config

35.8.1 Overview

Android's Network Security Config allows applications to customize their network security settings in a declarative XML format. This includes certificate pinning, custom trust anchors, and cleartext traffic policies.

Framework source: frameworks/base/packages/NetworkSecurityConfig/platform/src/android/security/net/config/

35.8.2 XML Configuration Format

Applications define their security configuration in res/xml/network_security_config.xml, referenced from the AndroidManifest.xml:

<!-- Example network_security_config.xml -->
<network-security-config>
    <!-- Base configuration applying to all connections -->
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>

    <!-- Per-domain configuration -->
    <domain-config>
        <domain includeSubdomains="true">example.com</domain>
        <pin-set expiration="2025-12-31">
            <pin digest="SHA-256">base64EncodedPin=</pin>
            <pin digest="SHA-256">backupPinBase64=</pin>
        </pin-set>
        <trust-anchors>
            <certificates src="system" />
            <certificates src="@raw/my_ca" />
        </trust-anchors>
    </domain-config>

    <!-- Debug overrides (only active in debug builds) -->
    <debug-overrides>
        <trust-anchors>
            <certificates src="user" />
        </trust-anchors>
    </debug-overrides>
</network-security-config>

35.8.3 NetworkSecurityConfig Class

The NetworkSecurityConfig class is the runtime representation of the security configuration:

Source file: frameworks/base/packages/NetworkSecurityConfig/platform/src/android/security/net/config/NetworkSecurityConfig.java

// Source: frameworks/base/packages/NetworkSecurityConfig/platform/src/android/security/net/config/NetworkSecurityConfig.java
public final class NetworkSecurityConfig {
    public static final boolean DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED = true;
    public static final boolean DEFAULT_HSTS_ENFORCED = false;

    // Certificate Transparency verification for apps targeting after BAKLAVA
    @ChangeId
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
    static final long DEFAULT_ENABLE_CERTIFICATE_TRANSPARENCY = 407952621L;

    private final boolean mCleartextTrafficPermitted;
    private final boolean mHstsEnforced;
    private final boolean mCertificateTransparencyVerificationRequired;
    private final PinSet mPins;
    private final List<CertificatesEntryRef> mCertificatesEntryRefs;
    private Set<TrustAnchor> mAnchors;
    private NetworkSecurityTrustManager mTrustManager;
    // ...
}

35.8.4 XML Parsing

The XmlConfigSource class parses the XML configuration and creates the runtime NetworkSecurityConfig objects:

Source file: frameworks/base/packages/NetworkSecurityConfig/platform/src/android/security/net/config/XmlConfigSource.java

// Source: frameworks/base/packages/NetworkSecurityConfig/platform/src/android/security/net/config/XmlConfigSource.java
public class XmlConfigSource implements ConfigSource {
    private static final int CONFIG_BASE = 0;
    private static final int CONFIG_DOMAIN = 1;
    private static final int CONFIG_DEBUG = 2;

    private NetworkSecurityConfig mDefaultConfig;
    private NetworkSecurityConfig mLocalhostConfig;
    private Set<Pair<Domain, NetworkSecurityConfig>> mDomainMap;
    // ...
}

35.8.5 Configuration Elements

Element Description
<base-config> Default config for all connections
<domain-config> Per-domain overrides
<debug-overrides> Debug-build-only settings
<trust-anchors> Custom CA certificates
<certificates> Certificate source (system, user, @raw/)
<pin-set> Certificate pinning with expiration
<pin> Individual pin (SHA-256 digest of public key)
cleartextTrafficPermitted Allow/deny HTTP
<certificateTransparency> CT verification requirement

35.8.6 Key Implementation Classes

Class Role
NetworkSecurityConfig Runtime config representation
XmlConfigSource XML parser
ManifestConfigSource Reads manifest for config reference
ApplicationConfig Per-app configuration manager
NetworkSecurityTrustManager Custom X509TrustManager
RootTrustManager Root of trust chain
PinSet Certificate pin storage
CertificatesEntryRef Certificate source reference
SystemCertificateSource System CA store
DirectoryCertificateSource Directory-based CA source

35.8.7 Certificate Pinning Flow

sequenceDiagram
    participant App as Application
    participant TM as NetworkSecurityTrustManager
    participant Config as NetworkSecurityConfig
    participant PinSet as PinSet
    participant System as SystemCertificateSource

    App->>TM: TLS handshake
    TM->>Config: Get config for domain
    Config-->>TM: NetworkSecurityConfig
    TM->>System: Validate certificate chain
    System-->>TM: Chain valid
    TM->>PinSet: Check pins
    alt Pin matches
        PinSet-->>TM: Pin valid
        TM-->>App: Connection allowed
    else Pin mismatch
        PinSet-->>TM: Pin invalid
        TM-->>App: Connection rejected
    end

35.8.8 Certificate Transparency

Starting with Android 16, Certificate Transparency (CT) verification is enabled by default for apps targeting the latest SDK:

// Source: NetworkSecurityConfig.java
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
static final long DEFAULT_ENABLE_CERTIFICATE_TRANSPARENCY = 407952621L;

The CertificateTransparencyService in the Connectivity module manages CT log list updates and verification:

Source directory: packages/modules/Connectivity/networksecurity/service/src/com/android/server/net/ct/

<!-- Example: enabling CT in network security config -->
<network-security-config>
  <base-config>
    <certificateTransparency enabled="true" />
  </base-config>
</network-security-config>

35.8.9 Cleartext Traffic Restrictions

By default, Android blocks cleartext (non-HTTPS) traffic for apps targeting Android 9+. The StrictController in netd enforces this at the network level:

// Source: system/netd/server/Controllers.cpp
// StrictController is in the FILTER_OUTPUT chain:
static const std::vector<const char*> FILTER_OUTPUT = {
    OEM_IPTABLES_FILTER_OUTPUT,
    FirewallController::LOCAL_OUTPUT,
    StrictController::LOCAL_OUTPUT,    // <-- cleartext enforcement
    BandwidthController::LOCAL_OUTPUT,
};

35.8.10 The usesCleartextTraffic Manifest Flag Is Deprecated in Android 17

The manifest attribute android:usesCleartextTraffic is the older, coarser control: a single boolean on <application> that the runtime folds into the default NetworkSecurityConfig when the app ships no XML config of its own. Android 17 (CINNAMON_BUN, API 37) begins deprecating it in favor of the Network Security Config XML.

The change is wired as a compatibility change plus an aconfig flag. The network_security flag deprecate_uses_cleartext_traffic2 (frameworks/base/core/java/android/security/flags.aconfig) is described as "the XML application flag usesCleartextTraffic is ignored for targetSdk version C+". The matching @ChangeId lives in ManifestConfigSource:

// frameworks/base/packages/NetworkSecurityConfig/platform/src/android/security/net/config/ManifestConfigSource.java
@ChangeId
@Disabled
static final long DEPRECATE_USES_CLEARTEXT_TRAFFIC = 415007211L;

When the config source builds the default config for an app with no XML, it reads the manifest flag and then zeroes it out if both the change and the aconfig flag are on:

boolean usesCleartextTraffic =
        (mApplicationInfo.flags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0
        && !mApplicationInfo.isInstantApp();
if (CompatChanges.isChangeEnabled(DEPRECATE_USES_CLEARTEXT_TRAFFIC) &&
    deprecateUsesCleartextTraffic2()) {
    usesCleartextTraffic = false;
}

For an affected app, the manifest attribute is treated as false regardless of its declared value. An app that still needs cleartext for specific hosts must say so in a Network Security Config (a <domain-config cleartextTrafficPermitted= "true"> for those hosts), which is the more granular and auditable mechanism the deprecation pushes apps toward. DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED itself is unchanged (still true), so the default-deny-by-target-SDK behavior in the table below is what governs apps that supply neither the manifest flag nor an XML config.

35.8.11 Default Security Behavior by Target SDK

Target SDK Cleartext User CAs CT Required
< 24 (Android 7) Allowed Trusted No
24-27 Allowed Not trusted No
28+ (Android 9) Blocked Not trusted No
36+ (BAKLAVA) Blocked Not trusted Yes (default)
37+ (CINNAMON_BUN) Blocked Not trusted Yes; usesCleartextTraffic manifest flag ignored

35.9 NetworkStack Module

35.9.1 Overview

The NetworkStack Mainline module handles IP provisioning, network validation (captive portal detection), and DHCP. It runs in a separate process (com.android.networkstack) with NETWORK_STACK permission, isolating these critical functions from the rest of the system.

Module root: packages/modules/NetworkStack/

graph TD
    subgraph "NetworkStack Module"
        NM["NetworkMonitor"]
        IPC["IpClient"]
        DHCP["DHCP Client"]
        CPD["Captive Portal Detection"]
        DS["Data Stall Detection"]
        IPMS["IpMemoryStore"]
    end

    subgraph "ConnectivityService"
        CS["ConnectivityService"]
        NA["NetworkAgent"]
    end

    subgraph "Network"
        PORTAL["Captive Portal"]
        INTERNET["Internet"]
        DHCP_SRV["DHCP Server"]
    end

    CS -->|"Create monitor"| NM
    NM -->|"Validation result"| CS
    NA --> IPC
    IPC --> DHCP
    DHCP --> DHCP_SRV
    NM --> CPD
    NM --> DS
    CPD --> PORTAL
    CPD --> INTERNET
    NM --> IPMS

35.9.2 NetworkMonitor: Network Validation

NetworkMonitor is a state machine that validates network connectivity by probing Internet endpoints. It detects captive portals, partial connectivity, and data stalls.

Source file: packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java

// Source: packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
public class NetworkMonitor extends StateMachine {
    // Validation probe types
    // NETWORK_VALIDATION_PROBE_DNS      - DNS resolution test
    // NETWORK_VALIDATION_PROBE_HTTP     - HTTP probe (generate_204)
    // NETWORK_VALIDATION_PROBE_HTTPS    - HTTPS probe
    // NETWORK_VALIDATION_PROBE_FALLBACK - Fallback URL probe
    // NETWORK_VALIDATION_PROBE_PRIVDNS  - Private DNS validation
    // ...
}

The validation state machine:

stateDiagram-v2
    [*] --> DefaultState

    state DefaultState {
        [*] --> MaybeNotifyState
        MaybeNotifyState --> EvaluatingState: Start validation
        EvaluatingState --> ValidatedState: All probes pass
        EvaluatingState --> CaptivePortalState: Portal detected
        EvaluatingState --> WaitingForNextProbeState: Probe failed
        WaitingForNextProbeState --> EvaluatingState: Retry timer
        CaptivePortalState --> EvaluatingState: User dismissed portal
        ValidatedState --> EvaluatingState: Re-validation needed
    }

    state EvaluatingState {
        [*] --> ProbeHTTPS
        ProbeHTTPS --> ProbeHTTP: In parallel
        ProbeHTTPS --> ProbeDNS: In parallel
        ProbeDNS --> CheckResults
        ProbeHTTP --> CheckResults
        ProbeHTTPS --> CheckResults
    }

35.9.3 Validation Probes

NetworkMonitor performs multiple types of probes to determine network status:

Probe URL/Method Purpose
HTTP http://connectivitycheck.gstatic.com/generate_204 Detect captive portals
HTTPS https://www.google.com/generate_204 Verify TLS works
DNS A/AAAA queries for probe hostnames Verify DNS resolution
Fallback Configurable fallback URLs Alternative probing
Private DNS Probe private DNS hostname Verify DoT/DoH
// Source: NetworkMonitor.java imports showing probe constants
import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTPS_URL;
import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTP_URL;
import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_URL;
import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS;
import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_HTTPS_URLS;
import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_HTTP_URLS;

35.9.4 Captive Portal Detection

Captive portal detection works by comparing HTTP responses against expected values:

flowchart TD
    START["Send HTTP GET to<br/>connectivitycheck.gstatic.com/generate_204"]
    R204["Response: 204 No Content"]
    R302["Response: 302/301 Redirect"]
    R200["Response: 200 with content"]
    TIMEOUT["Timeout / DNS failure"]

    VALIDATED["Network VALIDATED"]
    PORTAL["CAPTIVE PORTAL<br/>Show sign-in notification"]
    PARTIAL["PARTIAL CONNECTIVITY"]
    INVALID["INVALID / Retry"]

    START --> R204
    START --> R302
    START --> R200
    START --> TIMEOUT

    R204 --> VALIDATED
    R302 --> PORTAL
    R200 -->|"Content != expected"| PORTAL
    R200 -->|"Content matches"| VALIDATED
    TIMEOUT --> INVALID

When a captive portal is detected:

  1. NetworkMonitor reports NETWORK_TEST_RESULT_INVALID with a redirect URL
  2. ConnectivityService adds NET_CAPABILITY_CAPTIVE_PORTAL to the network
  3. A notification is shown to the user
  4. The user taps the notification and is shown the portal login page
  5. After login, NetworkMonitor re-validates

35.9.5 Data Stall Detection

NetworkMonitor also detects data stalls on validated networks using two mechanisms:

DNS-based detection:

// Source: NetworkMonitor.java imports
import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_DNS;
import static android.net.util.DataStallUtils.DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD;

If consecutive DNS queries time out beyond a threshold, a data stall is reported.

TCP-based detection:

import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_TCP;
import static android.net.util.DataStallUtils.DEFAULT_TCP_POLLING_INTERVAL_MS;

TCP metrics (packet loss rate, retransmission count) are polled at regular intervals. If the failure rate exceeds a threshold, a data stall is detected.

graph TD
    subgraph "DNS Stall Detection"
        DNS_Q["DNS Queries"] --> DNS_T["Track timeouts"]
        DNS_T -->|"Consecutive timeouts<br/>> threshold"| STALL_DNS["Data Stall!"]
    end

    subgraph "TCP Stall Detection"
        TCP_P["Poll TCP metrics<br/>(every N seconds)"] --> TCP_A["Analyze"]
        TCP_A -->|"Packet fail rate<br/>> threshold"| STALL_TCP["Data Stall!"]
    end

    STALL_DNS --> REPORT["Report to<br/>ConnectivityService"]
    STALL_TCP --> REPORT
    REPORT --> CS_ACTION["CS: Notify apps via<br/>ConnectivityDiagnosticsManager"]

35.9.6 IpClient: IP Provisioning

IpClient (formerly IpManager) handles the IP provisioning lifecycle for a network interface. It manages:

  • DHCP client for IPv4 address assignment
  • IPv6 SLAAC (Stateless Address Autoconfiguration)
  • Router discovery
  • Neighbor discovery
  • APF (Android Packet Filter) program installation (see §35.30)

The IP provisioning flow:

sequenceDiagram
    participant WF as Wi-Fi Framework
    participant IPC as IpClient
    participant DHCP as DHCP Client
    participant SLAAC as IPv6 SLAAC
    participant Server as DHCP Server
    participant Router as Router

    WF->>IPC: startProvisioning(config)
    par IPv4 Provisioning
        IPC->>DHCP: Start DHCP discovery
        DHCP->>Server: DHCPDISCOVER
        Server->>DHCP: DHCPOFFER
        DHCP->>Server: DHCPREQUEST
        Server->>DHCP: DHCPACK
        DHCP->>IPC: IPv4 address assigned
    and IPv6 Provisioning
        IPC->>SLAAC: Listen for RAs
        Router->>SLAAC: Router Advertisement
        SLAAC->>IPC: IPv6 address (SLAAC)
    end
    IPC->>WF: onProvisioningSuccess(linkProperties)

35.9.7 IpMemoryStore

The IpMemoryStore persists network-related data across connections, enabling faster reconnections and smarter network selection:

  • L2 key mapping: Maps L2 (MAC/BSSID) information to stored data
  • Network attributes: Stores previously assigned addresses, DNS servers
  • Blob storage: Generic key-value storage for network metadata
  • Expiry management: Automatically cleans up stale entries

35.9.8 Module Isolation

The NetworkStack module runs in its own process with specific permissions:

graph TD
    subgraph "system_server Process"
        CS["ConnectivityService"]
    end

    subgraph "NetworkStack Process"
        NS["NetworkStackService"]
        NM["NetworkMonitor"]
        IPC["IpClient"]
    end

    CS <-->|"AIDL IPC<br/>INetworkMonitor<br/>IIpClient"| NS

    classDef server fill:#e1f5fe
    classDef stack fill:#f3e5f5
    class CS server
    class NS,NM,IPC stack

This process isolation provides:

  • Security: Network validation code runs with limited privileges
  • Updatability: Module can be updated independently
  • Stability: Crashes in NetworkStack do not bring down system_server
  • Testability: Easier to test in isolation

35.10 VCN (Virtual Carrier Network)

The Virtual Carrier Network (VCN) subsystem provides carrier-grade IPsec tunneling over any available transport -- cellular or Wi-Fi -- presenting the result as a single, seamless mobile network to the rest of the platform. Where a traditional VPN serves user privacy, VCN serves the carrier: a mobile operator can configure the device to wrap all traffic in an IKEv2/IPsec tunnel back to the carrier's gateway, effectively creating a "virtual" carrier network that follows the subscriber across physical transports.

35.10.1 Motivation and Design Goals

Carriers that deploy Wi-Fi Offload (ePDG) or private networks need a mechanism to tunnel subscriber traffic securely from the device to the carrier gateway, regardless of whether the device is on Wi-Fi, cellular, or switching between them. VCN provides:

  1. Carrier-bound tunneling: Unlike user VPNs, VCN tunnels are tied to a carrier subscription group. Only the carrier's provisioning app (with carrier privileges) can install a VCN configuration.
  2. Seamless mobility: When the underlying transport changes (e.g., Wi-Fi to cellular), the VCN migrates the IKE/IPsec session via MOBIKE (RFC 4555), avoiding TCP connection resets visible to applications.
  3. Safe-mode fallback: If the tunnel cannot be established within a timeout, VCN falls back to "safe mode" -- exposing the raw underlying networks so the device is never left without connectivity.
  4. Per-capability gateway connections: A single VCN instance can manage multiple gateway connections, each serving different NetworkCapabilities (e.g., one for INTERNET, another for DUN/tethering).

35.10.2 Architecture

Module root: packages/modules/Connectivity/Vcn/

The VCN subsystem is organised into four main classes, each at a different level of granularity:

graph TD
    subgraph "VCN Management Layer"
        VCNMS["VcnManagementService<br/>(IVcnManagementService.Stub)"]
        TST["TelephonySubscriptionTracker"]
    end

    subgraph "VCN Instance Layer"
        VCN["Vcn<br/>(per subscription group)"]
        VNP["VcnNetworkProvider"]
    end

    subgraph "Gateway Connection Layer"
        GW1["VcnGatewayConnection #1<br/>(INTERNET)"]
        GW2["VcnGatewayConnection #2<br/>(DUN)"]
    end

    subgraph "Route Selection Layer"
        UNC1["UnderlyingNetworkController"]
        UNE1["UnderlyingNetworkEvaluator"]
        NPC["NetworkPriorityClassifier"]
    end

    subgraph "Tunnel Layer"
        IKE["IkeSession<br/>(IKEv2 + IPsec)"]
        NA["VCN NetworkAgent"]
        TUN["IPsec Tunnel Interface"]
    end

    subgraph "Underlying Transports"
        WIFI["Wi-Fi Network"]
        CELL["Cellular Network"]
    end

    VCNMS -->|"Creates per sub-group"| VCN
    VCNMS --> TST
    TST -->|"Subscription snapshots"| VCNMS
    VCN --> VNP
    VNP -->|"NetworkRequest routing"| VCN
    VCN -->|"Creates per capability"| GW1
    VCN -->|"Creates per capability"| GW2
    GW1 --> UNC1
    UNC1 --> UNE1
    UNC1 --> NPC
    UNE1 -->|"Monitors"| WIFI
    UNE1 -->|"Monitors"| CELL
    GW1 --> IKE
    IKE --> TUN
    GW1 --> NA
    NA -->|"Registered with"| CS["ConnectivityService"]

The hierarchy from the AOSP source captures this precisely:

// Source: packages/modules/Connectivity/Vcn/service-b/src/com/android/server/VcnManagementService.java
// Lines 115-163 (ASCII art from the source comment)

VcnManagementService --1:1--> TelephonySubscriptionTracker
        |
     1:N Creates when config present, subscription group active,
        and providing app is carrier privileged
        |
        v
       Vcn -- manages GatewayConnection lifecycles based on
              fulfillable NetworkRequests and overall safe-mode
        |
     1:N Creates to fulfill NetworkRequests
        |
        v
  VcnGatewayConnection -- manages a single IKEv2 tunnel session
        and NetworkAgent, handles mobility events
        |
     1:1 Creates upon instantiation
        |
        v
  UnderlyingNetworkController -- manages underlying physical
        networks, filing requests to bring them up

35.10.3 VcnManagementService

VcnManagementService is the top-level system service, registered with ServiceManager and accessible via VcnManager. It is responsible for:

  • Config persistence: VCN configs are stored as XML in /data/system/vcn/configs.xml and survive reboots.
  • Carrier-privilege enforcement: Only apps with carrier privileges for the subscription group can set or clear VCN configs.
  • Vcn lifecycle management: Creates and tears down Vcn instances as subscription groups become active/inactive.
  • Underlying network policy: Provides VcnUnderlyingNetworkPolicy to ConnectivityService, controlling whether underlying networks are marked as NOT_VCN_MANAGED.
// Source: packages/modules/Connectivity/Vcn/service-b/src/com/android/server/VcnManagementService.java
public class VcnManagementService extends IVcnManagementService.Stub {
    // Configs stored persistently
    static final String VCN_CONFIG_FILE =
            new File(Environment.getDataDirectory(),
                "system/vcn/configs.xml").getPath();

    // Grace period before tearing down if carrier privileges are lost
    static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS =
            TimeUnit.SECONDS.toMillis(30);

    // Wi-Fi is restricted by default (must go through VCN tunnel)
    private static final Set<Integer> RESTRICTED_TRANSPORTS_DEFAULT =
            Collections.singleton(TRANSPORT_WIFI);
    // ...
}

35.10.4 TelephonySubscriptionTracker

The TelephonySubscriptionTracker de-noises subscription change events and provides a stable snapshot of active subscription groups to VcnManagementService. A subscription group is considered "active and ready" when:

  1. At least one contained subscription ID has carrier config loaded (CarrierConfigManager.isConfigForIdentifiedCarrier() returns true).
  2. The subscription is listed as active per SubscriptionManager.
// Source: packages/modules/Connectivity/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java
public class TelephonySubscriptionTracker extends BroadcastReceiver {
    // Maps slot IDs to ready subscription IDs
    private final Map<Integer, Integer> mReadySubIdsBySlotId = new HashMap<>();
    // ...
}

35.10.5 The Vcn Class

Each Vcn instance manages all VcnGatewayConnections for a single subscription group. It acts as a Handler, processing messages for configuration updates, network requests, subscription changes, safe-mode transitions, and mobile data toggles.

// Source: packages/modules/Connectivity/Vcn/service-b/src/com/android/server/vcn/Vcn.java
public class Vcn extends Handler {
    // VCN network score -- must beat raw underlying networks
    private static final int VCN_LEGACY_SCORE_INT = 52;

    // Capabilities requiring mobile data to be enabled
    private static final List<Integer> CAPS_REQUIRING_MOBILE_DATA =
            Arrays.asList(NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN);

    // Map of active gateway connections
    private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection>
            mVcnGatewayConnections = new HashMap<>();

    // Status tracking: ACTIVE, SAFE_MODE, or INACTIVE
    private volatile int mCurrentStatus = VCN_STATUS_CODE_ACTIVE;
}

Key message types handled by Vcn:

Message Trigger
MSG_EVENT_CONFIG_UPDATED Carrier app updated VCN configuration
MSG_EVENT_NETWORK_REQUESTED New NetworkRequest from VcnNetworkProvider
MSG_EVENT_SUBSCRIPTIONS_CHANGED Subscription snapshot changed
MSG_EVENT_GATEWAY_CONNECTION_QUIT A gateway connection tore down
MSG_EVENT_SAFE_MODE_STATE_CHANGED Safe-mode timer fired or cleared
MSG_EVENT_MOBILE_DATA_TOGGLED User toggled mobile data

35.10.6 VcnGatewayConnection State Machine

VcnGatewayConnection is the heart of the VCN subsystem -- a StateMachine managing a single IKEv2/IPsec tunnel and its corresponding NetworkAgent. The state machine has five states:

stateDiagram-v2
    [*] --> DisconnectedState
    DisconnectedState --> ConnectingState : Underlying network available
    ConnectingState --> ConnectedState : IKE session negotiated
    ConnectingState --> DisconnectingState : Error occurred
    ConnectingState --> RetryTimeoutState : Retriable error
    ConnectedState --> DisconnectingState : Teardown or error
    ConnectedState --> RetryTimeoutState : Retriable error
    DisconnectingState --> RetryTimeoutState : Has underlying network
    DisconnectingState --> DisconnectedState : No underlying network
    RetryTimeoutState --> ConnectingState : Retry timer expired
    RetryTimeoutState --> DisconnectingState : Teardown requested

Key events processed by the state machine:

// Source: packages/modules/Connectivity/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java
// Selected event constants
private static final int EVENT_UNDERLYING_NETWORK_CHANGED = 1;
private static final int EVENT_RETRY_TIMEOUT_EXPIRED = 2;
private static final int EVENT_SESSION_LOST = 3;
private static final int EVENT_SESSION_CLOSED = 4;
private static final int EVENT_TRANSFORM_CREATED = 5;
private static final int EVENT_SETUP_COMPLETED = 6;

Critical timeouts govern the behaviour:

Timeout Value Purpose
NETWORK_LOSS_DISCONNECT_TIMEOUT 30 s Grace period before tearing down after underlying network lost
TEARDOWN_TIMEOUT 5 s Maximum time to wait for IKE session closure
SAFEMODE_TIMEOUT 30 s Time before entering safe mode if tunnel cannot establish

35.10.7 Underlying Network Selection

The UnderlyingNetworkController evaluates available physical networks (cellular, Wi-Fi) and selects the best one for the tunnel. Selection is based on carrier-defined priority templates:

// Source: packages/modules/Connectivity/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
public class UnderlyingNetworkController {
    // Tracks all underlying networks with their evaluators
    private final Map<Network, UnderlyingNetworkEvaluator>
            mUnderlyingNetworkRecords = new ArrayMap<>();

    // Separate callbacks for Wi-Fi and cell bring-up
    private final List<NetworkCallback> mCellBringupCallbacks = new ArrayList<>();
    private NetworkCallback mWifiBringupCallback;
    // Wi-Fi RSSI threshold callbacks for entry/exit hysteresis
    private NetworkCallback mWifiEntryRssiThresholdCallback;
    private NetworkCallback mWifiExitRssiThresholdCallback;
}

The NetworkPriorityClassifier implements the priority logic. Carriers configure VcnUnderlyingNetworkTemplate objects (cell or Wi-Fi templates) with match criteria including:

  • Cellular: roaming state, opportunistic flag, home/roaming PLMN
  • Wi-Fi: SSID, RSSI thresholds (with entry/exit hysteresis)

35.10.8 Safe Mode

Safe mode is VCN's critical reliability mechanism. If a VcnGatewayConnection cannot establish a tunnel within SAFEMODE_TIMEOUT_SECONDS (30 seconds), the entire Vcn instance enters safe mode:

  1. Underlying networks are no longer marked as restricted (the NET_CAPABILITY_NOT_VCN_MANAGED capability is restored).
  2. Applications can access the raw underlying networks directly.
  3. The VCN continues attempting to establish the tunnel in the background.
  4. Once the tunnel is re-established, the VCN exits safe mode.

This ensures that a misconfigured or unreachable carrier gateway never leaves the device without network connectivity.

35.10.9 VcnNetworkProvider

VcnNetworkProvider registers with ConnectivityService as a NetworkProvider and routes incoming NetworkRequests to the appropriate Vcn instance. It builds a capability filter that matches cellular-type requests:

// Source: packages/modules/Connectivity/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
private NetworkCapabilities buildCapabilityFilter() {
    final NetworkCapabilities.Builder builder =
            new NetworkCapabilities.Builder()
                    .addTransportType(TRANSPORT_CELLULAR)
                    .addCapability(NET_CAPABILITY_TRUSTED)
                    .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
                    .addCapability(NET_CAPABILITY_NOT_VPN)
                    .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
    for (int cap : VcnGatewayConnectionConfig.ALLOWED_CAPABILITIES) {
        builder.addCapability(cap);
    }
    return builder.build();
}

35.10.10 Integration with ConnectivityService

From ConnectivityService's perspective, a VCN tunnel appears as a regular cellular NetworkAgent. The key distinction is the NOT_VCN_MANAGED capability:

  • Underlying networks (raw Wi-Fi/cellular) are marked as lacking NOT_VCN_MANAGED when VCN is active, making them invisible to most apps.
  • The VCN NetworkAgent carries NOT_VCN_MANAGED, so it satisfies normal NetworkRequests.
  • In safe mode, underlying networks regain NOT_VCN_MANAGED.
sequenceDiagram
    participant App as Application
    participant CM as ConnectivityManager
    participant CS as ConnectivityService
    participant VNP as VcnNetworkProvider
    participant VCN as Vcn
    participant GW as VcnGatewayConnection
    participant IKE as IkeSession
    participant UNC as UnderlyingNetworkController

    App->>CM: requestNetwork(INTERNET)
    CM->>CS: NetworkRequest
    CS->>VNP: onNetworkNeeded()
    VNP->>VCN: handleNetworkRequested()
    VCN->>GW: create VcnGatewayConnection
    GW->>UNC: start monitoring transports
    UNC-->>GW: underlying network selected
    GW->>IKE: openSession(IkeSessionParams)
    IKE-->>GW: onChildTransformCreated()
    GW->>GW: apply IPsec transforms to tunnel
    GW->>CS: register VCN NetworkAgent
    CS-->>App: onAvailable(VCN network)

35.10.11 Key Source Files

Class Path Lines
VcnManagementService packages/modules/Connectivity/Vcn/service-b/src/com/android/server/VcnManagementService.java 1,551
Vcn packages/modules/Connectivity/Vcn/service-b/src/com/android/server/vcn/Vcn.java 791
VcnGatewayConnection packages/modules/Connectivity/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java 3,122
VcnNetworkProvider packages/modules/Connectivity/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java ~200
UnderlyingNetworkController packages/modules/Connectivity/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java ~400
TelephonySubscriptionTracker packages/modules/Connectivity/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java ~350
NetworkPriorityClassifier packages/modules/Connectivity/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java ~300

35.11 Thread Network

Thread is an IPv6-based mesh networking protocol designed for low-power IoT (Internet of Things) devices, built on the IEEE 802.15.4 radio standard. Android's Thread implementation, added as part of the Connectivity Mainline module, allows Android devices to serve as Thread Border Routers -- bridging Thread mesh networks to the wider IP infrastructure (Wi-Fi/Ethernet).

35.11.1 What is Thread?

Thread is a low-power, low-latency mesh networking protocol standardised by the Thread Group. Key properties:

  • IEEE 802.15.4: Uses the 2.4 GHz radio band with 250 kbps throughput, channel pages 0 (channels 11-26).
  • IPv6 native: Every Thread device gets a globally routable IPv6 address; no NAT or application-layer gateways needed.
  • Mesh topology: Devices self-organise into a mesh, with Routers forwarding packets and a single Leader coordinating the network.
  • Low power: End devices (Children) can sleep for extended periods, delegating packet buffering to their parent Router.
  • Thread 1.3: The current version, supporting features like Service Registration Protocol (SRP), multicast, and NAT64 for IPv4 connectivity.

Device roles in a Thread network:

Role Description
Leader Elected Router that manages Router ID assignment and network data
Router Full participant that forwards packets and can act as parent for Children
Child End device associated with a parent Router; may sleep to save power
Border Router Router with connectivity to external IP networks (Wi-Fi/Ethernet)
Detached Device not currently part of a Thread partition

35.11.2 Architecture

Module root: packages/modules/Connectivity/thread/

Android's Thread implementation spans three layers:

graph TD
    subgraph "Framework Layer"
        TNM["ThreadNetworkManager<br/>(@SystemService)"]
        TNC["ThreadNetworkController"]
        AOD["ActiveOperationalDataset"]
    end

    subgraph "Service Layer"
        TNS["ThreadNetworkService<br/>(IThreadNetworkManager.Stub)"]
        TNCS["ThreadNetworkControllerService<br/>(IThreadNetworkController.Stub)"]
        TNF["ThreadNetworkFactory"]
        TNCC["ThreadNetworkCountryCode"]
    end

    subgraph "Native Layer"
        OTD["ot-daemon<br/>(OpenThread daemon)"]
        OT["OpenThread Stack"]
        TUN["TUN Interface<br/>(thread-wpan0)"]
        IEEE["IEEE 802.15.4 Radio"]
    end

    subgraph "Connectivity Integration"
        CS["ConnectivityService"]
        NA["NetworkAgent<br/>(TRANSPORT_THREAD)"]
        MDNS["NsdPublisher<br/>(mDNS/SRP)"]
    end

    TNM --> TNC
    TNC -->|Binder| TNCS
    TNCS --> OTD
    OTD --> OT
    OT --> TUN
    OT --> IEEE
    TNCS --> NA
    NA --> CS
    TNCS --> MDNS
    TNS --> TNCS
    TNF --> CS

35.11.3 ThreadNetworkManager and ThreadNetworkController

ThreadNetworkManager is the public @SystemApi entry point, registered as the thread_network system service. It provides access to ThreadNetworkController instances -- currently a single controller per device:

// Source: packages/modules/Connectivity/thread/framework/java/android/net/thread/ThreadNetworkManager.java
@SystemService(ThreadNetworkManager.SERVICE_NAME)
public final class ThreadNetworkManager {
    public static final String SERVICE_NAME = "thread_network";
    public static final String FEATURE_NAME =
            "android.hardware.thread_network";

    @NonNull
    public List<ThreadNetworkController> getAllThreadNetworkControllers() {
        return mUnmodifiableControllerServices;
    }
}

ThreadNetworkController exposes the full Thread control plane:

// Source: packages/modules/Connectivity/thread/framework/java/android/net/thread/ThreadNetworkController.java
public final class ThreadNetworkController {
    // Device roles
    public static final int DEVICE_ROLE_STOPPED = 0;
    public static final int DEVICE_ROLE_DETACHED = 1;
    public static final int DEVICE_ROLE_CHILD = 2;
    public static final int DEVICE_ROLE_ROUTER = 3;
    public static final int DEVICE_ROLE_LEADER = 4;

    // Radio states
    public static final int STATE_DISABLED = 0;
    public static final int STATE_ENABLED = 1;
    public static final int STATE_DISABLING = 2;

    // Thread version
    public static final int THREAD_VERSION_1_3 = 4;
}

Key APIs on the controller:

Method Permission Description
setEnabled() THREAD_NETWORK_PRIVILEGED Enable/disable Thread radio (persistent across reboots)
join() THREAD_NETWORK_PRIVILEGED Join a Thread network with an Operational Dataset
leave() THREAD_NETWORK_PRIVILEGED Leave the current Thread network
scheduleMigration() THREAD_NETWORK_PRIVILEGED Schedule migration to a new dataset
createRandomizedDataset() THREAD_NETWORK_PRIVILEGED Generate a new random dataset
registerStateCallback() -- Observe role, connectivity, and enabled state
registerOperationalDatasetCallback() -- Observe dataset changes
setChannelMaxPowers() THREAD_NETWORK_PRIVILEGED Set per-channel transmit power limits

35.11.4 Active Operational Dataset

An ActiveOperationalDataset contains all parameters needed to join a Thread network. It is serialised as a TLV (Type-Length-Value) byte array, following the Thread specification:

// Source: packages/modules/Connectivity/thread/framework/java/android/net/thread/ActiveOperationalDataset.java
public final class ActiveOperationalDataset implements Parcelable {
    public static final int LENGTH_MAX_DATASET_TLVS = 254;
    public static final int LENGTH_EXTENDED_PAN_ID = 8;
    public static final int LENGTH_NETWORK_KEY = 16;
    public static final int LENGTH_MESH_LOCAL_PREFIX_BITS = 64;
    public static final int LENGTH_PSKC = 16;
    public static final int CHANNEL_PAGE_24_GHZ = 0;
}

The dataset contains:

Field Length Description
Network Name 1-16 bytes (UTF-8) Human-readable network identifier
Network Key 16 bytes AES-128 encryption key for the mesh
Extended PAN ID 8 bytes Unique network identifier
Mesh-Local Prefix 64 bits IPv6 prefix for mesh-internal addresses
PSKc 16 bytes Pre-Shared Key for commissioner authentication
Channel 2 bytes IEEE 802.15.4 channel (page + number)
PAN ID 2 bytes Short PAN identifier
Security Policy variable Key rotation time and security flags

35.11.5 ThreadNetworkControllerService

The service implementation lives in ThreadNetworkControllerService, which communicates with the native ot-daemon process via AIDL:

// Source: packages/modules/Connectivity/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
final class ThreadNetworkControllerService extends IThreadNetworkController.Stub {
    // ot-daemon communication
    @Nullable private IOtDaemon mOtDaemon;
    // Network integration
    @Nullable private NetworkAgent mNetworkAgent;
    // Infrastructure link monitoring
    private Network mUpstreamNetwork;
    // TUN interface for Thread traffic
    private final TunInterfaceController mTunIfController;
    // mDNS/SRP publisher
    private final NsdPublisher mNsdPublisher;
}

The service initialises ot-daemon with the device configuration:

// Source: ThreadNetworkControllerService.java (simplified)
private IOtDaemon getOtDaemon() throws RemoteException {
    IOtDaemon otDaemon = mOtDaemonSupplier.get();  // waits for ot_daemon
    otDaemon.initialize(
            shouldEnableThread(),
            newOtDaemonConfig(mPersistentSettings.getConfiguration()),
            mTunIfController.getTunFd(),
            mNsdPublisher,
            getMeshcopTxtAttributes(mResources.get(), mSystemProperties),
            mCountryCodeSupplier.get(),
            FeatureFlags.isTrelEnabled(),
            mOtDaemonCallbackProxy);
    otDaemon.asBinder().linkToDeath(
            () -> mHandler.post(this::onOtDaemonDied), 0);
    return otDaemon;
}

35.11.6 OpenThread and ot-daemon

The native Thread protocol implementation is based on OpenThread, Google's open-source Thread stack. The ot-daemon process runs as a system daemon, initialised via an .rc file:

# packages/modules/Connectivity/thread/apex/ot-daemon.34rc
service ot-daemon /apex/com.android.tethering/bin/ot-daemon

ot-daemon manages:

  • The IEEE 802.15.4 radio driver (via the Thread Radio Co-Processor interface)
  • The Thread mesh protocol stack (MLE, routing, 6LoWPAN)
  • A TUN interface (thread-wpan0) for passing IPv6 traffic between the Thread mesh and the Android networking stack
  • Service Registration Protocol (SRP) for mDNS service discovery

35.11.7 Connectivity Integration

Thread networks integrate with ConnectivityService through a NetworkAgent with TRANSPORT_THREAD. The service creates a LocalNetworkConfig for the Thread interface and registers multicast routing rules:

sequenceDiagram
    participant TNCS as ThreadNetworkControllerService
    participant OTD as ot-daemon
    participant TUN as thread-wpan0 TUN
    participant NA as NetworkAgent
    participant CS as ConnectivityService
    participant UP as Upstream Network (Wi-Fi)

    OTD->>TNCS: onThreadDeviceRoleChanged(LEADER)
    TNCS->>NA: register(TRANSPORT_THREAD)
    NA->>CS: connected
    TNCS->>CS: requestUpstreamNetwork(Wi-Fi/Ethernet)
    CS-->>TNCS: upstream network available
    TNCS->>TNCS: configure multicast routing
    Note over TUN,UP: IPv6 traffic bridged<br/>between Thread mesh and upstream

The upstream network request prefers Wi-Fi or Ethernet with INTERNET capability:

// Source: ThreadNetworkControllerService.java
private NetworkRequest newUpstreamNetworkRequest() {
    return new NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
            .build();
}

35.11.8 Ephemeral Key Commissioning

Thread 1.3 introduced the Ephemeral Key (ePSKc) mechanism for secure device commissioning. A new device can use a short-lived key to join the network:

// Source: ThreadNetworkController.java
// Ephemeral key states
public static final int EPHEMERAL_KEY_DISABLED = 0;
public static final int EPHEMERAL_KEY_ENABLED = 1;
public static final int EPHEMERAL_KEY_IN_USE = 2;

// Maximum lifetime of 10 minutes
private static final Duration EPHEMERAL_KEY_LIFETIME_MAX =
        Duration.ofMinutes(10);

When enabled, an external commissioner (e.g., a smartphone app) can use the ephemeral key to establish a DTLS session with the Border Router, obtain the network credentials, and join the Thread mesh.

35.11.9 Country Code and Channel Management

ThreadNetworkCountryCode coordinates with Wi-Fi and Telephony country code modules to determine the operating region, which affects allowed channels and transmit power. The service is initialised after both modules are ready:

// Source: ThreadNetworkService.java
public void onBootPhase(int phase) {
    if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
        mControllerService = ThreadNetworkControllerService.newInstance(
                mContext, mPersistentSettings,
                () -> mCountryCode.getCountryCode());
        mCountryCode = ThreadNetworkCountryCode.newInstance(
                mContext, mControllerService, mPersistentSettings);
    } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
        // Delayed to BOOT_COMPLETED because Wi-Fi/Telephony
        // country code modules need to be ready first
        mCountryCode.initialize();
    }
}

35.11.10 Key Source Files

Class Path
ThreadNetworkManager packages/modules/Connectivity/thread/framework/java/android/net/thread/ThreadNetworkManager.java
ThreadNetworkController packages/modules/Connectivity/thread/framework/java/android/net/thread/ThreadNetworkController.java
ActiveOperationalDataset packages/modules/Connectivity/thread/framework/java/android/net/thread/ActiveOperationalDataset.java
ThreadNetworkService packages/modules/Connectivity/thread/service/java/com/android/server/thread/ThreadNetworkService.java
ThreadNetworkControllerService packages/modules/Connectivity/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
ThreadNetworkCountryCode packages/modules/Connectivity/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
ThreadNetworkFactory packages/modules/Connectivity/thread/service/java/com/android/server/thread/ThreadNetworkFactory.java
NsdPublisher packages/modules/Connectivity/thread/service/java/com/android/server/thread/NsdPublisher.java

35.12 Deep Dive: ConnectivityService Internals

This appendix section provides additional depth on the most critical internal mechanisms of ConnectivityService.

35.12.1 Network Agent Registration

When a transport (Wi-Fi, cellular, etc.) creates a NetworkAgent and calls register(), ConnectivityService processes the registration through handleRegisterNetworkAgent():

// Source: packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java
private void handleRegisterNetworkAgent(NetworkAgentInfo nai,
        INetworkMonitor networkMonitor) {
    if (VDBG) log("Network Monitor created for " + nai);
    // Store a copy of the declared capabilities.
    nai.setDeclaredCapabilities(nai.networkCapabilities);
    // Make sure the LinkProperties and NetworkCapabilities reflect
    // what the agent info said.
    nai.getAndSetNetworkCapabilities(mixInCapabilities(nai,
            nai.getDeclaredCapabilitiesSanitized(
                mCarrierPrivilegeAuthenticator)));
    processLinkPropertiesFromAgent(nai, nai.linkProperties);

    mNetworkAgentInfos.add(nai);
    synchronized (mNetworkForNetId) {
        mNetworkForNetId.put(nai.network.getNetId(), nai);
    }

    try {
        networkMonitor.start();
    } catch (RemoteException e) {
        e.rethrowAsRuntimeException();
    }

    if (nai.isLocalNetwork()) {
        handleUpdateLocalNetworkConfig(nai,
            null /* oldConfig */, nai.localNetworkConfig);
    }
    nai.notifyRegistered(networkMonitor);
    NetworkInfo networkInfo = nai.networkInfo;
    updateNetworkInfo(nai, networkInfo);
    maybeUpdateVpnUids(nai, null, nai.networkCapabilities);
    nai.processEnqueuedMessages(mTrackerHandler::handleMessage);
}

The registration process:

  1. Sanitize capabilities: The declared capabilities are validated and mixed with system-level overrides
  2. Process link properties: Validate routes, DNS servers, and interface
  3. Store the agent: Add to the tracking data structures
  4. Start NetworkMonitor: Begin validation probes
  5. Handle local networks: Configure forwarding for Thread, etc.
  6. Update network info: Trigger rematch if the network is connected
  7. Process enqueued messages: Deliver any messages queued during registration

35.12.2 The Rematch Algorithm

The rematchAllNetworksAndRequests() method is the heart of network selection. It runs every time something changes that could affect which network best satisfies each request.

// Source: packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java
private void rematchAllNetworksAndRequests() {
    rematchNetworksAndRequests(getNrisFromGlobalRequests());
}

private void rematchNetworksAndRequests(
        @NonNull final Set<NetworkRequestInfo> networkRequests) {
    ensureRunningOnConnectivityServiceThread();
    final long start = SystemClock.elapsedRealtime();
    final NetworkReassignment changes =
        computeNetworkReassignment(networkRequests);
    final long computed = SystemClock.elapsedRealtime();
    applyNetworkReassignment(changes, start);
    final long applied = SystemClock.elapsedRealtime();
    issueNetworkNeeds();
    final long end = SystemClock.elapsedRealtime();
    if (VDBG || DDBG) {
        log(String.format(
            "Rematched networks [computed %dms] [applied %dms] [issued %d]",
            computed - start, applied - computed, end - applied));
        log(changes.debugString());
    }
}

The rematch is a three-phase process:

Phase 1: Compute reassignment (computeNetworkReassignment)

  • For each network request, find the best network that satisfies it
  • Compare capabilities, score, and other attributes
  • Build a NetworkReassignment object describing all changes

Phase 2: Apply reassignment (applyNetworkReassignment)

  • Update the default network if it changed
  • Send callbacks to applications (onAvailable, onLost, etc.)
  • Configure forwarding rules for local networks
  • Update linger timers

Phase 3: Issue network needs (issueNetworkNeeds)

  • Notify network factories about unsatisfied requests
  • Allow factories to bring up new networks if needed

35.12.3 NetworkReassignment Data Structure

The NetworkReassignment class accumulates all changes that result from a rematch into a single atomic operation:

// Source: packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java
private static class NetworkReassignment {
    static class RequestReassignment {
        @NonNull public final NetworkRequestInfo mNetworkRequestInfo;
        @Nullable public final NetworkRequest mOldNetworkRequest;
        @Nullable public final NetworkRequest mNewNetworkRequest;
        @Nullable public final NetworkAgentInfo mOldNetwork;
        @Nullable public final NetworkAgentInfo mNewNetwork;
        // ...

        public String toString() {
            final NetworkRequest requestToShow = null != mNewNetworkRequest
                    ? mNewNetworkRequest
                    : mNetworkRequestInfo.mRequests.get(0);
            return requestToShow.requestId + " : "
                    + (null != mOldNetwork
                        ? mOldNetwork.network.getNetId() : "null")
                    + " -> "
                    + (null != mNewNetwork
                        ? mNewNetwork.network.getNetId() : "null");
        }
    }

    @NonNull private final ArrayList<RequestReassignment>
        mReassignments = new ArrayList<>();
    // ...
}

35.12.4 Default Network Selection

When the default network changes, ConnectivityService must update the kernel's default routing and notify all interested applications:

// Source: packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java
private void makeDefaultNetwork(
        @Nullable final NetworkAgentInfo newDefaultNetwork) {
    try {
        if (null != newDefaultNetwork) {
            mNetd.networkSetDefault(
                newDefaultNetwork.network.getNetId());
        } else {
            mNetd.networkClearDefault();
        }
    } catch (RemoteException | ServiceSpecificException e) {
        loge("Exception setting default network :" + e);
    }
}

The full default network change process:

sequenceDiagram
    participant CS as ConnectivityService
    participant NETD as netd
    participant DNSR as DnsResolver
    participant APPS as Applications
    participant KERNEL as Kernel

    CS->>CS: rematchAllNetworksAndRequests()
    Note over CS: New best network found
    CS->>NETD: networkSetDefault(newNetId)
    NETD->>KERNEL: Update default routing rules
    CS->>DNSR: setDefaultNetwork(newNetId)
    DNSR->>DNSR: Switch DNS cache to new network
    CS->>APPS: onAvailable(newNetwork)
    CS->>APPS: onLosing(oldNetwork, lingerMs)
    Note over CS: After linger timeout
    CS->>APPS: onLost(oldNetwork)
    CS->>CS: teardownUnneededNetwork(oldNai)

35.12.5 ConnectivityFlags: Feature Flags

ConnectivityService uses runtime feature flags to enable or disable specific behaviors, allowing gradual rollouts and quick rollbacks:

// Source: packages/modules/Connectivity/service/src/com/android/server/connectivity/ConnectivityFlags.java
public final class ConnectivityFlags {
    // Boot namespace for this module
    public static final String NAMESPACE_TETHERING_BOOT = "tethering_boot";

    // Feature flags
    public static final String REQUEST_RESTRICTED_WIFI =
            "request_restricted_wifi";
    public static final String INGRESS_TO_VPN_ADDRESS_FILTERING =
            "ingress_to_vpn_address_filtering";
    public static final String BACKGROUND_FIREWALL_CHAIN =
            "background_firewall_chain";
    public static final String CELLULAR_DATA_INACTIVITY_TIMEOUT =
            "cellular_data_inactivity_timeout";
    public static final String WIFI_DATA_INACTIVITY_TIMEOUT =
            "wifi_data_inactivity_timeout";
    public static final String DELAY_DESTROY_SOCKETS =
            "delay_destroy_sockets";
    public static final String QUEUE_CALLBACKS_FOR_FROZEN_APPS =
            "queue_callbacks_for_frozen_apps";
    public static final String CLOSE_QUIC_CONNECTION =
            "close_quic_connection";
    public static final String CONSTRAINED_DATA_SATELLITE_METRICS =
            "constrained_data_satellite_metrics";
    public static final String USE_SATELLITE_REPORTED_SUSPENDED_AND_ROAMING =
            "use_satellite_reported_suspended_and_roaming";
    // ...
}

Notable feature flags:

Flag Purpose
QUEUE_CALLBACKS_FOR_FROZEN_APPS Queue network callbacks for frozen apps
DELAY_DESTROY_SOCKETS Delay socket destruction on network switch
CLOSE_QUIC_CONNECTION Close QUIC connections on network change
BACKGROUND_FIREWALL_CHAIN Background firewall chain enforcement
CELLULAR_DATA_INACTIVITY_TIMEOUT Cellular idle timeout
WIFI_DATA_INACTIVITY_TIMEOUT Wi-Fi idle timeout
INGRESS_TO_VPN_ADDRESS_FILTERING Filter ingress to VPN addresses
REQUEST_RESTRICTED_WIFI Allow restricted Wi-Fi requests

35.12.6 DNS Resolver Unsolicited Events

ConnectivityService registers for unsolicited events from the DNS resolver to monitor DNS health and handle NAT64 prefix changes:

// Source: packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java
// DnsResolverUnsolicitedEventCallback handles:
//   onDnsHealthEvent - DNS query success/failure rates
//   onNat64PrefixEvent - NAT64 prefix discovery/removal
//   onPrivateDnsValidationEvent - Private DNS server validation
@Override
public void onDnsHealthEvent(final DnsHealthEventParcel event) {
    NetworkAgentInfo nai = getNetworkAgentInfoForNetId(event.netId);
    if (nai != null && nai.satisfies(
            mDefaultRequest.mRequests.get(0))) {
        nai.networkMonitor().notifyDnsResponse(event.healthResult);
    }
}

@Override
public void onNat64PrefixEvent(final Nat64PrefixEventParcel event) {
    mHandler.post(() -> handleNat64PrefixEvent(
        event.netId, event.prefixOperation,
        event.prefixAddress, event.prefixLength));
}

35.12.7 Blocked Reasons

ConnectivityService tracks why network access might be blocked for a specific UID, using a bitmask of reasons:

// From ConnectivityManager:
import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
import static android.net.ConnectivityManager.BLOCKED_REASON_LOCKDOWN_VPN;
import static android.net.ConnectivityManager.BLOCKED_REASON_NETWORK_RESTRICTED;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;

Blocked reasons and their triggers:

Reason Trigger
BLOCKED_REASON_NONE Traffic is not blocked
BLOCKED_REASON_APP_BACKGROUND App is in background with restrictions
BLOCKED_REASON_LOCKDOWN_VPN VPN lockdown active, app not in VPN
BLOCKED_REASON_NETWORK_RESTRICTED Network is restricted
BLOCKED_METERED_REASON_* Metered network restrictions (data saver)

Applications receive blocked status changes through the onBlockedStatusChanged callback.


35.13 Deep Dive: Wi-Fi Internals

35.13.1 ActiveModeWarden: Wi-Fi Mode Management

The ActiveModeWarden manages the Wi-Fi chip's operating modes. Modern Wi-Fi chips support concurrent operation in multiple modes (STA + STA, STA + AP, STA + P2P, etc.), and the warden coordinates these.

graph TD
    WARDEN["ActiveModeWarden"]
    CMM1["ConcreteClientModeManager<br/>(Primary STA)"]
    CMM2["ConcreteClientModeManager<br/>(Secondary STA)"]
    SAM["SoftApManager<br/>(AP Mode)"]
    CMI1["ClientModeImpl<br/>(wlan0)"]
    CMI2["ClientModeImpl<br/>(wlan1)"]

    WARDEN --> CMM1
    WARDEN --> CMM2
    WARDEN --> SAM
    CMM1 --> CMI1
    CMM2 --> CMI2

35.13.2 Client Roles

Each ClientModeManager operates in a specific role:

// Source: packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java
// ROLE_CLIENT_PRIMARY - the main STA interface (handles default connection)
// ROLE_CLIENT_LOCAL_ONLY - local-only connection (P2P, local hotspot)
// ROLE_CLIENT_SECONDARY_LONG_LIVED - persistent secondary (dual-STA)
// ROLE_CLIENT_SECONDARY_TRANSIENT - temporary secondary (make-before-break)
// ROLE_CLIENT_SCAN_ONLY - scan-only mode (no connection)

The dual-STA architecture enables:

  • Make-Before-Break (MBB): Connect to a new network before disconnecting from the old one, eliminating connectivity gaps during handover
  • Dual simultaneous connections: Connect to two different networks at once (e.g., Internet + IoT network)
  • Wi-Fi Direct while connected: Maintain STA connection during P2P

35.13.3 Wi-Fi Scanning Architecture

Wi-Fi scanning is a multi-layered process:

graph TD
    subgraph "Scan Requestors"
        APP_SCAN["App Scan Request"]
        AUTO_SCAN["Auto-join Scan"]
        CONN_SCAN["Connectivity Scan"]
        PNO["Preferred Network Offload"]
    end

    subgraph "Scan Coordination"
        PROXY["ScanRequestProxy"]
        SCHED["WifiScanningScheduler"]
    end

    subgraph "Execution"
        SCANNER["WifiScanner"]
        WNATIVE["WifiNative"]
        DRIVER["Wi-Fi Driver"]
    end

    APP_SCAN --> PROXY
    AUTO_SCAN --> SCHED
    CONN_SCAN --> SCHED
    PNO --> WNATIVE
    PROXY --> SCANNER
    SCHED --> SCANNER
    SCANNER --> WNATIVE
    WNATIVE --> DRIVER

Preferred Network Offload (PNO): Hardware-offloaded scanning that runs even when the CPU is asleep. The Wi-Fi firmware scans for preferred networks and wakes the CPU only when a match is found.

35.13.4 Wi-Fi Security Protocols

ClientModeImpl supports a comprehensive set of security protocols:

Protocol Key Authentication Introduced
Open None None Original
WEP Shared key Pre-shared key Original (deprecated)
WPA-Personal TKIP/AES PSK Android 1.0
WPA2-Personal AES PSK Android 1.0
WPA3-Personal AES SAE Android 10
WPA2-Enterprise AES 802.1X/EAP Android 1.0
WPA3-Enterprise AES-256 802.1X/EAP Android 10
OWE AES Opportunistic Android 10
WAPI SMS4 Certificate/PSK Android 11
DPP AES Device Provisioning Android 10

35.13.5 Wi-Fi Network Scoring Details

The WifiNetworkSelector uses a sophisticated scoring algorithm:

graph TD
    SCAN["Scan Results"]
    FILTER["Filter:<br/>- Security compatible<br/>- BSSID not blocked<br/>- Signal above threshold"]
    SCORE["Score each candidate:<br/>+ RSSI score (band-weighted)<br/>+ Security bonus<br/>+ Saved network bonus<br/>+ Suggestion bonus<br/>+ Current network bonus<br/>- Penalty for recent failures"]
    SELECT["Select highest score"]
    CONNECT["Initiate connection"]

    SCAN --> FILTER
    FILTER --> SCORE
    SCORE --> SELECT
    SELECT --> CONNECT

35.14 Deep Dive: netd Internals

35.14.1 netd Process Architecture

The netd process runs as root (or with CAP_NET_ADMIN) and consists of several threads:

graph TD
    subgraph "netd Process"
        MAIN["Main Thread<br/>(Binder server)"]
        NNS["NetdNativeService<br/>(AIDL Binder)"]
        NHW["NetdHwService<br/>(HIDL/AIDL HAL)"]
        FWS["FwmarkServer<br/>(UNIX socket)"]
        NLH["NetlinkHandler<br/>(Netlink listener)"]
        DNS["DnsResolver<br/>(shared library)"]
    end

    subgraph "Clients"
        CS_C["ConnectivityService"]
        BIONIC_C["Bionic libc"]
        KERNEL_C["Kernel Events"]
    end

    CS_C -->|"Binder"| NNS
    BIONIC_C -->|"UNIX socket"| FWS
    KERNEL_C -->|"Netlink"| NLH
    NNS --> NHW

35.14.2 IptablesRestoreController

Rather than executing individual iptables commands (which would require forking a process for each rule change), netd uses iptables-restore to batch rule updates:

// Source: system/netd/server/IptablesRestoreController.cpp
// The controller maintains persistent stdin/stdout pipes to iptables-restore
// processes, sending batches of rules and reading back results.

This approach provides:

  • Atomicity: Multiple rules are applied as a single transaction
  • Performance: No process fork overhead per rule
  • Error handling: Failures in a batch are reported as a group

35.14.3 SockDiag: Socket Diagnostics

The SockDiag class uses Linux's SOCK_DIAG netlink interface to enumerate and manipulate kernel sockets:

Source file: system/netd/server/SockDiag.cpp

This is used for:

  • Socket destruction: When VPN is enabled/disabled or networks change, existing sockets must be destroyed to force reconnection through the new path
  • Connection tracking: Enumerate TCP connections for diagnostics
  • UID-based socket operations: Target sockets by application UID

35.14.4 WakeupController

The WakeupController tracks which network packets wake the device from sleep:

Source file: system/netd/server/WakeupController.cpp

It uses NFLOG (netfilter logging) to capture packet metadata when the device wakes up, helping identify applications that cause excessive wakeups.

35.14.5 TcpSocketMonitor

The TcpSocketMonitor polls TCP socket statistics at regular intervals to detect network quality issues:

Source file: system/netd/server/TcpSocketMonitor.cpp

Monitored metrics include:

  • Retransmission count
  • Round-trip time (RTT)
  • Send congestion window size
  • Packet loss rate

35.15 Deep Dive: NetworkMonitor Validation

35.15.1 Probe Configuration

NetworkMonitor's probe behavior is highly configurable through DeviceConfig and resource overlays:

// Source: packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
// Configurable probe URLs
// CAPTIVE_PORTAL_HTTPS_URL - HTTPS validation URL
// CAPTIVE_PORTAL_HTTP_URL - HTTP captive portal probe URL
// CAPTIVE_PORTAL_FALLBACK_URL - Fallback probe URL
// CAPTIVE_PORTAL_OTHER_FALLBACK_URLS - Additional fallback URLs
// CAPTIVE_PORTAL_OTHER_HTTPS_URLS - Additional HTTPS URLs
// CAPTIVE_PORTAL_OTHER_HTTP_URLS - Additional HTTP URLs
Configuration Default Purpose
HTTP probe URL connectivitycheck.gstatic.com/generate_204 Primary portal detection
HTTPS probe URL www.google.com/generate_204 TLS verification
Probe timeout 10 seconds Maximum wait per probe
DNS timeout 5 seconds DNS resolution timeout
Evaluation interval Variable Time between validation attempts
Data stall DNS threshold 5 consecutive DNS timeout threshold
Data stall TCP interval 2 seconds TCP metrics polling interval

35.15.2 Multi-URL Probing

To reduce false positives, NetworkMonitor supports probing multiple URLs simultaneously:

graph TD
    START["Start Validation"]
    HTTP["HTTP Probe<br/>(generate_204)"]
    HTTPS["HTTPS Probe<br/>(google.com)"]
    FB1["Fallback Probe 1"]
    FB2["Fallback Probe 2"]

    START --> HTTP
    START --> HTTPS

    HTTP -->|"204"| PASS_H["HTTP Pass"]
    HTTP -->|"302"| PORTAL["Captive Portal"]
    HTTP -->|"timeout"| FB1
    HTTP -->|"200 with content"| PORTAL

    HTTPS -->|"204"| PASS_S["HTTPS Pass"]
    HTTPS -->|"TLS error"| PARTIAL["Partial Connectivity"]

    FB1 -->|"204"| PASS_F["Fallback Pass"]
    FB1 -->|"fail"| FB2

    PASS_H --> COMBINE["Combine Results"]
    PASS_S --> COMBINE
    PASS_F --> COMBINE
    PORTAL --> RESULT["Final Result"]
    PARTIAL --> RESULT
    COMBINE --> RESULT

35.15.3 Private DNS Validation

When Private DNS (DoT/DoH) is configured, NetworkMonitor performs additional validation:

// Source: NetworkMonitor.java
// Private DNS validation probes the configured DoT/DoH server with a
// synthetic DNS query to verify it is reachable and functioning.
// The probe hostname has the format:
//   <random>-dnsotls-ds.metric.gstatic.com
// This ensures the probe goes through the actual DNS resolution path.

The validation process:

  1. Resolve the private DNS hostname to get server IPs
  2. Establish a TLS connection to port 853 (DoT) or HTTPS (DoH)
  3. Send a synthetic DNS query
  4. Verify the response is valid
  5. If successful, mark private DNS as validated
  6. If failed, mark as broken and optionally fall back to plaintext

35.15.4 Captive Portal User Flow

When a captive portal is detected, the system guides the user through sign-in:

sequenceDiagram
    participant NM as NetworkMonitor
    participant CS as ConnectivityService
    participant NM_SVC as NotificationManager
    participant USER as User
    participant CPA as CaptivePortalLogin Activity
    participant PORTAL as Captive Portal

    NM->>CS: reportCaptivePortal(redirectUrl)
    CS->>NM_SVC: Show "Sign in to network" notification
    USER->>NM_SVC: Tap notification
    NM_SVC->>CPA: Launch CaptivePortalLogin
    CPA->>PORTAL: Load sign-in page in WebView
    USER->>PORTAL: Complete sign-in
    PORTAL->>CPA: Redirect to success
    CPA->>NM: APP_RETURN_DISMISSED
    NM->>NM: Re-validate network
    NM->>CS: reportNetworkConnectivity(true)
    CS->>CS: Update capabilities (VALIDATED)

35.16 Deep Dive: IPv6-Only Networks and CLAT

35.16.1 NAT64 / CLAT Architecture

Android supports IPv6-only networks through a combination of DNS64 (synthetic AAAA records) and CLAT (Client-side Local Address Translation). CLAT runs in the connectivity module and translates IPv4 packets to IPv6 for transmission over the IPv6-only network.

Source directory: packages/modules/Connectivity/clatd/

graph LR
    subgraph "Application"
        APP["IPv4 App<br/>(connects to 203.0.113.1)"]
    end

    subgraph "CLAT (clatd)"
        CLAT_IN["clat4 interface<br/>(192.0.0.4)"]
        XLAT["IPv4 -> IPv6<br/>Translation"]
    end

    subgraph "Network"
        V6_NET["IPv6-only Network"]
        NAT64["NAT64 Gateway<br/>(ISP)"]
        V4_DST["IPv4 Destination<br/>(203.0.113.1)"]
    end

    APP -->|"IPv4 packet<br/>dst: 203.0.113.1"| CLAT_IN
    CLAT_IN --> XLAT
    XLAT -->|"IPv6 packet<br/>dst: 64:ff9b::203.0.113.1"| V6_NET
    V6_NET --> NAT64
    NAT64 -->|"IPv4 packet<br/>dst: 203.0.113.1"| V4_DST

CLAT provides:

  • Transparent IPv4 connectivity over IPv6-only networks
  • Per-process CLAT interface (v4-wlan0, v4-rmnet0)
  • BPF-accelerated translation for performance
  • Automatic configuration via DNS64 prefix discovery

35.16.2 DNS64 Prefix Discovery

The DnsResolver discovers the NAT64 prefix by querying for the synthetic AAAA record of ipv4only.arpa:

sequenceDiagram
    participant DR as DnsResolver
    participant DNS as DNS Server
    participant CS as ConnectivityService

    DR->>DNS: AAAA query for ipv4only.arpa
    DNS-->>DR: AAAA: 64:ff9b::192.0.0.170
    DR->>DR: Extract prefix: 64:ff9b::/96
    DR->>CS: onNat64PrefixEvent(prefix)
    CS->>CS: Start CLAT on interface

35.17 Deep Dive: Tethering Offload

35.17.1 Hardware Offload HAL

In addition to BPF-based offload, Android supports hardware tethering offload through a HAL interface:

Source file: packages/modules/Connectivity/Tethering/src/com/android/networkstack/tethering/OffloadHalAidlImpl.java

The hardware offload HAL allows the modem or network processor to handle tethering forwarding entirely in hardware, achieving maximum throughput with zero CPU involvement.

graph TD
    subgraph "Software Path"
        PKT_SW["Packet"] --> KERNEL_SW["Kernel IP Stack"]
        KERNEL_SW --> IPTABLES_SW["iptables NAT"]
        IPTABLES_SW --> OUT_SW["Output"]
    end

    subgraph "BPF Path"
        PKT_BPF["Packet"] --> BPF_P["BPF Program"]
        BPF_P --> OUT_BPF["Output"]
    end

    subgraph "Hardware Path"
        PKT_HW["Packet"] --> HW_ENGINE["HW Offload Engine"]
        HW_ENGINE --> OUT_HW["Output"]
    end

    style PKT_SW fill:#ffcdd2
    style PKT_BPF fill:#fff9c4
    style PKT_HW fill:#c8e6c9

Performance comparison:

  • Software path: ~500 Mbps (CPU-bound)
  • BPF path: ~2 Gbps (kernel bypass)
  • Hardware path: Line rate (zero CPU)

35.17.2 Connection Tracking Integration

The BpfCoordinator integrates with the Linux connection tracking subsystem (conntrack) to monitor active NAT sessions:

// Source: packages/modules/Connectivity/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
import static com.android.net.module.util.ip.ConntrackMonitor.ConntrackEvent;

Conntrack events trigger BPF map updates:

  • New connection: Install forwarding entry in BPF map
  • Connection update: Refresh timeout, update counters
  • Connection destroy: Remove entry from BPF map

35.18 Deep Dive: QUIC and Modern Protocols

35.18.1 QUIC Connection Management

ConnectivityService includes handling for QUIC (HTTP/3) connections during network transitions:

// Source: ConnectivityFlags.java
public static final String CLOSE_QUIC_CONNECTION =
        "close_quic_connection";

Unlike TCP, QUIC connections use UDP and may not be properly reset during network changes. The CLOSE_QUIC_CONNECTION flag enables explicit QUIC connection termination to prevent stale connections.

35.18.2 Socket Destruction on Network Change

When the default network changes, ConnectivityService can destroy sockets on the old network to force applications to reconnect:

// Source: ConnectivityFlags.java
public static final String DELAY_DESTROY_SOCKETS =
        "delay_destroy_sockets";

The socket destruction process:

  1. Identify sockets bound to the old network (via UID range and fwmark)
  2. Send RST for TCP sockets using the SockDiag interface
  3. For QUIC, send CONNECTION_CLOSE if the flag is enabled
  4. Optionally delay destruction to allow graceful migration

35.19 Deep Dive: Satellite Connectivity

35.19.1 Satellite Network Support

Android includes support for satellite-based connectivity, a feature added for emergency and remote scenarios:

// Source: ConnectivityService.java imports
import static android.net.NetworkCapabilities.TRANSPORT_SATELLITE;

// Source: ConnectivityFlags.java
public static final String CONSTRAINED_DATA_SATELLITE_METRICS =
        "constrained_data_satellite_metrics";
public static final String USE_SATELLITE_REPORTED_SUSPENDED_AND_ROAMING =
        "use_satellite_reported_suspended_and_roaming";

Satellite networks are treated as a distinct transport type with special characteristics:

  • High latency: Round-trip times of 500ms+ (GEO) to 20-50ms (LEO)
  • Bandwidth constrained: Limited throughput
  • Intermittent: May be suspended during satellite hand-off
  • Metered: Typically billed per byte

ConnectivityService handles satellite-specific states like suspended and roaming differently from terrestrial networks, using carrier-reported status.


35.20 Deep Dive: Thread Mesh Networking

35.20.1 Thread Network Support

Android includes support for Thread, a low-power mesh networking protocol designed for IoT devices:

// Source: ConnectivityService.java imports
import static android.net.NetworkCapabilities.TRANSPORT_THREAD;

// Source: ConnectivityFlags.java imports
import static com.android.server.connectivity.ConnectivityFlags.SATISFIED_BY_LOCAL_NETWORK_METRICS;

Thread networks are classified as local networks (NET_CAPABILITY_LOCAL_NETWORK) and are managed through the Thread Network module:

// Source: packages/modules/Connectivity/thread/

The Thread integration enables:

  • Border Router functionality (Thread <-> Wi-Fi/Ethernet)
  • Matter protocol support for smart home devices
  • IPv6 mesh networking with 6LoWPAN
  • Low-power operation for battery-powered devices

35.21 Deep Dive: Network Permissions Model

35.21.1 Permission Hierarchy

Android's network access is governed by a multi-layered permission model:

graph TD
    subgraph "Application Permissions"
        INTERNET["android.permission.INTERNET<br/>(normal permission, auto-granted)"]
        NET_STATE["ACCESS_NETWORK_STATE<br/>(normal permission)"]
        WIFI_STATE["ACCESS_WIFI_STATE<br/>(normal permission)"]
        CHANGE_NET["CHANGE_NETWORK_STATE<br/>(normal permission)"]
        CHANGE_WIFI["CHANGE_WIFI_STATE<br/>(normal permission)"]
        FINE_LOC["ACCESS_FINE_LOCATION<br/>(dangerous permission)"]
    end

    subgraph "System Permissions"
        NET_ADMIN["NETWORK_SETTINGS<br/>(signature/privileged)"]
        NET_STACK["NETWORK_STACK<br/>(signature/privileged)"]
        MAINLINE["MAINLINE_NETWORK_STACK<br/>(module permission)"]
        CONN_INTERNAL["CONNECTIVITY_INTERNAL<br/>(signature)"]
    end

    INTERNET --> |"Required for"| SOCKET["Socket creation"]
    NET_STATE --> |"Required for"| QUERY["Query network state"]
    FINE_LOC --> |"Required for"| SCAN["Wi-Fi scan results"]
    NET_ADMIN --> |"Required for"| CONFIG["Network configuration"]
    NET_STACK --> |"Required for"| STACK["NetworkStack operations"]

35.21.2 INTERNET Permission Enforcement

The INTERNET permission is unique in Android: it is enforced at the kernel level through the inet supplementary group (GID 3003). When an app has the permission, its process is given this group at fork time. The kernel's paranoid network security (configured via /proc/sys/net/) restricts socket creation to processes with the appropriate GID.

// From system/netd/server/NetdNativeService.h
binder::Status trafficSetNetPermForUids(
    int32_t permission,
    const std::vector<int32_t>& uids) override;

Apps without INTERNET permission literally cannot create AF_INET or AF_INET6 sockets -- the socket() system call returns EACCES.

35.21.3 Location Permission for Wi-Fi Scans

Starting with Android 8.0, accessing Wi-Fi scan results requires location permission because BSSID/SSID data can be used for location tracking. ConnectivityService and WifiService redact location-sensitive data based on the caller's permission level:

// Source: packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
// Redaction levels for NetworkCapabilities
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
import static android.net.NetworkCapabilities.REDACT_FOR_THREAD_NETWORK_PRIVILEGED;
import static android.net.NetworkCapabilities.REDACT_NONE;

35.21.4 UID-Based Network Isolation

Each socket in Android is tagged with its owner's UID. This enables:

  • Per-UID firewall rules (allow/deny network access)
  • Per-UID traffic accounting (data usage tracking)
  • Per-UID VPN routing (per-app VPN)
  • Per-UID network selection (enterprise profiles)

The UID information flows from:

  1. Process creation (kernel assigns UID)
  2. Socket creation (kernel tags socket with UID via cgroup)
  3. BPF programs (read UID from socket, apply policy)
  4. iptables/nftables (match on UID for filtering)

35.22 Deep Dive: Multicast and mDNS

35.22.1 mDNS Service Discovery

mDNS (multicast DNS) powers Android's Network Service Discovery (NSD) API. The implementation has moved into the Connectivity module: NsdService (packages/modules/Connectivity/service-t/src/com/android/server/NsdService.java) drives a pure-Java mDNS stack under packages/modules/Connectivity/service-t/src/com/android/server/connectivity/mdns/ (MdnsDiscoveryManager, MdnsAdvertiser, MdnsSocketProvider, and the packet reader/writer classes), so discovery and advertisement no longer depend on a native daemon. A legacy MDnsService still ships in system/netd/server/MDnsService.cpp as the compatibility backend, but new devices use the in-module Java backend.

mDNS enables:

  • Device discovery on local networks (e.g., Chromecast, printers)
  • Service advertisement (NSD - Network Service Discovery API)
  • Zero-configuration networking

In Android 17, NSD discovery is also gated by a per-app, per-service-type access model backed by the ACCESS_LOCAL_NETWORK permission and a system "picker" UI. That access-control flow is covered in Section 35.28.

35.22.2 Multicast Routing for Local Networks

ConnectivityService manages multicast routing for local networks (Thread, etc.):

// Source: ConnectivityService.java
import static android.net.MulticastRoutingConfig.FORWARD_NONE;

The multicast routing configuration controls how multicast packets are forwarded between local network interfaces and upstream networks, enabling IoT device communication across network boundaries.


35.23 Deep Dive: DSCP Policy

35.23.1 Differentiated Services Code Point (DSCP) Marking

ConnectivityService supports DSCP policy management for QoS (Quality of Service) marking:

// Source: ConnectivityService.java
import com.android.server.connectivity.DscpPolicyTracker;

DSCP policies allow applications to mark their traffic for priority handling by the network infrastructure. The DscpPolicyTracker manages per-network DSCP rules through traffic control (TC) mechanisms.

// NetworkAgent DSCP events
public static final int EVENT_REMOVE_ALL_DSCP_POLICIES = BASE + /* ... */;

35.24 Deep Dive: QoS and Keepalive

35.24.1 Socket Keepalive

Android provides hardware-offloaded socket keepalive for maintaining NAT bindings and detecting connection failures:

// Source: packages/modules/Connectivity/framework/src/android/net/NetworkAgent.java
// Keepalive management messages
public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11;
public static final int CMD_STOP_SOCKET_KEEPALIVE = BASE + 12;
public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13;
public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16;
public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17;

Hardware keepalive offload:

  1. The application requests a keepalive via SocketKeepalive
  2. ConnectivityService assigns a hardware slot
  3. The NetworkAgent configures the hardware to send periodic packets
  4. For TCP, a packet filter is also installed to handle ACK responses
  5. The CPU remains asleep; only the network hardware is active
sequenceDiagram
    participant App as Application
    participant CS as ConnectivityService
    participant KT as KeepaliveTracker
    participant NA as NetworkAgent
    participant HW as Wi-Fi Hardware

    App->>CS: startNattKeepalive(network, interval)
    CS->>KT: handleStartKeepalive()
    KT->>NA: CMD_START_SOCKET_KEEPALIVE(slot, interval)
    NA->>HW: Configure keepalive offload
    HW->>HW: Send keepalive packet every N seconds
    Note over HW: CPU sleeps, hardware maintains NAT binding
    HW->>NA: EVENT_SOCKET_KEEPALIVE(error)
    NA->>KT: Report status
    KT->>App: Callback with result

35.24.2 QoS Callbacks

ConnectivityService supports per-flow QoS callbacks for applications that need to monitor quality metrics:

// Source: NetworkAgent.java
public static final int CMD_REGISTER_QOS_CALLBACK = BASE + 20;

QoS callbacks provide:

  • EPS bearer QoS attributes (LTE)
  • NR QoS session attributes (5G)
  • Per-flow bandwidth and latency information

35.25 Network Types and Their Android Representation

35.25.1 Complete Transport-to-Implementation Mapping

Transport Interface Pattern NetworkAgent Location HAL
Wi-Fi wlan0, wlan1 WifiNetworkAgent (in ClientModeImpl) Wi-Fi AIDL HAL
Cellular rmnet0, rmnet1 TelephonyNetworkAgent (in TelephonyNetworkFactory) Radio AIDL HAL
Ethernet eth0 EthernetNetworkAgent None (kernel driver)
Bluetooth bt-pan BluetoothNetworkAgent (in BluetoothPan) Bluetooth AIDL HAL
VPN tun0, ipsec0 Vpn-internal agent None (kernel TUN)
Wi-Fi Aware aware0 WifiAwareNetworkAgent Wi-Fi AIDL HAL
LoWPAN lowpan0 LowpanNetworkAgent LoWPAN HAL
Thread thread0 ThreadNetworkAgent Thread HAL
Satellite sat0 SatelliteNetworkAgent Satellite HAL
Test test0 TestNetworkAgent None

35.25.2 Network Lifecycle Complete Flow

The complete lifecycle of a network from creation to destruction:

graph TD
    NF["NetworkFactory.register()"] -->|"Advertise capabilities"| CS1["CS: Track factory"]
    APP["App: requestNetwork()"] --> CS2["CS: File request"]
    CS2 -->|"Match factory"| NF2["Factory: CMD_REQUEST_NETWORK"]
    NF2 --> NA_CREATE["Create NetworkAgent"]
    NA_CREATE --> NA_REG["NetworkAgent.register()"]
    NA_REG --> CS3["CS: handleRegisterNetworkAgent()"]
    CS3 --> NM_START["NetworkMonitor.start()"]
    NM_START --> PROBE["Validation probes"]
    PROBE -->|"Valid"| CS4["CS: NET_CAPABILITY_VALIDATED"]
    CS4 --> REMATCH["CS: rematchAllNetworksAndRequests()"]
    REMATCH --> NOTIFY["CS: Notify apps (onAvailable)"]
    NOTIFY --> ACTIVE["Network is ACTIVE"]
    ACTIVE -->|"Score decrease or<br/>better network"| LINGER["LINGERING"]
    LINGER -->|"30s timeout"| TEARDOWN["CS: teardownUnneededNetwork()"]
    LINGER -->|"New request matches"| ACTIVE
    ACTIVE -->|"Transport disconnect"| UNREGISTER["NetworkAgent.unregister()"]
    TEARDOWN --> DESTROY["CS: destroyNativeNetwork()"]
    UNREGISTER --> DESTROY
    DESTROY --> CLEANUP["CS: Cleanup routes, DNS, fwmarks"]
    CLEANUP --> NOTIFY_LOST["CS: Notify apps (onLost)"]
    NOTIFY_LOST --> DONE["Network removed"]

This comprehensive flow shows how a network moves through every stage from factory registration through active use, lingering, and eventual teardown, highlighting the interactions between the application, ConnectivityService, NetworkAgent, NetworkMonitor, and the kernel.


35.26 Multi-Proxy and Multi-PAC Framework

35.26.1 Why a New Proxy Stack

Historically Android supported a single, system-wide HTTP proxy. The proxy was either a static host/port or a PAC (Proxy Auto-Config) URL: a JavaScript file whose FindProxyForURL(url, host) function returns which proxy (if any) to use for a given request. The legacy implementation lives in packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java, which holds one global ProxyInfo and, for PAC, hands the script to a single out-of-process PAC processor. Every network and every app shares that one proxy decision.

That model breaks down on a multi-network device. A managed work network may ship its own PAC script while the personal Wi-Fi uses none; a VPN may want a different proxy than the underlying transport. Android 17 introduces a redesigned proxy stack that can run multiple independent PAC scripts simultaneously, selected by context (network, user, or application UID). The AIDL header for the new PAC processor states the goal directly: it supports "running multiple PacProcessors to allow using different PAC scripts to be used based on context, such as network, user, or application UID" (packages/modules/Connectivity/commercial/pac/multipacprocessor/src/com/android/multipacprocessor/IMultiPacService.aidl).

This is a scaffolded feature in the 17 tree: the wiring, service contracts, and coordinator are in place behind a flag, while several leaf operations are still stubbed (UnsupportedOperationException). It is documented here because the architecture is the durable part and is what an integrator needs to understand.

35.26.2 Two Cooperating Services: PacProcessor and ProxyServer

The new stack splits the job that the legacy single PAC service did into two cooperating, APEX-resident services, each running its own pool of paired {ProxyServer; PacProcessor} instances:

  • MultiPacService — runs the PacProcessor instances that actually evaluate PAC JavaScript. Package com.android.multiproxyhandler ships the proxy side; package com.android.multipacprocessor ships the PAC side (packages/modules/Connectivity/commercial/pac/multipacprocessor/src/com/android/multipacprocessor/MultiPacService.java).
  • MultiProxyService — runs local HTTP proxy servers. Its purpose, per its AIDL doc, is "running local HTTP servers [...] to provide PAC support for apps that can't directly access PacProcessors" (packages/modules/Connectivity/commercial/pac/multiproxyhandler/src/com/android/multiproxyhandler/MultiProxyService.java).

Both are exposed via AIDL stubs — packages/modules/Connectivity/commercial/pac/multipacprocessor/src/com/android/multipacprocessor/IMultiPacService.aidl and packages/modules/Connectivity/commercial/pac/multiproxyhandler/src/com/android/multiproxyhandler/IMultiProxyService.aidl. In the 17 tree both interface bodies are intentionally empty placeholders; the binding, lifecycle, and intent contracts are settled, and the RPC methods are filled in as the implementation lands. Both apps live inside the Connectivity (com.android.tethering) APEX, which is why the coordinator restricts its service lookup to packages under /apex/com.android.tethering/.

35.26.3 PacCoordinator

The orchestrator is PacCoordinator (packages/modules/Connectivity/service/src/com/android/server/connectivity/proxy/PacCoordinator.java). Its own class comment describes the role: it coordinates "the PAC script download and [manages] the MultiPacService and MultiProxyService services [...] keeps track of the PAC scripts that are currently in use and ensures that the corresponding PAC components serving these scripts are running."

It runs entirely on the ConnectivityService handler thread. Every public method opens with ensureRunningOnHandlerThread(), and the service-binding callbacks are posted back onto that handler via mConnectivityServiceHandler::post, so all state mutation is single-threaded. The two key entry points are:

// PacCoordinator.java
public void startServingPacScript(ProxyInfo proxy, Optional<Integer> netId) {
    ensureRunningOnHandlerThread();
    bindToPacComponentsIfNeeded();
    mPacDownloader.downloadPacScript(
            new PacKey(proxy.getPacFileUrl(), netId), this::onPacScriptDownloaded);
}

public void stopServingPacScript(ProxyInfo proxy, Optional<Integer> netId) { ... }

startServingPacScript() binds to both services if needed, then schedules a download of the PAC script and, on completion, asks the two services to stand up a {ProxyServer; PacProcessor} pair for that script. If a pair is already running for the key, the call is a no-op. The key is a PacKey (packages/modules/Connectivity/commercial/pac/common/src/com/android/commercial/PacKey.java), a parcelable pair of the PAC Uri and an Optional<Integer> network id — Optional.empty() (serialized as -1) means the default network. The download itself is delegated to PacDownloader (packages/modules/Connectivity/service/src/com/android/server/connectivity/proxy/PacDownloader.java), which fetches the script over the requested network so a per-network PAC is retrieved through that network.

Binding uses BIND_AUTO_CREATE | BIND_NOT_FOREGROUND. The NOT_FOREGROUND flag is deliberate: a long-running or blocking PAC evaluation (the proxy server makes blocking calls into the PAC processor while resolving a URL) must not be allowed to drag the system process into a foreground-priority state.

Description of how a PAC request flows through the coordinator:

graph TD
    PT["MultiProxyTracker<br/>(IProxyTracker)"] -->|"network proxy changed"| PC["PacCoordinator<br/>(CS handler thread)"]
    PC -->|"bindToPacComponentsIfNeeded()"| BIND["bindService<br/>(BIND_AUTO_CREATE | BIND_NOT_FOREGROUND)"]
    BIND --> MPS["MultiPacService<br/>(com.android.multipacprocessor)"]
    BIND --> MXS["MultiProxyService<br/>(com.android.multiproxyhandler)"]
    PC -->|"new PacKey(url, netId)"| DL["PacDownloader.downloadPacScript()"]
    DL -->|"onPacScriptDownloaded()"| PC
    PC -->|"start pair"| MPS
    PC -->|"start pair"| MXS
    MXS -->|"local HTTP proxy for apps"| APP["App HTTP traffic"]
    MXS -->|"blocking FindProxyForURL()"| MPS
    PC -->|"setup complete"| PT

35.26.4 MultiProxyTracker and ConnectivityService Wiring

ConnectivityService reaches the new stack through the IProxyTracker interface. The legacy ProxyTracker and the new MultiProxyTracker (packages/modules/Connectivity/service/src/com/android/server/connectivity/proxy/MultiProxyTracker.java) both implement that interface, so the rest of ConnectivityService is agnostic to which one is in use. The selection happens at construction, gated by a flag:

// ConnectivityService.java
final boolean multiProxyEnabled =
        mDeps.isMultiProxyEnabled()
                && mResources.get().getBoolean(R.bool.config_enable_multi_proxy_system);
mProxyTracker = multiProxyEnabled
        ? mDeps.makeMultiProxyTracker(mContext, mHandler)
        : /* legacy ProxyTracker */ ...;

isMultiProxyEnabled() returns com.android.tethering.flags.Flags.enableMultiProxySystem(), so both an aconfig flag and a resource overlay (config_enable_multi_proxy_system) must be set before the multi-proxy path is used; otherwise ConnectivityService falls back to the classic single ProxyTracker. MultiProxyTracker is the IProxyTracker whose updateNetworkProxy(network, newProxy, oldProxy) and updateDefaultNetworkState(...) callbacks are what ultimately drive PacCoordinator.startServingPacScript() per network. It also serves as the MultiPacProxyInstalledListener that PacCoordinator notifies once a proxy server is running and its PAC script is loaded.


35.27 The Mainline Supplicant

35.27.1 Motivation

The traditional wpa_supplicant is a vendor component: it lives on the vendor partition and is reached through the Wi-Fi supplicant HAL. Fixing a supplicant bug or shipping a new Wi-Fi feature therefore required an OEM build. Android 17 adds a second, updatable supplicant — the mainline supplicant — shipped inside the Wi-Fi APEX (com.android.wifi) so Google can update it through Play System Updates.

The mainline supplicant does not replace the vendor supplicant. It runs alongside it and acts as a thin front door: its root AIDL interface hands back the vendor supplicant for the bulk of STA/P2P work, while the mainline binary itself owns a small, evolving set of capabilities (today: Wi-Fi Aware / NAN interface management and per-user identity). This lets new supplicant-side code ship in the module while existing vendor behavior is untouched.

35.27.2 The wifi_mainline_supplicant Service

The binary is launched by an init service declared in external/wpa_supplicant_8/wpa_supplicant/aidl/config/mainline_supplicant.rc:

service wpa_supplicant_mainline /apex/com.android.wifi/bin/wpa_supplicant_mainline \
    -O/data/misc/wifi/mainline_supplicant/sockets -dd \
    -g@android:wpa_wlan0
    interface aidl wifi_mainline_supplicant
    class main
    user wifi
    group wifi net_raw net_admin
    capabilities NET_RAW NET_ADMIN
    socket wpa_wlan0 dgram 660 wifi wifi
    disabled
    oneshot

Key points: the executable lives under /apex/com.android.wifi/bin/ (inside the module, hence updatable); it registers in servicemanager under the AIDL instance name wifi_mainline_supplicant; it runs as user wifi with NET_RAW/NET_ADMIN capabilities; and it is disabled oneshot, so init does not start it at boot — the framework starts it on demand. A matching SELinux domain is defined in system/sepolicy/private/wifi_mainline_supplicant.te. The C++ side of the interface is implemented in external/wpa_supplicant_8/wpa_supplicant/aidl/mainline_supplicant.cpp.

35.27.3 IMainlineSupplicant

The AIDL contract is packages/modules/Wifi/aidl/mainline_supplicant/android/system/wifi/mainline_supplicant/IMainlineSupplicant.aidl, in package android.system.wifi.mainline_supplicant. It is explicitly an unstable interface (it ships with the module, not the platform), and it is small:

interface IMainlineSupplicant {
    @PropagateAllowBlocking ISupplicant getVendorSupplicant();
    @PropagateAllowBlocking ISupplicantNanIface addNanInterface(in String ifaceName);
    void removeNanInterface(in String ifaceName);
    void setCurrentUserIdentity(in int userId);
}
  • getVendorSupplicant() returns the standard vendor ISupplicant root — this is how STA and P2P operations get routed back to the vendor supplicant.
  • addNanInterface() / removeNanInterface() register and tear down a Wi-Fi Aware (NAN) interface (e.g. aware0), returning the vendor NAN iface object.
  • setCurrentUserIdentity() tells the supplicant which user is in the foreground so it can load that user's credential-encrypted (CE) configuration.

35.27.4 MainlineSupplicantAidlManager

The framework side is MainlineSupplicantAidlManager (packages/modules/Wifi/service/java/com/android/server/wifi/MainlineSupplicantAidlManager.java). It resolves the binder by name through a small JNI shim, packages/modules/Wifi/service/java/com/android/server/wifi/mainline_supplicant/ServiceManagerWrapper.java:

// MainlineSupplicantAidlManager.java
private static final String MAINLINE_SUPPLICANT_SERVICE_NAME = "wifi_mainline_supplicant";

protected IMainlineSupplicant getNewServiceBinderMockable() {
    return IMainlineSupplicant.Stub.asInterface(
            ServiceManagerWrapper.waitForService(MAINLINE_SUPPLICANT_SERVICE_NAME));
}

startDaemon() fetches the binder, caches the vendor ISupplicant returned by getVendorSupplicant(), and links a death recipient; terminate() asks the supplicant to exit and waits on a latch for the binder-death confirmation. NAN interface acquisition (getWifiNanIface()) wraps addNanInterface() and hands the result to the Aware stack as an AwareIfaceAidlSupplicantImpl. Death callbacks are posted onto the WifiThreadRunner, keeping the manager's state single-threaded.

Whether the mainline supplicant is used at all is decided by isServiceAvailable(), which requires several conditions to all hold:

// MainlineSupplicantAidlManager.java
public static boolean isServiceAvailable(WifiContext context) {
    boolean isEnabledInOverlay = context.getResourceCache().getBoolean(
            com.android.wifi.resources.R.bool.config_wifiMainlineSupplicantEnabled);
    return isEnabledInOverlay && (Environment.isSdkAtLeastC() || hasPcFeature(context))
            && Flags.mainlineSupplicant()
            && Environment.isMainlineSupplicantBinaryInWifiApex()
            && !isUnsupportedDevice(context);
}

That is: a resource overlay (config_wifiMainlineSupplicantEnabled) enables it, the platform is Android 17+ (or a PC form factor), the mainlineSupplicant aconfig flag is on, the binary actually exists inside the Wi-Fi APEX, and the device is not one of the resource-constrained form factors (watch, embedded, leanback/TV, automotive) that isUnsupportedDevice() excludes.

35.27.5 HAL Selection: Mainline, Vendor, or HIDL

Section 35.3.5 noted that SupplicantStaIfaceHal.createStaIfaceHalMockable() picks a backend in a preference order. The mainline supplicant slots in at the top of that order:

// SupplicantStaIfaceHal.java
if (SupplicantStaIfaceHalAidlMainlineImpl.isServiceAvailable(mContext)) {
    // AIDL Mainline implementation (supplicant shipped in the Wi-Fi APEX)
    return new SupplicantStaIfaceHalAidlMainlineImpl(...);
} else if (SupplicantStaIfaceHalAidlVendorImpl.serviceDeclared()) {
    // AIDL Vendor implementation (supplicant on the vendor partition)
    return new SupplicantStaIfaceHalAidlVendorImpl(...);
} else if (SupplicantStaIfaceHalHidlImpl.serviceDeclared()) {
    // Legacy HIDL implementation
    return new SupplicantStaIfaceHalHidlImpl(...);
}

The mainline STA implementation (packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHalAidlMainlineImpl.java) and its P2P counterpart (packages/modules/Wifi/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHalAidlMainlineImpl.java) both start the mainline daemon, call getVendorSupplicant() to obtain the vendor ISupplicant, and then drive ordinary STA/P2P operations through that vendor interface. The mainline-only surface (NAN interface management, current-user identity) is reached directly through IMainlineSupplicant. So on a device where the mainline supplicant is enabled, the framework gets an updatable supplicant process whose Aware/identity logic ships in the module while its STA/P2P mechanics still run through the vendor supplicant.


35.28 NSD Service-Access Picker and the Local-Network Permission

35.28.1 The Problem: Local Network Visibility

Until recently, any app with the INTERNET permission could use the NSD API to enumerate every mDNS service on the local network — printers, smart-home hubs, TVs, other phones. That is a meaningful privacy leak: the set of services on someone's home network is identifying. Android 17 closes it with a new runtime permission, ACCESS_LOCAL_NETWORK, plus a service-access picker: a user-driven allowlist that lets an app reach specific services it does not have blanket permission to discover.

35.28.2 DiscoveryRequest Flags

DiscoveryRequest (packages/modules/Connectivity/framework-t/src/android/net/nsd/DiscoveryRequest.java) gains three flags that tell NsdService how to behave when an app discovers services without holding ACCESS_LOCAL_NETWORK:

  • FLAG_NO_PICKER — never show the picker; fail if the app lacks the permission.
  • FLAG_SHOW_PICKER — force the picker UI; on selection the app is granted access to the chosen service even without the permission.
  • FLAG_USER_APPROVED_ONLY — show nothing; return only services the user has already approved for this app.

If neither FLAG_NO_PICKER nor FLAG_SHOW_PICKER is set, the default behavior depends on the app's permission and the USE_NSD_PICKER_WHEN_NO_LOCAL_NET_PERMISSION compatibility change.

35.28.3 Enforcement in NsdService

NsdService (packages/modules/Connectivity/service-t/src/com/android/server/NsdService.java) gates the feature on the aconfig flag FLAG_NSD_SERVICE_PICKER (mEnablePicker = mDeps.isAconfigFlagEnabled(FLAG_NSD_SERVICE_PICKER)). Two enforcement points matter:

  • Discovery: checkDiscoveryPermissionsAndPicker() decides, from the request flags and the caller's permission, whether to discover directly, refuse, or launch the picker. When the picker path is chosen, NsdService does not deliver raw results to the app — it routes them through a PickerListener whose startPicker() shows the system UI.
  • Resolve / register-callback: checkQueryServicePermissions() allows an operation either when the caller holds ACCESS_LOCAL_NETWORK or when the service is in the per-app allowlist (mAccessRepository.isServiceAllowed(uid, packageName, serviceName, serviceType)).

When the user picks a service, handleServiceSelected() records it: mAccessRepository.addAllowedService(uid, packageName, serviceName, serviceType). On client connect/disconnect, NsdService calls loadPackage() and unloadPackage() so the allowlist is paged in only while a client is active.

35.28.4 ServiceAccessRepository and ServiceAccessDb

The allowlist itself is split into an in-memory repository and a SQLite-backed store, both under packages/modules/Connectivity/service-t/src/com/android/server/connectivity/mdns/internal/:

  • ServiceAccessRepository.java is the in-memory cache and orchestrator. It maps each (uid, packageName) to the set of (serviceName, serviceType) tuples the user approved, keeps "last seen" timestamps for LRU eviction, caps entries per client, and runs entirely on the NsdService handler thread (it is explicitly not thread-safe). Its main methods are addAllowedService(), isServiceAllowed(), loadPackage(), unloadPackage(), and a maybeScheduleDatabaseMaintenance() that prunes entries for uninstalled packages.
  • ServiceAccessDb.java is the persistence layer: a small SQLite database (NsdServiceAccess.db) with a package table tracking known packages and a service_access table holding the approved (uid, package, serviceName, serviceType, last_seen_time) rows, with a cascading delete so uninstalling a package removes its grants.

Description of the access-control decision path:

graph TD
    APP["App: discoverServices(request)"] --> NSD["NsdService"]
    NSD --> CHK["checkDiscoveryPermissionsAndPicker()"]
    CHK -->|"has ACCESS_LOCAL_NETWORK"| DIRECT["Discover and deliver to app"]
    CHK -->|"FLAG_NO_PICKER, no permission"| FAIL["Reject"]
    CHK -->|"picker path"| PICK["PickerListener.startPicker()"]
    PICK --> UI["System picker UI"]
    UI -->|"user selects a service"| SEL["handleServiceSelected()"]
    SEL --> ADD["ServiceAccessRepository.addAllowedService()"]
    ADD --> DB["ServiceAccessDb (NsdServiceAccess.db)"]
    SEL -->|"return chosen service"| APP
    APP -->|"later: resolveService()"| QCHK["checkQueryServicePermissions()"]
    QCHK -->|"isServiceAllowed()? or has permission"| ALLOW["Allow resolve"]

35.28.5 NsdManager API

For apps, NsdManager (packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java) adds checkPermissionForService(serviceName, serviceType, executor, resultReceiver) so an app can ask whether a previously approved service is still accessible before resolving it. The result is one of SERVICE_PERMISSION_GRANTED or SERVICE_PERMISSION_DENIED. This pairs with the DiscoveryRequest flags so an app can drive the whole flow: discover with FLAG_USER_APPROVED_ONLY, and if a service it cares about is missing, re-discover with FLAG_SHOW_PICKER to prompt the user.


35.29 Wi-Fi USD: Unsynchronized Service Discovery

NSD/mDNS (§35.28) discovers services over an existing IP network. Android 17 adds a complementary discovery mechanism that runs lower in the stack, before any IP connectivity exists: Wi-Fi Unsynchronized Service Discovery (USD). USD is a member of the Wi-Fi Aware (NAN) family already introduced in this chapter (§35.25.1 maps aware0 to WifiAwareNetworkAgent), and it lets two devices advertise and find services over the air without first establishing a network, an IP address, or — as the name says — any prior time and channel synchronization. That makes it well suited to quickly finding a service on a new device that has just come into range.

The model is publish/subscribe. A publisher device advertises a named service and a subscriber device searches for it; when a subscriber receives a matching advertisement it exchanges follow-up messages with the publisher to carry service-specific configuration information. The implementation follows the Wi-Fi Aware Specification version 4.0.

The feature surfaces as a new system service in the Wifi mainline module rather than in frameworks/base. The manager class is packages/modules/Wifi/framework/java/android/net/wifi/usd/UsdManager.java, registered under the Context.WIFI_USD_SERVICE constant (string value "wifi_usd", declared at frameworks/base/core/java/android/content/Context.java:5484). Because the API ships in a mainline module, the manager is gated three ways:

  • A @FlaggedApi(Flags.FLAG_USD) annotation and @SystemApi visibility on UsdManager (UsdManager.java:65-66), so it is a system-app-only, flag-guarded API.
  • A minimum SDK gate: @RequiresApi(Build.VERSION_CODES.BAKLAVA) on the class, and the registration in packages/modules/Wifi/framework/java/android/net/wifi/WifiFrameworkInitializer.java only registers the service when Flags.usd() && Environment.isSdkAtLeastB().
  • A device-capability gate: registration returns null unless the device resource config_deviceSupportsWifiUsd is true, so getSystemService() yields no UsdManager on hardware that cannot do USD.

The companion classes in android.net.wifi.usd describe the discovery shape: PublishConfig/SubscribeConfig (what to advertise or look for), PublishSession/SubscribeSession with their callbacks, DiscoveryResult, and Characteristics (device USD limits, read via UsdManager.getCharacteristics()). UsdManager also exposes publish(...), subscribe(...), and publisher/subscriber availability listeners (registerPublisherStatusListener/registerSubscriberStatusListener), since USD availability depends on concurrent radio use.

The server side lives in the same module under packages/modules/Wifi/service/java/com/android/server/wifi/usd/: UsdService (the SystemService that publishes the binder under Context.WIFI_USD_SERVICE), UsdServiceImpl, UsdRequestManager, and UsdNativeManager, which drives the discovery down to the Wi-Fi HAL. So where NSD answers "what services are reachable on the network I am already on", USD answers the earlier question "what devices and services are nearby over the air, before any network exists" — the two sit at adjacent layers of the service-discovery story.


35.30 APF: The Android Packet Filter

Everything in this chapter so far assumes the application processor (AP) is awake to look at packets. That assumption is exactly what kills battery on an idle device. A phone sitting in a pocket on Wi-Fi still receives a steady drizzle of multicast and broadcast traffic that is not addressed to it: mDNS service announcements, SSDP/UPnP discovery, ARP probes from other hosts, IPv6 router advertisements, and assorted chatter from every other device on the network. Each frame the NIC receives normally wakes the AP out of its low-power sleep state just so the network stack can look at it, decide it is uninteresting, and drop it. On a busy home or office network that can be hundreds of needless wakeups per minute, and wakeups are one of the most expensive things a mobile SoC does for power.

The Android Packet Filter (APF) exists to win back that power. APF pushes a small packet-filtering program down into the Wi-Fi (or Ethernet) NIC firmware, so the firmware itself can decide whether an incoming frame is worth waking the AP for. Uninteresting multicast and broadcast is dropped while the AP stays asleep; only frames the program decides to PASS bubble up and wake the host. The firmware-resident interpreter and bytecode definition live in the Google APF hardware project at hardware/google/apf/, and the framework-side compiler that generates the programs lives in the NetworkStack mainline module at packages/modules/NetworkStack/src/android/net/apf/.

35.30.1 The Two Halves: Interpreter and Generator

APF is split across two codebases that must agree on a bytecode contract:

  • The interpreter is a tiny, freestanding C virtual machine compiled into the NIC firmware (or a vendor HAL shim). Its reference implementation is hardware/google/apf/next/apf_interpreter.c, with the bytecode and machine model defined in hardware/google/apf/next/apf.h. Versioned snapshots (v2/, v4/, v6/, v6.1/) are kept so firmware can pin a known revision.
  • The generator is Java code in the NetworkStack module that compiles an APF program at runtime from the current network state (the device's own addresses, multicast filter setting, RA filters, mDNS offload rules, and so on) and installs the resulting byte array into the firmware.

The device never ships a fixed filter. The framework regenerates and reinstalls the program whenever the relevant state changes, because the program embeds the device's current IP addresses and other live values as literal bytes.

graph LR
    subgraph AP["Application Processor (can sleep)"]
        STATE["Network state<br/>(IPv4/IPv6 addrs,<br/>multicast filter,<br/>RA + mDNS rules)"]
        GEN["ApfFilter +<br/>ApfV4/V6/V61Generator<br/>(NetworkStack)"]
        STATE --> GEN
        GEN -->|"installPacketFilter(byte[], ...)"| CTRL["IApfController"]
    end

    subgraph NIC["NIC firmware (AP asleep)"]
        VM["apf_run() interpreter<br/>(apf_interpreter.c)"]
    end

    CTRL -->|"APF program bytes<br/>via Wi-Fi HAL"| VM
    PKT["Incoming Wi-Fi/Ethernet frame"] --> VM
    VM -->|"DROP: discard,<br/>AP stays asleep"| DROP["(dropped)"]
    VM -->|"PASS: deliver,<br/>wake AP"| HOST["Host network stack"]
    VM -.->|"transmit: build + send reply<br/>(ARP/ND/mDNS offload)"| PKT2["Reply frame"]

35.30.2 The Bytecode Machine Model

The header comment in hardware/google/apf/next/apf.h defines the abstract machine precisely. An APF machine has:

  1. A read-only program of bytecode instructions.
  2. Two 32-bit registers, R0 and R1.
  3. Sixteen 32-bit temporary memory slots, cleared between packets.
  4. A read-only copy of the packet.
  5. An optional read-write transmit buffer (APFv6+), used to build a reply.

Each instruction begins with one byte whose top 5 bits are the opcode, next 2 bits encode the length of an immediate (0, 1, 2, or 4 bytes), and bottom bit selects a register. The instruction set is deliberately small: packet loads (ldb/ldh/ldw and their indexed *x variants) pull 1/2/4 bytes from a packet offset into a register; arithmetic and bitwise ops (add, and, or, sh, ...) combine a register with an immediate or the other register; and conditional jumps (jeq, jne, jgt, jlt, jset, and the byte-sequence match jbsmatch) compare R0 against a value and branch. These opcodes are enumerated as the *_OPCODE defines in apf.h (for example LDB_OPCODE, JEQ_OPCODE, JBSMATCH_OPCODE) and mirrored on the Java side by the Opcodes enum in packages/modules/NetworkStack/src/android/net/apf/BaseApfGenerator.java.

The interpreter pre-fills several of the sixteen memory slots before each run so programs do not have to recompute common values. Per the slot table in apf.h (and the memory_type union near the top of apf_interpreter.c), slot 13 holds the computed IPv4 header length, slot 14 holds the total packet size, and slot 15 holds the filter age in seconds since the program was installed, which lets a filter rate-limit a packet to one every N seconds or expire a stale rule.

PASS and DROP are encoded as jumps off the end of the program, which keeps the core loop branchless and trivial. The header comment spells out the convention: jumping to one byte past the end of the program means "pass this packet to the AP", and jumping to two bytes past the end means "drop it". The interpreter turns that into return codes at the top of its dispatch loop in apf_interpreter.c: the PASS/DROP/EXCEPTION macros resolve to 1/0/2, and apf_run() returns one of them. Any internal error or assertion failure is deliberately converted to PASS (ASSERT_RETURN returns EXCEPTION, and the runner rewrites EXCEPTION to PASS before returning) so a buggy or adversarial program can never cause a packet to be silently lost: when in doubt, wake the AP. APFv6 adds a transmit path: the program can allocate a tx buffer, copy bytes into it, and transmit it, which is how the firmware answers ARP requests, IPv6 neighbor solicitations, and offloaded mDNS queries on the AP's behalf without ever waking it. The host-side allocate/transmit callbacks are the apf_allocate_tx_buffer() / apf_transmit_tx_buffer() functions declared in hardware/google/apf/next/apf_interpreter.h.

A matching disassembler (hardware/google/apf/disassembler.c) and the assembler library (hardware/google/apf/apflib.c) let developers read installed programs back as human-readable assembly, which is invaluable when debugging why a packet was or was not dropped.

35.30.3 Capabilities Negotiation

Before any program can be installed, the framework must learn what the firmware can actually run. That contract is the ApfCapabilities class at packages/modules/Connectivity/framework/src/android/net/apf/ApfCapabilities.java, which carries three fields:

  • apfVersionSupported: the APF instruction-set version the firmware implements, where 0 means no APF support at all.
  • maximumApfProgramSize: how many bytes of NIC RAM are available for the program plus its data region.
  • apfPacketFormat: the link-layer frame format the firmware hands to the filter (one of the ARPHRD_* constants; the generator currently only emits code for ARPHRD_ETHER Ethernet framing).

IpClient.maybeCreateApfFilter() (around line 2936 of packages/modules/NetworkStack/src/android/net/ip/IpClient.java) reads these capabilities and decides whether and how to build a filter. The logic there is worth noting: if a device advertises APFv3+ but exposes fewer than 1024 bytes of RAM, IpClient downgrades the configured version to 2, because the counter region APF reserves at the end of RAM would leave too little room for an actual program. It also refuses to build a filter for any apfPacketFormat other than ARPHRD_ETHER, since the generator's hard-coded packet offsets assume Ethernet framing. The fully populated ApfConfiguration (multicast filter state, RA minimum lifetime, ARP/ND/mDNS/IGMP/MLD offload flags, and RAM size) is then handed to ApfFilter.maybeCreate().

35.30.4 The Interpreter <-> Generator Version-Sync Contract

Because the interpreter lives in firmware and the generator lives in an updatable mainline module, the two must never disagree about what a given opcode means. The contract is the version number returned by apf_version() in apf_interpreter.c (currently the date-stamped value 20250228) on the firmware side, matched against version constants on the framework side in BaseApfGenerator.java:

// This version number syncs up with APF_VERSION in hardware/google/apf/apf_interpreter.h
public static final int APF_VERSION_2 = 2;
public static final int APF_VERSION_3 = 3;
public static final int APF_VERSION_4 = 4;
public static final int APF_VERSION_6 = 6000;
public static final int APF_VERSION_61 = 6100;

The generator picks the most capable program format the firmware can run. ApfFilter.createApfGenerator() (around line 4187 of ApfFilter.java) instantiates an ApfV61Generator, ApfV6Generator, or ApfV4Generator depending on the negotiated version, gated through useApfV61Generator() / useApfV6Generator(), which call each generator's supportsVersion() check (for example ApfV6Generator.supportsVersion() returns true only for version >= APF_VERSION_6). All three derive from ApfV4GeneratorBase, and BaseApfGenerator.requireApfVersion() throws IllegalInstructionException if generator code ever tries to emit an instruction the negotiated firmware version cannot execute. That compile-time guard is what keeps a newer NetworkStack from shipping an APFv6 opcode to firmware that only understands APFv4.

35.30.5 Compiling and Installing a Program

ApfFilter.installNewProgram() (around line 4302 of ApfFilter.java) is the heart of the compile-and-install loop. It runs whenever relevant state changes (a new IP address, a toggled multicast filter, a fresh router advertisement, updated mDNS offload rules). The flow is:

  1. Create a generator for the negotiated version (createApfGenerator()).
  2. Emit a prologue, then progressively fit in as much filtering as the program budget allows. The code is explicit about prioritization: it first reserves room for mDNS offload rules (so the device can answer service queries from firmware rather than waking for them), then RA filters, sizing everything against mMaximumApfProgramSize. If a piece does not fit, it is dropped from this program rather than overflowing NIC RAM.
  3. Call gen.generate() to assemble the instruction list into the final byte[] program.
  4. Hand the bytes to installPacketFilter(), which calls through the IApfController interface to the Wi-Fi HAL, which writes the program into NIC RAM. IpClient wires up two controller implementations (mIpClientApfController and mNonHalApfController around line 1144 of IpClient.java) depending on whether the program is installed via the IpClient callback path or directly through a non-HAL API. On any install failure the filter records a QE_APF_INSTALL_FAILURE network-quirk metric.

A subtle but important detail is liveness: the program is not generic. It bakes the device's current unicast IPv4/IPv6 addresses into jeq comparisons so that broadcast/multicast destined for those addresses is passed while everything else is dropped. The moment an address changes, the old program is stale and installNewProgram() must run again. The "filter age" slot (slot 15) the interpreter pre-fills lets time-sensitive rules (such as RA lifetime checks and rate limits) reason about how long the current program has been live without the framework having to reinstall on a timer.

The net effect is a clean division of labor: the framework, with full visibility into network state and an updatable code path, does the thinking and compiles a tailored program; the firmware, with the AP asleep, does nothing but run a few hundred bytes of branchless bytecode per frame and decides PASS or DROP. That is how an idle Android device stays on the network without paying the wakeup tax for traffic it never wanted.


35.31 Wi-Fi RTT and 802.11az Secure Ranging

Wi-Fi RTT (Round-Trip Time) lets a device measure its distance to a Wi-Fi access point or to another device by timing the fine-timing-measurement (FTM) frame exchange defined by IEEE 802.11mc. The API has existed since Android 9: an app builds a RangingRequest, submits it through WifiRttManager, and gets back a list of RangingResult objects carrying a distance in millimetres and a distance standard deviation. Android 17 keeps that API and adds support for the newer IEEE 802.11az ranging amendment, including its secure variant.

The framework classes live in the Wifi mainline module under packages/modules/Wifi/framework/java/android/net/wifi/rtt/.

35.31.1 The Ranging Request Path

WifiRttManager (packages/modules/Wifi/framework/java/android/net/wifi/rtt/WifiRttManager.java) is obtained from Context.getSystemService(WifiRttManager.class) and gated on the android.hardware.wifi.rtt feature. A ranging request names its peers two ways:

  • By ScanResult (or ResponderConfig), to range against an access point.
  • By PeerHandle, to range against another Wi-Fi Aware (NAN) peer. The PeerHandle is the same opaque Aware peer identifier described in the Aware material earlier in this chapter; ranging reuses it so an app that has already discovered a peer over Aware can measure distance to it without a separate association.

ResponderConfig (.../rtt/ResponderConfig.java) describes one peer. For 802.11az it gains a supports80211azNtb field with is80211azNtbSupported() / Builder set80211azNtbSupported() (NTB = non-trigger-based ranging), plus getPeerHandle() / setPeerHandle() for the Aware case and a getSecureRangingConfig() accessor. The fromScanResult() factory inspects the scan result's capabilities and, when it sees PASN support, fills in a secure configuration automatically.

35.31.2 802.11az Secure Ranging: PASN and Frame Protection

The 802.11mc exchange is unauthenticated, so a nearby attacker can spoof FTM frames and lie about distance. 802.11az adds Pre-Association Security Negotiation (PASN): the two devices run a lightweight authenticated key exchange before ranging, then protect the ranging frames. Android 17 exposes this through two new classes, both guarded by the secure_ranging aconfig flag (packages/modules/Wifi/flags/wifi_flags.aconfig):

  • SecureRangingConfig (.../rtt/SecureRangingConfig.java) carries the per-session security options: isSecureHeLtfEnabled() (encrypted HE-LTF Long Training Fields, which prevent an observer from replaying the ranging waveform), isRangingFrameProtectionEnabled(), and the PasnConfig to use.
  • PasnConfig (.../rtt/PasnConfig.java) holds the authentication parameters: a set of base AKMs (AKM_PASN, AKM_SAE, AKM_FT_*, AKM_FILS_*) and pairwise ciphers (CIPHER_CCMP_128, CIPHER_GCMP_256, and so on), an optional password or SSID, and a PASN comeback cookie. The static helpers getBaseAkmsFromCapabilities() and getCiphersFromCapabilities() derive the AKM and cipher bitmasks from a scan result's capability string.

The PASN key cache is what lets repeated ranging sessions skip the full handshake: when the responder cannot immediately admit a requester it returns a comeback cookie and a back-off delay, which the requester echoes on its next attempt. RangingResult surfaces both through getPasnComebackCookie() and getPasnComebackAfterMillis(), alongside isRangingAuthenticated(), isRangingFrameProtected(), and isSecureHeLtfEnabled() so the caller can tell whether a given measurement was actually secured.

35.31.3 Choosing a Security Mode

RangingRequest (.../rtt/RangingRequest.java) gains a security mode that the requester sets with Builder.setSecurityMode():

Mode Behavior
SECURITY_MODE_OPEN Plain 802.11mc/az ranging, no authentication
SECURITY_MODE_OPPORTUNISTIC Use secure ranging when both peers support it, otherwise fall back to open
SECURITY_MODE_SECURE_AUTH Require authenticated PASN with a base AKM; drop peers that cannot do it

WifiRttManager.getRttCharacteristics() advertises what the local radio can do through new boolean keys: CHARACTERISTICS_KEY_BOOLEAN_NTB_INITIATOR, CHARACTERISTICS_KEY_BOOLEAN_SECURE_HE_LTF_SUPPORTED, CHARACTERISTICS_KEY_BOOLEAN_RANGING_FRAME_PROTECTION_SUPPORTED, and an integer CHARACTERISTICS_KEY_INT_MAX_SUPPORTED_SECURE_HE_LTF_PROTO_VERSION. An app can read these once and decide whether to ask for SECURITY_MODE_SECURE_AUTH or settle for opportunistic security.

The decision path for a single peer:

flowchart TD
    A["RangingRequest with peer + security mode"] --> B["WifiRttManager.startRanging()"]
    B --> C{"Security mode?"}
    C -->|"SECURE_AUTH"| D{"Peer supports PASN?"}
    D -->|No| E["Drop peer from request"]
    D -->|Yes| F["PASN authenticated key exchange"]
    C -->|"OPPORTUNISTIC"| G{"Peer supports PASN?"}
    G -->|Yes| F
    G -->|No| H["Open 802.11mc/az FTM"]
    C -->|"OPEN"| H
    F --> I["Protected FTM with secure HE-LTF"]
    I --> J["RangingResult: authenticated, frame-protected"]
    H --> K["RangingResult: distance only"]

35.32 Try It: Network Debugging

35.32.1 dumpsys connectivity

The most powerful tool for debugging Android networking is dumpsys connectivity. It provides a comprehensive snapshot of the entire connectivity state.

# Full connectivity dump
adb shell dumpsys connectivity

# Short format (summary)
adb shell dumpsys connectivity --short

# Diagnostic mode
adb shell dumpsys connectivity --diag

# Just network information
adb shell dumpsys connectivity networks

# Just request information
adb shell dumpsys connectivity requests

# Traffic controller state
adb shell dumpsys connectivity trafficcontroller

Reading the output:

The dump includes several sections:

  1. NetworkAgentInfo: Lists all active networks with their capabilities, score, and validation status:
NetworkAgentInfo [WIFI () - 100] {
  mNetworkCapabilities: [ Transports: WIFI Capabilities: INTERNET&NOT_METERED&NOT_RESTRICTED
    &TRUSTED&NOT_VPN&VALIDATED&NOT_ROAMING&FOREGROUND&NOT_CONGESTED&NOT_SUSPENDED
    &NOT_VCN_MANAGED LinkUpBandwidthKbps: 1048576 LinkDnBandwidthKbps: 1048576
    SignalStrength: -55 ]
  mLinkProperties: {InterfaceName: wlan0 LinkAddresses: [192.168.1.100/24,
    fe80::1234:5678:abcd:ef01/64] DnsAddresses: [192.168.1.1] Domains: null
    MTU: 1500 Routes: [0.0.0.0/0 -> 192.168.1.1 wlan0,
    192.168.1.0/24 -> 0.0.0.0 wlan0]}
  mScore: Score(70 ; Policies : TRANSPORT_PRIMARY)
  Validated: true
}
  1. Network requests: Shows what applications have requested:
NetworkRequest [ REQUEST id=1, [ Capabilities: INTERNET&NOT_RESTRICTED
  &TRUSTED&NOT_VPN ] ]
  1. Default network: The currently selected default network

35.32.2 dumpsys wifi

# Full Wi-Fi dump
adb shell dumpsys wifi

# Specific sections
adb shell dumpsys wifi scan    # Scan results
adb shell dumpsys wifi config  # Saved networks

Key information in the Wi-Fi dump:

  • Current connection state and RSSI
  • Scan results with channel information
  • Saved network configurations
  • SoftAP state
  • Connection history and failure reasons

35.32.3 dumpsys netd

# netd status
adb shell dumpsys netd

# Network routing tables
adb shell ip rule show
adb shell ip route show table all

# iptables rules
adb shell iptables -L -v -n
adb shell ip6tables -L -v -n

35.32.4 DNS Debugging

# DNS resolver state
adb shell dumpsys dnsresolver

# Test DNS resolution
adb shell nslookup example.com

# Check private DNS status
adb shell settings get global private_dns_mode
adb shell settings get global private_dns_specifier

35.32.5 Network Diagnostics Commands

# Check connectivity
adb shell ping -c 4 8.8.8.8
adb shell ping6 -c 4 2001:4860:4860::8888

# Trace route
adb shell traceroute 8.8.8.8

# Check interface status
adb shell ifconfig
adb shell ip addr show
adb shell ip link show

# Monitor network events
adb shell logcat -s ConnectivityService:V NetworkMonitor:V Vpn:V

# Check active connections
adb shell cat /proc/net/tcp
adb shell cat /proc/net/tcp6

# Network statistics
adb shell cat /proc/net/dev

35.32.6 ConnectivityDiagnosticsManager

For programmatic network diagnostics, Android provides the ConnectivityDiagnosticsManager API:

// Register for connectivity diagnostics
ConnectivityDiagnosticsManager cdm = context.getSystemService(
        ConnectivityDiagnosticsManager.class);

NetworkRequest request = new NetworkRequest.Builder()
        .addCapability(NET_CAPABILITY_INTERNET)
        .build();

cdm.registerConnectivityDiagnosticsCallback(
        request, executor,
        new ConnectivityDiagnosticsCallback() {
            @Override
            public void onConnectivityReportAvailable(
                    ConnectivityReport report) {
                // Analyze report
                Bundle additional = report.getAdditionalInfo();
                int probesAttempted = additional.getInt(
                    KEY_NETWORK_PROBES_ATTEMPTED_BITMASK);
                int probesSucceeded = additional.getInt(
                    KEY_NETWORK_PROBES_SUCCEEDED_BITMASK);
            }

            @Override
            public void onDataStallSuspected(DataStallReport report) {
                int method = report.getDetectionMethod();
                if (method == DETECTION_METHOD_DNS_EVENTS) {
                    // DNS-based data stall
                } else if (method == DETECTION_METHOD_TCP_METRICS) {
                    // TCP-based data stall
                }
            }
        });

35.32.7 Simulating Network Conditions

For testing, Android provides several tools to simulate network conditions:

# Enable/disable Wi-Fi
adb shell svc wifi enable
adb shell svc wifi disable

# Enable/disable mobile data
adb shell svc data enable
adb shell svc data disable

# Set network speed limit (emulator only)
adb shell cmd connectivity set-bandwidth-limit <interface> <kbps>

# Simulate captive portal
adb shell settings put global captive_portal_mode 0  # Disable detection
adb shell settings put global captive_portal_mode 1  # Enable (prompt)

# Test VPN
adb shell dumpsys connectivity --diag

35.32.8 Reading BPF Maps

For advanced debugging of BPF-based traffic control:

# Dump tethering BPF stats
adb shell dumpsys tethering

# View BPF program status
adb shell cat /sys/fs/bpf/

# Check traffic controller maps
adb shell dumpsys connectivity trafficcontroller

35.32.9 Common Debugging Scenarios

Scenario 1: Network connected but no Internet

# 1. Check network validation state
adb shell dumpsys connectivity | grep -A5 "Validated"

# 2. Check DNS resolution
adb shell nslookup www.google.com

# 3. Check routing
adb shell ip route get 8.8.8.8

# 4. Check captive portal
adb shell dumpsys connectivity | grep "CAPTIVE_PORTAL"

# 5. Check iptables for blocked traffic
adb shell iptables -L fw_OUTPUT -v -n

Scenario 2: VPN not working

# 1. Check VPN state
adb shell dumpsys connectivity | grep -A10 "VPN"

# 2. Check TUN interface
adb shell ip addr show tun0

# 3. Check routing rules
adb shell ip rule show

# 4. Check VPN-specific routing table
adb shell ip route show table <vpn-netid>

# 5. Check UID routing
adb shell dumpsys connectivity | grep "UidRange"

Scenario 3: Slow Wi-Fi

# 1. Check signal strength
adb shell dumpsys wifi | grep "RSSI"

# 2. Check link speed
adb shell dumpsys wifi | grep "Link speed"

# 3. Check for data stalls
adb shell dumpsys connectivity --diag | grep "DataStall"

# 4. Check for channel congestion
adb shell dumpsys wifi scan | grep "freq"

# 5. Check bandwidth estimates
adb shell dumpsys connectivity | grep "Bandwidth"

Scenario 4: Tethering issues

# 1. Check tethering state
adb shell dumpsys tethering

# 2. Check upstream network
adb shell dumpsys tethering | grep "upstream"

# 3. Check NAT rules
adb shell iptables -t nat -L -v -n

# 4. Check DHCP server
adb shell dumpsys tethering | grep "DHCP"

# 5. Check IP forwarding
adb shell cat /proc/sys/net/ipv4/ip_forward

35.32.10 Network Logging and Tracing

For deeper analysis, enable verbose logging:

# Enable verbose connectivity logging
adb shell setprop log.tag.ConnectivityService VERBOSE
adb shell setprop log.tag.NetworkMonitor VERBOSE
adb shell setprop log.tag.DnsResolver VERBOSE
adb shell setprop log.tag.Vpn VERBOSE

# Monitor specific tags
adb logcat -s ConnectivityService:V NetworkAgent:V \
    NetworkMonitor:V WifiService:V ClientModeImpl:V

# Enable netd debug logging
adb shell setprop log.tag.Netd VERBOSE

35.32.11 Developer Options: Network Settings

The Settings app provides several network-related developer options:

Setting Effect
Wi-Fi verbose logging Enables detailed Wi-Fi logs
Mobile data always active Keeps cellular active alongside Wi-Fi
USB configuration Select USB tethering mode
Networking diagnostics Run connectivity tests

35.32.12 Programmatic Network Testing

// Test if a specific network has connectivity
ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
Network activeNetwork = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(activeNetwork);

if (caps != null) {
    boolean hasInternet = caps.hasCapability(
            NetworkCapabilities.NET_CAPABILITY_INTERNET);
    boolean isValidated = caps.hasCapability(
            NetworkCapabilities.NET_CAPABILITY_VALIDATED);
    boolean isMetered = !caps.hasCapability(
            NetworkCapabilities.NET_CAPABILITY_NOT_METERED);

    Log.d(TAG, "Internet: " + hasInternet
            + ", Validated: " + isValidated
            + ", Metered: " + isMetered);
}

// Request a specific network type
NetworkRequest wifiRequest = new NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        .build();

cm.requestNetwork(wifiRequest, new ConnectivityManager.NetworkCallback() {
    @Override
    public void onAvailable(@NonNull Network network) {
        // Wi-Fi network is available
        // Bind socket to this network:
        network.bindSocket(socket);
    }

    @Override
    public void onLost(@NonNull Network network) {
        // Wi-Fi network lost
    }

    @Override
    public void onCapabilitiesChanged(@NonNull Network network,
            @NonNull NetworkCapabilities caps) {
        // Capabilities changed (e.g., validated, signal strength)
        int signalStrength = caps.getSignalStrength();
    }

    @Override
    public void onLinkPropertiesChanged(@NonNull Network network,
            @NonNull LinkProperties lp) {
        // IP config changed
        List<InetAddress> dnsServers = lp.getDnsServers();
    }
});

Summary

Android's networking and connectivity stack is a deeply layered system that combines Java framework services, native daemons, eBPF programs, and Linux kernel subsystems into a cohesive whole. The key architectural insights are:

  1. ConnectivityService is the orchestrator: All network management flows through this single service, which maintains a global view of all networks, requests, and their matching.

  2. NetworkAgent is the network abstraction: Each transport (Wi-Fi, cellular, VPN) communicates with ConnectivityService through this uniform interface, enabling transport-agnostic network management.

  3. Mainline modularization enables agility: Critical networking components (Connectivity, NetworkStack, Wi-Fi, DnsResolver) ship as independently updatable APEX modules, decoupling security fixes from platform OTAs.

  4. eBPF is replacing iptables: Modern Android increasingly uses BPF programs for traffic control, offering better performance and more flexible policy enforcement than traditional iptables chains.

  5. Per-network isolation is fundamental: The netId/fwmark mechanism ensures that routing, DNS, and firewall rules are correctly scoped to individual networks, enabling features like per-app VPN and multi-network connectivity.

  6. Security is layered: From Network Security Config (application-level) through encrypted DNS (transport-level) to firewall rules (network-level), Android applies defense in depth to protect network communications.

The networking stack continues to evolve rapidly. Recent additions include Wi-Fi 7 MLO support, satellite connectivity, Thread mesh networking, and DoH for encrypted DNS. Android 17 pushes modularization into native code with the mainline supplicant (an updatable wpa_supplicant shipped inside the Wi-Fi APEX), redesigns the proxy stack to support multiple concurrent PAC scripts per network/user/UID via PacCoordinator and the APEX-resident MultiPacService/MultiProxyService, and adds a per-app, user-driven NSD service-access picker behind the new ACCESS_LOCAL_NETWORK permission. The modular architecture ensures these features can be delivered to users without waiting for full platform upgrades.

Key Source Files Reference

File Path
ConnectivityService packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java
NetworkAgent packages/modules/Connectivity/framework/src/android/net/NetworkAgent.java
NetworkFactory packages/modules/Connectivity/staticlibs/device/android/net/NetworkFactory.java
NetworkCapabilities packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
NetworkRequest packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java
ClientModeImpl packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java
WifiServiceImpl packages/modules/Wifi/service/java/com/android/server/wifi/WifiServiceImpl.java
WifiNative packages/modules/Wifi/service/java/com/android/server/wifi/WifiNative.java
SoftApManager packages/modules/Wifi/service/java/com/android/server/wifi/SoftApManager.java
NetdNativeService system/netd/server/NetdNativeService.h
Controllers system/netd/server/Controllers.cpp
BandwidthController system/netd/server/BandwidthController.cpp
FirewallController system/netd/server/FirewallController.cpp
NetworkController system/netd/server/NetworkController.cpp
DnsResolver packages/modules/DnsResolver/DnsResolver.cpp
DnsTlsTransport packages/modules/DnsResolver/DnsTlsTransport.cpp
PrivateDnsConfiguration packages/modules/DnsResolver/PrivateDnsConfiguration.cpp
Vpn frameworks/base/services/core/java/com/android/server/connectivity/Vpn.java
Tethering packages/modules/Connectivity/Tethering/src/com/android/networkstack/tethering/Tethering.java
BpfCoordinator packages/modules/Connectivity/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
IpServer packages/modules/Connectivity/Tethering/src/android/net/ip/IpServer.java
NetworkMonitor packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
NetworkSecurityConfig frameworks/base/packages/NetworkSecurityConfig/platform/src/android/security/net/config/NetworkSecurityConfig.java
XmlConfigSource frameworks/base/packages/NetworkSecurityConfig/platform/src/android/security/net/config/XmlConfigSource.java
PacCoordinator packages/modules/Connectivity/service/src/com/android/server/connectivity/proxy/PacCoordinator.java
MultiProxyTracker packages/modules/Connectivity/service/src/com/android/server/connectivity/proxy/MultiProxyTracker.java
IMultiProxyService packages/modules/Connectivity/commercial/pac/multiproxyhandler/src/com/android/multiproxyhandler/IMultiProxyService.aidl
IMainlineSupplicant packages/modules/Wifi/aidl/mainline_supplicant/android/system/wifi/mainline_supplicant/IMainlineSupplicant.aidl
MainlineSupplicantAidlManager packages/modules/Wifi/service/java/com/android/server/wifi/MainlineSupplicantAidlManager.java
NsdService packages/modules/Connectivity/service-t/src/com/android/server/NsdService.java
ServiceAccessRepository packages/modules/Connectivity/service-t/src/com/android/server/connectivity/mdns/internal/ServiceAccessRepository.java