From 44ab0acbdbd8b79af74ea1f8187c4d782ce38635 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 15 Mar 2021 13:58:10 -0700 Subject: [PATCH] net/portmapper, wgengine/monitor: cache gateway IP info until link changes Cuts down allocs & CPU in steady state (on regular STUN probes) when network is unchanging. Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/depaware.txt | 2 +- cmd/tailscaled/depaware.txt | 2 +- net/portmapper/portmapper.go | 15 ++++++++++++--- wgengine/magicsock/magicsock.go | 8 ++++++++ wgengine/monitor/monitor.go | 29 ++++++++++++++++++++++++++--- wgengine/userspace.go | 1 + 6 files changed, 49 insertions(+), 8 deletions(-) diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 3d5e58726..2e722db5c 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -50,7 +50,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/types/wgkey from tailscale.com/types/netmap+ tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+ W tailscale.com/util/endian from tailscale.com/net/netns - tailscale.com/util/lineread from tailscale.com/net/interfaces + LW tailscale.com/util/lineread from tailscale.com/net/interfaces tailscale.com/version from tailscale.com/cmd/tailscale/cli+ tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli tailscale.com/wgengine/filter from tailscale.com/types/netmap diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 0b7583033..5abb3cccb 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -126,7 +126,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/types/wgkey from tailscale.com/control/controlclient+ tailscale.com/util/dnsname from tailscale.com/wgengine/tsdns+ LW tailscale.com/util/endian from tailscale.com/net/netns+ - tailscale.com/util/lineread from tailscale.com/control/controlclient+ + LW tailscale.com/util/lineread from tailscale.com/control/controlclient+ tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver tailscale.com/util/racebuild from tailscale.com/logpolicy tailscale.com/util/systemd from tailscale.com/control/controlclient+ diff --git a/net/portmapper/portmapper.go b/net/portmapper/portmapper.go index a865ce225..a0ec0795a 100644 --- a/net/portmapper/portmapper.go +++ b/net/portmapper/portmapper.go @@ -42,7 +42,8 @@ const trustServiceStillAvailableDuration = 10 * time.Minute // Client is a port mapping client. type Client struct { - logf logger.Logf + logf logger.Logf + ipAndGateway func() (gw, ip netaddr.IP, ok bool) mu sync.Mutex // guards following, and all fields thereof @@ -100,10 +101,18 @@ func (m *pmpMapping) release() { // NewClient returns a new portmapping client. func NewClient(logf logger.Logf) *Client { return &Client{ - logf: logf, + logf: logf, + ipAndGateway: interfaces.LikelyHomeRouterIP, } } +// SetGatewayLookupFunc set the func that returns the machine's default gateway IP, and +// the primary IP address for that gateway. It must be called before the client is used. +// If not called, interfaces.LikelyHomeRouterIP is used. +func (c *Client) SetGatewayLookupFunc(f func() (gw, myIP netaddr.IP, ok bool)) { + c.ipAndGateway = f +} + // NoteNetworkDown should be called when the network has transitioned to a down state. // It's too late to release port mappings at this point (the user might've just turned off // their wifi), but we can make sure we invalidate mappings for later when the network @@ -140,7 +149,7 @@ func (c *Client) SetLocalPort(localPort uint16) { } func (c *Client) gatewayAndSelfIP() (gw, myIP netaddr.IP, ok bool) { - gw, myIP, ok = interfaces.LikelyHomeRouterIP() + gw, myIP, ok = c.ipAndGateway() if !ok { gw = netaddr.IP{} myIP = netaddr.IP{} diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 945418702..5584e3324 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -55,6 +55,7 @@ import ( "tailscale.com/types/pad32" "tailscale.com/types/wgkey" "tailscale.com/version" + "tailscale.com/wgengine/monitor" "tailscale.com/wgengine/wgcfg" ) @@ -423,6 +424,10 @@ type Options struct { // enabled, only active discovery-aware nodes will be able to // communicate with Conn. DisableLegacyNetworking bool + + // LinkMonitor is the link monitor to use. + // With one, the portmapper won't be used. + LinkMonitor *monitor.Mon } func (o *Options) logf() logger.Logf { @@ -483,6 +488,9 @@ func NewConn(opts Options) (*Conn, error) { c.simulatedNetwork = opts.SimulatedNetwork c.disableLegacy = opts.DisableLegacyNetworking c.portMapper = portmapper.NewClient(logger.WithPrefix(c.logf, "portmapper: ")) + if opts.LinkMonitor != nil { + c.portMapper.SetGatewayLookupFunc(opts.LinkMonitor.GatewayAndSelfIP) + } if err := c.initialBind(); err != nil { return nil, err diff --git a/wgengine/monitor/monitor.go b/wgengine/monitor/monitor.go index 65c06ed1b..254df7cb6 100644 --- a/wgengine/monitor/monitor.go +++ b/wgengine/monitor/monitor.go @@ -13,6 +13,7 @@ import ( "sync" "time" + "inet.af/netaddr" "tailscale.com/net/interfaces" "tailscale.com/types/logger" ) @@ -51,9 +52,12 @@ type Mon struct { change chan struct{} stop chan struct{} - mu sync.Mutex // guards cbs - cbs map[*callbackHandle]ChangeFunc - ifState *interfaces.State + mu sync.Mutex // guards cbs + cbs map[*callbackHandle]ChangeFunc + ifState *interfaces.State + gwValid bool // whether gw and gwSelfIP are valid (cached)x + gw netaddr.IP + gwSelfIP netaddr.IP onceStart sync.Once started bool @@ -105,6 +109,24 @@ func (m *Mon) interfaceStateUncached() (*interfaces.State, error) { return s, err } +// GatewayAndSelfIP returns the current network's default gateway, and +// the machine's default IP for that gateway. +// +// It's the same as interfaces.LikelyHomeRouterIP, but it caches the +// result until the monitor detects a network change. +func (m *Mon) GatewayAndSelfIP() (gw, myIP netaddr.IP, ok bool) { + m.mu.Lock() + defer m.mu.Unlock() + if m.gwValid { + return m.gw, m.gwSelfIP, true + } + gw, myIP, ok = interfaces.LikelyHomeRouterIP() + if ok { + m.gw, m.gwSelfIP, m.gwValid = gw, myIP, true + } + return gw, myIP, ok +} + // RegisterChangeCallback adds callback to the set of parties to be // notified (in their own goroutine) when the network state changes. // To remove this callback, call unregister (or close the monitor). @@ -213,6 +235,7 @@ func (m *Mon) debounce() { oldState := m.ifState changed := !curState.Equal(oldState) if changed { + m.gwValid = false m.ifState = curState if s1, s2 := oldState.String(), curState.String(); s1 == s2 { diff --git a/wgengine/userspace.go b/wgengine/userspace.go index af8f823a2..1baf7f040 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -270,6 +270,7 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_ DERPActiveFunc: e.RequestStatus, IdleFunc: e.tundev.IdleDuration, NoteRecvActivity: e.noteReceiveActivity, + LinkMonitor: e.linkMon, } var err error e.magicConn, err = magicsock.NewConn(magicsockOpts)