Update dependencies
This commit is contained in:
61
vendor/tailscale.com/derp/README.md
generated
vendored
Normal file
61
vendor/tailscale.com/derp/README.md
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
# DERP
|
||||
|
||||
This directory (and subdirectories) contain the DERP code. The server itself is
|
||||
in `../cmd/derper`.
|
||||
|
||||
DERP is a packet relay system (client and servers) where peers are addressed
|
||||
using WireGuard public keys instead of IP addresses.
|
||||
|
||||
It relays two types of packets:
|
||||
|
||||
* "Disco" discovery messages (see `../disco`) as the a side channel during [NAT
|
||||
traversal](https://tailscale.com/blog/how-nat-traversal-works/).
|
||||
|
||||
* Encrypted WireGuard packets as the fallback of last resort when UDP is blocked
|
||||
or NAT traversal fails.
|
||||
|
||||
## DERP Map
|
||||
|
||||
Each client receives a "[DERP
|
||||
Map](https://pkg.go.dev/tailscale.com/tailcfg#DERPMap)" from the coordination
|
||||
server describing the DERP servers the client should try to use.
|
||||
|
||||
The client picks its home "DERP home" based on latency. This is done to keep
|
||||
costs low by avoid using cloud load balancers (pricey) or anycast, which would
|
||||
necessarily require server-side routing between DERP regions.
|
||||
|
||||
Clients pick their DERP home and report it to the coordination server which
|
||||
shares it to all the peers in the tailnet. When a peer wants to send a packet
|
||||
and it doesn't already have a WireGuard session open, it sends disco messages
|
||||
(some direct, and some over DERP), trying to do the NAT traversal. The client
|
||||
will make connections to multiple DERP regions as needed. Only the DERP home
|
||||
region connection needs to be alive forever.
|
||||
|
||||
## DERP Regions
|
||||
|
||||
Tailscale runs 1 or more DERP nodes (instances of `cmd/derper`) in various
|
||||
geographic regions to make sure users have low latency to their DERP home.
|
||||
|
||||
Regions generally have multiple nodes per region "meshed" (routing to each
|
||||
other) together for redundancy: it allows for cloud failures or upgrades without
|
||||
kicking users out to a higher latency region. Instead, clients will reconnect to
|
||||
the next node in the region. Each node in the region is required to to be meshed
|
||||
with every other node in the region and forward packets to the other nodes in
|
||||
the region. Packets are forwarded only one hop within the region. There is no
|
||||
routing between regions. The assumption is that the mesh TCP connections are
|
||||
over a VPC that's very fast, low latency, and not charged per byte. The
|
||||
coordination server assigns the list of nodes in a region as a function of the
|
||||
tailnet, so all nodes within a tailnet should generally be on the same node and
|
||||
not require forwarding. Only after a failure do clients of a particular tailnet
|
||||
get split between nodes in a region and require inter-node forwarding. But over
|
||||
time it balances back out. There's also an admin-only DERP frame type to force
|
||||
close the TCP connection of a particular client to force them to reconnect to
|
||||
their primary if the operator wants to force things to balance out sooner.
|
||||
(Using the `(*derphttp.Client).ClosePeer` method, as used by Tailscale's
|
||||
internal rarely-used `cmd/derpprune` maintenance tool)
|
||||
|
||||
We generally run a minimum of three nodes in a region not for quorum reasons
|
||||
(there's no voting) but just because two is too uncomfortably few for cascading
|
||||
failure reasons: if you're running two nodes at 51% load (CPU, memory, etc) and
|
||||
then one fails, that makes the second one fail. With three or more nodes, you
|
||||
can run each node a bit hotter.
|
||||
256
vendor/tailscale.com/derp/derp.go
generated
vendored
Normal file
256
vendor/tailscale.com/derp/derp.go
generated
vendored
Normal file
@@ -0,0 +1,256 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package derp implements the Designated Encrypted Relay for Packets (DERP)
|
||||
// protocol.
|
||||
//
|
||||
// DERP routes packets to clients using curve25519 keys as addresses.
|
||||
//
|
||||
// DERP is used by Tailscale nodes to proxy encrypted WireGuard
|
||||
// packets through the Tailscale cloud servers when a direct path
|
||||
// cannot be found or opened. DERP is a last resort. Both sides
|
||||
// between very aggressive NATs, firewalls, no IPv6, etc? Well, DERP.
|
||||
package derp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MaxPacketSize is the maximum size of a packet sent over DERP.
|
||||
// (This only includes the data bytes visible to magicsock, not
|
||||
// including its on-wire framing overhead)
|
||||
const MaxPacketSize = 64 << 10
|
||||
|
||||
// magic is the DERP magic number, sent in the frameServerKey frame
|
||||
// upon initial connection.
|
||||
const magic = "DERP🔑" // 8 bytes: 0x44 45 52 50 f0 9f 94 91
|
||||
|
||||
const (
|
||||
nonceLen = 24
|
||||
frameHeaderLen = 1 + 4 // frameType byte + 4 byte length
|
||||
keyLen = 32
|
||||
maxInfoLen = 1 << 20
|
||||
keepAlive = 60 * time.Second
|
||||
)
|
||||
|
||||
// ProtocolVersion is bumped whenever there's a wire-incompatible change.
|
||||
// - version 1 (zero on wire): consistent box headers, in use by employee dev nodes a bit
|
||||
// - version 2: received packets have src addrs in frameRecvPacket at beginning
|
||||
const ProtocolVersion = 2
|
||||
|
||||
// frameType is the one byte frame type at the beginning of the frame
|
||||
// header. The second field is a big-endian uint32 describing the
|
||||
// length of the remaining frame (not including the initial 5 bytes).
|
||||
type frameType byte
|
||||
|
||||
/*
|
||||
Protocol flow:
|
||||
|
||||
Login:
|
||||
* client connects
|
||||
* server sends frameServerKey
|
||||
* client sends frameClientInfo
|
||||
* server sends frameServerInfo
|
||||
|
||||
Steady state:
|
||||
* server occasionally sends frameKeepAlive (or framePing)
|
||||
* client responds to any framePing with a framePong
|
||||
* client sends frameSendPacket
|
||||
* server then sends frameRecvPacket to recipient
|
||||
*/
|
||||
const (
|
||||
frameServerKey = frameType(0x01) // 8B magic + 32B public key + (0+ bytes future use)
|
||||
frameClientInfo = frameType(0x02) // 32B pub key + 24B nonce + naclbox(json)
|
||||
frameServerInfo = frameType(0x03) // 24B nonce + naclbox(json)
|
||||
frameSendPacket = frameType(0x04) // 32B dest pub key + packet bytes
|
||||
frameForwardPacket = frameType(0x0a) // 32B src pub key + 32B dst pub key + packet bytes
|
||||
frameRecvPacket = frameType(0x05) // v0/1: packet bytes, v2: 32B src pub key + packet bytes
|
||||
frameKeepAlive = frameType(0x06) // no payload, no-op (to be replaced with ping/pong)
|
||||
frameNotePreferred = frameType(0x07) // 1 byte payload: 0x01 or 0x00 for whether this is client's home node
|
||||
|
||||
// framePeerGone is sent from server to client to signal that
|
||||
// a previous sender is no longer connected. That is, if A
|
||||
// sent to B, and then if A disconnects, the server sends
|
||||
// framePeerGone to B so B can forget that a reverse path
|
||||
// exists on that connection to get back to A. It is also sent
|
||||
// if A tries to send a CallMeMaybe to B and the server has no
|
||||
// record of B (which currently would only happen if there was
|
||||
// a bug).
|
||||
framePeerGone = frameType(0x08) // 32B pub key of peer that's gone + 1 byte reason
|
||||
|
||||
// framePeerPresent is like framePeerGone, but for other members of the DERP
|
||||
// region when they're meshed up together.
|
||||
//
|
||||
// The message is at least 32 bytes (the public key of the peer that's
|
||||
// connected). If there are at least 18 bytes remaining after that, it's the
|
||||
// 16 byte IP + 2 byte BE uint16 port of the client. If there's another byte
|
||||
// remaining after that, it's a PeerPresentFlags byte.
|
||||
// While current servers send 41 bytes, old servers will send fewer, and newer
|
||||
// servers might send more.
|
||||
framePeerPresent = frameType(0x09)
|
||||
|
||||
// frameWatchConns is how one DERP node in a regional mesh
|
||||
// subscribes to the others in the region.
|
||||
// There's no payload. If the sender doesn't have permission, the connection
|
||||
// is closed. Otherwise, the client is initially flooded with
|
||||
// framePeerPresent for all connected nodes, and then a stream of
|
||||
// framePeerPresent & framePeerGone has peers connect and disconnect.
|
||||
frameWatchConns = frameType(0x10)
|
||||
|
||||
// frameClosePeer is a privileged frame type (requires the
|
||||
// mesh key for now) that closes the provided peer's
|
||||
// connection. (To be used for cluster load balancing
|
||||
// purposes, when clients end up on a non-ideal node)
|
||||
frameClosePeer = frameType(0x11) // 32B pub key of peer to close.
|
||||
|
||||
framePing = frameType(0x12) // 8 byte ping payload, to be echoed back in framePong
|
||||
framePong = frameType(0x13) // 8 byte payload, the contents of the ping being replied to
|
||||
|
||||
// frameHealth is sent from server to client to tell the client
|
||||
// if their connection is unhealthy somehow. Currently the only unhealthy state
|
||||
// is whether the connection is detected as a duplicate.
|
||||
// The entire frame body is the text of the error message. An empty message
|
||||
// clears the error state.
|
||||
frameHealth = frameType(0x14)
|
||||
|
||||
// frameRestarting is sent from server to client for the
|
||||
// server to declare that it's restarting. Payload is two big
|
||||
// endian uint32 durations in milliseconds: when to reconnect,
|
||||
// and how long to try total. See ServerRestartingMessage docs for
|
||||
// more details on how the client should interpret them.
|
||||
frameRestarting = frameType(0x15)
|
||||
)
|
||||
|
||||
// PeerGoneReasonType is a one byte reason code explaining why a
|
||||
// server does not have a path to the requested destination.
|
||||
type PeerGoneReasonType byte
|
||||
|
||||
const (
|
||||
PeerGoneReasonDisconnected = PeerGoneReasonType(0x00) // peer disconnected from this server
|
||||
PeerGoneReasonNotHere = PeerGoneReasonType(0x01) // server doesn't know about this peer, unexpected
|
||||
PeerGoneReasonMeshConnBroke = PeerGoneReasonType(0xf0) // invented by Client.RunWatchConnectionLoop on disconnect; not sent on the wire
|
||||
)
|
||||
|
||||
// PeerPresentFlags is an optional byte of bit flags sent after a framePeerPresent message.
|
||||
//
|
||||
// For a modern server, the value should always be non-zero. If the value is zero,
|
||||
// that means the server doesn't support this field.
|
||||
type PeerPresentFlags byte
|
||||
|
||||
// PeerPresentFlags bits.
|
||||
const (
|
||||
PeerPresentIsRegular = 1 << 0
|
||||
PeerPresentIsMeshPeer = 1 << 1
|
||||
PeerPresentIsProber = 1 << 2
|
||||
)
|
||||
|
||||
var bin = binary.BigEndian
|
||||
|
||||
func writeUint32(bw *bufio.Writer, v uint32) error {
|
||||
var b [4]byte
|
||||
bin.PutUint32(b[:], v)
|
||||
// Writing a byte at a time is a bit silly,
|
||||
// but it causes b not to escape,
|
||||
// which more than pays for the silliness.
|
||||
for _, c := range &b {
|
||||
err := bw.WriteByte(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readUint32(br *bufio.Reader) (uint32, error) {
|
||||
var b [4]byte
|
||||
// Reading a byte at a time is a bit silly,
|
||||
// but it causes b not to escape,
|
||||
// which more than pays for the silliness.
|
||||
for i := range &b {
|
||||
c, err := br.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
b[i] = c
|
||||
}
|
||||
return bin.Uint32(b[:]), nil
|
||||
}
|
||||
|
||||
func readFrameTypeHeader(br *bufio.Reader, wantType frameType) (frameLen uint32, err error) {
|
||||
gotType, frameLen, err := readFrameHeader(br)
|
||||
if err == nil && wantType != gotType {
|
||||
err = fmt.Errorf("bad frame type 0x%X, want 0x%X", gotType, wantType)
|
||||
}
|
||||
return frameLen, err
|
||||
}
|
||||
|
||||
func readFrameHeader(br *bufio.Reader) (t frameType, frameLen uint32, err error) {
|
||||
tb, err := br.ReadByte()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
frameLen, err = readUint32(br)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return frameType(tb), frameLen, nil
|
||||
}
|
||||
|
||||
// readFrame reads a frame header and then reads its payload into
|
||||
// b[:frameLen].
|
||||
//
|
||||
// If the frame header length is greater than maxSize, readFrame returns
|
||||
// an error after reading the frame header.
|
||||
//
|
||||
// If the frame is less than maxSize but greater than len(b), len(b)
|
||||
// bytes are read, err will be io.ErrShortBuffer, and frameLen and t
|
||||
// will both be set. That is, callers need to explicitly handle when
|
||||
// they get more data than expected.
|
||||
func readFrame(br *bufio.Reader, maxSize uint32, b []byte) (t frameType, frameLen uint32, err error) {
|
||||
t, frameLen, err = readFrameHeader(br)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if frameLen > maxSize {
|
||||
return 0, 0, fmt.Errorf("frame header size %d exceeds reader limit of %d", frameLen, maxSize)
|
||||
}
|
||||
|
||||
n, err := io.ReadFull(br, b[:min(frameLen, uint32(len(b)))])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
remain := frameLen - uint32(n)
|
||||
if remain > 0 {
|
||||
if _, err := io.CopyN(io.Discard, br, int64(remain)); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
err = io.ErrShortBuffer
|
||||
}
|
||||
return t, frameLen, err
|
||||
}
|
||||
|
||||
func writeFrameHeader(bw *bufio.Writer, t frameType, frameLen uint32) error {
|
||||
if err := bw.WriteByte(byte(t)); err != nil {
|
||||
return err
|
||||
}
|
||||
return writeUint32(bw, frameLen)
|
||||
}
|
||||
|
||||
// writeFrame writes a complete frame & flushes it.
|
||||
func writeFrame(bw *bufio.Writer, t frameType, b []byte) error {
|
||||
if len(b) > 10<<20 {
|
||||
return errors.New("unreasonably large frame write")
|
||||
}
|
||||
if err := writeFrameHeader(bw, t, uint32(len(b))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := bw.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return bw.Flush()
|
||||
}
|
||||
673
vendor/tailscale.com/derp/derp_client.go
generated
vendored
Normal file
673
vendor/tailscale.com/derp/derp_client.go
generated
vendored
Normal file
@@ -0,0 +1,673 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package derp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go4.org/mem"
|
||||
"golang.org/x/time/rate"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tstime"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
// Client is a DERP client.
|
||||
type Client struct {
|
||||
serverKey key.NodePublic // of the DERP server; not a machine or node key
|
||||
privateKey key.NodePrivate
|
||||
publicKey key.NodePublic // of privateKey
|
||||
logf logger.Logf
|
||||
nc Conn
|
||||
br *bufio.Reader
|
||||
meshKey string
|
||||
canAckPings bool
|
||||
isProber bool
|
||||
|
||||
wmu sync.Mutex // hold while writing to bw
|
||||
bw *bufio.Writer
|
||||
rate *rate.Limiter // if non-nil, rate limiter to use
|
||||
|
||||
// Owned by Recv:
|
||||
peeked int // bytes to discard on next Recv
|
||||
readErr syncs.AtomicValue[error] // sticky (set by Recv)
|
||||
|
||||
clock tstime.Clock
|
||||
}
|
||||
|
||||
// ClientOpt is an option passed to NewClient.
|
||||
type ClientOpt interface {
|
||||
update(*clientOpt)
|
||||
}
|
||||
|
||||
type clientOptFunc func(*clientOpt)
|
||||
|
||||
func (f clientOptFunc) update(o *clientOpt) { f(o) }
|
||||
|
||||
// clientOpt are the options passed to newClient.
|
||||
type clientOpt struct {
|
||||
MeshKey string
|
||||
ServerPub key.NodePublic
|
||||
CanAckPings bool
|
||||
IsProber bool
|
||||
}
|
||||
|
||||
// MeshKey returns a ClientOpt to pass to the DERP server during connect to get
|
||||
// access to join the mesh.
|
||||
//
|
||||
// An empty key means to not use a mesh key.
|
||||
func MeshKey(key string) ClientOpt { return clientOptFunc(func(o *clientOpt) { o.MeshKey = key }) }
|
||||
|
||||
// IsProber returns a ClientOpt to pass to the DERP server during connect to
|
||||
// declare that this client is a a prober.
|
||||
func IsProber(v bool) ClientOpt { return clientOptFunc(func(o *clientOpt) { o.IsProber = v }) }
|
||||
|
||||
// ServerPublicKey returns a ClientOpt to declare that the server's DERP public key is known.
|
||||
// If key is the zero value, the returned ClientOpt is a no-op.
|
||||
func ServerPublicKey(key key.NodePublic) ClientOpt {
|
||||
return clientOptFunc(func(o *clientOpt) { o.ServerPub = key })
|
||||
}
|
||||
|
||||
// CanAckPings returns a ClientOpt to set whether it advertises to the
|
||||
// server that it's capable of acknowledging ping requests.
|
||||
func CanAckPings(v bool) ClientOpt {
|
||||
return clientOptFunc(func(o *clientOpt) { o.CanAckPings = v })
|
||||
}
|
||||
|
||||
func NewClient(privateKey key.NodePrivate, nc Conn, brw *bufio.ReadWriter, logf logger.Logf, opts ...ClientOpt) (*Client, error) {
|
||||
var opt clientOpt
|
||||
for _, o := range opts {
|
||||
if o == nil {
|
||||
return nil, errors.New("nil ClientOpt")
|
||||
}
|
||||
o.update(&opt)
|
||||
}
|
||||
return newClient(privateKey, nc, brw, logf, opt)
|
||||
}
|
||||
|
||||
func newClient(privateKey key.NodePrivate, nc Conn, brw *bufio.ReadWriter, logf logger.Logf, opt clientOpt) (*Client, error) {
|
||||
c := &Client{
|
||||
privateKey: privateKey,
|
||||
publicKey: privateKey.Public(),
|
||||
logf: logf,
|
||||
nc: nc,
|
||||
br: brw.Reader,
|
||||
bw: brw.Writer,
|
||||
meshKey: opt.MeshKey,
|
||||
canAckPings: opt.CanAckPings,
|
||||
isProber: opt.IsProber,
|
||||
clock: tstime.StdClock{},
|
||||
}
|
||||
if opt.ServerPub.IsZero() {
|
||||
if err := c.recvServerKey(); err != nil {
|
||||
return nil, fmt.Errorf("derp.Client: failed to receive server key: %v", err)
|
||||
}
|
||||
} else {
|
||||
c.serverKey = opt.ServerPub
|
||||
}
|
||||
if err := c.sendClientKey(); err != nil {
|
||||
return nil, fmt.Errorf("derp.Client: failed to send client key: %v", err)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Client) PublicKey() key.NodePublic { return c.publicKey }
|
||||
|
||||
func (c *Client) recvServerKey() error {
|
||||
var buf [40]byte
|
||||
t, flen, err := readFrame(c.br, 1<<10, buf[:])
|
||||
if err == io.ErrShortBuffer {
|
||||
// For future-proofing, allow server to send more in its greeting.
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if flen < uint32(len(buf)) || t != frameServerKey || string(buf[:len(magic)]) != magic {
|
||||
return errors.New("invalid server greeting")
|
||||
}
|
||||
c.serverKey = key.NodePublicFromRaw32(mem.B(buf[len(magic):]))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) parseServerInfo(b []byte) (*serverInfo, error) {
|
||||
const maxLength = nonceLen + maxInfoLen
|
||||
fl := len(b)
|
||||
if fl < nonceLen {
|
||||
return nil, fmt.Errorf("short serverInfo frame")
|
||||
}
|
||||
if fl > maxLength {
|
||||
return nil, fmt.Errorf("long serverInfo frame")
|
||||
}
|
||||
msg, ok := c.privateKey.OpenFrom(c.serverKey, b)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to open naclbox from server key %s", c.serverKey)
|
||||
}
|
||||
info := new(serverInfo)
|
||||
if err := json.Unmarshal(msg, info); err != nil {
|
||||
return nil, fmt.Errorf("invalid JSON: %v", err)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
type clientInfo struct {
|
||||
// MeshKey optionally specifies a pre-shared key used by
|
||||
// trusted clients. It's required to subscribe to the
|
||||
// connection list & forward packets. It's empty for regular
|
||||
// users.
|
||||
MeshKey string `json:"meshKey,omitempty"`
|
||||
|
||||
// Version is the DERP protocol version that the client was built with.
|
||||
// See the ProtocolVersion const.
|
||||
Version int `json:"version,omitempty"`
|
||||
|
||||
// CanAckPings is whether the client declares it's able to ack
|
||||
// pings.
|
||||
CanAckPings bool
|
||||
|
||||
// IsProber is whether this client is a prober.
|
||||
IsProber bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) sendClientKey() error {
|
||||
msg, err := json.Marshal(clientInfo{
|
||||
Version: ProtocolVersion,
|
||||
MeshKey: c.meshKey,
|
||||
CanAckPings: c.canAckPings,
|
||||
IsProber: c.isProber,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgbox := c.privateKey.SealTo(c.serverKey, msg)
|
||||
|
||||
buf := make([]byte, 0, keyLen+len(msgbox))
|
||||
buf = c.publicKey.AppendTo(buf)
|
||||
buf = append(buf, msgbox...)
|
||||
return writeFrame(c.bw, frameClientInfo, buf)
|
||||
}
|
||||
|
||||
// ServerPublicKey returns the server's public key.
|
||||
func (c *Client) ServerPublicKey() key.NodePublic { return c.serverKey }
|
||||
|
||||
// Send sends a packet to the Tailscale node identified by dstKey.
|
||||
//
|
||||
// It is an error if the packet is larger than 64KB.
|
||||
func (c *Client) Send(dstKey key.NodePublic, pkt []byte) error { return c.send(dstKey, pkt) }
|
||||
|
||||
func (c *Client) send(dstKey key.NodePublic, pkt []byte) (ret error) {
|
||||
defer func() {
|
||||
if ret != nil {
|
||||
ret = fmt.Errorf("derp.Send: %w", ret)
|
||||
}
|
||||
}()
|
||||
|
||||
if len(pkt) > MaxPacketSize {
|
||||
return fmt.Errorf("packet too big: %d", len(pkt))
|
||||
}
|
||||
|
||||
c.wmu.Lock()
|
||||
defer c.wmu.Unlock()
|
||||
if c.rate != nil {
|
||||
pktLen := frameHeaderLen + key.NodePublicRawLen + len(pkt)
|
||||
if !c.rate.AllowN(c.clock.Now(), pktLen) {
|
||||
return nil // drop
|
||||
}
|
||||
}
|
||||
if err := writeFrameHeader(c.bw, frameSendPacket, uint32(key.NodePublicRawLen+len(pkt))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.bw.Write(dstKey.AppendTo(nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.bw.Write(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.bw.Flush()
|
||||
}
|
||||
|
||||
func (c *Client) ForwardPacket(srcKey, dstKey key.NodePublic, pkt []byte) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("derp.ForwardPacket: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if len(pkt) > MaxPacketSize {
|
||||
return fmt.Errorf("packet too big: %d", len(pkt))
|
||||
}
|
||||
|
||||
c.wmu.Lock()
|
||||
defer c.wmu.Unlock()
|
||||
|
||||
timer := c.clock.AfterFunc(5*time.Second, c.writeTimeoutFired)
|
||||
defer timer.Stop()
|
||||
|
||||
if err := writeFrameHeader(c.bw, frameForwardPacket, uint32(keyLen*2+len(pkt))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.bw.Write(srcKey.AppendTo(nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.bw.Write(dstKey.AppendTo(nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.bw.Write(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.bw.Flush()
|
||||
}
|
||||
|
||||
func (c *Client) writeTimeoutFired() { c.nc.Close() }
|
||||
|
||||
func (c *Client) SendPing(data [8]byte) error {
|
||||
return c.sendPingOrPong(framePing, data)
|
||||
}
|
||||
|
||||
func (c *Client) SendPong(data [8]byte) error {
|
||||
return c.sendPingOrPong(framePong, data)
|
||||
}
|
||||
|
||||
func (c *Client) sendPingOrPong(typ frameType, data [8]byte) error {
|
||||
c.wmu.Lock()
|
||||
defer c.wmu.Unlock()
|
||||
if err := writeFrameHeader(c.bw, typ, 8); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.bw.Write(data[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.bw.Flush()
|
||||
}
|
||||
|
||||
// NotePreferred sends a packet that tells the server whether this
|
||||
// client is the user's preferred server. This is only used in the
|
||||
// server for stats.
|
||||
func (c *Client) NotePreferred(preferred bool) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("derp.NotePreferred: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
c.wmu.Lock()
|
||||
defer c.wmu.Unlock()
|
||||
|
||||
if err := writeFrameHeader(c.bw, frameNotePreferred, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
var b byte = 0x00
|
||||
if preferred {
|
||||
b = 0x01
|
||||
}
|
||||
if err := c.bw.WriteByte(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.bw.Flush()
|
||||
}
|
||||
|
||||
// WatchConnectionChanges sends a request to subscribe to the peer's connection list.
|
||||
// It's a fatal error if the client wasn't created using MeshKey.
|
||||
func (c *Client) WatchConnectionChanges() error {
|
||||
c.wmu.Lock()
|
||||
defer c.wmu.Unlock()
|
||||
if err := writeFrameHeader(c.bw, frameWatchConns, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.bw.Flush()
|
||||
}
|
||||
|
||||
// ClosePeer asks the server to close target's TCP connection.
|
||||
// It's a fatal error if the client wasn't created using MeshKey.
|
||||
func (c *Client) ClosePeer(target key.NodePublic) error {
|
||||
c.wmu.Lock()
|
||||
defer c.wmu.Unlock()
|
||||
return writeFrame(c.bw, frameClosePeer, target.AppendTo(nil))
|
||||
}
|
||||
|
||||
// ReceivedMessage represents a type returned by Client.Recv. Unless
|
||||
// otherwise documented, the returned message aliases the byte slice
|
||||
// provided to Recv and thus the message is only as good as that
|
||||
// buffer, which is up to the caller.
|
||||
type ReceivedMessage interface {
|
||||
msg()
|
||||
}
|
||||
|
||||
// ReceivedPacket is a ReceivedMessage representing an incoming packet.
|
||||
type ReceivedPacket struct {
|
||||
Source key.NodePublic
|
||||
// Data is the received packet bytes. It aliases the memory
|
||||
// passed to Client.Recv.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (ReceivedPacket) msg() {}
|
||||
|
||||
// PeerGoneMessage is a ReceivedMessage that indicates that the client
|
||||
// identified by the underlying public key is not connected to this
|
||||
// server.
|
||||
//
|
||||
// It has only historically been sent by the server when the client
|
||||
// connection count decremented from 1 to 0 and not from e.g. 2 to 1.
|
||||
// See https://github.com/tailscale/tailscale/issues/13566 for details.
|
||||
type PeerGoneMessage struct {
|
||||
Peer key.NodePublic
|
||||
Reason PeerGoneReasonType
|
||||
}
|
||||
|
||||
func (PeerGoneMessage) msg() {}
|
||||
|
||||
// PeerPresentMessage is a ReceivedMessage that indicates that the client is
|
||||
// connected to the server. (Only used by trusted mesh clients)
|
||||
//
|
||||
// It will be sent to client watchers for every new connection from a client,
|
||||
// even if the client's already connected with that public key.
|
||||
// See https://github.com/tailscale/tailscale/issues/13566 for PeerPresentMessage
|
||||
// and PeerGoneMessage not being 1:1.
|
||||
type PeerPresentMessage struct {
|
||||
// Key is the public key of the client.
|
||||
Key key.NodePublic
|
||||
// IPPort is the remote IP and port of the client.
|
||||
IPPort netip.AddrPort
|
||||
// Flags is a bitmask of info about the client.
|
||||
Flags PeerPresentFlags
|
||||
}
|
||||
|
||||
func (PeerPresentMessage) msg() {}
|
||||
|
||||
// ServerInfoMessage is sent by the server upon first connect.
|
||||
type ServerInfoMessage struct {
|
||||
// TokenBucketBytesPerSecond is how many bytes per second the
|
||||
// server says it will accept, including all framing bytes.
|
||||
//
|
||||
// Zero means unspecified. There might be a limit, but the
|
||||
// client need not try to respect it.
|
||||
TokenBucketBytesPerSecond int
|
||||
|
||||
// TokenBucketBytesBurst is how many bytes the server will
|
||||
// allow to burst, temporarily violating
|
||||
// TokenBucketBytesPerSecond.
|
||||
//
|
||||
// Zero means unspecified. There might be a limit, but the
|
||||
// client need not try to respect it.
|
||||
TokenBucketBytesBurst int
|
||||
}
|
||||
|
||||
func (ServerInfoMessage) msg() {}
|
||||
|
||||
// PingMessage is a request from a client or server to reply to the
|
||||
// other side with a PongMessage with the given payload.
|
||||
type PingMessage [8]byte
|
||||
|
||||
func (PingMessage) msg() {}
|
||||
|
||||
// PongMessage is a reply to a PingMessage from a client or server
|
||||
// with the payload sent previously in a PingMessage.
|
||||
type PongMessage [8]byte
|
||||
|
||||
func (PongMessage) msg() {}
|
||||
|
||||
// KeepAliveMessage is a one-way empty message from server to client, just to
|
||||
// keep the connection alive. It's like a PingMessage, but doesn't solicit
|
||||
// a reply from the client.
|
||||
type KeepAliveMessage struct{}
|
||||
|
||||
func (KeepAliveMessage) msg() {}
|
||||
|
||||
// HealthMessage is a one-way message from server to client, declaring the
|
||||
// connection health state.
|
||||
type HealthMessage struct {
|
||||
// Problem, if non-empty, is a description of why the connection
|
||||
// is unhealthy.
|
||||
//
|
||||
// The empty string means the connection is healthy again.
|
||||
//
|
||||
// The default condition is healthy, so the server doesn't
|
||||
// broadcast a HealthMessage until a problem exists.
|
||||
Problem string
|
||||
}
|
||||
|
||||
func (HealthMessage) msg() {}
|
||||
|
||||
// ServerRestartingMessage is a one-way message from server to client,
|
||||
// advertising that the server is restarting.
|
||||
type ServerRestartingMessage struct {
|
||||
// ReconnectIn is an advisory duration that the client should wait
|
||||
// before attempting to reconnect. It might be zero.
|
||||
// It exists for the server to smear out the reconnects.
|
||||
ReconnectIn time.Duration
|
||||
|
||||
// TryFor is an advisory duration for how long the client
|
||||
// should attempt to reconnect before giving up and proceeding
|
||||
// with its normal connection failure logic. The interval
|
||||
// between retries is undefined for now.
|
||||
// A server should not send a TryFor duration more than a few
|
||||
// seconds.
|
||||
TryFor time.Duration
|
||||
}
|
||||
|
||||
func (ServerRestartingMessage) msg() {}
|
||||
|
||||
// Recv reads a message from the DERP server.
|
||||
//
|
||||
// The returned message may alias memory owned by the Client; it
|
||||
// should only be accessed until the next call to Client.
|
||||
//
|
||||
// Once Recv returns an error, the Client is dead forever.
|
||||
func (c *Client) Recv() (m ReceivedMessage, err error) {
|
||||
return c.recvTimeout(120 * time.Second)
|
||||
}
|
||||
|
||||
func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err error) {
|
||||
readErr := c.readErr.Load()
|
||||
if readErr != nil {
|
||||
return nil, readErr
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("derp.Recv: %w", err)
|
||||
c.readErr.Store(err)
|
||||
}
|
||||
}()
|
||||
for {
|
||||
c.nc.SetReadDeadline(time.Now().Add(timeout))
|
||||
|
||||
// Discard any peeked bytes from a previous Recv call.
|
||||
if c.peeked != 0 {
|
||||
if n, err := c.br.Discard(c.peeked); err != nil || n != c.peeked {
|
||||
// Documented to never fail, but might as well check.
|
||||
return nil, fmt.Errorf("bufio.Reader.Discard(%d bytes): got %v, %v", c.peeked, n, err)
|
||||
}
|
||||
c.peeked = 0
|
||||
}
|
||||
|
||||
t, n, err := readFrameHeader(c.br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n > 1<<20 {
|
||||
return nil, fmt.Errorf("unexpectedly large frame of %d bytes returned", n)
|
||||
}
|
||||
|
||||
var b []byte // frame payload (past the 5 byte header)
|
||||
|
||||
// If the frame fits in our bufio.Reader buffer, just use it.
|
||||
// In practice it's 4KB (from derphttp.Client's bufio.NewReader(httpConn)) and
|
||||
// in practive, WireGuard packets (and thus DERP frames) are under 1.5KB.
|
||||
// So this is the common path.
|
||||
if int(n) <= c.br.Size() {
|
||||
b, err = c.br.Peek(int(n))
|
||||
c.peeked = int(n)
|
||||
} else {
|
||||
// But if for some reason we read a large DERP message (which isn't necessarily
|
||||
// a WireGuard packet), then just allocate memory for it.
|
||||
// TODO(bradfitz): use a pool if large frames ever happen in practice.
|
||||
b = make([]byte, n)
|
||||
_, err = io.ReadFull(c.br, b)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch t {
|
||||
default:
|
||||
continue
|
||||
case frameServerInfo:
|
||||
// Server sends this at start-up. Currently unused.
|
||||
// Just has a JSON message saying "version: 2",
|
||||
// but the protocol seems extensible enough as-is without
|
||||
// needing to wait an RTT to discover the version at startup.
|
||||
// We'd prefer to give the connection to the client (magicsock)
|
||||
// to start writing as soon as possible.
|
||||
si, err := c.parseServerInfo(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid server info frame: %v", err)
|
||||
}
|
||||
sm := ServerInfoMessage{
|
||||
TokenBucketBytesPerSecond: si.TokenBucketBytesPerSecond,
|
||||
TokenBucketBytesBurst: si.TokenBucketBytesBurst,
|
||||
}
|
||||
c.setSendRateLimiter(sm)
|
||||
return sm, nil
|
||||
case frameKeepAlive:
|
||||
// A one-way keep-alive message that doesn't require an acknowledgement.
|
||||
// This predated framePing/framePong.
|
||||
return KeepAliveMessage{}, nil
|
||||
case framePeerGone:
|
||||
if n < keyLen {
|
||||
c.logf("[unexpected] dropping short peerGone frame from DERP server")
|
||||
continue
|
||||
}
|
||||
// Backward compatibility for the older peerGone without reason byte
|
||||
reason := PeerGoneReasonDisconnected
|
||||
if n > keyLen {
|
||||
reason = PeerGoneReasonType(b[keyLen])
|
||||
}
|
||||
pg := PeerGoneMessage{
|
||||
Peer: key.NodePublicFromRaw32(mem.B(b[:keyLen])),
|
||||
Reason: reason,
|
||||
}
|
||||
return pg, nil
|
||||
|
||||
case framePeerPresent:
|
||||
remain := b
|
||||
chunk, remain, ok := cutLeadingN(remain, keyLen)
|
||||
if !ok {
|
||||
c.logf("[unexpected] dropping short peerPresent frame from DERP server")
|
||||
continue
|
||||
}
|
||||
var msg PeerPresentMessage
|
||||
msg.Key = key.NodePublicFromRaw32(mem.B(chunk))
|
||||
|
||||
const ipLen = 16
|
||||
const portLen = 2
|
||||
chunk, remain, ok = cutLeadingN(remain, ipLen+portLen)
|
||||
if !ok {
|
||||
// Older server which didn't send the IP.
|
||||
return msg, nil
|
||||
}
|
||||
msg.IPPort = netip.AddrPortFrom(
|
||||
netip.AddrFrom16([16]byte(chunk[:ipLen])).Unmap(),
|
||||
binary.BigEndian.Uint16(chunk[ipLen:]),
|
||||
)
|
||||
|
||||
chunk, _, ok = cutLeadingN(remain, 1)
|
||||
if !ok {
|
||||
// Older server which doesn't send PeerPresentFlags.
|
||||
return msg, nil
|
||||
}
|
||||
msg.Flags = PeerPresentFlags(chunk[0])
|
||||
return msg, nil
|
||||
|
||||
case frameRecvPacket:
|
||||
var rp ReceivedPacket
|
||||
if n < keyLen {
|
||||
c.logf("[unexpected] dropping short packet from DERP server")
|
||||
continue
|
||||
}
|
||||
rp.Source = key.NodePublicFromRaw32(mem.B(b[:keyLen]))
|
||||
rp.Data = b[keyLen:n]
|
||||
return rp, nil
|
||||
|
||||
case framePing:
|
||||
var pm PingMessage
|
||||
if n < 8 {
|
||||
c.logf("[unexpected] dropping short ping frame")
|
||||
continue
|
||||
}
|
||||
copy(pm[:], b[:])
|
||||
return pm, nil
|
||||
|
||||
case framePong:
|
||||
var pm PongMessage
|
||||
if n < 8 {
|
||||
c.logf("[unexpected] dropping short ping frame")
|
||||
continue
|
||||
}
|
||||
copy(pm[:], b[:])
|
||||
return pm, nil
|
||||
|
||||
case frameHealth:
|
||||
return HealthMessage{Problem: string(b[:])}, nil
|
||||
|
||||
case frameRestarting:
|
||||
var m ServerRestartingMessage
|
||||
if n < 8 {
|
||||
c.logf("[unexpected] dropping short server restarting frame")
|
||||
continue
|
||||
}
|
||||
m.ReconnectIn = time.Duration(binary.BigEndian.Uint32(b[0:4])) * time.Millisecond
|
||||
m.TryFor = time.Duration(binary.BigEndian.Uint32(b[4:8])) * time.Millisecond
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) setSendRateLimiter(sm ServerInfoMessage) {
|
||||
c.wmu.Lock()
|
||||
defer c.wmu.Unlock()
|
||||
|
||||
if sm.TokenBucketBytesPerSecond == 0 {
|
||||
c.rate = nil
|
||||
} else {
|
||||
c.rate = rate.NewLimiter(
|
||||
rate.Limit(sm.TokenBucketBytesPerSecond),
|
||||
sm.TokenBucketBytesBurst)
|
||||
}
|
||||
}
|
||||
|
||||
// LocalAddr returns the TCP connection's local address.
|
||||
//
|
||||
// If the client is broken in some previously detectable way, it
|
||||
// returns an error.
|
||||
func (c *Client) LocalAddr() (netip.AddrPort, error) {
|
||||
readErr, _ := c.readErr.Load().(error)
|
||||
if readErr != nil {
|
||||
return netip.AddrPort{}, readErr
|
||||
}
|
||||
if c.nc == nil {
|
||||
return netip.AddrPort{}, errors.New("nil conn")
|
||||
}
|
||||
a := c.nc.LocalAddr()
|
||||
if a == nil {
|
||||
return netip.AddrPort{}, errors.New("nil addr")
|
||||
}
|
||||
return netip.ParseAddrPort(a.String())
|
||||
}
|
||||
|
||||
func cutLeadingN(b []byte, n int) (chunk, remain []byte, ok bool) {
|
||||
if len(b) >= n {
|
||||
return b[:n], b[n:], true
|
||||
}
|
||||
return nil, b, false
|
||||
}
|
||||
2261
vendor/tailscale.com/derp/derp_server.go
generated
vendored
Normal file
2261
vendor/tailscale.com/derp/derp_server.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
13
vendor/tailscale.com/derp/derp_server_default.go
generated
vendored
Normal file
13
vendor/tailscale.com/derp/derp_server_default.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !linux
|
||||
|
||||
package derp
|
||||
|
||||
import "context"
|
||||
|
||||
func (c *sclient) startStatsLoop(ctx context.Context) {
|
||||
// Nothing to do
|
||||
return
|
||||
}
|
||||
89
vendor/tailscale.com/derp/derp_server_linux.go
generated
vendored
Normal file
89
vendor/tailscale.com/derp/derp_server_linux.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package derp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"tailscale.com/net/tcpinfo"
|
||||
)
|
||||
|
||||
func (c *sclient) startStatsLoop(ctx context.Context) {
|
||||
// Get the RTT initially to verify it's supported.
|
||||
conn := c.tcpConn()
|
||||
if conn == nil {
|
||||
c.s.tcpRtt.Add("non-tcp", 1)
|
||||
return
|
||||
}
|
||||
if _, err := tcpinfo.RTT(conn); err != nil {
|
||||
c.logf("error fetching initial RTT: %v", err)
|
||||
c.s.tcpRtt.Add("error", 1)
|
||||
return
|
||||
}
|
||||
|
||||
const statsInterval = 10 * time.Second
|
||||
|
||||
// Don't launch a goroutine; use a timer instead.
|
||||
var gatherStats func()
|
||||
gatherStats = func() {
|
||||
// Do nothing if the context is finished.
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Reschedule ourselves when this stats gathering is finished.
|
||||
defer c.s.clock.AfterFunc(statsInterval, gatherStats)
|
||||
|
||||
// Gather TCP RTT information.
|
||||
rtt, err := tcpinfo.RTT(conn)
|
||||
if err == nil {
|
||||
c.s.tcpRtt.Add(durationToLabel(rtt), 1)
|
||||
}
|
||||
|
||||
// TODO(andrew): more metrics?
|
||||
}
|
||||
|
||||
// Kick off the initial timer.
|
||||
c.s.clock.AfterFunc(statsInterval, gatherStats)
|
||||
}
|
||||
|
||||
// tcpConn attempts to get the underlying *net.TCPConn from this client's
|
||||
// Conn; if it cannot, then it will return nil.
|
||||
func (c *sclient) tcpConn() *net.TCPConn {
|
||||
nc := c.nc
|
||||
for {
|
||||
switch v := nc.(type) {
|
||||
case *net.TCPConn:
|
||||
return v
|
||||
case *tls.Conn:
|
||||
nc = v.NetConn()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func durationToLabel(dur time.Duration) string {
|
||||
switch {
|
||||
case dur <= 10*time.Millisecond:
|
||||
return "10ms"
|
||||
case dur <= 20*time.Millisecond:
|
||||
return "20ms"
|
||||
case dur <= 50*time.Millisecond:
|
||||
return "50ms"
|
||||
case dur <= 100*time.Millisecond:
|
||||
return "100ms"
|
||||
case dur <= 150*time.Millisecond:
|
||||
return "150ms"
|
||||
case dur <= 250*time.Millisecond:
|
||||
return "250ms"
|
||||
case dur <= 500*time.Millisecond:
|
||||
return "500ms"
|
||||
default:
|
||||
return "inf"
|
||||
}
|
||||
}
|
||||
1143
vendor/tailscale.com/derp/derphttp/derphttp_client.go
generated
vendored
Normal file
1143
vendor/tailscale.com/derp/derphttp/derphttp_client.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
108
vendor/tailscale.com/derp/derphttp/derphttp_server.go
generated
vendored
Normal file
108
vendor/tailscale.com/derp/derphttp/derphttp_server.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package derphttp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"tailscale.com/derp"
|
||||
)
|
||||
|
||||
// fastStartHeader is the header (with value "1") that signals to the HTTP
|
||||
// server that the DERP HTTP client does not want the HTTP 101 response
|
||||
// headers and it will begin writing & reading the DERP protocol immediately
|
||||
// following its HTTP request.
|
||||
const fastStartHeader = "Derp-Fast-Start"
|
||||
|
||||
// Handler returns an http.Handler to be mounted at /derp, serving s.
|
||||
func Handler(s *derp.Server) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// These are installed both here and in cmd/derper. The check here
|
||||
// catches both cmd/derper run with DERP disabled (STUN only mode) as
|
||||
// well as DERP being run in tests with derphttp.Handler directly,
|
||||
// as netcheck still assumes this replies.
|
||||
switch r.URL.Path {
|
||||
case "/derp/probe", "/derp/latency-check":
|
||||
ProbeHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
up := strings.ToLower(r.Header.Get("Upgrade"))
|
||||
if up != "websocket" && up != "derp" {
|
||||
if up != "" {
|
||||
log.Printf("Weird upgrade: %q", up)
|
||||
}
|
||||
http.Error(w, "DERP requires connection upgrade", http.StatusUpgradeRequired)
|
||||
return
|
||||
}
|
||||
|
||||
fastStart := r.Header.Get(fastStartHeader) == "1"
|
||||
|
||||
h, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
http.Error(w, "HTTP does not support general TCP support", 500)
|
||||
return
|
||||
}
|
||||
|
||||
netConn, conn, err := h.Hijack()
|
||||
if err != nil {
|
||||
log.Printf("Hijack failed: %v", err)
|
||||
http.Error(w, "HTTP does not support general TCP support", 500)
|
||||
return
|
||||
}
|
||||
|
||||
if !fastStart {
|
||||
pubKey := s.PublicKey()
|
||||
fmt.Fprintf(conn, "HTTP/1.1 101 Switching Protocols\r\n"+
|
||||
"Upgrade: DERP\r\n"+
|
||||
"Connection: Upgrade\r\n"+
|
||||
"Derp-Version: %v\r\n"+
|
||||
"Derp-Public-Key: %s\r\n\r\n",
|
||||
derp.ProtocolVersion,
|
||||
pubKey.UntypedHexString())
|
||||
}
|
||||
|
||||
s.Accept(r.Context(), netConn, conn, netConn.RemoteAddr().String())
|
||||
})
|
||||
}
|
||||
|
||||
// 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 ProbeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "HEAD", "GET":
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
default:
|
||||
http.Error(w, "bogus probe method", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
// ServeNoContent generates the /generate_204 response used by Tailscale's
|
||||
// captive portal detection.
|
||||
func ServeNoContent(w http.ResponseWriter, r *http.Request) {
|
||||
if challenge := r.Header.Get(NoContentChallengeHeader); challenge != "" {
|
||||
badChar := strings.IndexFunc(challenge, func(r rune) bool {
|
||||
return !isChallengeChar(r)
|
||||
}) != -1
|
||||
if len(challenge) <= 64 && !badChar {
|
||||
w.Header().Set(NoContentResponseHeader, "response "+challenge)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func isChallengeChar(c rune) bool {
|
||||
// Semi-randomly chosen as a limited set of valid characters
|
||||
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') ||
|
||||
('0' <= c && c <= '9') ||
|
||||
c == '.' || c == '-' || c == '_'
|
||||
}
|
||||
|
||||
const (
|
||||
NoContentChallengeHeader = "X-Tailscale-Challenge"
|
||||
NoContentResponseHeader = "X-Tailscale-Response"
|
||||
)
|
||||
177
vendor/tailscale.com/derp/derphttp/mesh_client.go
generated
vendored
Normal file
177
vendor/tailscale.com/derp/derphttp/mesh_client.go
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package derphttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/derp"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
var retryInterval = 5 * time.Second
|
||||
|
||||
// testHookWatchLookConnectResult, if non-nil for tests, is called by RunWatchConnectionLoop
|
||||
// with the connect result. If it returns false, the loop ends.
|
||||
var testHookWatchLookConnectResult func(connectError error, wasSelfConnect bool) (keepRunning bool)
|
||||
|
||||
// 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 (c *Client) RunWatchConnectionLoop(ctx context.Context, ignoreServerKey key.NodePublic, infoLogf logger.Logf, add func(derp.PeerPresentMessage), remove func(derp.PeerGoneMessage)) {
|
||||
if !c.WatchConnectionChanges {
|
||||
if c.isStarted() {
|
||||
panic("invalid use of RunWatchConnectionLoop on already-started Client without setting Client.RunWatchConnectionLoop")
|
||||
}
|
||||
c.WatchConnectionChanges = true
|
||||
}
|
||||
if infoLogf == nil {
|
||||
infoLogf = logger.Discard
|
||||
}
|
||||
logf := c.logf
|
||||
const statusInterval = 10 * time.Second
|
||||
var (
|
||||
mu sync.Mutex
|
||||
present = map[key.NodePublic]bool{}
|
||||
loggedConnected = false
|
||||
)
|
||||
clear := func() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if len(present) == 0 {
|
||||
return
|
||||
}
|
||||
logf("reconnected; clearing %d forwarding mappings", len(present))
|
||||
for k := range present {
|
||||
remove(derp.PeerGoneMessage{Peer: k, Reason: derp.PeerGoneReasonMeshConnBroke})
|
||||
}
|
||||
present = map[key.NodePublic]bool{}
|
||||
}
|
||||
lastConnGen := 0
|
||||
lastStatus := c.clock.Now()
|
||||
logConnectedLocked := func() {
|
||||
if loggedConnected {
|
||||
return
|
||||
}
|
||||
infoLogf("connected; %d peers", len(present))
|
||||
loggedConnected = true
|
||||
}
|
||||
|
||||
const logConnectedDelay = 200 * time.Millisecond
|
||||
timer := c.clock.AfterFunc(2*time.Second, func() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
logConnectedLocked()
|
||||
})
|
||||
defer timer.Stop()
|
||||
|
||||
updatePeer := func(k key.NodePublic, isPresent bool) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if isPresent {
|
||||
present[k] = true
|
||||
if !loggedConnected {
|
||||
timer.Reset(logConnectedDelay)
|
||||
}
|
||||
} else {
|
||||
// If we got a peerGone message, that means the initial connection's
|
||||
// flood of peerPresent messages is done, so we can log already:
|
||||
logConnectedLocked()
|
||||
delete(present, k)
|
||||
}
|
||||
}
|
||||
|
||||
sleep := func(d time.Duration) {
|
||||
t, tChannel := c.clock.NewTimer(d)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Stop()
|
||||
case <-tChannel:
|
||||
}
|
||||
}
|
||||
|
||||
for ctx.Err() == nil {
|
||||
// Make sure we're connected before calling s.ServerPublicKey.
|
||||
_, _, err := c.connect(ctx, "RunWatchConnectionLoop")
|
||||
if err != nil {
|
||||
if f := testHookWatchLookConnectResult; f != nil && !f(err, false) {
|
||||
return
|
||||
}
|
||||
logf("mesh connect: %v", err)
|
||||
sleep(retryInterval)
|
||||
continue
|
||||
}
|
||||
selfConnect := c.ServerPublicKey() == ignoreServerKey
|
||||
if f := testHookWatchLookConnectResult; f != nil && !f(err, selfConnect) {
|
||||
return
|
||||
}
|
||||
if selfConnect {
|
||||
logf("detected self-connect; ignoring host")
|
||||
return
|
||||
}
|
||||
for {
|
||||
m, connGen, err := c.RecvDetail()
|
||||
if err != nil {
|
||||
clear()
|
||||
logf("Recv: %v", err)
|
||||
sleep(retryInterval)
|
||||
break
|
||||
}
|
||||
if connGen != lastConnGen {
|
||||
lastConnGen = connGen
|
||||
clear()
|
||||
}
|
||||
switch m := m.(type) {
|
||||
case derp.PeerPresentMessage:
|
||||
add(m)
|
||||
updatePeer(m.Key, true)
|
||||
case derp.PeerGoneMessage:
|
||||
switch m.Reason {
|
||||
case derp.PeerGoneReasonDisconnected:
|
||||
// Normal case, log nothing
|
||||
case derp.PeerGoneReasonNotHere:
|
||||
logf("Recv: peer %s not connected to %s",
|
||||
key.NodePublic(m.Peer).ShortString(), c.ServerPublicKey().ShortString())
|
||||
default:
|
||||
logf("Recv: peer %s not at server %s for unknown reason %v",
|
||||
key.NodePublic(m.Peer).ShortString(), c.ServerPublicKey().ShortString(), m.Reason)
|
||||
}
|
||||
remove(m)
|
||||
updatePeer(m.Peer, false)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if now := c.clock.Now(); now.Sub(lastStatus) > statusInterval {
|
||||
lastStatus = now
|
||||
infoLogf("%d peers", len(present))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
vendor/tailscale.com/derp/derphttp/websocket.go
generated
vendored
Normal file
32
vendor/tailscale.com/derp/derphttp/websocket.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux || js
|
||||
|
||||
package derphttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"tailscale.com/net/wsconn"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dialWebsocketFunc = dialWebsocket
|
||||
}
|
||||
|
||||
func dialWebsocket(ctx context.Context, urlStr string) (net.Conn, error) {
|
||||
c, res, err := websocket.Dial(ctx, urlStr, &websocket.DialOptions{
|
||||
Subprotocols: []string{"derp"},
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("websocket Dial: %v, %+v", err, res)
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("websocket: connected to %v", urlStr)
|
||||
netConn := wsconn.NetConn(context.Background(), c, websocket.MessageBinary, urlStr)
|
||||
return netConn, nil
|
||||
}
|
||||
33
vendor/tailscale.com/derp/dropreason_string.go
generated
vendored
Normal file
33
vendor/tailscale.com/derp/dropreason_string.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by "stringer -type=dropReason -trimprefix=dropReason"; DO NOT EDIT.
|
||||
|
||||
package derp
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[dropReasonUnknownDest-0]
|
||||
_ = x[dropReasonUnknownDestOnFwd-1]
|
||||
_ = x[dropReasonGoneDisconnected-2]
|
||||
_ = x[dropReasonQueueHead-3]
|
||||
_ = x[dropReasonQueueTail-4]
|
||||
_ = x[dropReasonWriteError-5]
|
||||
_ = x[dropReasonDupClient-6]
|
||||
_ = x[numDropReasons-7]
|
||||
}
|
||||
|
||||
const _dropReason_name = "UnknownDestUnknownDestOnFwdGoneDisconnectedQueueHeadQueueTailWriteErrorDupClientnumDropReasons"
|
||||
|
||||
var _dropReason_index = [...]uint8{0, 11, 27, 43, 52, 61, 71, 80, 94}
|
||||
|
||||
func (i dropReason) String() string {
|
||||
if i < 0 || i >= dropReason(len(_dropReason_index)-1) {
|
||||
return "dropReason(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _dropReason_name[_dropReason_index[i]:_dropReason_index[i+1]]
|
||||
}
|
||||
Reference in New Issue
Block a user