diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index fe7e0ad07..5e74c07ed 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -161,6 +161,11 @@ type Conn struct { // whether a DERP channel read should be done. derpRecvCountLast int64 // owned by ReceiveIPv4 + // ippEndpoint4 and ippEndpoint6 are owned by ReceiveIPv4 and + // ReceiveIPv6, respectively, to cache an IPPort->endpoint for + // hot flows. + ippEndpoint4, ippEndpoint6 ippEndpointCache + // ============================================================ mu sync.Mutex // guards all following fields; see userspaceEngine lock ordering rules muCond *sync.Cond @@ -1483,7 +1488,7 @@ func (c *Conn) ReceiveIPv6(b []byte) (int, conn.Endpoint, error) { if err != nil { return 0, nil, err } - if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr)); ok { + if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr), &c.ippEndpoint6); ok { return n, ep, nil } } @@ -1520,14 +1525,14 @@ func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) { } return 0, nil, err } - if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr)); ok { + if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr), &c.ippEndpoint4); ok { return n, ep, nil } } } // receiveIP is the shared bits of ReceiveIPv4 and ReceiveIPv6. -func (c *Conn) receiveIP(b []byte, ua *net.UDPAddr) (ep conn.Endpoint, ok bool) { +func (c *Conn) receiveIP(b []byte, ua *net.UDPAddr, cache *ippEndpointCache) (ep conn.Endpoint, ok bool) { ipp, ok := netaddr.FromStdAddr(ua.IP, ua.Port, ua.Zone) if !ok { return @@ -1539,9 +1544,18 @@ func (c *Conn) receiveIP(b []byte, ua *net.UDPAddr) (ep conn.Endpoint, ok bool) if c.handleDiscoMessage(b, ipp) { return } - ep = c.findEndpoint(ipp, ua, b) - if ep == nil { - return + if cache.ipp == ipp && cache.de != nil && cache.gen == cache.de.numStopAndReset() { + ep = cache.de + } else { + ep = c.findEndpoint(ipp, ua, b) + if ep == nil { + return + } + if de, ok := ep.(*discoEndpoint); ok { + cache.ipp = ipp + cache.de = de + cache.gen = de.numStopAndReset() + } } c.noteRecvActivityFromEndpoint(ep) return ep, true @@ -2781,7 +2795,8 @@ func udpAddrDebugString(ua net.UDPAddr) string { // advertise a DiscoKey and participate in active discovery. type discoEndpoint struct { // atomically accessed; declared first for alignment reasons - lastRecvUnixAtomic int64 + lastRecvUnixAtomic int64 + numStopAndResetAtomic int64 // These fields are initialized once and never modified. c *Conn @@ -3420,6 +3435,7 @@ func (de *discoEndpoint) populatePeerStatus(ps *ipnstate.PeerStatus) { // It's called when a discovery endpoint is no longer present in the NetworkMap, // or when magicsock is transition from running to stopped state (via SetPrivateKey(zero)) func (de *discoEndpoint) stopAndReset() { + atomic.AddInt64(&de.numStopAndResetAtomic, 1) de.mu.Lock() defer de.mu.Unlock() @@ -3447,6 +3463,10 @@ func (de *discoEndpoint) stopAndReset() { de.pendingCLIPings = nil } +func (de *discoEndpoint) numStopAndReset() int64 { + return atomic.LoadInt64(&de.numStopAndResetAtomic) +} + // derpStr replaces DERP IPs in s with "derp-". func derpStr(s string) string { return strings.ReplaceAll(s, "127.3.3.40:", "derp-") } @@ -3468,3 +3488,11 @@ func (c *Conn) WhoIs(ip netaddr.IP) (n *tailcfg.Node, u tailcfg.UserProfile, ok } return nil, u, false } + +// ippEndpointCache is a mutex-free single-element cache, mapping from +// a single netaddr.IPPort to a single endpoint. +type ippEndpointCache struct { + ipp netaddr.IPPort + gen int64 + de *discoEndpoint +}