mrkeyoor.com_
Sat 12 Sept 20:54 UTC
Tech6 min read

Android VPN Lockdown Can Leak an IP Every 10 Seconds

A public Android keepalive API can bypass VPN lockdown and reveal a device's network address. A router capture found one UDP packet every 10 seconds.

A one-byte UDP packet every 10 seconds is enough to expose the network address that Android's VPN lockdown is meant to conceal. In a controlled router capture, a normal app on a Pixel 8 Pro sent that pulse outside the VPN while Always-on VPN and "Block connections without VPN" were enabled. The story had reached 133 points and 31 comments on Hacker News at the brief's 19:30 UTC snapshot on September 12. That discussion is a measure of developer interest. The evidence comes from Armin Šupuk's technical report, which documents the packet capture and the test controls.

Android's own VPN guide says lockdown blocks network traffic that does not use the VPN. This finding identifies a path that slips beneath the routing and firewall machinery enforcing that promise. A VPN provider cannot close it inside an ordinary VPN app because Android delegates the packet transmission to the Wi-Fi offload layer. Mullvad's independent account reaches the same conclusion: the operating system needs to change.

One byte crosses the boundary

NAT traversal keepalives have a mundane job. An IPsec client can periodically send a small UDP packet on port 4500 so a router keeps the relevant address translation mapping alive. Android exposes this through IpSecManager.UdpEncapsulationSocket and ConnectivityManager.createSocketKeepalive(). The public API reference describes it as a request to start keepalives on an IPsec NAT-T socket; the method has been available since API level 29.

The researcher's proof followed that documented route rather than a hidden interface:

UdpEncapsulationSocket socket = ipSecManager.openUdpEncapsulationSocket();
SocketKeepalive keepalive = connectivityManager.createSocketKeepalive(
    network, socket, source, destination, executor, callback);
keepalive.start(10);

After the request enters Android's connectivity service, the system hands a completed keepalive packet to the network agent and then to Wi-Fi hardware or firmware. Each later packet can be transmitted without a fresh write by the app. The report's call-path analysis found no check of the original caller's effective VPN policy before that handoff. The caller chooses the destination within the API's network constraints, while Android fixes the payload format.

That distinction bounds the finding. The app cannot place arbitrary secret data in this one-byte packet, according to the impact analysis. A server controlled by the app operator can still see the physical network's source IP and the packet timing. That can reveal whether the device is online and connect activity expected to sit behind a VPN with a home, workplace, or travel network address.

The Pixel test supplied a useful control. With both VPN settings enabled, ordinary UDP sends from the same app were absent from the access point capture while the NAT-T packets appeared. The OpenWrt router recorded a one-byte UDP/4500 packet every 10 seconds from a Pixel 8 Pro running Android 16 build CP1A.260505.005. Those paired observations in the evaluation separate an offload bypass from a general VPN failure.

The check is still a TODO

Current AOSP source makes the trust gap unusually easy to inspect. KeepaliveTracker.makeNattKeepaliveInfo() accepts a file descriptor and a resourceId, then calls isNattKeepaliveSocketValid(). The comment says the function should verify whether the socket came from the system API or IpSecService and should validate the resource ID. In the main-branch implementation, it only rejects a null file descriptor and otherwise returns true. The resourceId is not read in the function body.

The neighboring service methods expose the split in Android's trust model. The deprecated startNattKeepalive() calls enforceKeepalivePermission(). The file-descriptor version used by the public socket path forwards its arguments directly to the tracker, as the current ConnectivityService source shows. According to Šupuk's analysis, neither this admission point nor the downstream tracker asks whether lockdown permits the calling UID to emit on the selected physical network.

The missing validation has history. The paper's commit reconstruction says the raw file-descriptor API began as privileged behavior in January 2019. Android moved the public UDP encapsulation socket onto that route in March. Ownership validation and a lifetime lock arrived in April, then were reverted in May over service-dependency and deadlock concerns. Per-UID and per-network quotas replaced those checks, limiting slot consumption without proving that a caller owns the supplied IPsec resource.

