This commit is contained in:
2026-02-19 10:07:43 +00:00
parent 007438e372
commit 6e637ecf77
1763 changed files with 60820 additions and 279516 deletions

388
vendor/tailscale.com/disco/disco.go generated vendored
View File

@@ -25,6 +25,7 @@ import (
"fmt"
"net"
"net/netip"
"time"
"go4.org/mem"
"tailscale.com/types/key"
@@ -41,9 +42,15 @@ const NonceLen = 24
type MessageType byte
const (
TypePing = MessageType(0x01)
TypePong = MessageType(0x02)
TypeCallMeMaybe = MessageType(0x03)
TypePing = MessageType(0x01)
TypePong = MessageType(0x02)
TypeCallMeMaybe = MessageType(0x03)
TypeBindUDPRelayEndpoint = MessageType(0x04)
TypeBindUDPRelayEndpointChallenge = MessageType(0x05)
TypeBindUDPRelayEndpointAnswer = MessageType(0x06)
TypeCallMeMaybeVia = MessageType(0x07)
TypeAllocateUDPRelayEndpointRequest = MessageType(0x08)
TypeAllocateUDPRelayEndpointResponse = MessageType(0x09)
)
const v0 = byte(0)
@@ -77,12 +84,25 @@ func Parse(p []byte) (Message, error) {
}
t, ver, p := MessageType(p[0]), p[1], p[2:]
switch t {
// TODO(jwhited): consider using a signature matching encoding.BinaryUnmarshaler
case TypePing:
return parsePing(ver, p)
case TypePong:
return parsePong(ver, p)
case TypeCallMeMaybe:
return parseCallMeMaybe(ver, p)
case TypeBindUDPRelayEndpoint:
return parseBindUDPRelayEndpoint(ver, p)
case TypeBindUDPRelayEndpointChallenge:
return parseBindUDPRelayEndpointChallenge(ver, p)
case TypeBindUDPRelayEndpointAnswer:
return parseBindUDPRelayEndpointAnswer(ver, p)
case TypeCallMeMaybeVia:
return parseCallMeMaybeVia(ver, p)
case TypeAllocateUDPRelayEndpointRequest:
return parseAllocateUDPRelayEndpointRequest(ver, p)
case TypeAllocateUDPRelayEndpointResponse:
return parseAllocateUDPRelayEndpointResponse(ver, p)
default:
return nil, fmt.Errorf("unknown message type 0x%02x", byte(t))
}
@@ -91,6 +111,7 @@ func Parse(p []byte) (Message, error) {
// Message a discovery message.
type Message interface {
// AppendMarshal appends the message's marshaled representation.
// TODO(jwhited): consider using a signature matching encoding.BinaryAppender
AppendMarshal([]byte) []byte
}
@@ -266,7 +287,368 @@ func MessageSummary(m Message) string {
return fmt.Sprintf("pong tx=%x", m.TxID[:6])
case *CallMeMaybe:
return "call-me-maybe"
case *CallMeMaybeVia:
return "call-me-maybe-via"
case *BindUDPRelayEndpoint:
return "bind-udp-relay-endpoint"
case *BindUDPRelayEndpointChallenge:
return "bind-udp-relay-endpoint-challenge"
case *BindUDPRelayEndpointAnswer:
return "bind-udp-relay-endpoint-answer"
case *AllocateUDPRelayEndpointRequest:
return "allocate-udp-relay-endpoint-request"
case *AllocateUDPRelayEndpointResponse:
return "allocate-udp-relay-endpoint-response"
default:
return fmt.Sprintf("%#v", m)
}
}
// BindUDPRelayHandshakeState represents the state of the 3-way bind handshake
// between UDP relay client and UDP relay server. Its potential values include
// those for both participants, UDP relay client and UDP relay server. A UDP
// relay server implementation can be found in net/udprelay. This is currently
// considered experimental.
type BindUDPRelayHandshakeState int
const (
// BindUDPRelayHandshakeStateInit represents the initial state prior to any
// message being transmitted.
BindUDPRelayHandshakeStateInit BindUDPRelayHandshakeState = iota
// BindUDPRelayHandshakeStateBindSent is the first client state after
// transmitting a BindUDPRelayEndpoint message to a UDP relay server.
BindUDPRelayHandshakeStateBindSent
// BindUDPRelayHandshakeStateChallengeSent is the first server state after
// receiving a BindUDPRelayEndpoint message from a UDP relay client and
// replying with a BindUDPRelayEndpointChallenge.
BindUDPRelayHandshakeStateChallengeSent
// BindUDPRelayHandshakeStateAnswerSent is a client state that is entered
// after transmitting a BindUDPRelayEndpointAnswer message towards a UDP
// relay server in response to a BindUDPRelayEndpointChallenge message.
BindUDPRelayHandshakeStateAnswerSent
// BindUDPRelayHandshakeStateAnswerReceived is a server state that is
// entered after it has received a correct BindUDPRelayEndpointAnswer
// message from a UDP relay client in response to a
// BindUDPRelayEndpointChallenge message.
BindUDPRelayHandshakeStateAnswerReceived
)
// bindUDPRelayEndpointCommonLen is the length of a marshalled
// [BindUDPRelayEndpointCommon], without the message header.
const bindUDPRelayEndpointCommonLen = 72
// BindUDPRelayChallengeLen is the length of the Challenge field carried in
// [BindUDPRelayEndpointChallenge] & [BindUDPRelayEndpointAnswer] messages.
const BindUDPRelayChallengeLen = 32
// BindUDPRelayEndpointCommon contains fields that are common across all 3
// UDP relay handshake message types. All 4 field values are expected to be
// consistent for the lifetime of a handshake besides Challenge, which is
// irrelevant in a [BindUDPRelayEndpoint] message.
type BindUDPRelayEndpointCommon struct {
// VNI is the Geneve header Virtual Network Identifier field value, which
// must match this disco-sealed value upon reception. If they are
// non-matching it indicates the cleartext Geneve header was tampered with
// and/or mangled.
VNI uint32
// Generation represents the handshake generation. Clients must set a new,
// nonzero value at the start of every handshake.
Generation uint32
// RemoteKey is the disco key of the remote peer participating over this
// relay endpoint.
RemoteKey key.DiscoPublic
// Challenge is set by the server in a [BindUDPRelayEndpointChallenge]
// message, and expected to be echoed back by the client in a
// [BindUDPRelayEndpointAnswer] message. Its value is irrelevant in a
// [BindUDPRelayEndpoint] message, where it simply serves a padding purpose
// ensuring all handshake messages are equal in size.
Challenge [BindUDPRelayChallengeLen]byte
}
// encode encodes m in b. b must be at least bindUDPRelayEndpointCommonLen bytes
// long.
func (m *BindUDPRelayEndpointCommon) encode(b []byte) {
binary.BigEndian.PutUint32(b, m.VNI)
b = b[4:]
binary.BigEndian.PutUint32(b, m.Generation)
b = b[4:]
m.RemoteKey.AppendTo(b[:0])
b = b[key.DiscoPublicRawLen:]
copy(b, m.Challenge[:])
}
// decode decodes m from b.
func (m *BindUDPRelayEndpointCommon) decode(b []byte) error {
if len(b) < bindUDPRelayEndpointCommonLen {
return errShort
}
m.VNI = binary.BigEndian.Uint32(b)
b = b[4:]
m.Generation = binary.BigEndian.Uint32(b)
b = b[4:]
m.RemoteKey = key.DiscoPublicFromRaw32(mem.B(b[:key.DiscoPublicRawLen]))
b = b[key.DiscoPublicRawLen:]
copy(m.Challenge[:], b[:BindUDPRelayChallengeLen])
return nil
}
// BindUDPRelayEndpoint is the first messaged transmitted from UDP relay client
// towards UDP relay server as part of the 3-way bind handshake.
type BindUDPRelayEndpoint struct {
BindUDPRelayEndpointCommon
}
func (m *BindUDPRelayEndpoint) AppendMarshal(b []byte) []byte {
ret, d := appendMsgHeader(b, TypeBindUDPRelayEndpoint, v0, bindUDPRelayEndpointCommonLen)
m.BindUDPRelayEndpointCommon.encode(d)
return ret
}
func parseBindUDPRelayEndpoint(ver uint8, p []byte) (m *BindUDPRelayEndpoint, err error) {
m = new(BindUDPRelayEndpoint)
err = m.BindUDPRelayEndpointCommon.decode(p)
if err != nil {
return nil, err
}
return m, nil
}
// BindUDPRelayEndpointChallenge is transmitted from UDP relay server towards
// UDP relay client in response to a BindUDPRelayEndpoint message as part of the
// 3-way bind handshake.
type BindUDPRelayEndpointChallenge struct {
BindUDPRelayEndpointCommon
}
func (m *BindUDPRelayEndpointChallenge) AppendMarshal(b []byte) []byte {
ret, d := appendMsgHeader(b, TypeBindUDPRelayEndpointChallenge, v0, bindUDPRelayEndpointCommonLen)
m.BindUDPRelayEndpointCommon.encode(d)
return ret
}
func parseBindUDPRelayEndpointChallenge(ver uint8, p []byte) (m *BindUDPRelayEndpointChallenge, err error) {
m = new(BindUDPRelayEndpointChallenge)
err = m.BindUDPRelayEndpointCommon.decode(p)
if err != nil {
return nil, err
}
return m, nil
}
// BindUDPRelayEndpointAnswer is transmitted from UDP relay client to UDP relay
// server in response to a BindUDPRelayEndpointChallenge message.
type BindUDPRelayEndpointAnswer struct {
BindUDPRelayEndpointCommon
}
func (m *BindUDPRelayEndpointAnswer) AppendMarshal(b []byte) []byte {
ret, d := appendMsgHeader(b, TypeBindUDPRelayEndpointAnswer, v0, bindUDPRelayEndpointCommonLen)
m.BindUDPRelayEndpointCommon.encode(d)
return ret
}
func parseBindUDPRelayEndpointAnswer(ver uint8, p []byte) (m *BindUDPRelayEndpointAnswer, err error) {
m = new(BindUDPRelayEndpointAnswer)
err = m.BindUDPRelayEndpointCommon.decode(p)
if err != nil {
return nil, err
}
return m, nil
}
// AllocateUDPRelayEndpointRequest is a message sent only over DERP to request
// allocation of a relay endpoint on a [tailscale.com/net/udprelay.Server]
type AllocateUDPRelayEndpointRequest struct {
// ClientDisco are the Disco public keys of the clients that should be
// permitted to handshake with the endpoint.
ClientDisco [2]key.DiscoPublic
// Generation represents the allocation request generation. The server must
// echo it back in the [AllocateUDPRelayEndpointResponse] to enable request
// and response alignment client-side.
Generation uint32
}
// allocateUDPRelayEndpointRequestLen is the length of a marshaled
// [AllocateUDPRelayEndpointRequest] message without the message header.
const allocateUDPRelayEndpointRequestLen = key.DiscoPublicRawLen*2 + // ClientDisco
4 // Generation
func (m *AllocateUDPRelayEndpointRequest) AppendMarshal(b []byte) []byte {
ret, p := appendMsgHeader(b, TypeAllocateUDPRelayEndpointRequest, v0, allocateUDPRelayEndpointRequestLen)
for i := 0; i < len(m.ClientDisco); i++ {
disco := m.ClientDisco[i].AppendTo(nil)
copy(p, disco)
p = p[key.DiscoPublicRawLen:]
}
binary.BigEndian.PutUint32(p, m.Generation)
return ret
}
func parseAllocateUDPRelayEndpointRequest(ver uint8, p []byte) (m *AllocateUDPRelayEndpointRequest, err error) {
m = new(AllocateUDPRelayEndpointRequest)
if ver != 0 {
return
}
if len(p) < allocateUDPRelayEndpointRequestLen {
return m, errShort
}
for i := 0; i < len(m.ClientDisco); i++ {
m.ClientDisco[i] = key.DiscoPublicFromRaw32(mem.B(p[:key.DiscoPublicRawLen]))
p = p[key.DiscoPublicRawLen:]
}
m.Generation = binary.BigEndian.Uint32(p)
return m, nil
}
// AllocateUDPRelayEndpointResponse is a message sent only over DERP in response
// to a [AllocateUDPRelayEndpointRequest].
type AllocateUDPRelayEndpointResponse struct {
// Generation represents the allocation request generation. The server must
// echo back the [AllocateUDPRelayEndpointRequest.Generation] here to enable
// request and response alignment client-side.
Generation uint32
UDPRelayEndpoint
}
func (m *AllocateUDPRelayEndpointResponse) AppendMarshal(b []byte) []byte {
endpointsLen := epLength * len(m.AddrPorts)
generationLen := 4
ret, d := appendMsgHeader(b, TypeAllocateUDPRelayEndpointResponse, v0, generationLen+udpRelayEndpointLenMinusAddrPorts+endpointsLen)
binary.BigEndian.PutUint32(d, m.Generation)
m.encode(d[4:])
return ret
}
func parseAllocateUDPRelayEndpointResponse(ver uint8, p []byte) (m *AllocateUDPRelayEndpointResponse, err error) {
m = new(AllocateUDPRelayEndpointResponse)
if ver != 0 {
return m, nil
}
if len(p) < 4 {
return m, errShort
}
m.Generation = binary.BigEndian.Uint32(p)
err = m.decode(p[4:])
return m, err
}
const udpRelayEndpointLenMinusAddrPorts = key.DiscoPublicRawLen + // ServerDisco
(key.DiscoPublicRawLen * 2) + // ClientDisco
8 + // LamportID
4 + // VNI
8 + // BindLifetime
8 // SteadyStateLifetime
// UDPRelayEndpoint is a mirror of [tailscale.com/net/udprelay/endpoint.ServerEndpoint],
// refer to it for field documentation. [UDPRelayEndpoint] is carried in both
// [CallMeMaybeVia] and [AllocateUDPRelayEndpointResponse] messages.
type UDPRelayEndpoint struct {
// ServerDisco is [tailscale.com/net/udprelay/endpoint.ServerEndpoint.ServerDisco]
ServerDisco key.DiscoPublic
// ClientDisco is [tailscale.com/net/udprelay/endpoint.ServerEndpoint.ClientDisco]
ClientDisco [2]key.DiscoPublic
// LamportID is [tailscale.com/net/udprelay/endpoint.ServerEndpoint.LamportID]
LamportID uint64
// VNI is [tailscale.com/net/udprelay/endpoint.ServerEndpoint.VNI]
VNI uint32
// BindLifetime is [tailscale.com/net/udprelay/endpoint.ServerEndpoint.BindLifetime]
BindLifetime time.Duration
// SteadyStateLifetime is [tailscale.com/net/udprelay/endpoint.ServerEndpoint.SteadyStateLifetime]
SteadyStateLifetime time.Duration
// AddrPorts is [tailscale.com/net/udprelay/endpoint.ServerEndpoint.AddrPorts]
AddrPorts []netip.AddrPort
}
// encode encodes m in b. b must be at least [udpRelayEndpointLenMinusAddrPorts]
// + [epLength] * len(m.AddrPorts) bytes long.
func (m *UDPRelayEndpoint) encode(b []byte) {
disco := m.ServerDisco.AppendTo(nil)
copy(b, disco)
b = b[key.DiscoPublicRawLen:]
for i := 0; i < len(m.ClientDisco); i++ {
disco = m.ClientDisco[i].AppendTo(nil)
copy(b, disco)
b = b[key.DiscoPublicRawLen:]
}
binary.BigEndian.PutUint64(b[:8], m.LamportID)
b = b[8:]
binary.BigEndian.PutUint32(b[:4], m.VNI)
b = b[4:]
binary.BigEndian.PutUint64(b[:8], uint64(m.BindLifetime))
b = b[8:]
binary.BigEndian.PutUint64(b[:8], uint64(m.SteadyStateLifetime))
b = b[8:]
for _, ipp := range m.AddrPorts {
a := ipp.Addr().As16()
copy(b, a[:])
binary.BigEndian.PutUint16(b[16:18], ipp.Port())
b = b[epLength:]
}
}
// decode decodes m from b.
func (m *UDPRelayEndpoint) decode(b []byte) error {
if len(b) < udpRelayEndpointLenMinusAddrPorts+epLength ||
(len(b)-udpRelayEndpointLenMinusAddrPorts)%epLength != 0 {
return errShort
}
m.ServerDisco = key.DiscoPublicFromRaw32(mem.B(b[:key.DiscoPublicRawLen]))
b = b[key.DiscoPublicRawLen:]
for i := 0; i < len(m.ClientDisco); i++ {
m.ClientDisco[i] = key.DiscoPublicFromRaw32(mem.B(b[:key.DiscoPublicRawLen]))
b = b[key.DiscoPublicRawLen:]
}
m.LamportID = binary.BigEndian.Uint64(b[:8])
b = b[8:]
m.VNI = binary.BigEndian.Uint32(b[:4])
b = b[4:]
m.BindLifetime = time.Duration(binary.BigEndian.Uint64(b[:8]))
b = b[8:]
m.SteadyStateLifetime = time.Duration(binary.BigEndian.Uint64(b[:8]))
b = b[8:]
m.AddrPorts = make([]netip.AddrPort, 0, len(b)-udpRelayEndpointLenMinusAddrPorts/epLength)
for len(b) > 0 {
var a [16]byte
copy(a[:], b)
m.AddrPorts = append(m.AddrPorts, netip.AddrPortFrom(
netip.AddrFrom16(a).Unmap(),
binary.BigEndian.Uint16(b[16:18])))
b = b[epLength:]
}
return nil
}
// CallMeMaybeVia is a message sent only over DERP to request that the recipient
// try to open up a magicsock path back to the sender. The 'Via' in
// CallMeMaybeVia highlights that candidate paths are served through an
// intermediate relay, likely a [tailscale.com/net/udprelay.Server].
//
// Usage of the candidate paths in magicsock requires a 3-way handshake
// involving [BindUDPRelayEndpoint], [BindUDPRelayEndpointChallenge], and
// [BindUDPRelayEndpointAnswer].
//
// CallMeMaybeVia mirrors [tailscale.com/net/udprelay/endpoint.ServerEndpoint],
// which contains field documentation.
//
// The recipient may choose to not open a path back if it's already happy with
// its path. Direct connections, e.g. [CallMeMaybe]-signaled, take priority over
// CallMeMaybeVia paths.
type CallMeMaybeVia struct {
UDPRelayEndpoint
}
func (m *CallMeMaybeVia) AppendMarshal(b []byte) []byte {
endpointsLen := epLength * len(m.AddrPorts)
ret, p := appendMsgHeader(b, TypeCallMeMaybeVia, v0, udpRelayEndpointLenMinusAddrPorts+endpointsLen)
m.encode(p)
return ret
}
func parseCallMeMaybeVia(ver uint8, p []byte) (m *CallMeMaybeVia, err error) {
m = new(CallMeMaybeVia)
if ver != 0 {
return m, nil
}
err = m.decode(p)
return m, err
}