net/netcheck, tailcfg: add DERPHomeParams and use it

This allows providing additional information to the client about how to
select a home DERP region, such as preferring a given DERP region over
all others.

Updates #8603

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: I7c4a270f31d8585112fab5408799ffba5b75266f
This commit is contained in:
Andrew Dunham
2023-07-12 14:45:46 -04:00
parent 7a82fd8dbe
commit 7aba0b0d78
8 changed files with 328 additions and 18 deletions

View File

@@ -7,6 +7,11 @@ import "sort"
// DERPMap describes the set of DERP packet relay servers that are available.
type DERPMap struct {
// HomeParams, if non-nil, is a change in home parameters.
//
// The rest of the DEPRMap fields, if zero, means unchanged.
HomeParams *DERPHomeParams `json:",omitempty"`
// Regions is the set of geographic regions running DERP node(s).
//
// It's keyed by the DERPRegion.RegionID.
@@ -16,6 +21,8 @@ type DERPMap struct {
// OmitDefaultRegions specifies to not use Tailscale's DERP servers, and only use those
// specified in this DERPMap. If there are none set outside of the defaults, this is a noop.
//
// This field is only meaningful if the Regions map is non-nil (indicating a change).
OmitDefaultRegions bool `json:"omitDefaultRegions,omitempty"`
}
@@ -29,6 +36,25 @@ func (m *DERPMap) RegionIDs() []int {
return ret
}
// DERPHomeParams contains parameters from the server related to selecting a
// DERP home region (sometimes referred to as the "preferred DERP").
type DERPHomeParams struct {
// RegionScore scales latencies of DERP regions by a given scaling
// factor when determining which region to use as the home
// ("preferred") DERP. Scores in the range (0, 1) will cause this
// region to be proportionally more preferred, and scores in the range
// (1, ∞) will penalize a region.
//
// If a region is not present in this map, it is treated as having a
// score of 1.0.
//
// Scores should not be 0 or negative; such scores will be ignored.
//
// A nil map means no change from the previous value (if any); an empty
// non-nil map can be sent to reset all scores back to 1.0.
RegionScore map[int]float64 `json:",omitempty"`
}
// DERPRegion is a geographic region running DERP relay node(s).
//
// Client nodes discover which region they're closest to, advertise

View File

@@ -3,7 +3,7 @@
package tailcfg
//go:generate go run tailscale.com/cmd/viewer --type=User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode,SSHRule,SSHAction,SSHPrincipal,ControlDialPlan,Location --clonefunc
//go:generate go run tailscale.com/cmd/viewer --type=User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPHomeParams,DERPRegion,DERPMap,DERPNode,SSHRule,SSHAction,SSHPrincipal,ControlDialPlan,Location --clonefunc
import (
"bytes"
@@ -101,7 +101,8 @@ type CapabilityVersion int
// - 62: 2023-05-05: Client can notify control over noise for SSHEventNotificationRequest recording failure events
// - 63: 2023-06-08: Client understands SSHAction.AllowRemotePortForwarding.
// - 64: 2023-07-11: Client understands s/CapabilityTailnetLockAlpha/CapabilityTailnetLock
const CurrentCapabilityVersion CapabilityVersion = 64
// - 65: 2023-07-12: Client understands DERPMap.HomeParams + incremental DERPMap updates with params
const CurrentCapabilityVersion CapabilityVersion = 65
type StableID string

View File

@@ -286,6 +286,28 @@ var _RegisterResponseCloneNeedsRegeneration = RegisterResponse(struct {
Error string
}{})
// Clone makes a deep copy of DERPHomeParams.
// The result aliases no memory with the original.
func (src *DERPHomeParams) Clone() *DERPHomeParams {
if src == nil {
return nil
}
dst := new(DERPHomeParams)
*dst = *src
if dst.RegionScore != nil {
dst.RegionScore = map[int]float64{}
for k, v := range src.RegionScore {
dst.RegionScore[k] = v
}
}
return dst
}
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _DERPHomeParamsCloneNeedsRegeneration = DERPHomeParams(struct {
RegionScore map[int]float64
}{})
// Clone makes a deep copy of DERPRegion.
// The result aliases no memory with the original.
func (src *DERPRegion) Clone() *DERPRegion {
@@ -318,6 +340,7 @@ func (src *DERPMap) Clone() *DERPMap {
}
dst := new(DERPMap)
*dst = *src
dst.HomeParams = src.HomeParams.Clone()
if dst.Regions != nil {
dst.Regions = map[int]*DERPRegion{}
for k, v := range src.Regions {
@@ -329,6 +352,7 @@ func (src *DERPMap) Clone() *DERPMap {
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _DERPMapCloneNeedsRegeneration = DERPMap(struct {
HomeParams *DERPHomeParams
Regions map[int]*DERPRegion
OmitDefaultRegions bool
}{})
@@ -484,7 +508,7 @@ var _LocationCloneNeedsRegeneration = Location(struct {
// Clone duplicates src into dst and reports whether it succeeded.
// To succeed, <src, dst> must be of types <*T, *T> or <*T, **T>,
// where T is one of User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode,SSHRule,SSHAction,SSHPrincipal,ControlDialPlan,Location.
// where T is one of User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPHomeParams,DERPRegion,DERPMap,DERPNode,SSHRule,SSHAction,SSHPrincipal,ControlDialPlan,Location.
func Clone(dst, src any) bool {
switch src := src.(type) {
case *User:
@@ -550,6 +574,15 @@ func Clone(dst, src any) bool {
*dst = src.Clone()
return true
}
case *DERPHomeParams:
switch dst := dst.(type) {
case *DERPHomeParams:
*dst = *src.Clone()
return true
case **DERPHomeParams:
*dst = src.Clone()
return true
}
case *DERPRegion:
switch dst := dst.(type) {
case *DERPRegion:

View File

@@ -20,7 +20,7 @@ import (
"tailscale.com/types/views"
)
//go:generate go run tailscale.com/cmd/cloner -clonefunc=true -type=User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode,SSHRule,SSHAction,SSHPrincipal,ControlDialPlan,Location
//go:generate go run tailscale.com/cmd/cloner -clonefunc=true -type=User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPHomeParams,DERPRegion,DERPMap,DERPNode,SSHRule,SSHAction,SSHPrincipal,ControlDialPlan,Location
// View returns a readonly view of User.
func (p *User) View() UserView {
@@ -633,6 +633,60 @@ var _RegisterResponseViewNeedsRegeneration = RegisterResponse(struct {
Error string
}{})
// View returns a readonly view of DERPHomeParams.
func (p *DERPHomeParams) View() DERPHomeParamsView {
return DERPHomeParamsView{ж: p}
}
// DERPHomeParamsView provides a read-only view over DERPHomeParams.
//
// Its methods should only be called if `Valid()` returns true.
type DERPHomeParamsView struct {
// ж is the underlying mutable value, named with a hard-to-type
// character that looks pointy like a pointer.
// It is named distinctively to make you think of how dangerous it is to escape
// to callers. You must not let callers be able to mutate it.
ж *DERPHomeParams
}
// Valid reports whether underlying value is non-nil.
func (v DERPHomeParamsView) Valid() bool { return v.ж != nil }
// AsStruct returns a clone of the underlying value which aliases no memory with
// the original.
func (v DERPHomeParamsView) AsStruct() *DERPHomeParams {
if v.ж == nil {
return nil
}
return v.ж.Clone()
}
func (v DERPHomeParamsView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
func (v *DERPHomeParamsView) UnmarshalJSON(b []byte) error {
if v.ж != nil {
return errors.New("already initialized")
}
if len(b) == 0 {
return nil
}
var x DERPHomeParams
if err := json.Unmarshal(b, &x); err != nil {
return err
}
v.ж = &x
return nil
}
func (v DERPHomeParamsView) RegionScore() views.Map[int, float64] {
return views.MapOf(v.ж.RegionScore)
}
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _DERPHomeParamsViewNeedsRegeneration = DERPHomeParams(struct {
RegionScore map[int]float64
}{})
// View returns a readonly view of DERPRegion.
func (p *DERPRegion) View() DERPRegionView {
return DERPRegionView{ж: p}
@@ -740,6 +794,8 @@ func (v *DERPMapView) UnmarshalJSON(b []byte) error {
return nil
}
func (v DERPMapView) HomeParams() DERPHomeParamsView { return v.ж.HomeParams.View() }
func (v DERPMapView) Regions() views.MapFn[int, *DERPRegion, DERPRegionView] {
return views.MapFnOf(v.ж.Regions, func(t *DERPRegion) DERPRegionView {
return t.View()
@@ -749,6 +805,7 @@ func (v DERPMapView) OmitDefaultRegions() bool { return v.ж.OmitDefaultRegions
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _DERPMapViewNeedsRegeneration = DERPMap(struct {
HomeParams *DERPHomeParams
Regions map[int]*DERPRegion
OmitDefaultRegions bool
}{})