This is a narrow example of a recurring systems problem: delegated work can outlive the security decision made for the process that requested it. Here, the app's ordinary sockets remain subject to VPN routing, while firmware emits an already approved packet below that path. Any repair therefore has to carry the caller's identity and current VPN authorization into keepalive admission, rather than relying on the app socket rules alone. That conclusion follows directly from the two paths visible in AOSP's keepalive code.

Three phones, different levels of proof

Only the Pixel received the full external packet-capture treatment. A Samsung SM-F966B running Android 16 on Qualcomm hardware accepted one physical Wi-Fi keepalive slot and kept it active for 24 hours and 32 minutes. A Nothing A059, also on Android 16 and Qualcomm hardware, reached the active callback for one slot. The cross-device results do not claim a router capture or duration measurement for the Nothing phone, and the Samsung evidence is an active-slot observation rather than the Pixel's on-wire capture.

On the Pixel, the keepalive survived backgrounding, screen lock, Doze, battery saver, a restricted standby bucket, and the Binder freezer. It stopped after a force stop, uninstall, network loss, or reboot. A malicious app would not need to remain visibly active once Android had armed the hardware task. The lifecycle matrix does not claim reboot persistence.

The report's 91.24% estimate does not mean nine in ten phones were directly tested. Šupuk maps the shared Android 12 and later framework path against firmware evidence for seven WLAN families that the paper estimates covered that share of Android-derived shipments from late 2021 through early 2026. Runtime work covered three models, two WLAN families, and Wi-Fi only; the limitations section says cellular emission was not measured and the remaining 8.76% of shipments is unresolved.

Public API availability also does not show that many legitimate apps need this route. A static scan reported in the application study found no Android framework IPsec, IKE, or NAT-T API use across 4,679 distinct stored Git origins from F-Droid and IzzyOnDroid, even though reviewers identified 73 VpnService apps in the corpus. A smaller Google Play check acquired nine APKs from 26 current listings and found platform SDK call sites in two proprietary VPN clients. The open-source sample covers only one part of the Android app market. Its result suggests that tighter admission could have limited compatibility cost.

VPN apps have no dependable workaround

Network hardware has a finite number of keepalive slots, so one possible defense is to occupy every available slot before an unwanted app can. Mullvad considered that approach and rejected it. A defensive app would itself send traffic outside the tunnel, and a malicious app might reserve the slot first. Slot counts also vary with device configuration and privileged reservations, so exhaustion is a race rather than a policy boundary.

For users, the immediate advice is limited: install software you trust, and remember that Android's lockdown setting does not cover this delegated packet path on the tested systems. The paper says a VPN terminated on an upstream router can keep the device's public Internet address behind that router's tunnel because the Android handset no longer controls the outer boundary. That setup is practical in some fixed networks. It does not follow a phone between mobile and unfamiliar Wi-Fi connections. Mullvad likewise offers no app-level mitigation.

The next result must be on the wire

Šupuk reported the issue to Google's Android Vulnerability Reward Program on May 15, 2026. The report was marked as a duplicate of a private canonical issue four days later. The researcher's visible record contained no CVE, severity decision, or fix status when the disclosure account was published on July 29. Mullvad published its technical summary on September 10, and the public GrapheneOS tracker issue remains open and labeled upstream.

A convincing platform fix should make resourceId real authorization data, reject physical-network offload when the caller's UID is covered by lockdown, and revoke an active keepalive if that policy changes. The paper's regression plan also calls for testing records already active when lockdown or VPN ownership changes. The final test belongs outside the phone: a router capture from a patched device should show no attacker-directed UDP/4500 pulse while ordinary authorized IPsec keepalives still work. Until a patch is accompanied by those tests and a clean capture, the ten-second packet remains the clearest measure of the gap.

We reviewed this

  1. router — our honest review
  2. paper — our honest review
  3. fresh — our honest review

Sources

  1. Android NAT-T Keepalive Offload Bypasses VPN Lockdown
  2. Another way to leak traffic on Android has been discovered
  3. VPN | Android Developers
  4. ConnectivityManager | Android Developers
  5. KeepaliveTracker.java | Android Open Source Project
  6. ConnectivityService.java | Android Open Source Project
  7. Android NAT-T VPN bypass discussion | Hacker News
  8. Android NAT-T Keepalive Offload Bypasses VPN Lockdown issue