Mobile Computing · Mobile Networking
What a Connectivity Callback Reports and What It Cannot
Why the operating system reports a network as available before it can reach anything, and which checks distinguish a captive portal from an outage from a broken name lookup.
The operating system’s network status callback fires when the link comes up. That is a statement about the interface: an address has been assigned, a default route exists, the radio has associated. It is not a statement that a packet sent to a server will arrive, and on a phone the gap between those two statements is where most connectivity bugs live.
A device inside a hotel wireless network has a link, an address, a default route, and no working path to the internet until someone clicks a button on a web page. A device on a network that advertises only IPv6 has a link and a route and no path to servers that publish only IPv4 addresses. A device whose name resolution has broken has a perfect link and cannot start a single connection. In all three cases the callback that says “connected” is correct about what it measures and useless for the decision the application wants to make.
Link state, network state, and validated state
Platforms expose these as distinct concepts, though the naming differs.
The lowest layer is the interface. It reports whether a wireless or cellular interface is associated and has an address. This is available synchronously and changes within milliseconds of the radio event.
Above it is the notion of a network with capabilities: whether it is metered, whether it is expected to provide internet access, whether it supports certain transports. On Android, ConnectivityManager reports a Network object and NetworkCapabilities describing it, and applications can bind sockets to a particular network so that traffic does not follow the default route. On Apple platforms, NWPathMonitor reports a path whose status is satisfied or unsatisfied and whose properties include whether the path is expensive or constrained.
The highest layer is validation, and this is the one that requires packets. A network is validated when the platform has confirmed that traffic sent over it reaches something outside the local link. Android exposes this as a capability flag on the network’s capabilities, and it is set only after a probe succeeds. Apple’s path monitor has no equivalent public flag; the path is satisfied when a route exists, and the operating system handles portal detection separately.
The distinction matters because validation is asynchronous and can be wrong in both directions. It is wrong negatively when the probe target is blocked by a firewall on a network that otherwise works, and it is wrong positively when the probe is answered by something other than the intended server.
The captive portal problem
A captive portal is a network that intercepts traffic until the user authenticates. The interception is usually done by responding to any HTTP request with a redirect to a local page, and by allowing DNS to resolve normally so that the request can be made at all.
Portal detection works by making a request to a known URL and checking the response. Android’s probe requests a small document and treats the network as validated when the response has the expected status and no redirect. A portal that answers with a redirect, or with a body that does not match, leaves the network unvalidated, and the platform surfaces a notification offering to open the portal.
The mechanism has failure modes that applications inherit.
A network can be a portal and still pass validation if it chooses to allow the probe URL through while intercepting everything else. This is common in networks that want to keep background traffic working while requiring authentication for browsing, and it produces a device that reports a validated connection and fails on the first real request.
A network can fail validation while working perfectly, if the probe is blocked. Corporate networks that filter by destination produce this, and so do networks in some regions. Applications that treat the validated flag as a hard prerequisite will refuse to work on such a network.
A portal can be present without any HTTP interception if the network requires 802.1X authentication at the link layer, in which case the failure appears as an association failure rather than as a portal, and the user experience is a device that cannot connect at all.
The practical rule is that the flag is a hint about the network’s intent, not a guarantee about the path. The only reliable test is the request the application actually needs to make.
IPv6-only networks and the translation layer
A growing number of cellular networks are IPv6-only, with IPv4 reachability provided by translation at the edge. The device receives an IPv6 address and no IPv4 address, and the network operates a NAT64 that synthesises IPv4 connectivity, together with a DNS64 resolver that returns synthesised AAAA records for names that publish only A records.
For the application this is mostly invisible, and that is the design goal. It becomes visible in three places.
An application that embeds a literal IPv4 address in a request, rather than a name, has no name for DNS64 to translate and no route to send it on. This is the most common way an application breaks on an IPv6-only network, and it is entirely avoidable: the address should come from a name.
An application that implements its own name resolution, or that caches A records and uses them directly, bypasses the resolver that performs the synthesis. The synthesis is not a property of DNS in general; it is a function of the specific resolver, so an application that talks to a different resolver, or that hardcodes addresses, loses it.
An application that requires IPv4 for a protocol that has no IPv6 support needs a translation mechanism, and the general one is the client-side translator that maps IPv4 literals into the NAT64 prefix. Platforms provide this, and it works only for literals that the application passes through the platform’s socket layer rather than through its own network stack.
The connectivity callback tells the application nothing about any of this. A device on an IPv6-only network reports a normal connection, and the failure appears later as a timeout on a specific request.
Name resolution is the usual suspect
When a request fails on a mobile device, the most common cause after radio state is name resolution, and the symptom is easy to misread as a server problem.
The resolver has its own state. It caches answers with their time to live, and a device that has moved between networks may hold answers that were valid on the previous network and are wrong on the current one — for example, an internal address returned by a corporate resolver that the device is no longer attached to. Platforms invalidate the cache on network change, but the invalidation is not instantaneous and does not cover answers held by an application’s own resolver.
The resolver also has a time budget. A query that gets no response is retried, and the total wait before failure can be several seconds, which is longer than the timeout most applications set for the connection that depends on it. The result is that a name resolution failure often surfaces as a connection timeout, and the application’s logs show the wrong layer.
Testing reachability by resolving a name and connecting to the result conflates three separate questions: does the resolver work, does the route work, and does the server respond. Separating them is what makes a failure diagnosable. A request to a literal address on the same network tests the route without the resolver. A resolution without a connection tests the resolver without the route.
Building a check that means something
Given the above, a reachability check worth running has to be tied to the thing being reached.
The cheapest useful check is a request to the application’s own server, using the same name and the same transport as real traffic, with a short timeout. It costs one round trip and it tests exactly the path that matters, including the resolver, the translation layer, and any portal interception. Its weakness is cost: it is a real request, so it consumes the radio transition described in Radio resource control states and the tail timer after the last byte and it must be cheap on the server.
The second option is a dedicated health endpoint that returns a small fixed response and is explicitly excluded from caching. This separates the check from business logic, which makes it safe to call on a schedule, at the cost of a second endpoint that must be maintained and monitored.
The third option, checking a well-known public endpoint, tests the internet but not the application. It is useful only as a coarse distinction between “no connectivity at all” and “our server is unreachable”, and it inherits the probe-blocking problem described above.
Whatever the mechanism, the result should be treated as a timestamped observation rather than as state. A check that succeeded four minutes ago says something about four minutes ago. Mobile networks change state on the timescale of seconds, whether a train entering a tunnel, a handover between cells, or a wireless association moving to a portal, and any cached verdict older than the current user interaction is a guess. The honest model is to attempt the real request, fail fast, and use the failure to decide whether to retry, queue, or tell the user, which is the subject of Retry classification when the failure arrives as a timeout.
What the callback is good for
None of this makes the connectivity callback useless. It is the correct signal for a different decision: whether to attempt a request at all, and whether to stop waiting for one.
When the callback reports no network, an application can fail immediately rather than waiting for a timeout, which is the difference between a spinner that lasts a moment and one that lasts thirty seconds. When the callback reports a network change, an application can abandon in-flight requests that are now routed over a different path — a connection bound to the old network’s address will not survive the change, which is why sockets must be rebound or recreated. When the callback reports a network that is metered or expensive, an application can defer large transfers.
These are decisions about policy, and policy is what the callback is qualified to inform. Reachability is a decision about fact, and fact requires a packet.