diff --git a/net/portmapper/portmapper.go b/net/portmapper/portmapper.go index 4b4241cd7..8c1f55b6e 100644 --- a/net/portmapper/portmapper.go +++ b/net/portmapper/portmapper.go @@ -805,33 +805,48 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) { continue } ip = ip.Unmap() + + handleUPnPResponse := func() { + metricUPnPResponse.Add(1) + + if ip != gw { + // https://github.com/tailscale/tailscale/issues/5502 + c.logf("UPnP discovery response from %v, but gateway IP is %v", ip, gw) + } + meta, err := parseUPnPDiscoResponse(buf[:n]) + if err != nil { + metricUPnPParseErr.Add(1) + c.logf("unrecognized UPnP discovery response; ignoring: %v", err) + return + } + metricUPnPOK.Add(1) + c.logf("[v1] UPnP reply %+v, %q", meta, buf[:n]) + res.UPnP = true + c.mu.Lock() + c.uPnPSawTime = time.Now() + if c.uPnPMeta != meta { + c.logf("UPnP meta changed: %+v", meta) + c.uPnPMeta = meta + metricUPnPUpdatedMeta.Add(1) + } + c.mu.Unlock() + } + port := uint16(addr.(*net.UDPAddr).Port) switch port { case c.upnpPort(): - metricUPnPResponse.Add(1) if mem.Contains(mem.B(buf[:n]), mem.S(":InternetGatewayDevice:")) { - if ip != gw { - // https://github.com/tailscale/tailscale/issues/5502 - c.logf("UPnP discovery response from %v, but gateway IP is %v", ip, gw) - } - meta, err := parseUPnPDiscoResponse(buf[:n]) - if err != nil { - metricUPnPParseErr.Add(1) - c.logf("unrecognized UPnP discovery response; ignoring: %v", err) - continue - } - metricUPnPOK.Add(1) - c.logf("[v1] UPnP reply %+v, %q", meta, buf[:n]) - res.UPnP = true - c.mu.Lock() - c.uPnPSawTime = time.Now() - if c.uPnPMeta != meta { - c.logf("UPnP meta changed: %+v", meta) - c.uPnPMeta = meta - metricUPnPUpdatedMeta.Add(1) - } - c.mu.Unlock() + handleUPnPResponse() } + + default: + // https://github.com/tailscale/tailscale/issues/7377 + if mem.Contains(mem.B(buf[:n]), mem.S(":InternetGatewayDevice:")) { + c.logf("UPnP discovery response from non-UPnP port %d", port) + metricUPnPResponseAlternatePort.Add(1) + handleUPnPResponse() + } + case c.pxpPort(): // same value for PMP and PCP metricPXPResponse.Add(1) if pres, ok := parsePCPResponse(buf[:n]); ok { @@ -983,6 +998,10 @@ var ( // metricUPnPResponse counts the number of times we received a UPnP response. metricUPnPResponse = clientmetric.NewCounter("portmap_upnp_response") + // metricUPnPResponseAlternatePort counts the number of times we + // received a UPnP response from a port other than the UPnP port. + metricUPnPResponseAlternatePort = clientmetric.NewCounter("portmap_upnp_response_alternate_port") + // metricUPnPParseErr counts the number of times we failed to parse a UPnP response. metricUPnPParseErr = clientmetric.NewCounter("portmap_upnp_parse_err")