Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3020e58f57 | ||
|
|
f3be05e6ea | ||
|
|
6b5081ab31 | ||
|
|
7afb4a1f43 | ||
|
|
4bc90fee03 | ||
|
|
46e42292a5 | ||
|
|
a5b1456410 | ||
|
|
27d0e7cb0a | ||
|
|
f0b70ff186 | ||
|
|
f5d17dae18 | ||
|
|
ceaecdd4d5 | ||
|
|
8704fb308d | ||
|
|
afb95d7246 | ||
|
|
277bf8f48c | ||
|
|
c995ac72a3 | ||
|
|
e699226e80 | ||
|
|
d8e37edb40 | ||
|
|
0744d75238 | ||
|
|
15835f03b3 | ||
|
|
e78ac523da | ||
|
|
15c87017b8 | ||
|
|
bd911fdb12 | ||
|
|
0111d33eb8 | ||
|
|
62a458f7f4 |
@@ -1 +1 @@
|
||||
1.13.0
|
||||
1.14.4
|
||||
|
||||
@@ -4,7 +4,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
||||
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
|
||||
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli
|
||||
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli+
|
||||
github.com/peterbourgon/ff/v2 from github.com/peterbourgon/ff/v2/ffcli
|
||||
github.com/peterbourgon/ff/v2/ffcli from tailscale.com/cmd/tailscale/cli
|
||||
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+
|
||||
@@ -43,7 +43,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/net/tlsdial from tailscale.com/derp/derphttp
|
||||
tailscale.com/net/tsaddr from tailscale.com/net/interfaces+
|
||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
|
||||
tailscale.com/paths from tailscale.com/cmd/tailscale/cli+
|
||||
💣 tailscale.com/paths from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli+
|
||||
💣 tailscale.com/syncs from tailscale.com/net/interfaces+
|
||||
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
|
||||
|
||||
@@ -25,6 +25,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L 💣 github.com/mdlayher/netlink/nlenc from github.com/mdlayher/netlink+
|
||||
L github.com/mdlayher/sdnotify from tailscale.com/util/systemd
|
||||
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
||||
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
|
||||
W github.com/pkg/errors from github.com/tailscale/certstore
|
||||
W 💣 github.com/tailscale/certstore from tailscale.com/control/controlclient
|
||||
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+
|
||||
@@ -128,7 +129,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/net/tsaddr from tailscale.com/ipn/ipnlocal+
|
||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+
|
||||
tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/paths from tailscale.com/cmd/tailscaled+
|
||||
💣 tailscale.com/paths from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/portlist from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/safesocket from tailscale.com/ipn/ipnserver+
|
||||
tailscale.com/smallzstd from tailscale.com/ipn/ipnserver+
|
||||
|
||||
@@ -1837,6 +1837,17 @@ func (b *LocalBackend) authReconfig() {
|
||||
if err != nil {
|
||||
b.logf("[unexpected] non-FQDN route suffix %q", suffix)
|
||||
}
|
||||
|
||||
// Create map entry even if len(resolvers) == 0; Issue 2706.
|
||||
// This lets the control plane send ExtraRecords for which we
|
||||
// can authoritatively answer "name not exists" for when the
|
||||
// control plane also sends this explicit but empty route
|
||||
// making it as something we handle.
|
||||
//
|
||||
// While we're already populating it, might as well size the
|
||||
// slice appropriately.
|
||||
dcfg.Routes[fqdn] = make([]netaddr.IPPort, 0, len(resolvers))
|
||||
|
||||
for _, resolver := range resolvers {
|
||||
res, err := parseResolver(resolver)
|
||||
if err != nil {
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -37,6 +38,7 @@ import (
|
||||
"tailscale.com/log/filelogger"
|
||||
"tailscale.com/logtail/backoff"
|
||||
"tailscale.com/net/netstat"
|
||||
"tailscale.com/paths"
|
||||
"tailscale.com/safesocket"
|
||||
"tailscale.com/smallzstd"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -581,6 +583,28 @@ func (s *server) writeToClients(n ipn.Notify) {
|
||||
}
|
||||
}
|
||||
|
||||
// tryWindowsAppDataMigration attempts to copy the Windows state file
|
||||
// from its old location to the new location. (Issue 2856)
|
||||
//
|
||||
// Tailscale 1.14 and before stored state under %LocalAppData%
|
||||
// (usually "C:\WINDOWS\system32\config\systemprofile\AppData\Local"
|
||||
// when tailscaled.exe is running as a non-user system service).
|
||||
// However it is frequently cleared for almost any reason: Windows
|
||||
// updates, System Restore, even various System Cleaner utilities.
|
||||
//
|
||||
// Returns a string of the path to use for the state file.
|
||||
// This will be a fallback %LocalAppData% path if migration fails,
|
||||
// a %ProgramData% path otherwise.
|
||||
func tryWindowsAppDataMigration(logf logger.Logf, path string) string {
|
||||
if path != paths.DefaultTailscaledStateFile() {
|
||||
// If they're specifying a non-default path, just trust that they know
|
||||
// what they are doing.
|
||||
return path
|
||||
}
|
||||
oldFile := filepath.Join(os.Getenv("LocalAppData"), "Tailscale", "server-state.conf")
|
||||
return paths.TryConfigFileMigration(logf, oldFile, path)
|
||||
}
|
||||
|
||||
// Run runs a Tailscale backend service.
|
||||
// The getEngine func is called repeatedly, once per connection, until it returns an engine successfully.
|
||||
func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (wgengine.Engine, error), opts Options) error {
|
||||
@@ -613,14 +637,18 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
|
||||
|
||||
var store ipn.StateStore
|
||||
if opts.StatePath != "" {
|
||||
store, err = ipn.NewFileStore(opts.StatePath)
|
||||
path := opts.StatePath
|
||||
if runtime.GOOS == "windows" {
|
||||
path = tryWindowsAppDataMigration(logf, path)
|
||||
}
|
||||
store, err = ipn.NewFileStore(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ipn.NewFileStore(%q): %v", opts.StatePath, err)
|
||||
return fmt.Errorf("ipn.NewFileStore(%q): %v", path, err)
|
||||
}
|
||||
if opts.AutostartStateKey == "" {
|
||||
autoStartKey, err := store.ReadState(ipn.ServerModeStartKey)
|
||||
if err != nil && err != ipn.ErrStateNotExist {
|
||||
return fmt.Errorf("calling ReadState on %s: %w", opts.StatePath, err)
|
||||
return fmt.Errorf("calling ReadState on %s: %w", path, err)
|
||||
}
|
||||
key := string(autoStartKey)
|
||||
if strings.HasPrefix(key, "user-") {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"tailscale.com/atomicfile"
|
||||
"tailscale.com/paths"
|
||||
)
|
||||
|
||||
// ErrStateNotExist is returned by StateStore.ReadState when the
|
||||
@@ -97,6 +98,11 @@ func (s *FileStore) String() string { return fmt.Sprintf("FileStore(%q)", s.path
|
||||
|
||||
// NewFileStore returns a new file store that persists to path.
|
||||
func NewFileStore(path string) (*FileStore, error) {
|
||||
// We unconditionally call this to ensure that our perms are correct
|
||||
if err := paths.MkStateDir(filepath.Dir(path)); err != nil {
|
||||
return nil, fmt.Errorf("creating state directory: %w", err)
|
||||
}
|
||||
|
||||
bs, err := ioutil.ReadFile(path)
|
||||
|
||||
// Treat an empty file as a missing file.
|
||||
@@ -110,7 +116,6 @@ func NewFileStore(path string) (*FileStore, error) {
|
||||
if os.IsNotExist(err) {
|
||||
// Write out an initial file, to verify that we can write
|
||||
// to the path.
|
||||
os.MkdirAll(filepath.Dir(path), 0755) // best effort
|
||||
if err = atomicfile.WriteFile(path, []byte("{}"), 0600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func New(fileBasePrefix, logID string, logf logger.Logf) logger.Logf {
|
||||
if logf == nil {
|
||||
panic("nil logf")
|
||||
}
|
||||
dir := filepath.Join(os.Getenv("LocalAppData"), "Tailscale", "Logs")
|
||||
dir := filepath.Join(os.Getenv("ProgramData"), "Tailscale", "Logs")
|
||||
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
log.Printf("failed to create local log directory; not writing logs to disk: %v", err)
|
||||
|
||||
@@ -135,12 +135,33 @@ func logsDir(logf logger.Logf) string {
|
||||
}
|
||||
}
|
||||
|
||||
// STATE_DIRECTORY is set by systemd 240+ but we support older
|
||||
// systems-d. For example, Ubuntu 18.04 (Bionic Beaver) is 237.
|
||||
systemdStateDir := os.Getenv("STATE_DIRECTORY")
|
||||
if systemdStateDir != "" {
|
||||
logf("logpolicy: using $STATE_DIRECTORY, %q", systemdStateDir)
|
||||
return systemdStateDir
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
if version.CmdName() == "tailscaled" {
|
||||
// In the common case, when tailscaled is run as the Local System (as a service),
|
||||
// we want to use %ProgramData% (C:\ProgramData\Tailscale), aside the
|
||||
// system state config with the machine key, etc. But if that directory's
|
||||
// not accessible, then it's probably because the user is running tailscaled
|
||||
// as a regular user (perhaps in userspace-networking/SOCK5 mode) and we should
|
||||
// just use the %LocalAppData% instead. In a user context, %LocalAppData% isn't
|
||||
// subject to random deletions from Windows system updates.
|
||||
dir := filepath.Join(os.Getenv("ProgramData"), "Tailscale")
|
||||
if winProgramDataAccessible(dir) {
|
||||
logf("logpolicy: using dir %v", dir)
|
||||
return dir
|
||||
}
|
||||
}
|
||||
dir := filepath.Join(os.Getenv("LocalAppData"), "Tailscale")
|
||||
logf("logpolicy: using LocalAppData dir %v", dir)
|
||||
return dir
|
||||
case "linux":
|
||||
// STATE_DIRECTORY is set by systemd 240+ but we support older
|
||||
// systems-d. For example, Ubuntu 18.04 (Bionic Beaver) is 237.
|
||||
systemdStateDir := os.Getenv("STATE_DIRECTORY")
|
||||
if systemdStateDir != "" {
|
||||
logf("logpolicy: using $STATE_DIRECTORY, %q", systemdStateDir)
|
||||
return systemdStateDir
|
||||
}
|
||||
}
|
||||
|
||||
// Default to e.g. /var/lib/tailscale or /var/db/tailscale on Unix.
|
||||
@@ -191,6 +212,23 @@ func redirectStderrToLogPanics() bool {
|
||||
return runningUnderSystemd() || os.Getenv("TS_PLEASE_PANIC") != ""
|
||||
}
|
||||
|
||||
// winProgramDataAccessible reports whether the directory (assumed to
|
||||
// be a Windows %ProgramData% directory) is accessible to the current
|
||||
// process. It's created if needed.
|
||||
func winProgramDataAccessible(dir string) bool {
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
// TODO: windows ACLs
|
||||
return false
|
||||
}
|
||||
// The C:\ProgramData\Tailscale directory should be locked down
|
||||
// by with ACLs to only be readable by the local system so a
|
||||
// regular user shouldn't be able to do this operation:
|
||||
if _, err := os.ReadDir(dir); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// tryFixLogStateLocation is a temporary fixup for
|
||||
// https://github.com/tailscale/tailscale/issues/247 . We accidentally
|
||||
// wrote logging state files to /, and then later to $CACHE_DIRECTORY
|
||||
@@ -372,14 +410,44 @@ func New(collection string) *Policy {
|
||||
|
||||
cfgPath := filepath.Join(dir, fmt.Sprintf("%s.log.conf", cmdName))
|
||||
|
||||
// The Windows service previously ran as tailscale-ipn.exe, so
|
||||
// let's keep using that log base name if it exists.
|
||||
if runtime.GOOS == "windows" && cmdName == "tailscaled" {
|
||||
const oldCmdName = "tailscale-ipn"
|
||||
oldPath := filepath.Join(dir, oldCmdName+".log.conf")
|
||||
if fi, err := os.Stat(oldPath); err == nil && fi.Mode().IsRegular() {
|
||||
cfgPath = oldPath
|
||||
cmdName = oldCmdName
|
||||
if runtime.GOOS == "windows" {
|
||||
switch cmdName {
|
||||
case "tailscaled":
|
||||
// Tailscale 1.14 and before stored state under %LocalAppData%
|
||||
// (usually "C:\WINDOWS\system32\config\systemprofile\AppData\Local"
|
||||
// when tailscaled.exe is running as a non-user system service).
|
||||
// However it is frequently cleared for almost any reason: Windows
|
||||
// updates, System Restore, even various System Cleaner utilities.
|
||||
//
|
||||
// The Windows service previously ran as tailscale-ipn.exe, so
|
||||
// machines which ran very old versions might still have their
|
||||
// log conf named %LocalAppData%\tailscale-ipn.log.conf
|
||||
//
|
||||
// Machines which started using Tailscale more recently will have
|
||||
// %LocalAppData%\tailscaled.log.conf
|
||||
//
|
||||
// Attempt to migrate the log conf to C:\ProgramData\Tailscale
|
||||
oldDir := filepath.Join(os.Getenv("LocalAppData"), "Tailscale")
|
||||
|
||||
oldPath := filepath.Join(oldDir, "tailscaled.log.conf")
|
||||
if fi, err := os.Stat(oldPath); err != nil || !fi.Mode().IsRegular() {
|
||||
// *Only* if tailscaled.log.conf does not exist,
|
||||
// check for tailscale-ipn.log.conf
|
||||
oldPathOldCmd := filepath.Join(oldDir, "tailscale-ipn.log.conf")
|
||||
if fi, err := os.Stat(oldPathOldCmd); err == nil && fi.Mode().IsRegular() {
|
||||
oldPath = oldPathOldCmd
|
||||
}
|
||||
}
|
||||
|
||||
cfgPath = paths.TryConfigFileMigration(earlyLogf, oldPath, cfgPath)
|
||||
case "tailscale-ipn":
|
||||
for _, oldBase := range []string{"wg64.log.conf", "wg32.log.conf"} {
|
||||
oldConf := filepath.Join(dir, oldBase)
|
||||
if fi, err := os.Stat(oldConf); err == nil && fi.Mode().IsRegular() {
|
||||
cfgPath = paths.TryConfigFileMigration(earlyLogf, oldConf, cfgPath)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,34 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"12": {
|
||||
"RegionID": 12,
|
||||
"RegionCode": "r12",
|
||||
"RegionName": "r12",
|
||||
"Nodes": [
|
||||
{
|
||||
"Name": "12a",
|
||||
"RegionID": 12,
|
||||
"HostName": "derp12.tailscale.com",
|
||||
"IPv4": "216.128.144.130",
|
||||
"IPv6": "2001:19f0:5c01:289:5400:3ff:fe8d:cb5e"
|
||||
},
|
||||
{
|
||||
"Name": "12b",
|
||||
"RegionID": 12,
|
||||
"HostName": "derp12b.tailscale.com",
|
||||
"IPv4": "45.63.71.144",
|
||||
"IPv6": "2001:19f0:5c01:48a:5400:3ff:fe8d:cb5f"
|
||||
},
|
||||
{
|
||||
"Name": "12c",
|
||||
"RegionID": 12,
|
||||
"HostName": "derp12c.tailscale.com",
|
||||
"IPv4": "149.28.119.105",
|
||||
"IPv6": "2001:19f0:5c01:2cb:5400:3ff:fe8d:cb60"
|
||||
}
|
||||
]
|
||||
},
|
||||
"2": {
|
||||
"RegionID": 2,
|
||||
"RegionCode": "r2",
|
||||
@@ -193,6 +221,20 @@
|
||||
"HostName": "derp9.tailscale.com",
|
||||
"IPv4": "207.148.3.137",
|
||||
"IPv6": "2001:19f0:6401:1d9c:5400:2ff:feef:bb82"
|
||||
},
|
||||
{
|
||||
"Name": "9b",
|
||||
"RegionID": 9,
|
||||
"HostName": "derp9b.tailscale.com",
|
||||
"IPv4": "144.202.67.195",
|
||||
"IPv6": "2001:19f0:6401:eb5:5400:3ff:fe8d:6d9b"
|
||||
},
|
||||
{
|
||||
"Name": "9c",
|
||||
"RegionID": 9,
|
||||
"HostName": "derp9c.tailscale.com",
|
||||
"IPv4": "155.138.243.219",
|
||||
"IPv6": "2001:19f0:6401:fe7:5400:3ff:fe8d:6d9c"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -112,22 +112,36 @@ func NonTailscaleMTUs() (map[winipcfg.LUID]uint32, error) {
|
||||
return mtus, err
|
||||
}
|
||||
|
||||
func notTailscaleInterface(iface *winipcfg.IPAdapterAddresses) bool {
|
||||
// TODO(bradfitz): do this without the Description method's
|
||||
// utf16-to-string allocation. But at least we only do it for
|
||||
// the virtual interfaces, for which there won't be many.
|
||||
return !(iface.IfType == winipcfg.IfTypePropVirtual &&
|
||||
iface.Description() == tsconst.WintunInterfaceDesc)
|
||||
}
|
||||
|
||||
// NonTailscaleInterfaces returns a map of interface LUID to interface
|
||||
// for all interfaces except Tailscale tunnels.
|
||||
func NonTailscaleInterfaces() (map[winipcfg.LUID]*winipcfg.IPAdapterAddresses, error) {
|
||||
ifs, err := winipcfg.GetAdaptersAddresses(windows.AF_UNSPEC, winipcfg.GAAFlagIncludeAllInterfaces)
|
||||
return getInterfaces(windows.AF_UNSPEC, winipcfg.GAAFlagIncludeAllInterfaces, notTailscaleInterface)
|
||||
}
|
||||
|
||||
// getInterfaces returns a map of interfaces keyed by their LUID for
|
||||
// all interfaces matching the provided match predicate.
|
||||
//
|
||||
// The family (AF_UNSPEC, AF_INET, or AF_INET6) and flags are passed
|
||||
// to winipcfg.GetAdaptersAddresses.
|
||||
func getInterfaces(family winipcfg.AddressFamily, flags winipcfg.GAAFlags, match func(*winipcfg.IPAdapterAddresses) bool) (map[winipcfg.LUID]*winipcfg.IPAdapterAddresses, error) {
|
||||
ifs, err := winipcfg.GetAdaptersAddresses(family, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := map[winipcfg.LUID]*winipcfg.IPAdapterAddresses{}
|
||||
for _, iface := range ifs {
|
||||
if iface.Description() == tsconst.WintunInterfaceDesc {
|
||||
continue
|
||||
if match(iface) {
|
||||
ret[iface.LUID] = iface
|
||||
}
|
||||
ret[iface.LUID] = iface
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -135,8 +149,26 @@ func NonTailscaleInterfaces() (map[winipcfg.LUID]*winipcfg.IPAdapterAddresses, e
|
||||
// default route for the given address family.
|
||||
//
|
||||
// It returns (nil, nil) if no interface is found.
|
||||
//
|
||||
// The family must be one of AF_INET or AF_INET6.
|
||||
func GetWindowsDefault(family winipcfg.AddressFamily) (*winipcfg.IPAdapterAddresses, error) {
|
||||
ifs, err := NonTailscaleInterfaces()
|
||||
ifs, err := getInterfaces(family, winipcfg.GAAFlagIncludeAllInterfaces, func(iface *winipcfg.IPAdapterAddresses) bool {
|
||||
switch iface.IfType {
|
||||
case winipcfg.IfTypeSoftwareLoopback:
|
||||
return false
|
||||
}
|
||||
switch family {
|
||||
case windows.AF_INET:
|
||||
if iface.Flags&winipcfg.IPAAFlagIpv4Enabled == 0 {
|
||||
return false
|
||||
}
|
||||
case windows.AF_INET6:
|
||||
if iface.Flags&winipcfg.IPAAFlagIpv6Enabled == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return iface.OperStatus == winipcfg.IfOperStatusUp && notTailscaleInterface(iface)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -149,12 +181,31 @@ func GetWindowsDefault(family winipcfg.AddressFamily) (*winipcfg.IPAdapterAddres
|
||||
bestMetric := ^uint32(0)
|
||||
var bestIface *winipcfg.IPAdapterAddresses
|
||||
for _, route := range routes {
|
||||
iface := ifs[route.InterfaceLUID]
|
||||
if route.DestinationPrefix.PrefixLength != 0 || iface == nil {
|
||||
if route.DestinationPrefix.PrefixLength != 0 {
|
||||
// Not a default route.
|
||||
continue
|
||||
}
|
||||
if iface.OperStatus == winipcfg.IfOperStatusUp && route.Metric < bestMetric {
|
||||
bestMetric = route.Metric
|
||||
iface := ifs[route.InterfaceLUID]
|
||||
if iface == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Microsoft docs say:
|
||||
//
|
||||
// "The actual route metric used to compute the route
|
||||
// preferences for IPv4 is the summation of the route
|
||||
// metric offset specified in the Metric member of the
|
||||
// MIB_IPFORWARD_ROW2 structure and the interface
|
||||
// metric specified in this member for IPv4"
|
||||
metric := route.Metric
|
||||
switch family {
|
||||
case windows.AF_INET:
|
||||
metric += iface.Ipv4Metric
|
||||
case windows.AF_INET6:
|
||||
metric += iface.Ipv6Metric
|
||||
}
|
||||
if metric < bestMetric {
|
||||
bestMetric = metric
|
||||
bestIface = iface
|
||||
}
|
||||
}
|
||||
@@ -163,6 +214,9 @@ func GetWindowsDefault(family winipcfg.AddressFamily) (*winipcfg.IPAdapterAddres
|
||||
}
|
||||
|
||||
func DefaultRouteInterface() (string, error) {
|
||||
// We always return the IPv4 default route.
|
||||
// TODO(bradfitz): adjust API if/when anything cares. They could in theory differ, though,
|
||||
// in which case we might send traffic to the wrong interface.
|
||||
iface, err := GetWindowsDefault(windows.AF_INET)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -236,7 +236,19 @@ func (c *Client) upnpPort() uint16 {
|
||||
}
|
||||
|
||||
func (c *Client) listenPacket(ctx context.Context, network, addr string) (net.PacketConn, error) {
|
||||
if (c.testPxPPort != 0 || c.testUPnPPort != 0) && os.Getenv("GITHUB_ACTIONS") == "true" {
|
||||
// When running under testing conditions, we bind the IGD server
|
||||
// to localhost, and may be running in an environment where our
|
||||
// netns code would decide that binding the portmapper client
|
||||
// socket to the default route interface is the correct way to
|
||||
// ensure connectivity. This can result in us trying to send
|
||||
// packets for 127.0.0.1 out the machine's LAN interface, which
|
||||
// obviously gets dropped on the floor.
|
||||
//
|
||||
// So, under those testing conditions, do _not_ use netns to
|
||||
// create listening sockets. Such sockets are vulnerable to
|
||||
// routing loops, but it's tests that don't set up routing loops,
|
||||
// so we don't care.
|
||||
if c.testPxPPort != 0 || c.testUPnPPort != 0 || os.Getenv("GITHUB_ACTIONS") == "true" {
|
||||
var lc net.ListenConfig
|
||||
return lc.ListenPacket(ctx, network, addr)
|
||||
}
|
||||
|
||||
59
paths/migrate.go
Normal file
59
paths/migrate.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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.
|
||||
|
||||
package paths
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
// TryConfigFileMigration carefully copies the contents of oldFile to
|
||||
// newFile, returning the path which should be used to read the config.
|
||||
// - if newFile already exists, don't modify it just return its path
|
||||
// - if neither oldFile nor newFile exist, return newFile for a fresh
|
||||
// default config to be written to.
|
||||
// - if oldFile exists but copying to newFile fails, return oldFile so
|
||||
// there will at least be some config to work with.
|
||||
func TryConfigFileMigration(logf logger.Logf, oldFile, newFile string) string {
|
||||
_, err := os.Stat(newFile)
|
||||
if err == nil {
|
||||
// Common case for a system which has already been migrated.
|
||||
return newFile
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
logf("TryConfigFileMigration failed; new file: %v", err)
|
||||
return newFile
|
||||
}
|
||||
|
||||
contents, err := os.ReadFile(oldFile)
|
||||
if err != nil {
|
||||
// Common case for a new user.
|
||||
return newFile
|
||||
}
|
||||
|
||||
if err = MkStateDir(filepath.Dir(newFile)); err != nil {
|
||||
logf("TryConfigFileMigration failed; MkStateDir: %v", err)
|
||||
return oldFile
|
||||
}
|
||||
|
||||
err = os.WriteFile(newFile, contents, 0600)
|
||||
if err != nil {
|
||||
removeErr := os.Remove(newFile)
|
||||
if removeErr != nil {
|
||||
logf("TryConfigFileMigration failed; write newFile no cleanup: %v, remove err: %v",
|
||||
err, removeErr)
|
||||
return oldFile
|
||||
}
|
||||
logf("TryConfigFileMigration failed; write newFile: %v", err)
|
||||
return oldFile
|
||||
}
|
||||
|
||||
logf("TryConfigFileMigration: successfully migrated: from %v to %v",
|
||||
oldFile, newFile)
|
||||
|
||||
return newFile
|
||||
}
|
||||
@@ -55,7 +55,18 @@ func DefaultTailscaledStateFile() string {
|
||||
return f()
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
return filepath.Join(os.Getenv("LocalAppData"), "Tailscale", "server-state.conf")
|
||||
return filepath.Join(os.Getenv("ProgramData"), "Tailscale", "server-state.conf")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// MkStateDir ensures that dirPath, the daemon's configurtaion directory
|
||||
// containing machine keys etc, both exists and has the correct permissions.
|
||||
// We want it to only be accessible to the user the daemon is running under.
|
||||
func MkStateDir(dirPath string) error {
|
||||
if err := os.MkdirAll(dirPath, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ensureStateDirPerms(dirPath)
|
||||
}
|
||||
|
||||
@@ -61,3 +61,11 @@ func xdgDataHome() string {
|
||||
}
|
||||
return filepath.Join(os.Getenv("HOME"), ".local/share")
|
||||
}
|
||||
|
||||
func ensureStateDirPerms(dirPath string) error {
|
||||
if filepath.Base(dirPath) != "tailscale" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return os.Chmod(dirPath, 0700)
|
||||
}
|
||||
|
||||
150
paths/paths_windows.go
Normal file
150
paths/paths_windows.go
Normal file
@@ -0,0 +1,150 @@
|
||||
// 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.
|
||||
|
||||
package paths
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func getTokenInfo(token windows.Token, infoClass uint32) ([]byte, error) {
|
||||
var desiredLen uint32
|
||||
err := windows.GetTokenInformation(token, infoClass, nil, 0, &desiredLen)
|
||||
if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := make([]byte, desiredLen)
|
||||
actualLen := desiredLen
|
||||
err = windows.GetTokenInformation(token, infoClass, &buf[0], desiredLen, &actualLen)
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func getTokenUserInfo(token windows.Token) (*windows.Tokenuser, error) {
|
||||
buf, err := getTokenInfo(token, windows.TokenUser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return (*windows.Tokenuser)(unsafe.Pointer(&buf[0])), nil
|
||||
}
|
||||
|
||||
func getTokenPrimaryGroupInfo(token windows.Token) (*windows.Tokenprimarygroup, error) {
|
||||
buf, err := getTokenInfo(token, windows.TokenPrimaryGroup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return (*windows.Tokenprimarygroup)(unsafe.Pointer(&buf[0])), nil
|
||||
}
|
||||
|
||||
type userSids struct {
|
||||
User *windows.SID
|
||||
PrimaryGroup *windows.SID
|
||||
}
|
||||
|
||||
func getCurrentUserSids() (*userSids, error) {
|
||||
token, err := windows.OpenCurrentProcessToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer token.Close()
|
||||
|
||||
userInfo, err := getTokenUserInfo(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
primaryGroup, err := getTokenPrimaryGroupInfo(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &userSids{userInfo.User.Sid, primaryGroup.PrimaryGroup}, nil
|
||||
}
|
||||
|
||||
// ensureStateDirPerms applies a restrictive ACL to the directory specified by dirPath.
|
||||
// It sets the following security attributes on the directory:
|
||||
// Owner: The user for the current process;
|
||||
// Primary Group: The primary group for the current process;
|
||||
// DACL: Full control to the current user and to the Administrators group.
|
||||
// (We include Administrators so that admin users may still access logs;
|
||||
// granting access exclusively to LocalSystem would require admins to use
|
||||
// special tools to access the Log directory)
|
||||
// Inheritance: The directory does not inherit the ACL from its parent.
|
||||
// However, any directories and/or files created within this
|
||||
// directory *do* inherit the ACL that we are setting.
|
||||
func ensureStateDirPerms(dirPath string) error {
|
||||
fi, err := os.Stat(dirPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if strings.ToLower(filepath.Base(dirPath)) != "tailscale" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We need the info for our current user as SIDs
|
||||
sids, err := getCurrentUserSids()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We also need the SID for the Administrators group so that admins may
|
||||
// easily access logs.
|
||||
adminGroupSid, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Munge the SIDs into the format required by EXPLICIT_ACCESS.
|
||||
userTrustee := windows.TRUSTEE{nil, windows.NO_MULTIPLE_TRUSTEE,
|
||||
windows.TRUSTEE_IS_SID, windows.TRUSTEE_IS_USER,
|
||||
windows.TrusteeValueFromSID(sids.User)}
|
||||
|
||||
adminTrustee := windows.TRUSTEE{nil, windows.NO_MULTIPLE_TRUSTEE,
|
||||
windows.TRUSTEE_IS_SID, windows.TRUSTEE_IS_WELL_KNOWN_GROUP,
|
||||
windows.TrusteeValueFromSID(adminGroupSid)}
|
||||
|
||||
// We declare our access rights via this array of EXPLICIT_ACCESS structures.
|
||||
// We set full access to our user and to Administrators.
|
||||
// We configure the DACL such that any files or directories created within
|
||||
// dirPath will also inherit this DACL.
|
||||
explicitAccess := []windows.EXPLICIT_ACCESS{
|
||||
windows.EXPLICIT_ACCESS{
|
||||
windows.GENERIC_ALL,
|
||||
windows.SET_ACCESS,
|
||||
windows.SUB_CONTAINERS_AND_OBJECTS_INHERIT,
|
||||
userTrustee,
|
||||
},
|
||||
windows.EXPLICIT_ACCESS{
|
||||
windows.GENERIC_ALL,
|
||||
windows.SET_ACCESS,
|
||||
windows.SUB_CONTAINERS_AND_OBJECTS_INHERIT,
|
||||
adminTrustee,
|
||||
},
|
||||
}
|
||||
|
||||
dacl, err := windows.ACLFromEntries(explicitAccess, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We now reset the file's owner, primary group, and DACL.
|
||||
// We also must pass PROTECTED_DACL_SECURITY_INFORMATION so that our new ACL
|
||||
// does not inherit any ACL entries from the parent directory.
|
||||
const flags = windows.OWNER_SECURITY_INFORMATION |
|
||||
windows.GROUP_SECURITY_INFORMATION |
|
||||
windows.DACL_SECURITY_INFORMATION |
|
||||
windows.PROTECTED_DACL_SECURITY_INFORMATION
|
||||
return windows.SetNamedSecurityInfo(dirPath, windows.SE_FILE_OBJECT, flags,
|
||||
sids.User, sids.PrimaryGroup, dacl, nil)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
type closeable interface {
|
||||
@@ -29,9 +30,39 @@ func ConnCloseWrite(c net.Conn) error {
|
||||
return c.(closeable).CloseWrite()
|
||||
}
|
||||
|
||||
var processStartTime = time.Now()
|
||||
var tailscaledProcExists = func() bool { return false } // set by safesocket_ps.go
|
||||
|
||||
// tailscaledStillStarting reports whether tailscaled is probably
|
||||
// still starting up. That is, it reports whether the caller should
|
||||
// keep retrying to connect.
|
||||
func tailscaledStillStarting() bool {
|
||||
d := time.Since(processStartTime)
|
||||
if d < 2*time.Second {
|
||||
// Without even checking the process table, assume
|
||||
// that for the first two seconds that tailscaled is
|
||||
// probably still starting. That is, assume they're
|
||||
// running "tailscaled & tailscale up ...." and make
|
||||
// the tailscale client block for a bit for tailscaled
|
||||
// to start accepting on the socket.
|
||||
return true
|
||||
}
|
||||
if d > 5*time.Second {
|
||||
return false
|
||||
}
|
||||
return tailscaledProcExists()
|
||||
}
|
||||
|
||||
// Connect connects to either path (on Unix) or the provided localhost port (on Windows).
|
||||
func Connect(path string, port uint16) (net.Conn, error) {
|
||||
return connect(path, port)
|
||||
for {
|
||||
c, err := connect(path, port)
|
||||
if err != nil && tailscaledStillStarting() {
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
}
|
||||
|
||||
// Listen returns a listener either on Unix socket path (on Unix), or
|
||||
|
||||
37
safesocket/safesocket_ps.go
Normal file
37
safesocket/safesocket_ps.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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.
|
||||
|
||||
//go:build linux || windows || darwin || freebsd
|
||||
// +build linux windows darwin freebsd
|
||||
|
||||
package safesocket
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
ps "github.com/mitchellh/go-ps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tailscaledProcExists = func() bool {
|
||||
procs, err := ps.Processes()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, proc := range procs {
|
||||
name := proc.Executable()
|
||||
const tailscaled = "tailscaled"
|
||||
if len(name) < len(tailscaled) {
|
||||
continue
|
||||
}
|
||||
// Do case insensitive comparison for Windows,
|
||||
// notably, and ignore any ".exe" suffix.
|
||||
if strings.EqualFold(name[:len(tailscaled)], tailscaled) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,8 @@ import (
|
||||
// 20: 2021-06-11: MapResponse.LastSeen used even less (https://github.com/tailscale/tailscale/issues/2107)
|
||||
// 21: 2021-06-15: added MapResponse.DNSConfig.CertDomains
|
||||
// 22: 2021-06-16: added MapResponse.DNSConfig.ExtraRecords
|
||||
const CurrentMapRequestVersion = 22
|
||||
// 23: 2021-08-25: DNSConfig.Routes values may be empty (for ExtraRecords support in 1.14.1+)
|
||||
const CurrentMapRequestVersion = 23
|
||||
|
||||
type StableID string
|
||||
|
||||
@@ -837,12 +838,19 @@ var FilterAllowAll = []FilterRule{
|
||||
type DNSConfig struct {
|
||||
// Resolvers are the DNS resolvers to use, in order of preference.
|
||||
Resolvers []dnstype.Resolver `json:",omitempty"`
|
||||
|
||||
// Routes maps DNS name suffixes to a set of DNS resolvers to
|
||||
// use. It is used to implement "split DNS" and other advanced DNS
|
||||
// routing overlays.
|
||||
// Map keys must be fully-qualified DNS name suffixes, with a
|
||||
// trailing dot but no leading dot.
|
||||
//
|
||||
// Map keys are fully-qualified DNS name suffixes; they may
|
||||
// optionally contain a trailing dot but no leading dot.
|
||||
//
|
||||
// If the value is an empty slice, that means the suffix should still
|
||||
// be handled by Tailscale's built-in resolver (100.100.100.100), such
|
||||
// as for the purpose of handling ExtraRecords.
|
||||
Routes map[string][]dnstype.Resolver `json:",omitempty"`
|
||||
|
||||
// FallbackResolvers is like Resolvers, but is only used if a
|
||||
// split DNS configuration is requested in a configuration that
|
||||
// doesn't work yet without explicit default resolvers.
|
||||
|
||||
@@ -182,15 +182,18 @@ func TestLongRunningQPS(t *testing.T) {
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
// This will still offer ~500 requests per second,
|
||||
// but won't consume outrageous amount of CPU.
|
||||
start := time.Now()
|
||||
end := start.Add(5 * time.Second)
|
||||
for time.Now().Before(end) {
|
||||
ticker := time.NewTicker(2 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for now := range ticker.C {
|
||||
if now.After(end) {
|
||||
break
|
||||
}
|
||||
wg.Add(1)
|
||||
go f()
|
||||
|
||||
// This will still offer ~500 requests per second, but won't consume
|
||||
// outrageous amount of CPU.
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
}
|
||||
wg.Wait()
|
||||
elapsed := time.Since(start)
|
||||
@@ -201,7 +204,7 @@ func TestLongRunningQPS(t *testing.T) {
|
||||
t.Errorf("numOK = %d, want %d (ideal %f)", numOK, want, ideal)
|
||||
}
|
||||
// We should get very close to the number of requests allowed.
|
||||
if want := int32(0.999 * ideal); numOK < want {
|
||||
if want := int32(0.995 * ideal); numOK < want {
|
||||
t.Errorf("numOK = %d, want %d (ideal %f)", numOK, want, ideal)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"inet.af/netaddr"
|
||||
@@ -332,15 +334,32 @@ func TestArrayAllocs(t *testing.T) {
|
||||
if version.IsRace() {
|
||||
t.Skip("skipping test under race detector")
|
||||
}
|
||||
|
||||
// In theory, there should be no allocations. However, escape analysis on
|
||||
// certain architectures fails to detect that certain cases do not escape.
|
||||
// This discrepency currently affects sha256.digest.Sum.
|
||||
// Measure the number of allocations in sha256 to ensure that Hash does
|
||||
// not allocate on top of its usage of sha256.
|
||||
// See https://golang.org/issue/48055.
|
||||
var b []byte
|
||||
h := sha256.New()
|
||||
want := int(testing.AllocsPerRun(1000, func() {
|
||||
b = h.Sum(b[:0])
|
||||
}))
|
||||
switch runtime.GOARCH {
|
||||
case "amd64", "arm64":
|
||||
want = 0 // ensure no allocations on popular architectures
|
||||
}
|
||||
|
||||
type T struct {
|
||||
X [32]byte
|
||||
}
|
||||
x := &T{X: [32]byte{1: 1, 2: 2, 3: 3, 4: 4}}
|
||||
n := int(testing.AllocsPerRun(1000, func() {
|
||||
got := int(testing.AllocsPerRun(1000, func() {
|
||||
sink = Hash(x)
|
||||
}))
|
||||
if n > 0 {
|
||||
t.Errorf("allocs = %v; want 0", n)
|
||||
if got > want {
|
||||
t.Errorf("allocs = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -468,19 +468,37 @@ func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.Wrapper) filter.Respons
|
||||
return filter.DropSilently
|
||||
}
|
||||
|
||||
func netaddrIPFromNetstackIP(s tcpip.Address) netaddr.IP {
|
||||
switch len(s) {
|
||||
case 4:
|
||||
return netaddr.IPv4(s[0], s[1], s[2], s[3])
|
||||
case 16:
|
||||
var a [16]byte
|
||||
copy(a[:], s)
|
||||
return netaddr.IPFrom16(a)
|
||||
}
|
||||
return netaddr.IP{}
|
||||
}
|
||||
|
||||
func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
|
||||
reqDetails := r.ID()
|
||||
if debugNetstack {
|
||||
ns.logf("[v2] TCP ForwarderRequest: %s", stringifyTEI(reqDetails))
|
||||
}
|
||||
dialAddr := reqDetails.LocalAddress
|
||||
dialNetAddr, _ := netaddr.FromStdIP(net.IP(dialAddr))
|
||||
isTailscaleIP := tsaddr.IsTailscaleIP(dialNetAddr)
|
||||
clientRemoteIP := netaddrIPFromNetstackIP(reqDetails.RemoteAddress)
|
||||
if !clientRemoteIP.IsValid() {
|
||||
ns.logf("invalid RemoteAddress in TCP ForwarderRequest: %s", stringifyTEI(reqDetails))
|
||||
r.Complete(true)
|
||||
return
|
||||
}
|
||||
|
||||
dialIP := netaddrIPFromNetstackIP(reqDetails.LocalAddress)
|
||||
isTailscaleIP := tsaddr.IsTailscaleIP(dialIP)
|
||||
defer func() {
|
||||
if !isTailscaleIP {
|
||||
// if this is a subnet IP, we added this in before the TCP handshake
|
||||
// so netstack is happy TCP-handshaking as a subnet IP
|
||||
ns.removeSubnetAddress(dialNetAddr)
|
||||
ns.removeSubnetAddress(dialIP)
|
||||
}
|
||||
}()
|
||||
var wq waiter.Queue
|
||||
@@ -490,21 +508,31 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
|
||||
return
|
||||
}
|
||||
r.Complete(false)
|
||||
|
||||
// Asynchronously start the TCP handshake. Note that the
|
||||
// gonet.TCPConn methods c.RemoteAddr() and c.LocalAddr() will
|
||||
// return nil until the handshake actually completes. But we
|
||||
// have the remote address in reqDetails instead, so we don't
|
||||
// use RemoteAddr. The byte copies in both directions in
|
||||
// forwardTCP will block until the TCP handshake is complete.
|
||||
c := gonet.NewTCPConn(&wq, ep)
|
||||
|
||||
if ns.ForwardTCPIn != nil {
|
||||
ns.ForwardTCPIn(c, reqDetails.LocalPort)
|
||||
return
|
||||
}
|
||||
if isTailscaleIP {
|
||||
dialAddr = tcpip.Address(net.ParseIP("127.0.0.1")).To4()
|
||||
dialIP = netaddr.IPv4(127, 0, 0, 1)
|
||||
}
|
||||
ns.forwardTCP(c, &wq, dialAddr, reqDetails.LocalPort)
|
||||
dialAddr := netaddr.IPPortFrom(dialIP, uint16(reqDetails.LocalPort))
|
||||
ns.forwardTCP(c, clientRemoteIP, &wq, dialAddr)
|
||||
}
|
||||
|
||||
func (ns *Impl) forwardTCP(client *gonet.TCPConn, wq *waiter.Queue, dialAddr tcpip.Address, dialPort uint16) {
|
||||
func (ns *Impl) forwardTCP(client *gonet.TCPConn, clientRemoteIP netaddr.IP, wq *waiter.Queue, dialAddr netaddr.IPPort) {
|
||||
defer client.Close()
|
||||
dialAddrStr := net.JoinHostPort(dialAddr.String(), strconv.Itoa(int(dialPort)))
|
||||
dialAddrStr := dialAddr.String()
|
||||
ns.logf("[v2] netstack: forwarding incoming connection to %s", dialAddrStr)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
waitEntry, notifyCh := waiter.NewChannelEntry(nil)
|
||||
@@ -530,7 +558,6 @@ func (ns *Impl) forwardTCP(client *gonet.TCPConn, wq *waiter.Queue, dialAddr tcp
|
||||
defer server.Close()
|
||||
backendLocalAddr := server.LocalAddr().(*net.TCPAddr)
|
||||
backendLocalIPPort, _ := netaddr.FromStdAddr(backendLocalAddr.IP, backendLocalAddr.Port, backendLocalAddr.Zone)
|
||||
clientRemoteIP, _ := netaddr.FromStdIP(client.RemoteAddr().(*net.TCPAddr).IP)
|
||||
ns.e.RegisterIPPortIdentity(backendLocalIPPort, clientRemoteIP)
|
||||
defer ns.e.UnregisterIPPortIdentity(backendLocalIPPort)
|
||||
connClosed := make(chan error, 2)
|
||||
|
||||
@@ -84,6 +84,7 @@ type userspaceEngine struct {
|
||||
wgLogger *wglog.Logger //a wireguard-go logging wrapper
|
||||
reqCh chan struct{}
|
||||
waitCh chan struct{} // chan is closed when first Close call completes; contrast with closing bool
|
||||
magicConnStarted chan struct{} // chan is closed after magicConn.Start
|
||||
timeNow func() mono.Time
|
||||
tundev *tstun.Wrapper
|
||||
wgdev *device.Device
|
||||
@@ -249,13 +250,14 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
||||
closePool.add(tsTUNDev)
|
||||
|
||||
e := &userspaceEngine{
|
||||
timeNow: mono.Now,
|
||||
logf: logf,
|
||||
reqCh: make(chan struct{}, 1),
|
||||
waitCh: make(chan struct{}),
|
||||
tundev: tsTUNDev,
|
||||
router: conf.Router,
|
||||
confListenPort: conf.ListenPort,
|
||||
timeNow: mono.Now,
|
||||
logf: logf,
|
||||
reqCh: make(chan struct{}, 1),
|
||||
waitCh: make(chan struct{}),
|
||||
tundev: tsTUNDev,
|
||||
router: conf.Router,
|
||||
confListenPort: conf.ListenPort,
|
||||
magicConnStarted: make(chan struct{}),
|
||||
}
|
||||
e.isLocalAddr.Store(tsaddr.NewContainsIPFunc(nil))
|
||||
e.isDNSIPOverTailscale.Store(tsaddr.NewContainsIPFunc(nil))
|
||||
@@ -371,7 +373,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
||||
|
||||
// It's a little pointless to apply no-op settings here (they
|
||||
// should already be empty?), but it at least exercises the
|
||||
// router implementation early on the machine.
|
||||
// router implementation early on.
|
||||
e.logf("Clearing router settings...")
|
||||
if err := e.router.Set(nil); err != nil {
|
||||
return nil, err
|
||||
@@ -380,6 +382,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
||||
e.linkMon.Start()
|
||||
e.logf("Starting magicsock...")
|
||||
e.magicConn.Start()
|
||||
close(e.magicConnStarted)
|
||||
|
||||
go e.pollResolver()
|
||||
|
||||
@@ -1092,6 +1095,10 @@ func (e *userspaceEngine) LinkChange(_ bool) {
|
||||
}
|
||||
|
||||
func (e *userspaceEngine) linkChange(changed bool, cur *interfaces.State) {
|
||||
// Issue 2733: wait for e.magicConn to be started; there's two tiny
|
||||
// windows at startup where this callback can be run before Start
|
||||
<-e.magicConnStarted
|
||||
|
||||
up := cur.AnyInterfaceUp()
|
||||
if !up {
|
||||
e.logf("LinkChange: all links down; pausing: %v", cur)
|
||||
|
||||
Reference in New Issue
Block a user