@@ -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
5254const 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+ }
0 commit comments