MobDev.Tunnel (mob_dev v0.7.4)

Copy Markdown View Source

Manages port tunnels for Android and physical iOS devices.

Android (adb): adb reverse tcp:4369 tcp:4369 — Android BEAM registers in Mac's EPMD adb forward tcp:<dist> tcp:<dist> — Mac reaches the device's dist port (1:1)

Physical iOS (direct networking — WiFi/LAN preferred, USB link-local fallback): mob_beam.m finds the device's own IP via getifaddrs() and starts the BEAM as <app>_ios@<device-ip>. The in-process EPMD binds 0.0.0.0:4369 so Mac can query it at any of the device's IPs. The dist port is directly reachable. A USB-discovered device's node is read from EPMD, not predicted (MobDev.Discovery.IOS.resolve_usb_node/1).

iOS simulator: Shares Mac network stack — no tunnels needed.

Dist ports are keyed by device serial, not run index

The Mac runs ONE EPMD (port 4369) that every device — across every project and every mix mob.connect run — registers into. Assigning dist ports by per-run index (9100 + index) meant project A's device-0 and project B's device-0 both claimed 9100: two nodes at the same port in the shared EPMD, but adb forward tcp:9100 can only point at one device → the other resolved to the wrong phone or nothing (silent timeout). Now the port is derived from the device serial (serial_base_port/1, a crc32 hash into 9100..9899), so a given phone always gets the same unique port regardless of project/run, and assign_dist_port/2 bumps past any port another live node/forward already holds (cross-project or hash collision).

Summary

Functions

The serial's base port, bumped to the next free slot if in_use already claims it (a cross-project collision or a crc32 hash collision between two serials). Walks the window from the base; falls back to the base if the whole window is somehow taken. Pure — in_use is gathered by the caller.

Forwards host port to the same port on serial, to reach a node that is already running there. Refuses ({:error, _}) when the host port already forwards to a different device, rather than taking it from that session.

Makes sure an Android app started now can join distribution: adb reverse for EPMD (so the device BEAM registers in the Mac's EPMD) and a forward of the dist port. Both are gone after an emulator reboot or an adbd restart, and an app launched without them gives up on dist after 10 s. Idempotent.

The {name, port} pairs registered in the Mac's EPMD, which every Android device and iOS simulator registers into. Empty when EPMD isn't reachable.

Stable, deterministic dist port for a device serial — a crc32 hash into [9100, 9100 + 800). Same serial → same port across runs and projects, so the port a device is deployed to listen on matches what mix mob.connect later forwards to.

Assigns a serial-derived dist port and sets up tunnels for a device.

Tears down tunnels for a device.

Functions

assign_dist_port(serial, in_use \\ MapSet.new())

@spec assign_dist_port(String.t(), MapSet.t()) :: pos_integer()

The serial's base port, bumped to the next free slot if in_use already claims it (a cross-project collision or a crc32 hash collision between two serials). Walks the window from the base; falls back to the base if the whole window is somehow taken. Pure — in_use is gathered by the caller.

attach_forward(serial, port)

@spec attach_forward(String.t(), pos_integer()) :: :ok | {:error, String.t()}

Forwards host port to the same port on serial, to reach a node that is already running there. Refuses ({:error, _}) when the host port already forwards to a different device, rather than taking it from that session.

ensure_android(serial, port)

@spec ensure_android(String.t(), pos_integer()) :: :ok | {:error, String.t()}

Makes sure an Android app started now can join distribution: adb reverse for EPMD (so the device BEAM registers in the Mac's EPMD) and a forward of the dist port. Both are gone after an emulator reboot or an adbd restart, and an app launched without them gives up on dist after 10 s. Idempotent.

epmd_names()

@spec epmd_names() :: [{String.t(), pos_integer()}]

The {name, port} pairs registered in the Mac's EPMD, which every Android device and iOS simulator registers into. Empty when EPMD isn't reachable.

serial_base_port(serial)

@spec serial_base_port(String.t()) :: pos_integer()

Stable, deterministic dist port for a device serial — a crc32 hash into [9100, 9100 + 800). Same serial → same port across runs and projects, so the port a device is deployed to listen on matches what mix mob.connect later forwards to.

setup(device)

@spec setup(MobDev.Device.t()) :: {:ok, MobDev.Device.t()} | {:error, String.t()}

Assigns a serial-derived dist port and sets up tunnels for a device.

Cleans the device's own stale forwards first, then picks a port that no other live node/forward on this Mac is using. Returns {:ok, %Device{}} with dist_port (and host_ip for USB iOS) filled in, or {:error, reason}.

teardown(device)

@spec teardown(MobDev.Device.t()) :: :ok

Tears down tunnels for a device.