summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--cmd/viewer/viewer.go216
-rw-r--r--control/controlclient/auto.go20
-rw-r--r--control/controlclient/direct.go6
-rw-r--r--control/controlclient/map.go2
-rw-r--r--control/controlclient/status.go4
-rw-r--r--ipn/ipnlocal/local.go84
-rw-r--r--tailcfg/tailcfg.go1
-rw-r--r--tailcfg/tailcfg_view.go239
-rw-r--r--types/dnstype/dnstype.go1
-rw-r--r--types/dnstype/dnstype_view.go30
-rw-r--r--types/netmap/netmap.go6
-rw-r--r--types/netmap/netmap_view.go95
-rw-r--r--wgengine/filter/match.go1
-rw-r--r--wgengine/filter/match_view.go33
-rw-r--r--wgengine/netstack/netstack.go2
15 files changed, 680 insertions, 60 deletions
diff --git a/cmd/viewer/viewer.go b/cmd/viewer/viewer.go
new file mode 100644
index 000000000..df6287c26
--- /dev/null
+++ b/cmd/viewer/viewer.go
@@ -0,0 +1,216 @@
+// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Viewer is a tool to automate the creation of a view type.
+//
+// The generated View method provides a readonly view of the struct.
+//
+// This tool makes lots of implicit assumptions about the types you feed it.
+// In particular, it can only write relatively "shallow" View methods.
+// That is, if a type contains another named struct type, viewer assumes that
+// named type will also have a View method.
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "go/types"
+ "log"
+ "os"
+ "strings"
+
+ "golang.org/x/tools/go/packages"
+ "tailscale.com/util/codegen"
+)
+
+var (
+ flagTypes = flag.String("type", "", "comma-separated list of types; required")
+ flagOutput = flag.String("output", "", "output file; required")
+ flagBuildTags = flag.String("tags", "", "compiler build tags to apply")
+)
+
+func main() {
+ log.SetFlags(0)
+ log.SetPrefix("viewer: ")
+ flag.Parse()
+ if len(*flagTypes) == 0 {
+ flag.Usage()
+ os.Exit(2)
+ }
+ typeNames := strings.Split(*flagTypes, ",")
+
+ cfg := &packages.Config{
+ Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedName,
+ Tests: false,
+ }
+ if *flagBuildTags != "" {
+ cfg.BuildFlags = []string{"-tags=" + *flagBuildTags}
+ }
+ pkgs, err := packages.Load(cfg, ".")
+ if err != nil {
+ log.Fatal(err)
+ }
+ if len(pkgs) != 1 {
+ log.Fatalf("wrong number of packages: %d", len(pkgs))
+ }
+ pkg := pkgs[0]
+ buf := new(bytes.Buffer)
+ imports := make(map[string]struct{})
+ namedTypes := codegen.NamedTypes(pkg)
+ for _, typeName := range typeNames {
+ typ, ok := namedTypes[typeName]
+ if !ok {
+ log.Fatalf("could not find type %s", typeName)
+ }
+ gen(buf, imports, typ, pkg.Types)
+ }
+
+ contents := new(bytes.Buffer)
+ fmt.Fprintf(contents, header, *flagTypes, pkg.Name)
+ fmt.Fprintf(contents, "import (\n")
+ for s := range imports {
+ fmt.Fprintf(contents, "\t%q\n", s)
+ }
+ fmt.Fprintf(contents, ")\n\n")
+ contents.Write(buf.Bytes())
+
+ output := *flagOutput
+ if output == "" {
+ flag.Usage()
+ os.Exit(2)
+ }
+ if err := codegen.WriteFormatted(contents.Bytes(), output); err != nil {
+ log.Fatal(err)
+ }
+}
+
+const header = `// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Code generated by the following command; DO NOT EDIT.
+// tailscale.com/cmd/viewer -type %s
+
+package %s
+
+`
+
+func gen(buf *bytes.Buffer, imports map[string]struct{}, typ *types.Named, thisPkg *types.Package) {
+ pkgQual := func(pkg *types.Package) string {
+ if thisPkg == pkg {
+ return ""
+ }
+ imports[pkg.Path()] = struct{}{}
+ return pkg.Name()
+ }
+ importedName := func(t types.Type) string {
+ return types.TypeString(t, pkgQual)
+ }
+
+ t, ok := typ.Underlying().(*types.Struct)
+ if !ok {
+ return
+ }
+
+ name := typ.Obj().Name()
+ viewName := name + "View"
+ fmt.Fprintf(buf, "// View makes a readonly view of %s.\n", name)
+ fmt.Fprintf(buf, "func (src *%s) View() %s {\n", name, viewName)
+ fmt.Fprintf(buf, " return %s{src}\n", viewName)
+ fmt.Fprintf(buf, "}\n")
+
+ fmt.Fprintf(buf, "// %s is a readonly view of %s.\n", viewName, name)
+ fmt.Fprintf(buf, "type %s struct{ ж *%s }\n", viewName, name)
+ fmt.Fprintf(buf, "func (v %s) Valid() bool { return v.ж != nil }\n", viewName)
+
+ for i := 0; i < t.NumFields(); i++ {
+ fname := t.Field(i).Name()
+ ft := t.Field(i).Type()
+ if !codegen.ContainsPointers(ft) {
+ fmt.Fprintf(buf, "func (v %s) %s() %s { return v.ж.%s }\n", viewName, fname, importedName(ft), fname)
+ continue
+ }
+ if named, _ := ft.(*types.Named); named != nil && !hasBasicUnderlying(ft) {
+ genViewCall(buf, viewName, fname, importedName(ft))
+ continue
+ }
+ switch ft := ft.Underlying().(type) {
+ case *types.Slice:
+ if !codegen.ContainsPointers(ft.Elem()) {
+ // OK to return the slice as-is, since they can't modify the contents.
+ fmt.Fprintf(buf, "func (v %s) %s() %s { return v.ж.%s }\n", viewName, fname, importedName(ft), fname)
+ continue
+ }
+
+ n := importedName(ft.Elem())
+ if ptrTyp, isPtr := ft.Elem().(*types.Pointer); isPtr {
+ n = importedName(ptrTyp.Elem())
+ }
+
+ // Generate slice view.
+ styp := fmt.Sprintf("_%s_%s", viewName, fname)
+ fmt.Fprintf(buf, "type %s []%s\n", styp, importedName(ft.Elem()))
+ fmt.Fprintf(buf, "func (s %s) Len() int { return len(s) }\n", styp)
+ fmt.Fprintf(buf, "func (s %s) At(i int) %sView { return s[i].View() }\n", styp, n)
+
+ fmt.Fprintf(buf, "func (v %s) %s() interface { Len() int; At(int) %sView } {\n", viewName, fname, n)
+ fmt.Fprintf(buf, " return %s(v.ж.%s)\n", styp, fname)
+ fmt.Fprintf(buf, "}\n")
+ case *types.Pointer:
+ if named, _ := ft.Elem().(*types.Named); named != nil && codegen.ContainsPointers(ft.Elem()) {
+ genViewCall(buf, viewName, fname, importedName(named))
+ continue
+ }
+ if codegen.ContainsPointers(ft.Elem()) {
+ log.Fatalf("unhandled: pointers in pointers (%v)", ft)
+ }
+ n := importedName(ft.Elem())
+ fmt.Fprintf(buf, "func (v %s) %s() *%s {\n", viewName, fname, n)
+ fmt.Fprintf(buf, " ptr := v.ж.%s\n", fname)
+ fmt.Fprintf(buf, " if ptr == nil {\n")
+ fmt.Fprintf(buf, " return nil\n")
+ fmt.Fprintf(buf, " }\n")
+ fmt.Fprintf(buf, " cp := *ptr\n")
+ fmt.Fprintf(buf, " return &cp\n")
+ fmt.Fprintf(buf, "}\n")
+ case *types.Map:
+ // TODO: Generate map view, like the slice view.
+ // We need:
+ // * Len() int
+ // * Load(k) v
+ // * LoadOK(k) (v, bool)
+ // * Range(func(k, v) bool)
+ //
+ // Note that we need to handle a variety of elem types:
+ // basic types (float64), types with a View method,
+ // slices of the foregoing.
+ //
+ // This may require recursion to handle completely,
+ // or we can follow cloner's lead and just manually
+ // inline one level deep the code generation
+ // that we happen to need right now.
+ // (If we figure out recursion in this context,
+ // we might want to backport to cloner, too.)
+ log.Printf("TODO: Handle %s (%s)", name, ft)
+ default:
+ fmt.Fprintf(buf, `panic("TODO: %s (%T)")`, fname, ft)
+ }
+ }
+
+ buf.Write(codegen.AssertStructUnchanged(t, thisPkg, name, "View", imports))
+}
+
+func genViewCall(buf *bytes.Buffer, viewName, fieldName, importedName string) {
+ fmt.Fprintf(buf, "func (v %s) %s() %sView { return v.ж.%s.View() }\n", viewName, fieldName, importedName, fieldName)
+}
+
+func hasBasicUnderlying(typ types.Type) bool {
+ switch typ.Underlying().(type) {
+ case *types.Slice, *types.Map:
+ return true
+ default:
+ return false
+ }
+}
diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go
index ad117e08a..449698eda 100644
--- a/control/controlclient/auto.go
+++ b/control/controlclient/auto.go
@@ -285,7 +285,7 @@ func (c *Auto) authRoutine() {
// don't send status updates for context errors,
// since context cancelation is always on purpose.
if ctx.Err() == nil {
- c.sendStatus("authRoutine-report", err, "", nil)
+ c.sendStatus("authRoutine-report", err, "", netmap.NetworkMapView{})
}
}
@@ -313,7 +313,7 @@ func (c *Auto) authRoutine() {
c.synced = false
c.mu.Unlock()
- c.sendStatus("authRoutine-wantout", nil, "", nil)
+ c.sendStatus("authRoutine-wantout", nil, "", netmap.NetworkMapView{})
bo.BackOff(ctx, nil)
} else { // ie. goal.wantLoggedIn
c.mu.Lock()
@@ -355,7 +355,7 @@ func (c *Auto) authRoutine() {
c.synced = false
c.mu.Unlock()
- c.sendStatus("authRoutine-url", err, url, nil)
+ c.sendStatus("authRoutine-url", err, url, netmap.NetworkMapView{})
bo.BackOff(ctx, err)
continue
}
@@ -367,7 +367,7 @@ func (c *Auto) authRoutine() {
c.state = StateAuthenticated
c.mu.Unlock()
- c.sendStatus("authRoutine-success", nil, "", nil)
+ c.sendStatus("authRoutine-success", nil, "", netmap.NetworkMapView{})
c.cancelMapSafely()
bo.BackOff(ctx, nil)
}
@@ -435,7 +435,7 @@ func (c *Auto) mapRoutine() {
// don't send status updates for context errors,
// since context cancelation is always on purpose.
if ctx.Err() == nil {
- c.sendStatus("mapRoutine1", err, "", nil)
+ c.sendStatus("mapRoutine1", err, "", netmap.NetworkMapView{})
}
}
@@ -461,7 +461,7 @@ func (c *Auto) mapRoutine() {
c.mu.Unlock()
health.SetInPollNetMap(false)
- err := c.direct.PollNetMap(ctx, -1, func(nm *netmap.NetworkMap) {
+ err := c.direct.PollNetMap(ctx, -1, func(nm netmap.NetworkMapView) {
health.SetInPollNetMap(true)
c.mu.Lock()
@@ -482,7 +482,7 @@ func (c *Auto) mapRoutine() {
if c.loggedIn {
c.state = StateSynchronized
}
- exp := nm.Expiry
+ exp := nm.Expiry()
c.expiry = &exp
stillAuthed := c.loggedIn
state := c.state
@@ -563,7 +563,7 @@ func (c *Auto) SetNetInfo(ni *tailcfg.NetInfo) {
c.sendNewMapRequest()
}
-func (c *Auto) sendStatus(who string, err error, url string, nm *netmap.NetworkMap) {
+func (c *Auto) sendStatus(who string, err error, url string, nm netmap.NetworkMapView) {
c.mu.Lock()
state := c.state
loggedIn := c.loggedIn
@@ -583,13 +583,13 @@ func (c *Auto) sendStatus(who string, err error, url string, nm *netmap.NetworkM
if state == StateNotAuthenticated {
logoutFin = new(empty.Message)
}
- if nm != nil && loggedIn && synced {
+ if nm.Valid() && loggedIn && synced {
pp := c.direct.GetPersist()
p = &pp
} else {
// don't send netmap status, as it's misleading when we're
// not logged in.
- nm = nil
+ nm = netmap.NetworkMapView{}
}
new := Status{
LoginFinished: loginFin,
diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go
index 0a7105cb3..4cf47a2ad 100644
--- a/control/controlclient/direct.go
+++ b/control/controlclient/direct.go
@@ -535,7 +535,7 @@ func inTest() bool { return flag.Lookup("test.v") != nil }
//
// maxPolls is how many network maps to download; common values are 1
// or -1 (to keep a long-poll query open to the server).
-func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*netmap.NetworkMap)) error {
+func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(netmap.NetworkMapView)) error {
return c.sendMapRequest(ctx, maxPolls, cb)
}
@@ -552,7 +552,7 @@ func (c *Direct) SendLiteMapUpdate(ctx context.Context) error {
const pollTimeout = 120 * time.Second
// cb nil means to omit peers.
-func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netmap.NetworkMap)) error {
+func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(netmap.NetworkMapView)) error {
c.mu.Lock()
persist := c.persist
serverURL := c.serverURL
@@ -822,7 +822,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
c.expiry = &nm.Expiry
c.mu.Unlock()
- cb(nm)
+ cb(nm.View())
}
if ctx.Err() != nil {
return ctx.Err()
diff --git a/control/controlclient/map.go b/control/controlclient/map.go
index 40d3109e3..f4df7609b 100644
--- a/control/controlclient/map.go
+++ b/control/controlclient/map.go
@@ -139,7 +139,7 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
}
ms.addUserProfile(nm.User)
- magicDNSSuffix := nm.MagicDNSSuffix()
+ magicDNSSuffix := nm.View().MagicDNSSuffix()
if nm.SelfNode != nil {
nm.SelfNode.InitDisplayNames(magicDNSSuffix)
}
diff --git a/control/controlclient/status.go b/control/controlclient/status.go
index 3cbe9261d..a7ffdfc68 100644
--- a/control/controlclient/status.go
+++ b/control/controlclient/status.go
@@ -68,8 +68,8 @@ type Status struct {
LoginFinished *empty.Message // nonempty when login finishes
LogoutFinished *empty.Message // nonempty when logout finishes
Err string
- URL string // interactive URL to visit to finish logging in
- NetMap *netmap.NetworkMap // server-pushed configuration
+ URL string // interactive URL to visit to finish logging in
+ NetMap netmap.NetworkMapView // server-pushed configuration
// The internal state should not be exposed outside this
// package, but we have some automated tests elsewhere that need to
diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go
index 93667d283..d0ce19dc6 100644
--- a/ipn/ipnlocal/local.go
+++ b/ipn/ipnlocal/local.go
@@ -114,9 +114,8 @@ type LocalBackend struct {
state ipn.State
capFileSharing bool // whether netMap contains the file sharing capability
// hostinfo is mutated in-place while mu is held.
- hostinfo *tailcfg.Hostinfo
- // netMap is not mutated in-place once set.
- netMap *netmap.NetworkMap
+ hostinfo *tailcfg.Hostinfo
+ netMap netmap.NetworkMapView
nodeByAddr map[netaddr.IP]*tailcfg.Node
activeLogin string // last logged LoginName from netMap
engineStatus ipn.EngineStatus
@@ -224,7 +223,7 @@ func (b *LocalBackend) maybePauseControlClientLocked() {
return
}
networkUp := b.prevIfState.AnyInterfaceUp()
- b.cc.SetPaused((b.state == ipn.Stopped && b.netMap != nil) || !networkUp)
+ b.cc.SetPaused((b.state == ipn.Stopped && b.netMap.Valid()) || !networkUp)
}
// linkChange is our link monitor callback, called whenever the network changes.
@@ -253,8 +252,8 @@ func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) {
// need updating to tweak default routes.
b.updateFilter(b.netMap, b.prefs)
- if peerAPIListenAsync && b.netMap != nil && b.state == ipn.Running {
- want := len(b.netMap.Addresses)
+ if peerAPIListenAsync && b.netMap.Valid() && b.state == ipn.Running {
+ want := len(b.netMap.Addresses())
if len(b.peerAPIListeners) < want {
b.logf("linkChange: peerAPIListeners too low; trying again")
go b.initPeerAPIListener()
@@ -343,14 +342,14 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
s.Health = append(s.Health, err.Error())
}
}
- if b.netMap != nil {
+ if b.netMap.Valid() {
s.MagicDNSSuffix = b.netMap.MagicDNSSuffix()
- s.CertDomains = append([]string(nil), b.netMap.DNS.CertDomains...)
+ s.CertDomains = append([]string(nil), b.netMap.DNS().CertDomains()...)
}
})
sb.MutateSelfStatus(func(ss *ipnstate.PeerStatus) {
- if b.netMap != nil && b.netMap.SelfNode != nil {
- ss.ID = b.netMap.SelfNode.StableID
+ if b.netMap.Valid() && b.netMap.SelfNode != nil {
+ ss.ID = b.netMap.SelfNode().StableID()
}
for _, pln := range b.peerAPIListeners {
ss.PeerAPIURL = append(ss.PeerAPIURL, pln.urlStr)
@@ -365,20 +364,21 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
}
func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
- if b.netMap == nil {
+ if !b.netMap.Valid() {
return
}
- for id, up := range b.netMap.UserProfiles {
+ for _, id := range b.netMap.UserIDs() {
+ up := b.netMap.UserProfile(id)
sb.AddUser(id, up)
}
- for _, p := range b.netMap.Peers {
+ for _, p := range b.netMap.Peers() {
var lastSeen time.Time
- if p.LastSeen != nil {
- lastSeen = *p.LastSeen
+ if p.LastSeen() != nil {
+ lastSeen = *p.LastSeen()
}
var tailAddr4 string
- var tailscaleIPs = make([]netaddr.IP, 0, len(p.Addresses))
- for _, addr := range p.Addresses {
+ var tailscaleIPs = make([]netaddr.IP, 0, len(p.Addresses()))
+ for _, addr := range p.Addresses() {
if addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.IP()) {
if addr.IP().Is4() && tailAddr4 == "" {
// The peer struct previously only allowed a single
@@ -389,20 +389,20 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
tailscaleIPs = append(tailscaleIPs, addr.IP())
}
}
- sb.AddPeer(key.Public(p.Key), &ipnstate.PeerStatus{
+ sb.AddPeer(key.Public(p.Key()), &ipnstate.PeerStatus{
InNetworkMap: true,
- ID: p.StableID,
- UserID: p.User,
+ ID: p.StableID(),
+ UserID: p.User(),
TailAddrDeprecated: tailAddr4,
TailscaleIPs: tailscaleIPs,
- HostName: p.Hostinfo.Hostname,
- DNSName: p.Name,
- OS: p.Hostinfo.OS,
- KeepAlive: p.KeepAlive,
- Created: p.Created,
+ HostName: p.Hostinfo().Hostname(),
+ DNSName: p.Name(),
+ OS: p.Hostinfo().OS(),
+ KeepAlive: p.KeepAlive(),
+ Created: p.Created(),
LastSeen: lastSeen,
- ShareeNode: p.Hostinfo.ShareeNode,
- ExitNode: p.StableID != "" && p.StableID == b.prefs.ExitNodeID,
+ ShareeNode: p.Hostinfo().ShareeNode(),
+ ExitNode: p.StableID() != "" && p.StableID() == b.prefs.ExitNodeID,
})
}
}
@@ -478,7 +478,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
// Since we're logged out now, our netmap cache is invalid.
// Since st.NetMap==nil means "netmap is unchanged", there is
// no other way to represent this change.
- b.setNetMapLocked(nil)
+ b.setNetMapLocked(netmap.NetworkMapView{})
}
prefs := b.prefs
@@ -501,7 +501,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
b.prefs.Persist = st.Persist.Clone()
}
}
- if st.NetMap != nil {
+ if st.NetMap.Valid() {
if b.findExitNodeIDLocked(st.NetMap) {
prefsChanged = true
}
@@ -537,8 +537,8 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
}
b.send(ipn.Notify{Prefs: prefs})
}
- if st.NetMap != nil {
- if netMap != nil {
+ if st.NetMap.Valid() {
+ if netMap.Valid() {
diff := st.NetMap.ConciseDiffFrom(netMap)
if strings.TrimSpace(diff) == "" {
b.logf("[v1] netmap diff: (none)")
@@ -567,7 +567,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
// findExitNodeIDLocked updates b.prefs to reference an exit node by ID,
// rather than by IP. It returns whether prefs was mutated.
-func (b *LocalBackend) findExitNodeIDLocked(nm *netmap.NetworkMap) (prefsChanged bool) {
+func (b *LocalBackend) findExitNodeIDLocked(nm netmap.NetworkMapView) (prefsChanged bool) {
// If we have a desired IP on file, try to find the corresponding
// node.
if b.prefs.ExitNodeIP.IsZero() {
@@ -580,14 +580,16 @@ func (b *LocalBackend) findExitNodeIDLocked(nm *netmap.NetworkMap) (prefsChanged
prefsChanged = true
}
- for _, peer := range nm.Peers {
- for _, addr := range peer.Addresses {
+ peers := nm.Peers()
+ for i := 0; i < peers.Len(); i++ {
+ peer := peers.At(i)
+ for _, addr := range peer.Addresses() {
if !addr.IsSingleIP() || addr.IP() != b.prefs.ExitNodeIP {
continue
}
// Found the node being referenced, upgrade prefs to
// reference it directly for next time.
- b.prefs.ExitNodeID = peer.StableID
+ b.prefs.ExitNodeID = peer.StableID()
b.prefs.ExitNodeIP = netaddr.IP{}
return true
}
@@ -922,14 +924,14 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
// updateFilter updates the packet filter in wgengine based on the
// given netMap and user preferences.
-func (b *LocalBackend) updateFilter(netMap *netmap.NetworkMap, prefs *ipn.Prefs) {
+func (b *LocalBackend) updateFilter(netMap netmap.NetworkMapView, prefs *ipn.Prefs) {
// NOTE(danderson): keep change detection as the first thing in
// this function. Don't try to optimize by returning early, more
// likely than not you'll just end up breaking the change
// detection and end up with the wrong filter installed. This is
// quite hard to debug, so save yourself the trouble.
var (
- haveNetmap = netMap != nil
+ haveNetmap = netMap.Valid()
addrs []netaddr.IPPrefix
packetFilter []filter.Match
localNetsB netaddr.IPSetBuilder
@@ -941,7 +943,7 @@ func (b *LocalBackend) updateFilter(netMap *netmap.NetworkMap, prefs *ipn.Prefs)
logNetsB.AddPrefix(tsaddr.TailscaleULARange())
logNetsB.RemovePrefix(tsaddr.ChromeOSVMRange())
if haveNetmap {
- addrs = netMap.Addresses
+ addrs = netMap.Addresses()
for _, p := range addrs {
localNetsB.AddPrefix(p)
}
@@ -2533,10 +2535,10 @@ func hasCapability(nm *netmap.NetworkMap, cap string) bool {
return false
}
-func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
+func (b *LocalBackend) setNetMapLocked(nm netmap.NetworkMapView) {
var login string
- if nm != nil {
- login = nm.UserProfiles[nm.User].LoginName
+ if nm.Valid() {
+ login = nm.UserProfile(nm.User()).LoginName
if login == "" {
login = "<missing-profile>"
}
diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go
index bef39bb2b..538a8e88e 100644
--- a/tailcfg/tailcfg.go
+++ b/tailcfg/tailcfg.go
@@ -5,6 +5,7 @@
package tailcfg
//go:generate go run tailscale.com/cmd/cloner --type=User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode --clonefunc=true --output=tailcfg_clone.go
+//go:generate go run tailscale.com/cmd/viewer --type=Node,Hostinfo,DNSConfig,NetInfo,DERPMap --output=tailcfg_view.go
import (
"encoding/hex"
diff --git a/tailcfg/tailcfg_view.go b/tailcfg/tailcfg_view.go
new file mode 100644
index 000000000..60661b607
--- /dev/null
+++ b/tailcfg/tailcfg_view.go
@@ -0,0 +1,239 @@
+// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Code generated by the following command; DO NOT EDIT.
+// tailscale.com/cmd/viewer -type Node,Hostinfo,DNSConfig,NetInfo,DERPMap
+
+package tailcfg
+
+import (
+ "inet.af/netaddr"
+ "tailscale.com/types/dnstype"
+ "tailscale.com/types/key"
+ "tailscale.com/types/opt"
+ "time"
+)
+
+// View makes a readonly view of Node.
+func (src *Node) View() NodeView {
+ return NodeView{src}
+}
+
+// NodeView is a readonly view of Node.
+type NodeView struct{ ж *Node }
+
+func (v NodeView) Valid() bool { return v.ж != nil }
+func (v NodeView) ID() NodeID { return v.ж.ID }
+func (v NodeView) StableID() StableNodeID { return v.ж.StableID }
+func (v NodeView) Name() string { return v.ж.Name }
+func (v NodeView) User() UserID { return v.ж.User }
+func (v NodeView) Sharer() UserID { return v.ж.Sharer }
+func (v NodeView) Key() NodeKey { return v.ж.Key }
+func (v NodeView) KeyExpiry() time.Time { return v.ж.KeyExpiry }
+func (v NodeView) Machine() key.MachinePublic { return v.ж.Machine }
+func (v NodeView) DiscoKey() DiscoKey { return v.ж.DiscoKey }
+func (v NodeView) Addresses() []netaddr.IPPrefix { return v.ж.Addresses }
+func (v NodeView) AllowedIPs() []netaddr.IPPrefix { return v.ж.AllowedIPs }
+func (v NodeView) Endpoints() []string { return v.ж.Endpoints }
+func (v NodeView) DERP() string { return v.ж.DERP }
+func (v NodeView) Hostinfo() HostinfoView { return v.ж.Hostinfo.View() }
+func (v NodeView) Created() time.Time { return v.ж.Created }
+func (v NodeView) PrimaryRoutes() []netaddr.IPPrefix { return v.ж.PrimaryRoutes }
+func (v NodeView) LastSeen() *time.Time {
+ ptr := v.ж.LastSeen
+ if ptr == nil {
+ return nil
+ }
+ cp := *ptr
+ return &cp
+}
+func (v NodeView) Online() *bool {
+ ptr := v.ж.Online
+ if ptr == nil {
+ return nil
+ }
+ cp := *ptr
+ return &cp
+}
+func (v NodeView) KeepAlive() bool { return v.ж.KeepAlive }
+func (v NodeView) MachineAuthorized() bool { return v.ж.MachineAuthorized }
+func (v NodeView) Capabilities() []string { return v.ж.Capabilities }
+func (v NodeView) ComputedName() string { return v.ж.ComputedName }
+func (v NodeView) computedHostIfDifferent() string { return v.ж.computedHostIfDifferent }
+func (v NodeView) ComputedNameWithHost() string { return v.ж.ComputedNameWithHost }
+
+// A compilation failure here means this code must be regenerated, with the command at the top of this file.
+var _NodeViewNeedsRegeneration = Node(struct {
+ ID NodeID
+ StableID StableNodeID
+ Name string
+ User UserID
+ Sharer UserID
+ Key NodeKey
+ KeyExpiry time.Time
+ Machine key.MachinePublic
+ DiscoKey DiscoKey
+ Addresses []netaddr.IPPrefix
+ AllowedIPs []netaddr.IPPrefix
+ Endpoints []string
+ DERP string
+ Hostinfo Hostinfo
+ Created time.Time
+ PrimaryRoutes []netaddr.IPPrefix
+ LastSeen *time.Time
+ Online *bool
+ KeepAlive bool
+ MachineAuthorized bool
+ Capabilities []string
+ ComputedName string
+ computedHostIfDifferent string
+ ComputedNameWithHost string
+}{})
+
+// View makes a readonly view of Hostinfo.
+func (src *Hostinfo) View() HostinfoView {
+ return HostinfoView{src}
+}
+
+// HostinfoView is a readonly view of Hostinfo.
+type HostinfoView struct{ ж *Hostinfo }
+
+func (v HostinfoView) Valid() bool { return v.ж != nil }
+func (v HostinfoView) IPNVersion() string { return v.ж.IPNVersion }
+func (v HostinfoView) FrontendLogID() string { return v.ж.FrontendLogID }
+func (v HostinfoView) BackendLogID() string { return v.ж.BackendLogID }
+func (v HostinfoView) OS() string { return v.ж.OS }
+func (v HostinfoView) OSVersion() string { return v.ж.OSVersion }
+func (v HostinfoView) Package() string { return v.ж.Package }
+func (v HostinfoView) DeviceModel() string { return v.ж.DeviceModel }
+func (v HostinfoView) Hostname() string { return v.ж.Hostname }
+func (v HostinfoView) ShieldsUp() bool { return v.ж.ShieldsUp }
+func (v HostinfoView) ShareeNode() bool { return v.ж.ShareeNode }
+func (v HostinfoView) GoArch() string { return v.ж.GoArch }
+func (v HostinfoView) RoutableIPs() []netaddr.IPPrefix { return v.ж.RoutableIPs }
+func (v HostinfoView) RequestTags() []string { return v.ж.RequestTags }
+func (v HostinfoView) Services() []Service { return v.ж.Services }
+func (v HostinfoView) NetInfo() NetInfoView { return v.ж.NetInfo.View() }
+
+// A compilation failure here means this code must be regenerated, with the command at the top of this file.
+var _HostinfoViewNeedsRegeneration = Hostinfo(struct {
+ IPNVersion string
+ FrontendLogID string
+ BackendLogID string
+ OS string
+ OSVersion string
+ Package string
+ DeviceModel string
+ Hostname string
+ ShieldsUp bool
+ ShareeNode bool
+ GoArch string
+ RoutableIPs []netaddr.IPPrefix
+ RequestTags []string
+ Services []Service
+ NetInfo *NetInfo
+}{})
+
+// View makes a readonly view of DNSConfig.
+func (src *DNSConfig) View() DNSConfigView {
+ return DNSConfigView{src}
+}
+
+// DNSConfigView is a readonly view of DNSConfig.
+type DNSConfigView struct{ ж *DNSConfig }
+
+func (v DNSConfigView) Valid() bool { return v.ж != nil }
+
+type _DNSConfigView_Resolvers []dnstype.Resolver
+
+func (s _DNSConfigView_Resolvers) Len() int { return len(s) }
+func (s _DNSConfigView_Resolvers) At(i int) dnstype.ResolverView { return s[i].View() }
+func (v DNSConfigView) Resolvers() interface {
+ Len() int
+ At(int) dnstype.ResolverView
+} {
+ return _DNSConfigView_Resolvers(v.ж.Resolvers)
+}
+
+type _DNSConfigView_FallbackResolvers []dnstype.Resolver
+
+func (s _DNSConfigView_FallbackResolvers) Len() int { return len(s) }
+func (s _DNSConfigView_FallbackResolvers) At(i int) dnstype.ResolverView { return s[i].View() }
+func (v DNSConfigView) FallbackResolvers() interface {
+ Len() int
+ At(int) dnstype.ResolverView
+} {
+ return _DNSConfigView_FallbackResolvers(v.ж.FallbackResolvers)
+}
+func (v DNSConfigView) Domains() []string { return v.ж.Domains }
+func (v DNSConfigView) Proxied() bool { return v.ж.Proxied }
+func (v DNSConfigView) Nameservers() []netaddr.IP { return v.ж.Nameservers }
+func (v DNSConfigView) PerDomain() bool { return v.ж.PerDomain }
+func (v DNSConfigView) CertDomains() []string { return v.ж.CertDomains }
+func (v DNSConfigView) ExtraRecords() []DNSRecord { return v.ж.ExtraRecords }
+
+// A compilation failure here means this code must be regenerated, with the command at the top of this file.
+var _DNSConfigViewNeedsRegeneration = DNSConfig(struct {
+ Resolvers []dnstype.Resolver
+ Routes map[string][]dnstype.Resolver
+ FallbackResolvers []dnstype.Resolver
+ Domains []string
+ Proxied bool
+ Nameservers []netaddr.IP
+ PerDomain bool
+ CertDomains []string
+ ExtraRecords []DNSRecord
+}{})
+
+// View makes a readonly view of NetInfo.
+func (src *NetInfo) View() NetInfoView {
+ return NetInfoView{src}
+}
+
+// NetInfoView is a readonly view of NetInfo.
+type NetInfoView struct{ ж *NetInfo }
+
+func (v NetInfoView) Valid() bool { return v.ж != nil }
+func (v NetInfoView) MappingVariesByDestIP() opt.Bool { return v.ж.MappingVariesByDestIP }
+func (v NetInfoView) HairPinning() opt.Bool { return v.ж.HairPinning }
+func (v NetInfoView) WorkingIPv6() opt.Bool { return v.ж.WorkingIPv6 }
+func (v NetInfoView) WorkingUDP() opt.Bool { return v.ж.WorkingUDP }
+func (v NetInfoView) HavePortMap() bool { return v.ж.HavePortMap }
+func (v NetInfoView) UPnP() opt.Bool { return v.ж.UPnP }
+func (v NetInfoView) PMP() opt.Bool { return v.ж.PMP }
+func (v NetInfoView) PCP() opt.Bool { return v.ж.PCP }
+func (v NetInfoView) PreferredDERP() int { return v.ж.PreferredDERP }
+func (v NetInfoView) LinkType() string { return v.ж.LinkType }
+
+// A compilation failure here means this code must be regenerated, with the command at the top of this file.
+var _NetInfoViewNeedsRegeneration = NetInfo(struct {
+ MappingVariesByDestIP opt.Bool
+ HairPinning opt.Bool
+ WorkingIPv6 opt.Bool
+ WorkingUDP opt.Bool
+ HavePortMap bool
+ UPnP opt.Bool
+ PMP opt.Bool
+ PCP opt.Bool
+ PreferredDERP int
+ LinkType string
+ DERPLatency map[string]float64
+}{})
+
+// View makes a readonly view of DERPMap.
+func (src *DERPMap) View() DERPMapView {
+ return DERPMapView{src}
+}
+
+// DERPMapView is a readonly view of DERPMap.
+type DERPMapView struct{ ж *DERPMap }
+
+func (v DERPMapView) Valid() bool { return v.ж != nil }
+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 {
+ Regions map[int]*DERPRegion
+ OmitDefaultRegions bool
+}{})
diff --git a/types/dnstype/dnstype.go b/types/dnstype/dnstype.go
index e1f9e9fdb..9bef2c039 100644
--- a/types/dnstype/dnstype.go
+++ b/types/dnstype/dnstype.go
@@ -6,6 +6,7 @@
package dnstype
//go:generate go run tailscale.com/cmd/cloner --type=Resolver --clonefunc=true --output=dnstype_clone.go
+//go:generate go run tailscale.com/cmd/viewer --type=Resolver --output=dnstype_view.go
import "inet.af/netaddr"
diff --git a/types/dnstype/dnstype_view.go b/types/dnstype/dnstype_view.go
new file mode 100644
index 000000000..fe63bb2a5
--- /dev/null
+++ b/types/dnstype/dnstype_view.go
@@ -0,0 +1,30 @@
+// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Code generated by the following command; DO NOT EDIT.
+// tailscale.com/cmd/viewer -type Resolver
+
+package dnstype
+
+import (
+ "inet.af/netaddr"
+)
+
+// View makes a readonly view of Resolver.
+func (src *Resolver) View() ResolverView {
+ return ResolverView{src}
+}
+
+// ResolverView is a readonly view of Resolver.
+type ResolverView struct{ ж *Resolver }
+
+func (v ResolverView) Valid() bool { return v.ж != nil }
+func (v ResolverView) Addr() string { return v.ж.Addr }
+func (v ResolverView) BootstrapResolution() []netaddr.IP { return v.ж.BootstrapResolution }
+
+// A compilation failure here means this code must be regenerated, with the command at the top of this file.
+var _ResolverViewNeedsRegeneration = Resolver(struct {
+ Addr string
+ BootstrapResolution []netaddr.IP
+}{})
diff --git a/types/netmap/netmap.go b/types/netmap/netmap.go
index 556ed5d06..c22683819 100644
--- a/types/netmap/netmap.go
+++ b/types/netmap/netmap.go
@@ -5,6 +5,8 @@
// Package netmap contains the netmap.NetworkMap type.
package netmap
+//go:generate go run tailscale.com/cmd/viewer --type=NetworkMap --output=netmap_view.go
+
import (
"encoding/json"
"fmt"
@@ -66,8 +68,8 @@ type NetworkMap struct {
// MagicDNS isn't necessarily in use).
//
// It will neither start nor end with a period.
-func (nm *NetworkMap) MagicDNSSuffix() string {
- name := strings.Trim(nm.Name, ".")
+func (nm NetworkMapView) MagicDNSSuffix() string {
+ name := strings.Trim(nm.Name(), ".")
if i := strings.Index(name, "."); i != -1 {
name = name[i+1:]
}
diff --git a/types/netmap/netmap_view.go b/types/netmap/netmap_view.go
new file mode 100644
index 000000000..2fb88292e
--- /dev/null
+++ b/types/netmap/netmap_view.go
@@ -0,0 +1,95 @@
+// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Code generated by the following command; DO NOT EDIT.
+// tailscale.com/cmd/viewer -type NetworkMap
+
+package netmap
+
+import (
+ "inet.af/netaddr"
+ "tailscale.com/tailcfg"
+ "tailscale.com/types/key"
+ "tailscale.com/types/wgkey"
+ "tailscale.com/wgengine/filter"
+ "time"
+)
+
+// View makes a readonly view of NetworkMap.
+func (src *NetworkMap) View() NetworkMapView {
+ return NetworkMapView{src}
+}
+
+// NetworkMapView is a readonly view of NetworkMap.
+type NetworkMapView struct{ ж *NetworkMap }
+
+func (v NetworkMapView) Valid() bool { return v.ж != nil }
+func (v NetworkMapView) SelfNode() tailcfg.NodeView { return v.ж.SelfNode.View() }
+func (v NetworkMapView) NodeKey() tailcfg.NodeKey { return v.ж.NodeKey }
+func (v NetworkMapView) PrivateKey() wgkey.Private { return v.ж.PrivateKey }
+func (v NetworkMapView) Expiry() time.Time { return v.ж.Expiry }
+func (v NetworkMapView) Name() string { return v.ж.Name }
+func (v NetworkMapView) Addresses() []netaddr.IPPrefix { return v.ж.Addresses }
+func (v NetworkMapView) LocalPort() uint16 { return v.ж.LocalPort }
+func (v NetworkMapView) MachineStatus() tailcfg.MachineStatus { return v.ж.MachineStatus }
+func (v NetworkMapView) MachineKey() key.MachinePublic { return v.ж.MachineKey }
+
+type _NetworkMapView_Peers []*tailcfg.Node
+
+func (s _NetworkMapView_Peers) Len() int { return len(s) }
+func (s _NetworkMapView_Peers) At(i int) tailcfg.NodeView { return s[i].View() }
+func (v NetworkMapView) Peers() interface {
+ Len() int
+ At(int) tailcfg.NodeView
+} {
+ return _NetworkMapView_Peers(v.ж.Peers)
+}
+func (v NetworkMapView) DNS() tailcfg.DNSConfigView { return v.ж.DNS.View() }
+func (v NetworkMapView) Hostinfo() tailcfg.HostinfoView { return v.ж.Hostinfo.View() }
+
+type _NetworkMapView_PacketFilter []filter.Match
+
+func (s _NetworkMapView_PacketFilter) Len() int { return len(s) }
+func (s _NetworkMapView_PacketFilter) At(i int) filter.MatchView { return s[i].View() }
+func (v NetworkMapView) PacketFilter() interface {
+ Len() int
+ At(int) filter.MatchView
+} {
+ return _NetworkMapView_PacketFilter(v.ж.PacketFilter)
+}
+func (v NetworkMapView) CollectServices() bool { return v.ж.CollectServices }
+func (v NetworkMapView) DERPMap() tailcfg.DERPMapView { return v.ж.DERPMap.View() }
+func (v NetworkMapView) Debug() *tailcfg.Debug {
+ ptr := v.ж.Debug
+ if ptr == nil {
+ return nil
+ }
+ cp := *ptr
+ return &cp
+}
+func (v NetworkMapView) User() tailcfg.UserID { return v.ж.User }
+func (v NetworkMapView) Domain() string { return v.ж.Domain }
+
+// A compilation failure here means this code must be regenerated, with the command at the top of this file.
+var _NetworkMapViewNeedsRegeneration = NetworkMap(struct {
+ SelfNode *tailcfg.Node
+ NodeKey tailcfg.NodeKey
+ PrivateKey wgkey.Private
+ Expiry time.Time
+ Name string
+ Addresses []netaddr.IPPrefix
+ LocalPort uint16
+ MachineStatus tailcfg.MachineStatus
+ MachineKey key.MachinePublic
+ Peers []*tailcfg.Node
+ DNS tailcfg.DNSConfig
+ Hostinfo tailcfg.Hostinfo
+ PacketFilter []filter.Match
+ CollectServices bool
+ DERPMap *tailcfg.DERPMap
+ Debug *tailcfg.Debug
+ User tailcfg.UserID
+ Domain string
+ UserProfiles map[tailcfg.UserID]tailcfg.UserProfile
+}{})
diff --git a/wgengine/filter/match.go b/wgengine/filter/match.go
index 74807dda7..2115be452 100644
--- a/wgengine/filter/match.go
+++ b/wgengine/filter/match.go
@@ -14,6 +14,7 @@ import (
)
//go:generate go run tailscale.com/cmd/cloner --type=Match --output=match_clone.go
+//go:generate go run tailscale.com/cmd/viewer --type=Match --output=match_view.go
// PortRange is a range of TCP and UDP ports.
type PortRange struct {
diff --git a/wgengine/filter/match_view.go b/wgengine/filter/match_view.go
new file mode 100644
index 000000000..c3c267a6e
--- /dev/null
+++ b/wgengine/filter/match_view.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Code generated by the following command; DO NOT EDIT.
+// tailscale.com/cmd/viewer -type Match
+
+package filter
+
+import (
+ "inet.af/netaddr"
+ "tailscale.com/types/ipproto"
+)
+
+// View makes a readonly view of Match.
+func (src *Match) View() MatchView {
+ return MatchView{src}
+}
+
+// MatchView is a readonly view of Match.
+type MatchView struct{ ж *Match }
+
+func (v MatchView) Valid() bool { return v.ж != nil }
+func (v MatchView) IPProto() []ipproto.Proto { return v.ж.IPProto }
+func (v MatchView) Dsts() []NetPortRange { return v.ж.Dsts }
+func (v MatchView) Srcs() []netaddr.IPPrefix { return v.ж.Srcs }
+
+// A compilation failure here means this code must be regenerated, with the command at the top of this file.
+var _MatchViewNeedsRegeneration = Match(struct {
+ IPProto []ipproto.Proto
+ Dsts []NetPortRange
+ Srcs []netaddr.IPPrefix
+}{})
diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go
index 791e030d0..f0bb7f6ff 100644
--- a/wgengine/netstack/netstack.go
+++ b/wgengine/netstack/netstack.go
@@ -176,7 +176,7 @@ type DNSMap map[string]netaddr.IP
func DNSMapFromNetworkMap(nm *netmap.NetworkMap) DNSMap {
ret := make(DNSMap)
- suffix := nm.MagicDNSSuffix()
+ suffix := nm.View().MagicDNSSuffix()
have4 := false
if nm.Name != "" && len(nm.Addresses) > 0 {
ip := nm.Addresses[0].IP()