This commit is contained in:
Claire Wang 2024-01-31 10:38:15 -05:00
parent 1ae16546af
commit d32aaffca6
5 changed files with 77 additions and 8 deletions

View File

@ -1417,6 +1417,14 @@ func (lc *LocalClient) CheckUpdate(ctx context.Context) (*tailcfg.ClientVersion,
return &cv, nil return &cv, nil
} }
func (lc *LocalClient) SuggestExitNode(ctx context.Context) error {
body, err := lc.send(ctx, "POST", "/localapi/v0/suggest-exit-node", 200, nil)
if err != nil {
return fmt.Errorf("error %w: %s", err, body)
}
return nil
}
// IPNBusWatcher is an active subscription (watch) of the local tailscaled IPN bus. // IPNBusWatcher is an active subscription (watch) of the local tailscaled IPN bus.
// It's returned by LocalClient.WatchIPNBus. // It's returned by LocalClient.WatchIPNBus.
// //

View File

@ -64,7 +64,7 @@ func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
setf.StringVar(&setArgs.profileName, "nickname", "", "nickname for the current account") setf.StringVar(&setArgs.profileName, "nickname", "", "nickname for the current account")
setf.BoolVar(&setArgs.acceptRoutes, "accept-routes", false, "accept routes advertised by other Tailscale nodes") setf.BoolVar(&setArgs.acceptRoutes, "accept-routes", false, "accept routes advertised by other Tailscale nodes")
setf.BoolVar(&setArgs.acceptDNS, "accept-dns", false, "accept DNS configuration from the admin panel") setf.BoolVar(&setArgs.acceptDNS, "accept-dns", false, "accept DNS configuration from the admin panel")
setf.StringVar(&setArgs.exitNodeIP, "exit-node", "", "Tailscale exit node (IP or base name) for internet traffic, or empty string to not use an exit node") setf.StringVar(&setArgs.exitNodeIP, "exit-node", "", "Tailscale exit node (IP or base name) for internet traffic, or empty string to not use an exit node. Input suggest as the string for Tailscale to pick the best exit node.")
setf.BoolVar(&setArgs.exitNodeAllowLANAccess, "exit-node-allow-lan-access", false, "Allow direct access to the local network when routing traffic via an exit node") setf.BoolVar(&setArgs.exitNodeAllowLANAccess, "exit-node-allow-lan-access", false, "Allow direct access to the local network when routing traffic via an exit node")
setf.BoolVar(&setArgs.shieldsUp, "shields-up", false, "don't allow incoming connections") setf.BoolVar(&setArgs.shieldsUp, "shields-up", false, "don't allow incoming connections")
setf.BoolVar(&setArgs.runSSH, "ssh", false, "run an SSH server, permitting access per tailnet admin's declared policy") setf.BoolVar(&setArgs.runSSH, "ssh", false, "run an SSH server, permitting access per tailnet admin's declared policy")
@ -128,12 +128,19 @@ func runSet(ctx context.Context, args []string) (retErr error) {
} }
if setArgs.exitNodeIP != "" { if setArgs.exitNodeIP != "" {
if err := maskedPrefs.Prefs.SetExitNodeIP(setArgs.exitNodeIP, st); err != nil { if setArgs.exitNodeIP == "suggest" {
var e ipn.ExitNodeLocalIPError err := localClient.SuggestExitNode(ctx)
if errors.As(err, &e) { if err != nil {
return fmt.Errorf("%w; did you mean --advertise-exit-node?", err) return err
}
} else {
if err := maskedPrefs.Prefs.SetExitNodeIP(setArgs.exitNodeIP, st); err != nil {
var e ipn.ExitNodeLocalIPError
if errors.As(err, &e) {
return fmt.Errorf("%w; did you mean --advertise-exit-node?", err)
}
return err
} }
return err
} }
} }

View File

