diff options
| author | Brad Fitzpatrick <bradfitz@tailscale.com> | 2021-08-02 22:09:50 -0700 |
|---|---|---|
| committer | Brad Fitzpatrick <bradfitz@tailscale.com> | 2021-08-04 08:36:50 -0700 |
| commit | 5e0b58861860b1f2abd4f78cad5ad060e12ebb04 (patch) | |
| tree | cefcb24054f40791c503b23cabcf3b357d32d6cf /net/portmapper/portmapper.go | |
| parent | 1db9032ff5dfd96e6bd2e1064226065d79d40561 (diff) | |
| download | tailscale-upnpdebug.tar.xz tailscale-upnpdebug.zip | |
net/portmapper: fix UPnP probing, work against all portsupnpdebug
Prior to Tailscale 1.12 it detected UPnP on any port.
Starting with Tailscale 1.11.x, it stopped detecting UPnP on all ports.
Then start plumbing its discovered Location header port number to the
code that was assuming port 5000.
Fixes #2109
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Diffstat (limited to 'net/portmapper/portmapper.go')
| -rw-r--r-- | net/portmapper/portmapper.go | 59 |
1 files changed, 37 insertions, 22 deletions
diff --git a/net/portmapper/portmapper.go b/net/portmapper/portmapper.go index e9ddf27c9..595ad2e08 100644 --- a/net/portmapper/portmapper.go +++ b/net/portmapper/portmapper.go @@ -14,9 +14,11 @@ import ( "fmt" "io" "net" + "net/http" "sync" "time" + "go4.org/mem" "inet.af/netaddr" "tailscale.com/net/interfaces" "tailscale.com/net/netns" @@ -62,8 +64,11 @@ type Client struct { pmpPubIPTime time.Time // time pmpPubIP last verified pmpLastEpoch uint32 - pcpSawTime time.Time // time we last saw PCP was available - uPnPSawTime time.Time // time we last saw UPnP was available + pcpSawTime time.Time // time we last saw PCP was available + + uPnPSawTime time.Time // time we last saw UPnP was available + uPnPMeta uPnPDiscoResponse // Location header from UPnP UDP discovery response + uPnPHTTPClient *http.Client // nil until needed localPort uint16 @@ -560,27 +565,9 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) { defer cancel() defer closeCloserOnContextDone(ctx, uc)() - if c.sawUPnPRecently() { - res.UPnP = true - } else { - hasUPnP := make(chan bool, 1) - defer func() { - res.UPnP = <-hasUPnP - }() - go func() { - client, err := getUPnPClient(ctx, gw) - if err == nil && client != nil { - hasUPnP <- true - c.mu.Lock() - c.uPnPSawTime = time.Now() - c.mu.Unlock() - } - close(hasUPnP) - }() - } - pcpAddr := netaddr.IPPortFrom(gw, pcpPort).UDPAddr() pmpAddr := netaddr.IPPortFrom(gw, pmpPort).UDPAddr() + upnpAddr := netaddr.IPPortFrom(gw, upnpPort).UDPAddr() // Don't send probes to services that we recently learned (for // the same gw/myIP) are available. See @@ -595,11 +582,16 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) { } else { uc.WriteTo(pcpAnnounceRequest(myIP), pcpAddr) } + if c.sawUPnPRecently() { + res.UPnP = true + } else { + uc.WriteTo(uPnPPacket, upnpAddr) + } buf := make([]byte, 1500) pcpHeard := false // true when we get any PCP response for { - if pcpHeard && res.PMP { + if pcpHeard && res.PMP && res.UPnP { // Nothing more to discover. return res, nil } @@ -612,6 +604,19 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) { } port := addr.(*net.UDPAddr).Port switch port { + case upnpPort: + if mem.Contains(mem.B(buf[:n]), mem.S(":InternetGatewayDevice:")) { + meta, err := parseUPnPDiscoResponse(buf[:n]) + if err != nil { + c.logf("unrecognized UPnP discovery response; ignoring") + } + // log.Printf("UPnP reply %+v, %q", meta, buf[:n]) + res.UPnP = true + c.mu.Lock() + c.uPnPSawTime = time.Now() + c.uPnPMeta = meta + c.mu.Unlock() + } case pcpPort: // same as pmpPort if pres, ok := parsePCPResponse(buf[:n]); ok { if pres.OpCode == pcpOpReply|pcpOpAnnounce { @@ -724,3 +729,13 @@ func parsePCPResponse(b []byte) (res pcpResponse, ok bool) { } var pmpReqExternalAddrPacket = []byte{0, 0} // version 0, opcode 0 = "Public address request" + +const ( + upnpPort = 1900 +) + +var uPnPPacket = []byte("M-SEARCH * HTTP/1.1\r\n" + + "HOST: 239.255.255.250:1900\r\n" + + "ST: ssdp:all\r\n" + + "MAN: \"ssdp:discover\"\r\n" + + "MX: 2\r\n\r\n") |
