Skip to content

feat: optional reverse TCP tunnel for WDA in NAT-restricted environments#1128

Open
dankefox wants to merge 1 commit intoappium:masterfrom
dankefox:feat/reverse-tcp-tunnel
Open

feat: optional reverse TCP tunnel for WDA in NAT-restricted environments#1128
dankefox wants to merge 1 commit intoappium:masterfrom
dankefox:feat/reverse-tcp-tunnel

Conversation

@dankefox
Copy link
Copy Markdown

@dankefox dankefox commented Apr 25, 2026

Summary

Add an optional reverse TCP tunnel mode that allows WDA to actively connect outbound to an external relay server. This enables remote control of iOS devices in network environments where inbound connections to port 8100 are not feasible.

Problem

WDA defaults to listening on port 8100 for inbound HTTP connections. However, in many real-world environments, inbound connections to the iOS device are blocked or unreachable:

  • Symmetric NAT — common in corporate and campus networks
  • Multi-layer firewalls — enterprise security policies blocking inbound traffic to mobile devices
  • VPN tunnels — overlay networks where iOS restricts inbound TCP on the tunnel interface
  • Remote test labs — devices behind NAT gateways with no port forwarding

In these scenarios, the standard http://<device-ip>:8100 approach simply does not work.

Solution

Instead of requiring the client to connect in to WDA, this PR lets WDA connect out to a relay server. The relay bridges HTTP clients on one side and the WDA reverse connection on the other.

How it works

  1. Set WDA_RELAY_HOST and optionally WDA_RELAY_PORT (default: 8201) as environment variables when launching WDA
  2. On startup, WDA opens an outbound TCP connection to the relay
  3. The relay forwards HTTP requests through this connection and returns responses
  4. If the connection drops, WDA automatically reconnects after 5 seconds

When not configured

Zero impact. If WDA_RELAY_HOST is not set, the feature is completely inactive. No code paths are touched, no connections are made, existing behavior is identical.

Changes

File Change
FBConfiguration.h/m Add relayHost and relayPort accessors reading from WDA_RELAY_HOST / WDA_RELAY_PORT env vars
FBWebServer.m Add reverse tunnel client using Apple's Network.framework (nw_connection), with auto-reconnect on failure
Scripts/wda-relay-server.js Example Node.js relay server (~100 lines) that bridges HTTP ↔ reverse tunnel

Usage

# 1. Start the relay server on a reachable host
node Scripts/wda-relay-server.js  # listens on 8201 (relay) and 8100 (HTTP proxy)

# 2. Launch WDA with relay configured
WDA_RELAY_HOST=192.168.1.100 WDA_RELAY_PORT=8201 xcodebuild test-without-building \
  -project WebDriverAgent.xcodeproj \
  -scheme WebDriverAgentRunner \
  -destination 'id=<device-udid>'

# 3. Use WDA as usual — requests go through the relay
curl http://localhost:8100/status

Design decisions

  • Opt-in via environment variables — consistent with existing USE_PORT, USE_IP, and MJPEG_SERVER_PORT patterns in FBConfiguration
  • Network.framework — uses Apple's modern networking API (nw_connection) for reliable connection management
  • 4-byte length-prefixed framing — simple binary protocol for multiplexing HTTP over a single TCP connection
  • Auto-reconnect — 5-second backoff on connection failure, no manual intervention needed
  • Relay server as example script — included in Scripts/ for easy adoption, not a required component

Testing

Tested on:

  • iPhone 16 Pro Max, iOS 26.3.1
  • Xcode 26.4
  • macOS Tahoe 26.4
  • Relay server running on a separate host behind NAT

Verified: /status, /session, tap, swipe, screenshot, and other WDA endpoints all work correctly through the reverse tunnel.

Add opt-in reverse TCP tunnel mode that allows WDA to actively connect
outbound to an external relay server, enabling remote control in
environments where inbound connections to the iOS device are not feasible
(symmetric NAT, multi-layer firewalls, corporate VPNs, etc.).

Controlled via environment variables (disabled by default):
- WDA_RELAY_HOST: relay server address
- WDA_RELAY_PORT: relay server port (default 8201)

When not configured, WDA behavior is completely unchanged.

Includes:
- FBConfiguration: relay host/port accessors from env vars
- FBWebServer: reverse tunnel client with auto-reconnect
- Scripts/wda-relay-server.js: example Node.js relay server
@linux-foundation-easycla
Copy link
Copy Markdown

CLA Missing ID CLA Not Signed

@@ -0,0 +1,122 @@
#!/usr/bin/env node
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't find this script useful.
consider adding an end-to-end test instead


#pragma mark - Reverse TCP Tunnel

- (void)startReverseTunnel
Copy link
Copy Markdown

@mykola-mokhnach mykola-mokhnach Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this functionality must be extracted to a separate module and covered by integration tests

}
}

[[[NSURLSession sharedSession] dataTaskWithRequest:localReq
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method is too complicated. Consider splitting it to smaller parts

return true;
});

if (bodyLen == 0 || bodyLen > 10 * 1024 * 1024) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this magic number?

@mykola-mokhnach
Copy link
Copy Markdown

I'm not sure why to reinvent a custom protocol if the project already uses CocoaHTTPServer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants