Why Android apps can't talk to your camera when you're connected to a VPN

TL;DR: When connected to a VPN app, such as Nebula, WireGuard, or Tailscale, apps designed to download photos from a camera (or otherwise interact with offline WiFi networks) may be unable to access the device’s network. Some VPN apps offer the ability to exclude those apps from the VPN—otherwise, the only workaround is to temporarily disconnect from the VPN when using them.

As a software engineer working on mesh overlay networks (aka VPNs) I typically have a VPN connected on my Android phone (a Google Pixel.) I also like taking photos, and over the years I have owned cameras from a variety of brands, including Sony, Fujifilm, Ricoh, and GoPro.

In every camera brand’s forums, the same complaints about the phone app used for downloading pictures crop up in various ways: poor UX, poor performance, or finicky file transfers. I personally found Ricoh’s Image Sync app to be so unreliable that I eventually wrote my own Android app. But I have noticed one constant issue among camera brands, and it stems from a design decision in Android itself.

When a VPN is active, camera apps are unable to reach the camera’s WiFi network—even when the VPN only routes IP addresses outside of the WiFi network’s range.

Unlike desktop operating systems, which add a VPN’s routes to a single global routing table, Android gives each network (WiFi, cellular, VPN) its own routing table and then assigns each app to a single network. Normally, that’s the phone’s default network: whichever network it’s using for Internet access. But when a VPN app is active, Android assigns apps to the VPN’s network instead. Traffic outside of the VPN’s routes is allowed to fall through—but only to the default network. The same rule applies to routes the VPN explicitly excludes. And since camera WiFi networks don’t have Internet access, they are rarely the default network (usually cellular, or a second WiFi network with Internet.)

So why doesn’t Android allow camera apps to just say they want the camera’s WiFi network instead? Well, actually, they’re already required to explicitly bind to the camera’s network so that they don’t end up on the default network (which has no route to the camera) even without a VPN. But, when a VPN is running, unless it explicitly marks itself as bypassable, Android refuses that bind by design, so that apps can’t escape a privacy-focused VPN by choosing a network other than the VPN. The result is that a VPN which only routes 100.64.0.0/10 still breaks access to 192.168.0.0/16 on the camera’s WiFi.

This affects Google’s own apps too: Tailscale excludes Android Auto and Messages (RCS) from its VPN by default because they don’t work through it. It also affects ad blockers like AdGuard, Blokada, and RethinkDNS, which use Android’s VPN interface to filter traffic without tunneling anything, to the point that apps like OpenPocketCine and my own Eureka now warn their users about them.

While Android is right to deny apps the ability to bypass a VPN, it could still allow apps to fall through to their chosen network when using a split-tunnel VPN. A split-tunnel VPN already allows apps to send traffic it doesn’t route over the default network, so blocking the bind doesn’t keep anything inside the VPN; it only stops apps from choosing which network that traffic flows out of. Full-tunnel VPNs would still capture everything, since their routes always match first. I’ve filed a feature request proposing this change.

Camera manufacturers can’t work around this from inside their own app—the fix has to come from the VPN. A VPN app can exclude specific apps from the tunnel, or mark itself as bypassable, allowing any app to escape the VPN. Many VPNs let users choose which apps to exclude in their settings. If the VPN app you use doesn’t, the only workaround is to temporarily disable the VPN.

For further reading, here are some examples of this issue on Reddit.