diff --git a/net/portmapper/pcp.go b/net/portmapper/pcp.go index f4c9ceaaf..3964a5d78 100644 --- a/net/portmapper/pcp.go +++ b/net/portmapper/pcp.go @@ -45,6 +45,9 @@ type pcpMapping struct { renewAfter time.Time goodUntil time.Time + + // TODO should this also contain an epoch? + // Doesn't seem to be used elsewhere, but can use it for validation at some point. } func (p *pcpMapping) GoodUntil() time.Time { return p.goodUntil } @@ -64,8 +67,6 @@ func (p *pcpMapping) Release(ctx context.Context) { // To create a packet which deletes a mapping, lifetimeSec should be set to 0. // If prevPort is not known, it should be set to 0. func buildPCPRequestMappingPacket(myIP netaddr.IP, localPort, prevPort uint16, lifetimeSec uint32) (pkt []byte) { - // note: lifetimeSec = 0 implies delete the mapping, should that be special-cased here? - // 24 byte common PCP header + 36 bytes of MAP-specific fields pkt = make([]byte, 24+36) pkt[0] = pcpVersion @@ -77,12 +78,14 @@ func buildPCPRequestMappingPacket(myIP netaddr.IP, localPort, prevPort uint16, l mapOp := pkt[24:] rand.Read(mapOp[:12]) // 96 bit mapping nonce - // TODO should this be a UDP mapping? It looks like it supports "all protocols" with 0, but + // TODO: should this be a UDP mapping? It looks like it supports "all protocols" with 0, but // also doesn't support a local port then. mapOp[12] = pcpUDPMapping binary.BigEndian.PutUint16(mapOp[16:18], localPort) binary.BigEndian.PutUint16(mapOp[18:20], prevPort) + // TODO: This can also be the previous external IP similar to how PMP caches the + // last external IP, not sure what the benefits of that are. v4unspec := netaddr.MustParseIP("0.0.0.0") v4unspec16 := v4unspec.As16() copy(mapOp[20:], v4unspec16[:]) @@ -100,7 +103,7 @@ func parsePCPMapResponse(resp []byte) (*pcpMapping, error) { if res.ResultCode != pcpCodeOK { return nil, fmt.Errorf("PCP response not ok, code %d", res.ResultCode) } - // TODO don't ignore the nonce and make sure it's the same? + // TODO: don't ignore the nonce and make sure it's the same? externalPort := binary.BigEndian.Uint16(resp[42:44]) externalIPBytes := [16]byte{} copy(externalIPBytes[:], resp[44:]) diff --git a/net/portmapper/portmapper.go b/net/portmapper/portmapper.go index bbd4175e5..237f6f614 100644 --- a/net/portmapper/portmapper.go +++ b/net/portmapper/portmapper.go @@ -24,9 +24,12 @@ "tailscale.com/types/logger" ) -// Debub knobs for "tailscaled debug --portmap". +// Debug knobs for "tailscaled debug --portmap". var ( VerboseLogs bool + + // Disable* disables a specific service from mapping. + DisableUPnP bool DisablePMP bool DisablePCP bool @@ -345,6 +348,9 @@ func (c *Client) createMapping() { // If no mapping is available, the error will be of type // NoMappingError; see IsNoMappingError. func (c *Client) createOrGetMapping(ctx context.Context) (external netaddr.IPPort, err error) { + if DisableUPnP && DisablePCP && DisablePMP { + return + } gw, myIP, ok := c.gatewayAndSelfIP() if !ok { return netaddr.IPPort{}, NoMappingError{ErrGatewayRange} @@ -353,10 +359,6 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netaddr.IPPor c.mu.Lock() localPort := c.localPort internalAddr := netaddr.IPPortFrom(myIP, localPort) - m := &pmpMapping{ - gw: gw, - internal: internalAddr, - } // prevPort is the port we had most previously, if any. We try // to ask for the same port. 0 means to give us any port. @@ -373,12 +375,24 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netaddr.IPPor prevPort = m.External().Port() } + if DisablePCP && DisablePMP { + c.mu.Unlock() + if external, ok := c.getUPnPPortMapping(ctx, gw, internalAddr, prevPort); ok { + return external, nil + } + return netaddr.IPPort{}, NoMappingError{ErrNoPortMappingServices} + } + // If we just did a Probe (e.g. via netchecker) but didn't // find a PMP service, bail out early rather than probing // again. Cuts down latency for most clients. haveRecentPMP := c.sawPMPRecentlyLocked() haveRecentPCP := c.sawPCPRecentlyLocked() + m := &pmpMapping{ + gw: gw, + internal: internalAddr, + } if haveRecentPMP { m.external = m.external.WithIP(c.pmpPubIP) } @@ -390,7 +404,6 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netaddr.IPPor } return netaddr.IPPort{}, NoMappingError{ErrNoPortMappingServices} } - c.mu.Unlock() uc, err := netns.Listener().ListenPacket(ctx, "udp4", ":0") @@ -408,7 +421,7 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netaddr.IPPor pcpAddru := pcpAddr.UDPAddr() // Create a mapping, defaulting to PMP unless only PCP was seen recently. - if !haveRecentPMP && haveRecentPCP { + if DisablePMP || (!haveRecentPMP && haveRecentPCP) { // Only do PCP mapping in the case when PMP did not appear to be available recently. pkt := buildPCPRequestMappingPacket(myIP, localPort, prevPort, pcpMapLifetimeSec) if _, err := uc.WriteTo(pkt, pcpAddru); err != nil { @@ -473,15 +486,18 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netaddr.IPPor pcpMapping, err := parsePCPMapResponse(res[:n]) if err != nil { c.logf("failed to get PCP mapping: %v", err) - continue + // PCP should only have a single packet response + return netaddr.IPPort{}, NoMappingError{ErrNoPortMappingServices} } pcpMapping.internal = m.internal + pcpMapping.gw = gw c.mu.Lock() defer c.mu.Unlock() c.mapping = pcpMapping return pcpMapping.external, nil default: c.logf("unknown PMP/PCP version number: %d %v", version, res[:n]) + return netaddr.IPPort{}, NoMappingError{ErrNoPortMappingServices} } } diff --git a/net/portmapper/upnp.go b/net/portmapper/upnp.go index e07f91f7b..d153dbe33 100644 --- a/net/portmapper/upnp.go +++ b/net/portmapper/upnp.go @@ -154,7 +154,7 @@ func addAnyPortMapping( // The provided ctx is not retained in the returned upnpClient, but // its associated HTTP client is (if set via goupnp.WithHTTPClient). func getUPnPClient(ctx context.Context, logf logger.Logf, gw netaddr.IP, meta uPnPDiscoResponse) (client upnpClient, err error) { - if controlknobs.DisableUPnP() { + if controlknobs.DisableUPnP() || DisableUPnP { return nil, nil } @@ -236,7 +236,7 @@ func (c *Client) getUPnPPortMapping( internal netaddr.IPPort, prevPort uint16, ) (external netaddr.IPPort, ok bool) { - if controlknobs.DisableUPnP() { + if controlknobs.DisableUPnP() || DisableUPnP { return netaddr.IPPort{}, false } now := time.Now()