tailscale.comtailscale.com/derp/derphttp Index | Files

package derphttp

import "tailscale.com/derp/derphttp"

Package derphttp implements DERP-over-HTTP.

This makes DERP look exactly like WebSockets. A server can implement DERP over HTTPS and even if the TLS connection intercepted using a fake root CA, unless the interceptor knows how to detect DERP packets, it will look like a web socket.

Index

Constants

const (
	NoContentChallengeHeader = "X-Tailscale-Challenge"
	NoContentResponseHeader  = "X-Tailscale-Response"
)

Variables

var ErrClientClosed = errors.New("derphttp.Client closed")

Functions

func Handler

func Handler(s *derp.Server) http.Handler

Handler returns an http.Handler to be mounted at /derp, serving s.

func ProbeHandler

func ProbeHandler(w http.ResponseWriter, r *http.Request)

ProbeHandler is the endpoint that clients without UDP access (including js/wasm) hit to measure DERP latency, as a replacement for UDP STUN queries.

func ServeNoContent

func ServeNoContent(w http.ResponseWriter, r *http.Request)

ServeNoContent generates the /generate_204 response used by Tailscale's captive portal detection.

Types

type AddressFamilySelector

type AddressFamilySelector interface {
	// PreferIPv6 reports whether IPv4 dials should be slightly
	// delayed to give IPv6 a better chance of winning dial races.
	// Implementations should only return true if IPv6 is expected
	// to succeed. (otherwise delaying IPv4 will delay the
	// connection overall)
	PreferIPv6() bool
}

AddressFamilySelector decides whether IPv6 is preferred for outbound dials.

type Client

type Client struct {
	TLSConfig     *tls.Config        // optional; nil means default
	HealthTracker *health.Tracker    // optional; used if non-nil only
	DNSCache      *dnscache.Resolver // optional; nil means no caching
	MeshKey       string             // optional; for trusted clients
	IsProber      bool               // optional; for probers to optional declare themselves as such

	// WatchConnectionChanges is whether the client wishes to subscribe to
	// notifications about clients connecting & disconnecting.
	//
	// Only trusted connections (using MeshKey) are allowed to use this.
	WatchConnectionChanges bool

	// BaseContext, if non-nil, returns the base context to use for dialing a
	// new derp server. If nil, context.Background is used.
	// In either case, additional timeouts may be added to the base context.
	BaseContext func() context.Context
	// contains filtered or unexported fields
}

Client is a DERP-over-HTTP client.

It automatically reconnects on error retry. That is, a failed Send or Recv will report the error and not retry, but subsequent calls to Send/Recv will completely re-establish the connection (unless Close has been called).

func NewClient

func NewClient(privateKey key.NodePrivate, serverURL string, logf logger.Logf, netMon *netmon.Monitor) (*Client, error)

NewClient returns a new DERP-over-HTTP client. It connects lazily. To trigger a connection, use Connect.

func NewNetcheckClient

func NewNetcheckClient(logf logger.Logf, netMon *netmon.Monitor) *Client

NewNetcheckClient returns a Client that's only able to have its DialRegionTLS method called. It's used by the netcheck package.

func NewRegionClient

func NewRegionClient(privateKey key.NodePrivate, logf logger.Logf, netMon *netmon.Monitor, getRegion func() *tailcfg.DERPRegion) *Client

NewRegionClient returns a new DERP-over-HTTP client. It connects lazily. To trigger a connection, use Connect. The healthTracker parameter is also optional.

func (*Client) Close

func (c *Client) Close() error

Close closes the client. It will not automatically reconnect after being closed.

func (*Client) ClosePeer

func (c *Client) ClosePeer(target key.NodePublic) error

ClosePeer asks the server to close target's TCP connection.

Only trusted connections (using MeshKey) are allowed to use this.

func (*Client) Connect

func (c *Client) Connect(ctx context.Context) error

Connect connects or reconnects to the server, unless already connected. It returns nil if there was already a good connection, or if one was made.

func (*Client) DialRegionTLS

func (c *Client) DialRegionTLS(ctx context.Context, reg *tailcfg.DERPRegion) (tlsConn *tls.Conn, connClose io.Closer, node *tailcfg.DERPNode, err error)

DialRegionTLS returns a TLS connection to a DERP node in the given region.

DERP nodes for a region are tried in sequence according to their order in the DERP map. TLS is initiated on the first node where a socket is established.

func (*Client) ForwardPacket

func (c *Client) ForwardPacket(from, to key.NodePublic, b []byte) error

func (*Client) LocalAddr

func (c *Client) LocalAddr() (netip.AddrPort, error)

LocalAddr reports c's local TCP address, without any implicit connect or reconnect.

func (*Client) NotePreferred

func (c *Client) NotePreferred(v bool)

