tailcfg: update node display name fields and methods (#1207)

Signed-off-by: Sonia Appasamy <sonia@tailscale.com>

Consolidates the node display name logic from each of the clients into
tailcfg.Node. UI clients can use these names directly, rather than computing
them independently.
This commit is contained in:
Sonia Appasamy 2021-01-27 11:50:31 -05:00 committed by GitHub
parent 35e10c78fc
commit 4dab0c1702
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 121 additions and 75 deletions

View File

@ -770,12 +770,12 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw
c.mu.Unlock() c.mu.Unlock()
nm := &NetworkMap{ nm := &NetworkMap{
SelfNode: resp.Node,
NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()), NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()),
PrivateKey: persist.PrivateNodeKey, PrivateKey: persist.PrivateNodeKey,
MachineKey: machinePubKey, MachineKey: machinePubKey,
Expiry: resp.Node.KeyExpiry, Expiry: resp.Node.KeyExpiry,
Name: resp.Node.Name, Name: resp.Node.Name,
DisplayName: resp.Node.DisplayName,
Addresses: resp.Node.Addresses, Addresses: resp.Node.Addresses,
Peers: resp.Peers, Peers: resp.Peers,
LocalPort: localPort, LocalPort: localPort,
@ -799,10 +799,10 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw
} }
} }
addUserProfile(nm.User) addUserProfile(nm.User)
magicDNSSuffix := nm.MagicDNSSuffix()
nm.SelfNode.InitDisplayNames(magicDNSSuffix)
for _, peer := range resp.Peers { for _, peer := range resp.Peers {
if peer.DisplayName == "" { peer.InitDisplayNames(magicDNSSuffix)
peer.DisplayName = peer.DefaultDisplayName()
}
if !peer.Sharer.IsZero() { if !peer.Sharer.IsZero() {
if c.keepSharerAndUserSplit { if c.keepSharerAndUserSplit {
addUserProfile(peer.Sharer) addUserProfile(peer.Sharer)
@ -812,9 +812,6 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw
} }
addUserProfile(peer.User) addUserProfile(peer.User)
} }
if resp.Node.DisplayName == "" {
nm.DisplayName = resp.Node.DefaultDisplayName()
}
if resp.Node.MachineAuthorized { if resp.Node.MachineAuthorized {
nm.MachineStatus = tailcfg.MachineAuthorized nm.MachineStatus = tailcfg.MachineAuthorized
} else { } else {

View File

@ -24,13 +24,12 @@
type NetworkMap struct { type NetworkMap struct {
// Core networking // Core networking
SelfNode *tailcfg.Node
NodeKey tailcfg.NodeKey NodeKey tailcfg.NodeKey
PrivateKey wgkey.Private PrivateKey wgkey.Private
Expiry time.Time Expiry time.Time
// Name is the DNS name assigned to this node. // Name is the DNS name assigned to this node.
Name string Name string
// DisplayName is the title to show for the node in client UIs.
DisplayName string
Addresses []netaddr.IPPrefix Addresses []netaddr.IPPrefix
LocalPort uint16 // used for debugging LocalPort uint16 // used for debugging
MachineStatus tailcfg.MachineStatus MachineStatus tailcfg.MachineStatus

View File

@ -161,12 +161,6 @@ type Node struct {
StableID StableNodeID StableID StableNodeID
Name string // DNS Name string // DNS
// DisplayName is the title to show for the node in client
// UIs. This field is assigned by default in controlclient,
// but can be overriden by providing this field non-empty
// in a MapResponse.
DisplayName string `json:",omitempty"`
// User is the user who created the node. If ACL tags are in // User is the user who created the node. If ACL tags are in
// use for the node then it doesn't reflect the ACL identity // use for the node then it doesn't reflect the ACL identity
// that the node is running as. // that the node is running as.
@ -190,21 +184,98 @@ type Node struct {
KeepAlive bool `json:",omitempty"` // open and keep open a connection to this peer KeepAlive bool `json:",omitempty"` // open and keep open a connection to this peer
MachineAuthorized bool `json:",omitempty"` // TODO(crawshaw): replace with MachineStatus MachineAuthorized bool `json:",omitempty"` // TODO(crawshaw): replace with MachineStatus
// The following three computed fields hold the various names that can
// be used for this node in UIs. They are populated from controlclient
// (not from control) by calling node.InitDisplayNames. These can be
// used directly or accessed via node.DisplayName or node.DisplayNames.
ComputedName string `json:",omitempty"` // MagicDNS base name (for normal non-shared-in nodes), FQDN (without trailing dot, for shared-in nodes), or Hostname (if no MagicDNS)
computedHostIfDifferent string // hostname, if different than ComputedName, otherwise empty
ComputedNameWithHost string `json:",omitempty"` // either "ComputedName" or "ComputedName (computedHostIfDifferent)", if computedHostIfDifferent is set
} }
// DefaultDisplayName returns a value suitable // DisplayName returns the user-facing name for a node which should
// for using as the default value for n.DisplayName. // be shown in client UIs.
func (n *Node) DefaultDisplayName() string { //
if n.Name != "" { // Parameter forOwner specifies whether the name is requested by
// Use the Magic DNS prefix as the default display name. // the owner of the node. When forOwner is false, the hostname is
return dnsname.ToBaseName(n.Name) // never included in the return value.
//
// Return value is either either "Name" or "Name (Hostname)", where
// Name is the node's MagicDNS base name (for normal non-shared-in
// nodes), FQDN (without trailing dot, for shared-in nodes), or
// Hostname (if no MagicDNS). Hostname is only included in the
// return value if it varies from Name and forOwner is provided true.
//
// DisplayName is only valid if InitDisplayNames has been called.
func (n *Node) DisplayName(forOwner bool) string {
if forOwner {
return n.ComputedNameWithHost
} }
if n.Hostinfo.Hostname != "" { return n.ComputedName
// When no Magic DNS name is present, use the hostname. }
return n.Hostinfo.Hostname
// DisplayName returns the decomposed user-facing name for a node.
//
// Parameter forOwner specifies whether the name is requested by
// the owner of the node. When forOwner is false, hostIfDifferent
// is always returned empty.
//
// Return value name is the node's primary name, populated with the
// node's MagicDNS base name (for normal non-shared-in nodes), FQDN
// (without trailing dot, for shared-in nodes), or Hostname (if no
// MagicDNS).
//
// Return value hostIfDifferent, when non-empty, is the node's
// hostname. hostIfDifferent is only populated when the hostname
// varies from name and forOwner is provided as true.
//
// DisplayNames is only valid if InitDisplayNames has been called.
func (n *Node) DisplayNames(forOwner bool) (name, hostIfDifferent string) {
if forOwner {
return n.ComputedName, n.computedHostIfDifferent
} }
// When we've exhausted all other name options, use the node's ID. return n.ComputedName, ""
return n.ID.String() }
// InitDisplayNames computes and populates n's display name
// fields: n.ComputedName, n.computedHostIfDifferent, and
// n.ComputedNameWithHost.
func (n *Node) InitDisplayNames(networkMagicDNSSuffix string) {
dnsName := n.Name
if dnsName != "" {
dnsName = strings.TrimRight(dnsName, ".")
if i := strings.Index(dnsName, "."); i != -1 && dnsname.HasSuffix(dnsName, networkMagicDNSSuffix) {
dnsName = dnsName[:i]
}
}
name := dnsName
hostIfDifferent := n.Hostinfo.Hostname
if strings.EqualFold(name, hostIfDifferent) {
hostIfDifferent = ""
}
if name == "" {
if hostIfDifferent != "" {
name = hostIfDifferent
hostIfDifferent = ""
} else {
name = n.Key.String()
}
}
var nameWithHost string
if hostIfDifferent != "" {
nameWithHost = fmt.Sprintf("%s (%s)", name, hostIfDifferent)
} else {
nameWithHost = name
}
n.ComputedName = name
n.computedHostIfDifferent = hostIfDifferent
n.ComputedNameWithHost = nameWithHost
} }
type MachineStatus int type MachineStatus int
@ -818,7 +889,6 @@ func (n *Node) Equal(n2 *Node) bool {
n.ID == n2.ID && n.ID == n2.ID &&
n.StableID == n2.StableID && n.StableID == n2.StableID &&
n.Name == n2.Name && n.Name == n2.Name &&
n.DisplayName == n2.DisplayName &&
n.User == n2.User && n.User == n2.User &&
n.Sharer == n2.Sharer && n.Sharer == n2.Sharer &&
n.Key == n2.Key && n.Key == n2.Key &&
@ -832,7 +902,10 @@ func (n *Node) Equal(n2 *Node) bool {
n.Hostinfo.Equal(&n2.Hostinfo) && n.Hostinfo.Equal(&n2.Hostinfo) &&
n.Created.Equal(n2.Created) && n.Created.Equal(n2.Created) &&
eqTimePtr(n.LastSeen, n2.LastSeen) && eqTimePtr(n.LastSeen, n2.LastSeen) &&
n.MachineAuthorized == n2.MachineAuthorized n.MachineAuthorized == n2.MachineAuthorized &&
n.ComputedName == n2.ComputedName &&
n.computedHostIfDifferent == n2.computedHostIfDifferent &&
n.ComputedNameWithHost == n2.ComputedNameWithHost
} }
func eqStrings(a, b []string) bool { func eqStrings(a, b []string) bool {

View File

@ -61,25 +61,27 @@ func (src *Node) Clone() *Node {
// A compilation failure here means this code must be regenerated, with command: // A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig,RegisterResponse // tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig,RegisterResponse
var _NodeNeedsRegeneration = Node(struct { var _NodeNeedsRegeneration = Node(struct {
ID NodeID ID NodeID
StableID StableNodeID StableID StableNodeID
Name string Name string
DisplayName string User UserID
User UserID Sharer UserID
Sharer UserID Key NodeKey
Key NodeKey KeyExpiry time.Time
KeyExpiry time.Time Machine MachineKey
Machine MachineKey DiscoKey DiscoKey
DiscoKey DiscoKey Addresses []netaddr.IPPrefix
Addresses []netaddr.IPPrefix AllowedIPs []netaddr.IPPrefix
AllowedIPs []netaddr.IPPrefix Endpoints []string
Endpoints []string DERP string
DERP string Hostinfo Hostinfo
Hostinfo Hostinfo Created time.Time
Created time.Time LastSeen *time.Time
LastSeen *time.Time KeepAlive bool
KeepAlive bool MachineAuthorized bool
MachineAuthorized bool ComputedName string
computedHostIfDifferent string
ComputedNameWithHost string
}{}) }{})
// Clone makes a deep copy of Hostinfo. // Clone makes a deep copy of Hostinfo.

View File

@ -189,10 +189,11 @@ func TestHostinfoEqual(t *testing.T) {
func TestNodeEqual(t *testing.T) { func TestNodeEqual(t *testing.T) {
nodeHandles := []string{ nodeHandles := []string{
"ID", "StableID", "Name", "DisplayName", "User", "Sharer", "ID", "StableID", "Name", "User", "Sharer",
"Key", "KeyExpiry", "Machine", "DiscoKey", "Key", "KeyExpiry", "Machine", "DiscoKey",
"Addresses", "AllowedIPs", "Endpoints", "DERP", "Hostinfo", "Addresses", "AllowedIPs", "Endpoints", "DERP", "Hostinfo",
"Created", "LastSeen", "KeepAlive", "MachineAuthorized", "Created", "LastSeen", "KeepAlive", "MachineAuthorized",
"ComputedName", "computedHostIfDifferent", "ComputedNameWithHost",
} }
if have := fieldsOf(reflect.TypeOf(Node{})); !reflect.DeepEqual(have, nodeHandles) { if have := fieldsOf(reflect.TypeOf(Node{})); !reflect.DeepEqual(have, nodeHandles) {
t.Errorf("Node.Equal check might be out of sync\nfields: %q\nhandled: %q\n", t.Errorf("Node.Equal check might be out of sync\nfields: %q\nhandled: %q\n",

View File

@ -17,11 +17,3 @@ func HasSuffix(name, suffix string) bool {
nameBase := strings.TrimSuffix(name, suffix) nameBase := strings.TrimSuffix(name, suffix)
return len(nameBase) < len(name) && strings.HasSuffix(nameBase, ".") return len(nameBase) < len(name) && strings.HasSuffix(nameBase, ".")
} }
// ToBaseName removes the domain ending from a DNS name of a node.
func ToBaseName(name string) string {
if i := strings.Index(name, "."); i != -1 {
return name[:i]
}
return name
}

View File

@ -26,21 +26,3 @@ func TestHasSuffix(t *testing.T) {
} }
} }
} }
func TestToBaseName(t *testing.T) {
tests := []struct {
name string
want string
}{
{"foo", "foo"},
{"foo.com", "foo"},
{"foo.example.com.beta.tailscale.net", "foo"},
{"computer-a.test.gmail.com.beta.tailscale.net", "computer-a"},
}
for _, tt := range tests {
got := ToBaseName(tt.name)
if got != tt.want {
t.Errorf("ToBaseName(%q) = %q; want %q", tt.name, got, tt.want)
}
}
}