Skip to content

tailcat: add Server.PeerKey to identify an accepted connection's peer - #129

Open
Mo3he wants to merge 1 commit into
tailscale:mainfrom
Mo3he:peerkey
Open

Mo3he wants to merge 1 commit into
tailscale:mainfrom
Mo3he:peerkey

Conversation

@Mo3he

@Mo3he Mo3he commented Sep 21, 2026

Copy link
Copy Markdown

tailcat already resolves a connection's remote address back to the node key the
tunnel authenticated, but only privately: PeerEnv formats it into
TAILCAT_PEER_KEY for served subprocesses (#78, #89), and the SSH session
handler carried a second copy of the same lookup.

A Go program embedding tailcat has no way to get the key. It can parse
PeerEnv's strings, but it cannot map a connection to one of its
AllowedClients: the address-to-key direction is peerByIP, and the
key-to-address direction is tcAddrForKey, both unexported. Export the lookup
as Server.PeerKey and route both existing callers through it, so there is one
copy instead of two.

This also covers UDP flows. Both copies type-asserted the remote address to
*net.TCPAddr, so the ConnPacketConn passed to OnUDP and OnUDPForward,
and the per-flow net.Conn that Server.Listen's UDP mode returns, never
matched and got no key. Nothing in cmd/tailcat reaches that path today, since
the CLI's UDP forwarding goes through ProxyPacketConns, but a library user
calling PeerEnv or PeerKey from a UDP handler does. PeerKey accepts
*net.UDPAddr too.

Where I ran into it: I maintain an ACAP that embeds tailcat on Axis cameras and
lists the clients that have connected, so an address shared further than
intended is visible to the operator. With AllowedClients pinned, that list
can show an address but not which authorized key it belongs to. Same app
behind #116.

TestPeerKey covers both TCP and UDP. Reverting only the *net.UDPAddr case
makes the UDP assertion fail with ok=false while TCP still passes, so the test
gates the bug fix and not just the refactor.

The API change is purely additive: one new method, nothing removed or altered.

The server already resolves a connection's remote address back to the
node key the tunnel authenticated, but only privately: PeerEnv formats
it into TAILCAT_PEER_KEY for subprocesses, and the SSH session handler
had a second copy of the same lookup. A Go program embedding tailcat
had no way to get the key itself, so it could not tell which of its
AllowedClients a connection belonged to, and had to fall back to
matching on the tailcat IP.

Export it as Server.PeerKey and route both existing callers through it.

This also fixes UDP flows. Both copies type-asserted the address to
*net.TCPAddr, so the ConnPacketConn handed to OnUDP and OnUDPForward
never matched and PeerEnv silently omitted TAILCAT_PEER_KEY for them.
PeerKey accepts *net.UDPAddr too. The new test covers both TCP and UDP
and fails on the UDP case without this change.

This branch has not been deployed

No deployments
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.

1 participant