MasqueCat is a Tailcat fork that keeps Tailcat's userspace WireGuard + gVisor application model while replacing the normal runtime transport with explicit HTTP/3 MASQUE CONNECT-UDP paths.
The goal is an in-place Tailcat CLI replacement: keep the tailcat binary,
command shape, server/client workflow, SSH/file-transfer integration, and saved
key behavior as close to upstream Tailcat as practical, while using mc...
connection tokens and MASQUE direct/relay transport.
The original tc... client path is still supported for compatibility. Starting
a legacy DERP/STUN/disco server is explicit with --legacy-derp; MasqueCat does
not silently fall back to public Tailscale DERP infrastructure.
This branch is experimental and currently has no release artifact. Build the two user-facing executables explicitly:
git clone https://github.com/knowlet/tailcat.git
cd tailcat
git checkout feat/masquecat-masque-transport
mkdir -p bin
go build -o ./bin/tailcat ./cmd/tailcat
go build -o ./bin/masquecat-relay ./cmd/masquecat-relay
./bin/tailcat version
./bin/tailcat --helpgo build ./... is useful as a compile check, but it matches multiple packages.
Go builds those packages into its build cache and does not leave a
./tailcat executable in the current directory. Use go build -o ... ./cmd/tailcat when you want a binary you can run.
To test the whole repository:
go test ./...MasqueCat deliberately has no built-in public relay. A normal relay-only setup
therefore has three steps: run your relay, point the server at it, then give the
printed mc... token to the client.
The relay terminates HTTP/3 itself and must receive UDP/443. A TCP-only HTTP/1.1 or HTTP/2 reverse proxy cannot carry this protocol.
sudo ./bin/masquecat-relay \
-listen :443 \
-cert /etc/masquecat/tls/fullchain.pem \
-key /etc/masquecat/tls/privkey.pemUse a certificate whose hostname matches the relay URL used by peers.
Set the relay once in the environment:
export MASQUECAT_RELAY_URL=https://relay.example.com
./bin/tailcatWith no positional arguments, tailcat enters server mode just like upstream
Tailcat. It prints both the configured MASQUE endpoint and the connection token,
for example:
# MASQUE relay server: https://relay.example.com
# 🐈 Server listening with new address: mc...
Because this fork intentionally has no default public relay, running tailcat
with neither a relay nor a direct endpoint configured fails closed with a clear
configuration error instead of silently reverting to DERP.
The flag form is equivalent:
./bin/tailcat --relay-url https://relay.example.comThe basic Tailcat pipe interface is unchanged:
# Default pipe mode.
echo 'hello' | ./bin/tailcat 'mc...'
# Connect to a TCP port exposed by the peer.
./bin/tailcat 'mc...' 8000
# Diagnostics.
./bin/tailcat ping 'mc...'
./bin/tailcat parse 'mc...'
./bin/tailcat resolve 'mc...'resolve simply validates and reprints an mc... token because MASQUE tokens
already contain their exact direct/relay URLs; there is no DERP-map expansion.
The upstream serve syntax is retained:
export MASQUECAT_RELAY_URL=https://relay.example.com
# Proxy incoming peer traffic to local TCP ports 80 and 443.
./bin/tailcat serve 80,443
# Ranges work too.
./bin/tailcat serve 8000-8999Then a client can use the same token with the desired port:
./bin/tailcat 'mc...' 80The built-in auth-free SSH server is retained. The WireGuard peer identity is the authentication boundary, as in Tailcat.
Server:
export MASQUECAT_RELAY_URL=https://relay.example.com
./bin/tailcat serve no-auth-sshClient:
./bin/tailcat ssh 'mc...'
./bin/tailcat ssh 'mc...' uname -atailcat ssh continues to use the current tailcat executable as OpenSSH's
ProxyCommand. Because the root pipe mode now understands mc..., SSH uses the
same MASQUE carrier without a separate MasqueCat SSH command.
The upstream cp / ls commands follow the same ProxyCommand path when the
server enables the corresponding SSH/files service.
Without --key, server mode uses a saved key named default if it exists;
otherwise it uses a new ephemeral key. To keep a stable server identity/token:
export MASQUECAT_RELAY_URL=https://relay.example.com
./bin/tailcat genkey --key=default
./bin/tailcatFor a persistent client identity:
./bin/tailcat genkey --client --key=client-defaultClient commands automatically load client-default when it exists.
A directly reachable server can advertise its own HTTP/3 endpoint. Direct mode requires an externally reachable UDP listener and a normal TLS certificate:
./bin/tailcat \
--direct-url https://mc.example.com \
--direct-listen :443 \
--tls-cert /etc/masquecat/tls/fullchain.pem \
--tls-key /etc/masquecat/tls/privkey.pemYou can configure both direct and relay endpoints:
./bin/tailcat \
--direct-url https://mc.example.com \
--direct-listen :443 \
--tls-cert /etc/masquecat/tls/fullchain.pem \
--tls-key /etc/masquecat/tls/privkey.pem \
--relay-url https://relay.example.comAt startup a client tries the explicit direct endpoint first and the explicit relay if direct setup fails. Runtime direct-to-relay migration after an already established path fails is not implemented yet.
| CLI option | Environment | Purpose |
|---|---|---|
--relay-url URL |
MASQUECAT_RELAY_URL |
Explicit MasqueCat relay URL |
--direct-url URL |
MASQUECAT_DIRECT_URL |
Public URL advertised for direct MASQUE |
--direct-listen ADDR |
MASQUECAT_DIRECT_LISTEN |
UDP listener for direct HTTP/3, e.g. :443 |
--tls-cert FILE |
MASQUECAT_TLS_CERT |
Direct endpoint TLS certificate |
--tls-key FILE |
MASQUECAT_TLS_KEY |
Direct endpoint TLS private key |
--insecure-skip-verify |
MASQUECAT_INSECURE_SKIP_VERIFY=1 |
Development-only outer TLS verification bypass |
--legacy-derp |
TAILCAT_LEGACY_DERP=1 |
Use the original Tailcat DERP/STUN/disco server/genkey path |
Production deployments should use normal TLS hostname and certificate
verification. --insecure-skip-verify is for development only.
Existing tc... tokens continue to dispatch to the upstream Tailcat client
implementation automatically:
./bin/tailcat 'tc...'
./bin/tailcat ping 'tc...'
./bin/tailcat ssh 'tc...'To deliberately start the original Tailcat server instead of MasqueCat:
./bin/tailcat --legacy-derpDERP-specific options such as DERP-map/region selection belong to this legacy mode. MasqueCat itself never needs STUN, netcheck, disco CallMeMaybe, endpoint probing, UDP hole punching, or a raw peer WireGuard UDP socket.
application / SSH / forwarded TCP
|
gVisor netstack
|
wireguard-go Device
|
MasqueBind
|
MasqueCat framing
|
HTTP/3 CONNECT-UDP
|
QUIC / UDP 443
/ \
direct MASQUE MasqueCat relay
endpoint endpoint
MasqueBind is a wireguard-go/conn.Bind implementation. WireGuard sees each
peer as a logical node-key endpoint; the bind selects a configured MASQUE path
instead of opening a WireGuard UDP socket or delegating transport to magicsock.
MasqueCat has no built-in public relay, map service, control plane, or default external hostname. Every Internet-facing endpoint is operator supplied:
DirectURLadvertises an explicitly reachable peer MASQUE endpoint.RelayURLconfigures an explicit MasqueCat relay.- relay-only deployments require outbound QUIC/UDP 443 from both peers.
- direct deployments require an explicitly reachable QUIC/UDP endpoint.
There is no MasqueCat fallback to an upstream Tailscale DERP or DERP map.
Application traffic is protected by two layers:
- WireGuard provides end-to-end encryption between MasqueCat peers.
- QUIC/TLS 1.3 protects the outer HTTP/3 connection to the direct endpoint or relay.
A relay terminates the outer TLS connection but forwards opaque WireGuard ciphertext. It can observe peer node keys and traffic metadata but cannot decrypt the inner WireGuard session.
Direct and relay CONNECT-UDP registration uses a one-time challenge and proof of possession of the advertised node private key. Duplicate live registrations are rejected rather than silently replacing the current peer.
This transport design is not an anonymity guarantee. A network observer can still see QUIC traffic, endpoint addresses, packet sizes, timing, and connection lifetimes.
The CLI is the normal entry point, but the transport is also available as a Go
API through tailcat.MasqueServer and tailcat.MasqueClient. Useful client
operations include Ping, DialTCPPort, DialTCP, Dial, DrainTCP, Path,
and Close.
For deeper protocol/design notes and relay deployment details, see: