Compare commits

...

15 Commits

Author SHA1 Message Date
Andrew Dunham
a37032932f net/dnscache: add a bunch of synthetic failure knobs 2022-09-02 13:22:36 -04:00
Denton Gentry
6f700925ce VERSION.txt: this is v1.22.2
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-03-17 18:40:04 -07:00
Maisem Ali
80b31f3893 wgengine: handle nil netmaps when assigning isSubnetRouter.
Fixes tailscale/coral#51

Signed-off-by: Maisem Ali <maisem@tailscale.com>
(cherry picked from commit 07f48a7bfe)
2022-03-17 18:37:40 -07:00
Denton Gentry
c8fb4f8c79 VERSION.txt: this is v1.22.1
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-03-09 12:38:59 -08:00
Joonas Kuorilehto
6562f4c6c7 cmd/tailscale: allow use of flags in gokrazy
Enable use of command line arguments with tailscale cli on gokrazy. Before
this change using arguments like "up" would cause tailscale cli to be
repeatedly restarted by gokrazy process supervisor.

We never want to have gokrazy restart tailscale cli, even if user would
manually start the process.

Expected usage is that user creates files:

flags/tailscale.com/cmd/tailscale/flags.txt:

    up

flags/tailscale.com/cmd/tailscaled/flags.txt:

    --statedir=/perm/tailscaled/
    --tun=userspace-networking

Then tailscale prints URL for user to log in with browser.

Alternatively it should be possible to use up with auth key to allow
unattended gokrazy installs.

Signed-off-by: Joonas Kuorilehto <joneskoo@derbian.fi>
(cherry picked from commit c1b3500a05)
2022-03-09 12:30:53 -08:00
Brad Fitzpatrick
9fd9abfd3f net/interfaces: add FreeBSD default route lookup (portmapping, etc)
Updates #4101 (probably fixes)

Change-Id: I2b75ee3ced276fb7b211f17c382621cf1ef882fa
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 61cdcf4082)
2022-03-08 13:54:13 -08:00
Maisem Ali
24319e840d net/socks5: always close client connections after serving
Customer reported an issue where the connections were not closing, and
would instead just stay open. This commit makes it so that we close out
the connection regardless of what error we see. I've verified locally
that it fixes the issue, we should add a test for this.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
(cherry picked from commit 2fb087891b)
2022-03-08 13:54:13 -08:00
Robert Fritzsche
0a399bb6c6 tstime/mono: fix Before function comment
Signed-off-by: Robert Fritzsche <r.fritzsche@gridx.de>
(cherry picked from commit 0e62a7d1a2)
2022-03-08 13:54:13 -08:00
Josh Bleecher Snyder
0b5b3287cb go.toolchain.rev: bump to Go 1.17.8
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
(cherry picked from commit 7ddf2e2fea)
2022-03-08 13:54:13 -08:00
Brad Fitzpatrick
dee0b7f8b8 cmd/tailscale: tell gokrazy to not manage the CLI as a daemon
In the future we'll probably want to run the "tailscale web"
server instead, but for now stop the infinite restart loop.

See https://gokrazy.org/userguide/process-interface/ for details.

Updates #1866

Change-Id: I4133a5fdb859b848813972620495865727fe397a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit f18bb6397b)
2022-03-08 13:54:13 -08:00
Brad Fitzpatrick
231f8b74e4 cmd/tailscaled: default to userspace-networking mode on gokrazy, set paths
One of the current few steps to run Tailscale on gokrazy is to
specify the --tun=userspace-networking flag:

    https://gokrazy.org/userguide/install/tailscale/

Instead, make it the default for now. Later we can change the
default to kernel mode if available and fall back to userspace
mode like Synology, once #391 is done.

Likewise, set default paths for Gokrazy, as its filesystem hierarchy
is not the Linux standard one. Instead, use the conventional paths as
documented at https://gokrazy.org/userguide/install/tailscale/.

Updates #1866

RELNOTE=default to userspace-networking mode on gokrazy

Change-Id: I3766159a294738597b4b30629d2860312dbb7609
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit db85384f9c)
2022-03-08 13:54:13 -08:00
Brad Fitzpatrick
d0698cfcec version, hostinfo: recognize gokrazy as a distro
Now:

/tmp/breakglass3929186798 # /user/tailscale debug hostinfo
{
  "IPNVersion": "1.23.0-date.20220107",
  "OS": "linux",
  "OSVersion": "Gokrazy; kernel=5.16.11",
  "DeviceModel": "Raspberry Pi 4 Model B Rev 1.2",
  "Hostname": "gokrazy",
  "GoArch": "arm64"
}

Also, cache the distro lookup. It doesn't change while the program is
running:

name   old time/op    new time/op    delta
Get-6    5.21µs ± 5%    0.00µs ± 3%   -99.91%  (p=0.008 n=5+5)

name   old alloc/op   new alloc/op   delta
Get-6      792B ± 0%        0B       -100.00%  (p=0.008 n=5+5)

name   old allocs/op  new allocs/op  delta
Get-6      8.00 ± 0%      0.00       -100.00%  (p=0.008 n=5+5)

Updates #1866

Change-Id: Ifb9a63b94287010d3f4c8bfeb6b78119e8a9b203
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 58a6c9b2b8)
2022-03-08 13:54:13 -08:00
Brad Fitzpatrick
bfd7f9d318 tstime/rate: deflake TestLongRunningQPS even more
Previous de-flakings:
* 8cf1af8a07 for #3733
* 30458c71c8 for #2727

Fixes #4044

Change-Id: I506cf1ff37bb224f5a9929f1998901e60b24535d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 6a2e94cbeb)
2022-03-08 13:54:13 -08:00
Brad Fitzpatrick
fca3592c1c net/interfaces: get Linux default route from netlink as fallback
If it's in a non-standard table, as it is on Unifi UDM Pro, apparently.

Updates #4038 (probably fixes, but don't have hardware to verify)

Change-Id: I2cb9a098d8bb07d1a97a6045b686aca31763a937
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 55095df644)
2022-03-08 13:54:13 -08:00
Denton Gentry
4e0b00ad83 VERSION.txt: this is v1.22.0
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-02-23 15:30:02 -08:00
22 changed files with 357 additions and 91 deletions

View File

@@ -1 +1 @@
1.21.0
1.22.2

View File

@@ -125,10 +125,22 @@ func CleanUpArgs(args []string) []string {
}
// Run runs the CLI. The args do not include the binary name.
func Run(args []string) error {
func Run(args []string) (err error) {
if len(args) == 1 && (args[0] == "-V" || args[0] == "--version") {
args = []string{"version"}
}
if runtime.GOOS == "linux" && distro.Get() == distro.Gokrazy &&
os.Getenv("GOKRAZY_FIRST_START") == "1" {
defer func() {
// Exit with 125 otherwise the CLI binary is restarted
// forever in a loop by the Gokrazy process supervisor.
// See https://gokrazy.org/userguide/process-interface/
if err != nil {
log.Println(err)
}
os.Exit(125)
}()
}
var warnOnce sync.Once
tailscale.SetVersionMismatchHandler(func(clientVer, serverVer string) {
@@ -194,7 +206,7 @@ change in the future.
}
})
err := rootCmd.Run(context.Background())
err = rootCmd.Run(context.Background())
if tailscale.IsAccessDeniedError(err) && os.Getuid() != 0 && runtime.GOOS != "windows" {
return fmt.Errorf("%v\n\nUse 'sudo tailscale %s' or 'tailscale up --operator=$USER' to not require root.", err, strings.Join(args, " "))
}

View File

@@ -4,8 +4,14 @@ 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/golang/groupcache/lru from tailscale.com/net/dnscache
L github.com/josharian/native from github.com/mdlayher/netlink+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
L github.com/klauspost/compress/flate from nhooyr.io/websocket
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli+
github.com/peterbourgon/ff/v3 from github.com/peterbourgon/ff/v3/ffcli
github.com/peterbourgon/ff/v3/ffcli from tailscale.com/cmd/tailscale/cli
@@ -97,6 +103,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
L golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/dns/dnsmessage from net+
golang.org/x/net/http/httpguts from net/http+
golang.org/x/net/http/httpproxy from net/http

View File

@@ -74,7 +74,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L github.com/insomniacslk/dhcp/rfc1035label from github.com/insomniacslk/dhcp/dhcpv4
L github.com/jmespath/go-jmespath from github.com/aws/aws-sdk-go-v2/service/ssm
L github.com/josharian/native from github.com/mdlayher/netlink+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor+
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/klauspost/compress from github.com/klauspost/compress/zstd
L github.com/klauspost/compress/flate from nhooyr.io/websocket

View File

@@ -68,11 +68,27 @@ func defaultTunName() string {
// as a magic value that uses/creates any free number.
return "utun"
case "linux":
if distro.Get() == distro.Synology {
switch distro.Get() {
case distro.Synology:
// Try TUN, but fall back to userspace networking if needed.
// See https://github.com/tailscale/tailscale-synology/issues/35
return "tailscale0,userspace-networking"
case distro.Gokrazy:
// Gokrazy doesn't yet work in tun mode because the whole
// Gokrazy thing is no C code, and Tailscale currently
// depends on the iptables binary for Linux's
// wgengine/router.
// But on Gokrazy there's no legacy iptables, so we could use netlink
// to program nft-iptables directly. It just isn't done yet;
// see https://github.com/tailscale/tailscale/issues/391
//
// But Gokrazy does have the tun module built-in, so users
// can stil run --tun=tailscale0 if they wish, if they
// arrange for iptables to be present or run in "tailscale
// up --netfilter-mode=off" mode, perhaps. Untested.
return "userspace-networking"
}
}
return "tailscale0"
}

View File

@@ -1 +1 @@
fd6b85b80f5fbbfaddef34e7ab6ac83384c50e4d
dce70b6d327c7a30b81701f4cc134b56c4e6c229

View File

@@ -113,6 +113,8 @@ func osVersionLinux() string {
return fmt.Sprintf("Synology %s%s", m["productversion"], attr)
case distro.OpenWrt:
return fmt.Sprintf("OpenWrt %s%s", m["DISTRIB_RELEASE"], attr)
case distro.Gokrazy:
return fmt.Sprintf("Gokrazy%s", attr)
}
return fmt.Sprintf("Other%s", attr)
}

