diff --git a/control/controlknobs/controlknobs.go b/control/controlknobs/controlknobs.go index 6a36c9261..a6ff1281a 100644 --- a/control/controlknobs/controlknobs.go +++ b/control/controlknobs/controlknobs.go @@ -73,6 +73,9 @@ type Knobs struct { // ProbeUDPLifetime is whether the node should probe UDP path lifetime on // the tail end of an active direct connection in magicsock. ProbeUDPLifetime atomic.Bool + + // SuggestedExitNode is whether the node should be given a suggested exit node. + SuggestedExitNode atomic.Bool } // UpdateFromNodeAttributes updates k (if non-nil) based on the provided self @@ -100,6 +103,7 @@ func (k *Knobs) UpdateFromNodeAttributes(selfNodeAttrs []tailcfg.NodeCapability, forceNfTables = has(tailcfg.NodeAttrLinuxMustUseNfTables) seamlessKeyRenewal = has(tailcfg.NodeAttrSeamlessKeyRenewal) probeUDPLifetime = has(tailcfg.NodeAttrProbeUDPLifetime) + suggestedExitNode = has(tailcfg.NodeAttrSuggestedExitNode) ) if has(tailcfg.NodeAttrOneCGNATEnable) { @@ -122,6 +126,7 @@ func (k *Knobs) UpdateFromNodeAttributes(selfNodeAttrs []tailcfg.NodeCapability, k.LinuxForceNfTables.Store(forceNfTables) k.SeamlessKeyRenewal.Store(seamlessKeyRenewal) k.ProbeUDPLifetime.Store(probeUDPLifetime) + k.SuggestedExitNode.Store(suggestedExitNode) } // AsDebugJSON returns k as something that can be marshalled with json.Marshal @@ -145,5 +150,6 @@ func (k *Knobs) AsDebugJSON() map[string]any { "LinuxForceNfTables": k.LinuxForceNfTables.Load(), "SeamlessKeyRenewal": k.SeamlessKeyRenewal.Load(), "ProbeUDPLifetime": k.ProbeUDPLifetime.Load(), + "SuggestedExitNode": k.SuggestedExitNode.Load(), } } diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 83038bb04..2a75dafdb 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -1740,6 +1740,10 @@ type MapResponse struct { // Starting with MapRequest.Version 18, nil means unchanged. Node *Node `json:",omitempty"` + // SuggestedExitNode describes the suggested exit node to use + // given the self node's derp region. + SuggestedExitNode *Node `json:",omitempty"` + // DERPMap describe the set of DERP servers available. // A nil value means unchanged. DERPMap *DERPMap `json:",omitempty"` @@ -2208,6 +2212,9 @@ type Oauth2Token struct { // NodeAttrProbeUDPLifetime makes the client probe UDP path lifetime at the // tail end of an active direct connection in magicsock. NodeAttrProbeUDPLifetime NodeCapability = "probe-udp-lifetime" + + // NodeAttrSuggestedExitNode makes clients determine a suggested exit node. + NodeAttrSuggestedExitNode NodeCapability = "suggested-exit-node" ) // SetDNSRequest is a request to add a DNS record.