summaryrefslogtreecommitdiffhomepage
path: root/net/portmapper/portmapper.go
diff options
context:
space:
mode:
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")