Connect an Android emulator to local services
lim android tunnel sends the connections you declare from the emulator through the machine that runs the tunnel, so the app keeps using the addresses it already has. This page covers Android emulators; the selector and inspection rules are shared with iOS and described once in Connect an iOS simulator to local services.
Start a tunnel
Start the tunnel before you launch the app. Connections the app opened earlier keep their original route until they close. Declare each destination as a --selector:
lim android tunnel --selector localhost:8080 --id <instance-id>Without --detach, the tunnel runs until you stop the command. Add --detach to keep it running in the background, and --verbose to log every forwarded connection and dial failure; with --detach those lines go to the tunnel log file that tunnel status points to.
Query or stop the tunnel from another process:
lim android tunnel status --id <instance-id>
lim android tunnel stop --id <instance-id>From the TypeScript SDK, start the tunnel on a device client that was opened with the instance's adbUrl (status.adbWebSocketUrl). The caller owns the returned tunnel; disconnecting the device client does not close it.
import { createInstanceClient } from '@limrun/api';
const client = await createInstanceClient({
apiUrl: instance.status.apiUrl!,
adbUrl: instance.status.adbWebSocketUrl!,
token: instance.status.token!,
});
const tunnel = await client.startTunnel({ selectors: ['localhost:8080', '*.corp.example'] });
const status = await client.getTunnelStatus();
await client.stopTunnel(tunnel.tunnelId);Choose selectors
Selectors take the same forms as on iOS: localhost:port, IPv4:port, [IPv6]:port, an exact domain, or a *. wildcard domain. The domain rules, the caps of ten exact selectors and 64 domain selectors, and the port 53 exclusion are described in Choose selectors and Limits and behavior.
Two rules differ on Android:
- Exact selectors need a port of 1024 or higher. A lower port fails with
invalid tunnel route port <port>. - Exact selectors are also reachable as
10.0.2.2:<port>, following the emulator convention for the host machine. An app configured for10.0.2.2:8080reacheslocalhost:8080on your machine.
Apps that resolve DNS themselves over HTTPS (DoH) bypass domain interception; declare the resolved IP instead.
Inspect tunnel traffic
HTTP and HTTPS through the tunnel are inspected by default, with the same --[no-]inspect, --har, --har-body-limit, --persist, and --ttl flags as on iOS. See Inspect tunnel traffic for what each flag does and how inspection affects apps that pin certificates.
This keeps a persisted network log for seven days:
lim android tunnel --selector "*.api.example" --persist --ttl 604800 --id <instance-id>Use adb reverse instead
For exact ports, adb reverse over the ADB tunnel also works. It forwards a port on the emulator to a port on your machine, without domain interception or traffic inspection:
adb -s 127.0.0.1:<adb-port> reverse tcp:8080 tcp:8080Troubleshooting
| Symptom or message | Cause | Fix |
|---|---|---|
invalid tunnel route port <port> | The exact selector uses a port below 1024, or port 53. | Run the local service on a port of 1024 or higher and declare that port. |
| The app ignores the tunnel | The app opened its connections before the tunnel started. | Start the tunnel first, then relaunch the app. |
| A domain selector has no effect | The app resolves DNS over HTTPS, or the wildcard does not cover the bare domain. | Declare the resolved IP as host:port, or add the exact domain next to the wildcard. |
| An app with certificate pinning fails through the tunnel | Inspection decodes HTTPS with a certificate the pinning app rejects. | Leave that host out of the selectors, or pass --no-inspect. |
adbUrl is required to manage a destination tunnel | The TypeScript device client was created without adbUrl. | Pass adbUrl: instance.status.adbWebSocketUrl to createInstanceClient. |
Next steps
Was this guide helpful?