mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-11 13:18:53 +00:00
net/udprelay: start of UDP relay server implementation (#15480)
This commit implements an experimental UDP relay server. The UDP relay server leverages the Disco protocol for a 3-way handshake between client and server, along with 3 new Disco message types for said handshake. These new Disco message types are also considered experimental, and are not yet tied to a capver. The server expects, and imposes, a Geneve (Generic Network Virtualization Encapsulation) header immediately following the underlay UDP header. Geneve protocol field values have been defined for Disco and WireGuard. The Geneve control bit must be set for the handshake between client and server, and unset for messages relayed between clients through the server. Updates tailscale/corp#27101 Signed-off-by: Jordan Whited <jordan@tailscale.com>
This commit is contained in:
128
disco/disco.go
128
disco/disco.go
@@ -41,9 +41,12 @@ 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)
|
||||
)
|
||||
|
||||
const v0 = byte(0)
|
||||
@@ -77,12 +80,19 @@ 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)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown message type 0x%02x", byte(t))
|
||||
}
|
||||
@@ -91,6 +101,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 +277,118 @@ func MessageSummary(m Message) string {
|
||||
return fmt.Sprintf("pong tx=%x", m.TxID[:6])
|
||||
case *CallMeMaybe:
|
||||
return "call-me-maybe"
|
||||
case *BindUDPRelayEndpoint:
|
||||
return "bind-udp-relay-endpoint"
|
||||
case *BindUDPRelayEndpointChallenge:
|
||||
return "bind-udp-relay-endpoint-challenge"
|
||||
case *BindUDPRelayEndpointAnswer:
|
||||
return "bind-udp-relay-endpoint-answer"
|
||||
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
|
||||
)
|
||||
|
||||
// bindUDPRelayEndpointLen is the length of a marshalled BindUDPRelayEndpoint
|
||||
// message, without the message header.
|
||||
const bindUDPRelayEndpointLen = BindUDPRelayEndpointChallengeLen
|
||||
|
||||
// BindUDPRelayEndpoint is the first messaged transmitted from UDP relay client
|
||||
// towards UDP relay server as part of the 3-way bind handshake. It is padded to
|
||||
// match the length of BindUDPRelayEndpointChallenge. This message type is
|
||||
// currently considered experimental and is not yet tied to a
|
||||
// tailcfg.CapabilityVersion.
|
||||
type BindUDPRelayEndpoint struct {
|
||||
}
|
||||
|
||||
func (m *BindUDPRelayEndpoint) AppendMarshal(b []byte) []byte {
|
||||
ret, _ := appendMsgHeader(b, TypeBindUDPRelayEndpoint, v0, bindUDPRelayEndpointLen)
|
||||
return ret
|
||||
}
|
||||
|
||||
func parseBindUDPRelayEndpoint(ver uint8, p []byte) (m *BindUDPRelayEndpoint, err error) {
|
||||
m = new(BindUDPRelayEndpoint)
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// BindUDPRelayEndpointChallengeLen is the length of a marshalled
|
||||
// BindUDPRelayEndpointChallenge message, without the message header.
|
||||
const BindUDPRelayEndpointChallengeLen = 32
|
||||
|
||||
// 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. This message type is currently considered experimental
|
||||
// and is not yet tied to a tailcfg.CapabilityVersion.
|
||||
type BindUDPRelayEndpointChallenge struct {
|
||||
Challenge [BindUDPRelayEndpointChallengeLen]byte
|
||||
}
|
||||
|
||||
func (m *BindUDPRelayEndpointChallenge) AppendMarshal(b []byte) []byte {
|
||||
ret, d := appendMsgHeader(b, TypeBindUDPRelayEndpointChallenge, v0, BindUDPRelayEndpointChallengeLen)
|
||||
copy(d, m.Challenge[:])
|
||||
return ret
|
||||
}
|
||||
|
||||
func parseBindUDPRelayEndpointChallenge(ver uint8, p []byte) (m *BindUDPRelayEndpointChallenge, err error) {
|
||||
if len(p) < BindUDPRelayEndpointChallengeLen {
|
||||
return nil, errShort
|
||||
}
|
||||
m = new(BindUDPRelayEndpointChallenge)
|
||||
copy(m.Challenge[:], p[:])
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// bindUDPRelayEndpointAnswerLen is the length of a marshalled
|
||||
// BindUDPRelayEndpointAnswer message, without the message header.
|
||||
const bindUDPRelayEndpointAnswerLen = BindUDPRelayEndpointChallengeLen
|
||||
|
||||
// BindUDPRelayEndpointAnswer is transmitted from UDP relay client to UDP relay
|
||||
// server in response to a BindUDPRelayEndpointChallenge message. This message
|
||||
// type is currently considered experimental and is not yet tied to a
|
||||
// tailcfg.CapabilityVersion.
|
||||
type BindUDPRelayEndpointAnswer struct {
|
||||
Answer [bindUDPRelayEndpointAnswerLen]byte
|
||||
}
|
||||
|
||||
func (m *BindUDPRelayEndpointAnswer) AppendMarshal(b []byte) []byte {
|
||||
ret, d := appendMsgHeader(b, TypeBindUDPRelayEndpointAnswer, v0, bindUDPRelayEndpointAnswerLen)
|
||||
copy(d, m.Answer[:])
|
||||
return ret
|
||||
}
|
||||
|
||||
func parseBindUDPRelayEndpointAnswer(ver uint8, p []byte) (m *BindUDPRelayEndpointAnswer, err error) {
|
||||
if len(p) < bindUDPRelayEndpointAnswerLen {
|
||||
return nil, errShort
|
||||
}
|
||||
m = new(BindUDPRelayEndpointAnswer)
|
||||
copy(m.Answer[:], p[:])
|
||||
return m, nil
|
||||
}
|
||||
|
@@ -83,6 +83,29 @@ func TestMarshalAndParse(t *testing.T) {
|
||||
},
|
||||
want: "03 00 00 00 00 00 00 00 00 00 00 00 ff ff 01 02 03 04 02 37 20 01 00 00 00 00 00 00 00 00 00 00 00 00 34 56 03 15",
|
||||
},
|
||||
{
|
||||
name: "bind_udp_relay_endpoint",
|
||||
m: &BindUDPRelayEndpoint{},
|
||||
want: "04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
|
||||
},
|
||||
{
|
||||
name: "bind_udp_relay_endpoint_challenge",
|
||||
m: &BindUDPRelayEndpointChallenge{
|
||||
Challenge: [BindUDPRelayEndpointChallengeLen]byte{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
},
|
||||
},
|
||||
want: "05 00 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f",
|
||||
},
|
||||
{
|
||||
name: "bind_udp_relay_endpoint_answer",
|
||||
m: &BindUDPRelayEndpointAnswer{
|
||||
Answer: [bindUDPRelayEndpointAnswerLen]byte{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
},
|
||||
},
|
||||
want: "06 00 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
Reference in New Issue
Block a user