diff options
| author | Nick Khyl <nickk@tailscale.com> | 2024-12-05 13:16:48 -0600 |
|---|---|---|
| committer | Nick Khyl <nickk@tailscale.com> | 2024-12-05 13:16:48 -0600 |
| commit | 0267fe83b200f1702a2fa0a395442c02a053fadb (patch) | |
| tree | 63654c55225eeb834de59a5a0bc8d19033c6145b /types | |
| parent | 87546a5edf6b6503a87eeb2d666baba57398a066 (diff) | |
| download | tailscale-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')
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)
+ }
+}
|
