Skip to content

cmd/tailcat: support forwarding to exit-node targets - #75

Merged
bradfitz merged 4 commits into
tailscale:mainfrom
Audi-dask:forward-exit-node-v2
Sep 3, 2026
Merged

cmd/tailcat: support forwarding to exit-node targets#75
bradfitz merged 4 commits into
tailscale:mainfrom
Audi-dask:forward-exit-node-v2

Conversation

@Audi-dask

@Audi-dask Audi-dask commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

What does this change do?

Extend the tailcat forward subcommand to forward local TCP ports to
arbitrary IP:port targets reachable through a Tailcat server running as an
exit node.

This is useful for accessing TCP services on remote network assets through
an exit-node server, including applications that only support regular
host:port connections and do not provide Tailnet, SOCKS, or stdio proxy
integration.

The mapping syntax for exit-node targets is:

<local-port>:<remote-ip>:<remote-port>

The <addrblob> argument is the address blob printed by tailcat serve,
typically a value beginning with tc.

Related to #14.

Examples

Start a Tailcat server in exit-node mode:

tailcat serve exit-node
# Server listening with new address: tcXXXXXXXXX

Forward local ports to two remote network assets:

tailcat forward tcXXXXXXXXX \
    3001:172.23.52.30:3001 \
    17170:172.23.52.31:17170

This forwards:

127.0.0.1:3001  -> Tailcat -> exit-node -> 172.23.52.30:3001
127.0.0.1:17170 -> Tailcat -> exit-node -> 172.23.52.31:17170

Regular forwarding to ports served directly by the Tailcat server continues
to work:

tailcat serve 8080,3306
# Server listening with new address: tcXXXXXXXXX

tailcat forward tcXXXXXXXXX 18080:8080 3306

By default, local listeners bind to 127.0.0.1. Use --bind when a
specific local address is required:

tailcat forward --bind=0.0.0.0 tcXXXXXXXXX \
    3001:172.23.52.30:3001 \
    17170:172.23.52.31:17170

Use --bind=0.0.0.0 only when intentionally allowing connections from
other network interfaces.

Design

  • Reuses one Tailcat client for all forwarded ports.
  • Uses the existing Client.DialTCP API for exit-node IP:port targets.
  • Uses the existing Client.DialTCPPort API for ports served by Tailcat.
  • Uses the existing ProxyConns helper.
  • Supports regular <remote-port> and <local>:<remote-port> mappings.
  • Supports multiple local-to-remote mappings in one command.
  • Supports IPv4 and IPv6 IP:port targets.
  • Binds to 127.0.0.1 by default.
  • Does not change the Tailcat protocol or server behavior.
  • Does not introduce a new dependency.

Testing

  • Added unit test coverage for regular and exit-node target mapping parsing.
  • Added an end-to-end test for forwarding to an exit-node target.
  • Preserved the existing end-to-end test for forwarding to a Tailcat-served
    port.
  • Verified multiple exit-node target mappings manually.

Tested with:

go test ./cmd/tailcat -run '^TestForward(ToExitNodeTarget|EndToEnd)$' -v

Allow forward mappings to target IP:port destinations reachable through an exit-node server, with end-to-end coverage and README examples.
@bradfitz

bradfitz commented Sep 3, 2026

Copy link
Copy Markdown
Member

You already have #74 open. Why'd you open a new one?

@Audi-dask

Copy link
Copy Markdown
Contributor Author

Sorry for the duplicate PR. I created #75 after rebasing the change onto the latest main, since #74 included the forward subcommand changes that had already landed in #62. #74 is now closed, and #75 is the cleaned-up version.

@bradfitz
bradfitz merged commit 750f1d1 into tailscale:main Sep 3, 2026
6 checks passed
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