summaryrefslogtreecommitdiffhomepage
path: root/types
diff options
context:
space:
mode:
authorNick Khyl <nickk@tailscale.com>2024-12-05 13:16:48 -0600
committerNick Khyl <nickk@tailscale.com>2024-12-05 13:16:48 -0600
commit0267fe83b200f1702a2fa0a395442c02a053fadb (patch)
tree63654c55225eeb834de59a5a0bc8d19033c6145b /types
parent87546a5edf6b6503a87eeb2d666baba57398a066 (diff)
downloadtailscale-1.78.0.tar.xz
tailscale-1.78.0.zip
VERSION.txt: this is v1.78.0v1.78.0
Signed-off-by: Nick Khyl <nickk@tailscale.com>
Diffstat (limited to 'types')
-rw-r--r--types/appctype/appconnector_test.go156
-rw-r--r--types/dnstype/dnstype.go136
-rw-r--r--types/empty/message.go26
-rw-r--r--types/flagtype/flagtype.go90
-rw-r--r--types/ipproto/ipproto.go398
-rw-r--r--types/key/chal.go182
-rw-r--r--types/key/control.go136
-rw-r--r--types/key/control_test.go76
-rw-r--r--types/key/disco_test.go166
-rw-r--r--types/key/machine.go528
-rw-r--r--types/key/machine_test.go238
-rw-r--r--types/key/nl_test.go96
-rw-r--r--types/lazy/unsync.go198
-rw-r--r--types/lazy/unsync_test.go280
-rw-r--r--types/logger/rusage.go46
-rw-r--r--types/logger/rusage_stub.go22
-rw-r--r--types/logger/rusage_syscall.go58
-rw-r--r--types/logger/tokenbucket.go126
-rw-r--r--types/netlogtype/netlogtype.go200
-rw-r--r--types/netlogtype/netlogtype_test.go78
-rw-r--r--types/netmap/netmap_test.go636
-rw-r--r--types/nettype/nettype.go130
-rw-r--r--types/preftype/netfiltermode.go92
-rw-r--r--types/ptr/ptr.go20
-rw-r--r--types/structs/structs.go30
-rw-r--r--types/tkatype/tkatype.go80
-rw-r--r--types/tkatype/tkatype_test.go86
27 files changed, 2155 insertions, 2155 deletions
diff --git a/types/appctype/appconnector_test.go b/types/appctype/appconnector_test.go
index 390d1776a..8aef135b4 100644
--- a/types/appctype/appconnector_test.go
+++ b/types/appctype/appconnector_test.go
@@ -1,78 +1,78 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package appctype
-
-import (
- "encoding/json"
- "net/netip"
- "strings"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "tailscale.com/tailcfg"
- "tailscale.com/util/must"
-)
-
-var golden = `{
- "dnat": {
- "opaqueid1": {
- "addrs": ["100.64.0.1", "fd7a:115c:a1e0::1"],
- "to": ["example.org"],
- "ip": ["*"]
- }
- },
- "sniProxy": {
- "opaqueid2": {
- "addrs": ["::"],
- "ip": ["tcp:443"],
- "allowedDomains": ["*"]
- }
- },
- "advertiseRoutes": true
-}`
-
-func TestGolden(t *testing.T) {
- wantDNAT := map[ConfigID]DNATConfig{"opaqueid1": {
- Addrs: []netip.Addr{netip.MustParseAddr("100.64.0.1"), netip.MustParseAddr("fd7a:115c:a1e0::1")},
- To: []string{"example.org"},
- IP: []tailcfg.ProtoPortRange{{Proto: 0, Ports: tailcfg.PortRange{First: 0, Last: 65535}}},
- }}
-
- wantSNI := map[ConfigID]SNIProxyConfig{"opaqueid2": {
- Addrs: []netip.Addr{netip.MustParseAddr("::")},
- IP: []tailcfg.ProtoPortRange{{Proto: 6, Ports: tailcfg.PortRange{First: 443, Last: 443}}},
- AllowedDomains: []string{"*"},
- }}
-
- var config AppConnectorConfig
- if err := json.NewDecoder(strings.NewReader(golden)).Decode(&config); err != nil {
- t.Fatalf("failed to decode golden config: %v", err)
- }
-
- if !config.AdvertiseRoutes {
- t.Fatalf("expected AdvertiseRoutes to be true, got false")
- }
-
- assertEqual(t, "DNAT", config.DNAT, wantDNAT)
- assertEqual(t, "SNI", config.SNIProxy, wantSNI)
-}
-
-func TestRoundTrip(t *testing.T) {
- var config AppConnectorConfig
- must.Do(json.NewDecoder(strings.NewReader(golden)).Decode(&config))
- b := must.Get(json.Marshal(config))
- var config2 AppConnectorConfig
- must.Do(json.Unmarshal(b, &config2))
- assertEqual(t, "DNAT", config.DNAT, config2.DNAT)
-}
-
-func assertEqual(t *testing.T, name string, a, b any) {
- var addrComparer = cmp.Comparer(func(a, b netip.Addr) bool {
- return a.Compare(b) == 0
- })
- t.Helper()
- if diff := cmp.Diff(a, b, addrComparer); diff != "" {
- t.Fatalf("mismatch (-want +got):\n%s", diff)
- }
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package appctype
+
+import (
+ "encoding/json"
+ "net/netip"
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "tailscale.com/tailcfg"
+ "tailscale.com/util/must"
+)
+
+var golden = `{
+ "dnat": {
+ "opaqueid1": {
+ "addrs": ["100.64.0.1", "fd7a:115c:a1e0::1"],
+ "to": ["example.org"],
+ "ip": ["*"]
+ }
+ },
+ "sniProxy": {
+ "opaqueid2": {
+ "addrs": ["::"],
+ "ip": ["tcp:443"],
+ "allowedDomains": ["*"]
+ }
+ },
+ "advertiseRoutes": true
+}`
+
+func TestGolden(t *testing.T) {
+ wantDNAT := map[ConfigID]DNATConfig{"opaqueid1": {
+ Addrs: []netip.Addr{netip.MustParseAddr("100.64.0.1"), netip.MustParseAddr("fd7a:115c:a1e0::1")},
+ To: []string{"example.org"},
+ IP: []tailcfg.ProtoPortRange{{Proto: 0, Ports: tailcfg.PortRange{First: 0, Last: 65535}}},
+ }}
+
+ wantSNI := map[ConfigID]SNIProxyConfig{"opaqueid2": {
+ Addrs: []netip.Addr{netip.MustParseAddr("::")},
+ IP: []tailcfg.ProtoPortRange{{Proto: 6, Ports: tailcfg.PortRange{First: 443, Last: 443}}},
+ AllowedDomains: []string{"*"},
+ }}
+
+ var config AppConnectorConfig
+ if err := json.NewDecoder(strings.NewReader(golden)).Decode(&config); err != nil {
+ t.Fatalf("failed to decode golden config: %v", err)
+ }
+
+ if !config.AdvertiseRoutes {
+ t.Fatalf("expected AdvertiseRoutes to be true, got false")
+ }
+
+ assertEqual(t, "DNAT", config.DNAT, wantDNAT)
+ assertEqual(t, "SNI", config.SNIProxy, wantSNI)
+}
+
+func TestRoundTrip(t *testing.T) {
+ var config AppConnectorConfig
+ must.Do(json.NewDecoder(strings.NewReader(golden)).Decode(&config))
+ b := must.Get(json.Marshal(config))
+ var config2 AppConnectorConfig
+ must.Do(json.Unmarshal(b, &config2))
+ assertEqual(t, "DNAT", config.DNAT, config2.DNAT)
+}
+
+func assertEqual(t *testing.T, name string, a, b any) {
+ var addrComparer = cmp.Comparer(func(a, b netip.Addr) bool {
+ return a.Compare(b) == 0
+ })
+ t.Helper()
+ if diff := cmp.Diff(a, b, addrComparer); diff != "" {
+ t.Fatalf("mismatch (-want +got):\n%s", diff)
+ }
+}
diff --git a/types/dnstype/dnstype.go b/types/dnstype/dnstype.go
index b7f5b9d02..6cc91c999 100644
--- a/types/dnstype/dnstype.go
+++ b/types/dnstype/dnstype.go
@@ -1,68 +1,68 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-// Package dnstype defines types for working with DNS.
-package dnstype
-
-//go:generate go run tailscale.com/cmd/viewer --type=Resolver --clonefunc=true
-
-import (
- "net/netip"
- "slices"
-)
-
-// Resolver is the configuration for one DNS resolver.
-type Resolver struct {
- // Addr is the address of the DNS resolver, one of:
- // - A plain IP address for a "classic" UDP+TCP DNS resolver.
- // This is the common format as sent by the control plane.
- // - An IP:port, for tests.
- // - "https://resolver.com/path" for DNS over HTTPS; currently
- // as of 2022-09-08 only used for certain well-known resolvers
- // (see the publicdns package) for which the IP addresses to dial DoH are
- // known ahead of time, so bootstrap DNS resolution is not required.
- // - "http://node-address:port/path" for DNS over HTTP over WireGuard. This
- // is implemented in the PeerAPI for exit nodes and app connectors.
- // - [TODO] "tls://resolver.com" for DNS over TCP+TLS
- Addr string `json:",omitempty"`
-
- // BootstrapResolution is an optional suggested resolution for the
- // DoT/DoH resolver, if the resolver URL does not reference an IP
- // address directly.
- // BootstrapResolution may be empty, in which case clients should
- // look up the DoT/DoH server using their local "classic" DNS
- // resolver.
- //
- // As of 2022-09-08, BootstrapResolution is not yet used.
- BootstrapResolution []netip.Addr `json:",omitempty"`
-}
-
-// IPPort returns r.Addr as an IP address and port if either
-// r.Addr is an IP address (the common case) or if r.Addr
-// is an IP:port (as done in tests).
-func (r *Resolver) IPPort() (ipp netip.AddrPort, ok bool) {
- if r.Addr == "" || r.Addr[0] == 'h' || r.Addr[0] == 't' {
- // Fast path to avoid ParseIP error allocation for obviously not IP
- // cases.
- return
- }
- if ip, err := netip.ParseAddr(r.Addr); err == nil {
- return netip.AddrPortFrom(ip, 53), true
- }
- if ipp, err := netip.ParseAddrPort(r.Addr); err == nil {
- return ipp, true
- }
- return
-}
-
-// Equal reports whether r and other are equal.
-func (r *Resolver) Equal(other *Resolver) bool {
- if r == nil || other == nil {
- return r == other
- }
- if r == other {
- return true
- }
-
- return r.Addr == other.Addr && slices.Equal(r.BootstrapResolution, other.BootstrapResolution)
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package dnstype defines types for working with DNS.
+package dnstype
+
+//go:generate go run tailscale.com/cmd/viewer --type=Resolver --clonefunc=true
+
+import (
+ "net/netip"
+ "slices"
+)
+
+// Resolver is the configuration for one DNS resolver.
+type Resolver struct {
+ // Addr is the address of the DNS resolver, one of:
+ // - A plain IP address for a "classic" UDP+TCP DNS resolver.
+ // This is the common format as sent by the control plane.
+ // - An IP:port, for tests.
+ // - "https://resolver.com/path" for DNS over HTTPS; currently
+ // as of 2022-09-08 only used for certain well-known resolvers
+ // (see the publicdns package) for which the IP addresses to dial DoH are
+ // known ahead of time, so bootstrap DNS resolution is not required.
+ // - "http://node-address:port/path" for DNS over HTTP over WireGuard. This
+ // is implemented in the PeerAPI for exit nodes and app connectors.
+ // - [TODO] "tls://resolver.com" for DNS over TCP+TLS
+ Addr string `json:",omitempty"`
+
+ // BootstrapResolution is an optional suggested resolution for the
+ // DoT/DoH resolver, if the resolver URL does not reference an IP
+ // address directly.
+ // BootstrapResolution may be empty, in which case clients should
+ // look up the DoT/DoH server using their local "classic" DNS
+ // resolver.
+ //
+ // As of 2022-09-08, BootstrapResolution is not yet used.
+ BootstrapResolution []netip.Addr `json:",omitempty"`
+}
+
+// IPPort returns r.Addr as an IP address and port if either
+// r.Addr is an IP address (the common case) or if r.Addr
+// is an IP:port (as done in tests).
+func (r *Resolver) IPPort() (ipp netip.AddrPort, ok bool) {
+ if r.Addr == "" || r.Addr[0] == 'h' || r.Addr[0] == 't' {
+ // Fast path to avoid ParseIP error allocation for obviously not IP
+ // cases.
+ return
+ }
+ if ip, err := netip.ParseAddr(r.Addr); err == nil {
+ return netip.AddrPortFrom(ip, 53), true
+ }
+ if ipp, err := netip.ParseAddrPort(r.Addr); err == nil {
+ return ipp, true
+ }
+ return
+}
+
+// Equal reports whether r and other are equal.
+func (r *Resolver) Equal(other *Resolver) bool {
+ if r == nil || other == nil {
+ return r == other
+ }
+ if r == other {
+ return true
+ }
+
+ return r.Addr == other.Addr && slices.Equal(r.BootstrapResolution, other.BootstrapResolution)
+}
diff --git a/types/empty/message.go b/types/empty/message.go
index dc8eb4cc2..5ada7f402 100644
--- a/types/empty/message.go
+++ b/types/empty/message.go
@@ -1,13 +1,13 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-// Package empty defines an empty struct type.
-package empty
-
-// Message is an empty message. Its purpose is to be used as pointer
-// type where nil and non-nil distinguish whether it's set. This is
-// used instead of a bool when we want to marshal it as a JSON empty
-// object (or null) for the future ability to add other fields, at
-// which point callers would define a new struct and not use
-// empty.Message.
-type Message struct{}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package empty defines an empty struct type.
+package empty
+
+// Message is an empty message. Its purpose is to be used as pointer
+// type where nil and non-nil distinguish whether it's set. This is
+// used instead of a bool when we want to marshal it as a JSON empty
+// object (or null) for the future ability to add other fields, at
+// which point callers would define a new struct and not use
+// empty.Message.
+type Message struct{}
diff --git a/types/flagtype/flagtype.go b/types/flagtype/flagtype.go
index be160dee8..c76b16353 100644
--- a/types/flagtype/flagtype.go
+++ b/types/flagtype/flagtype.go
@@ -1,45 +1,45 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-// Package flagtype defines flag.Value types.
-package flagtype
-
-import (
- "errors"
- "flag"
- "fmt"
- "math"
- "strconv"
- "strings"
-)
-
-type portValue struct{ n *uint16 }
-
-func PortValue(dst *uint16, defaultPort uint16) flag.Value {
- *dst = defaultPort
- return portValue{dst}
-}
-
-func (p portValue) String() string {
- if p.n == nil {
- return ""
- }
- return fmt.Sprint(*p.n)
-}
-func (p portValue) Set(v string) error {
- if v == "" {
- return errors.New("can't be the empty string")
- }
- if strings.Contains(v, ":") {
- return errors.New("expecting just a port number, without a colon")
- }
- n, err := strconv.ParseUint(v, 10, 64) // use 64 instead of 16 to return nicer error message
- if err != nil {
- return fmt.Errorf("not a valid number")
- }
- if n > math.MaxUint16 {
- return errors.New("out of range for port number")
- }
- *p.n = uint16(n)
- return nil
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package flagtype defines flag.Value types.
+package flagtype
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "math"
+ "strconv"
+ "strings"
+)
+
+type portValue struct{ n *uint16 }
+
+func PortValue(dst *uint16, defaultPort uint16) flag.Value {
+ *dst = defaultPort
+ return portValue{dst}
+}
+
+func (p portValue) String() string {
+ if p.n == nil {
+ return ""
+ }
+ return fmt.Sprint(*p.n)
+}
+func (p portValue) Set(v string) error {
+ if v == "" {
+ return errors.New("can't be the empty string")
+ }
+ if strings.Contains(v, ":") {
+ return errors.New("expecting just a port number, without a colon")
+ }
+ n, err := strconv.ParseUint(v, 10, 64) // use 64 instead of 16 to return nicer error message
+ if err != nil {
+ return fmt.Errorf("not a valid number")
+ }
+ if n > math.MaxUint16 {
+ return errors.New("out of range for port number")
+ }
+ *p.n = uint16(n)
+ return nil
+}
diff --git a/types/ipproto/ipproto.go b/types/ipproto/ipproto.go
index b5333eb56..97fc4f3dd 100644
--- a/types/ipproto/ipproto.go
+++ b/types/ipproto/ipproto.go
@@ -1,199 +1,199 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-// Package ipproto contains IP Protocol constants.
-package ipproto
-
-import (
- "fmt"
- "strconv"
-
- "tailscale.com/util/nocasemaps"
- "tailscale.com/util/vizerror"
-)
-
-// Version describes the IP address version.
-type Version uint8
-
-// Valid Version values.
-const (
- Version4 = 4
- Version6 = 6
-)
-
-func (p Version) String() string {
- switch p {
- case Version4:
- return "IPv4"
- case Version6:
- return "IPv6"
- default:
- return fmt.Sprintf("Version-%d", int(p))
- }
-}
-
-// Proto is an IP subprotocol as defined by the IANA protocol
-// numbers list
-// (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml),
-// or the special values Unknown or Fragment.
-type Proto uint8
-
-const (
- // Unknown represents an unknown or unsupported protocol; it's
- // deliberately the zero value. Strictly speaking the zero
- // value is IPv6 hop-by-hop extensions, but we don't support
- // those, so this is still technically correct.
- Unknown Proto = 0x00
-
- // Values from the IANA registry.
- ICMPv4 Proto = 0x01
- IGMP Proto = 0x02
- ICMPv6 Proto = 0x3a
- TCP Proto = 0x06
- UDP Proto = 0x11
- DCCP Proto = 0x21
- GRE Proto = 0x2f
- SCTP Proto = 0x84
-
- // TSMP is the Tailscale Message Protocol (our ICMP-ish
- // thing), an IP protocol used only between Tailscale nodes
- // (still encrypted by WireGuard) that communicates why things
- // failed, etc.
- //
- // Proto number 99 is reserved for "any private encryption
- // scheme". We never accept these from the host OS stack nor
- // send them to the host network stack. It's only used between
- // nodes.
- TSMP Proto = 99
-
- // Fragment represents any non-first IP fragment, for which we
- // don't have the sub-protocol header (and therefore can't
- // figure out what the sub-protocol is).
- //
- // 0xFF is reserved in the IANA registry, so we steal it for
- // internal use.
- Fragment Proto = 0xFF
-)
-
-// Deprecated: use MarshalText instead.
-func (p Proto) String() string {
- switch p {
- case Unknown:
- return "Unknown"
- case Fragment:
- return "Frag"
- case ICMPv4:
- return "ICMPv4"
- case IGMP:
- return "IGMP"
- case ICMPv6:
- return "ICMPv6"
- case UDP:
- return "UDP"
- case TCP:
- return "TCP"
- case SCTP:
- return "SCTP"
- case TSMP:
- return "TSMP"
- case GRE:
- return "GRE"
- case DCCP:
- return "DCCP"
- default:
- return fmt.Sprintf("IPProto-%d", int(p))
- }
-}
-
-// Prefer names from
-// https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
-// unless otherwise noted.
-var (
- // preferredNames is the set of protocol names that re produced by
- // MarshalText, and are the preferred representation.
- preferredNames = map[Proto]string{
- 51: "ah",
- DCCP: "dccp",
- 8: "egp",
- 50: "esp",
- 47: "gre",
- ICMPv4: "icmp",
- IGMP: "igmp",
- 9: "igp",
- 4: "ipv4",
- ICMPv6: "ipv6-icmp",
- SCTP: "sctp",
- TCP: "tcp",
- UDP: "udp",
- }
-
- // acceptedNames is the set of protocol names that are accepted by
- // UnmarshalText.
- acceptedNames = map[string]Proto{
- "ah": 51,
- "dccp": DCCP,
- "egp": 8,
- "esp": 50,
- "gre": 47,
- "icmp": ICMPv4,
- "icmpv4": ICMPv4,
- "icmpv6": ICMPv6,
- "igmp": IGMP,
- "igp": 9,
- "ip-in-ip": 4, // IANA says "ipv4"; Wikipedia/popular use says "ip-in-ip"
- "ipv4": 4,
- "ipv6-icmp": ICMPv6,
- "sctp": SCTP,
- "tcp": TCP,
- "tsmp": TSMP,
- "udp": UDP,
- }
-)
-
-// UnmarshalText implements encoding.TextUnmarshaler. If the input is empty, p
-// is set to 0. If an error occurs, p is unchanged.
-func (p *Proto) UnmarshalText(b []byte) error {
- if len(b) == 0 {
- *p = 0
- return nil
- }
-
- if u, err := strconv.ParseUint(string(b), 10, 8); err == nil {
- *p = Proto(u)
- return nil
- }
-
- if newP, ok := nocasemaps.GetOk(acceptedNames, string(b)); ok {
- *p = newP
- return nil
- }
-
- return vizerror.Errorf("proto name %q not known; use protocol number 0-255", b)
-}
-
-// MarshalText implements encoding.TextMarshaler.
-func (p Proto) MarshalText() ([]byte, error) {
- if s, ok := preferredNames[p]; ok {
- return []byte(s), nil
- }
- return []byte(strconv.Itoa(int(p))), nil
-}
-
-// MarshalJSON implements json.Marshaler.
-func (p Proto) MarshalJSON() ([]byte, error) {
- return []byte(strconv.Itoa(int(p))), nil
-}
-
-// UnmarshalJSON implements json.Unmarshaler. If the input is empty, p is set to
-// 0. If an error occurs, p is unchanged. The input must be a JSON number or an
-// accepted string name.
-func (p *Proto) UnmarshalJSON(b []byte) error {
- if len(b) == 0 {
- *p = 0
- return nil
- }
- if b[0] == '"' {
- b = b[1 : len(b)-1]
- }
- return p.UnmarshalText(b)
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package ipproto contains IP Protocol constants.
+package ipproto
+
+import (
+ "fmt"
+ "strconv"
+
+ "tailscale.com/util/nocasemaps"
+ "tailscale.com/util/vizerror"
+)
+
+// Version describes the IP address version.
+type Version uint8
+
+// Valid Version values.
+const (
+ Version4 = 4
+ Version6 = 6
+)
+
+func (p Version) String() string {
+ switch p {
+ case Version4:
+ return "IPv4"
+ case Version6:
+ return "IPv6"
+ default:
+ return fmt.Sprintf("Version-%d", int(p))
+ }
+}
+
+// Proto is an IP subprotocol as defined by the IANA protocol
+// numbers list
+// (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml),
+// or the special values Unknown or Fragment.
+type Proto uint8
+
+const (
+ // Unknown represents an unknown or unsupported protocol; it's
+ // deliberately the zero value. Strictly speaking the zero
+ // value is IPv6 hop-by-hop extensions, but we don't support
+ // those, so this is still technically correct.
+ Unknown Proto = 0x00
+
+ // Values from the IANA registry.
+ ICMPv4 Proto = 0x01
+ IGMP Proto = 0x02
+ ICMPv6 Proto = 0x3a
+ TCP Proto = 0x06
+ UDP Proto = 0x11
+ DCCP Proto = 0x21
+ GRE Proto = 0x2f
+ SCTP Proto = 0x84
+
+ // TSMP is the Tailscale Message Protocol (our ICMP-ish
+ // thing), an IP protocol used only between Tailscale nodes
+ // (still encrypted by WireGuard) that communicates why things
+ // failed, etc.
+ //
+ // Proto number 99 is reserved for "any private encryption
+ // scheme". We never accept these from the host OS stack nor
+ // send them to the host network stack. It's only used between
+ // nodes.
+ TSMP Proto = 99
+
+ // Fragment represents any non-first IP fragment, for which we
+ // don't have the sub-protocol header (and therefore can't
+ // figure out what the sub-protocol is).
+ //
+ // 0xFF is reserved in the IANA registry, so we steal it for
+ // internal use.
+ Fragment Proto = 0xFF
+)
+
+// Deprecated: use MarshalText instead.
+func (p Proto) String() string {
+ switch p {
+ case Unknown:
+ return "Unknown"
+ case Fragment:
+ return "Frag"
+ case ICMPv4:
+ return "ICMPv4"
+ case IGMP:
+ return "IGMP"
+ case ICMPv6:
+ return "ICMPv6"
+ case UDP:
+ return "UDP"
+ case TCP:
+ return "TCP"
+ case SCTP:
+ return "SCTP"
+ case TSMP:
+ return "TSMP"
+ case GRE:
+ return "GRE"
+ case DCCP:
+ return "DCCP"
+ default:
+ return fmt.Sprintf("IPProto-%d", int(p))
+ }
+}
+
+// Prefer names from
+// https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
+// unless otherwise noted.
+var (
+ // preferredNames is the set of protocol names that re produced by
+ // MarshalText, and are the preferred representation.
+ preferredNames = map[Proto]string{
+ 51: "ah",
+ DCCP: "dccp",
+ 8: "egp",
+ 50: "esp",
+ 47: "gre",
+ ICMPv4: "icmp",
+ IGMP: "igmp",
+ 9: "igp",
+ 4: "ipv4",
+ ICMPv6: "ipv6-icmp",
+ SCTP: "sctp",
+ TCP: "tcp",
+ UDP: "udp",
+ }
+
+ // acceptedNames is the set of protocol names that are accepted by
+ // UnmarshalText.
+ acceptedNames = map[string]Proto{
+ "ah": 51,
+ "dccp": DCCP,
+ "egp": 8,
+ "esp": 50,
+ "gre": 47,
+ "icmp": ICMPv4,
+ "icmpv4": ICMPv4,
+ "icmpv6": ICMPv6,
+ "igmp": IGMP,
+ "igp": 9,
+ "ip-in-ip": 4, // IANA says "ipv4"; Wikipedia/popular use says "ip-in-ip"
+ "ipv4": 4,
+ "ipv6-icmp": ICMPv6,
+ "sctp": SCTP,
+ "tcp": TCP,
+ "tsmp": TSMP,
+ "udp": UDP,
+ }
+)
+
+// UnmarshalText implements encoding.TextUnmarshaler. If the input is empty, p
+// is set to 0. If an error occurs, p is unchanged.
+func (p *Proto) UnmarshalText(b []byte) error {
+ if len(b) == 0 {
+ *p = 0
+ return nil
+ }
+
+ if u, err := strconv.ParseUint(string(b), 10, 8); err == nil {
+ *p = Proto(u)
+ return nil
+ }
+
+ if newP, ok := nocasemaps.GetOk(acceptedNames, string(b)); ok {
+ *p = newP
+ return nil
+ }
+
+ return vizerror.Errorf("proto name %q not known; use protocol number 0-255", b)
+}
+
+// MarshalText implements encoding.TextMarshaler.
+func (p Proto) MarshalText() ([]byte, error) {
+ if s, ok := preferredNames[p]; ok {
+ return []byte(s), nil
+ }
+ return []byte(strconv.Itoa(int(p))), nil
+}
+
+// MarshalJSON implements json.Marshaler.
+func (p Proto) MarshalJSON() ([]byte, error) {
+ return []byte(strconv.Itoa(int(p))), nil
+}
+
+// UnmarshalJSON implements json.Unmarshaler. If the input is empty, p is set to
+// 0. If an error occurs, p is unchanged. The input must be a JSON number or an
+// accepted string name.
+func (p *Proto) UnmarshalJSON(b []byte) error {
+ if len(b) == 0 {
+ *p = 0
+ return nil
+ }
+ if b[0] == '"' {
+ b = b[1 : len(b)-1]
+ }
+ return p.UnmarshalText(b)
+}
diff --git a/types/key/chal.go b/types/key/chal.go
index 742ac5479..da15dd1f8 100644
--- a/types/key/chal.go
+++ b/types/key/chal.go
@@ -1,91 +1,91 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package key
-
-import (
- "errors"
-
- "go4.org/mem"
- "tailscale.com/types/structs"
-)
-
-const (
- // chalPublicHexPrefix is the prefix used to identify a
- // hex-encoded challenge public key.
- //
- // This prefix is used in the control protocol, so cannot be
- // changed.
- chalPublicHexPrefix = "chalpub:"
-)
-
-// ChallengePrivate is a challenge key, used to test whether clients control a
-// key they want to prove ownership of.
-//
-// A ChallengePrivate is ephemeral and not serialized to the disk or network.
-type ChallengePrivate struct {
- _ structs.Incomparable // because == isn't constant-time
- k [32]byte
-}
-
-// NewChallenge creates and returns a new node private key.
-func NewChallenge() ChallengePrivate {
- return ChallengePrivate(NewNode())
-}
-
-// Public returns the ChallengePublic for k.
-// Panics if ChallengePublic is zero.
-func (k ChallengePrivate) Public() ChallengePublic {
- pub := NodePrivate(k).Public()
- return ChallengePublic(pub)
-}
-
-// MarshalText implements encoding.TextMarshaler, but by returning an error.
-// It shouldn't need to be marshalled anywhere.
-func (k ChallengePrivate) MarshalText() ([]byte, error) {
- return nil, errors.New("refusing to marshal")
-}
-
-// SealToChallenge is like SealTo, but for a ChallengePublic.
-func (k NodePrivate) SealToChallenge(p ChallengePublic, cleartext []byte) (ciphertext []byte) {
- return k.SealTo(NodePublic(p), cleartext)
-}
-
-// OpenFrom opens the NaCl box ciphertext, which must be a value
-// created by NodePrivate.SealToChallenge, and returns the inner cleartext if
-// ciphertext is a valid box from p to k.
-func (k ChallengePrivate) OpenFrom(p NodePublic, ciphertext []byte) (cleartext []byte, ok bool) {
- return NodePrivate(k).OpenFrom(p, ciphertext)
-}
-
-// ChallengePublic is the public portion of a ChallengePrivate.
-type ChallengePublic struct {
- k [32]byte
-}
-
-// String returns the output of MarshalText as a string.
-func (k ChallengePublic) String() string {
- bs, err := k.MarshalText()
- if err != nil {
- panic(err)
- }
- return string(bs)
-}
-
-// AppendText implements encoding.TextAppender.
-func (k ChallengePublic) AppendText(b []byte) ([]byte, error) {
- return appendHexKey(b, chalPublicHexPrefix, k.k[:]), nil
-}
-
-// MarshalText implements encoding.TextMarshaler.
-func (k ChallengePublic) MarshalText() ([]byte, error) {
- return k.AppendText(nil)
-}
-
-// UnmarshalText implements encoding.TextUnmarshaler.
-func (k *ChallengePublic) UnmarshalText(b []byte) error {
- return parseHex(k.k[:], mem.B(b), mem.S(chalPublicHexPrefix))
-}
-
-// IsZero reports whether k is the zero value.
-func (k ChallengePublic) IsZero() bool { return k == ChallengePublic{} }
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package key
+
+import (
+ "errors"
+
+ "go4.org/mem"
+ "tailscale.com/types/structs"
+)
+
+const (
+ // chalPublicHexPrefix is the prefix used to identify a
+ // hex-encoded challenge public key.
+ //
+ // This prefix is used in the control protocol, so cannot be
+ // changed.
+ chalPublicHexPrefix = "chalpub:"
+)
+
+// ChallengePrivate is a challenge key, used to test whether clients control a
+// key they want to prove ownership of.
+//
+// A ChallengePrivate is ephemeral and not serialized to the disk or network.
+type ChallengePrivate struct {
+ _ structs.Incomparable // because == isn't constant-time
+ k [32]byte
+}
+
+// NewChallenge creates and returns a new node private key.
+func NewChallenge() ChallengePrivate {
+ return ChallengePrivate(NewNode())
+}
+
+// Public returns the ChallengePublic for k.
+// Panics if ChallengePublic is zero.
+func (k ChallengePrivate) Public() ChallengePublic {
+ pub := NodePrivate(k).Public()
+ return ChallengePublic(pub)
+}
+
+// MarshalText implements encoding.TextMarshaler, but by returning an error.
+// It shouldn't need to be marshalled anywhere.
+func (k ChallengePrivate) MarshalText() ([]byte, error) {
+ return nil, errors.New("refusing to marshal")
+}
+
+// SealToChallenge is like SealTo, but for a ChallengePublic.
+func (k NodePrivate) SealToChallenge(p ChallengePublic, cleartext []byte) (ciphertext []byte) {
+ return k.SealTo(NodePublic(p), cleartext)
+}
+
+// OpenFrom opens the NaCl box ciphertext, which must be a value
+// created by NodePrivate.SealToChallenge, and returns the inner cleartext if
+// ciphertext is a valid box from p to k.
+func (k ChallengePrivate) OpenFrom(p NodePublic, ciphertext []byte) (cleartext []byte, ok bool) {
+ return NodePrivate(k).OpenFrom(p, ciphertext)
+}
+
+// ChallengePublic is the public portion of a ChallengePrivate.
+type ChallengePublic struct {
+ k [32]byte
+}
+
+// String returns the output of MarshalText as a string.
+func (k ChallengePublic) String() string {
+ bs, err := k.MarshalText()
+ if err != nil {
+ panic(err)
+ }
+ return string(bs)
+}
+
+// AppendText implements encoding.TextAppender.
+func (k ChallengePublic) AppendText(b []byte) ([]byte, error) {
+ return appendHexKey(b, chalPublicHexPrefix, k.k[:]), nil
+}
+
+// MarshalText implements encoding.TextMarshaler.
+func (k ChallengePublic) MarshalText() ([]byte, error) {
+ return k.AppendText(nil)
+}
+
+// UnmarshalText implements encoding.TextUnmarshaler.
+func (k *ChallengePublic) UnmarshalText(b []byte) error {
+ return parseHex(k.k[:], mem.B(b), mem.S(chalPublicHexPrefix))
+}
+
+// IsZero reports whether k is the zero value.
+func (k ChallengePublic) IsZero() bool { return k == ChallengePublic{} }
diff --git a/types/key/control.go b/types/key/control.go
index 96021249b..a84359771 100644
--- a/types/key/control.go
+++ b/types/key/control.go
@@ -1,68 +1,68 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package key
-
-import "encoding/json"
-
-// ControlPrivate is a Tailscale control plane private key.
-//
-// It is functionally equivalent to a MachinePrivate, but serializes
-// to JSON as a byte array rather than a typed string, because our
-// control plane database stores the key that way.
-//
-// Deprecated: this type should only be used in Tailscale's control
-// plane, where existing database serializations require this
-// less-good serialization format to persist. Other control plane
-// implementations can use MachinePrivate with no downsides.
-type ControlPrivate struct {
- mkey MachinePrivate // unexported so we can limit the API surface to only exactly what we need
-}
-
-// NewControl generates and returns a new control plane private key.
-func NewControl() ControlPrivate {
- return ControlPrivate{NewMachine()}
-}
-
-// IsZero reports whether k is the zero value.
-func (k ControlPrivate) IsZero() bool {
- return k.mkey.IsZero()
-}
-
-// Public returns the MachinePublic for k.
-// Panics if ControlPrivate is zero.
-func (k ControlPrivate) Public() MachinePublic {
- return k.mkey.Public()
-}
-
-// MarshalJSON implements json.Marshaler.
-func (k ControlPrivate) MarshalJSON() ([]byte, error) {
- return json.Marshal(k.mkey.k)
-}
-
-// UnmarshalJSON implements json.Unmarshaler.
-func (k *ControlPrivate) UnmarshalJSON(bs []byte) error {
- return json.Unmarshal(bs, &k.mkey.k)
-}
-
-// SealTo wraps cleartext into a NaCl box (see
-// golang.org/x/crypto/nacl) to p, authenticated from k, using a
-// random nonce.
-//
-// The returned ciphertext is a 24-byte nonce concatenated with the
-// box value.
-func (k ControlPrivate) SealTo(p MachinePublic, cleartext []byte) (ciphertext []byte) {
- return k.mkey.SealTo(p, cleartext)
-}
-
-// SharedKey returns the precomputed Nacl box shared key between k and p.
-func (k ControlPrivate) SharedKey(p MachinePublic) MachinePrecomputedSharedKey {
- return k.mkey.SharedKey(p)
-}
-
-// OpenFrom opens the NaCl box ciphertext, which must be a value
-// created by SealTo, and returns the inner cleartext if ciphertext is
-// a valid box from p to k.
-func (k ControlPrivate) OpenFrom(p MachinePublic, ciphertext []byte) (cleartext []byte, ok bool) {
- return k.mkey.OpenFrom(p, ciphertext)
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package key
+
+import "encoding/json"
+
+// ControlPrivate is a Tailscale control plane private key.
+//
+// It is functionally equivalent to a MachinePrivate, but serializes
+// to JSON as a byte array rather than a typed string, because our
+// control plane database stores the key that way.
+//
+// Deprecated: this type should only be used in Tailscale's control
+// plane, where existing database serializations require this
+// less-good serialization format to persist. Other control plane
+// implementations can use MachinePrivate with no downsides.
+type ControlPrivate struct {
+ mkey MachinePrivate // unexported so we can limit the API surface to only exactly what we need
+}
+
+// NewControl generates and returns a new control plane private key.
+func NewControl() ControlPrivate {
+ return ControlPrivate{NewMachine()}
+}
+
+// IsZero reports whether k is the zero value.
+func (k ControlPrivate) IsZero() bool {
+ return k.mkey.IsZero()
+}
+
+// Public returns the MachinePublic for k.
+// Panics if ControlPrivate is zero.
+func (k ControlPrivate) Public() MachinePublic {
+ return k.mkey.Public()
+}
+
+// MarshalJSON implements json.Marshaler.
+func (k ControlPrivate) MarshalJSON() ([]byte, error) {
+ return json.Marshal(k.mkey.k)
+}
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (k *ControlPrivate) UnmarshalJSON(bs []byte) error {
+ return json.Unmarshal(bs, &k.mkey.k)
+}
+
+// SealTo wraps cleartext into a NaCl box (see
+// golang.org/x/crypto/nacl) to p, authenticated from k, using a
+// random nonce.
+//
+// The returned ciphertext is a 24-byte nonce concatenated with the
+// box value.
+func (k ControlPrivate) SealTo(p MachinePublic, cleartext []byte) (ciphertext []byte) {
+ return k.mkey.SealTo(p, cleartext)
+}
+
+// SharedKey returns the precomputed Nacl box shared key between k and p.
+func (k ControlPrivate) SharedKey(p MachinePublic) MachinePrecomputedSharedKey {
+ return k.mkey.SharedKey(p)
+}
+
+// OpenFrom opens the NaCl box ciphertext, which must be a value
+// created by SealTo, and returns the inner cleartext if ciphertext is
+// a valid box from p to k.
+func (k ControlPrivate) OpenFrom(p MachinePublic, ciphertext []byte) (cleartext []byte, ok bool) {
+ return k.mkey.OpenFrom(p, ciphertext)
+}
diff --git a/types/key/control_test.go b/types/key/control_test.go
index a98a586f3..06e0f36d5 100644
--- a/types/key/control_test.go
+++ b/types/key/control_test.go
@@ -1,38 +1,38 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package key
-
-import (
- "encoding/json"
- "testing"
-)
-
-func TestControlKey(t *testing.T) {
- serialized := `{"PrivateKey":[36,132,249,6,73,141,249,49,9,96,49,60,240,217,253,57,3,69,248,64,178,62,121,73,121,88,115,218,130,145,68,254]}`
- want := ControlPrivate{
- MachinePrivate{
- k: [32]byte{36, 132, 249, 6, 73, 141, 249, 49, 9, 96, 49, 60, 240, 217, 253, 57, 3, 69, 248, 64, 178, 62, 121, 73, 121, 88, 115, 218, 130, 145, 68, 254},
- },
- }
-
- var got struct {
- PrivateKey ControlPrivate
- }
- if err := json.Unmarshal([]byte(serialized), &got); err != nil {
- t.Fatalf("decoding serialized ControlPrivate: %v", err)
- }
-
- if !got.PrivateKey.mkey.Equal(want.mkey) {
- t.Fatalf("Serialized ControlPrivate didn't deserialize as expected, got %v want %v", got.PrivateKey, want)
- }
-
- bs, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("json reserialization of ControlPrivate failed: %v", err)
- }
-
- if got, want := string(bs), serialized; got != want {
- t.Fatalf("ControlPrivate didn't round-trip, got %q want %q", got, want)
- }
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package key
+
+import (
+ "encoding/json"
+ "testing"
+)
+
+func TestControlKey(t *testing.T) {
+ serialized := `{"PrivateKey":[36,132,249,6,73,141,249,49,9,96,49,60,240,217,253,57,3,69,248,64,178,62,121,73,121,88,115,218,130,145,68,254]}`
+ want := ControlPrivate{
+ MachinePrivate{
+ k: [32]byte{36, 132, 249, 6, 73, 141, 249, 49, 9, 96, 49, 60, 240, 217, 253, 57, 3, 69, 248, 64, 178, 62, 121, 73, 121, 88, 115, 218, 130, 145, 68, 254},
+ },
+ }
+
+ var got struct {
+ PrivateKey ControlPrivate
+ }
+ if err := json.Unmarshal([]byte(serialized), &got); err != nil {
+ t.Fatalf("decoding serialized ControlPrivate: %v", err)
+ }
+
+ if !got.PrivateKey.mkey.Equal(want.mkey) {
+ t.Fatalf("Serialized ControlPrivate didn't deserialize as expected, got %v want %v", got.PrivateKey, want)
+ }
+
+ bs, err := json.Marshal(got)
+ if err != nil {
+ t.Fatalf("json reserialization of ControlPrivate failed: %v", err)
+ }
+
+ if got, want := string(bs), serialized; got != want {
+ t.Fatalf("ControlPrivate didn't round-trip, got %q want %q", got, want)
+ }
+}
diff --git a/types/key/disco_test.go b/types/key/disco_test.go
index c62c13cbf..c9d60c828 100644
--- a/types/key/disco_test.go
+++ b/types/key/disco_test.go
@@ -1,83 +1,83 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package key
-
-import (
- "bytes"
- "encoding/json"
- "testing"
-)
-
-func TestDiscoKey(t *testing.T) {
- k := NewDisco()
- if k.IsZero() {
- t.Fatal("DiscoPrivate should not be zero")
- }
-
- p := k.Public()
- if p.IsZero() {
- t.Fatal("DiscoPublic should not be zero")
- }
-
- bs, err := p.MarshalText()
- if err != nil {
- t.Fatal(err)
- }
- if !bytes.HasPrefix(bs, []byte("discokey:")) {
- t.Fatalf("serialization of public discokey %s has wrong prefix", p)
- }
-
- z := DiscoPublic{}
- if !z.IsZero() {
- t.Fatal("IsZero(DiscoPublic{}) is false")
- }
- if s := z.ShortString(); s != "" {
- t.Fatalf("DiscoPublic{}.ShortString() is %q, want \"\"", s)
- }
-}
-
-func TestDiscoSerialization(t *testing.T) {
- serialized := `{
- "Pub":"discokey:50d20b455ecf12bc453f83c2cfdb2a24925d06cf2598dcaa54e91af82ce9f765"
- }`
-
- pub := DiscoPublic{
- k: [32]uint8{
- 0x50, 0xd2, 0xb, 0x45, 0x5e, 0xcf, 0x12, 0xbc, 0x45, 0x3f, 0x83,
- 0xc2, 0xcf, 0xdb, 0x2a, 0x24, 0x92, 0x5d, 0x6, 0xcf, 0x25, 0x98,
- 0xdc, 0xaa, 0x54, 0xe9, 0x1a, 0xf8, 0x2c, 0xe9, 0xf7, 0x65,
- },
- }
-
- type key struct {
- Pub DiscoPublic
- }
-
- var a key
- if err := json.Unmarshal([]byte(serialized), &a); err != nil {
- t.Fatal(err)
- }
- if a.Pub != pub {
- t.Errorf("wrong deserialization of public key, got %#v want %#v", a.Pub, pub)
- }
-
- bs, err := json.MarshalIndent(a, "", " ")
- if err != nil {
- t.Fatal(err)
- }
-
- var b bytes.Buffer
- json.Indent(&b, []byte(serialized), "", " ")
- if got, want := string(bs), b.String(); got != want {
- t.Error("json serialization doesn't roundtrip")
- }
-}
-
-func TestDiscoShared(t *testing.T) {
- k1, k2 := NewDisco(), NewDisco()
- s1, s2 := k1.Shared(k2.Public()), k2.Shared(k1.Public())
- if !s1.Equal(s2) {
- t.Error("k1.Shared(k2) != k2.Shared(k1)")
- }
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package key
+
+import (
+ "bytes"
+ "encoding/json"
+ "testing"
+)
+
+func TestDiscoKey(t *testing.T) {
+ k := NewDisco()
+ if k.IsZero() {
+ t.Fatal("DiscoPrivate should not be zero")
+ }
+
+ p := k.Public()
+ if p.IsZero() {
+ t.Fatal("DiscoPublic should not be zero")
+ }
+
+ bs, err := p.MarshalText()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.HasPrefix(bs, []byte("discokey:")) {
+ t.Fatalf("serialization of public discokey %s has wrong prefix", p)
+ }
+
+ z := DiscoPublic{}
+ if !z.IsZero() {
+ t.Fatal("IsZero(DiscoPublic{}) is false")
+ }
+ if s := z.ShortString(); s != "" {
+ t.Fatalf("DiscoPublic{}.ShortString() is %q, want \"\"", s)
+ }
+}
+
+func TestDiscoSerialization(t *testing.T) {
+ serialized := `{
+ "Pub":"discokey:50d20b455ecf12bc453f83c2cfdb2a24925d06cf2598dcaa54e91af82ce9f765"
+ }`
+
+ pub := DiscoPublic{
+ k: [32]uint8{
+ 0x50, 0xd2, 0xb, 0x45, 0x5e, 0xcf, 0x12, 0xbc, 0x45, 0x3f, 0x83,
+ 0xc2, 0xcf, 0xdb, 0x2a, 0x24, 0x92, 0x5d, 0x6, 0xcf, 0x25, 0x98,
+ 0xdc, 0xaa, 0x54, 0xe9, 0x1a, 0xf8, 0x2c, 0xe9, 0xf7, 0x65,
+ },
+ }
+
+ type key struct {
+ Pub DiscoPublic
+ }
+
+ var a key
+ if err := json.Unmarshal([]byte(serialized), &a); err != nil {
+ t.Fatal(err)
+ }
+ if a.Pub != pub {
+ t.Errorf("wrong deserialization of public key, got %#v want %#v", a.Pub, pub)
+ }
+
+ bs, err := json.MarshalIndent(a, "", " ")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var b bytes.Buffer
+ json.Indent(&b, []byte(serialized), "", " ")
+ if got, want := string(bs), b.String(); got != want {
+ t.Error("json serialization doesn't roundtrip")
+ }
+}
+
+func TestDiscoShared(t *testing.T) {
+ k1, k2 := NewDisco(), NewDisco()
+ s1, s2 := k1.Shared(k2.Public()), k2.Shared(k1.Public())
+ if !s1.Equal(s2) {
+ t.Error("k1.Shared(k2) != k2.Shared(k1)")
+ }
+}
diff --git a/types/key/machine.go b/types/key/machine.go
index a05f3cc1f..0dc02574c 100644
--- a/types/key/machine.go
+++ b/types/key/machine.go
@@ -1,264 +1,264 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package key
-
-import (
- "bytes"
- "crypto/subtle"
- "encoding/hex"
-
- "go4.org/mem"
- "golang.org/x/crypto/curve25519"
- "golang.org/x/crypto/nacl/box"
- "tailscale.com/types/structs"
-)
-
-const (
- // machinePrivateHexPrefix is the prefix used to identify a
- // hex-encoded machine private key.
- //
- // This prefix name is a little unfortunate, in that it comes from
- // WireGuard's own key types. Unfortunately we're stuck with it for
- // machine keys, because we serialize them to disk with this prefix.
- machinePrivateHexPrefix = "privkey:"
-
- // machinePublicHexPrefix is the prefix used to identify a
- // hex-encoded machine public key.
- //
- // This prefix is used in the control protocol, so cannot be
- // changed.
- machinePublicHexPrefix = "mkey:"
-)
-
-// MachinePrivate is a machine key, used for communication with the
-// Tailscale coordination server.
-type MachinePrivate struct {
- _ structs.Incomparable // == isn't constant-time
- k [32]byte
-}
-
-// NewMachine creates and returns a new machine private key.
-func NewMachine() MachinePrivate {
- var ret MachinePrivate
- rand(ret.k[:])
- clamp25519Private(ret.k[:])
- return ret
-}
-
-// IsZero reports whether k is the zero value.
-func (k MachinePrivate) IsZero() bool {
- return k.Equal(MachinePrivate{})
-}
-
-// Equal reports whether k and other are the same key.
-func (k MachinePrivate) Equal(other MachinePrivate) bool {
- return subtle.ConstantTimeCompare(k.k[:], other.k[:]) == 1
-}
-
-// Public returns the MachinePublic for k.
-// Panics if MachinePrivate is zero.
-func (k MachinePrivate) Public() MachinePublic {
- if k.IsZero() {
- panic("can't take the public key of a zero MachinePrivate")
- }
- var ret MachinePublic
- curve25519.ScalarBaseMult(&ret.k, &k.k)
- return ret
-}
-
-// AppendText implements encoding.TextAppender.
-func (k MachinePrivate) AppendText(b []byte) ([]byte, error) {
- return appendHexKey(b, machinePrivateHexPrefix, k.k[:]), nil
-}
-
-// MarshalText implements encoding.TextMarshaler.
-func (k MachinePrivate) MarshalText() ([]byte, error) {
- return k.AppendText(nil)
-}
-
-// MarshalText implements encoding.TextUnmarshaler.
-func (k *MachinePrivate) UnmarshalText(b []byte) error {
- return parseHex(k.k[:], mem.B(b), mem.S(machinePrivateHexPrefix))
-}
-
-// UntypedBytes returns k, encoded as an untyped 64-character hex
-// string.
-//
-// Deprecated: this function is risky to use, because it produces
-// serialized values that do not identify themselves as a
-// MachinePrivate, allowing other code to potentially parse it back in
-// as the wrong key type. For new uses that don't require this
-// specific raw byte serialization, please use
-// MarshalText/UnmarshalText.
-func (k MachinePrivate) UntypedBytes() []byte {
- return bytes.Clone(k.k[:])
-}
-
-// SealTo wraps cleartext into a NaCl box (see
-// golang.org/x/crypto/nacl) to p, authenticated from k, using a
-// random nonce.
-//
-// The returned ciphertext is a 24-byte nonce concatenated with the
-// box value.
-func (k MachinePrivate) SealTo(p MachinePublic, cleartext []byte) (ciphertext []byte) {
- if k.IsZero() || p.IsZero() {
- panic("can't seal with zero keys")
- }
- var nonce [24]byte
- rand(nonce[:])
- return box.Seal(nonce[:], cleartext, &nonce, &p.k, &k.k)
-}
-
-// SharedKey returns the precomputed Nacl box shared key between k and p.
-func (k MachinePrivate) SharedKey(p MachinePublic) MachinePrecomputedSharedKey {
- var shared MachinePrecomputedSharedKey
- box.Precompute(&shared.k, &p.k, &k.k)
- return shared
-}
-
-// MachinePrecomputedSharedKey is a precomputed shared NaCl box shared key.
-type MachinePrecomputedSharedKey struct {
- k [32]byte
-}
-
-// Seal wraps cleartext into a NaCl box (see
-// golang.org/x/crypto/nacl) using the shared key k as generated
-// by MachinePrivate.SharedKey.
-//
-// The returned ciphertext is a 24-byte nonce concatenated with the
-// box value.
-func (k MachinePrecomputedSharedKey) Seal(cleartext []byte) (ciphertext []byte) {
- if k == (MachinePrecomputedSharedKey{}) {
- panic("can't seal with zero keys")
- }
- var nonce [24]byte
- rand(nonce[:])
- return box.SealAfterPrecomputation(nonce[:], cleartext, &nonce, &k.k)
-}
-
-// Open opens the NaCl box ciphertext, which must be a value created by
-// MachinePrecomputedSharedKey.Seal or MachinePrivate.SealTo, and returns the
-// inner cleartext if ciphertext is a valid box for the shared key k.
-func (k MachinePrecomputedSharedKey) Open(ciphertext []byte) (cleartext []byte, ok bool) {
- if k == (MachinePrecomputedSharedKey{}) {
- panic("can't open with zero keys")
- }
- if len(ciphertext) < 24 {
- return nil, false
- }
- var nonce [24]byte
- copy(nonce[:], ciphertext)
- return box.OpenAfterPrecomputation(nil, ciphertext[len(nonce):], &nonce, &k.k)
-}
-
-// OpenFrom opens the NaCl box ciphertext, which must be a value
-// created by SealTo, and returns the inner cleartext if ciphertext is
-// a valid box from p to k.
-func (k MachinePrivate) OpenFrom(p MachinePublic, ciphertext []byte) (cleartext []byte, ok bool) {
- if k.IsZero() || p.IsZero() {
- panic("can't open with zero keys")
- }
- if len(ciphertext) < 24 {
- return nil, false
- }
- var nonce [24]byte
- copy(nonce[:], ciphertext)
- return box.Open(nil, ciphertext[len(nonce):], &nonce, &p.k, &k.k)
-}
-
-// MachinePublic is the public portion of a a MachinePrivate.
-type MachinePublic struct {
- k [32]byte
-}
-
-// MachinePublicFromRaw32 parses a 32-byte raw value as a MachinePublic.
-//
-// This should be used only when deserializing a MachinePublic from a
-// binary protocol.
-func MachinePublicFromRaw32(raw mem.RO) MachinePublic {
- if raw.Len() != 32 {
- panic("input has wrong size")
- }
- var ret MachinePublic
- raw.Copy(ret.k[:])
- return ret
-}
-
-// ParseMachinePublicUntyped parses an untyped 64-character hex value
-// as a MachinePublic.
-//
-// Deprecated: this function is risky to use, because it cannot verify
-// that the hex string was intended to be a MachinePublic. This can
-// lead to accidentally decoding one type of key as another. For new
-// uses that don't require backwards compatibility with the untyped
-// string format, please use MarshalText/UnmarshalText.
-func ParseMachinePublicUntyped(raw mem.RO) (MachinePublic, error) {
- var ret MachinePublic
- if err := parseHex(ret.k[:], raw, mem.B(nil)); err != nil {
- return MachinePublic{}, err
- }
- return ret, nil
-}
-
-// IsZero reports whether k is the zero value.
-func (k MachinePublic) IsZero() bool {
- return k == MachinePublic{}
-}
-
-// ShortString returns the Tailscale conventional debug representation
-// of a public key: the first five base64 digits of the key, in square
-// brackets.
-func (k MachinePublic) ShortString() string {
- return debug32(k.k)
-}
-
-// UntypedHexString returns k, encoded as an untyped 64-character hex
-// string.
-//
-// Deprecated: this function is risky to use, because it produces
-// serialized values that do not identify themselves as a
-// MachinePublic, allowing other code to potentially parse it back in
-// as the wrong key type. For new uses that don't require backwards
-// compatibility with the untyped string format, please use
-// MarshalText/UnmarshalText.
-func (k MachinePublic) UntypedHexString() string {
- return hex.EncodeToString(k.k[:])
-}
-
-// UntypedBytes returns k, encoded as an untyped 64-character hex
-// string.
-//
-// Deprecated: this function is risky to use, because it produces
-// serialized values that do not identify themselves as a
-// MachinePublic, allowing other code to potentially parse it back in
-// as the wrong key type. For new uses that don't require this
-// specific raw byte serialization, please use
-// MarshalText/UnmarshalText.
-func (k MachinePublic) UntypedBytes() []byte {
- return bytes.Clone(k.k[:])
-}
-
-// String returns the output of MarshalText as a string.
-func (k MachinePublic) String() string {
- bs, err := k.MarshalText()
- if err != nil {
- panic(err)
- }
- return string(bs)
-}
-
-// AppendText implements encoding.TextAppender.
-func (k MachinePublic) AppendText(b []byte) ([]byte, error) {
- return appendHexKey(b, machinePublicHexPrefix, k.k[:]), nil
-}
-
-// MarshalText implements encoding.TextMarshaler.
-func (k MachinePublic) MarshalText() ([]byte, error) {
- return k.AppendText(nil)
-}
-
-// MarshalText implements encoding.TextUnmarshaler.
-func (k *MachinePublic) UnmarshalText(b []byte) error {
- return parseHex(k.k[:], mem.B(b), mem.S(machinePublicHexPrefix))
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package key
+
+import (
+ "bytes"
+ "crypto/subtle"
+ "encoding/hex"
+
+ "go4.org/mem"
+ "golang.org/x/crypto/curve25519"
+ "golang.org/x/crypto/nacl/box"
+ "tailscale.com/types/structs"
+)
+
+const (
+ // machinePrivateHexPrefix is the prefix used to identify a
+ // hex-encoded machine private key.
+ //
+ // This prefix name is a little unfortunate, in that it comes from
+ // WireGuard's own key types. Unfortunately we're stuck with it for
+ // machine keys, because we serialize them to disk with this prefix.
+ machinePrivateHexPrefix = "privkey:"
+
+ // machinePublicHexPrefix is the prefix used to identify a
+ // hex-encoded machine public key.
+ //
+ // This prefix is used in the control protocol, so cannot be
+ // changed.
+ machinePublicHexPrefix = "mkey:"
+)
+
+// MachinePrivate is a machine key, used for communication with the
+// Tailscale coordination server.
+type MachinePrivate struct {
+ _ structs.Incomparable // == isn't constant-time
+ k [32]byte
+}
+
+// NewMachine creates and returns a new machine private key.
+func NewMachine() MachinePrivate {
+ var ret MachinePrivate
+ rand(ret.k[:])
+ clamp25519Private(ret.k[:])
+ return ret
+}
+
+// IsZero reports whether k is the zero value.
+func (k MachinePrivate) IsZero() bool {
+ return k.Equal(MachinePrivate{})
+}
+
+// Equal reports whether k and other are the same key.
+func (k MachinePrivate) Equal(other MachinePrivate) bool {
+ return subtle.ConstantTimeCompare(k.k[:], other.k[:]) == 1
+}
+
+// Public returns the MachinePublic for k.
+// Panics if MachinePrivate is zero.
+func (k MachinePrivate) Public() MachinePublic {
+ if k.IsZero() {
+ panic("can't take the public key of a zero MachinePrivate")
+ }
+ var ret MachinePublic
+ curve25519.ScalarBaseMult(&ret.k, &k.k)
+ return ret
+}
+
+// AppendText implements encoding.TextAppender.
+func (k MachinePrivate) AppendText(b []byte) ([]byte, error) {
+ return appendHexKey(b, machinePrivateHexPrefix, k.k[:]), nil
+}
+
+// MarshalText implements encoding.TextMarshaler.
+func (k MachinePrivate) MarshalText() ([]byte, error) {
+ return k.AppendText(nil)
+}
+
+// MarshalText implements encoding.TextUnmarshaler.
+func (k *MachinePrivate) UnmarshalText(b []byte) error {
+ return parseHex(k.k[:], mem.B(b), mem.S(machinePrivateHexPrefix))
+}
+
+// UntypedBytes returns k, encoded as an untyped 64-character hex
+// string.
+//
+// Deprecated: this function is risky to use, because it produces
+// serialized values that do not identify themselves as a
+// MachinePrivate, allowing other code to potentially parse it back in
+// as the wrong key type. For new uses that don't require this
+// specific raw byte serialization, please use
+// MarshalText/UnmarshalText.
+func (k MachinePrivate) UntypedBytes() []byte {
+ return bytes.Clone(k.k[:])
+}
+
+// SealTo wraps cleartext into a NaCl box (see
+// golang.org/x/crypto/nacl) to p, authenticated from k, using a
+// random nonce.
+//
+// The returned ciphertext is a 24-byte nonce concatenated with the
+// box value.
+func (k MachinePrivate) SealTo(p MachinePublic, cleartext []byte) (ciphertext []byte) {
+ if k.IsZero() || p.IsZero() {
+ panic("can't seal with zero keys")
+ }
+ var nonce [24]byte
+ rand(nonce[:])
+ return box.Seal(nonce[:], cleartext, &nonce, &p.k, &k.k)
+}
+
+// SharedKey returns the precomputed Nacl box shared key between k and p.
+func (k MachinePrivate) SharedKey(p MachinePublic) MachinePrecomputedSharedKey {
+ var shared MachinePrecomputedSharedKey
+ box.Precompute(&shared.k, &p.k, &k.k)
+ return shared
+}
+
+// MachinePrecomputedSharedKey is a precomputed shared NaCl box shared key.
+type MachinePrecomputedSharedKey struct {
+ k [32]byte
+}
+
+// Seal wraps cleartext into a NaCl box (see
+// golang.org/x/crypto/nacl) using the shared key k as generated
+// by MachinePrivate.SharedKey.
+//
+// The returned ciphertext is a 24-byte nonce concatenated with the
+// box value.
+func (k MachinePrecomputedSharedKey) Seal(cleartext []byte) (ciphertext []byte) {
+ if k == (MachinePrecomputedSharedKey{}) {
+ panic("can't seal with zero keys")
+ }
+ var nonce [24]byte
+ rand(nonce[:])
+ return box.SealAfterPrecomputation(nonce[:], cleartext, &nonce, &k.k)
+}
+
+// Open opens the NaCl box ciphertext, which must be a value created by
+// MachinePrecomputedSharedKey.Seal or MachinePrivate.SealTo, and returns the
+// inner cleartext if ciphertext is a valid box for the shared key k.
+func (k MachinePrecomputedSharedKey) Open(ciphertext []byte) (cleartext []byte, ok bool) {
+ if k == (MachinePrecomputedSharedKey{}) {
+ panic("can't open with zero keys")
+ }
+ if len(ciphertext) < 24 {
+ return nil, false
+ }
+ var nonce [24]byte
+ copy(nonce[:], ciphertext)
+ return box.OpenAfterPrecomputation(nil, ciphertext[len(nonce):], &nonce, &k.k)
+}
+
+// OpenFrom opens the NaCl box ciphertext, which must be a value
+// created by SealTo, and returns the inner cleartext if ciphertext is
+// a valid box from p to k.
+func (k MachinePrivate) OpenFrom(p MachinePublic, ciphertext []byte) (cleartext []byte, ok bool) {
+ if k.IsZero() || p.IsZero() {
+ panic("can't open with zero keys")
+ }
+ if len(ciphertext) < 24 {
+ return nil, false
+ }
+ var nonce [24]byte
+ copy(nonce[:], ciphertext)
+ return box.Open(nil, ciphertext[len(nonce):], &nonce, &p.k, &k.k)
+}
+
+// MachinePublic is the public portion of a a MachinePrivate.
+type MachinePublic struct {
+ k [32]byte
+}
+
+// MachinePublicFromRaw32 parses a 32-byte raw value as a MachinePublic.
+//
+// This should be used only when deserializing a MachinePublic from a
+// binary protocol.
+func MachinePublicFromRaw32(raw mem.RO) MachinePublic {
+ if raw.Len() != 32 {
+ panic("input has wrong size")
+ }
+ var ret MachinePublic
+ raw.Copy(ret.k[:])
+ return ret
+}
+
+// ParseMachinePublicUntyped parses an untyped 64-character hex value
+// as a MachinePublic.
+//
+// Deprecated: this function is risky to use, because it cannot verify
+// that the hex string was intended to be a MachinePublic. This can
+// lead to accidentally decoding one type of key as another. For new
+// uses that don't require backwards compatibility with the untyped
+// string format, please use MarshalText/UnmarshalText.
+func ParseMachinePublicUntyped(raw mem.RO) (MachinePublic, error) {
+ var ret MachinePublic
+ if err := parseHex(ret.k[:], raw, mem.B(nil)); err != nil {
+ return MachinePublic{}, err
+ }
+ return ret, nil
+}
+
+// IsZero reports whether k is the zero value.
+func (k MachinePublic) IsZero() bool {
+ return k == MachinePublic{}
+}
+
+// ShortString returns the Tailscale conventional debug representation
+// of a public key: the first five base64 digits of the key, in square
+// brackets.
+func (k MachinePublic) ShortString() string {
+ return debug32(k.k)
+}
+
+// UntypedHexString returns k, encoded as an untyped 64-character hex
+// string.
+//
+// Deprecated: this function is risky to use, because it produces
+// serialized values that do not identify themselves as a
+// MachinePublic, allowing other code to potentially parse it back in
+// as the wrong key type. For new uses that don't require backwards
+// compatibility with the untyped string format, please use
+// MarshalText/UnmarshalText.
+func (k MachinePublic) UntypedHexString() string {
+ return hex.EncodeToString(k.k[:])
+}
+
+// UntypedBytes returns k, encoded as an untyped 64-character hex
+// string.
+//
+// Deprecated: this function is risky to use, because it produces
+// serialized values that do not identify themselves as a
+// MachinePublic, allowing other code to potentially parse it back in
+// as the wrong key type. For new uses that don't require this
+// specific raw byte serialization, please use
+// MarshalText/UnmarshalText.
+func (k MachinePublic) UntypedBytes() []byte {
+ return bytes.Clone(k.k[:])
+}
+
+// String returns the output of MarshalText as a string.
+func (k MachinePublic) String() string {
+ bs, err := k.MarshalText()
+ if err != nil {
+ panic(err)
+ }
+ return string(bs)
+}
+
+// AppendText implements encoding.TextAppender.
+func (k MachinePublic) AppendText(b []byte) ([]byte, error) {
+ return appendHexKey(b, machinePublicHexPrefix, k.k[:]), nil
+}
+
+// MarshalText implements encoding.TextMarshaler.
+func (k MachinePublic) MarshalText() ([]byte, error) {
+ return k.AppendText(nil)
+}
+
+// MarshalText implements encoding.TextUnmarshaler.
+func (k *MachinePublic) UnmarshalText(b []byte) error {
+ return parseHex(k.k[:], mem.B(b), mem.S(machinePublicHexPrefix))
+}
diff --git a/types/key/machine_test.go b/types/key/machine_test.go
index 157df9e43..f797ff087 100644
--- a/types/key/machine_test.go
+++ b/types/key/machine_test.go
@@ -1,119 +1,119 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package key
-
-import (
- "bytes"
- "encoding/json"
- "strings"
- "testing"
-)
-
-func TestMachineKey(t *testing.T) {
- k := NewMachine()
- if k.IsZero() {
- t.Fatal("MachinePrivate should not be zero")
- }
-
- p := k.Public()
- if p.IsZero() {
- t.Fatal("MachinePublic should not be zero")
- }
-
- bs, err := p.MarshalText()
- if err != nil {
- t.Fatal(err)
- }
- if full, got := string(bs), ":"+p.UntypedHexString(); !strings.HasSuffix(full, got) {
- t.Fatalf("MachinePublic.UntypedHexString is not a suffix of the typed serialization, got %q want suffix of %q", got, full)
- }
-
- z := MachinePublic{}
- if !z.IsZero() {
- t.Fatal("IsZero(MachinePublic{}) is false")
- }
- if s := z.ShortString(); s != "" {
- t.Fatalf("MachinePublic{}.ShortString() is %q, want \"\"", s)
- }
-}
-
-func TestMachineSerialization(t *testing.T) {
- serialized := `{
- "Priv": "privkey:40ab1b58e9076c7a4d9d07291f5edf9d1aa017eb949624ba683317f48a640369",
- "Pub":"mkey:50d20b455ecf12bc453f83c2cfdb2a24925d06cf2598dcaa54e91af82ce9f765"
- }`
-
- // Carefully check that the expected serialized data decodes and
- // reencodes to the expected keys. These types are serialized to
- // disk all over the place and need to be stable.
- priv := MachinePrivate{
- k: [32]uint8{
- 0x40, 0xab, 0x1b, 0x58, 0xe9, 0x7, 0x6c, 0x7a, 0x4d, 0x9d, 0x7,
- 0x29, 0x1f, 0x5e, 0xdf, 0x9d, 0x1a, 0xa0, 0x17, 0xeb, 0x94,
- 0x96, 0x24, 0xba, 0x68, 0x33, 0x17, 0xf4, 0x8a, 0x64, 0x3, 0x69,
- },
- }
- pub := MachinePublic{
- k: [32]uint8{
- 0x50, 0xd2, 0xb, 0x45, 0x5e, 0xcf, 0x12, 0xbc, 0x45, 0x3f, 0x83,
- 0xc2, 0xcf, 0xdb, 0x2a, 0x24, 0x92, 0x5d, 0x6, 0xcf, 0x25, 0x98,
- 0xdc, 0xaa, 0x54, 0xe9, 0x1a, 0xf8, 0x2c, 0xe9, 0xf7, 0x65,
- },
- }
-
- type keypair struct {
- Priv MachinePrivate
- Pub MachinePublic
- }
-
- var a keypair
- if err := json.Unmarshal([]byte(serialized), &a); err != nil {
- t.Fatal(err)
- }
- if !a.Priv.Equal(priv) {
- t.Errorf("wrong deserialization of private key, got %#v want %#v", a.Priv, priv)
- }
- if a.Pub != pub {
- t.Errorf("wrong deserialization of public key, got %#v want %#v", a.Pub, pub)
- }
-
- bs, err := json.MarshalIndent(a, "", " ")
- if err != nil {
- t.Fatal(err)
- }
-
- var b bytes.Buffer
- json.Indent(&b, []byte(serialized), "", " ")
- if got, want := string(bs), b.String(); got != want {
- t.Error("json serialization doesn't roundtrip")
- }
-}
-
-func TestSealViaSharedKey(t *testing.T) {
- // encrypt a message from a to b
- a := NewMachine()
- b := NewMachine()
- apub, bpub := a.Public(), b.Public()
-
- shared := a.SharedKey(bpub)
-
- const clear = "the eagle flies at midnight"
- enc := shared.Seal([]byte(clear))
-
- back, ok := b.OpenFrom(apub, enc)
- if !ok {
- t.Fatal("failed to decrypt")
- }
- if string(back) != clear {
- t.Errorf("OpenFrom got %q; want cleartext %q", back, clear)
- }
-
- backShared, ok := shared.Open(enc)
- if !ok {
- t.Fatal("failed to decrypt from shared key")
- }
- if string(backShared) != clear {
- t.Errorf("Open got %q; want cleartext %q", back, clear)
- }
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package key
+
+import (
+ "bytes"
+ "encoding/json"
+ "strings"
+ "testing"
+)
+
+func TestMachineKey(t *testing.T) {
+ k := NewMachine()
+ if k.IsZero() {
+ t.Fatal("MachinePrivate should not be zero")
+ }
+
+ p := k.Public()
+ if p.IsZero() {
+ t.Fatal("MachinePublic should not be zero")
+ }
+
+ bs, err := p.MarshalText()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if full, got := string(bs), ":"+p.UntypedHexString(); !strings.HasSuffix(full, got) {
+ t.Fatalf("MachinePublic.UntypedHexString is not a suffix of the typed serialization, got %q want suffix of %q", got, full)
+ }
+
+ z := MachinePublic{}
+ if !z.IsZero() {
+ t.Fatal("IsZero(MachinePublic{}) is false")
+ }
+ if s := z.ShortString(); s != "" {
+ t.Fatalf("MachinePublic{}.ShortString() is %q, want \"\"", s)
+ }
+}
+
+func TestMachineSerialization(t *testing.T) {
+ serialized := `{
+ "Priv": "privkey:40ab1b58e9076c7a4d9d07291f5edf9d1aa017eb949624ba683317f48a640369",
+ "Pub":"mkey:50d20b455ecf12bc453f83c2cfdb2a24925d06cf2598dcaa54e91af82ce9f765"
+ }`
+
+ // Carefully check that the expected serialized data decodes and
+ // reencodes to the expected keys. These types are serialized to
+ // disk all over the place and need to be stable.
+ priv := MachinePrivate{
+ k: [32]uint8{
+ 0x40, 0xab, 0x1b, 0x58, 0xe9, 0x7, 0x6c, 0x7a, 0x4d, 0x9d, 0x7,
+ 0x29, 0x1f, 0x5e, 0xdf, 0x9d, 0x1a, 0xa0, 0x17, 0xeb, 0x94,
+ 0x96, 0x24, 0xba, 0x68, 0x33, 0x17, 0xf4, 0x8a, 0x64, 0x3, 0x69,
+ },
+ }
+ pub := MachinePublic{
+ k: [32]uint8{
+ 0x50, 0xd2, 0xb, 0x45, 0x5e, 0xcf, 0x12, 0xbc, 0x45, 0x3f, 0x83,
+ 0xc2, 0xcf, 0xdb, 0x2a, 0x24, 0x92, 0x5d, 0x6, 0xcf, 0x25, 0x98,
+ 0xdc, 0xaa, 0x54, 0xe9, 0x1a, 0xf8, 0x2c, 0xe9, 0xf7, 0x65,
+ },
+ }
+
+ type keypair struct {
+ Priv MachinePrivate
+ Pub MachinePublic
+ }
+
+ var a keypair
+ if err := json.Unmarshal([]byte(serialized), &a); err != nil {
+ t.Fatal(err)
+ }
+ if !a.Priv.Equal(priv) {
+ t.Errorf("wrong deserialization of private key, got %#v want %#v", a.Priv, priv)
+ }
+ if a.Pub != pub {
+ t.Errorf("wrong deserialization of public key, got %#v want %#v", a.Pub, pub)
+ }
+
+ bs, err := json.MarshalIndent(a, "", " ")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var b bytes.Buffer
+ json.Indent(&b, []byte(serialized), "", " ")
+ if got, want := string(bs), b.String(); got != want {
+ t.Error("json serialization doesn't roundtrip")
+ }
+}
+
+func TestSealViaSharedKey(t *testing.T) {
+ // encrypt a message from a to b
+ a := NewMachine()
+ b := NewMachine()
+ apub, bpub := a.Public(), b.Public()
+
+ shared := a.SharedKey(bpub)
+
+ const clear = "the eagle flies at midnight"
+ enc := shared.Seal([]byte(clear))
+
+ back, ok := b.OpenFrom(apub, enc)
+ if !ok {
+ t.Fatal("failed to decrypt")
+ }
+ if string(back) != clear {
+ t.Errorf("OpenFrom got %q; want cleartext %q", back, clear)
+ }
+
+ backShared, ok := shared.Open(enc)
+ if !ok {
+ t.Fatal("failed to decrypt from shared key")
+ }
+ if string(backShared) != clear {
+ t.Errorf("Open got %q; want cleartext %q", back, clear)
+ }
+}
diff --git a/types/key/nl_test.go b/types/key/nl_test.go
index 75b7765a1..2e10d04ac 100644
--- a/types/key/nl_test.go
+++ b/types/key/nl_test.go
@@ -1,48 +1,48 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package key
-
-import (
- "bytes"
- "testing"
-)
-
-func TestNLPrivate(t *testing.T) {
- p := NewNLPrivate()
-
- encoded, err := p.MarshalText()
- if err != nil {
- t.Fatal(err)
- }
- var decoded NLPrivate
- if err := decoded.UnmarshalText(encoded); err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(decoded.k[:], p.k[:]) {
- t.Error("decoded and generated NLPrivate bytes differ")
- }
-
- // Test NLPublic
- pub := p.Public()
- encoded, err = pub.MarshalText()
- if err != nil {
- t.Fatal(err)
- }
- var decodedPub NLPublic
- if err := decodedPub.UnmarshalText(encoded); err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(decodedPub.k[:], pub.k[:]) {
- t.Error("decoded and generated NLPublic bytes differ")
- }
-
- // Test decoding with CLI prefix: 'nlpub:' => 'tlpub:'
- decodedPub = NLPublic{}
- if err := decodedPub.UnmarshalText([]byte(pub.CLIString())); err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(decodedPub.k[:], pub.k[:]) {
- t.Error("decoded and generated NLPublic bytes differ (CLI prefix)")
- }
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package key
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestNLPrivate(t *testing.T) {
+ p := NewNLPrivate()
+
+ encoded, err := p.MarshalText()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var decoded NLPrivate
+ if err := decoded.UnmarshalText(encoded); err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(decoded.k[:], p.k[:]) {
+ t.Error("decoded and generated NLPrivate bytes differ")
+ }
+
+ // Test NLPublic
+ pub := p.Public()
+ encoded, err = pub.MarshalText()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var decodedPub NLPublic
+ if err := decodedPub.UnmarshalText(encoded); err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(decodedPub.k[:], pub.k[:]) {
+ t.Error("decoded and generated NLPublic bytes differ")
+ }
+
+ // Test decoding with CLI prefix: 'nlpub:' => 'tlpub:'
+ decodedPub = NLPublic{}
+ if err := decodedPub.UnmarshalText([]byte(pub.CLIString())); err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(decodedPub.k[:], pub.k[:]) {
+ t.Error("decoded and generated NLPublic bytes differ (CLI prefix)")
+ }
+}
diff --git a/types/lazy/unsync.go b/types/lazy/unsync.go
index 0f89ce4f6..ca46f9c7b 100644
--- a/types/lazy/unsync.go
+++ b/types/lazy/unsync.go
@@ -1,99 +1,99 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package lazy
-
-// GValue is a lazily computed value.
-//
-// Use either Get or GetErr, depending on whether your fill function returns an
-// error.
-//
-// Recursive use of a GValue from its own fill function will panic.
-//
-// GValue is not safe for concurrent use. (Mnemonic: G is for one Goroutine,
-// which isn't strictly true if you provide your own synchronization between
-// goroutines, but in practice most of our callers have been using it within
-// a single goroutine.)
-type GValue[T any] struct {
- done bool
- calling bool
- V T
- err error
-}
-
-// Set attempts to set z's value to val, and reports whether it succeeded.
-// Set only succeeds if none of Get/GetErr/Set have been called before.
-func (z *GValue[T]) Set(v T) bool {
- if z.done {
- return false
- }
- if z.calling {
- panic("Set while Get fill is running")
- }
- z.V = v
- z.done = true
- return true
-}
-
-// MustSet sets z's value to val, or panics if z already has a value.
-func (z *GValue[T]) MustSet(val T) {
- if !z.Set(val) {
- panic("Set after already filled")
- }
-}
-
-// Get returns z's value, calling fill to compute it if necessary.
-// f is called at most once.
-func (z *GValue[T]) Get(fill func() T) T {
- if !z.done {
- if z.calling {
- panic("recursive lazy fill")
- }
- z.calling = true
- z.V = fill()
- z.done = true
- z.calling = false
- }
- return z.V
-}
-
-// GetErr returns z's value, calling fill to compute it if necessary.
-// f is called at most once, and z remembers both of fill's outputs.
-func (z *GValue[T]) GetErr(fill func() (T, error)) (T, error) {
- if !z.done {
- if z.calling {
- panic("recursive lazy fill")
- }
- z.calling = true
- z.V, z.err = fill()
- z.done = true
- z.calling = false
- }
- return z.V, z.err
-}
-
-// GFunc wraps a function to make it lazy.
-//
-// The returned function calls fill the first time it's called, and returns
-// fill's result on every subsequent call.
-//
-// The returned function is not safe for concurrent use.
-func GFunc[T any](fill func() T) func() T {
- var v GValue[T]
- return func() T {
- return v.Get(fill)
- }
-}
-
-// SyncFuncErr wraps a function to make it lazy.
-//
-// The returned function calls fill the first time it's called, and returns
-// fill's results on every subsequent call.
-//
-// The returned function is not safe for concurrent use.
-func GFuncErr[T any](fill func() (T, error)) func() (T, error) {
- var v GValue[T]
- return func() (T, error) {
- return v.GetErr(fill)
- }
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package lazy
+
+// GValue is a lazily computed value.
+//
+// Use either Get or GetErr, depending on whether your fill function returns an
+// error.
+//
+// Recursive use of a GValue from its own fill function will panic.
+//
+// GValue is not safe for concurrent use. (Mnemonic: G is for one Goroutine,
+// which isn't strictly true if you provide your own synchronization between
+// goroutines, but in practice most of our callers have been using it within
+// a single goroutine.)
+type GValue[T any] struct {
+ done bool
+ calling bool
+ V T
+ err error
+}
+
+// Set attempts to set z's value to val, and reports whether it succeeded.
+// Set only succeeds if none of Get/GetErr/Set have been called before.
+func (z *GValue[T]) Set(v T) bool {
+ if z.done {
+ return false
+ }
+ if z.calling {
+ panic("Set while Get fill is running")
+ }
+ z.V = v
+ z.done = true
+ return true
+}
+
+// MustSet sets z's value to val, or panics if z already has a value.
+func (z *GValue[T]) MustSet(val T) {
+ if !z.Set(val) {
+ panic("Set after already filled")
+ }
+}
+
+// Get returns z's value, calling fill to compute it if necessary.
+// f is called at most once.
+func (z *GValue[T]) Get(fill func() T) T {
+ if !z.done {
+ if z.calling {
+ panic("recursive lazy fill")
+ }
+ z.calling = true
+ z.V = fill()
+ z.done = true
+ z.calling = false
+ }
+ return z.V
+}
+
+// GetErr returns z's value, calling fill to compute it if necessary.
+// f is called at most once, and z remembers both of fill's outputs.
+func (z *GValue[T]) GetErr(fill func() (T, error)) (T, error) {
+ if !z.done {
+ if z.calling {
+ panic("recursive lazy fill")
+ }
+ z.calling = true
+ z.V, z.err = fill()
+ z.done = true
+ z.calling = false
+ }
+ return z.V, z.err
+}
+
+// GFunc wraps a function to make it lazy.
+//
+// The returned function calls fill the first time it's called, and returns
+// fill's result on every subsequent call.
+//
+// The returned function is not safe for concurrent use.
+func GFunc[T any](fill func() T) func() T {
+ var v GValue[T]
+ return func() T {
+ return v.Get(fill)
+ }
+}
+
+// SyncFuncErr wraps a function to make it lazy.
+//
+// The returned function calls fill the first time it's called, and returns
+// fill's results on every subsequent call.
+//
+// The returned function is not safe for concurrent use.
+func GFuncErr[T any](fill func() (T, error)) func() (T, error) {
+ var v GValue[T]
+ return func() (T, error) {
+ return v.GetErr(fill)
+ }
+}
diff --git a/types/lazy/unsync_test.go b/types/lazy/unsync_test.go
index f0d2494d1..d8b870dbe 100644
--- a/types/lazy/unsync_test.go
+++ b/types/lazy/unsync_test.go
@@ -1,140 +1,140 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package lazy
-
-import (
- "errors"
- "testing"
-)
-
-func fortyTwo() int { return 42 }
-
-func TestGValue(t *testing.T) {
- var lt GValue[int]
- n := int(testing.AllocsPerRun(1000, func() {
- got := lt.Get(fortyTwo)
- if got != 42 {
- t.Fatalf("got %v; want 42", got)
- }
- }))
- if n != 0 {
- t.Errorf("allocs = %v; want 0", n)
- }
-}
-
-func TestGValueErr(t *testing.T) {
- var lt GValue[int]
- n := int(testing.AllocsPerRun(1000, func() {
- got, err := lt.GetErr(func() (int, error) {
- return 42, nil
- })
- if got != 42 || err != nil {
- t.Fatalf("got %v, %v; want 42, nil", got, err)
- }
- }))
- if n != 0 {
- t.Errorf("allocs = %v; want 0", n)
- }
-
- var lterr GValue[int]
- wantErr := errors.New("test error")
- n = int(testing.AllocsPerRun(1000, func() {
- got, err := lterr.GetErr(func() (int, error) {
- return 0, wantErr
- })
- if got != 0 || err != wantErr {
- t.Fatalf("got %v, %v; want 0, %v", got, err, wantErr)
- }
- }))
- if n != 0 {
- t.Errorf("allocs = %v; want 0", n)
- }
-}
-
-func TestGValueSet(t *testing.T) {
- var lt GValue[int]
- if !lt.Set(42) {
- t.Fatalf("Set failed")
- }
- if lt.Set(43) {
- t.Fatalf("Set succeeded after first Set")
- }
- n := int(testing.AllocsPerRun(1000, func() {
- got := lt.Get(fortyTwo)
- if got != 42 {
- t.Fatalf("got %v; want 42", got)
- }
- }))
- if n != 0 {
- t.Errorf("allocs = %v; want 0", n)
- }
-}
-
-func TestGValueMustSet(t *testing.T) {
- var lt GValue[int]
- lt.MustSet(42)
- defer func() {
- if e := recover(); e == nil {
- t.Errorf("unexpected success; want panic")
- }
- }()
- lt.MustSet(43)
-}
-
-func TestGValueRecursivePanic(t *testing.T) {
- defer func() {
- if e := recover(); e != nil {
- t.Logf("got panic, as expected")
- } else {
- t.Errorf("unexpected success; want panic")
- }
- }()
- v := GValue[int]{}
- v.Get(func() int {
- return v.Get(func() int { return 42 })
- })
-}
-
-func TestGFunc(t *testing.T) {
- f := GFunc(fortyTwo)
-
- n := int(testing.AllocsPerRun(1000, func() {
- got := f()
- if got != 42 {
- t.Fatalf("got %v; want 42", got)
- }
- }))
- if n != 0 {
- t.Errorf("allocs = %v; want 0", n)
- }
-}
-
-func TestGFuncErr(t *testing.T) {
- f := GFuncErr(func() (int, error) {
- return 42, nil
- })
- n := int(testing.AllocsPerRun(1000, func() {
- got, err := f()
- if got != 42 || err != nil {
- t.Fatalf("got %v, %v; want 42, nil", got, err)
- }
- }))
- if n != 0 {
- t.Errorf("allocs = %v; want 0", n)
- }
-
- wantErr := errors.New("test error")
- f = GFuncErr(func() (int, error) {
- return 0, wantErr
- })
- n = int(testing.AllocsPerRun(1000, func() {
- got, err := f()
- if got != 0 || err != wantErr {
- t.Fatalf("got %v, %v; want 0, %v", got, err, wantErr)
- }
- }))
- if n != 0 {
- t.Errorf("allocs = %v; want 0", n)
- }
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package lazy
+
+import (
+ "errors"
+ "testing"
+)
+
+func fortyTwo() int { return 42 }
+
+func TestGValue(t *testing.T) {
+ var lt GValue[int]
+ n := int(testing.AllocsPerRun(1000, func() {
+ got := lt.Get(fortyTwo)
+ if got != 42 {
+ t.Fatalf("got %v; want 42", got)
+ }
+ }))
+ if n != 0 {
+ t.Errorf("allocs = %v; want 0", n)
+ }
+}
+
+func TestGValueErr(t *testing.T) {
+ var lt GValue[int]
+ n := int(testing.AllocsPerRun(1000, func() {
+ got, err := lt.GetErr(func() (int, error) {
+ return 42, nil
+ })
+ if got != 42 || err != nil {
+ t.Fatalf("got %v, %v; want 42, nil", got, err)
+ }
+ }))
+ if n != 0 {
+ t.Errorf("allocs = %v; want 0", n)
+ }
+
+ var lterr GValue[int]
+ wantErr := errors.New("test error")
+ n = int(testing.AllocsPerRun(1000, func() {
+ got, err := lterr.GetErr(func() (int, error) {
+ return 0, wantErr
+ })
+ if got != 0 || err != wantErr {
+ t.Fatalf("got %v, %v; want 0, %v", got, err, wantErr)
+ }
+ }))
+ if n != 0 {
+ t.Errorf("allocs = %v; want 0", n)
+ }
+}
+
+func TestGValueSet(t *testing.T) {
+ var lt GValue[int]
+ if !lt.Set(42) {
+ t.Fatalf("Set failed")
+ }
+ if lt.Set(43) {
+ t.Fatalf("Set succeeded after first Set")
+ }
+ n := int(testing.AllocsPerRun(1000, func() {
+ got := lt.Get(fortyTwo)
+ if got != 42 {
+ t.Fatalf("got %v; want 42", got)
+ }
+ }))
+ if n != 0 {
+ t.Errorf("allocs = %v; want 0", n)
+ }
+}
+
+func TestGValueMustSet(t *testing.T) {
+ var lt GValue[int]
+ lt.MustSet(42)
+ defer func() {
+ if e := recover(); e == nil {
+ t.Errorf("unexpected success; want panic")
+ }
+ }()
+ lt.MustSet(43)
+}
+
+func TestGValueRecursivePanic(t *testing.T) {
+ defer func() {
+ if e := recover(); e != nil {
+ t.Logf("got panic, as expected")
+ } else {
+ t.Errorf("unexpected success; want panic")
+ }
+ }()
+ v := GValue[int]{}
+ v.Get(func() int {
+ return v.Get(func() int { return 42 })
+ })
+}
+
+func TestGFunc(t *testing.T) {
+ f := GFunc(fortyTwo)
+
+ n := int(testing.AllocsPerRun(1000, func() {
+ got := f()
+ if got != 42 {
+ t.Fatalf("got %v; want 42", got)
+ }
+ }))
+ if n != 0 {
+ t.Errorf("allocs = %v; want 0", n)
+ }
+}
+
+func TestGFuncErr(t *testing.T) {
+ f := GFuncErr(func() (int, error) {
+ return 42, nil
+ })
+ n := int(testing.AllocsPerRun(1000, func() {
+ got, err := f()
+ if got != 42 || err != nil {
+ t.Fatalf("got %v, %v; want 42, nil", got, err)
+ }
+ }))
+ if n != 0 {
+ t.Errorf("allocs = %v; want 0", n)
+ }
+
+ wantErr := errors.New("test error")
+ f = GFuncErr(func() (int, error) {
+ return 0, wantErr
+ })
+ n = int(testing.AllocsPerRun(1000, func() {
+ got, err := f()
+ if got != 0 || err != wantErr {
+ t.Fatalf("got %v, %v; want 0, %v", got, err, wantErr)
+ }
+ }))
+ if n != 0 {
+ t.Errorf("allocs = %v; want 0", n)
+ }
+}
diff --git a/types/logger/rusage.go b/types/logger/rusage.go
index 3943636d6..ebe0e972d 100644
--- a/types/logger/rusage.go
+++ b/types/logger/rusage.go
@@ -1,23 +1,23 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package logger
-
-import (
- "fmt"
- "runtime"
-)
-
-// RusagePrefixLog returns a Logf func wrapping the provided logf func that adds
-// a prefixed log message to each line with the current binary memory usage
-// and max RSS.
-func RusagePrefixLog(logf Logf) Logf {
- return func(f string, argv ...any) {
- var m runtime.MemStats
- runtime.ReadMemStats(&m)
- goMem := float64(m.HeapInuse+m.StackInuse) / (1 << 20)
- maxRSS := rusageMaxRSS()
- pf := fmt.Sprintf("%.1fM/%.1fM %s", goMem, maxRSS, f)
- logf(pf, argv...)
- }
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package logger
+
+import (
+ "fmt"
+ "runtime"
+)
+
+// RusagePrefixLog returns a Logf func wrapping the provided logf func that adds
+// a prefixed log message to each line with the current binary memory usage
+// and max RSS.
+func RusagePrefixLog(logf Logf) Logf {
+ return func(f string, argv ...any) {
+ var m runtime.MemStats
+ runtime.ReadMemStats(&m)
+ goMem := float64(m.HeapInuse+m.StackInuse) / (1 << 20)
+ maxRSS := rusageMaxRSS()
+ pf := fmt.Sprintf("%.1fM/%.1fM %s", goMem, maxRSS, f)
+ logf(pf, argv...)
+ }
+}
diff --git a/types/logger/rusage_stub.go b/types/logger/rusage_stub.go
index f646f1e1e..a228b0865 100644
--- a/types/logger/rusage_stub.go
+++ b/types/logger/rusage_stub.go
@@ -1,11 +1,11 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-//go:build windows || wasm || plan9 || tamago
-
-package logger
-
-func rusageMaxRSS() float64 {
- // TODO(apenwarr): Substitute Windows equivalent of Getrusage() here.
- return 0
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build windows || wasm || plan9 || tamago
+
+package logger
+
+func rusageMaxRSS() float64 {
+ // TODO(apenwarr): Substitute Windows equivalent of Getrusage() here.
+ return 0
+}
diff --git a/types/logger/rusage_syscall.go b/types/logger/rusage_syscall.go
index 2871b66c6..19488aef1 100644
--- a/types/logger/rusage_syscall.go
+++ b/types/logger/rusage_syscall.go
@@ -1,29 +1,29 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-//go:build !windows && !wasm && !plan9 && !tamago
-
-package logger
-
-import (
- "runtime"
-
- "golang.org/x/sys/unix"
-)
-
-func rusageMaxRSS() float64 {
- var ru unix.Rusage
- err := unix.Getrusage(unix.RUSAGE_SELF, &ru)
- if err != nil {
- return 0
- }
-
- rss := float64(ru.Maxrss)
- if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
- rss /= 1 << 20 // ru_maxrss is bytes on darwin
- } else {
- // ru_maxrss is kilobytes elsewhere (linux, openbsd, etc)
- rss /= 1 << 10
- }
- return rss
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !windows && !wasm && !plan9 && !tamago
+
+package logger
+
+import (
+ "runtime"
+
+ "golang.org/x/sys/unix"
+)
+
+func rusageMaxRSS() float64 {
+ var ru unix.Rusage
+ err := unix.Getrusage(unix.RUSAGE_SELF, &ru)
+ if err != nil {
+ return 0
+ }
+
+ rss := float64(ru.Maxrss)
+ if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
+ rss /= 1 << 20 // ru_maxrss is bytes on darwin
+ } else {
+ // ru_maxrss is kilobytes elsewhere (linux, openbsd, etc)
+ rss /= 1 << 10
+ }
+ return rss
+}
diff --git a/types/logger/tokenbucket.go b/types/logger/tokenbucket.go
index 83d4059c2..2407e01a7 100644
--- a/types/logger/tokenbucket.go
+++ b/types/logger/tokenbucket.go
@@ -1,63 +1,63 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package logger
-
-import (
- "time"
-)
-
-// tokenBucket is a simple token bucket style rate limiter.
-
-// It's similar in function to golang.org/x/time/rate.Limiter, which we
-// can't use because:
-// - It doesn't give access to the number of accumulated tokens, which we
-// need for implementing hysteresis;
-// - It doesn't let us provide our own time function, which we need for
-// implementing proper unit tests.
-//
-// rate.Limiter is also much more complex than necessary, but that wouldn't
-// be enough to disqualify it on its own.
-//
-// Unlike rate.Limiter, this token bucket does not attempt to
-// do any locking of its own. Don't try to access it reentrantly.
-// That's fine inside this types/logger package because we already have
-// locking at a higher level.
-type tokenBucket struct {
- remaining int
- max int
- tick time.Duration
- t time.Time
-}
-
-func newTokenBucket(tick time.Duration, max int, now time.Time) *tokenBucket {
- return &tokenBucket{max, max, tick, now}
-}
-
-func (tb *tokenBucket) Get() bool {
- if tb.remaining > 0 {
- tb.remaining--
- return true
- }
- return false
-}
-
-func (tb *tokenBucket) Refund(n int) {
- b := tb.remaining + n
- if b > tb.max {
- tb.remaining = tb.max
- } else {
- tb.remaining = b
- }
-}
-
-func (tb *tokenBucket) AdvanceTo(t time.Time) {
- diff := t.Sub(tb.t)
-
- // only use up whole ticks. The remainder will be used up
- // next time.
- ticks := int(diff / tb.tick)
- tb.t = tb.t.Add(time.Duration(ticks) * tb.tick)
-
- tb.Refund(ticks)
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package logger
+
+import (
+ "time"
+)
+
+// tokenBucket is a simple token bucket style rate limiter.
+
+// It's similar in function to golang.org/x/time/rate.Limiter, which we
+// can't use because:
+// - It doesn't give access to the number of accumulated tokens, which we
+// need for implementing hysteresis;
+// - It doesn't let us provide our own time function, which we need for
+// implementing proper unit tests.
+//
+// rate.Limiter is also much more complex than necessary, but that wouldn't
+// be enough to disqualify it on its own.
+//
+// Unlike rate.Limiter, this token bucket does not attempt to
+// do any locking of its own. Don't try to access it reentrantly.
+// That's fine inside this types/logger package because we already have
+// locking at a higher level.
+type tokenBucket struct {
+ remaining int
+ max int
+ tick time.Duration
+ t time.Time
+}
+
+func newTokenBucket(tick time.Duration, max int, now time.Time) *tokenBucket {
+ return &tokenBucket{max, max, tick, now}
+}
+
+func (tb *tokenBucket) Get() bool {
+ if tb.remaining > 0 {
+ tb.remaining--
+ return true
+ }
+ return false
+}
+
+func (tb *tokenBucket) Refund(n int) {
+ b := tb.remaining + n
+ if b > tb.max {
+ tb.remaining = tb.max
+ } else {
+ tb.remaining = b
+ }
+}
+
+func (tb *tokenBucket) AdvanceTo(t time.Time) {
+ diff := t.Sub(tb.t)
+
+ // only use up whole ticks. The remainder will be used up
+ // next time.
+ ticks := int(diff / tb.tick)
+ tb.t = tb.t.Add(time.Duration(ticks) * tb.tick)
+
+ tb.Refund(ticks)
+}
diff --git a/types/netlogtype/netlogtype.go b/types/netlogtype/netlogtype.go
index f2fa2bda9..56002628e 100644
--- a/types/netlogtype/netlogtype.go
+++ b/types/netlogtype/netlogtype.go
@@ -1,100 +1,100 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-// Package netlogtype defines types for network logging.
-package netlogtype
-
-import (
- "net/netip"
- "time"
-
- "tailscale.com/tailcfg"
- "tailscale.com/types/ipproto"
-)
-
-// TODO(joetsai): Remove "omitempty" if "omitzero" is ever supported in both
-// the v1 and v2 "json" packages.
-
-// Message is the log message that captures network traffic.
-type Message struct {
- NodeID tailcfg.StableNodeID `json:"nodeId" cbor:"0,keyasint"` // e.g., "n123456CNTRL"
-
- Start time.Time `json:"start" cbor:"12,keyasint"` // inclusive
- End time.Time `json:"end" cbor:"13,keyasint"` // inclusive
-
- VirtualTraffic []ConnectionCounts `json:"virtualTraffic,omitempty" cbor:"14,keyasint,omitempty"`
- SubnetTraffic []ConnectionCounts `json:"subnetTraffic,omitempty" cbor:"15,keyasint,omitempty"`
- ExitTraffic []ConnectionCounts `json:"exitTraffic,omitempty" cbor:"16,keyasint,omitempty"`
- PhysicalTraffic []ConnectionCounts `json:"physicalTraffic,omitempty" cbor:"17,keyasint,omitempty"`
-}
-
-const (
- messageJSON = `{"nodeId":"n0123456789abcdefCNTRL",` + maxJSONTimeRange + `,` + minJSONTraffic + `}`
- maxJSONTimeRange = `"start":` + maxJSONRFC3339 + `,"end":` + maxJSONRFC3339
- maxJSONRFC3339 = `"0001-01-01T00:00:00.000000000Z"`
- minJSONTraffic = `"virtualTraffic":{},"subnetTraffic":{},"exitTraffic":{},"physicalTraffic":{}`
-
- // MaxMessageJSONSize is the overhead size of Message when it is
- // serialized as JSON assuming that each traffic map is populated.
- MaxMessageJSONSize = len(messageJSON)
-
- maxJSONConnCounts = `{` + maxJSONConn + `,` + maxJSONCounts + `}`
- maxJSONConn = `"proto":` + maxJSONProto + `,"src":` + maxJSONAddrPort + `,"dst":` + maxJSONAddrPort
- maxJSONProto = `255`
- maxJSONAddrPort = `"[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535"`
- maxJSONCounts = `"txPkts":` + maxJSONCount + `,"txBytes":` + maxJSONCount + `,"rxPkts":` + maxJSONCount + `,"rxBytes":` + maxJSONCount
- maxJSONCount = `18446744073709551615`
-
- // MaxConnectionCountsJSONSize is the maximum size of a ConnectionCounts
- // when it is serialized as JSON, assuming no superfluous whitespace.
- // It does not include the trailing comma that often appears when
- // this object is nested within an array.
- // It assumes that netip.Addr never has IPv6 zones.
- MaxConnectionCountsJSONSize = len(maxJSONConnCounts)
-
- maxCBORConnCounts = "\xbf" + maxCBORConn + maxCBORCounts + "\xff"
- maxCBORConn = "\x00" + maxCBORProto + "\x01" + maxCBORAddrPort + "\x02" + maxCBORAddrPort
- maxCBORProto = "\x18\xff"
- maxCBORAddrPort = "\x52\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
- maxCBORCounts = "\x0c" + maxCBORCount + "\x0d" + maxCBORCount + "\x0e" + maxCBORCount + "\x0f" + maxCBORCount
- maxCBORCount = "\x1b\xff\xff\xff\xff\xff\xff\xff\xff"
-
- // MaxConnectionCountsCBORSize is the maximum size of a ConnectionCounts
- // when it is serialized as CBOR.
- // It assumes that netip.Addr never has IPv6 zones.
- MaxConnectionCountsCBORSize = len(maxCBORConnCounts)
-)
-
-// ConnectionCounts is a flattened struct of both a connection and counts.
-type ConnectionCounts struct {
- Connection
- Counts
-}
-
-// Connection is a 5-tuple of proto, source and destination IP and port.
-type Connection struct {
- Proto ipproto.Proto `json:"proto,omitzero,omitempty" cbor:"0,keyasint,omitempty"`
- Src netip.AddrPort `json:"src,omitzero,omitempty" cbor:"1,keyasint,omitempty"`
- Dst netip.AddrPort `json:"dst,omitzero,omitempty" cbor:"2,keyasint,omitempty"`
-}
-
-func (c Connection) IsZero() bool { return c == Connection{} }
-
-// Counts are statistics about a particular connection.
-type Counts struct {
- TxPackets uint64 `json:"txPkts,omitzero,omitempty" cbor:"12,keyasint,omitempty"`
- TxBytes uint64 `json:"txBytes,omitzero,omitempty" cbor:"13,keyasint,omitempty"`
- RxPackets uint64 `json:"rxPkts,omitzero,omitempty" cbor:"14,keyasint,omitempty"`
- RxBytes uint64 `json:"rxBytes,omitzero,omitempty" cbor:"15,keyasint,omitempty"`
-}
-
-func (c Counts) IsZero() bool { return c == Counts{} }
-
-// Add adds the counts from both c1 and c2.
-func (c1 Counts) Add(c2 Counts) Counts {
- c1.TxPackets += c2.TxPackets
- c1.TxBytes += c2.TxBytes
- c1.RxPackets += c2.RxPackets
- c1.RxBytes += c2.RxBytes
- return c1
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package netlogtype defines types for network logging.
+package netlogtype
+
+import (
+ "net/netip"
+ "time"
+
+ "tailscale.com/tailcfg"
+ "tailscale.com/types/ipproto"
+)
+
+// TODO(joetsai): Remove "omitempty" if "omitzero" is ever supported in both
+// the v1 and v2 "json" packages.
+
+// Message is the log message that captures network traffic.
+type Message struct {
+ NodeID tailcfg.StableNodeID `json:"nodeId" cbor:"0,keyasint"` // e.g., "n123456CNTRL"
+
+ Start time.Time `json:"start" cbor:"12,keyasint"` // inclusive
+ End time.Time `json:"end" cbor:"13,keyasint"` // inclusive
+
+ VirtualTraffic []ConnectionCounts `json:"virtualTraffic,omitempty" cbor:"14,keyasint,omitempty"`
+ SubnetTraffic []ConnectionCounts `json:"subnetTraffic,omitempty" cbor:"15,keyasint,omitempty"`
+ ExitTraffic []ConnectionCounts `json:"exitTraffic,omitempty" cbor:"16,keyasint,omitempty"`
+ PhysicalTraffic []ConnectionCounts `json:"physicalTraffic,omitempty" cbor:"17,keyasint,omitempty"`
+}
+
+const (
+ messageJSON = `{"nodeId":"n0123456789abcdefCNTRL",` + maxJSONTimeRange + `,` + minJSONTraffic + `}`
+ maxJSONTimeRange = `"start":` + maxJSONRFC3339 + `,"end":` + maxJSONRFC3339
+ maxJSONRFC3339 = `"0001-01-01T00:00:00.000000000Z"`
+ minJSONTraffic = `"virtualTraffic":{},"subnetTraffic":{},"exitTraffic":{},"physicalTraffic":{}`
+
+ // MaxMessageJSONSize is the overhead size of Message when it is
+ // serialized as JSON assuming that each traffic map is populated.
+ MaxMessageJSONSize = len(messageJSON)
+
+ maxJSONConnCounts = `{` + maxJSONConn + `,` + maxJSONCounts + `}`
+ maxJSONConn = `"proto":` + maxJSONProto + `,"src":` + maxJSONAddrPort + `,"dst":` + maxJSONAddrPort
+ maxJSONProto = `255`
+ maxJSONAddrPort = `"[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535"`
+ maxJSONCounts = `"txPkts":` + maxJSONCount + `,"txBytes":` + maxJSONCount + `,"rxPkts":` + maxJSONCount + `,"rxBytes":` + maxJSONCount
+ maxJSONCount = `18446744073709551615`
+
+ // MaxConnectionCountsJSONSize is the maximum size of a ConnectionCounts
+ // when it is serialized as JSON, assuming no superfluous whitespace.
+ // It does not include the trailing comma that often appears when
+ // this object is nested within an array.
+ // It assumes that netip.Addr never has IPv6 zones.
+ MaxConnectionCountsJSONSize = len(maxJSONConnCounts)
+
+ maxCBORConnCounts = "\xbf" + maxCBORConn + maxCBORCounts + "\xff"
+ maxCBORConn = "\x00" + maxCBORProto + "\x01" + maxCBORAddrPort + "\x02" + maxCBORAddrPort
+ maxCBORProto = "\x18\xff"
+ maxCBORAddrPort = "\x52\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
+ maxCBORCounts = "\x0c" + maxCBORCount + "\x0d" + maxCBORCount + "\x0e" + maxCBORCount + "\x0f" + maxCBORCount
+ maxCBORCount = "\x1b\xff\xff\xff\xff\xff\xff\xff\xff"
+
+ // MaxConnectionCountsCBORSize is the maximum size of a ConnectionCounts
+ // when it is serialized as CBOR.
+ // It assumes that netip.Addr never has IPv6 zones.
+ MaxConnectionCountsCBORSize = len(maxCBORConnCounts)
+)
+
+// ConnectionCounts is a flattened struct of both a connection and counts.
+type ConnectionCounts struct {
+ Connection
+ Counts
+}
+
+// Connection is a 5-tuple of proto, source and destination IP and port.
+type Connection struct {
+ Proto ipproto.Proto `json:"proto,omitzero,omitempty" cbor:"0,keyasint,omitempty"`
+ Src netip.AddrPort `json:"src,omitzero,omitempty" cbor:"1,keyasint,omitempty"`
+ Dst netip.AddrPort `json:"dst,omitzero,omitempty" cbor:"2,keyasint,omitempty"`
+}
+
+func (c Connection) IsZero() bool { return c == Connection{} }
+
+// Counts are statistics about a particular connection.
+type Counts struct {
+ TxPackets uint64 `json:"txPkts,omitzero,omitempty" cbor:"12,keyasint,omitempty"`
+ TxBytes uint64 `json:"txBytes,omitzero,omitempty" cbor:"13,keyasint,omitempty"`
+ RxPackets uint64 `json:"rxPkts,omitzero,omitempty" cbor:"14,keyasint,omitempty"`
+ RxBytes uint64 `json:"rxBytes,omitzero,omitempty" cbor:"15,keyasint,omitempty"`
+}
+
+func (c Counts) IsZero() bool { return c == Counts{} }
+
+// Add adds the counts from both c1 and c2.
+func (c1 Counts) Add(c2 Counts) Counts {
+ c1.TxPackets += c2.TxPackets
+ c1.TxBytes += c2.TxBytes
+ c1.RxPackets += c2.RxPackets
+ c1.RxBytes += c2.RxBytes
+ return c1
+}
diff --git a/types/netlogtype/netlogtype_test.go b/types/netlogtype/netlogtype_test.go
index 7f29090c5..1fa604b31 100644
--- a/types/netlogtype/netlogtype_test.go
+++ b/types/netlogtype/netlogtype_test.go
@@ -1,39 +1,39 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package netlogtype
-
-import (
- "encoding/json"
- "math"
- "net/netip"
- "testing"
-
- "github.com/fxamacker/cbor/v2"
- "github.com/google/go-cmp/cmp"
- "tailscale.com/util/must"
-)
-
-func TestMaxSize(t *testing.T) {
- maxAddr := netip.AddrFrom16([16]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255})
- maxAddrPort := netip.AddrPortFrom(maxAddr, math.MaxUint16)
- cc := ConnectionCounts{
- // NOTE: These composite literals are deliberately unkeyed so that
- // added fields result in a build failure here.
- // Newly added fields should result in an update to both
- // MaxConnectionCountsJSONSize and MaxConnectionCountsCBORSize.
- Connection{math.MaxUint8, maxAddrPort, maxAddrPort},
- Counts{math.MaxUint64, math.MaxUint64, math.MaxUint64, math.MaxUint64},
- }
-
- outJSON := must.Get(json.Marshal(cc))
- if string(outJSON) != maxJSONConnCounts {
- t.Errorf("JSON mismatch (-got +want):\n%s", cmp.Diff(string(outJSON), maxJSONConnCounts))
- }
-
- outCBOR := must.Get(cbor.Marshal(cc))
- maxCBORConnCountsAlt := "\xa7" + maxCBORConnCounts[1:len(maxCBORConnCounts)-1] // may use a definite encoding of map
- if string(outCBOR) != maxCBORConnCounts && string(outCBOR) != maxCBORConnCountsAlt {
- t.Errorf("CBOR mismatch (-got +want):\n%s", cmp.Diff(string(outCBOR), maxCBORConnCounts))
- }
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package netlogtype
+
+import (
+ "encoding/json"
+ "math"
+ "net/netip"
+ "testing"
+
+ "github.com/fxamacker/cbor/v2"
+ "github.com/google/go-cmp/cmp"
+ "tailscale.com/util/must"
+)
+
+func TestMaxSize(t *testing.T) {
+ maxAddr := netip.AddrFrom16([16]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255})
+ maxAddrPort := netip.AddrPortFrom(maxAddr, math.MaxUint16)
+ cc := ConnectionCounts{
+ // NOTE: These composite literals are deliberately unkeyed so that
+ // added fields result in a build failure here.
+ // Newly added fields should result in an update to both
+ // MaxConnectionCountsJSONSize and MaxConnectionCountsCBORSize.
+ Connection{math.MaxUint8, maxAddrPort, maxAddrPort},
+ Counts{math.MaxUint64, math.MaxUint64, math.MaxUint64, math.MaxUint64},
+ }
+
+ outJSON := must.Get(json.Marshal(cc))
+ if string(outJSON) != maxJSONConnCounts {
+ t.Errorf("JSON mismatch (-got +want):\n%s", cmp.Diff(string(outJSON), maxJSONConnCounts))
+ }
+
+ outCBOR := must.Get(cbor.Marshal(cc))
+ maxCBORConnCountsAlt := "\xa7" + maxCBORConnCounts[1:len(maxCBORConnCounts)-1] // may use a definite encoding of map
+ if string(outCBOR) != maxCBORConnCounts && string(outCBOR) != maxCBORConnCountsAlt {
+ t.Errorf("CBOR mismatch (-got +want):\n%s", cmp.Diff(string(outCBOR), maxCBORConnCounts))
+ }
+}
diff --git a/types/netmap/netmap_test.go b/types/netmap/netmap_test.go
index e7e2d1957..910b6bc21 100644
--- a/types/netmap/netmap_test.go
+++ b/types/netmap/netmap_test.go
@@ -1,318 +1,318 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package netmap
-
-import (
- "encoding/hex"
- "net/netip"
- "testing"
-
- "go4.org/mem"
- "tailscale.com/net/netaddr"
- "tailscale.com/tailcfg"
- "tailscale.com/types/key"
-)
-
-func testNodeKey(b byte) (ret key.NodePublic) {
- var bs [key.NodePublicRawLen]byte
- for i := range bs {
- bs[i] = b
- }
- return key.NodePublicFromRaw32(mem.B(bs[:]))
-}
-
-func testDiscoKey(hexPrefix string) (ret key.DiscoPublic) {
- b, err := hex.DecodeString(hexPrefix)
- if err != nil {
- panic(err)
- }
- // this function is used with short hexes, so zero-extend the raw
- // value.
- var bs [32]byte
- copy(bs[:], b)
- return key.DiscoPublicFromRaw32(mem.B(bs[:]))
-}
-
-func nodeViews(v []*tailcfg.Node) []tailcfg.NodeView {
- nv := make([]tailcfg.NodeView, len(v))
- for i, n := range v {
- nv[i] = n.View()
- }
- return nv
-}
-
-func eps(s ...string) []netip.AddrPort {
- var eps []netip.AddrPort
- for _, ep := range s {
- eps = append(eps, netip.MustParseAddrPort(ep))
- }
- return eps
-}
-
-func TestNetworkMapConcise(t *testing.T) {
- for _, tt := range []struct {
- name string
- nm *NetworkMap
- want string
- }{
- {
- name: "basic",
- nm: &NetworkMap{
- NodeKey: testNodeKey(1),
- Peers: nodeViews([]*tailcfg.Node{
- {
- Key: testNodeKey(2),
- DERP: "127.3.3.40:2",
- Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
- },
- {
- Key: testNodeKey(3),
- DERP: "127.3.3.40:4",
- Endpoints: eps("10.2.0.100:12", "10.1.0.100:12345"),
- },
- }),
- },
- want: "netmap: self: [AQEBA] auth=machine-unknown u=? []\n [AgICA] D2 : 192.168.0.100:12 192.168.0.100:12354\n [AwMDA] D4 : 10.2.0.100:12 10.1.0.100:12345\n",
- },
- } {
- t.Run(tt.name, func(t *testing.T) {
- var got string
- n := int(testing.AllocsPerRun(1000, func() {
- got = tt.nm.Concise()
- }))
- t.Logf("Allocs = %d", n)
- if got != tt.want {
- t.Errorf("Wrong output\n Got: %q\nWant: %q\n## Got (unescaped):\n%s\n## Want (unescaped):\n%s\n", got, tt.want, got, tt.want)
- }
- })
- }
-}
-
-func TestConciseDiffFrom(t *testing.T) {
- for _, tt := range []struct {
- name string
- a, b *NetworkMap
- want string
- }{
- {
- name: "no_change",
- a: &NetworkMap{
- NodeKey: testNodeKey(1),
- Peers: nodeViews([]*tailcfg.Node{
- {
- Key: testNodeKey(2),
- DERP: "127.3.3.40:2",
- Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
- },
- }),
- },
- b: &NetworkMap{
- NodeKey: testNodeKey(1),
- Peers: nodeViews([]*tailcfg.Node{
- {
- Key: testNodeKey(2),
- DERP: "127.3.3.40:2",
- Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
- },
- }),
- },
- want: "",
- },
- {
- name: "header_change",
- a: &NetworkMap{
- NodeKey: testNodeKey(1),
- Peers: nodeViews([]*tailcfg.Node{
- {
- Key: testNodeKey(2),
- DERP: "127.3.3.40:2",
- Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
- },
- }),
- },
- b: &NetworkMap{
- NodeKey: testNodeKey(2),
- Peers: nodeViews([]*tailcfg.Node{
- {
- Key: testNodeKey(2),
- DERP: "127.3.3.40:2",
- Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
- },
- }),
- },
- want: "-netmap: self: [AQEBA] auth=machine-unknown u=? []\n+netmap: self: [AgICA] auth=machine-unknown u=? []\n",
- },
- {
- name: "peer_add",
- a: &NetworkMap{
- NodeKey: testNodeKey(1),
- Peers: nodeViews([]*tailcfg.Node{
- {
- ID: 2,
- Key: testNodeKey(2),
- DERP: "127.3.3.40:2",
- Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
- },
- }),
- },
- b: &NetworkMap{
- NodeKey: testNodeKey(1),
- Peers: nodeViews([]*tailcfg.Node{
- {
- ID: 1,
- Key: testNodeKey(1),
- DERP: "127.3.3.40:1",
- Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
- },
- {
- ID: 2,
- Key: testNodeKey(2),
- DERP: "127.3.3.40:2",
- Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
- },
- {
- ID: 3,
- Key: testNodeKey(3),
- DERP: "127.3.3.40:3",
- Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
- },
- }),
- },
- want: "+ [AQEBA] D1 : 192.168.0.100:12 192.168.0.100:12354\n+ [AwMDA] D3 : 192.168.0.100:12 192.168.0.100:12354\n",
- },
- {
- name: "peer_remove",
- a: &NetworkMap{
- NodeKey: testNodeKey(1),
- Peers: nodeViews([]*tailcfg.Node{
- {
- ID: 1,
- Key: testNodeKey(1),
- DERP: "127.3.3.40:1",
- Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
- },
- {
- ID: 2,
- Key: testNodeKey(2),
- DERP: "127.3.3.40:2",
- Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
- },
- {
- ID: 3,
- Key: testNodeKey(3),
- DERP: "127.3.3.40:3",
- Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
- },
- }),
- },
- b: &NetworkMap{
- NodeKey: testNodeKey(1),
- Peers: nodeViews([]*tailcfg.Node{
- {
- ID: 2,
- Key: testNodeKey(2),
- DERP: "127.3.3.40:2",
- Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
- },
- }),
- },
- want: "- [AQEBA] D1 : 192.168.0.100:12 192.168.0.100:12354\n- [AwMDA] D3 : 192.168.0.100:12 192.168.0.100:12354\n",
- },
- {
- name: "peer_port_change",
- a: &NetworkMap{
- NodeKey: testNodeKey(1),
- Peers: nodeViews([]*tailcfg.Node{
- {
- ID: 2,
- Key: testNodeKey(2),
- DERP: "127.3.3.40:2",
- Endpoints: eps("192.168.0.100:12", "1.1.1.1:1"),
- },
- }),
- },
- b: &NetworkMap{
- NodeKey: testNodeKey(1),
- Peers: nodeViews([]*tailcfg.Node{
- {
- ID: 2,
- Key: testNodeKey(2),
- DERP: "127.3.3.40:2",
- Endpoints: eps("192.168.0.100:12", "1.1.1.1:2"),
- },
- }),
- },
- want: "- [AgICA] D2 : 192.168.0.100:12 1.1.1.1:1 \n+ [AgICA] D2 : 192.168.0.100:12 1.1.1.1:2 \n",
- },
- {
- name: "disco_key_only_change",
- a: &NetworkMap{
- NodeKey: testNodeKey(1),
- Peers: nodeViews([]*tailcfg.Node{
- {
- ID: 2,
- Key: testNodeKey(2),
- DERP: "127.3.3.40:2",
- Endpoints: eps("192.168.0.100:41641", "1.1.1.1:41641"),
- DiscoKey: testDiscoKey("f00f00f00f"),
- AllowedIPs: []netip.Prefix{netip.PrefixFrom(netaddr.IPv4(100, 102, 103, 104), 32)},
- },
- }),
- },
- b: &NetworkMap{
- NodeKey: testNodeKey(1),
- Peers: nodeViews([]*tailcfg.Node{
- {
- ID: 2,
- Key: testNodeKey(2),
- DERP: "127.3.3.40:2",
- Endpoints: eps("192.168.0.100:41641", "1.1.1.1:41641"),
- DiscoKey: testDiscoKey("ba4ba4ba4b"),
- AllowedIPs: []netip.Prefix{netip.PrefixFrom(netaddr.IPv4(100, 102, 103, 104), 32)},
- },
- }),
- },
- want: "- [AgICA] d:f00f00f00f000000 D2 100.102.103.104 : 192.168.0.100:41641 1.1.1.1:41641\n+ [AgICA] d:ba4ba4ba4b000000 D2 100.102.103.104 : 192.168.0.100:41641 1.1.1.1:41641\n",
- },
- } {
- t.Run(tt.name, func(t *testing.T) {
- var got string
- n := int(testing.AllocsPerRun(50, func() {
- got = tt.b.ConciseDiffFrom(tt.a)
- }))
- t.Logf("Allocs = %d", n)
- if got != tt.want {
- t.Errorf("Wrong output\n Got: %q\nWant: %q\n## Got (unescaped):\n%s\n## Want (unescaped):\n%s\n", got, tt.want, got, tt.want)
- }
- })
- }
-}
-
-func TestPeerIndexByNodeID(t *testing.T) {
- var nilPtr *NetworkMap
- if nilPtr.PeerIndexByNodeID(123) != -1 {
- t.Errorf("nil PeerIndexByNodeID should return -1")
- }
- var nm NetworkMap
- const min = 2
- const max = 10000
- const hole = max / 2
- for nid := tailcfg.NodeID(2); nid <= max; nid++ {
- if nid == hole {
- continue
- }
- nm.Peers = append(nm.Peers, (&tailcfg.Node{ID: nid}).View())
- }
- for want, nv := range nm.Peers {
- got := nm.PeerIndexByNodeID(nv.ID())
- if got != want {
- t.Errorf("PeerIndexByNodeID(%v) = %v; want %v", nv.ID(), got, want)
- }
- }
- for _, miss := range []tailcfg.NodeID{min - 1, hole, max + 1} {
- if got := nm.PeerIndexByNodeID(miss); got != -1 {
- t.Errorf("PeerIndexByNodeID(%v) = %v; want -1", miss, got)
- }
- }
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package netmap
+
+import (
+ "encoding/hex"
+ "net/netip"
+ "testing"
+
+ "go4.org/mem"
+ "tailscale.com/net/netaddr"
+ "tailscale.com/tailcfg"
+ "tailscale.com/types/key"
+)
+
+func testNodeKey(b byte) (ret key.NodePublic) {
+ var bs [key.NodePublicRawLen]byte
+ for i := range bs {
+ bs[i] = b
+ }
+ return key.NodePublicFromRaw32(mem.B(bs[:]))
+}
+
+func testDiscoKey(hexPrefix string) (ret key.DiscoPublic) {
+ b, err := hex.DecodeString(hexPrefix)
+ if err != nil {
+ panic(err)
+ }
+ // this function is used with short hexes, so zero-extend the raw
+ // value.
+ var bs [32]byte
+ copy(bs[:], b)
+ return key.DiscoPublicFromRaw32(mem.B(bs[:]))
+}
+
+func nodeViews(v []*tailcfg.Node) []tailcfg.NodeView {
+ nv := make([]tailcfg.NodeView, len(v))
+ for i, n := range v {
+ nv[i] = n.View()
+ }
+ return nv
+}
+
+func eps(s ...string) []netip.AddrPort {
+ var eps []netip.AddrPort
+ for _, ep := range s {
+ eps = append(eps, netip.MustParseAddrPort(ep))
+ }
+ return eps
+}
+
+func TestNetworkMapConcise(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ nm *NetworkMap
+ want string
+ }{
+ {
+ name: "basic",
+ nm: &NetworkMap{
+ NodeKey: testNodeKey(1),
+ Peers: nodeViews([]*tailcfg.Node{
+ {
+ Key: testNodeKey(2),
+ DERP: "127.3.3.40:2",
+ Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
+ },
+ {
+ Key: testNodeKey(3),
+ DERP: "127.3.3.40:4",
+ Endpoints: eps("10.2.0.100:12", "10.1.0.100:12345"),
+ },
+ }),
+ },
+ want: "netmap: self: [AQEBA] auth=machine-unknown u=? []\n [AgICA] D2 : 192.168.0.100:12 192.168.0.100:12354\n [AwMDA] D4 : 10.2.0.100:12 10.1.0.100:12345\n",
+ },
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ var got string
+ n := int(testing.AllocsPerRun(1000, func() {
+ got = tt.nm.Concise()
+ }))
+ t.Logf("Allocs = %d", n)
+ if got != tt.want {
+ t.Errorf("Wrong output\n Got: %q\nWant: %q\n## Got (unescaped):\n%s\n## Want (unescaped):\n%s\n", got, tt.want, got, tt.want)
+ }
+ })
+ }
+}
+
+func TestConciseDiffFrom(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ a, b *NetworkMap
+ want string
+ }{
+ {
+ name: "no_change",
+ a: &NetworkMap{
+ NodeKey: testNodeKey(1),
+ Peers: nodeViews([]*tailcfg.Node{
+ {
+ Key: testNodeKey(2),
+ DERP: "127.3.3.40:2",
+ Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
+ },
+ }),
+ },
+ b: &NetworkMap{
+ NodeKey: testNodeKey(1),
+ Peers: nodeViews([]*tailcfg.Node{
+ {
+ Key: testNodeKey(2),
+ DERP: "127.3.3.40:2",
+ Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
+ },
+ }),
+ },
+ want: "",
+ },
+ {
+ name: "header_change",
+ a: &NetworkMap{
+ NodeKey: testNodeKey(1),
+ Peers: nodeViews([]*tailcfg.Node{
+ {
+ Key: testNodeKey(2),
+ DERP: "127.3.3.40:2",
+ Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
+ },
+ }),
+ },
+ b: &NetworkMap{
+ NodeKey: testNodeKey(2),
+ Peers: nodeViews([]*tailcfg.Node{
+ {
+ Key: testNodeKey(2),
+ DERP: "127.3.3.40:2",
+ Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
+ },
+ }),
+ },
+ want: "-netmap: self: [AQEBA] auth=machine-unknown u=? []\n+netmap: self: [AgICA] auth=machine-unknown u=? []\n",
+ },
+ {
+ name: "peer_add",
+ a: &NetworkMap{
+ NodeKey: testNodeKey(1),
+ Peers: nodeViews([]*tailcfg.Node{
+ {
+ ID: 2,
+ Key: testNodeKey(2),
+ DERP: "127.3.3.40:2",
+ Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
+ },
+ }),
+ },
+ b: &NetworkMap{
+ NodeKey: testNodeKey(1),
+ Peers: nodeViews([]*tailcfg.Node{
+ {
+ ID: 1,
+ Key: testNodeKey(1),
+ DERP: "127.3.3.40:1",
+ Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
+ },
+ {
+ ID: 2,
+ Key: testNodeKey(2),
+ DERP: "127.3.3.40:2",
+ Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
+ },
+ {
+ ID: 3,
+ Key: testNodeKey(3),
+ DERP: "127.3.3.40:3",
+ Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
+ },
+ }),
+ },
+ want: "+ [AQEBA] D1 : 192.168.0.100:12 192.168.0.100:12354\n+ [AwMDA] D3 : 192.168.0.100:12 192.168.0.100:12354\n",
+ },
+ {
+ name: "peer_remove",
+ a: &NetworkMap{
+ NodeKey: testNodeKey(1),
+ Peers: nodeViews([]*tailcfg.Node{
+ {
+ ID: 1,
+ Key: testNodeKey(1),
+ DERP: "127.3.3.40:1",
+ Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
+ },
+ {
+ ID: 2,
+ Key: testNodeKey(2),
+ DERP: "127.3.3.40:2",
+ Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
+ },
+ {
+ ID: 3,
+ Key: testNodeKey(3),
+ DERP: "127.3.3.40:3",
+ Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
+ },
+ }),
+ },
+ b: &NetworkMap{
+ NodeKey: testNodeKey(1),
+ Peers: nodeViews([]*tailcfg.Node{
+ {
+ ID: 2,
+ Key: testNodeKey(2),
+ DERP: "127.3.3.40:2",
+ Endpoints: eps("192.168.0.100:12", "192.168.0.100:12354"),
+ },
+ }),
+ },
+ want: "- [AQEBA] D1 : 192.168.0.100:12 192.168.0.100:12354\n- [AwMDA] D3 : 192.168.0.100:12 192.168.0.100:12354\n",
+ },
+ {
+ name: "peer_port_change",
+ a: &NetworkMap{
+ NodeKey: testNodeKey(1),
+ Peers: nodeViews([]*tailcfg.Node{
+ {
+ ID: 2,
+ Key: testNodeKey(2),
+ DERP: "127.3.3.40:2",
+ Endpoints: eps("192.168.0.100:12", "1.1.1.1:1"),
+ },
+ }),
+ },
+ b: &NetworkMap{
+ NodeKey: testNodeKey(1),
+ Peers: nodeViews([]*tailcfg.Node{
+ {
+ ID: 2,
+ Key: testNodeKey(2),
+ DERP: "127.3.3.40:2",
+ Endpoints: eps("192.168.0.100:12", "1.1.1.1:2"),
+ },
+ }),
+ },
+ want: "- [AgICA] D2 : 192.168.0.100:12 1.1.1.1:1 \n+ [AgICA] D2 : 192.168.0.100:12 1.1.1.1:2 \n",
+ },
+ {
+ name: "disco_key_only_change",
+ a: &NetworkMap{
+ NodeKey: testNodeKey(1),
+ Peers: nodeViews([]*tailcfg.Node{
+ {
+ ID: 2,
+ Key: testNodeKey(2),
+ DERP: "127.3.3.40:2",
+ Endpoints: eps("192.168.0.100:41641", "1.1.1.1:41641"),
+ DiscoKey: testDiscoKey("f00f00f00f"),
+ AllowedIPs: []netip.Prefix{netip.PrefixFrom(netaddr.IPv4(100, 102, 103, 104), 32)},
+ },
+ }),
+ },
+ b: &NetworkMap{
+ NodeKey: testNodeKey(1),
+ Peers: nodeViews([]*tailcfg.Node{
+ {
+ ID: 2,
+ Key: testNodeKey(2),
+ DERP: "127.3.3.40:2",
+ Endpoints: eps("192.168.0.100:41641", "1.1.1.1:41641"),
+ DiscoKey: testDiscoKey("ba4ba4ba4b"),
+ AllowedIPs: []netip.Prefix{netip.PrefixFrom(netaddr.IPv4(100, 102, 103, 104), 32)},
+ },
+ }),
+ },
+ want: "- [AgICA] d:f00f00f00f000000 D2 100.102.103.104 : 192.168.0.100:41641 1.1.1.1:41641\n+ [AgICA] d:ba4ba4ba4b000000 D2 100.102.103.104 : 192.168.0.100:41641 1.1.1.1:41641\n",
+ },
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ var got string
+ n := int(testing.AllocsPerRun(50, func() {
+ got = tt.b.ConciseDiffFrom(tt.a)
+ }))
+ t.Logf("Allocs = %d", n)
+ if got != tt.want {
+ t.Errorf("Wrong output\n Got: %q\nWant: %q\n## Got (unescaped):\n%s\n## Want (unescaped):\n%s\n", got, tt.want, got, tt.want)
+ }
+ })
+ }
+}
+
+func TestPeerIndexByNodeID(t *testing.T) {
+ var nilPtr *NetworkMap
+ if nilPtr.PeerIndexByNodeID(123) != -1 {
+ t.Errorf("nil PeerIndexByNodeID should return -1")
+ }
+ var nm NetworkMap
+ const min = 2
+ const max = 10000
+ const hole = max / 2
+ for nid := tailcfg.NodeID(2); nid <= max; nid++ {
+ if nid == hole {
+ continue
+ }
+ nm.Peers = append(nm.Peers, (&tailcfg.Node{ID: nid}).View())
+ }
+ for want, nv := range nm.Peers {
+ got := nm.PeerIndexByNodeID(nv.ID())
+ if got != want {
+ t.Errorf("PeerIndexByNodeID(%v) = %v; want %v", nv.ID(), got, want)
+ }
+ }
+ for _, miss := range []tailcfg.NodeID{min - 1, hole, max + 1} {
+ if got := nm.PeerIndexByNodeID(miss); got != -1 {
+ t.Errorf("PeerIndexByNodeID(%v) = %v; want -1", miss, got)
+ }
+ }
+}
diff --git a/types/nettype/nettype.go b/types/nettype/nettype.go
index 5d3d303c3..8930c36d8 100644
--- a/types/nettype/nettype.go
+++ b/types/nettype/nettype.go
@@ -1,65 +1,65 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-// Package nettype defines an interface that doesn't exist in the Go net package.
-package nettype
-
-import (
- "context"
- "io"
- "net"
- "net/netip"
- "time"
-)
-
-// PacketListener defines the ListenPacket method as implemented
-// by net.ListenConfig, net.ListenPacket, and tstest/natlab.
-type PacketListener interface {
- ListenPacket(ctx context.Context, network, address string) (net.PacketConn, error)
-}
-
-type PacketListenerWithNetIP interface {
- ListenPacket(ctx context.Context, network, address string) (PacketConn, error)
-}
-
-// Std implements PacketListener using the Go net package's ListenPacket func.
-type Std struct{}
-
-func (Std) ListenPacket(ctx context.Context, network, address string) (net.PacketConn, error) {
- var conf net.ListenConfig
- return conf.ListenPacket(ctx, network, address)
-}
-
-// PacketConn is like a net.PacketConn but uses the newer netip.AddrPort
-// write/read methods.
-type PacketConn interface {
- WriteToUDPAddrPort([]byte, netip.AddrPort) (int, error)
- ReadFromUDPAddrPort([]byte) (int, netip.AddrPort, error)
- io.Closer
- LocalAddr() net.Addr
- SetDeadline(time.Time) error
- SetReadDeadline(time.Time) error
- SetWriteDeadline(time.Time) error
-}
-
-func MakePacketListenerWithNetIP(ln PacketListener) PacketListenerWithNetIP {
- return packetListenerAdapter{ln}
-}
-
-type packetListenerAdapter struct {
- PacketListener
-}
-
-func (a packetListenerAdapter) ListenPacket(ctx context.Context, network, address string) (PacketConn, error) {
- pc, err := a.PacketListener.ListenPacket(ctx, network, address)
- if err != nil {
- return nil, err
- }
- return pc.(PacketConn), nil
-}
-
-// ConnPacketConn is the interface that's a superset of net.Conn and net.PacketConn.
-type ConnPacketConn interface {
- net.Conn
- net.PacketConn
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package nettype defines an interface that doesn't exist in the Go net package.
+package nettype
+
+import (
+ "context"
+ "io"
+ "net"
+ "net/netip"
+ "time"
+)
+
+// PacketListener defines the ListenPacket method as implemented
+// by net.ListenConfig, net.ListenPacket, and tstest/natlab.
+type PacketListener interface {
+ ListenPacket(ctx context.Context, network, address string) (net.PacketConn, error)
+}
+
+type PacketListenerWithNetIP interface {
+ ListenPacket(ctx context.Context, network, address string) (PacketConn, error)
+}
+
+// Std implements PacketListener using the Go net package's ListenPacket func.
+type Std struct{}
+
+func (Std) ListenPacket(ctx context.Context, network, address string) (net.PacketConn, error) {
+ var conf net.ListenConfig
+ return conf.ListenPacket(ctx, network, address)
+}
+
+// PacketConn is like a net.PacketConn but uses the newer netip.AddrPort
+// write/read methods.
+type PacketConn interface {
+ WriteToUDPAddrPort([]byte, netip.AddrPort) (int, error)
+ ReadFromUDPAddrPort([]byte) (int, netip.AddrPort, error)
+ io.Closer
+ LocalAddr() net.Addr
+ SetDeadline(time.Time) error
+ SetReadDeadline(time.Time) error
+ SetWriteDeadline(time.Time) error
+}
+
+func MakePacketListenerWithNetIP(ln PacketListener) PacketListenerWithNetIP {
+ return packetListenerAdapter{ln}
+}
+
+type packetListenerAdapter struct {
+ PacketListener
+}
+
+func (a packetListenerAdapter) ListenPacket(ctx context.Context, network, address string) (PacketConn, error) {
+ pc, err := a.PacketListener.ListenPacket(ctx, network, address)
+ if err != nil {
+ return nil, err
+ }
+ return pc.(PacketConn), nil
+}
+
+// ConnPacketConn is the interface that's a superset of net.Conn and net.PacketConn.
+type ConnPacketConn interface {
+ net.Conn
+ net.PacketConn
+}
diff --git a/types/preftype/netfiltermode.go b/types/preftype/netfiltermode.go
index 273e17344..5756e5096 100644
--- a/types/preftype/netfiltermode.go
+++ b/types/preftype/netfiltermode.go
@@ -1,46 +1,46 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-// Package preftype is a leaf package containing types for various
-// preferences.
-package preftype
-
-import "fmt"
-
-// NetfilterMode is the firewall management mode to use when
-// programming the Linux network stack.
-type NetfilterMode int
-
-// These numbers are persisted to disk in JSON files and thus can't be
-// renumbered or repurposed.
-const (
- NetfilterOff NetfilterMode = 0 // remove all tailscale netfilter state
- NetfilterNoDivert NetfilterMode = 1 // manage tailscale chains, but don't call them
- NetfilterOn NetfilterMode = 2 // manage tailscale chains and call them from main chains
-)
-
-func ParseNetfilterMode(s string) (NetfilterMode, error) {
- switch s {
- case "off":
- return NetfilterOff, nil
- case "nodivert":
- return NetfilterNoDivert, nil
- case "on":
- return NetfilterOn, nil
- default:
- return NetfilterOff, fmt.Errorf("unknown netfilter mode %q", s)
- }
-}
-
-func (m NetfilterMode) String() string {
- switch m {
- case NetfilterOff:
- return "off"
- case NetfilterNoDivert:
- return "nodivert"
- case NetfilterOn:
- return "on"
- default:
- return "???"
- }
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package preftype is a leaf package containing types for various
+// preferences.
+package preftype
+
+import "fmt"
+
+// NetfilterMode is the firewall management mode to use when
+// programming the Linux network stack.
+type NetfilterMode int
+
+// These numbers are persisted to disk in JSON files and thus can't be
+// renumbered or repurposed.
+const (
+ NetfilterOff NetfilterMode = 0 // remove all tailscale netfilter state
+ NetfilterNoDivert NetfilterMode = 1 // manage tailscale chains, but don't call them
+ NetfilterOn NetfilterMode = 2 // manage tailscale chains and call them from main chains
+)
+
+func ParseNetfilterMode(s string) (NetfilterMode, error) {
+ switch s {
+ case "off":
+ return NetfilterOff, nil
+ case "nodivert":
+ return NetfilterNoDivert, nil
+ case "on":
+ return NetfilterOn, nil
+ default:
+ return NetfilterOff, fmt.Errorf("unknown netfilter mode %q", s)
+ }
+}
+
+func (m NetfilterMode) String() string {
+ switch m {
+ case NetfilterOff:
+ return "off"
+ case NetfilterNoDivert:
+ return "nodivert"
+ case NetfilterOn:
+ return "on"
+ default:
+ return "???"
+ }
+}
diff --git a/types/ptr/ptr.go b/types/ptr/ptr.go
index beb17bee8..beb955bf0 100644
--- a/types/ptr/ptr.go
+++ b/types/ptr/ptr.go
@@ -1,10 +1,10 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-// Package ptr contains the ptr.To function.
-package ptr
-
-// To returns a pointer to a shallow copy of v.
-func To[T any](v T) *T {
- return &v
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package ptr contains the ptr.To function.
+package ptr
+
+// To returns a pointer to a shallow copy of v.
+func To[T any](v T) *T {
+ return &v
+}
diff --git a/types/structs/structs.go b/types/structs/structs.go
index 47c359f0c..bac6b2991 100644
--- a/types/structs/structs.go
+++ b/types/structs/structs.go
@@ -1,15 +1,15 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-// Package structs contains the Incomparable type.
-package structs
-
-// Incomparable is a zero-width incomparable type. If added as the
-// first field in a struct, it marks that struct as not comparable
-// (can't do == or be a map key) and usually doesn't add any width to
-// the struct (unless the struct has only small fields).
-//
-// Be making a struct incomparable, you can prevent misuse (prevent
-// people from using ==), but also you can shrink generated binaries,
-// as the compiler can omit equality funcs from the binary.
-type Incomparable [0]func()
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package structs contains the Incomparable type.
+package structs
+
+// Incomparable is a zero-width incomparable type. If added as the
+// first field in a struct, it marks that struct as not comparable
+// (can't do == or be a map key) and usually doesn't add any width to
+// the struct (unless the struct has only small fields).
+//
+// Be making a struct incomparable, you can prevent misuse (prevent
+// people from using ==), but also you can shrink generated binaries,
+// as the compiler can omit equality funcs from the binary.
+type Incomparable [0]func()
diff --git a/types/tkatype/tkatype.go b/types/tkatype/tkatype.go
index 6ad51f6a9..aca6f1443 100644
--- a/types/tkatype/tkatype.go
+++ b/types/tkatype/tkatype.go
@@ -1,40 +1,40 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-// Package tkatype defines types for working with the tka package.
-//
-// Do not add extra dependencies to this package unless they are tiny,
-// because this package encodes wire types that should be lightweight to use.
-package tkatype
-
-// KeyID references a verification key stored in the key authority. A keyID
-// uniquely identifies a key. KeyIDs are all 32 bytes.
-//
-// For 25519 keys: We just use the 32-byte public key.
-//
-// Even though this is a 32-byte value, we use a byte slice because
-// CBOR-encoded byte slices have a different prefix to CBOR-encoded arrays.
-// Encoding as a byte slice allows us to change the size in the future if we
-// ever need to.
-type KeyID []byte
-
-// MarshaledSignature represents a marshaled tka.NodeKeySignature.
-type MarshaledSignature []byte
-
-// MarshaledAUM represents a marshaled tka.AUM.
-type MarshaledAUM []byte
-
-// AUMSigHash represents the BLAKE2s digest of an Authority Update
-// Message (AUM), sans any signatures.
-type AUMSigHash [32]byte
-
-// NKSSigHash represents the BLAKE2s digest of a Node-Key Signature (NKS),
-// sans the Signature field if present.
-type NKSSigHash [32]byte
-
-// Signature describes a signature over an AUM, which can be verified
-// using the key referenced by KeyID.
-type Signature struct {
- KeyID KeyID `cbor:"1,keyasint"`
- Signature []byte `cbor:"2,keyasint"`
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package tkatype defines types for working with the tka package.
+//
+// Do not add extra dependencies to this package unless they are tiny,
+// because this package encodes wire types that should be lightweight to use.
+package tkatype
+
+// KeyID references a verification key stored in the key authority. A keyID
+// uniquely identifies a key. KeyIDs are all 32 bytes.
+//
+// For 25519 keys: We just use the 32-byte public key.
+//
+// Even though this is a 32-byte value, we use a byte slice because
+// CBOR-encoded byte slices have a different prefix to CBOR-encoded arrays.
+// Encoding as a byte slice allows us to change the size in the future if we
+// ever need to.
+type KeyID []byte
+
+// MarshaledSignature represents a marshaled tka.NodeKeySignature.
+type MarshaledSignature []byte
+
+// MarshaledAUM represents a marshaled tka.AUM.
+type MarshaledAUM []byte
+
+// AUMSigHash represents the BLAKE2s digest of an Authority Update
+// Message (AUM), sans any signatures.
+type AUMSigHash [32]byte
+
+// NKSSigHash represents the BLAKE2s digest of a Node-Key Signature (NKS),
+// sans the Signature field if present.
+type NKSSigHash [32]byte
+
+// Signature describes a signature over an AUM, which can be verified
+// using the key referenced by KeyID.
+type Signature struct {
+ KeyID KeyID `cbor:"1,keyasint"`
+ Signature []byte `cbor:"2,keyasint"`
+}
diff --git a/types/tkatype/tkatype_test.go b/types/tkatype/tkatype_test.go
index c81891b9c..bff908072 100644
--- a/types/tkatype/tkatype_test.go
+++ b/types/tkatype/tkatype_test.go
@@ -1,43 +1,43 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package tkatype
-
-import (
- "encoding/json"
- "testing"
-
- "golang.org/x/crypto/blake2s"
-)
-
-func TestSigHashSize(t *testing.T) {
- var sigHash AUMSigHash
- if len(sigHash) != blake2s.Size {
- t.Errorf("AUMSigHash is wrong size: got %d, want %d", len(sigHash), blake2s.Size)
- }
-
- var nksHash NKSSigHash
- if len(nksHash) != blake2s.Size {
- t.Errorf("NKSSigHash is wrong size: got %d, want %d", len(nksHash), blake2s.Size)
- }
-}
-
-func TestMarshaledSignatureJSON(t *testing.T) {
- sig := MarshaledSignature("abcdef")
- j, err := json.Marshal(sig)
- if err != nil {
- t.Fatal(err)
- }
- const encoded = `"YWJjZGVm"`
- if string(j) != encoded {
- t.Errorf("got JSON %q; want %q", j, encoded)
- }
-
- var back MarshaledSignature
- if err := json.Unmarshal([]byte(encoded), &back); err != nil {
- t.Fatal(err)
- }
- if string(back) != string(sig) {
- t.Errorf("decoded JSON back to %q; want %q", back, sig)
- }
-}
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package tkatype
+
+import (
+ "encoding/json"
+ "testing"
+
+ "golang.org/x/crypto/blake2s"
+)
+
+func TestSigHashSize(t *testing.T) {
+ var sigHash AUMSigHash
+ if len(sigHash) != blake2s.Size {
+ t.Errorf("AUMSigHash is wrong size: got %d, want %d", len(sigHash), blake2s.Size)
+ }
+
+ var nksHash NKSSigHash
+ if len(nksHash) != blake2s.Size {
+ t.Errorf("NKSSigHash is wrong size: got %d, want %d", len(nksHash), blake2s.Size)
+ }
+}
+
+func TestMarshaledSignatureJSON(t *testing.T) {
+ sig := MarshaledSignature("abcdef")
+ j, err := json.Marshal(sig)
+ if err != nil {
+ t.Fatal(err)
+ }
+ const encoded = `"YWJjZGVm"`
+ if string(j) != encoded {
+ t.Errorf("got JSON %q; want %q", j, encoded)
+ }
+
+ var back MarshaledSignature
+ if err := json.Unmarshal([]byte(encoded), &back); err != nil {
+ t.Fatal(err)
+ }
+ if string(back) != string(sig) {
+ t.Errorf("decoded JSON back to %q; want %q", back, sig)
+ }
+}