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

@ -64,7 +64,6 @@ func (src *Node) Clone() *Node {
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
@ -80,6 +79,9 @@ func (src *Node) Clone() *Node {
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)
}
}
}