@ -13,6 +13,7 @@
"io" "io"
"log" "log"
"maps" "maps"
"math"
"net" "net"
"net/http" "net/http"
"net/netip" "net/netip"
@ -107,6 +108,8 @@
var controlDebugFlags = getControlDebugFlags() var controlDebugFlags = getControlDebugFlags()
const derpPrefix = "127.3.3.40:"
func getControlDebugFlags() []string { func getControlDebugFlags() []string {
if e := envknob.String("TS_DEBUG_CONTROL_FLAGS"); e != "" { if e := envknob.String("TS_DEBUG_CONTROL_FLAGS"); e != "" {
return strings.Split(e, ",") return strings.Split(e, ",")
@ -306,7 +309,8 @@ type LocalBackend struct {
clock tstime.Clock clock tstime.Clock
// Last ClientVersion received in MapResponse, guarded by mu. // Last ClientVersion received in MapResponse, guarded by mu.
lastClientVersion *tailcfg.ClientVersion lastClientVersion *tailcfg.ClientVersion
suggestedExitNodeMap map[tailcfg.NodeID]tailcfg.DERPRegion
} }
type updateStatus struct { type updateStatus struct {
@ -1431,7 +1435,6 @@ func setExitNodeID(prefs *ipn.Prefs, nm *netmap.NetworkMap) (prefsChanged bool)
return oldExitNodeID != prefs.ExitNodeID return oldExitNodeID != prefs.ExitNodeID
} }
} }
return prefsChanged return prefsChanged
} }
@ -5909,3 +5912,36 @@ func mayDeref[T any](p *T) (v T) {
} }
return *p return *p
} }
func (b *LocalBackend) SuggestExitNode() error {
//b.mu.Lock()
netMap := b.netMap
peers := netMap.Peers
lastReport := b.MagicConn().GetLastNetcheckReport()
var fastestRegionLatency = time.Duration(math.MaxInt64)
var preferredExitNodeID tailcfg.StableNodeID
for _, peer := range peers {
if tsaddr.ContainsExitRoutes(peer.AllowedIPs()) && strings.HasPrefix(peer.DERP(), derpPrefix) {
ipp, _ := netip.ParseAddrPort(peer.DERP())
regionID := int(ipp.Port())
if lastReport.RegionLatency[regionID] < fastestRegionLatency {
fastestRegionLatency = lastReport.RegionLatency[regionID]
preferredExitNodeID = peer.StableID()
b.logf("fastest region latency %v preferred exit node id %v", lastReport.RegionLatency[regionID], peer.StableID())
}
}
}
// b.mu.Unlock()
prefs := b.Prefs().AsStruct()
prefs.ExitNodeID = preferredExitNodeID
_, err := b.EditPrefs(&ipn.MaskedPrefs{
Prefs: ipn.Prefs{
ExitNodeID: preferredExitNodeID,
},
ExitNodeIDSet: true,
})
if err != nil {
return fmt.Errorf("Failed to suggest exit node %v, err %v", preferredExitNodeID, err)
}
return nil
}

View File

@ -129,6 +129,7 @@
"update/check": (*Handler).serveUpdateCheck, "update/check": (*Handler).serveUpdateCheck,
"update/install": (*Handler).serveUpdateInstall, "update/install": (*Handler).serveUpdateInstall,
"update/progress": (*Handler).serveUpdateProgress, "update/progress": (*Handler).serveUpdateProgress,
"suggest-exit-node": (*Handler).serveSuggestExitNode,
} }
var ( var (
@ -2504,3 +2505,19 @@ func (h *Handler) serveUpdateProgress(w http.ResponseWriter, r *http.Request) {
// User-visible LocalAPI endpoints. // User-visible LocalAPI endpoints.
metricFilePutCalls = clientmetric.NewCounter("localapi_file_put") metricFilePutCalls = clientmetric.NewCounter("localapi_file_put")
) )
func (h *Handler) serveSuggestExitNode(w http.ResponseWriter, r *http.Request) {
if !h.PermitWrite {
http.Error(w, "access denied", http.StatusForbidden)
return
}
if r.Method != "POST" {
http.Error(w, "want POST", http.StatusBadRequest)
return
}
err := h.b.SuggestExitNode()
if err != nil {
writeErrorJSON(w, err)
return
}
}

View File

@ -799,6 +799,7 @@ func (p *Prefs) SetExitNodeIP(s string, st *ipnstate.Status) error {
if err == nil { if err == nil {
p.ExitNodeIP = ip p.ExitNodeIP = ip
} }
return err return err
} }