NotePreferred notes whether this Client is the caller's preferred (home) DERP node. It's only used for stats.

func (*Client) Ping

func (c *Client) Ping(ctx context.Context) error

Ping sends a ping to the peer and waits for it either to be acknowledged (in which case Ping returns nil) or waits for ctx to be over and returns an error. It will wait at most 5 seconds before returning an error.

Another goroutine must be in a loop calling Recv or RecvDetail or ping responses won't be handled.

func (*Client) Recv

func (c *Client) Recv() (derp.ReceivedMessage, error)

Recv reads a message from c. The returned message may alias memory from Client. The message should only be used until the next Client call.

func (*Client) RecvDetail

func (c *Client) RecvDetail() (m derp.ReceivedMessage, connGen int, err error)

RecvDetail is like Recv, but additional returns the connection generation on each message. The connGen value is incremented every time the derphttp.Client reconnects to the server.

func (*Client) RunWatchConnectionLoop

func (c *Client) RunWatchConnectionLoop(ctx context.Context, ignoreServerKey key.NodePublic, infoLogf logger.Logf, add func(derp.PeerPresentMessage), remove func(derp.PeerGoneMessage))

RunWatchConnectionLoop loops until ctx is done, sending WatchConnectionChanges and subscribing to connection changes.

If the server's public key is ignoreServerKey, RunWatchConnectionLoop returns.

Otherwise, the add and remove funcs are called as clients come & go. Note that add is called for every new connection and remove is only called for the final disconnection. See https://github.com/tailscale/tailscale/issues/13566. This behavior will likely change. Callers should do their own accounting and dup suppression as needed.

infoLogf, if non-nil, is the logger to write periodic status updates about how many peers are on the server. Error log output is set to the c's logger, regardless of infoLogf's value.

To force RunWatchConnectionLoop to return quickly, its ctx needs to be closed, and c itself needs to be closed.

It is a fatal error to call this on an already-started Client without having initialized Client.WatchConnectionChanges to true.

If the DERP connection breaks and reconnects, remove will be called for all previously seen peers, with Reason type PeerGoneReasonSynthetic. Those clients are likely still connected and their add message will appear after reconnect.

func (*Client) SelfPublicKey

func (c *Client) SelfPublicKey() key.NodePublic

SelfPublicKey returns our own public key.

func (*Client) Send

func (c *Client) Send(dstKey key.NodePublic, b []byte) error

func (*Client) SendPing

func (c *Client) SendPing(data [8]byte) error

SendPing writes a ping message, without any implicit connect or reconnect. This is a lower-level interface that writes a frame without any implicit handling of the response pong, if any. For a higher-level interface, use Ping.

func (*Client) SendPong

func (c *Client) SendPong(data [8]byte) error

SendPong sends a reply to a ping, with the ping's provided challenge/identifier data.

Unlike other send methods, SendPong makes no attempt to connect or reconnect to the peer. It's best effort. If there's a connection problem, the server will choose to hang up on us if we're not replying.

func (*Client) ServerPublicKey

func (c *Client) ServerPublicKey() key.NodePublic

ServerPublicKey returns the server's public key.

It only returns a non-zero value once a connection has succeeded from an earlier call.

func (*Client) SetAddressFamilySelector

func (c *Client) SetAddressFamilySelector(s AddressFamilySelector)

SetAddressFamilySelector sets the AddressFamilySelector that this connection will use. It should be called before any dials. The value must not be nil. If called more than once, s must be the same concrete type as any prior calls.

func (*Client) SetCanAckPings

func (c *Client) SetCanAckPings(v bool)

SetCanAckPings sets whether this client will reply to ping requests from the server.

This only affects future connections.

func (*Client) SetURLDialer

func (c *Client) SetURLDialer(dialer netx.DialFunc)

SetURLDialer sets the dialer to use for dialing URLs. This dialer is only use for clients created with NewClient, not NewRegionClient. If unset or nil, the default dialer is used.

The primary use for this is the derper mesh mode to connect to each other over a VPC network.

func (*Client) String

func (c *Client) String() string

func (*Client) TLSConnectionState

func (c *Client) TLSConnectionState() (_ *tls.ConnectionState, ok bool)

TLSConnectionState returns the last TLS connection state, if any. The client must already be connected.

type ConnectedState

type ConnectedState struct {
	Connected  bool
	Connecting bool
	Closed     bool
	LocalAddr  netip.AddrPort // if Connected
}

ConnectedState describes the state of a derphttp Client.

Source Files

derphttp_client.go derphttp_server.go mesh_client.go websocket_stub.go

Version
v1.84.1 (latest)
Published
May 29, 2025
Platform
linux/amd64
Imports
35 packages
Last checked
1 day ago

Tools for package owners.