Sfoglia il codice sorgente

all: start groundwork for using capver for localapi & peerapi

Updates #7015

Change-Id: I3d4c11b42a727a62eaac3262a879f29bb4ce82dd
Signed-off-by: Brad Fitzpatrick <[email protected]>
Brad Fitzpatrick 3 anni fa
parent
commit
6edf357b96

+ 1 - 0
client/tailscale/localclient.go

@@ -114,6 +114,7 @@ func (lc *LocalClient) defaultDialer(ctx context.Context, network, addr string)
 //
 // DoLocalRequest may mutate the request to add Authorization headers.
 func (lc *LocalClient) DoLocalRequest(req *http.Request) (*http.Response, error) {
+	req.Header.Set("Tailscale-Cap", strconv.Itoa(int(tailcfg.CurrentCapabilityVersion)))
 	lc.tsClientOnce.Do(func() {
 		lc.tsClient = &http.Client{
 			Transport: &http.Transport{

+ 3 - 0
control/controlclient/map.go

@@ -310,6 +310,9 @@ func undeltaPeers(mapRes *tailcfg.MapResponse, prev []*tailcfg.Node) {
 				if ec.DERPRegion != 0 {
 					n.DERP = fmt.Sprintf("%s:%v", tailcfg.DerpMagicIP, ec.DERPRegion)
 				}
+				if ec.Cap != 0 {
+					n.Cap = ec.Cap
+				}
 				if ec.Endpoints != nil {
 					n.Endpoints = ec.Endpoints
 				}

+ 21 - 9
ipn/ipnlocal/local.go

@@ -4348,20 +4348,32 @@ func exitNodeCanProxyDNS(nm *netmap.NetworkMap, exitNodeID tailcfg.StableNodeID)
 		return "", false
 	}
 	for _, p := range nm.Peers {
-		if p.StableID != exitNodeID {
-			continue
-		}
-		services := p.Hostinfo.Services()
-		for i, n := 0, services.Len(); i < n; i++ {
-			s := services.At(i)
-			if s.Proto == tailcfg.PeerAPIDNS && s.Port >= 1 {
-				return peerAPIBase(nm, p) + "/dns-query", true
-			}
+		if p.StableID == exitNodeID && peerCanProxyDNS(p) {
+			return peerAPIBase(nm, p) + "/dns-query", true
 		}
 	}
 	return "", false
 }
 
+func peerCanProxyDNS(p *tailcfg.Node) bool {
+	if p.Cap >= 26 {
+		// Actually added at 25
+		// (https://github.com/tailscale/tailscale/blob/3ae6f898cfdb58fd0e30937147dd6ce28c6808dd/tailcfg/tailcfg.go#L51)
+		// so anything >= 26 can do it.
+		return true
+	}
+	// If p.Cap is not populated (e.g. older control server), then do the old
+	// thing of searching through services.
+	services := p.Hostinfo.Services()
+	for i, n := 0, services.Len(); i < n; i++ {
+		s := services.At(i)
+		if s.Proto == tailcfg.PeerAPIDNS && s.Port >= 1 {
+			return true
+		}
+	}
+	return false
+}
+
 func (b *LocalBackend) DebugRebind() error {
 	mc, err := b.magicConn()
 	if err != nil {

+ 1 - 0
ipn/localapi/localapi.go

@@ -155,6 +155,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	w.Header().Set("Tailscale-Version", version.Long)
+	w.Header().Set("Tailscale-Cap", strconv.Itoa(int(tailcfg.CurrentCapabilityVersion)))
 	w.Header().Set("Content-Security-Policy", `default-src 'none'; frame-ancestors 'none'; script-src 'none'; script-src-elem 'none'; script-src-attr 'none'`)
 	w.Header().Set("X-Frame-Options", "DENY")
 	w.Header().Set("X-Content-Type-Options", "nosniff")

+ 9 - 2
tailcfg/tailcfg.go

@@ -29,7 +29,8 @@ import (
 // single monotonically increasing integer, rather than the relatively
 // complex x.y.z-xxxxx semver+hash(es). Whenever the client gains a
 // capability or wants to negotiate a change in semantics with the
-// server (control plane), bump this number and document what's new.
+// server (control plane),  peers (over PeerAPI), or frontend (over
+// LocalAPI), bump this number and document what's new.
 //
 // Previously (prior to 2022-03-06), it was known as the "MapRequest
 // version" or "mapVer" or "map cap" and that name and usage persists
@@ -90,7 +91,8 @@ type CapabilityVersion int
 //   - 51: 2022-11-30: Client understands CapabilityTailnetLockAlpha
 //   - 52: 2023-01-05: client can handle c2n POST /logtail/flush
 //   - 53: 2023-01-18: client respects explicit Node.Expired + auto-sets based on Node.KeyExpiry
-const CurrentCapabilityVersion CapabilityVersion = 53
+//   - 54: 2023-01-19: Node.Cap added, PeersChangedPatch.Cap, uses Node.Cap for ExitDNS before Hostinfo.Services fallback
+const CurrentCapabilityVersion CapabilityVersion = 54
 
 type StableID string
 
@@ -199,6 +201,7 @@ type Node struct {
 	DERP         string         `json:",omitempty"` // DERP-in-IP:port ("127.3.3.40:N") endpoint
 	Hostinfo     HostinfoView
 	Created      time.Time
+	Cap          CapabilityVersion `json:",omitempty"` // if non-zero, the node's capability version; old servers might not send
 
 	// Tags are the list of ACL tags applied to this node.
 	// Tags take the form of `tag:<value>` where value starts
@@ -1627,6 +1630,7 @@ func (n *Node) Equal(n2 *Node) bool {
 		eqCIDRs(n.PrimaryRoutes, n2.PrimaryRoutes) &&
 		eqStrings(n.Endpoints, n2.Endpoints) &&
 		n.DERP == n2.DERP &&
+		n.Cap == n2.Cap &&
 		n.Hostinfo.Equal(n2.Hostinfo) &&
 		n.Created.Equal(n2.Created) &&
 		eqTimePtr(n.LastSeen, n2.LastSeen) &&
@@ -2001,6 +2005,9 @@ type PeerChange struct {
 	// region ID is now this number.
 	DERPRegion int `json:",omitempty"`
 
+	// Cap, if non-zero, means that NodeID's capability version has changed.
+	Cap CapabilityVersion `json:",omitempty"`
+
 	// Endpoints, if non-empty, means that NodeID's UDP Endpoints
 	// have changed to these.
 	Endpoints []string `json:",omitempty"`

+ 1 - 0
tailcfg/tailcfg_clone.go

@@ -85,6 +85,7 @@ var _NodeCloneNeedsRegeneration = Node(struct {
 	DERP                    string
 	Hostinfo                HostinfoView
 	Created                 time.Time
+	Cap                     CapabilityVersion
 	Tags                    []string
 	PrimaryRoutes           []netip.Prefix
 	LastSeen                *time.Time

+ 1 - 1
tailcfg/tailcfg_test.go

@@ -329,7 +329,7 @@ func TestNodeEqual(t *testing.T) {
 		"ID", "StableID", "Name", "User", "Sharer",
 		"Key", "KeyExpiry", "KeySignature", "Machine", "DiscoKey",
 		"Addresses", "AllowedIPs", "Endpoints", "DERP", "Hostinfo",
-		"Created", "Tags", "PrimaryRoutes",
+		"Created", "Cap", "Tags", "PrimaryRoutes",
 		"LastSeen", "Online", "KeepAlive", "MachineAuthorized",
 		"Capabilities",
 		"UnsignedPeerAPIOnly",

+ 2 - 0
tailcfg/tailcfg_view.go

@@ -148,6 +148,7 @@ func (v NodeView) Endpoints() views.Slice[string]  { return views.SliceOf(v.ж.E
 func (v NodeView) DERP() string                    { return v.ж.DERP }
 func (v NodeView) Hostinfo() HostinfoView          { return v.ж.Hostinfo }
 func (v NodeView) Created() time.Time              { return v.ж.Created }
+func (v NodeView) Cap() CapabilityVersion          { return v.ж.Cap }
 func (v NodeView) Tags() views.Slice[string]       { return views.SliceOf(v.ж.Tags) }
 func (v NodeView) PrimaryRoutes() views.IPPrefixSlice {
 	return views.IPPrefixSliceOf(v.ж.PrimaryRoutes)
@@ -196,6 +197,7 @@ var _NodeViewNeedsRegeneration = Node(struct {
 	DERP                    string
 	Hostinfo                HostinfoView
 	Created                 time.Time
+	Cap                     CapabilityVersion
 	Tags                    []string
 	PrimaryRoutes           []netip.Prefix
 	LastSeen                *time.Time

+ 4 - 3
util/deephash/deephash_test.go

@@ -574,9 +574,10 @@ func TestGetTypeHasher(t *testing.T) {
 			out: "\x01\x01\x00\x00\x00\x02\x00\x00\x00\x03\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00\a\b\x00\x00\x00",
 		},
 		{
-			name: "tailcfg.Node",
-			val:  &tailcfg.Node{},
-			out:  "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+			name:  "tailcfg.Node",
+			val:   &tailcfg.Node{},
+			out:   "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+			out32: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
 		},
 	}
 	for _, tt := range tests {