diff --git a/cmd/tailscale/depaware-minlinux.txt b/cmd/tailscale/depaware-minlinux.txt index 6fb6710fd..77993b7f0 100644 --- a/cmd/tailscale/depaware-minlinux.txt +++ b/cmd/tailscale/depaware-minlinux.txt @@ -80,7 +80,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/types/empty from tailscale.com/ipn tailscale.com/types/ipproto from tailscale.com/ipn+ tailscale.com/types/key from tailscale.com/client/tailscale+ - tailscale.com/types/lazy from tailscale.com/util/cloudenv+ + tailscale.com/types/lazy from tailscale.com/version+ tailscale.com/types/logger from tailscale.com/client/web+ tailscale.com/types/netmap from tailscale.com/cmd/tailscale/cli+ tailscale.com/types/nettype from tailscale.com/net/netcheck+ @@ -92,7 +92,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/types/structs from tailscale.com/ipn+ tailscale.com/types/views from tailscale.com/client/web+ tailscale.com/util/clientmetric from tailscale.com/net/netcheck+ - tailscale.com/util/cloudenv from tailscale.com/hostinfo tailscale.com/util/cmpver from tailscale.com/clientupdate tailscale.com/util/ctxkey from tailscale.com/types/logger L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics diff --git a/cmd/tailscaled/depaware-minlinux.txt b/cmd/tailscaled/depaware-minlinux.txt index 0e0acdb5f..820d27f20 100644 --- a/cmd/tailscaled/depaware-minlinux.txt +++ b/cmd/tailscaled/depaware-minlinux.txt @@ -77,7 +77,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled tailscale.com/types/ipproto from tailscale.com/ipn+ tailscale.com/types/key from tailscale.com/control/controlbase+ - tailscale.com/types/lazy from tailscale.com/util/cloudenv+ + tailscale.com/types/lazy from tailscale.com/version+ tailscale.com/types/logger from tailscale.com/cmd/tailscaled+ tailscale.com/types/logid from tailscale.com/ipn/ipnlocal+ tailscale.com/types/netmap from tailscale.com/control/controlclient+ @@ -90,7 +90,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/types/structs from tailscale.com/control/controlclient+ tailscale.com/types/views from tailscale.com/control/controlclient+ tailscale.com/util/clientmetric from tailscale.com/control/controlclient+ - tailscale.com/util/cloudenv from tailscale.com/hostinfo+ tailscale.com/util/ctxkey from tailscale.com/derp+ 💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+ L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics diff --git a/hostinfo/hostinfo.go b/hostinfo/hostinfo.go index 89968e1e6..dcbecab69 100644 --- a/hostinfo/hostinfo.go +++ b/hostinfo/hostinfo.go @@ -23,7 +23,6 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/opt" "tailscale.com/types/ptr" - "tailscale.com/util/cloudenv" "tailscale.com/util/dnsname" "tailscale.com/util/lineiter" "tailscale.com/version" @@ -43,33 +42,24 @@ func New() *tailcfg.Hostinfo { OS: version.OS(), OSVersion: GetOSVersion(), Container: lazyInContainer.Get(), - Distro: condCall(distroName), - DistroVersion: condCall(distroVersion), - DistroCodeName: condCall(distroCodeName), Env: string(GetEnvType()), Desktop: desktop(), - Package: packageTypeCached(), GoArch: runtime.GOARCH, GoArchVar: lazyGoArchVar.Get(), GoVersion: runtime.Version(), Machine: condCall(unameMachine), DeviceModel: deviceModelCached(), - Cloud: string(cloudenv.Get()), NoLogsNoSupport: envknob.NoLogsNoSupport(), AllowsUpdate: envknob.AllowsRemoteUpdate(), - WoLMACs: getWoLMACs(), } } // non-nil on some platforms var ( - osVersion func() string - packageType func() string - distroName func() string - distroVersion func() string - distroCodeName func() string - unameMachine func() string - deviceModel func() string + osVersion func() string + packageType func() string + unameMachine func() string + deviceModel func() string ) func condCall[T any](fn func() T) T { diff --git a/hostinfo/hostinfo_linux.go b/hostinfo/hostinfo_linux.go index 66484a358..f709b3a51 100644 --- a/hostinfo/hostinfo_linux.go +++ b/hostinfo/hostinfo_linux.go @@ -19,9 +19,6 @@ import ( func init() { osVersion = lazyOSVersion.Get packageType = packageTypeLinux - distroName = distroNameLinux - distroVersion = distroVersionLinux - distroCodeName = distroCodeNameLinux deviceModel = deviceModelLinux } diff --git a/hostinfo/wol.go b/hostinfo/wol.go index 3a30af2fe..ad5a42314 100644 --- a/hostinfo/wol.go +++ b/hostinfo/wol.go @@ -2,105 +2,3 @@ // SPDX-License-Identifier: BSD-3-Clause package hostinfo - -import ( - "log" - "net" - "runtime" - "strings" - "unicode" - - "tailscale.com/envknob" -) - -// TODO(bradfitz): this is all too simplistic and static. It needs to run -// continuously in response to netmon events (USB ethernet adapaters might get -// plugged in) and look for the media type/status/etc. Right now on macOS it -// still detects a half dozen "up" en0, en1, en2, en3 etc interfaces that don't -// have any media. We should only report the one that's actually connected. -// But it works for now (2023-10-05) for fleshing out the rest. - -var wakeMAC = envknob.RegisterString("TS_WAKE_MAC") // mac address, "false" or "auto". for https://github.com/tailscale/tailscale/issues/306 - -// getWoLMACs returns up to 10 MAC address of the local machine to send -// wake-on-LAN packets to in order to wake it up. The returned MACs are in -// lowercase hex colon-separated form ("xx:xx:xx:xx:xx:xx"). -// -// If TS_WAKE_MAC=auto, it tries to automatically find the MACs based on the OS -// type and interface properties. (TODO(bradfitz): incomplete) If TS_WAKE_MAC is -// set to a MAC address, that sole MAC address is returned. -func getWoLMACs() (macs []string) { - switch runtime.GOOS { - case "ios", "android": - return nil - } - if s := wakeMAC(); s != "" { - switch s { - case "auto": - ifs, _ := net.Interfaces() - for _, iface := range ifs { - if iface.Flags&net.FlagLoopback != 0 { - continue - } - if iface.Flags&net.FlagBroadcast == 0 || - iface.Flags&net.FlagRunning == 0 || - iface.Flags&net.FlagUp == 0 { - continue - } - if keepMAC(iface.Name, iface.HardwareAddr) { - macs = append(macs, iface.HardwareAddr.String()) - } - if len(macs) == 10 { - break - } - } - return macs - case "false", "off": // fast path before ParseMAC error - return nil - } - mac, err := net.ParseMAC(s) - if err != nil { - log.Printf("invalid MAC %q", s) - return nil - } - return []string{mac.String()} - } - return nil -} - -var ignoreWakeOUI = map[[3]byte]bool{ - {0x00, 0x15, 0x5d}: true, // Hyper-V - {0x00, 0x50, 0x56}: true, // VMware - {0x00, 0x1c, 0x14}: true, // VMware - {0x00, 0x05, 0x69}: true, // VMware - {0x00, 0x0c, 0x29}: true, // VMware - {0x00, 0x1c, 0x42}: true, // Parallels - {0x08, 0x00, 0x27}: true, // VirtualBox - {0x00, 0x21, 0xf6}: true, // VirtualBox - {0x00, 0x14, 0x4f}: true, // VirtualBox - {0x00, 0x0f, 0x4b}: true, // VirtualBox - {0x52, 0x54, 0x00}: true, // VirtualBox/Vagrant -} - -func keepMAC(ifName string, mac []byte) bool { - if len(mac) != 6 { - return false - } - base := strings.TrimRightFunc(ifName, unicode.IsNumber) - switch runtime.GOOS { - case "darwin": - switch base { - case "llw", "awdl", "utun", "bridge", "lo", "gif", "stf", "anpi", "ap": - return false - } - } - if mac[0] == 0x02 && mac[1] == 0x42 { - // Docker container. - return false - } - oui := [3]byte{mac[0], mac[1], mac[2]} - if ignoreWakeOUI[oui] { - return false - } - return true -} diff --git a/util/syspolicy/setting/policy_scope.go b/util/syspolicy/setting/policy_scope.go index 21d9c67c1..a28017297 100644 --- a/util/syspolicy/setting/policy_scope.go +++ b/util/syspolicy/setting/policy_scope.go @@ -3,16 +3,7 @@ package setting -import ( - "fmt" - "strings" - - "tailscale.com/types/lazy" -) - var ( - lazyDefaultScope lazy.SyncValue[PolicyScope] - // DeviceScope indicates a scope containing device-global policies. DeviceScope = PolicyScope{kind: DeviceSetting} // CurrentProfileScope indicates a scope containing policies that apply to the @@ -30,153 +21,3 @@ type PolicyScope struct { userID string profileID string } - -// SetDefaultScope attempts to set the specified scope as the default scope -// to be used by a program when querying policy settings. -// It fails and returns false if called more than once, or if the [DefaultScope] -// has already been used. -func SetDefaultScope(scope PolicyScope) bool { - return lazyDefaultScope.Set(scope) -} - -// UserScopeOf returns a policy [PolicyScope] of the user with the specified id. -func UserScopeOf(uid string) PolicyScope { - return PolicyScope{kind: UserSetting, userID: uid} -} - -// Kind reports the scope kind of s. -func (s PolicyScope) Kind() Scope { - return s.kind -} - -// IsApplicableSetting reports whether the specified setting applies to -// and can be retrieved for this scope. Policy settings are applicable -// to their own scopes as well as more specific scopes. For example, -// device settings are applicable to device, profile and user scopes, -// but user settings are only applicable to user scopes. -// For instance, a menu visibility setting is inherently a user setting -// and only makes sense in the context of a specific user. -func (s PolicyScope) IsApplicableSetting(setting *Definition) bool { - return setting != nil && setting.Scope() <= s.Kind() -} - -// IsConfigurableSetting reports whether the specified setting can be configured -// by a policy at this scope. Policy settings are configurable at their own scopes -// as well as broader scopes. For example, [UserSetting]s are configurable in -// user, profile, and device scopes, but [DeviceSetting]s are only configurable -// in the [DeviceScope]. For instance, the InstallUpdates policy setting -// can only be configured in the device scope, as it controls whether updates -// will be installed automatically on the device, rather than for specific users. -func (s PolicyScope) IsConfigurableSetting(setting *Definition) bool { - return setting != nil && setting.Scope() >= s.Kind() -} - -// Contains reports whether policy settings that apply to s also apply to s2. -// For example, policy settings that apply to the [DeviceScope] also apply to -// the [CurrentUserScope]. -func (s PolicyScope) Contains(s2 PolicyScope) bool { - if s.Kind() > s2.Kind() { - return false - } - switch s.Kind() { - case DeviceSetting: - return true - case ProfileSetting: - return s.profileID == s2.profileID - case UserSetting: - return s.userID == s2.userID - default: - panic("unreachable") - } -} - -// StrictlyContains is like [PolicyScope.Contains], but returns false -// when s and s2 is the same scope. -func (s PolicyScope) StrictlyContains(s2 PolicyScope) bool { - return s != s2 && s.Contains(s2) -} - -// String implements [fmt.Stringer]. -func (s PolicyScope) String() string { - if s.profileID == "" && s.userID == "" { - return s.kind.String() - } - return s.stringSlow() -} - -// MarshalText implements [encoding.TextMarshaler]. -func (s PolicyScope) MarshalText() ([]byte, error) { - return []byte(s.String()), nil -} - -// MarshalText implements [encoding.TextUnmarshaler]. -func (s *PolicyScope) UnmarshalText(b []byte) error { - *s = PolicyScope{} - parts := strings.SplitN(string(b), "/", 2) - for i, part := range parts { - kind, id, err := parseScopeAndID(part) - if err != nil { - return err - } - if i > 0 && kind <= s.kind { - return fmt.Errorf("invalid scope hierarchy: %s", b) - } - s.kind = kind - switch kind { - case DeviceSetting: - if id != "" { - return fmt.Errorf("the device scope must not have an ID: %s", b) - } - case ProfileSetting: - s.profileID = id - case UserSetting: - s.userID = id - } - } - return nil -} - -func (s PolicyScope) stringSlow() string { - var sb strings.Builder - writeScopeWithID := func(s Scope, id string) { - sb.WriteString(s.String()) - if id != "" { - sb.WriteRune('(') - sb.WriteString(id) - sb.WriteRune(')') - } - } - if s.kind == ProfileSetting || s.profileID != "" { - writeScopeWithID(ProfileSetting, s.profileID) - if s.kind != ProfileSetting { - sb.WriteRune('/') - } - } - if s.kind == UserSetting { - writeScopeWithID(UserSetting, s.userID) - } - return sb.String() -} - -func parseScopeAndID(s string) (scope Scope, id string, err error) { - name, params, ok := extractScopeAndParams(s) - if !ok { - return 0, "", fmt.Errorf("%q is not a valid scope string", s) - } - if err := scope.UnmarshalText([]byte(name)); err != nil { - return 0, "", err - } - return scope, params, nil -} - -func extractScopeAndParams(s string) (name, params string, ok bool) { - paramsStart := strings.Index(s, "(") - if paramsStart == -1 { - return s, "", true - } - paramsEnd := strings.LastIndex(s, ")") - if paramsEnd < paramsStart { - return "", "", false - } - return s[0:paramsStart], s[paramsStart+1 : paramsEnd], true -} diff --git a/util/syspolicy/setting/setting.go b/util/syspolicy/setting/setting.go index 5d784ae28..2b1536d45 100644 --- a/util/syspolicy/setting/setting.go +++ b/util/syspolicy/setting/setting.go @@ -9,12 +9,7 @@ package setting import ( "fmt" - "slices" "strings" - "sync" - "time" - - "tailscale.com/types/lazy" ) // Scope indicates the broadest scope at which a policy setting may apply, @@ -124,117 +119,3 @@ func (t Type) String() string { panic("unreachable") } } - -// ValueType is a constraint that allows Go types corresponding to [Type]. -type ValueType interface { - bool | uint64 | string | []string | Visibility | PreferenceOption | time.Duration -} - -// Definition defines policy key, scope and value type. -type Definition struct { - key Key - scope Scope - typ Type - platforms PlatformList -} - -// NewDefinition returns a new [Definition] with the specified -// key, scope, type and supported platforms (see [PlatformList]). -func NewDefinition(k Key, s Scope, t Type, platforms ...string) *Definition { - return &Definition{key: k, scope: s, typ: t, platforms: platforms} -} - -// Key returns a policy setting's identifier. -func (d *Definition) Key() Key { - if d == nil { - return "" - } - return d.key -} - -// Scope reports the broadest [Scope] the policy setting may apply to. -func (d *Definition) Scope() Scope { - if d == nil { - return 0 - } - return d.scope -} - -// Type reports the underlying value type of the policy setting. -func (d *Definition) Type() Type { - if d == nil { - return InvalidValue - } - return d.typ -} - -// SupportedPlatforms reports platforms on which the policy setting is supported. -// An empty [PlatformList] indicates that s is available on all platforms. -func (d *Definition) SupportedPlatforms() PlatformList { - if d == nil { - return nil - } - return d.platforms -} - -// String implements [fmt.Stringer]. -func (d *Definition) String() string { - if d == nil { - return "(nil)" - } - return fmt.Sprintf("%v(%q, %v)", d.scope, d.key, d.typ) -} - -// Equal reports whether d and d2 have the same key, type and scope. -// It does not check whether both s and s2 are supported on the same platforms. -func (d *Definition) Equal(d2 *Definition) bool { - if d == d2 { - return true - } - if d == nil || d2 == nil { - return false - } - return d.key == d2.key && d.typ == d2.typ && d.scope == d2.scope -} - -// DefinitionMap is a map of setting [Definition] by [Key]. -type DefinitionMap map[Key]*Definition - -var ( - definitions lazy.SyncValue[DefinitionMap] - - definitionsMu sync.Mutex - definitionsList []*Definition - definitionsUsed bool -) - -// PlatformList is a list of OSes. -// An empty list indicates that all possible platforms are supported. -type PlatformList []string - -// Has reports whether l contains the target platform. -func (l PlatformList) Has(target string) bool { - if len(l) == 0 { - return true - } - return slices.ContainsFunc(l, func(os string) bool { - return strings.EqualFold(os, target) - }) -} - -// mergeFrom merges l2 into l. Since an empty list indicates no platform restrictions, -// if either l or l2 is empty, the merged result in l will also be empty. -func (l *PlatformList) mergeFrom(l2 PlatformList) { - switch { - case len(*l) == 0: - // No-op. An empty list indicates no platform restrictions. - case len(l2) == 0: - // Merging with an empty list results in an empty list. - *l = l2 - default: - // Append, sort and dedup. - *l = append(*l, l2...) - slices.Sort(*l) - *l = slices.Compact(*l) - } -} diff --git a/util/testenv/testenv.go b/util/testenv/testenv.go index 12ada9003..45f8ea4f5 100644 --- a/util/testenv/testenv.go +++ b/util/testenv/testenv.go @@ -5,17 +5,7 @@ // the `testing` package to allow usage in non-test code. package testenv -import ( - "flag" - - "tailscale.com/types/lazy" -) - -var lazyInTest lazy.SyncValue[bool] - // InTest reports whether the current binary is a test binary. func InTest() bool { - return lazyInTest.Get(func() bool { - return flag.Lookup("test.v") != nil - }) + return false } diff --git a/wgengine/magicsock/cloudinfo.go b/wgengine/magicsock/cloudinfo.go deleted file mode 100644 index 1de369631..000000000 --- a/wgengine/magicsock/cloudinfo.go +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !(ios || android || js) - -package magicsock - -import ( - "context" - "errors" - "fmt" - "io" - "net" - "net/http" - "net/netip" - "slices" - "strings" - "time" - - "tailscale.com/types/logger" - "tailscale.com/util/cloudenv" -) - -const maxCloudInfoWait = 2 * time.Second - -type cloudInfo struct { - client http.Client - logf logger.Logf - - // The following parameters are fixed for the lifetime of the cloudInfo - // object, but are used for testing. - cloud cloudenv.Cloud - endpoint string -} - -func newCloudInfo(logf logger.Logf) *cloudInfo { - tr := &http.Transport{ - DisableKeepAlives: true, - Dial: (&net.Dialer{ - Timeout: maxCloudInfoWait, - }).Dial, - } - - return &cloudInfo{ - client: http.Client{Transport: tr}, - logf: logf, - cloud: cloudenv.Get(), - endpoint: "http://" + cloudenv.CommonNonRoutableMetadataIP, - } -} - -// GetPublicIPs returns any public IPs attached to the current cloud instance, -// if the tailscaled process is running in a known cloud and there are any such -// IPs present. -func (ci *cloudInfo) GetPublicIPs(ctx context.Context) ([]netip.Addr, error) { - switch ci.cloud { - case cloudenv.AWS: - ret, err := ci.getAWS(ctx) - ci.logf("[v1] cloudinfo.GetPublicIPs: AWS: %v, %v", ret, err) - return ret, err - } - - return nil, nil -} - -// getAWSMetadata makes a request to the AWS metadata service at the given -// path, authenticating with the provided IMDSv2 token. The returned metadata -// is split by newline and returned as a slice. -func (ci *cloudInfo) getAWSMetadata(ctx context.Context, token, path string) ([]string, error) { - req, err := http.NewRequestWithContext(ctx, "GET", ci.endpoint+path, nil) - if err != nil { - return nil, fmt.Errorf("creating request to %q: %w", path, err) - } - req.Header.Set("X-aws-ec2-metadata-token", token) - - resp, err := ci.client.Do(req) - if err != nil { - return nil, fmt.Errorf("making request to metadata service %q: %w", path, err) - } - defer resp.Body.Close() - - switch resp.StatusCode { - case http.StatusOK: - // Good - case http.StatusNotFound: - // Nothing found, but this isn't an error; just return - return nil, nil - default: - return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("reading response body for %q: %w", path, err) - } - - return strings.Split(strings.TrimSpace(string(body)), "\n"), nil -} - -// getAWS returns all public IPv4 and IPv6 addresses present in the AWS instance metadata. -func (ci *cloudInfo) getAWS(ctx context.Context) ([]netip.Addr, error) { - ctx, cancel := context.WithTimeout(ctx, maxCloudInfoWait) - defer cancel() - - // Get a token so we can query the metadata service. - req, err := http.NewRequestWithContext(ctx, "PUT", ci.endpoint+"/latest/api/token", nil) - if err != nil { - return nil, fmt.Errorf("creating token request: %w", err) - } - req.Header.Set("X-Aws-Ec2-Metadata-Token-Ttl-Seconds", "10") - - resp, err := ci.client.Do(req) - if err != nil { - return nil, fmt.Errorf("making token request to metadata service: %w", err) - } - body, err := io.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - return nil, fmt.Errorf("reading token response body: %w", err) - } - token := string(body) - - server := resp.Header.Get("Server") - if server != "EC2ws" { - return nil, fmt.Errorf("unexpected server header: %q", server) - } - - // Iterate over all interfaces and get their public IP addresses, both IPv4 and IPv6. - macAddrs, err := ci.getAWSMetadata(ctx, token, "/latest/meta-data/network/interfaces/macs/") - if err != nil { - return nil, fmt.Errorf("getting interface MAC addresses: %w", err) - } - - var ( - addrs []netip.Addr - errs []error - ) - - addAddr := func(addr string) { - ip, err := netip.ParseAddr(addr) - if err != nil { - errs = append(errs, fmt.Errorf("parsing IP address %q: %w", addr, err)) - return - } - addrs = append(addrs, ip) - } - for _, mac := range macAddrs { - ips, err := ci.getAWSMetadata(ctx, token, "/latest/meta-data/network/interfaces/macs/"+mac+"/public-ipv4s") - if err != nil { - errs = append(errs, fmt.Errorf("getting IPv4 addresses for %q: %w", mac, err)) - continue - } - - for _, ip := range ips { - addAddr(ip) - } - - // Try querying for IPv6 addresses. - ips, err = ci.getAWSMetadata(ctx, token, "/latest/meta-data/network/interfaces/macs/"+mac+"/ipv6s") - if err != nil { - errs = append(errs, fmt.Errorf("getting IPv6 addresses for %q: %w", mac, err)) - continue - } - for _, ip := range ips { - addAddr(ip) - } - } - - // Sort the returned addresses for determinism. - slices.SortFunc(addrs, func(a, b netip.Addr) int { - return a.Compare(b) - }) - - // Preferentially return any addresses we found, even if there were errors. - if len(addrs) > 0 { - return addrs, nil - } - if len(errs) > 0 { - return nil, fmt.Errorf("getting IP addresses: %w", errors.Join(errs...)) - } - return nil, nil -} diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 4ece00b66..0bae8ef1b 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -175,9 +175,6 @@ type Conn struct { // bind is the wireguard-go conn.Bind for Conn. bind *connBind - // cloudInfo is used to query cloud metadata services. - cloudInfo *cloudInfo - // ============================================================ // Fields that must be accessed via atomic load/stores. @@ -481,7 +478,6 @@ func newConn(logf logger.Logf) *Conn { discoInfo: make(map[key.DiscoPublic]*discoInfo), discoPrivate: discoPrivate, discoPublic: discoPrivate.Public(), - cloudInfo: newCloudInfo(logf), } c.discoShort = c.discoPublic.ShortString() c.bind = &connBind{Conn: c, closed: true} @@ -1017,27 +1013,6 @@ func (c *Conn) determineEndpoints(ctx context.Context) ([]tailcfg.Endpoint, erro addAddr(ap, tailcfg.EndpointExplicitConf) } - // If we're on a cloud instance, we might have a public IPv4 or IPv6 - // address that we can be reached at. Find those, if they exist, and - // add them. - if addrs, err := c.cloudInfo.GetPublicIPs(ctx); err == nil { - var port4, port6 uint16 - if addr := c.pconn4.LocalAddr(); addr != nil { - port4 = uint16(addr.Port) - } - if addr := c.pconn6.LocalAddr(); addr != nil { - port6 = uint16(addr.Port) - } - - for _, addr := range addrs { - if addr.Is4() && port4 > 0 { - addAddr(netip.AddrPortFrom(addr, port4), tailcfg.EndpointLocal) - } else if addr.Is6() && port6 > 0 { - addAddr(netip.AddrPortFrom(addr, port6), tailcfg.EndpointLocal) - } - } - } - // Update our set of endpoints by adding any endpoints that we // previously found but haven't expired yet. This also updates the // cache with the set of endpoints discovered in this function.