summaryrefslogtreecommitdiffhomepage
path: root/net/portmapper/portmapper.go
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2021-08-02 22:09:50 -0700
committerBrad Fitzpatrick <bradfitz@tailscale.com>2021-08-04 08:36:50 -0700
commit5e0b58861860b1f2abd4f78cad5ad060e12ebb04 (patch)
treecefcb24054f40791c503b23cabcf3b357d32d6cf /net/portmapper/portmapper.go
parent1db9032ff5dfd96e6bd2e1064226065d79d40561 (diff)
downloadtailscale-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.go59
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")