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:
- Connectivity module (
packages/modules/Connectivity/): Contains ConnectivityService, the tethering subsystem, and related framework code. - NetworkStack module (
packages/modules/NetworkStack/): Handles DHCP client, network validation (captive portal detection), and IP provisioning. - Wi-Fi module (
packages/modules/Wifi/): The entire Wi-Fi subsystem including WifiService, ClientModeImpl, and scanning logic. - 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:
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_METEREDforbidden 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:
-
Legacy scoring (pre-Android 12): Simple integer scores. Higher wins. Wi-Fi defaulted to 60, cellular to 50.
-
Modern scoring (Android 12+): A policy-based
NetworkScorethat encodes multiple dimensions:
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:
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
ActiveModeWardenfor 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:
- Saved network priority: User-configured preferences
- Signal strength (RSSI): Weighted by band (2.4 GHz, 5 GHz, 6 GHz)
- Security type: Prefers stronger security
- Past performance: Historical connection quality metrics
- Network suggestions: App-suggested networks
- Enterprise policies: Device admin restrictions
- 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:
CoexManagerhandles 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()
35.3.9 Multi-Link Operation (MLO) and Wi-Fi 7¶
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:
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:
DnsProxyListener: Listens for DNS queries on a UNIX domain socketDnsResolverService: 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:
- Off: All DNS queries are plaintext
- Opportunistic (default): Try DoT/DoH, fall back to plaintext
- 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:
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:
- ConnectivityService blocks all traffic for the VPN's UID ranges using
BLOCKED_REASON_LOCKDOWN_VPN - Only traffic through the VPN interface is permitted
- 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:
- First 2 attempts: Try MOBIKE (IKEv2 Mobility and Multihoming) to migrate the session to a different path
- Subsequent attempts: Full session restart with exponential backoff
- 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):
- DUN (Dedicated Upstream Network) capable cellular
- Wi-Fi
- Regular cellular
- 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:
- NetworkMonitor reports
NETWORK_TEST_RESULT_INVALIDwith a redirect URL - ConnectivityService adds
NET_CAPABILITY_CAPTIVE_PORTALto the network - A notification is shown to the user
- The user taps the notification and is shown the portal login page
- 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:
- 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.
- 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.
- 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.
- 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.xmland 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
Vcninstances as subscription groups become active/inactive. - Underlying network policy: Provides
VcnUnderlyingNetworkPolicyto ConnectivityService, controlling whether underlying networks are marked asNOT_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:
- At least one contained subscription ID has carrier config loaded
(
CarrierConfigManager.isConfigForIdentifiedCarrier()returns true). - 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:
- Underlying networks are no longer marked as restricted (the
NET_CAPABILITY_NOT_VCN_MANAGEDcapability is restored). - Applications can access the raw underlying networks directly.
- The VCN continues attempting to establish the tunnel in the background.
- 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_MANAGEDwhen VCN is active, making them invisible to most apps. - The VCN NetworkAgent carries
NOT_VCN_MANAGED, so it satisfies normalNetworkRequests. - 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:
- Sanitize capabilities: The declared capabilities are validated and mixed with system-level overrides
- Process link properties: Validate routes, DNS servers, and interface
- Store the agent: Add to the tracking data structures
- Start NetworkMonitor: Begin validation probes
- Handle local networks: Configure forwarding for Thread, etc.
- Update network info: Trigger rematch if the network is connected
- 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
NetworkReassignmentobject 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:
- Resolve the private DNS hostname to get server IPs
- Establish a TLS connection to port 853 (DoT) or HTTPS (DoH)
- Send a synthetic DNS query
- Verify the response is valid
- If successful, mark private DNS as validated
- 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:
- Identify sockets bound to the old network (via UID range and fwmark)
- Send RST for TCP sockets using the
SockDiaginterface - For QUIC, send CONNECTION_CLOSE if the flag is enabled
- 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:
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:
- Process creation (kernel assigns UID)
- Socket creation (kernel tags socket with UID via cgroup)
- BPF programs (read UID from socket, apply policy)
- 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.):
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:
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:
- The application requests a keepalive via
SocketKeepalive - ConnectivityService assigns a hardware slot
- The NetworkAgent configures the hardware to send periodic packets
- For TCP, a packet filter is also installed to handle ACK responses
- 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:
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
PacProcessorinstances that actually evaluate PAC JavaScript. Packagecom.android.multiproxyhandlerships the proxy side; packagecom.android.multipacprocessorships 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 vendorISupplicantroot — 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,NsdServicedoes not deliver raw results to the app — it routes them through aPickerListenerwhosestartPicker()shows the system UI. - Resolve / register-callback:
checkQueryServicePermissions()allows an operation either when the caller holdsACCESS_LOCAL_NETWORKor 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.javais 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 theNsdServicehandler thread (it is explicitly not thread-safe). Its main methods areaddAllowedService(),isServiceAllowed(),loadPackage(),unloadPackage(), and amaybeScheduleDatabaseMaintenance()that prunes entries for uninstalled packages.ServiceAccessDb.javais the persistence layer: a small SQLite database (NsdServiceAccess.db) with apackagetable tracking known packages and aservice_accesstable 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@SystemApivisibility onUsdManager(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 inpackages/modules/Wifi/framework/java/android/net/wifi/WifiFrameworkInitializer.javaonly registers the service whenFlags.usd() && Environment.isSdkAtLeastB(). - A device-capability gate: registration returns
nullunless the device resourceconfig_deviceSupportsWifiUsdis true, sogetSystemService()yields noUsdManageron 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 inhardware/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:
- A read-only program of bytecode instructions.
- Two 32-bit registers,
R0andR1. - Sixteen 32-bit temporary memory slots, cleared between packets.
- A read-only copy of the packet.
- 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, where0means 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 theARPHRD_*constants; the generator currently only emits code forARPHRD_ETHEREthernet 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:
- Create a generator for the negotiated version (
createApfGenerator()). - 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. - Call
gen.generate()to assemble the instruction list into the finalbyte[]program. - Hand the bytes to
installPacketFilter(), which calls through theIApfControllerinterface to the Wi-Fi HAL, which writes the program into NIC RAM. IpClient wires up two controller implementations (mIpClientApfControllerandmNonHalApfControlleraround line 1144 ofIpClient.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 aQE_APF_INSTALL_FAILUREnetwork-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(orResponderConfig), to range against an access point. - By
PeerHandle, to range against another Wi-Fi Aware (NAN) peer. ThePeerHandleis 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 thePasnConfigto 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 helpersgetBaseAkmsFromCapabilities()andgetCiphersFromCapabilities()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:
- 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
}
- Network requests: Shows what applications have requested:
- 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:
-
ConnectivityService is the orchestrator: All network management flows through this single service, which maintains a global view of all networks, requests, and their matching.
-
NetworkAgent is the network abstraction: Each transport (Wi-Fi, cellular, VPN) communicates with ConnectivityService through this uniform interface, enabling transport-agnostic network management.
-
Mainline modularization enables agility: Critical networking components (Connectivity, NetworkStack, Wi-Fi, DnsResolver) ship as independently updatable APEX modules, decoupling security fixes from platform OTAs.
-
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.
-
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.
-
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 |