Compare commits
12 Commits
dependabot
...
v1.22.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8fb4f8c79 | ||
|
|
6562f4c6c7 | ||
|
|
9fd9abfd3f | ||
|
|
24319e840d | ||
|
|
0a399bb6c6 | ||
|
|
0b5b3287cb | ||
|
|
dee0b7f8b8 | ||
|
|
231f8b74e4 | ||
|
|
d0698cfcec | ||
|
|
bfd7f9d318 | ||
|
|
fca3592c1c | ||
|
|
4e0b00ad83 |
@@ -1 +1 @@
|
||||
1.21.0
|
||||
1.22.1
|
||||
|
||||
@@ -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, " "))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
fd6b85b80f5fbbfaddef34e7ab6ac83384c50e4d
|
||||
dce70b6d327c7a30b81701f4cc134b56c4e6c229
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
|
||||
136
net/interfaces/interfaces_bsd.go
Normal file
136
net/interfaces/interfaces_bsd.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ""
|
||||
}
|
||||
|
||||
16
version/distro/distro_test.go
Normal file
16
version/distro/distro_test.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user