From 6fb5d4080c34fb91b71aca27a2a8d17d3aeb8666 Mon Sep 17 00:00:00 2001
From: David Anderson <danderson@tailscale.com>
Date: Wed, 17 Mar 2021 15:33:45 -0700
Subject: [PATCH] net/portmapper: silently handle PCP NOT_AUTHORIZED responses.

Fixes #1525.

Signed-off-by: David Anderson <danderson@tailscale.com>
---
 net/portmapper/portmapper.go | 25 +++++++++++++++++++------
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/net/portmapper/portmapper.go b/net/portmapper/portmapper.go
index a0ec0795a..0bf4e3028 100644
--- a/net/portmapper/portmapper.go
+++ b/net/portmapper/portmapper.go
@@ -492,8 +492,9 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
 	}
 
 	buf := make([]byte, 1500)
+	pcpHeard := false // true when we get any PCP response
 	for {
-		if res.PCP && res.PMP && res.UPnP {
+		if pcpHeard && res.PMP && res.UPnP {
 			// Nothing more to discover.
 			return res, nil
 		}
@@ -515,13 +516,24 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
 			}
 		case pcpPort: // same as pmpPort
 			if pres, ok := parsePCPResponse(buf[:n]); ok {
-				if pres.OpCode == pcpOpReply|pcpOpAnnounce && pres.ResultCode == pcpCodeOK {
-					c.logf("Got PCP response: epoch: %v", pres.Epoch)
-					res.PCP = true
+				if pres.OpCode == pcpOpReply|pcpOpAnnounce {
+					pcpHeard = true
 					c.mu.Lock()
 					c.pcpSawTime = time.Now()
 					c.mu.Unlock()
-					continue
+					switch pres.ResultCode {
+					case pcpCodeOK:
+						c.logf("Got PCP response: epoch: %v", pres.Epoch)
+						res.PCP = true
+						continue
+					case pcpCodeNotAuthorized:
+						// A PCP service is running, but refuses to
+						// provide port mapping services.
+						res.PCP = false
+						continue
+					default:
+						// Fall through to unexpected log line.
+					}
 				}
 				c.logf("unexpected PCP probe response: %+v", pres)
 			}
@@ -546,7 +558,8 @@ const (
 	pcpVersion = 2
 	pcpPort    = 5351
 
-	pcpCodeOK = 0
+	pcpCodeOK            = 0
+	pcpCodeNotAuthorized = 2
 
 	pcpOpReply    = 0x80 // OR'd into request's op code on response
 	pcpOpAnnounce = 0