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
}
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.
// 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.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.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.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")
@ -128,6 +128,12 @@ func runSet(ctx context.Context, args []string) (retErr error) {
}
if setArgs.exitNodeIP != "" {
if setArgs.exitNodeIP == "suggest" {
err := localClient.SuggestExitNode(ctx)
if err != nil {
return err
}
} else {
if err := maskedPrefs.Prefs.SetExitNodeIP(setArgs.exitNodeIP, st); err != nil {
var e ipn.ExitNodeLocalIPError
if errors.As(err, &e) {
@ -136,6 +142,7 @@ func runSet(ctx context.Context, args []string) (retErr error) {
return err
}
}
}
warnOnAdvertiseRouts(ctx, &maskedPrefs.Prefs)
var advertiseExitNodeSet, advertiseRoutesSet bool

View File

@ -13,6 +13,7 @@
"io"
"log"
"maps"
"math"
"net"
"net/http"
"net/netip"
@ -107,6 +108,8 @@
var controlDebugFlags = getControlDebugFlags()
const derpPrefix = "127.3.3.40:"
func getControlDebugFlags() []string {
if e := envknob.String("TS_DEBUG_CONTROL_FLAGS"); e != "" {
return strings.Split(e, ",")
@ -307,6 +310,7 @@ type LocalBackend struct {
// Last ClientVersion received in MapResponse, guarded by mu.
lastClientVersion *tailcfg.ClientVersion
suggestedExitNodeMap map[tailcfg.NodeID]tailcfg.DERPRegion
}
type updateStatus struct {
@ -1431,7 +1435,6 @@ func setExitNodeID(prefs *ipn.Prefs, nm *netmap.NetworkMap) (prefsChanged bool)
return oldExitNodeID != prefs.ExitNodeID
}
}
return prefsChanged
}
@ -5909,3 +5912,36 @@ func mayDeref[T any](p *T) (v T) {
}
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/install": (*Handler).serveUpdateInstall,
"update/progress": (*Handler).serveUpdateProgress,
"suggest-exit-node": (*Handler).serveSuggestExitNode,
}
var (
@ -2504,3 +2505,19 @@ func (h *Handler) serveUpdateProgress(w http.ResponseWriter, r *http.Request) {
// User-visible LocalAPI endpoints.
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 {
p.ExitNodeIP = ip
}
return err
}