net/netcheck: do PCP discovery without side effects

Manually cherry-picked subset of c64bd587aee9709bc6a6203de58361e2bafc2dbf
back into the 1.4 branch.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2021-02-23 14:46:17 -08:00
parent 3b9fcc2b81
commit 1aeeeb7e45

View File

@ -8,9 +8,7 @@ package netcheck
import ( import (
"bufio" "bufio"
"context" "context"
"crypto/rand"
"crypto/tls" "crypto/tls"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -701,16 +699,14 @@ func (rs *reportState) probePortMapServices() {
return return
} }
defer uc.Close() defer uc.Close()
tempPort := uc.LocalAddr().(*net.UDPAddr).Port
uc.SetReadDeadline(time.Now().Add(portMapServiceProbeTimeout)) uc.SetReadDeadline(time.Now().Add(portMapServiceProbeTimeout))
// Send request packets for all three protocols. // Send request packets for all three protocols.
uc.WriteTo(uPnPPacket, port1900) uc.WriteTo(uPnPPacket, port1900)
uc.WriteTo(pmpPacket, port5351) uc.WriteTo(pmpPacket, port5351)
uc.WriteTo(pcpPacket(myIP, tempPort, false), port5351) uc.WriteTo(pcpAnnounceRequest(myIP), port5351)
res := make([]byte, 1500) res := make([]byte, 1500)
sentPCPDelete := false
for { for {
n, addr, err := uc.ReadFrom(res) n, addr, err := uc.ReadFrom(res)
if err != nil { if err != nil {
@ -725,17 +721,8 @@ func (rs *reportState) probePortMapServices() {
if n == 12 && res[0] == 0x00 { // right length and version 0 if n == 12 && res[0] == 0x00 { // right length and version 0
rs.setOptBool(&rs.report.PMP, true) rs.setOptBool(&rs.report.PMP, true)
} }
if n == 60 && res[0] == 0x02 { // right length and version 2 if n == 24 && res[0] == 0x02 { // right length and version 2
rs.setOptBool(&rs.report.PCP, true) rs.setOptBool(&rs.report.PCP, true)
if !sentPCPDelete {
sentPCPDelete = true
// And now delete the mapping.
// (PCP is the only protocol of the three that requires
// we cause a side effect to detect whether it's present,
// so we need to redo that side effect now.)
uc.WriteTo(pcpPacket(myIP, tempPort, true), port5351)
}
} }
} }
} }
@ -751,32 +738,19 @@ var uPnPPacket = []byte("M-SEARCH * HTTP/1.1\r\n" +
var v4unspec, _ = netaddr.ParseIP("0.0.0.0") var v4unspec, _ = netaddr.ParseIP("0.0.0.0")
// pcpPacket generates a PCP packet with a MAP opcode. const (
func pcpPacket(myIP netaddr.IP, mapToLocalPort int, delete bool) []byte { pcpVersion = 2
const udpProtoNumber = 17 pcpOpAnnounce = 0
lifetimeSeconds := uint32(1) )
if delete {
lifetimeSeconds = 0
}
const opMap = 1
// 24 byte header + 36 byte map opcode // pcpAnnounceRequest generates a PCP packet with an ANNOUNCE opcode.
pkt := make([]byte, (32+32+128)/8+(96+8+24+16+16+128)/8) func pcpAnnounceRequest(myIP netaddr.IP) []byte {
// See https://tools.ietf.org/html/rfc6887#section-7.1
// The header (https://tools.ietf.org/html/rfc6887#section-7.1) pkt := make([]byte, 24)
pkt[0] = 2 // version pkt[0] = pcpVersion // version
pkt[1] = opMap pkt[1] = pcpOpAnnounce
binary.BigEndian.PutUint32(pkt[4:8], lifetimeSeconds)
myIP16 := myIP.As16() myIP16 := myIP.As16()
copy(pkt[8:], myIP16[:]) copy(pkt[8:], myIP16[:])
// The map opcode body (https://tools.ietf.org/html/rfc6887#section-11.1)
mapOp := pkt[24:]
rand.Read(mapOp[:12]) // 96 bit mappping nonce
mapOp[12] = udpProtoNumber
binary.BigEndian.PutUint16(mapOp[16:], uint16(mapToLocalPort))
v4unspec16 := v4unspec.As16()
copy(mapOp[20:], v4unspec16[:])
return pkt return pkt
} }