Update
This commit is contained in:
224
vendor/tailscale.com/net/portmapper/portmapper.go
generated
vendored
224
vendor/tailscale.com/net/portmapper/portmapper.go
generated
vendored
@@ -8,29 +8,36 @@ package portmapper
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go4.org/mem"
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/neterror"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/portmapper/portmappertype"
|
||||
"tailscale.com/net/sockstats"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/nettype"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/eventbus"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoPortMappingServices = portmappertype.ErrNoPortMappingServices
|
||||
ErrGatewayRange = portmappertype.ErrGatewayRange
|
||||
ErrGatewayIPv6 = portmappertype.ErrGatewayIPv6
|
||||
ErrPortMappingDisabled = portmappertype.ErrPortMappingDisabled
|
||||
)
|
||||
|
||||
var disablePortMapperEnv = envknob.RegisterBool("TS_DISABLE_PORTMAPPER")
|
||||
@@ -48,15 +55,33 @@ type DebugKnobs struct {
|
||||
LogHTTP bool
|
||||
|
||||
// Disable* disables a specific service from mapping.
|
||||
DisableUPnP bool
|
||||
DisablePMP bool
|
||||
DisablePCP bool
|
||||
// If the funcs are nil or return false, the service is not disabled.
|
||||
// Use the corresponding accessor methods without the "Func" suffix
|
||||
// to check whether a service is disabled.
|
||||
DisableUPnPFunc func() bool
|
||||
DisablePMPFunc func() bool
|
||||
DisablePCPFunc func() bool
|
||||
|
||||
// DisableAll, if non-nil, is a func that reports whether all port
|
||||
// mapping attempts should be disabled.
|
||||
DisableAll func() bool
|
||||
}
|
||||
|
||||
// DisableUPnP reports whether UPnP is disabled.
|
||||
func (k *DebugKnobs) DisableUPnP() bool {
|
||||
return k != nil && k.DisableUPnPFunc != nil && k.DisableUPnPFunc()
|
||||
}
|
||||
|
||||
// DisablePMP reports whether NAT-PMP is disabled.
|
||||
func (k *DebugKnobs) DisablePMP() bool {
|
||||
return k != nil && k.DisablePMPFunc != nil && k.DisablePMPFunc()
|
||||
}
|
||||
|
||||
// DisablePCP reports whether PCP is disabled.
|
||||
func (k *DebugKnobs) DisablePCP() bool {
|
||||
return k != nil && k.DisablePCPFunc != nil && k.DisablePCPFunc()
|
||||
}
|
||||
|
||||
func (k *DebugKnobs) disableAll() bool {
|
||||
if disablePortMapperEnv() {
|
||||
return true
|
||||
@@ -84,16 +109,20 @@ const trustServiceStillAvailableDuration = 10 * time.Minute
|
||||
|
||||
// Client is a port mapping client.
|
||||
type Client struct {
|
||||
// The following two fields must both be non-nil.
|
||||
// Both are immutable after construction.
|
||||
pubClient *eventbus.Client
|
||||
updates *eventbus.Publisher[portmappertype.Mapping]
|
||||
|
||||
logf logger.Logf
|
||||
netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand
|
||||
controlKnobs *controlknobs.Knobs
|
||||
ipAndGateway func() (gw, ip netip.Addr, ok bool)
|
||||
onChange func() // or nil
|
||||
debug DebugKnobs
|
||||
testPxPPort uint16 // if non-zero, pxpPort to use for tests
|
||||
testUPnPPort uint16 // if non-zero, uPnPPort to use for tests
|
||||
|
||||
mu sync.Mutex // guards following, and all fields thereof
|
||||
mu syncs.Mutex // guards following, and all fields thereof
|
||||
|
||||
// runningCreate is whether we're currently working on creating
|
||||
// a port mapping (whether GetCachedMappingOrStartCreatingOne kicked
|
||||
@@ -124,6 +153,8 @@ type Client struct {
|
||||
mapping mapping // non-nil if we have a mapping
|
||||
}
|
||||
|
||||
var _ portmappertype.Client = (*Client)(nil)
|
||||
|
||||
func (c *Client) vlogf(format string, args ...any) {
|
||||
if c.debug.VerboseLogs {
|
||||
c.logf(format, args...)
|
||||
@@ -153,7 +184,6 @@ type mapping interface {
|
||||
MappingDebug() string
|
||||
}
|
||||
|
||||
// HaveMapping reports whether we have a current valid mapping.
|
||||
func (c *Client) HaveMapping() bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
@@ -201,32 +231,52 @@ func (m *pmpMapping) Release(ctx context.Context) {
|
||||
uc.WriteToUDPAddrPort(pkt, m.gw)
|
||||
}
|
||||
|
||||
// NewClient returns a new portmapping client.
|
||||
//
|
||||
// The netMon parameter is required.
|
||||
//
|
||||
// The debug argument allows configuring the behaviour of the portmapper for
|
||||
// debugging; if nil, a sensible set of defaults will be used.
|
||||
//
|
||||
// The controlKnobs, if non-nil, specifies the control knobs from the control
|
||||
// plane that might disable portmapping.
|
||||
//
|
||||
// The optional onChange argument specifies a func to run in a new goroutine
|
||||
// whenever the port mapping status has changed. If nil, it doesn't make a
|
||||
// callback.
|
||||
func NewClient(logf logger.Logf, netMon *netmon.Monitor, debug *DebugKnobs, controlKnobs *controlknobs.Knobs, onChange func()) *Client {
|
||||
if netMon == nil {
|
||||
panic("nil netMon")
|
||||
// Config carries the settings for a [Client].
|
||||
type Config struct {
|
||||
// EventBus, which must be non-nil, is used for event publication and
|
||||
// subscription by portmapper clients created from this config.
|
||||
EventBus *eventbus.Bus
|
||||
|
||||
// Logf is called to generate text logs for the client. If nil, logger.Discard is used.
|
||||
Logf logger.Logf
|
||||
|
||||
// NetMon is the network monitor used by the client. It must be non-nil.
|
||||
NetMon *netmon.Monitor
|
||||
|
||||
// DebugKnobs, if non-nil, configure the behaviour of the portmapper for
|
||||
// debugging. If nil, a sensible set of defaults will be used.
|
||||
DebugKnobs *DebugKnobs
|
||||
|
||||
// OnChange is called to run in a new goroutine whenever the port mapping
|
||||
// status has changed. If nil, no callback is issued.
|
||||
OnChange func()
|
||||
}
|
||||
|
||||
// NewClient constructs a new portmapping [Client] from c. It will panic if any
|
||||
// required parameters are omitted.
|
||||
func NewClient(c Config) *Client {
|
||||
switch {
|
||||
case c.NetMon == nil:
|
||||
panic("nil NetMon")
|
||||
case c.EventBus == nil:
|
||||
panic("nil EventBus")
|
||||
}
|
||||
ret := &Client{
|
||||
logf: logf,
|
||||
netMon: netMon,
|
||||
ipAndGateway: netmon.LikelyHomeRouterIP, // TODO(bradfitz): move this to method on netMon
|
||||
onChange: onChange,
|
||||
controlKnobs: controlKnobs,
|
||||
logf: c.Logf,
|
||||
netMon: c.NetMon,
|
||||
onChange: c.OnChange,
|
||||
}
|
||||
if debug != nil {
|
||||
ret.debug = *debug
|
||||
if buildfeatures.HasPortMapper {
|
||||
// TODO(bradfitz): move this to method on netMon
|
||||
ret.ipAndGateway = netmon.LikelyHomeRouterIP
|
||||
}
|
||||
ret.pubClient = c.EventBus.Client("portmapper")
|
||||
ret.updates = eventbus.Publish[portmappertype.Mapping](ret.pubClient)
|
||||
if ret.logf == nil {
|
||||
ret.logf = logger.Discard
|
||||
}
|
||||
if c.DebugKnobs != nil {
|
||||
ret.debug = *c.DebugKnobs
|
||||
}
|
||||
return ret
|
||||
}
|
||||
@@ -256,6 +306,9 @@ func (c *Client) Close() error {
|
||||
}
|
||||
c.closed = true
|
||||
c.invalidateMappingsLocked(true)
|
||||
c.updates.Close()
|
||||
c.pubClient.Close()
|
||||
|
||||
// TODO: close some future ever-listening UDP socket(s),
|
||||
// waiting for multicast announcements from router.
|
||||
return nil
|
||||
@@ -417,13 +470,6 @@ func IsNoMappingError(err error) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNoPortMappingServices = errors.New("no port mapping services were found")
|
||||
ErrGatewayRange = errors.New("skipping portmap; gateway range likely lacks support")
|
||||
ErrGatewayIPv6 = errors.New("skipping portmap; no IPv6 support for portmapping")
|
||||
ErrPortMappingDisabled = errors.New("port mapping is disabled")
|
||||
)
|
||||
|
||||
// GetCachedMappingOrStartCreatingOne quickly returns with our current cached portmapping, if any.
|
||||
// If there's not one, it starts up a background goroutine to create one.
|
||||
// If the background goroutine ends up creating one, the onChange hook registered with the
|
||||
@@ -467,10 +513,29 @@ func (c *Client) createMapping() {
|
||||
c.runningCreate = false
|
||||
}()
|
||||
|
||||
if _, err := c.createOrGetMapping(ctx); err == nil && c.onChange != nil {
|
||||
mapping, _, err := c.createOrGetMapping(ctx)
|
||||
if err != nil {
|
||||
if !IsNoMappingError(err) {
|
||||
c.logf("createOrGetMapping: %v", err)
|
||||
}
|
||||
return
|
||||
} else if mapping == nil {
|
||||
return
|
||||
|
||||
// TODO(creachadair): This was already logged in createOrGetMapping.
|
||||
// It really should not happen at all, but we will need to untangle
|
||||
// the control flow to eliminate that possibility. Meanwhile, this
|
||||
// mitigates a panic downstream, cf. #16662.
|
||||
}
|
||||
c.updates.Publish(portmappertype.Mapping{
|
||||
External: mapping.External(),
|
||||
Type: mapping.MappingType(),
|
||||
GoodUntil: mapping.GoodUntil(),
|
||||
})
|
||||
// TODO(creachadair): Remove this entirely once there are no longer any
|
||||
// places where the callback is set.
|
||||
if c.onChange != nil {
|
||||
go c.onChange()
|
||||
} else if err != nil && !IsNoMappingError(err) {
|
||||
c.logf("createOrGetMapping: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,19 +547,19 @@ var wildcardIP = netip.MustParseAddr("0.0.0.0")
|
||||
//
|
||||
// If no mapping is available, the error will be of type
|
||||
// NoMappingError; see IsNoMappingError.
|
||||
func (c *Client) createOrGetMapping(ctx context.Context) (external netip.AddrPort, err error) {
|
||||
func (c *Client) createOrGetMapping(ctx context.Context) (mapping mapping, external netip.AddrPort, err error) {
|
||||
if c.debug.disableAll() {
|
||||
return netip.AddrPort{}, NoMappingError{ErrPortMappingDisabled}
|
||||
return nil, netip.AddrPort{}, NoMappingError{ErrPortMappingDisabled}
|
||||
}
|
||||
if c.debug.DisableUPnP && c.debug.DisablePCP && c.debug.DisablePMP {
|
||||
return netip.AddrPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
if c.debug.DisableUPnP() && c.debug.DisablePCP() && c.debug.DisablePMP() {
|
||||
return nil, netip.AddrPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
}
|
||||
gw, myIP, ok := c.gatewayAndSelfIP()
|
||||
if !ok {
|
||||
return netip.AddrPort{}, NoMappingError{ErrGatewayRange}
|
||||
return nil, netip.AddrPort{}, NoMappingError{ErrGatewayRange}
|
||||
}
|
||||
if gw.Is6() {
|
||||
return netip.AddrPort{}, NoMappingError{ErrGatewayIPv6}
|
||||
return nil, netip.AddrPort{}, NoMappingError{ErrGatewayIPv6}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
@@ -523,6 +588,17 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netip.AddrPor
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(creachadair): This is more subtle than it should be. Ideally we
|
||||
// would just return the mapping directly, but there are many different
|
||||
// paths through the function with carefully-balanced locks, and not all
|
||||
// the paths have a mapping to return. As a workaround, while we're here
|
||||
// doing cleanup under the lock, grab the final mapping value and return
|
||||
// it, so the caller does not need to grab the lock again and potentially
|
||||
// race with a later update. The mapping itself is concurrency-safe.
|
||||
//
|
||||
// We should restructure this code so the locks are properly scoped.
|
||||
mapping = c.mapping
|
||||
|
||||
// Print the internal details of each mapping if we're being verbose.
|
||||
if c.debug.VerboseLogs {
|
||||
c.logf("successfully obtained mapping: now=%d external=%v type=%s mapping=%s",
|
||||
@@ -548,19 +624,19 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netip.AddrPor
|
||||
if now.Before(m.RenewAfter()) {
|
||||
defer c.mu.Unlock()
|
||||
reusedExisting = true
|
||||
return m.External(), nil
|
||||
return nil, m.External(), nil
|
||||
}
|
||||
// The mapping might still be valid, so just try to renew it.
|
||||
prevPort = m.External().Port()
|
||||
}
|
||||
|
||||
if c.debug.DisablePCP && c.debug.DisablePMP {
|
||||
if c.debug.DisablePCP() && c.debug.DisablePMP() {
|
||||
c.mu.Unlock()
|
||||
if external, ok := c.getUPnPPortMapping(ctx, gw, internalAddr, prevPort); ok {
|
||||
return external, nil
|
||||
return nil, external, nil
|
||||
}
|
||||
c.vlogf("fallback to UPnP due to PCP and PMP being disabled failed")
|
||||
return netip.AddrPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
return nil, netip.AddrPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
}
|
||||
|
||||
// If we just did a Probe (e.g. via netchecker) but didn't
|
||||
@@ -587,16 +663,16 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netip.AddrPor
|
||||
c.mu.Unlock()
|
||||
// fallback to UPnP portmapping
|
||||
if external, ok := c.getUPnPPortMapping(ctx, gw, internalAddr, prevPort); ok {
|
||||
return external, nil
|
||||
return nil, external, nil
|
||||
}
|
||||
c.vlogf("fallback to UPnP due to no PCP and PMP failed")
|
||||
return netip.AddrPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
return nil, netip.AddrPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
uc, err := c.listenPacket(ctx, "udp4", ":0")
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, err
|
||||
return nil, netip.AddrPort{}, err
|
||||
}
|
||||
defer uc.Close()
|
||||
|
||||
@@ -605,7 +681,7 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netip.AddrPor
|
||||
|
||||
pxpAddr := netip.AddrPortFrom(gw, c.pxpPort())
|
||||
|
||||
preferPCP := !c.debug.DisablePCP && (c.debug.DisablePMP || (!haveRecentPMP && haveRecentPCP))
|
||||
preferPCP := !c.debug.DisablePCP() && (c.debug.DisablePMP() || (!haveRecentPMP && haveRecentPCP))
|
||||
|
||||
// Create a mapping, defaulting to PMP unless only PCP was seen recently.
|
||||
if preferPCP {
|
||||
@@ -616,7 +692,7 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netip.AddrPor
|
||||
if neterror.TreatAsLostUDP(err) {
|
||||
err = NoMappingError{ErrNoPortMappingServices}
|
||||
}
|
||||
return netip.AddrPort{}, err
|
||||
return nil, netip.AddrPort{}, err
|
||||
}
|
||||
} else {
|
||||
// Ask for our external address if needed.
|
||||
@@ -625,7 +701,7 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netip.AddrPor
|
||||
if neterror.TreatAsLostUDP(err) {
|
||||
err = NoMappingError{ErrNoPortMappingServices}
|
||||
}
|
||||
return netip.AddrPort{}, err
|
||||
return nil, netip.AddrPort{}, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,7 +710,7 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netip.AddrPor
|
||||
if neterror.TreatAsLostUDP(err) {
|
||||
err = NoMappingError{ErrNoPortMappingServices}
|
||||
}
|
||||
return netip.AddrPort{}, err
|
||||
return nil, netip.AddrPort{}, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -643,13 +719,13 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netip.AddrPor
|
||||
n, src, err := uc.ReadFromUDPAddrPort(res)
|
||||
if err != nil {
|
||||
if ctx.Err() == context.Canceled {
|
||||
return netip.AddrPort{}, err
|
||||
return nil, netip.AddrPort{}, err
|
||||
}
|
||||
// fallback to UPnP portmapping
|
||||
if mapping, ok := c.getUPnPPortMapping(ctx, gw, internalAddr, prevPort); ok {
|
||||
return mapping, nil
|
||||
return nil, mapping, nil
|
||||
}
|
||||
return netip.AddrPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
return nil, netip.AddrPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
}
|
||||
src = netaddr.Unmap(src)
|
||||
if !src.IsValid() {
|
||||
@@ -665,7 +741,7 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netip.AddrPor
|
||||
continue
|
||||
}
|
||||
if pres.ResultCode != 0 {
|
||||
return netip.AddrPort{}, NoMappingError{fmt.Errorf("PMP response Op=0x%x,Res=0x%x", pres.OpCode, pres.ResultCode)}
|
||||
return nil, netip.AddrPort{}, NoMappingError{fmt.Errorf("PMP response Op=0x%x,Res=0x%x", pres.OpCode, pres.ResultCode)}
|
||||
}
|
||||
if pres.OpCode == pmpOpReply|pmpOpMapPublicAddr {
|
||||
m.external = netip.AddrPortFrom(pres.PublicAddr, m.external.Port())
|
||||
@@ -683,7 +759,7 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netip.AddrPor
|
||||
if err != nil {
|
||||
c.logf("failed to get PCP mapping: %v", err)
|
||||
// PCP should only have a single packet response
|
||||
return netip.AddrPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
return nil, netip.AddrPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
}
|
||||
pcpMapping.c = c
|
||||
pcpMapping.internal = m.internal
|
||||
@@ -691,10 +767,10 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netip.AddrPor
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.mapping = pcpMapping
|
||||
return pcpMapping.external, nil
|
||||
return pcpMapping, pcpMapping.external, nil
|
||||
default:
|
||||
c.logf("unknown PMP/PCP version number: %d %v", version, res[:n])
|
||||
return netip.AddrPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
return nil, netip.AddrPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,7 +778,7 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netip.AddrPor
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.mapping = m
|
||||
return m.external, nil
|
||||
return nil, m.external, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -790,19 +866,13 @@ func parsePMPResponse(pkt []byte) (res pmpResponse, ok bool) {
|
||||
return res, true
|
||||
}
|
||||
|
||||
type ProbeResult struct {
|
||||
PCP bool
|
||||
PMP bool
|
||||
UPnP bool
|
||||
}
|
||||
|
||||
// Probe returns a summary of which port mapping services are
|
||||
// available on the network.
|
||||
//
|
||||
// If a probe has run recently and there haven't been any network changes since,
|
||||
// the returned result might be server from the Client's cache, without
|
||||
// sending any network traffic.
|
||||
func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
|
||||
func (c *Client) Probe(ctx context.Context) (res portmappertype.ProbeResult, err error) {
|
||||
if c.debug.disableAll() {
|
||||
return res, ErrPortMappingDisabled
|
||||
}
|
||||
@@ -837,19 +907,19 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
|
||||
// https://github.com/tailscale/tailscale/issues/1001
|
||||
if c.sawPMPRecently() {
|
||||
res.PMP = true
|
||||
} else if !c.debug.DisablePMP {
|
||||
} else if !c.debug.DisablePMP() {
|
||||
metricPMPSent.Add(1)
|
||||
uc.WriteToUDPAddrPort(pmpReqExternalAddrPacket, pxpAddr)
|
||||
}
|
||||
if c.sawPCPRecently() {
|
||||
res.PCP = true
|
||||
} else if !c.debug.DisablePCP {
|
||||
} else if !c.debug.DisablePCP() {
|
||||
metricPCPSent.Add(1)
|
||||
uc.WriteToUDPAddrPort(pcpAnnounceRequest(myIP), pxpAddr)
|
||||
}
|
||||
if c.sawUPnPRecently() {
|
||||
res.UPnP = true
|
||||
} else if !c.debug.DisableUPnP {
|
||||
} else if !c.debug.DisableUPnP() {
|
||||
// Strictly speaking, you discover UPnP services by sending an
|
||||
// SSDP query (which uPnPPacket is) to udp/1900 on the SSDP
|
||||
// multicast address, and then get a flood of responses back
|
||||
|
||||
Reference in New Issue
Block a user