Skip to content

Commit dae2319

Browse files
authored
disco: implement CallMeMaybeVia serialization (tailscale#15779)
This message type is currently unused and considered experimental. Updates tailscale/corp#27502 Signed-off-by: Jordan Whited <jordan@tailscale.com>
1 parent dbf1397 commit dae2319

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed

disco/disco.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"fmt"
2626
"net"
2727
"net/netip"
28+
"time"
2829

2930
"go4.org/mem"
3031
"tailscale.com/types/key"
@@ -47,6 +48,7 @@ const (
4748
TypeBindUDPRelayEndpoint = MessageType(0x04)
4849
TypeBindUDPRelayEndpointChallenge = MessageType(0x05)
4950
TypeBindUDPRelayEndpointAnswer = MessageType(0x06)
51+
TypeCallMeMaybeVia = MessageType(0x07)
5052
)
5153

5254
const v0 = byte(0)
@@ -93,6 +95,8 @@ func Parse(p []byte) (Message, error) {
9395
return parseBindUDPRelayEndpointChallenge(ver, p)
9496
case TypeBindUDPRelayEndpointAnswer:
9597
return parseBindUDPRelayEndpointAnswer(ver, p)
98+
case TypeCallMeMaybeVia:
99+
return parseCallMeMaybeVia(ver, p)
96100
default:
97101
return nil, fmt.Errorf("unknown message type 0x%02x", byte(t))
98102
}
@@ -392,3 +396,94 @@ func parseBindUDPRelayEndpointAnswer(ver uint8, p []byte) (m *BindUDPRelayEndpoi
392396
copy(m.Answer[:], p[:])
393397
return m, nil
394398
}
399+
400+
// CallMeMaybeVia is a message sent only over DERP to request that the recipient
401+
// try to open up a magicsock path back to the sender. The 'Via' in
402+
// CallMeMaybeVia highlights that candidate paths are served through an
403+
// intermediate relay, likely a [tailscale.com/net/udprelay.Server].
404+
//
405+
// Usage of the candidate paths in magicsock requires a 3-way handshake
406+
// involving [BindUDPRelayEndpoint], [BindUDPRelayEndpointChallenge], and
407+
// [BindUDPRelayEndpointAnswer].
408+
//
409+
// CallMeMaybeVia mirrors [tailscale.com/net/udprelay.ServerEndpoint], which
410+
// contains field documentation.
411+
//
412+
// The recipient may choose to not open a path back if it's already happy with
413+
// its path. Direct connections, e.g. [CallMeMaybe]-signaled, take priority over
414+
// CallMeMaybeVia paths.
415+
//
416+
// This message type is currently considered experimental and is not yet tied to
417+
// a [tailscale.com/tailcfg.CapabilityVersion].
418+
type CallMeMaybeVia struct {
419+
// ServerDisco is [tailscale.com/net/udprelay.ServerEndpoint.ServerDisco]
420+
ServerDisco key.DiscoPublic
421+
// LamportID is [tailscale.com/net/udprelay.ServerEndpoint.LamportID]
422+
LamportID uint64
423+
// VNI is [tailscale.com/net/udprelay.ServerEndpoint.VNI]
424+
VNI uint32
425+
// BindLifetime is [tailscale.com/net/udprelay.ServerEndpoint.BindLifetime]
426+
BindLifetime time.Duration
427+
// SteadyStateLifetime is [tailscale.com/net/udprelay.ServerEndpoint.SteadyStateLifetime]
428+
SteadyStateLifetime time.Duration
429+
// AddrPorts is [tailscale.com/net/udprelay.ServerEndpoint.AddrPorts]
430+
AddrPorts []netip.AddrPort
431+
}
432+
433+
const cmmvDataLenMinusEndpoints = key.DiscoPublicRawLen + // ServerDisco
434+
8 + // LamportID
435+
4 + // VNI
436+
8 + // BindLifetime
437+
8 // SteadyStateLifetime
438+
439+
func (m *CallMeMaybeVia) AppendMarshal(b []byte) []byte {
440+
endpointsLen := epLength * len(m.AddrPorts)
441+
ret, p := appendMsgHeader(b, TypeCallMeMaybeVia, v0, cmmvDataLenMinusEndpoints+endpointsLen)
442+
disco := m.ServerDisco.AppendTo(nil)
443+
copy(p, disco)
444+
p = p[key.DiscoPublicRawLen:]
445+
binary.BigEndian.PutUint64(p[:8], m.LamportID)
446+
p = p[8:]
447+
binary.BigEndian.PutUint32(p[:4], m.VNI)
448+
p = p[4:]
449+
binary.BigEndian.PutUint64(p[:8], uint64(m.BindLifetime))
450+
p = p[8:]
451+
binary.BigEndian.PutUint64(p[:8], uint64(m.SteadyStateLifetime))
452+
p = p[8:]
453+
for _, ipp := range m.AddrPorts {
454+
a := ipp.Addr().As16()
455+
copy(p, a[:])
456+
binary.BigEndian.PutUint16(p[16:18], ipp.Port())
457+
p = p[epLength:]
458+
}
459+
return ret
460+
}
461+
462+
func parseCallMeMaybeVia(ver uint8, p []byte) (m *CallMeMaybeVia, err error) {
463+
m = new(CallMeMaybeVia)
464+
if len(p) < cmmvDataLenMinusEndpoints+epLength ||
465+
(len(p)-cmmvDataLenMinusEndpoints)%epLength != 0 ||
466+
ver != 0 {
467+
return m, nil
468+
}
469+
m.ServerDisco = key.DiscoPublicFromRaw32(mem.B(p[:key.DiscoPublicRawLen]))
470+
p = p[key.DiscoPublicRawLen:]
471+
m.LamportID = binary.BigEndian.Uint64(p[:8])
472+
p = p[8:]
473+
m.VNI = binary.BigEndian.Uint32(p[:4])
474+
p = p[4:]
475+
m.BindLifetime = time.Duration(binary.BigEndian.Uint64(p[:8]))
476+
p = p[8:]
477+
m.SteadyStateLifetime = time.Duration(binary.BigEndian.Uint64(p[:8]))
478+
p = p[8:]
479+
m.AddrPorts = make([]netip.AddrPort, 0, len(p)-cmmvDataLenMinusEndpoints/epLength)
480+
for len(p) > 0 {
481+
var a [16]byte
482+
copy(a[:], p)
483+
m.AddrPorts = append(m.AddrPorts, netip.AddrPortFrom(
484+
netip.AddrFrom16(a).Unmap(),
485+
binary.BigEndian.Uint16(p[16:18])))
486+
p = p[epLength:]
487+
}
488+
return m, nil
489+
}

disco/disco_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"reflect"
1010
"strings"
1111
"testing"
12+
"time"
1213

1314
"go4.org/mem"
1415
"tailscale.com/types/key"
@@ -106,6 +107,21 @@ func TestMarshalAndParse(t *testing.T) {
106107
},
107108
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",
108109
},
110+
{
111+
name: "call_me_maybe_via",
112+
m: &CallMeMaybeVia{
113+
ServerDisco: key.DiscoPublicFromRaw32(mem.B([]byte{1: 1, 2: 2, 30: 30, 31: 31})),
114+
LamportID: 123,
115+
VNI: 456,
116+
BindLifetime: time.Second,
117+
SteadyStateLifetime: time.Minute,
118+
AddrPorts: []netip.AddrPort{
119+
netip.MustParseAddrPort("1.2.3.4:567"),
120+
netip.MustParseAddrPort("[2001::3456]:789"),
121+
},
122+
},
123+
want: "07 00 00 01 02 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 1e 1f 00 00 00 00 00 00 00 7b 00 00 01 c8 00 00 00 00 3b 9a ca 00 00 00 00 0d f8 47 58 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",
124+
},
109125
}
110126
for _, tt := range tests {
111127
t.Run(tt.name, func(t *testing.T) {

0 commit comments

Comments
 (0)