cmd/tailscale: improve exit node menu for location based exit nodes

This change provides minor improvements to the exit node menu when there
are location based exit nodes present. It will ensure that non location
based exit nodes are displayed at the top of the list, followed by a
the best node for a country/city combination, and followed by all
location based exit nodes.

Updates tailscale/tailscale#9421

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
catzkorn/menu
Charlotte Brandhorst-Satzkorn 3 months ago
parent 813ca8adea
commit e9687e60bf

@ -5,6 +5,7 @@
package main
import (
"cmp"
"context"
"crypto/rand"
"crypto/sha1"
@ -31,6 +32,7 @@ import (
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"golang.org/x/exp/maps"
"inet.af/netaddr"
"github.com/tailscale/tailscale-android/jni"
@ -135,9 +137,11 @@ const (
)
type Peer struct {
Label string
Online bool
ID tailcfg.StableNodeID
Label string
Online bool
ID tailcfg.StableNodeID
Location *tailcfg.Location
PreferredExitNode bool
}
type BackendState struct {
@ -675,10 +679,19 @@ func (s *BackendState) updateExitNodes() {
myExit := p.StableID() == exitID
hasMyExit = hasMyExit || myExit
exit := Peer{
Label: p.DisplayName(true),
Online: canRoute,
ID: p.StableID(),
Label: p.DisplayName(true),
Online: canRoute,
ID: p.StableID(),
Location: p.Hostinfo().Location(),
}
if exit.Location != nil {
// We want to shorten what the users sees here,
// so override the display name with the computed
// name.
exit.Label = p.ComputedName()
}
if myExit {
s.Exit = exit
if canRoute {
@ -689,9 +702,80 @@ func (s *BackendState) updateExitNodes() {
s.Exits = append(s.Exits, exit)
}
}
locationBasedExitPeersMap := make(map[string]Peer)
var nonLocationBasedExitPeers []Peer
var allLocationBasedExitPeers []Peer
for _, peer := range s.Exits {
if peer.Location != nil {
countryCityLocation, ok := locationBasedExitPeersMap[fmt.Sprintf("%s (%s)", peer.Location.Country, peer.Location.City)]
if !ok {
// If we have not seen the country/city combination, add it to the
// map.
locationBasedExitPeersMap[fmt.Sprintf("%s (%s)", peer.Location.Country, peer.Location.City)] = peer
continue
}
if countryCityLocation.Location.Priority < peer.Location.Priority {
// If the priority for the location based exit node is higher than
// the current option, replace it.
locationBasedExitPeersMap[fmt.Sprintf("%s (%s)", peer.Location.Country, peer.Location.City)] = peer
}
allLocationBasedExitPeers = append(allLocationBasedExitPeers, peer)
continue
}
nonLocationBasedExitPeers = append(nonLocationBasedExitPeers, peer)
}
// We want to order the exit nodes to be display to the user in
// the order of non location based exit nodes, the best exit
// node per location, and then all of the location based exit nodes.
// Non location based exit nodes.
s.Exits = nonLocationBasedExitPeers
sort.Slice(s.Exits, func(i, j int) bool {
return s.Exits[i].Label < s.Exits[j].Label
})
// Best location based exit nodes
locationBasedExitPeersMapValues := maps.Values(locationBasedExitPeersMap)
if len(locationBasedExitPeersMapValues) > 0 {
var preferredLocationBasedExitPeers []Peer
for _, peer := range locationBasedExitPeersMapValues {
peerCopy := peer
peerCopy.PreferredExitNode = true
peerCopy.Label = fmt.Sprintf("%s - %s (%s)", peerCopy.Location.Country, peerCopy.Location.City, peerCopy.Label)
preferredLocationBasedExitPeers = append(preferredLocationBasedExitPeers, peerCopy)
}
sort.Slice(preferredLocationBasedExitPeers, func(i, j int) bool {
// Sort the order by country, and cities.
res := cmp.Compare(preferredLocationBasedExitPeers[i].Location.Country, preferredLocationBasedExitPeers[j].Location.Country)
switch res {
case -1:
return true
case 1:
return false
default:
// If the two peers have the same country, sort by city.
return preferredLocationBasedExitPeers[i].Location.City < preferredLocationBasedExitPeers[j].Location.City
}
})
s.Exits = append(s.Exits, preferredLocationBasedExitPeers...)
}
if len(allLocationBasedExitPeers) > 0 {
// All location based exit nodes at the end.
sort.Slice(allLocationBasedExitPeers, func(i, j int) bool {
// Sort the order by label
return allLocationBasedExitPeers[i].Label < allLocationBasedExitPeers[j].Label
})
s.Exits = append(s.Exits, allLocationBasedExitPeers...)
}
if !hasMyExit {
// Insert node missing from netmap.
s.Exit = Peer{Label: "Unknown device", ID: exitID}

@ -1054,7 +1054,7 @@ func (ui *UI) layoutExitNodeDialog(gtx layout.Context, sysIns system.Insets, exi
Bottom: unit.Dp(16),
}.Layout(gtx, btn.Layout)
}
node := Peer{Label: "None", Online: true}
node := Peer{Label: "None", Online: true, Location: nil}
if idx >= 2 {
node = exits[idx-2]
}

Loading…
Cancel
Save