2020-02-05 22:16:58 +00:00
// 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 tailcfg
2020-10-19 17:46:30 +00:00
//go:generate go run tailscale.com/cmd/cloner --type=User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig,RegisterResponse --clonefunc=true --output=tailcfg_clone.go
2020-07-27 17:40:34 +00:00
2020-02-05 22:16:58 +00:00
import (
"bytes"
"errors"
"fmt"
2020-02-16 06:23:58 +00:00
"reflect"
2020-02-05 22:16:58 +00:00
"strings"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
2020-06-19 02:32:55 +00:00
"go4.org/mem"
2020-02-05 22:16:58 +00:00
"golang.org/x/oauth2"
2020-07-31 20:27:09 +00:00
"inet.af/netaddr"
2020-06-19 02:32:55 +00:00
"tailscale.com/types/key"
2020-02-25 22:05:17 +00:00
"tailscale.com/types/opt"
2020-05-03 20:58:39 +00:00
"tailscale.com/types/structs"
2020-02-05 22:16:58 +00:00
)
type ID int64
type UserID ID
2020-09-30 00:38:56 +00:00
func ( u UserID ) IsZero ( ) bool {
return u == 0
}
2020-02-05 22:16:58 +00:00
type LoginID ID
2020-10-01 00:55:12 +00:00
func ( u LoginID ) IsZero ( ) bool {
return u == 0
}
2020-02-05 22:16:58 +00:00
type NodeID ID
2020-09-30 00:38:56 +00:00
func ( u NodeID ) IsZero ( ) bool {
return u == 0
}
2020-02-05 22:16:58 +00:00
type GroupID ID
2020-10-01 00:47:07 +00:00
func ( u GroupID ) IsZero ( ) bool {
return u == 0
}
2020-02-05 22:16:58 +00:00
type RoleID ID
2020-10-01 00:47:07 +00:00
func ( u RoleID ) IsZero ( ) bool {
return u == 0
}
2020-02-05 22:16:58 +00:00
type CapabilityID ID
2020-02-17 21:17:40 +00:00
// MachineKey is the curve25519 public key for a machine.
2020-02-05 22:16:58 +00:00
type MachineKey [ 32 ] byte
2020-05-15 20:13:44 +00:00
// NodeKey is the curve25519 public key for a node.
2020-02-05 22:16:58 +00:00
type NodeKey [ 32 ] byte
2020-06-19 02:32:55 +00:00
// DiscoKey is the curve25519 public key for path discovery key.
// It's never written to disk or reused between network start-ups.
type DiscoKey [ 32 ] byte
2020-02-05 22:16:58 +00:00
type Group struct {
ID GroupID
Name string
Members [ ] ID
}
type Role struct {
ID RoleID
Name string
Capabilities [ ] CapabilityID
}
type CapType string
const (
CapRead = CapType ( "read" )
CapWrite = CapType ( "write" )
)
type Capability struct {
ID CapabilityID
Type CapType
Val ID
}
// User is an IPN user.
//
// A user can have multiple logins associated with it (e.g. gmail and github oauth).
// (Note: none of our UIs support this yet.)
//
// Some properties are inhereted from the logins and can be overridden, such as
// display name and profile picture.
//
// Other properties must be the same for all logins associated with a user.
// In particular: domain. If a user has a "tailscale.io" domain login, they cannot
// have a general gmail address login associated with the user.
type User struct {
ID UserID
LoginName string ` json:"-" ` // not stored, filled from Login // TODO REMOVE
DisplayName string // if non-empty overrides Login field
ProfilePicURL string // if non-empty overrides Login field
Domain string
Logins [ ] LoginID
Roles [ ] RoleID
Created time . Time
2020-02-27 20:20:29 +00:00
// Note: be sure to update Clone when adding new fields
}
2020-02-05 22:16:58 +00:00
type Login struct {
2020-05-03 20:58:39 +00:00
_ structs . Incomparable
2020-02-05 22:16:58 +00:00
ID LoginID
Provider string
LoginName string
DisplayName string
ProfilePicURL string
Domain string
}
// A UserProfile is display-friendly data for a user.
// It includes the LoginName for display purposes but *not* the Provider.
// It also includes derived data from one of the user's logins.
type UserProfile struct {
ID UserID
2020-06-12 15:17:03 +00:00
LoginName string // "alice@smith.com"; for display purposes only (provider is not listed)
DisplayName string // "Alice Smith"
2020-02-05 22:16:58 +00:00
ProfilePicURL string
2020-10-09 22:56:39 +00:00
Roles [ ] RoleID // deprecated; clients should not rely on Roles
2020-02-05 22:16:58 +00:00
}
type Node struct {
ID NodeID
Name string // DNS
User UserID
Key NodeKey
KeyExpiry time . Time
Machine MachineKey
2020-06-19 02:32:55 +00:00
DiscoKey DiscoKey
2020-02-05 22:16:58 +00:00
Addresses [ ] wgcfg . CIDR // IP addresses of this Node directly
AllowedIPs [ ] wgcfg . CIDR // range of IP addresses to route to this node
Endpoints [ ] string ` json:",omitempty" ` // IP+port (public via STUN, and local LANs)
2020-03-02 21:40:42 +00:00
DERP string ` json:",omitempty" ` // DERP-in-IP:port ("127.3.3.40:N") endpoint
2020-02-05 22:16:58 +00:00
Hostinfo Hostinfo
Created time . Time
LastSeen * time . Time ` json:",omitempty" `
2020-03-14 15:56:52 +00:00
KeepAlive bool // open and keep open a connection to this peer
2020-02-05 22:16:58 +00:00
MachineAuthorized bool // TODO(crawshaw): replace with MachineStatus
// NOTE: any new fields containing pointers in this type
2020-02-27 20:20:29 +00:00
// require changes to Node.Clone.
2020-02-05 22:16:58 +00:00
}
type MachineStatus int
const (
MachineUnknown = MachineStatus ( iota )
MachineUnauthorized // server has yet to approve
MachineAuthorized // server has approved
MachineInvalid // server has explicitly rejected this machine key
)
func ( m MachineStatus ) MarshalText ( ) ( [ ] byte , error ) {
return [ ] byte ( m . String ( ) ) , nil
}
func ( m * MachineStatus ) UnmarshalText ( b [ ] byte ) error {
switch string ( b ) {
case "machine-unknown" :
* m = MachineUnknown
case "machine-unauthorized" :
* m = MachineUnauthorized
case "machine-authorized" :
* m = MachineAuthorized
case "machine-invalid" :
* m = MachineInvalid
default :
var val int
if _ , err := fmt . Sscanf ( string ( b ) , "machine-unknown(%d)" , & val ) ; err != nil {
* m = MachineStatus ( val )
} else {
* m = MachineUnknown
}
}
return nil
}
func ( m MachineStatus ) String ( ) string {
switch m {
case MachineUnknown :
return "machine-unknown"
case MachineUnauthorized :
return "machine-unauthorized"
case MachineAuthorized :
return "machine-authorized"
case MachineInvalid :
return "machine-invalid"
default :
return fmt . Sprintf ( "machine-unknown(%d)" , int ( m ) )
}
}
2020-05-01 05:01:27 +00:00
func isNum ( b byte ) bool {
return b >= '0' && b <= '9'
}
func isAlpha ( b byte ) bool {
return ( b >= 'A' && b <= 'Z' ) || ( b >= 'a' && b <= 'z' )
}
// CheckTag valids whether a given string can be used as an ACL tag.
// For now we allow only ascii alphanumeric tags, and they need to start
// with a letter. No unicode shenanigans allowed, and we reserve punctuation
// marks other than '-' for a possible future URI scheme.
//
// Because we're ignoring unicode entirely, we can treat utf-8 as a series of
// bytes. Anything >= 128 is disqualified anyway.
//
// We might relax these rules later.
func CheckTag ( tag string ) error {
if ! strings . HasPrefix ( tag , "tag:" ) {
return errors . New ( "tags must start with 'tag:'" )
}
tag = tag [ 4 : ]
if tag == "" {
return errors . New ( "tag names must not be empty" )
}
if ! isAlpha ( tag [ 0 ] ) {
return errors . New ( "tag names must start with a letter, after 'tag:'" )
}
for _ , b := range [ ] byte ( tag ) {
if ! isNum ( b ) && ! isAlpha ( b ) && b != '-' {
return errors . New ( "tag names can only contain numbers, letters, or dashes" )
}
}
return nil
}
2020-02-05 22:16:58 +00:00
type ServiceProto string
const (
TCP = ServiceProto ( "tcp" )
UDP = ServiceProto ( "udp" )
)
type Service struct {
2020-05-03 20:58:39 +00:00
_ structs . Incomparable
2020-02-05 22:16:58 +00:00
Proto ServiceProto // TCP or UDP
Port uint16 // port number service is listening on
2020-10-19 15:30:36 +00:00
Description string ` json:",omitempty" ` // text description of service
2020-02-05 22:16:58 +00:00
// TODO(apenwarr): allow advertising services on subnet IPs?
// TODO(apenwarr): add "tags" here for each service?
// NOTE: any new fields containing pointers in this type
2020-02-27 20:20:29 +00:00
// require changes to Hostinfo.Clone.
2020-02-05 22:16:58 +00:00
}
2020-02-25 18:04:20 +00:00
// Hostinfo contains a summary of a Tailscale host.
//
// Because it contains pointers (slices), this type should not be used
// as a value type.
2020-02-05 22:16:58 +00:00
type Hostinfo struct {
// TODO(crawshaw): mark all these fields ",omitempty" when all the
// iOS apps are updated with the latest swift version of this struct.
2020-02-25 18:04:20 +00:00
IPNVersion string // version of this code
2020-10-19 15:30:36 +00:00
FrontendLogID string ` json:",omitempty" ` // logtail ID of frontend instance
BackendLogID string ` json:",omitempty" ` // logtail ID of backend instance
2020-04-01 16:27:35 +00:00
OS string // operating system the client runs on (a version.OS value)
2020-10-19 15:30:36 +00:00
OSVersion string ` json:",omitempty" ` // operating system version, with optional distro prefix ("Debian 10.4", "Windows 10 Pro 10.0.19041")
DeviceModel string ` json:",omitempty" ` // mobile phone model ("Pixel 3a", "iPhone 11 Pro")
2020-02-05 22:16:58 +00:00
Hostname string // name of the host the client runs on
2020-10-19 15:30:36 +00:00
GoArch string ` json:",omitempty" ` // the host's GOARCH value (of the running binary)
2020-02-05 22:16:58 +00:00
RoutableIPs [ ] wgcfg . CIDR ` json:",omitempty" ` // set of IP ranges this client can route
2020-05-01 05:01:27 +00:00
RequestTags [ ] string ` json:",omitempty" ` // set of ACL tags this node wants to claim
2020-02-05 22:16:58 +00:00
Services [ ] Service ` json:",omitempty" ` // services advertised by this machine
2020-02-25 22:05:17 +00:00
NetInfo * NetInfo ` json:",omitempty" `
2020-02-05 22:16:58 +00:00
// NOTE: any new fields containing pointers in this type
2020-02-27 20:20:29 +00:00
// require changes to Hostinfo.Clone and Hostinfo.Equal.
2020-02-05 22:16:58 +00:00
}
2020-02-25 22:05:17 +00:00
// NetInfo contains information about the host's network state.
type NetInfo struct {
// MappingVariesByDestIP says whether the host's NAT mappings
// vary based on the destination IP.
MappingVariesByDestIP opt . Bool
2020-02-28 22:14:02 +00:00
// HairPinning is their router does hairpinning.
// It reports true even if there's no NAT involved.
HairPinning opt . Bool
2020-02-25 22:05:17 +00:00
// WorkingIPv6 is whether IPv6 works.
WorkingIPv6 opt . Bool
// WorkingUDP is whether UDP works.
WorkingUDP opt . Bool
2020-07-06 20:51:17 +00:00
// UPnP is whether UPnP appears present on the LAN.
// Empty means not checked.
UPnP opt . Bool
// PMP is whether NAT-PMP appears present on the LAN.
// Empty means not checked.
PMP opt . Bool
// PCP is whether PCP appears present on the LAN.
// Empty means not checked.
PCP opt . Bool
2020-03-04 06:21:56 +00:00
// PreferredDERP is this node's preferred DERP server
// for incoming traffic. The node might be be temporarily
// connected to multiple DERP servers (to send to other nodes)
// but PreferredDERP is the instance number that the node
// subscribes to traffic at.
// Zero means disconnected or unknown.
PreferredDERP int
// LinkType is the current link type, if known.
2020-10-19 15:30:36 +00:00
LinkType string ` json:",omitempty" ` // "wired", "wifi", "mobile" (LTE, 4G, 3G, etc)
2020-03-04 06:21:56 +00:00
2020-02-25 22:05:17 +00:00
// DERPLatency is the fastest recent time to reach various
2020-05-17 16:51:38 +00:00
// DERP STUN servers, in seconds. The map key is the
// "regionID-v4" or "-v6"; it was previously the DERP server's
// STUN host:port.
2020-02-25 22:05:17 +00:00
//
// This should only be updated rarely, or when there's a
// material change, as any change here also gets uploaded to
// the control plane.
DERPLatency map [ string ] float64 ` json:",omitempty" `
2020-03-04 06:21:56 +00:00
// Update Clone and BasicallyEqual when adding fields.
}
2020-03-13 05:29:47 +00:00
func ( ni * NetInfo ) String ( ) string {
2020-03-23 02:34:09 +00:00
if ni == nil {
return "NetInfo(nil)"
}
2020-07-06 20:51:17 +00:00
return fmt . Sprintf ( "NetInfo{varies=%v hairpin=%v ipv6=%v udp=%v derp=#%v portmap=%v link=%q}" ,
2020-03-13 05:29:47 +00:00
ni . MappingVariesByDestIP , ni . HairPinning , ni . WorkingIPv6 ,
2020-07-06 20:51:17 +00:00
ni . WorkingUDP , ni . PreferredDERP ,
ni . portMapSummary ( ) ,
ni . LinkType )
}
func ( ni * NetInfo ) portMapSummary ( ) string {
if ni . UPnP == "" && ni . PMP == "" && ni . PCP == "" {
2020-07-08 01:54:41 +00:00
return "?"
2020-07-06 20:51:17 +00:00
}
return conciseOptBool ( ni . UPnP , "U" ) + conciseOptBool ( ni . PMP , "M" ) + conciseOptBool ( ni . PCP , "C" )
}
func conciseOptBool ( b opt . Bool , trueVal string ) string {
if b == "" {
return "_"
}
v , ok := b . Get ( )
if ! ok {
return "x"
}
if v {
return trueVal
}
return ""
2020-03-13 05:29:47 +00:00
}
2020-03-04 06:21:56 +00:00
// BasicallyEqual reports whether ni and ni2 are basically equal, ignoring
2020-05-17 16:51:38 +00:00
// changes in DERP ServerLatency & RegionLatency.
2020-03-04 06:21:56 +00:00
func ( ni * NetInfo ) BasicallyEqual ( ni2 * NetInfo ) bool {
if ( ni == nil ) != ( ni2 == nil ) {
return false
}
if ni == nil {
return true
}
return ni . MappingVariesByDestIP == ni2 . MappingVariesByDestIP &&
ni . HairPinning == ni2 . HairPinning &&
ni . WorkingIPv6 == ni2 . WorkingIPv6 &&
ni . WorkingUDP == ni2 . WorkingUDP &&
2020-07-06 20:51:17 +00:00
ni . UPnP == ni2 . UPnP &&
ni . PMP == ni2 . PMP &&
ni . PCP == ni2 . PCP &&
2020-03-04 06:21:56 +00:00
ni . PreferredDERP == ni2 . PreferredDERP &&
ni . LinkType == ni2 . LinkType
2020-02-25 22:05:17 +00:00
}
2020-02-18 03:33:01 +00:00
// Equal reports whether h and h2 are equal.
func ( h * Hostinfo ) Equal ( h2 * Hostinfo ) bool {
2020-07-23 17:41:54 +00:00
if h == nil && h2 == nil {
return true
}
if ( h == nil ) != ( h2 == nil ) {
return false
}
2020-02-18 03:33:01 +00:00
return reflect . DeepEqual ( h , h2 )
}
2020-02-18 11:45:42 +00:00
// RegisterRequest is sent by a client to register the key for a node.
// It is encoded to JSON, encrypted with golang.org/x/crypto/nacl/box,
// using the local machine key, and sent to:
// https://login.tailscale.com/machine/<mkey hex>
2020-02-05 22:16:58 +00:00
type RegisterRequest struct {
2020-05-03 20:58:39 +00:00
_ structs . Incomparable
2020-02-18 11:45:42 +00:00
Version int // currently 1
2020-02-05 22:16:58 +00:00
NodeKey NodeKey
OldNodeKey NodeKey
Auth struct {
2020-05-03 20:58:39 +00:00
_ structs . Incomparable
2020-04-09 07:16:20 +00:00
// One of Provider/LoginName, Oauth2Token, or AuthKey is set.
Provider , LoginName string
Oauth2Token * oauth2 . Token
AuthKey string
2020-02-05 22:16:58 +00:00
}
Expiry time . Time // requested key expiry, server policy may override
Followup string // response waits until AuthURL is visited
2020-02-25 18:04:20 +00:00
Hostinfo * Hostinfo
2020-02-05 22:16:58 +00:00
}
2020-02-27 20:20:29 +00:00
// Clone makes a deep copy of RegisterRequest.
2020-02-18 11:45:42 +00:00
// The result aliases no memory with the original.
2020-07-24 07:59:49 +00:00
//
// TODO: extend cmd/cloner to generate this method.
2020-02-27 20:20:29 +00:00
func ( req * RegisterRequest ) Clone ( ) * RegisterRequest {
2020-02-25 18:04:20 +00:00
res := new ( RegisterRequest )
* res = * req
if res . Hostinfo != nil {
2020-02-27 20:20:29 +00:00
res . Hostinfo = res . Hostinfo . Clone ( )
2020-02-25 18:04:20 +00:00
}
2020-02-18 11:45:42 +00:00
if res . Auth . Oauth2Token != nil {
tok := * res . Auth . Oauth2Token
res . Auth . Oauth2Token = & tok
}
2020-02-25 18:04:20 +00:00
return res
2020-02-18 11:45:42 +00:00
}
// RegisterResponse is returned by the server in response to a RegisterRequest.
2020-02-05 22:16:58 +00:00
type RegisterResponse struct {
User User
Login Login
NodeKeyExpired bool // if true, the NodeKey needs to be replaced
MachineAuthorized bool // TODO(crawshaw): move to using MachineStatus
AuthURL string // if set, authorization pending
}
2020-02-18 11:45:42 +00:00
// MapRequest is sent by a client to start a long-poll network map updates.
// The request includes a copy of the client's current set of WireGuard
// endpoints and general host information.
//
// The request is encoded to JSON, encrypted with golang.org/x/crypto/nacl/box,
// using the local machine key, and sent to:
// https://login.tailscale.com/machine/<mkey hex>/map
2020-02-05 22:16:58 +00:00
type MapRequest struct {
2020-10-15 21:11:57 +00:00
Version int // current version is 4
Compress string // "zstd" or "" (no compression)
KeepAlive bool // whether server should send keep-alives back to us
NodeKey NodeKey
DiscoKey DiscoKey
Endpoints [ ] string // caller's endpoints (IPv4 or IPv6)
IncludeIPv6 bool // include IPv6 endpoints in returned Node Endpoints
IncludeIPv6Overlay bool // include IPv6 Addresses and AllowedIPs in returned Nodes.
DeltaPeers bool // whether the 2nd+ network map in response should be deltas, using PeersChanged, PeersRemoved
Stream bool // if true, multiple MapResponse objects are returned
Hostinfo * Hostinfo
2020-07-03 20:55:33 +00:00
2020-09-17 18:28:09 +00:00
// ReadOnly is whether the client just wants to fetch the
// MapResponse, without updating their Endpoints. The
// Endpoints field will be ignored and LastSeen will not be
// updated and peers will not be notified of changes.
//
2020-09-28 21:44:34 +00:00
// The intended use is for clients to discover the DERP map at
2020-09-17 18:28:09 +00:00
// start-up before their first real endpoint update.
ReadOnly bool ` json:",omitempty" `
// OmitPeers is whether the client is okay with the Peers list
// being omitted in the response. (For example, a client on
// start up using ReadOnly to get the DERP map.)
OmitPeers bool ` json:",omitempty" `
2020-10-19 22:56:59 +00:00
// DebugFlags is a list of strings specifying debugging and
// development features to enable in handling this map
// request. The values are deliberately unspecified, as they get
// added and removed all the time during development, and offer no
// compatibility promise. To roll out semantic changes, bump
// Version instead.
DebugFlags [ ] string ` json:",omitempty" `
2020-02-05 22:16:58 +00:00
}
2020-04-30 05:49:17 +00:00
// PortRange represents a range of UDP or TCP port numbers.
type PortRange struct {
First uint16
Last uint16
}
var PortRangeAny = PortRange { 0 , 65535 }
// NetPortRange represents a single subnet:portrange.
type NetPortRange struct {
2020-05-03 20:58:39 +00:00
_ structs . Incomparable
2020-04-30 05:49:17 +00:00
IP string
Bits * int // backward compatibility: if missing, means "all" bits
Ports PortRange
}
// FilterRule represents one rule in a packet filter.
type FilterRule struct {
SrcIPs [ ] string
SrcBits [ ] int
DstPorts [ ] NetPortRange
}
var FilterAllowAll = [ ] FilterRule {
FilterRule {
SrcIPs : [ ] string { "*" } ,
SrcBits : nil ,
DstPorts : [ ] NetPortRange { NetPortRange {
IP : "*" ,
Bits : nil ,
Ports : PortRange { 0 , 65535 } ,
} } ,
} ,
}
2020-07-31 20:27:09 +00:00
// DNSConfig is the DNS configuration.
type DNSConfig struct {
2020-08-24 21:27:21 +00:00
// Nameservers are the IP addresses of the nameservers to use.
2020-07-31 20:27:09 +00:00
Nameservers [ ] netaddr . IP ` json:",omitempty" `
2020-08-24 21:27:21 +00:00
// Domains are the search domains to use.
Domains [ ] string ` json:",omitempty" `
// PerDomain indicates whether it is preferred to use Nameservers
// only for DNS queries for subdomains of Domains.
// Some OSes and OS configurations don't support per-domain DNS configuration,
// in which case Nameservers applies to all DNS requests regardless of PerDomain's value.
PerDomain bool
// Proxied indicates whether DNS requests are proxied through a tsdns.Resolver.
// This enables Magic DNS. It is togglable independently of PerDomain.
Proxied bool
2020-07-31 20:27:09 +00:00
}
2020-02-05 22:16:58 +00:00
type MapResponse struct {
2020-08-08 03:44:04 +00:00
KeepAlive bool ` json:",omitempty" ` // if set, all other fields are ignored
2020-02-05 22:16:58 +00:00
// Networking
2020-07-31 20:27:09 +00:00
Node * Node
2020-08-08 03:44:04 +00:00
DERPMap * DERPMap ` json:",omitempty" ` // if non-empty, a change in the DERP map.
// Peers, if non-empty, is the complete list of peers.
// It will be set in the first MapResponse for a long-polled request/response.
// Subsequent responses will be delta-encoded if DeltaPeers was set in the request.
// If Peers is non-empty, PeersChanged and PeersRemoved should
// be ignored (and should be empty).
// Peers is always returned sorted by Node.ID.
Peers [ ] * Node ` json:",omitempty" `
// PeersChanged are the Nodes (identified by their ID) that
// have changed or been added since the past update on the
// HTTP response. It's only set if MapRequest.DeltaPeers was true.
// PeersChanged is always returned sorted by Node.ID.
PeersChanged [ ] * Node ` json:",omitempty" `
// PeersRemoved are the NodeIDs that are no longer in the peer list.
PeersRemoved [ ] NodeID ` json:",omitempty" `
2020-07-31 20:27:09 +00:00
// DNS is the same as DNSConfig.Nameservers.
//
// TODO(dmytro): should be sent in DNSConfig.Nameservers once clients have updated.
2020-08-08 03:44:04 +00:00
DNS [ ] wgcfg . IP ` json:",omitempty" `
2020-07-31 20:27:09 +00:00
// SearchPaths are the same as DNSConfig.Domains.
//
// TODO(dmytro): should be sent in DNSConfig.Domains once clients have updated.
2020-08-08 03:44:04 +00:00
SearchPaths [ ] string ` json:",omitempty" `
DNSConfig DNSConfig ` json:",omitempty" `
2020-02-05 22:16:58 +00:00
// ACLs
Domain string
2020-04-30 05:49:17 +00:00
PacketFilter [ ] FilterRule
2020-10-15 01:35:55 +00:00
UserProfiles [ ] UserProfile // as of 1.1.541: may be new or updated user profiles only
Roles [ ] Role // deprecated; clients should not rely on Roles
2020-02-05 22:16:58 +00:00
// TODO: Groups []Group
// TODO: Capabilities []Capability
2020-04-08 05:24:06 +00:00
// Debug is normally nil, except for when the control server
// is setting debug settings on a node.
Debug * Debug ` json:",omitempty" `
}
// Debug are instructions from the control server to the client
// to adjust debug settings.
type Debug struct {
2020-06-12 17:12:35 +00:00
// LogHeapPprof controls whether the client should log
2020-04-08 05:24:06 +00:00
// its heap pprof data. Each true value sent from the server
// means that client should do one more log.
LogHeapPprof bool ` json:",omitempty" `
2020-06-12 17:12:35 +00:00
// LogHeapURL is the URL to POST its heap pprof to.
// Empty means to not log.
LogHeapURL string ` json:",omitempty" `
2020-06-25 21:19:12 +00:00
// ForceBackgroundSTUN controls whether magicsock should
// always do its background STUN queries (see magicsock's
// periodicReSTUN), regardless of inactivity.
ForceBackgroundSTUN bool ` json:",omitempty" `
2020-08-17 19:56:17 +00:00
// DERPRoute controls whether the DERP reverse path
// optimization (see Issue 150) should be enabled or
// disabled. The environment variable in magicsock is the
// highest priority (if set), then this (if set), then the
// binary default value.
DERPRoute opt . Bool ` json:",omitempty" `
2020-08-20 20:21:25 +00:00
// TrimWGConfig controls whether Tailscale does lazy, on-demand
// wireguard configuration of peers.
TrimWGConfig opt . Bool ` json:",omitempty" `
2020-02-05 22:16:58 +00:00
}
2020-06-19 02:32:55 +00:00
func ( k MachineKey ) String ( ) string { return fmt . Sprintf ( "mkey:%x" , k [ : ] ) }
func ( k MachineKey ) MarshalText ( ) ( [ ] byte , error ) { return keyMarshalText ( "mkey:" , k ) , nil }
2020-09-28 22:28:26 +00:00
func ( k MachineKey ) HexString ( ) string { return fmt . Sprintf ( "%x" , k [ : ] ) }
2020-06-19 02:32:55 +00:00
func ( k * MachineKey ) UnmarshalText ( text [ ] byte ) error { return keyUnmarshalText ( k [ : ] , "mkey:" , text ) }
2020-02-05 22:16:58 +00:00
2020-06-19 02:32:55 +00:00
func keyMarshalText ( prefix string , k [ 32 ] byte ) [ ] byte {
buf := bytes . NewBuffer ( make ( [ ] byte , 0 , len ( prefix ) + 64 ) )
fmt . Fprintf ( buf , "%s%x" , prefix , k [ : ] )
return buf . Bytes ( )
2020-02-05 22:16:58 +00:00
}
2020-06-19 02:32:55 +00:00
func keyUnmarshalText ( dst [ ] byte , prefix string , text [ ] byte ) error {
if len ( text ) < len ( prefix ) || string ( text [ : len ( prefix ) ] ) != prefix {
return fmt . Errorf ( "UnmarshalText: missing %q prefix" , prefix )
2020-02-05 22:16:58 +00:00
}
2020-06-19 02:32:55 +00:00
pub , err := key . NewPublicFromHexMem ( mem . B ( text [ len ( prefix ) : ] ) )
2020-02-05 22:16:58 +00:00
if err != nil {
2020-06-19 02:32:55 +00:00
return fmt . Errorf ( "UnmarshalText: after %q: %v" , prefix , err )
2020-02-05 22:16:58 +00:00
}
2020-06-19 02:32:55 +00:00
copy ( dst [ : ] , pub [ : ] )
2020-02-05 22:16:58 +00:00
return nil
}
2020-06-19 02:32:55 +00:00
func ( k NodeKey ) ShortString ( ) string { return ( key . Public ( k ) ) . ShortString ( ) }
2020-02-05 22:16:58 +00:00
2020-06-19 02:32:55 +00:00
func ( k NodeKey ) String ( ) string { return fmt . Sprintf ( "nodekey:%x" , k [ : ] ) }
func ( k NodeKey ) MarshalText ( ) ( [ ] byte , error ) { return keyMarshalText ( "nodekey:" , k ) , nil }
func ( k * NodeKey ) UnmarshalText ( text [ ] byte ) error { return keyUnmarshalText ( k [ : ] , "nodekey:" , text ) }
2020-02-05 22:16:58 +00:00
2020-06-19 02:32:55 +00:00
// IsZero reports whether k is the zero value.
func ( k NodeKey ) IsZero ( ) bool { return k == NodeKey { } }
2020-02-05 22:16:58 +00:00
2020-09-21 19:19:24 +00:00
// IsZero reports whether k is the zero value.
func ( k MachineKey ) IsZero ( ) bool { return k == MachineKey { } }
2020-06-19 02:32:55 +00:00
func ( k DiscoKey ) String ( ) string { return fmt . Sprintf ( "discokey:%x" , k [ : ] ) }
func ( k DiscoKey ) MarshalText ( ) ( [ ] byte , error ) { return keyMarshalText ( "discokey:" , k ) , nil }
func ( k * DiscoKey ) UnmarshalText ( text [ ] byte ) error { return keyUnmarshalText ( k [ : ] , "discokey:" , text ) }
2020-07-02 17:48:13 +00:00
func ( k DiscoKey ) ShortString ( ) string { return fmt . Sprintf ( "d:%x" , k [ : 8 ] ) }
2020-02-05 22:16:58 +00:00
2020-06-19 02:32:55 +00:00
// IsZero reports whether k is the zero value.
func ( k DiscoKey ) IsZero ( ) bool { return k == DiscoKey { } }
2020-02-05 22:16:58 +00:00
func ( id ID ) String ( ) string { return fmt . Sprintf ( "id:%x" , int64 ( id ) ) }
func ( id UserID ) String ( ) string { return fmt . Sprintf ( "userid:%x" , int64 ( id ) ) }
func ( id LoginID ) String ( ) string { return fmt . Sprintf ( "loginid:%x" , int64 ( id ) ) }
func ( id NodeID ) String ( ) string { return fmt . Sprintf ( "nodeid:%x" , int64 ( id ) ) }
func ( id GroupID ) String ( ) string { return fmt . Sprintf ( "groupid:%x" , int64 ( id ) ) }
func ( id RoleID ) String ( ) string { return fmt . Sprintf ( "roleid:%x" , int64 ( id ) ) }
func ( id CapabilityID ) String ( ) string { return fmt . Sprintf ( "capid:%x" , int64 ( id ) ) }
2020-02-16 06:23:58 +00:00
// Equal reports whether n and n2 are equal.
2020-02-05 22:16:58 +00:00
func ( n * Node ) Equal ( n2 * Node ) bool {
2020-02-16 06:23:58 +00:00
if n == nil && n2 == nil {
return true
2020-02-05 22:16:58 +00:00
}
2020-02-16 06:23:58 +00:00
return n != nil && n2 != nil &&
n . ID == n2 . ID &&
n . Name == n2 . Name &&
n . User == n2 . User &&
n . Key == n2 . Key &&
n . KeyExpiry . Equal ( n2 . KeyExpiry ) &&
n . Machine == n2 . Machine &&
2020-06-19 02:32:55 +00:00
n . DiscoKey == n2 . DiscoKey &&
2020-07-23 17:41:54 +00:00
eqCIDRs ( n . Addresses , n2 . Addresses ) &&
eqCIDRs ( n . AllowedIPs , n2 . AllowedIPs ) &&
eqStrings ( n . Endpoints , n2 . Endpoints ) &&
2020-08-11 02:45:20 +00:00
n . DERP == n2 . DERP &&
2020-07-23 17:41:54 +00:00
n . Hostinfo . Equal ( & n2 . Hostinfo ) &&
2020-02-16 06:23:58 +00:00
n . Created . Equal ( n2 . Created ) &&
2020-07-23 17:41:54 +00:00
eqTimePtr ( n . LastSeen , n2 . LastSeen ) &&
2020-02-16 06:23:58 +00:00
n . MachineAuthorized == n2 . MachineAuthorized
2020-02-05 22:16:58 +00:00
}
2020-07-23 17:41:54 +00:00
func eqStrings ( a , b [ ] string ) bool {
if len ( a ) != len ( b ) || ( ( a == nil ) != ( b == nil ) ) {
return false
}
for i , v := range a {
if v != b [ i ] {
return false
}
}
return true
}
func eqCIDRs ( a , b [ ] wgcfg . CIDR ) bool {
if len ( a ) != len ( b ) || ( ( a == nil ) != ( b == nil ) ) {
return false
}
for i , v := range a {
if v != b [ i ] {
return false
}
}
return true
}
func eqTimePtr ( a , b * time . Time ) bool {
return ( ( a == nil ) == ( b == nil ) ) && ( a == nil || a . Equal ( * b ) )
}