diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go index 5cdfe68cf..d54f0e911 100644 --- a/control/controlclient/auto.go +++ b/control/controlclient/auto.go @@ -22,6 +22,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/empty" "tailscale.com/types/logger" + "tailscale.com/types/structs" ) // TODO(apenwarr): eliminate the 'state' variable, as it's now obsolete. @@ -61,6 +62,7 @@ func (s state) String() string { } type Status struct { + _ structs.Incomparable LoginFinished *empty.Message Err string URL string @@ -94,6 +96,7 @@ func (s Status) String() string { } type LoginGoal struct { + _ structs.Incomparable wantLoggedIn bool // true if we *want* to be logged in token *oauth2.Token // oauth token to use when logging in flags LoginFlags // flags to use when logging in diff --git a/control/controlclient/controlclient_test.go b/control/controlclient/controlclient_test.go index 4790eec13..855e2d5b9 100644 --- a/control/controlclient/controlclient_test.go +++ b/control/controlclient/controlclient_test.go @@ -13,7 +13,9 @@ import ( func fieldsOf(t reflect.Type) (fields []string) { for i := 0; i < t.NumField(); i++ { - fields = append(fields, t.Field(i).Name) + if name := t.Field(i).Name; name != "_" { + fields = append(fields, name) + } } return } diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 3df89b7c0..250cddf1c 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -30,10 +30,12 @@ import ( "tailscale.com/net/tlsdial" "tailscale.com/tailcfg" "tailscale.com/types/logger" + "tailscale.com/types/structs" "tailscale.com/version" ) type Persist struct { + _ structs.Incomparable PrivateMachineKey wgcfg.PrivateKey PrivateNodeKey wgcfg.PrivateKey OldPrivateNodeKey wgcfg.PrivateKey // needed to request key rotation diff --git a/derp/derpmap/derpmap.go b/derp/derpmap/derpmap.go index 2eb411718..8ba4a248b 100644 --- a/derp/derpmap/derpmap.go +++ b/derp/derpmap/derpmap.go @@ -7,6 +7,8 @@ package derpmap import ( "fmt" + + "tailscale.com/types/structs" ) // World is a set of DERP server. @@ -109,6 +111,8 @@ func init() { // Server is configuration for a DERP server. type Server struct { + _ structs.Incomparable + ID int // HostHTTPS is the HTTPS hostname. diff --git a/ipn/backend.go b/ipn/backend.go index 00ef9e306..96841d93e 100644 --- a/ipn/backend.go +++ b/ipn/backend.go @@ -11,6 +11,7 @@ import ( "tailscale.com/ipn/ipnstate" "tailscale.com/tailcfg" "tailscale.com/types/empty" + "tailscale.com/types/structs" "tailscale.com/wgengine" ) @@ -46,6 +47,7 @@ type NetworkMap = controlclient.NetworkMap // that they have not changed. // They are JSON-encoded on the wire, despite the lack of struct tags. type Notify struct { + _ structs.Incomparable Version string // version number of IPN backend ErrMessage *string // critical error message, if any LoginFinished *empty.Message // event: non-nil when login process succeeded diff --git a/ipn/message.go b/ipn/message.go index f06c989d6..ea5fbaef7 100644 --- a/ipn/message.go +++ b/ipn/message.go @@ -14,6 +14,7 @@ import ( "time" "tailscale.com/types/logger" + "tailscale.com/types/structs" "tailscale.com/version" ) @@ -34,6 +35,7 @@ type FakeExpireAfterArgs struct { // Command is a command message that is JSON encoded and sent by a // frontend to a backend. type Command struct { + _ structs.Incomparable Version string // Exactly one of the following must be non-nil. diff --git a/ratelimit/ratelimit.go b/ratelimit/ratelimit.go index ca7403ca5..3a0bad70e 100644 --- a/ratelimit/ratelimit.go +++ b/ratelimit/ratelimit.go @@ -7,9 +7,12 @@ package ratelimit import ( "sync" "time" + + "tailscale.com/types/structs" ) type Bucket struct { + _ structs.Incomparable mu sync.Mutex FillInterval time.Duration Burst int diff --git a/stunner/stunner.go b/stunner/stunner.go index 41cb868e8..f407bf400 100644 --- a/stunner/stunner.go +++ b/stunner/stunner.go @@ -17,6 +17,7 @@ import ( "tailscale.com/net/dnscache" "tailscale.com/stun" + "tailscale.com/types/structs" ) // Stunner sends a STUN request to several servers and handles a response. @@ -86,6 +87,7 @@ func (s *Stunner) removeTX(tx stun.TxID) (request, bool) { } type request struct { + _ structs.Incomparable sent time.Time server string } diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 669143b9b..761822cb0 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -15,6 +15,7 @@ import ( "github.com/tailscale/wireguard-go/wgcfg" "golang.org/x/oauth2" "tailscale.com/types/opt" + "tailscale.com/types/structs" ) type ID int64 @@ -99,6 +100,7 @@ func (u *User) Clone() *User { } type Login struct { + _ structs.Incomparable ID LoginID Provider string LoginName string @@ -253,6 +255,7 @@ const ( ) type Service struct { + _ structs.Incomparable Proto ServiceProto // TCP or UDP Port uint16 // port number service is listening on Description string // text description of service @@ -386,10 +389,12 @@ func (h *Hostinfo) Equal(h2 *Hostinfo) bool { // using the local machine key, and sent to: // https://login.tailscale.com/machine/ type RegisterRequest struct { + _ structs.Incomparable Version int // currently 1 NodeKey NodeKey OldNodeKey NodeKey Auth struct { + _ structs.Incomparable // One of Provider/LoginName, Oauth2Token, or AuthKey is set. Provider, LoginName string Oauth2Token *oauth2.Token @@ -452,6 +457,7 @@ var PortRangeAny = PortRange{0, 65535} // NetPortRange represents a single subnet:portrange. type NetPortRange struct { + _ structs.Incomparable IP string Bits *int // backward compatibility: if missing, means "all" bits Ports PortRange diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index c402f6dea..2801c94f1 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -157,13 +157,13 @@ func TestHostinfoEqual(t *testing.T) { }, { - &Hostinfo{Services: []Service{Service{TCP, 1234, "foo"}}}, - &Hostinfo{Services: []Service{Service{UDP, 2345, "bar"}}}, + &Hostinfo{Services: []Service{Service{Proto: TCP, Port: 1234, Description: "foo"}}}, + &Hostinfo{Services: []Service{Service{Proto: UDP, Port: 2345, Description: "bar"}}}, false, }, { - &Hostinfo{Services: []Service{Service{TCP, 1234, "foo"}}}, - &Hostinfo{Services: []Service{Service{TCP, 1234, "foo"}}}, + &Hostinfo{Services: []Service{Service{Proto: TCP, Port: 1234, Description: "foo"}}}, + &Hostinfo{Services: []Service{Service{Proto: TCP, Port: 1234, Description: "foo"}}}, true, }, } diff --git a/types/structs/structs.go b/types/structs/structs.go new file mode 100644 index 000000000..237c02d6a --- /dev/null +++ b/types/structs/structs.go @@ -0,0 +1,16 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package 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/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 0d95a00ba..a95a6c5f3 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -43,6 +43,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/key" "tailscale.com/types/logger" + "tailscale.com/types/structs" "tailscale.com/version" ) @@ -1072,6 +1073,7 @@ func (c *Conn) findAddrSet(addr *net.UDPAddr) *AddrSet { } type udpReadResult struct { + _ structs.Incomparable n int err error addr *net.UDPAddr