This document records the M56 design for future server-owned forwarding. It is a
documentation-only milestone: the existing adb-go forward command remains a
foreground, process-scoped local TCP forward, and adb-gos still does not own
ADB devices, transports, or forwards until later implementation milestones add
that behavior.
Today adb-go forwarding is intentionally direct and foreground-only. The CLI or
library process binds the host listener, connects directly to one selected ADB
target, and opens a fresh device tcp:PORT service for each accepted host
connection. When that process exits, the listener and active bridges disappear.
Persistent forwarding needs a different owner. The only adb-go process designed
to outlive a CLI invocation is adb-gos, so background forwards should be
represented as server state and controlled through explicit server protocol
commands. This keeps adb-go honest about lifetime: a forward is persistent only
when the server owns the listener and the target connection policy.
- Add a concrete design for server-owned forwards that can keep listening after
adb-go forward --background ...exits. - Preserve the current foreground
adb-go forward LOCAL REMOTEbehavior. - Define how forwards are represented, listed, removed, and diagnosed.
- Define server protocol extensions without implementing them yet.
- Keep target selection explicit; do not introduce broad adb-server-compatible discovery or global transport ownership in this design.
- No implementation in this milestone.
- No compatibility promise for the official ADB server smart-socket protocol.
- No reverse forwarding.
- No host Unix sockets, JDWP, vsock, Android local socket namespaces, or raw advanced service targets in the first persistent-forwarding slice.
- No server-managed authentication key discovery or persistence. Any future authenticated persistent forward must use explicit credentials or a separately designed auth store.
The server should store a small in-memory table of forwarding registrations. A registration describes desired listener state, not just one active connection.
Suggested logical fields:
{
"id": "fwd_01J...",
"state": "listening",
"local": {"network":"tcp", "address":"127.0.0.1:9000"},
"remote": {"service":"tcp:9000"},
"target": {"transport":"tcp", "address":"127.0.0.1:5555"},
"norebind": false,
"createdAt": "2026-07-26T12:00:00Z",
"activeConnections": 0,
"lastError": ""
}Definitions:
idis an adb-go-generated stable identifier for later removal. It should be opaque to callers.localis the host-side listener. The first implementation should support TCP only and should bind loopback by default fortcp:PORTCLI syntax.remoteis the ADB service opened on the device for each accepted host connection. The first implementation should support onlytcp:PORT.targetis the explicit ADB target selection needed to reconnect or open streams. Initially this can be TCP-only; USB persistence should be designed as a separate slice because device paths and permissions can change across reconnects.stateshould distinguish at leastlistening,degraded, andstoppedin server responses. Removed forwards do not need to stay in the table.lastErroris diagnostic text for the most recent listener, target, or bridge setup failure. It is informational and not a stable API field.
The table should be in-memory for the first server-backed implementation.
Systemd can keep adb-gos running, but restarting the server intentionally drops
all registered forwards unless a later milestone designs durable state.
The existing foreground command remains unchanged:
adb-go forward --addr 127.0.0.1:5555 tcp:9000 tcp:9000Persistent forwarding should be opt-in with an explicit background flag:
adb-go forward --background --addr 127.0.0.1:5555 tcp:9000 tcp:9000
adb-go forward --background --norebind --addr 127.0.0.1:5555 tcp:9000 tcp:9000Behavior:
- Without
--background, the command continues to run in the foreground exactly as it does today. - With
--background, the CLI sends a server request and exits after the server has bound the listener or returned a setup error. --norebindrejects creation when another server-owned forward already uses the same local endpoint. Without--norebind, adb-go may replace an existing server-owned forward for that local endpoint, but it must never steal an unrelated listener owned by another process.- If the local port is
tcp:0, the server binds an ephemeral port and returns the actual address so the CLI can print it.
Listing and removal should be server-backed commands, not foreground-client commands:
adb-go forward --list
adb-go forward --remove tcp:9000
adb-go forward --remove-id fwd_01J...
adb-go forward --remove-allListing output should be human-readable by default and include enough data for support requests:
ID LOCAL REMOTE TARGET STATE ACTIVE
fwd_01JABC 127.0.0.1:9000 tcp:9000 tcp:127.0.0.1:5555 listening 2
Removal semantics:
--remove LOCALremoves the server-owned registration for the local endpoint.--remove-id IDremoves exactly one registration by server ID and is safest for scripts.--remove-allcloses all server-owned forwarding listeners.- Removing a forward closes its listener and all active bridged connections.
- Removing a missing forward should return a clear not-found error.
The command should make server dependence visible. If adb-gos is unreachable,
background/list/remove operations should fail with a hint to run
adb-go server doctor or start/install the server service.
The server control protocol can remain newline-delimited JSON with
version: 1 if these commands are added as optional request/response shapes.
Older servers will return unknown_command, which the CLI can turn into a clear
"server is too old" message.
Suggested commands:
forward_createforward_listforward_removeforward_remove_all
Request:
{
"version": 1,
"id": "c1",
"command": "forward_create",
"params": {
"local": {"network":"tcp", "address":"127.0.0.1:9000"},
"remote": {"service":"tcp:9000"},
"target": {"transport":"tcp", "address":"127.0.0.1:5555"},
"norebind": false
}
}Successful response:
{
"version": 1,
"id": "c1",
"ok": true,
"result": {
"forward": {
"id": "fwd_01JABC",
"state": "listening",
"local": {"network":"tcp", "address":"127.0.0.1:9000"},
"remote": {"service":"tcp:9000"},
"target": {"transport":"tcp", "address":"127.0.0.1:5555"},
"activeConnections": 0
}
}
}Creation should bind the local listener before returning success. A bind failure
or target-selection validation failure returns ok:false and a stable error
code such as address_in_use, unsupported_endpoint, bad_target, or
rebind_disallowed.
Request:
{"version":1,"id":"l1","command":"forward_list"}Successful response includes all current server-owned forwards:
{
"version": 1,
"id": "l1",
"ok": true,
"result": {"forwards": []}
}Request by ID:
{"version":1,"id":"r1","command":"forward_remove","params":{"id":"fwd_01JABC"}}Request by local endpoint:
{
"version": 1,
"id": "r2",
"command": "forward_remove",
"params": {"local":{"network":"tcp", "address":"127.0.0.1:9000"}}
}The server should require exactly one selector. Success means the listener has been closed and active bridges have been asked to close.
Request:
{"version":1,"id":"ra1","command":"forward_remove_all"}The response should include a count of removed forwards.
For each server-owned registration:
- The server binds the local TCP listener.
- For each accepted host connection, the server dials or reuses the selected ADB target according to the implementation slice.
- The server opens the configured remote service, such as
tcp:9000. - The server copies bytes in both directions until one side closes or errors.
- Connection-level failures close only that host connection. Listener-level
failures move the registration to
degradedor remove it, depending on the failure.
The first implementation should prefer simple correctness over pooling. Opening one ADB connection or stream per accepted host connection is easier to reason about and avoids server-wide device lifecycle ownership. Later milestones can add connection reuse if tests prove it is safe.
On graceful shutdown, adb-gos closes every forwarding listener and active
bridge before exiting. Because the initial table is in-memory, a server restart
starts with no forwards. This must be documented in CLI help and --list output
should not imply durable state.
A systemd user-service restart has the same effect as server restart: all forwards are dropped. Users who need forwards recreated after login or restart should use their own service units or scripts until adb-go explicitly designs persistent on-disk configuration.
A failed per-connection target dial or ADB stream open should reject that host
connection and update lastError, but the listener may remain active so later
connections can succeed after the device returns. If repeated failures occur,
the server may report state: degraded while keeping the listener bound.
If another process owns the requested local address, creation fails with
address_in_use. If another server-owned forward owns it:
norebind: truefails withrebind_disallowed.norebind: falsemay replace the old server-owned registration after closing its listener and active bridges.
The server must not remove or alter listeners it does not own.
- Bind loopback for shorthand CLI endpoints. Users must opt in explicitly to wider bind addresses if a later CLI syntax supports them.
- Treat remote services and target addresses as caller-controlled input; do not add allowlists beyond the endpoint families supported by the specific milestone.
- Do not log forwarded payload bytes.
- Include forwarding counts in server diagnostics, but keep detailed mappings
and last setup errors in
forward --listsoserver statusremains concise. - Keep errors actionable and stable enough for CLI handling: unsupported endpoint, server too old, server unavailable, address in use, rebind disallowed, forward not found, and target connection failed.
- Add server protocol request/response types and tests for forwarding commands without opening listeners.
- Implement TCP listener ownership in
adb-goswith create/list/remove/remove all over loopback TCP endpoints. - Bridge accepted connections to direct TCP ADB targets and
tcp:PORTremote services. - Add CLI
forward --background,--list,--remove,--remove-id, and--remove-allwired to the server protocol. - Surface concise forwarding diagnostics in
server statusandserver doctorwhile keeping the detailed table inforward --list. - Revisit USB target persistence, broader endpoint families, and optional durable forward restoration as separate designs.