all: start groundwork for using capver for localapi & peerapi

Updates #7015

Change-Id: I3d4c11b42a727a62eaac3262a879f29bb4ce82dd
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/7019/head
Brad Fitzpatrick 2 years ago committed by Brad Fitzpatrick
parent c129bf1da1
commit 6edf357b96

@ -114,6 +114,7 @@ func (lc *LocalClient) defaultDialer(ctx context.Context, network, addr string)
// //
// DoLocalRequest may mutate the request to add Authorization headers. // DoLocalRequest may mutate the request to add Authorization headers.
func (lc *LocalClient) DoLocalRequest(req *http.Request) (*http.Response, error) { 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.tsClientOnce.Do(func() {
lc.tsClient = &http.Client{ lc.tsClient = &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{

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

@ -4348,20 +4348,32 @@ func exitNodeCanProxyDNS(nm *netmap.NetworkMap, exitNodeID tailcfg.StableNodeID)
return "", false return "", false
} }
for _, p := range nm.Peers { for _, p := range nm.Peers {
if p.StableID != exitNodeID { if p.StableID == exitNodeID && peerCanProxyDNS(p) {
continue return peerAPIBase(nm, p) + "/dns-query", true
}
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
}
} }
} }
return "", false 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 { func (b *LocalBackend) DebugRebind() error {
mc, err := b.magicConn() mc, err := b.magicConn()
if err != nil { if err != nil {

@ -155,6 +155,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
w.Header().Set("Tailscale-Version", version.Long) 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("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-Frame-Options", "DENY")
w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Content-Type-Options", "nosniff")

@ -29,7 +29,8 @@ import (
// single monotonically increasing integer, rather than the relatively // single monotonically increasing integer, rather than the relatively
// complex x.y.z-xxxxx semver+hash(es). Whenever the client gains a // complex x.y.z-xxxxx semver+hash(es). Whenever the client gains a
// capability or wants to negotiate a change in semantics with the // 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 // Previously (prior to 2022-03-06), it was known as the "MapRequest
// version" or "mapVer" or "map cap" and that name and usage persists // 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 // - 51: 2022-11-30: Client understands CapabilityTailnetLockAlpha
// - 52: 2023-01-05: client can handle c2n POST /logtail/flush // - 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 // - 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 type StableID string
@ -199,6 +201,7 @@ type Node struct {
DERP string `json:",omitempty"` // DERP-in-IP:port ("127.3.3.40:N") endpoint DERP string `json:",omitempty"` // DERP-in-IP:port ("127.3.3.40:N") endpoint
Hostinfo HostinfoView Hostinfo HostinfoView
Created time.Time 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 are the list of ACL tags applied to this node.
// Tags take the form of `tag:<value>` where value starts // 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) && eqCIDRs(n.PrimaryRoutes, n2.PrimaryRoutes) &&
eqStrings(n.Endpoints, n2.Endpoints) && eqStrings(n.Endpoints, n2.Endpoints) &&
n.DERP == n2.DERP && n.DERP == n2.DERP &&
n.Cap == n2.Cap &&
n.Hostinfo.Equal(n2.Hostinfo) && n.Hostinfo.Equal(n2.Hostinfo) &&
n.Created.Equal(n2.Created) && n.Created.Equal(n2.Created) &&
eqTimePtr(n.LastSeen, n2.LastSeen) && eqTimePtr(n.LastSeen, n2.LastSeen) &&
@ -2001,6 +2005,9 @@ type PeerChange struct {
// region ID is now this number. // region ID is now this number.
DERPRegion int `json:",omitempty"` 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 // Endpoints, if non-empty, means that NodeID's UDP Endpoints
// have changed to these. // have changed to these.
Endpoints []string `json:",omitempty"` Endpoints []string `json:",omitempty"`

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

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

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

@ -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", 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", name: "tailcfg.Node",
val: &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", 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 { for _, tt := range tests {

Loading…
Cancel
Save