mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-31 16:23:44 +00:00
lanscaping: remove cloudenv, more WoL, syspolicy, hostinfo distro type etc
-rwxr-xr-x@ 1 bradfitz staff 10092370 Jan 11 11:35 /Users/bradfitz/bin/tailscaled.min -rwxr-xr-x@ 1 bradfitz staff 10092696 Jan 11 11:35 /Users/bradfitz/bin/tailscaled.minlinux Change-Id: I064895895043239d4e2ed995ce40be786bdbc11c Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
9abc1629fa
commit
718e30c4b9
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -19,9 +19,6 @@ import (
|
||||
func init() {
|
||||
osVersion = lazyOSVersion.Get
|
||||
packageType = packageTypeLinux
|
||||
distroName = distroNameLinux
|
||||
distroVersion = distroVersionLinux
|
||||
distroCodeName = distroCodeNameLinux
|
||||
deviceModel = deviceModelLinux
|
||||
}
|
||||
|
||||
|
102
hostinfo/wol.go
102
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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user