Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+1
-1
@@ -39,7 +39,7 @@ 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
|
||||
the next node in the region. Each node in the region is required 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
|
||||
|
||||
+50
-50
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package derp implements the Designated Encrypted Relay for Packets (DERP)
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
// including its on-wire framing overhead)
|
||||
const MaxPacketSize = 64 << 10
|
||||
|
||||
// Magic is the DERP Magic number, sent in the frameServerKey frame
|
||||
// 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
|
||||
|
||||
@@ -58,15 +58,15 @@ Protocol flow:
|
||||
|
||||
Login:
|
||||
* client connects
|
||||
* server sends frameServerKey
|
||||
* client sends frameClientInfo
|
||||
* server sends frameServerInfo
|
||||
* 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
|
||||
* 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)
|
||||
@@ -78,35 +78,38 @@ const (
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
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
|
||||
// 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.
|
||||
// remaining after that, it's a PeerPresentFlags byte. If there's another
|
||||
// byte remaining after that, it's the length of the client's advertised
|
||||
// app name (see ClientInfo.AppName), followed by that many bytes of app
|
||||
// name.
|
||||
// While current servers send at least 42 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
|
||||
// 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.
|
||||
// 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
|
||||
// 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)
|
||||
@@ -115,14 +118,14 @@ const (
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
@@ -140,7 +143,7 @@ const (
|
||||
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.
|
||||
// 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.
|
||||
@@ -168,6 +171,8 @@ const FastStartHeader = "Derp-Fast-Start"
|
||||
|
||||
var bin = binary.BigEndian
|
||||
|
||||
// writeUint32 writes v to bw one byte at a time
|
||||
// as a big-endian uint32.
|
||||
func writeUint32(bw *bufio.Writer, v uint32) error {
|
||||
var b [4]byte
|
||||
bin.PutUint32(b[:], v)
|
||||
@@ -183,21 +188,6 @@ func writeUint32(bw *bufio.Writer, v uint32) error {
|
||||
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
|
||||
}
|
||||
|
||||
// ReadFrameTypeHeader reads a frame header from br and
|
||||
// verifies that the frame type matches wantType.
|
||||
//
|
||||
@@ -213,18 +203,16 @@ func ReadFrameTypeHeader(br *bufio.Reader, wantType FrameType) (frameLen uint32,
|
||||
return frameLen, err
|
||||
}
|
||||
|
||||
// ReadFrameHeader reads the header of a DERP frame,
|
||||
// reading 5 bytes from br.
|
||||
// ReadFrameHeader reads a DERP frame header ([FrameHeaderLen] bytes) from br.
|
||||
// It uses Peek+Discard to read directly from bufio's internal buffer
|
||||
// without copying or allocating.
|
||||
func ReadFrameHeader(br *bufio.Reader) (t FrameType, frameLen uint32, err error) {
|
||||
tb, err := br.ReadByte()
|
||||
hdr, err := br.Peek(FrameHeaderLen)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
frameLen, err = readUint32(br)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return FrameType(tb), frameLen, nil
|
||||
defer br.Discard(FrameHeaderLen)
|
||||
return FrameType(hdr[0]), bin.Uint32(hdr[1:FrameHeaderLen]), nil
|
||||
}
|
||||
|
||||
// readFrame reads a frame header and then reads its payload into
|
||||
@@ -260,14 +248,26 @@ func readFrame(br *bufio.Reader, maxSize uint32, b []byte) (t FrameType, frameLe
|
||||
return t, frameLen, err
|
||||
}
|
||||
|
||||
// WriteFrameHeader writes a frame header to bw.
|
||||
//
|
||||
// The frame header is 5 bytes: a one byte frame type
|
||||
// followed by a big-endian uint32 length of the
|
||||
// remaining frame (not including the 5 byte header).
|
||||
// WriteFrameHeader writes a DERP frame header to bw: a one-byte frame
|
||||
// type followed by a big-endian uint32 frame length.
|
||||
//
|
||||
// It uses AvailableBuffer to append the header directly into bufio's
|
||||
// internal buffer without allocation, falling back to WriteByte when
|
||||
// the buffer has insufficient space.
|
||||
// It does not flush bw.
|
||||
func WriteFrameHeader(bw *bufio.Writer, t FrameType, frameLen uint32) error {
|
||||
// Fast path: enough space in the buffer to append the header
|
||||
// directly without allocation via AvailableBuffer.
|
||||
if bw.Available() >= FrameHeaderLen {
|
||||
buf := bw.AvailableBuffer()
|
||||
buf = append(buf, byte(t))
|
||||
buf = bin.AppendUint32(buf, frameLen)
|
||||
_, err := bw.Write(buf)
|
||||
return err
|
||||
}
|
||||
// Slow path: buffer nearly full. Write byte-at-a-time to let
|
||||
// bufio flush as needed, avoiding a heap allocation from append
|
||||
// growing past AvailableBuffer's capacity.
|
||||
if err := bw.WriteByte(byte(t)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+71
-11
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package derp
|
||||
@@ -33,6 +33,7 @@ type Client struct {
|
||||
meshKey key.DERPMesh
|
||||
canAckPings bool
|
||||
isProber bool
|
||||
appName string
|
||||
|
||||
wmu sync.Mutex // hold while writing to bw
|
||||
bw *bufio.Writer
|
||||
@@ -60,6 +61,7 @@ type clientOpt struct {
|
||||
ServerPub key.NodePublic
|
||||
CanAckPings bool
|
||||
IsProber bool
|
||||
AppName string
|
||||
}
|
||||
|
||||
// MeshKey returns a ClientOpt to pass to the DERP server during connect to get
|
||||
@@ -69,7 +71,7 @@ type clientOpt struct {
|
||||
func MeshKey(k key.DERPMesh) ClientOpt { return clientOptFunc(func(o *clientOpt) { o.MeshKey = k }) }
|
||||
|
||||
// IsProber returns a ClientOpt to pass to the DERP server during connect to
|
||||
// declare that this client is a a prober.
|
||||
// declare that this client is 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.
|
||||
@@ -84,6 +86,31 @@ func CanAckPings(v bool) ClientOpt {
|
||||
return clientOptFunc(func(o *clientOpt) { o.CanAckPings = v })
|
||||
}
|
||||
|
||||
// AppName returns a ClientOpt to set an opaque app name string to
|
||||
// advertise to the DERP server for stats purposes. It is sent to the
|
||||
// server in the ClientInfo. The name must be valid per [ValidAppName]
|
||||
// or NewClient returns an error.
|
||||
func AppName(name string) ClientOpt {
|
||||
return clientOptFunc(func(o *clientOpt) { o.AppName = name })
|
||||
}
|
||||
|
||||
// MaxAppNameLen is the maximum length in bytes of a [ClientInfo.AppName].
|
||||
const MaxAppNameLen = 32
|
||||
|
||||
// ValidAppName reports whether name is a valid app name: at most
|
||||
// [MaxAppNameLen] bytes of printable ASCII. The empty string is valid.
|
||||
func ValidAppName(name string) bool {
|
||||
if len(name) > MaxAppNameLen {
|
||||
return false
|
||||
}
|
||||
for i := range len(name) {
|
||||
if b := name[i]; b < ' ' || b > '~' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func NewClient(privateKey key.NodePrivate, nc Conn, brw *bufio.ReadWriter, logf logger.Logf, opts ...ClientOpt) (*Client, error) {
|
||||
var opt clientOpt
|
||||
for _, o := range opts {
|
||||
@@ -92,6 +119,9 @@ func NewClient(privateKey key.NodePrivate, nc Conn, brw *bufio.ReadWriter, logf
|
||||
}
|
||||
o.update(&opt)
|
||||
}
|
||||
if !ValidAppName(opt.AppName) {
|
||||
return nil, fmt.Errorf("invalid AppName %.40q", opt.AppName)
|
||||
}
|
||||
return newClient(privateKey, nc, brw, logf, opt)
|
||||
}
|
||||
|
||||
@@ -106,6 +136,7 @@ func newClient(privateKey key.NodePrivate, nc Conn, brw *bufio.ReadWriter, logf
|
||||
meshKey: opt.MeshKey,
|
||||
canAckPings: opt.CanAckPings,
|
||||
isProber: opt.IsProber,
|
||||
appName: opt.AppName,
|
||||
clock: tstime.StdClock{},
|
||||
}
|
||||
if opt.ServerPub.IsZero() {
|
||||
@@ -167,7 +198,7 @@ type ClientInfo struct {
|
||||
// trusted clients. It's required to subscribe to the
|
||||
// connection list & forward packets. It's empty for regular
|
||||
// users.
|
||||
MeshKey key.DERPMesh `json:"meshKey,omitempty,omitzero"`
|
||||
MeshKey key.DERPMesh `json:"meshKey,omitzero"`
|
||||
|
||||
// Version is the DERP protocol version that the client was built with.
|
||||
// See the ProtocolVersion const.
|
||||
@@ -179,6 +210,12 @@ type ClientInfo struct {
|
||||
|
||||
// IsProber is whether this client is a prober.
|
||||
IsProber bool `json:",omitempty"`
|
||||
|
||||
// AppName is an optional opaque app name string the client
|
||||
// advertises to the server for stats purposes. It must be
|
||||
// valid per [ValidAppName] or the server rejects the
|
||||
// connection.
|
||||
AppName string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Equal reports if two clientInfo values are equal.
|
||||
@@ -186,7 +223,7 @@ func (c *ClientInfo) Equal(other *ClientInfo) bool {
|
||||
if c == nil || other == nil {
|
||||
return c == other
|
||||
}
|
||||
if c.Version != other.Version || c.CanAckPings != other.CanAckPings || c.IsProber != other.IsProber {
|
||||
if c.Version != other.Version || c.CanAckPings != other.CanAckPings || c.IsProber != other.IsProber || c.AppName != other.AppName {
|
||||
return false
|
||||
}
|
||||
return c.MeshKey.Equal(other.MeshKey)
|
||||
@@ -198,6 +235,7 @@ func (c *Client) sendClientKey() error {
|
||||
MeshKey: c.meshKey,
|
||||
CanAckPings: c.canAckPings,
|
||||
IsProber: c.isProber,
|
||||
AppName: c.appName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -249,15 +287,18 @@ func (c *Client) send(dstKey key.NodePublic, pkt []byte) (ret error) {
|
||||
return c.bw.Flush()
|
||||
}
|
||||
|
||||
func (c *Client) ForwardPacket(srcKey, dstKey key.NodePublic, pkt []byte) (err error) {
|
||||
// ForwardPacket sends a forward packet frame carrying pkt from srcKey
|
||||
// to dstKey. It writes and flushes pkt before returning and does not
|
||||
// retain it.
|
||||
func (c *Client) ForwardPacket(srcKey, dstKey key.NodePublic, pkt LoanedBytes) (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))
|
||||
if pkt.Len() > MaxPacketSize {
|
||||
return fmt.Errorf("packet too big: %d", pkt.Len())
|
||||
}
|
||||
|
||||
c.wmu.Lock()
|
||||
@@ -266,7 +307,7 @@ func (c *Client) ForwardPacket(srcKey, dstKey key.NodePublic, pkt []byte) (err e
|
||||
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 {
|
||||
if err := WriteFrameHeader(c.bw, FrameForwardPacket, uint32(KeyLen*2+pkt.Len())); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.bw.Write(srcKey.AppendTo(nil)); err != nil {
|
||||
@@ -275,7 +316,7 @@ func (c *Client) ForwardPacket(srcKey, dstKey key.NodePublic, pkt []byte) (err e
|
||||
if _, err := c.bw.Write(dstKey.AppendTo(nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.bw.Write(pkt); err != nil {
|
||||
if _, err := pkt.WriteTo(c.bw); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.bw.Flush()
|
||||
@@ -394,6 +435,10 @@ type PeerPresentMessage struct {
|
||||
IPPort netip.AddrPort
|
||||
// Flags is a bitmask of info about the client.
|
||||
Flags PeerPresentFlags
|
||||
// AppName is the optional app name the client advertised in
|
||||
// its ClientInfo, if any. It's empty if the client didn't
|
||||
// send one or the server is too old to relay it.
|
||||
AppName string
|
||||
}
|
||||
|
||||
func (PeerPresentMessage) msg() {}
|
||||
@@ -554,7 +599,7 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro
|
||||
return sm, nil
|
||||
case FrameKeepAlive:
|
||||
// A one-way keep-alive message that doesn't require an acknowledgement.
|
||||
// This predated framePing/framePong.
|
||||
// This predated FramePing/FramePong.
|
||||
return KeepAliveMessage{}, nil
|
||||
case FramePeerGone:
|
||||
if n < KeyLen {
|
||||
@@ -594,12 +639,27 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro
|
||||
binary.BigEndian.Uint16(chunk[ipLen:]),
|
||||
)
|
||||
|
||||
chunk, _, ok = cutLeadingN(remain, 1)
|
||||
chunk, remain, ok = cutLeadingN(remain, 1)
|
||||
if !ok {
|
||||
// Older server which doesn't send PeerPresentFlags.
|
||||
return msg, nil
|
||||
}
|
||||
msg.Flags = PeerPresentFlags(chunk[0])
|
||||
|
||||
chunk, remain, ok = cutLeadingN(remain, 1)
|
||||
if !ok {
|
||||
// Older server which doesn't send the app name.
|
||||
return msg, nil
|
||||
}
|
||||
nameLen := int(chunk[0])
|
||||
chunk, _, ok = cutLeadingN(remain, nameLen)
|
||||
if !ok {
|
||||
c.logf("[unexpected] short peerPresent app name from DERP server")
|
||||
return msg, nil
|
||||
}
|
||||
if name := string(chunk); ValidAppName(name) {
|
||||
msg.AppName = name
|
||||
}
|
||||
return msg, nil
|
||||
|
||||
case FrameRecvPacket:
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package derpconst contains constants used by the DERP client and server.
|
||||
|
||||
+35
-5
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package derphttp implements DERP-over-HTTP.
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"time"
|
||||
|
||||
"go4.org/mem"
|
||||
"golang.org/x/net/http/httpguts"
|
||||
"tailscale.com/derp"
|
||||
"tailscale.com/derp/derpconst"
|
||||
"tailscale.com/envknob"
|
||||
@@ -60,6 +61,7 @@ type Client struct {
|
||||
DNSCache *dnscache.Resolver // optional; nil means no caching
|
||||
MeshKey key.DERPMesh // optional; for trusted clients
|
||||
IsProber bool // optional; for probers to optional declare themselves as such
|
||||
AppName string // optional; opaque app name to advertise to the server for stats
|
||||
|
||||
// WatchConnectionChanges is whether the client wishes to subscribe to
|
||||
// notifications about clients connecting & disconnecting.
|
||||
@@ -179,7 +181,7 @@ func NewClient(privateKey key.NodePrivate, serverURL string, logf logger.Logf, n
|
||||
|
||||
// isStarted reports whether this client has been used yet.
|
||||
//
|
||||
// If if reports false, it may still have its exported fields configured.
|
||||
// If it reports false, it may still have its exported fields configured.
|
||||
func (c *Client) isStarted() bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
@@ -280,10 +282,16 @@ func (c *Client) urlString(node *tailcfg.DERPNode) string {
|
||||
return c.url.String()
|
||||
}
|
||||
proto := "https"
|
||||
defaultPort := 443
|
||||
if debugUseDERPHTTP() {
|
||||
proto = "http"
|
||||
defaultPort = 80
|
||||
}
|
||||
return fmt.Sprintf("%s://%s/derp", proto, node.HostName)
|
||||
host := node.HostName
|
||||
if node.DERPPort != 0 && node.DERPPort != defaultPort {
|
||||
host = net.JoinHostPort(host, fmt.Sprint(node.DERPPort))
|
||||
}
|
||||
return fmt.Sprintf("%s://%s/derp", proto, host)
|
||||
}
|
||||
|
||||
// AddressFamilySelector decides whether IPv6 is preferred for
|
||||
@@ -407,6 +415,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
|
||||
derp.MeshKey(c.MeshKey),
|
||||
derp.CanAckPings(c.canAckPings),
|
||||
derp.IsProber(c.IsProber),
|
||||
derp.AppName(c.AppName),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
@@ -552,6 +561,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
|
||||
derp.ServerPublicKey(serverPub),
|
||||
derp.CanAckPings(c.canAckPings),
|
||||
derp.IsProber(c.IsProber),
|
||||
derp.AppName(c.AppName),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
@@ -836,6 +846,16 @@ func firstStr(a, b string) string {
|
||||
|
||||
// dialNodeUsingProxy connects to n using a CONNECT to the HTTP(s) proxy in proxyURL.
|
||||
func (c *Client) dialNodeUsingProxy(ctx context.Context, n *tailcfg.DERPNode, proxyURL *url.URL) (_ net.Conn, err error) {
|
||||
// n.HostName comes from the control-supplied DERP map and is written
|
||||
// verbatim into the CONNECT request line and Host header below. Reject
|
||||
// anything that isn't a valid host value so a hostname carrying CR/LF (or
|
||||
// other control bytes) can't inject extra headers or a second request into
|
||||
// the proxy connection. ValidHostHeader accepts the empty string, so check
|
||||
// for that separately.
|
||||
if n.HostName == "" || !httpguts.ValidHostHeader(n.HostName) {
|
||||
return nil, fmt.Errorf("derphttp: invalid DERP node hostname %q", n.HostName)
|
||||
}
|
||||
|
||||
pu := proxyURL
|
||||
var proxyConn net.Conn
|
||||
if pu.Scheme == "https" {
|
||||
@@ -867,7 +887,15 @@ func (c *Client) dialNodeUsingProxy(ctx context.Context, n *tailcfg.DERPNode, pr
|
||||
}
|
||||
}()
|
||||
|
||||
target := net.JoinHostPort(n.HostName, "443")
|
||||
// Keep port selection in sync with dialNode.
|
||||
port := "443"
|
||||
if !c.useHTTPS() {
|
||||
port = "3340"
|
||||
}
|
||||
if n.DERPPort != 0 {
|
||||
port = fmt.Sprint(n.DERPPort)
|
||||
}
|
||||
target := net.JoinHostPort(n.HostName, port)
|
||||
|
||||
var authHeader string
|
||||
if buildfeatures.HasUseProxy {
|
||||
@@ -1002,7 +1030,9 @@ func (c *Client) LocalAddr() (netip.AddrPort, error) {
|
||||
return la, nil
|
||||
}
|
||||
|
||||
func (c *Client) ForwardPacket(from, to key.NodePublic, b []byte) error {
|
||||
// ForwardPacket forwards b from the node from to the node to over the
|
||||
// mesh connection. It does not retain b after it returns.
|
||||
func (c *Client) ForwardPacket(from, to key.NodePublic, b derp.LoanedBytes) error {
|
||||
client, _, err := c.connect(c.newContext(), "derphttp.Client.ForwardPacket")
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package derphttp
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build js || ((linux || darwin) && ts_debug_websockets)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !(js || ((linux || darwin) && ts_debug_websockets))
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package derp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// LoanedBytes is a packet payload lent to a callee for the duration of
|
||||
// a call. The lender reuses the memory once the call returns, so the
|
||||
// callee must not retain it; Clone gives it a copy to keep.
|
||||
//
|
||||
// It deliberately has no accessor for the underlying slice.
|
||||
type LoanedBytes struct {
|
||||
bs []byte
|
||||
}
|
||||
|
||||
// LoanBytes lends b for the duration of the call it's passed to.
|
||||
func LoanBytes(b []byte) LoanedBytes { return LoanedBytes{bs: b} }
|
||||
|
||||
// Len returns the number of bytes.
|
||||
func (b LoanedBytes) Len() int { return len(b.bs) }
|
||||
|
||||
// WriteTo writes the bytes to w.
|
||||
func (b LoanedBytes) WriteTo(w io.Writer) (int64, error) {
|
||||
n, err := w.Write(b.bs)
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// Clone returns a copy of the bytes that the caller owns.
|
||||
func (b LoanedBytes) Clone() []byte { return bytes.Clone(b.bs) }
|
||||
Reference in new issue
Block a user