View File

@@ -15,6 +15,7 @@ import (
"fmt"
"log"
"net"
"os"
"runtime"
"sync"
"time"
@@ -108,6 +109,11 @@ var debug = envknob.Bool("TS_DEBUG_DNS_CACHE")
// If err is nil, ip will be non-nil. The v6 address may be nil even
// with a nil error.
func (r *Resolver) LookupIP(ctx context.Context, host string) (ip, v6 net.IP, allIPs []net.IPAddr, err error) {
if fileExists("/tmp/dnscache-synthetic-resolve-failure.txt") {
log.Printf("dnscache: synthetic resolve failure for %s", host)
return nil, nil, nil, fmt.Errorf("synthetic resolve failure for %s", host)
}
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
return ip4, nil, []net.IPAddr{{IP: ip4}}, nil
@@ -266,11 +272,13 @@ func (r *Resolver) addIPCache(host string, ip, ip6 net.IP, allIPs []net.IPAddr,
if r.ipCache == nil {
r.ipCache = make(map[string]ipCacheEntry)
}
r.ipCache[host] = ipCacheEntry{
ip: ip,
ip6: ip6,
allIPs: allIPs,
expires: time.Now().Add(d),
if false { // DEBUG DEBUG
r.ipCache[host] = ipCacheEntry{
ip: ip,
ip6: ip6,
allIPs: allIPs,
expires: time.Now().Add(d),
}
}
}
@@ -369,12 +377,30 @@ type dialCall struct {
// dnsWasTrustworthy reports whether we think the IP address(es) we
// tried (and failed) to dial were probably the correct IPs. Currently
// the heuristic is whether they ever worked previously.
func (dc *dialCall) dnsWasTrustworthy() bool {
func (dc *dialCall) dnsWasTrustworthy() (ret bool) {
dc.d.mu.Lock()
defer dc.d.mu.Unlock()
dc.mu.Lock()
defer dc.mu.Unlock()
defer func() {
log.Printf("dnscache: dnsWasTrustworthy = %v", ret)
}()
// DEBUG DEBUG DEBUG
if fileExists("/tmp/dnscache-drop-fails.txt") {
log.Printf("dnscache: clearing failed list")
dc.fails = map[netaddr.IP]error{}
}
if fileExists("/tmp/dnscache-drop-pastconnect.txt") {
log.Printf("dnscache: clearing past connections list")
dc.d.pastConnect = map[netaddr.IP]time.Time{}
}
if debug {
log.Printf("dnscache: we have %d failures and %d past connections", len(dc.fails), len(dc.d.pastConnect))
}
if len(dc.fails) == 0 {
// No information.
return false
@@ -384,14 +410,30 @@ func (dc *dialCall) dnsWasTrustworthy() bool {
// this dialer, assume the DNS is fine.
for ip := range dc.fails {
if _, ok := dc.d.pastConnect[ip]; ok {
if debug {
log.Printf("dnscache: DNS trustworthy due to past connection to %v", ip)
}
return true
}
}
return false
}
func (dc *dialCall) dialOne(ctx context.Context, ip netaddr.IP) (net.Conn, error) {
c, err := dc.d.fwd(ctx, dc.network, net.JoinHostPort(ip.String(), dc.port))
func fileExists(path string) bool {
st, err := os.Stat(path)
if err != nil {
return false
}
return st.Mode().IsRegular()
}
func (dc *dialCall) dialOne(ctx context.Context, ip netaddr.IP) (c net.Conn, err error) {
if fileExists("/tmp/dnscache-synthetic-dial-failure.txt") {
log.Printf("dnscache: synthetic dial failure for %s", ip)
err = fmt.Errorf("synthetic dial failure for %s", ip)
} else {
c, err = dc.d.fwd(ctx, dc.network, net.JoinHostPort(ip.String(), dc.port))
}
dc.noteDialResult(ip, err)
return c, err
}

View File

@@ -687,7 +687,8 @@ func netInterfaces() ([]Interface, error) {
return ret, nil
}
// DefaultRouteDetails are the
// DefaultRouteDetails are the details about a default route returned
// by DefaultRoute.
type DefaultRouteDetails struct {
// InterfaceName is the interface name. It must always be populated.
// It's like "eth0" (Linux), "Ethernet 2" (Windows), "en0" (macOS).

View File

@@ -0,0 +1,136 @@
// Copyright (c) 2022 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.
// This might work on other BSDs, but only tested on FreeBSD.
// Originally a fork of interfaces_darwin.go with slightly different flags.
//go:build freebsd
// +build freebsd
package interfaces
import (
"errors"
"fmt"
"log"
"net"
"syscall"
"golang.org/x/net/route"
"golang.org/x/sys/unix"
"inet.af/netaddr"
)
func defaultRoute() (d DefaultRouteDetails, err error) {
idx, err := DefaultRouteInterfaceIndex()
if err != nil {
return d, err
}
iface, err := net.InterfaceByIndex(idx)
if err != nil {
return d, err
}
d.InterfaceName = iface.Name
d.InterfaceIndex = idx
return d, nil
}
// fetchRoutingTable calls route.FetchRIB, fetching NET_RT_DUMP.
func fetchRoutingTable() (rib []byte, err error) {
return route.FetchRIB(syscall.AF_UNSPEC, unix.NET_RT_DUMP, 0)
}
func DefaultRouteInterfaceIndex() (int, error) {
// $ netstat -nr
// Routing tables
// Internet:
// Destination Gateway Flags Netif Expire
// default 10.0.0.1 UGSc en0 <-- want this one
// default 10.0.0.1 UGScI en1
// From man netstat:
// U RTF_UP Route usable
// G RTF_GATEWAY Destination requires forwarding by intermediary
// S RTF_STATIC Manually added
// c RTF_PRCLONING Protocol-specified generate new routes on use
// I RTF_IFSCOPE Route is associated with an interface scope
rib, err := fetchRoutingTable()
if err != nil {
return 0, fmt.Errorf("route.FetchRIB: %w", err)
}
msgs, err := route.ParseRIB(unix.NET_RT_IFLIST, rib)
if err != nil {
return 0, fmt.Errorf("route.ParseRIB: %w", err)
}
indexSeen := map[int]int{} // index => count
for _, m := range msgs {
rm, ok := m.(*route.RouteMessage)
if !ok {
continue
}
const RTF_GATEWAY = 0x2
const RTF_IFSCOPE = 0x1000000
if rm.Flags&RTF_GATEWAY == 0 {
continue
}
if rm.Flags&RTF_IFSCOPE != 0 {
continue
}
indexSeen[rm.Index]++
}
if len(indexSeen) == 0 {
return 0, errors.New("no gateway index found")
}
if len(indexSeen) == 1 {
for idx := range indexSeen {
return idx, nil
}
}
return 0, fmt.Errorf("ambiguous gateway interfaces found: %v", indexSeen)
}
func init() {
likelyHomeRouterIP = likelyHomeRouterIPBSDFetchRIB
}
func likelyHomeRouterIPBSDFetchRIB() (ret netaddr.IP, ok bool) {
rib, err := fetchRoutingTable()
if err != nil {
log.Printf("routerIP/FetchRIB: %v", err)
return ret, false
}
msgs, err := route.ParseRIB(unix.NET_RT_IFLIST, rib)
if err != nil {
log.Printf("routerIP/ParseRIB: %v", err)
return ret, false
}
for _, m := range msgs {
rm, ok := m.(*route.RouteMessage)
if !ok {
continue
}
const RTF_IFSCOPE = 0x1000000
if rm.Flags&unix.RTF_GATEWAY == 0 {
continue
}
if rm.Flags&RTF_IFSCOPE != 0 {
continue
}
if len(rm.Addrs) > unix.RTAX_GATEWAY {
dst4, ok := rm.Addrs[unix.RTAX_DST].(*route.Inet4Addr)
if !ok || dst4.IP != ([4]byte{0, 0, 0, 0}) {
// Expect 0.0.0.0 as DST field.
continue
}
gw, ok := rm.Addrs[unix.RTAX_GATEWAY].(*route.Inet4Addr)
if !ok {
continue
}
return netaddr.IPv4(gw.IP[0], gw.IP[1], gw.IP[2], gw.IP[3]), true
}
}
return ret, false
}

View File

@@ -2,8 +2,8 @@
// 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
// +build !linux,!windows,!darwin
//go:build !linux && !windows && !darwin && !freebsd
// +build !linux,!windows,!darwin,!freebsd
package interfaces

View File

@@ -11,12 +11,16 @@ import (
"fmt"
"io"
"log"
"net"
"os"
"os/exec"
"runtime"
"strings"
"github.com/jsimonetti/rtnetlink"
"github.com/mdlayher/netlink"
"go4.org/mem"
"golang.org/x/sys/unix"
"inet.af/netaddr"
"tailscale.com/syncs"
"tailscale.com/util/lineread"
@@ -70,9 +74,7 @@ func likelyHomeRouterIPLinux() (ret netaddr.IP, ok bool) {
if err != nil {
return nil // ignore error, skip line and keep going
}
const RTF_UP = 0x0001
const RTF_GATEWAY = 0x0002
if flags&(RTF_UP|RTF_GATEWAY) != RTF_UP|RTF_GATEWAY {
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
return nil
}
ipu32, err := mem.ParseUint(gwHex, 16, 32)
@@ -145,7 +147,62 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
d.InterfaceName = v
return d, err
}
return d, err
// Issue 4038: the default route (such as on Unifi UDM Pro)
// might be in a non-default table, so it won't show up in
// /proc/net/route. Use netlink to find the default route.
//
// TODO(bradfitz): this allocates a fair bit. We should track
// this in wgengine/monitor instead and have
// interfaces.GetState take a link monitor or similar so the
// routing table can be cached and the monitor's existing
// subscription to route changes can update the cached state,
// rather than querying the whole thing every time like
// defaultRouteFromNetlink does.
//
// Then we should just always try to use the cached route
// table from netlink every time, and only use /proc/net/route
// as a fallback for weird environments where netlink might be
// banned but /proc/net/route is emulated (e.g. stuff like
// Cloud Run?).
return defaultRouteFromNetlink()
}
func defaultRouteFromNetlink() (d DefaultRouteDetails, err error) {
c, err := rtnetlink.Dial(&netlink.Config{Strict: true})
if err != nil {
return d, fmt.Errorf("defaultRouteFromNetlink: Dial: %w", err)
}
defer c.Close()
rms, err := c.Route.List()
if err != nil {
return d, fmt.Errorf("defaultRouteFromNetlink: List: %w", err)
}
for _, rm := range rms {
if rm.Attributes.Gateway == nil {
// A default route has a gateway. If it doesn't, skip it.
continue
}
if rm.Attributes.Dst != nil {
// A default route has a nil destination to mean anything
// so ignore any route for a specific destination.
// TODO(bradfitz): better heuristic?
// empirically this seems like enough.
continue
}
// TODO(bradfitz): care about address family, if
// callers ever start caring about v4-vs-v6 default
// route differences.
idx := int(rm.Attributes.OutIface)
if idx == 0 {
continue
}
if iface, err := net.InterfaceByIndex(idx); err == nil {
d.InterfaceName = iface.Name
d.InterfaceIndex = idx
return d, nil
}
}
return d, errNoDefaultRoute
}
var zeroRouteBytes = []byte("00000000")
@@ -155,6 +212,8 @@ var procNetRoutePath = "/proc/net/route"
// /proc/net/route looking for a default route.
const maxProcNetRouteRead = 1000
var errNoDefaultRoute = errors.New("no default route found")
func defaultRouteInterfaceProcNetInternal(bufsize int) (string, error) {
f, err := os.Open(procNetRoutePath)
if err != nil {
@@ -168,7 +227,7 @@ func defaultRouteInterfaceProcNetInternal(bufsize int) (string, error) {
lineNum++
line, err := br.ReadSlice('\n')
if err == io.EOF || lineNum > maxProcNetRouteRead {
return "", fmt.Errorf("no default routes found: %w", err)
return "", errNoDefaultRoute
}
if err != nil {
return "", err

View File

@@ -5,7 +5,9 @@
package interfaces
import (
"errors"
"fmt"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
@@ -107,3 +109,14 @@ func BenchmarkDefaultRouteInterface(b *testing.B) {
}
}
}
func TestRouteLinuxNetlink(t *testing.T) {
d, err := defaultRouteFromNetlink()
if errors.Is(err, fs.ErrPermission) {
t.Skip(err)
}
if err != nil {
t.Fatal(err)
}
t.Logf("Got: %+v", d)
}

View File

@@ -112,11 +112,11 @@ func (s *Server) Serve(l net.Listener) error {
return err
}
go func() {
defer c.Close()
conn := &Conn{clientConn: c, srv: s}
err := conn.Run()
if err != nil {
s.logf("client connection failed: %v", err)
conn.clientConn.Close()
}
}()
}

View File

@@ -28,7 +28,8 @@ func DefaultTailscaledSocket() string {
if runtime.GOOS == "darwin" {
return "/var/run/tailscaled.socket"
}
if distro.Get() == distro.Synology {
switch distro.Get() {
case distro.Synology:
// TODO(maisem): be smarter about this. We can parse /etc/VERSION.
const dsm6Sock = "/var/packages/Tailscale/etc/tailscaled.sock"
const dsm7Sock = "/var/packages/Tailscale/var/tailscaled.sock"
@@ -38,6 +39,8 @@ func DefaultTailscaledSocket() string {
if fi, err := os.Stat(dsm7Sock); err == nil && !fi.IsDir() {
return dsm7Sock
}
case distro.Gokrazy:
return "/perm/tailscaled/tailscaled.sock"
}
if fi, err := os.Stat("/var/run"); err == nil && fi.IsDir() {
return "/var/run/tailscale/tailscaled.sock"

View File

@@ -14,6 +14,7 @@ import (
"runtime"
"golang.org/x/sys/unix"
"tailscale.com/version/distro"
)
func init() {
@@ -34,6 +35,9 @@ func statePath() string {
}
func stateFileUnix() string {
if distro.Get() == distro.Gokrazy {
return "/perm/tailscaled/tailscaled.state"
}
path := statePath()
if path == "" {
return ""

View File

@@ -19,6 +19,6 @@ pkgs.mkShell {
# - gopls, the language server for Go to increase editor integration
# - git, the version control program (used in some scripts)
buildInputs = with pkgs; [
go goimports gopls git
go gopls git
];
}

View File

@@ -53,7 +53,7 @@ func (t Time) After(n Time) bool {
return t > n
}
// After reports t < n, whether t is before n.
// Before reports t < n, whether t is before n.
func (t Time) Before(n Time) bool {
return t < n
}

View File

@@ -16,7 +16,6 @@ package rate
import (
"context"
"math"
"runtime"
"sync"
"sync/atomic"
"testing"
@@ -155,61 +154,6 @@ func TestSimultaneousRequests(t *testing.T) {
}
}
func TestLongRunningQPS(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
if runtime.GOOS == "openbsd" {
t.Skip("low resolution time.Sleep invalidates test (golang.org/issue/14183)")
return
}
// The test runs for a few seconds executing many requests and then checks
// that overall number of requests is reasonable.
const (
limit = 100
burst = 100
)
var numOK = int32(0)
lim := NewLimiter(limit, burst)
var wg sync.WaitGroup
f := func() {
if ok := lim.Allow(); ok {
atomic.AddInt32(&numOK, 1)
}
wg.Done()
}
// This will still offer ~500 requests per second,
// but won't consume outrageous amount of CPU.
start := time.Now()
end := start.Add(1 * time.Second)
ticker := time.NewTicker(2 * time.Millisecond)
defer ticker.Stop()
for now := range ticker.C {
if now.After(end) {
break
}
wg.Add(1)
go f()
}
wg.Wait()
elapsed := time.Since(start)
ideal := burst + (limit * float64(elapsed) / float64(time.Second))
// We should never get more requests than allowed.
if want := int32(ideal + 1); numOK > want {
t.Errorf("numOK = %d, want %d (ideal %f)", numOK, want, ideal)
}
// We should get close-ish to the number of requests allowed.
// Trying to get too close causes flakes. Treat this as a sanity check.
if want := int32(0.9 * ideal); numOK < want {
t.Errorf("numOK = %d, want %d (ideal %f)", numOK, want, ideal)
}
}
type request struct {
t time.Time
n int

View File

@@ -8,6 +8,7 @@ package distro
import (
"os"
"runtime"
"sync/atomic"
)
type Distro string
@@ -22,17 +23,25 @@ const (
Pfsense = Distro("pfsense")
OPNsense = Distro("opnsense")
TrueNAS = Distro("truenas")
Gokrazy = Distro("gokrazy")
)
var distroAtomic atomic.Value // of Distro
// Get returns the current distro, or the empty string if unknown.
func Get() Distro {
if runtime.GOOS == "linux" {
return linuxDistro()
d, ok := distroAtomic.Load().(Distro)
if ok {
return d
}
if runtime.GOOS == "freebsd" {
return freebsdDistro()
switch runtime.GOOS {
case "linux":
d = linuxDistro()
case "freebsd":
d = freebsdDistro()
}
return ""
distroAtomic.Store(d) // even if empty
return d
}
func have(file string) bool {
@@ -62,6 +71,8 @@ func linuxDistro() Distro {
return NixOS
case have("/etc/config/uLinux.conf"):
return QNAP
case haveDir("/gokrazy"):
return Gokrazy
}
return ""
}

View File

@@ -0,0 +1,16 @@
// Copyright (c) 2022 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 distro
import "testing"
func BenchmarkGet(b *testing.B) {
b.ReportAllocs()
var d Distro
for i := 0; i < b.N; i++ {
d = Get()
}
_ = d
}

View File

@@ -118,6 +118,7 @@ type userspaceEngine struct {
lastEngineSigFull deephash.Sum // of full wireguard config
lastEngineSigTrim deephash.Sum // of trimmed wireguard config
lastDNSConfig *dns.Config
lastIsSubnetRouter bool // was the node a primary subnet router in the last run.
recvActivityAt map[key.NodePublic]mono.Time
trimmedNodes map[key.NodePublic]bool // set of node keys of peers currently excluded from wireguard config
sentActivityAt map[netaddr.IP]*mono.Time // value is accessed atomically
@@ -125,8 +126,6 @@ type userspaceEngine struct {
statusBufioReader *bufio.Reader // reusable for UAPI
lastStatusPollTime mono.Time // last time we polled the engine status
lastIsSubnetRouter bool // was the node a primary subnet router in the last run.
mu sync.Mutex // guards following; see lock order comment below
netMap *netmap.NetworkMap // or nil
closing bool // Close was called (even if we're still closing)
@@ -854,6 +853,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
e.peerSequence = append(e.peerSequence, p.PublicKey)
peerSet[p.PublicKey] = struct{}{}
}
nm := e.netMap
e.mu.Unlock()
listenPort := e.confListenPort
@@ -862,8 +862,8 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
}
isSubnetRouter := false
if e.birdClient != nil {
isSubnetRouter = hasOverlap(e.netMap.SelfNode.PrimaryRoutes, e.netMap.Hostinfo.RoutableIPs)
if e.birdClient != nil && nm != nil && nm.SelfNode != nil {
isSubnetRouter = hasOverlap(nm.SelfNode.PrimaryRoutes, nm.Hostinfo.RoutableIPs)
}
isSubnetRouterChanged := isSubnetRouter != e.lastIsSubnetRouter