tailscale/tailscale · error
peerAPI dial requires tcp; %q not supported
Error message
peerAPI dial requires tcp; %q not supported
What it means
Returned by Dialer.dialPeerAPI in tailscale.com/net/tsdial when the network argument is not a TCP flavor. The Tailscale peerapi is an HTTP service reachable only over TCP, so the dialer accepts exactly "tcp", "tcp4", or "tcp6" and rejects everything else before attempting a connection. This is a programmer-error guard at the API boundary, not an environmental failure.
Source
Thrown at net/tsdial/tsdial.go:677
}
if version.IsMacGUIVariant() && tsaddr.IsTailscaleIP(ipp.Addr()) {
return ipp, true, nil
}
return ipp, false, nil
}
// dialPeerAPI connects to a Tailscale peer's peerapi over TCP.
//
// network must a "tcp" type, and addr must be an ip:port. Name resolution
// is not supported.
func (d *Dialer) dialPeerAPI(ctx context.Context, network, addr string) (net.Conn, error) {
if !buildfeatures.HasPeerAPIClient {
return nil, feature.ErrUnavailable
}
switch network {
case "tcp", "tcp6", "tcp4":
default:
return nil, fmt.Errorf("peerAPI dial requires tcp; %q not supported", network)
}
ipp, err := netip.ParseAddrPort(addr)
if err != nil {
return nil, fmt.Errorf("peerAPI dial requires ip:port, not name resolution: %w", err)
}
if d.UseNetstackForIP != nil && d.UseNetstackForIP(ipp.Addr()) {
if d.NetstackDialTCP == nil {
return nil, errors.New("Dialer not initialized correctly")
}
return d.NetstackDialTCP(ctx, ipp)
}
return d.getPeerDialer().DialContext(ctx, network, addr)
}
// getPeerDialer returns the *net.Dialer to use to dial peers (e.g. for peerapi,
// "tailscale nc", or querying internal DNS servers over Tailscale)
//
// This is not used in netstack mode.View on GitHub (pinned to 57c3357fdb)
Solutions
- Pass "tcp" (or "tcp4"/"tcp6") as the network argument when dialing peerapi
- If the network string comes from config or user input, validate it against a fixed allowlist before dialing
- For non-TCP peer communication use a different mechanism — peerapi has no UDP dial path
Example fix
// before conn, err := d.dialPeerAPI(ctx, "udp", "100.101.102.103:12345") // after conn, err := d.dialPeerAPI(ctx, "tcp", "100.101.102.103:12345")
Defensive patterns
Strategy: validation
Validate before calling
func isPeerAPINetwork(network string) bool {
switch network {
case "tcp", "tcp4", "tcp6":
return true
default:
return false
}
}
if !isPeerAPINetwork(network) {
return fmt.Errorf("refusing to dial peerapi with network %q", network)
}
conn, err := d.dialPeerAPI(ctx, network, addr) Prevention
- Always dial peerapi with "tcp"
- Whitelist network strings from config before forwarding them to any dialer
- Fail fast on unknown network values instead of passing them through
When it happens
Trigger: Calling dialPeerAPI (directly or via a peerapi client dial path) with network set to "udp", "unix", an empty string, or any value other than "tcp"/"tcp4"/"tcp6".
Common situations: Generic dial wrappers that forward a user- or config-supplied network string; porting UDP-based code to peerapi; copy-pasting a net.Dial call and forgetting to change the network argument.
Related errors
- peerAPI dial requires ip:port, not name resolution: %w
- errNotUsingTailscale
- tailscale version not found in output
- failed refresh apk repository indexes: %w, output: %s
- failed refresh pkg repository indexes: %w, output: %s
AI-assisted analysis of tailscale/tailscale@57c3357fdb (2026-08-18).
Data as JSON: /api/errors/f552a294a0e046a1.
Report an issue: GitHub.