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 = "" } 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()