From 6edf357b96b28ee1be659a70232c0135b2ffedfd Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 19 Jan 2023 12:40:58 -0800 Subject: [PATCH] all: start groundwork for using capver for localapi & peerapi Updates #7015 Change-Id: I3d4c11b42a727a62eaac3262a879f29bb4ce82dd Signed-off-by: Brad Fitzpatrick --- client/tailscale/localclient.go | 1 + control/controlclient/map.go | 3 +++ ipn/ipnlocal/local.go | 30 +++++++++++++++++++++--------- ipn/localapi/localapi.go | 1 + tailcfg/tailcfg.go | 11 +++++++++-- tailcfg/tailcfg_clone.go | 1 + tailcfg/tailcfg_test.go | 2 +- tailcfg/tailcfg_view.go | 2 ++ util/deephash/deephash_test.go | 7 ++++--- 9 files changed, 43 insertions(+), 15 deletions(-) diff --git a/client/tailscale/localclient.go b/client/tailscale/localclient.go index 59b047c66..bd7579078 100644 --- a/client/tailscale/localclient.go +++ b/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{ diff --git a/control/controlclient/map.go b/control/controlclient/map.go index e7db7aae4..f04fe7482 100644 --- a/control/controlclient/map.go +++ b/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 } diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index d9f513cac..7e9da9f12 100644 --- a/ipn/ipnlocal/local.go +++ b/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 { diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 4af93e6ac..fabc8952c 100644 --- a/ipn/localapi/localapi.go +++ b/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") diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 2e155bd6a..47936f51f 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -29,7 +29,8 @@ // 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 @@ // - 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:` 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"` diff --git a/tailcfg/tailcfg_clone.go b/tailcfg/tailcfg_clone.go index b502581a3..5f346ec02 100644 --- a/tailcfg/tailcfg_clone.go +++ b/tailcfg/tailcfg_clone.go @@ -85,6 +85,7 @@ func (src *Node) Clone() *Node { DERP string Hostinfo HostinfoView Created time.Time + Cap CapabilityVersion Tags []string PrimaryRoutes []netip.Prefix LastSeen *time.Time diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index 08cc6148a..0a3904ad6 100644 --- a/tailcfg/tailcfg_test.go +++ b/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", diff --git a/tailcfg/tailcfg_view.go b/tailcfg/tailcfg_view.go index 850d9edba..c957cac82 100644 --- a/tailcfg/tailcfg_view.go +++ b/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 @@ func (v NodeView) Equal(v2 NodeView) bool { return v.ж.Equal(v2.ж) DERP string Hostinfo HostinfoView Created time.Time + Cap CapabilityVersion Tags []string PrimaryRoutes []netip.Prefix LastSeen *time.Time diff --git a/util/deephash/deephash_test.go b/util/deephash/deephash_test.go index ce783ec46..2802b4adf 100644 --- a/util/deephash/deephash_test.go +++ b/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 {