From 5c98b1b8d03b6f5b038c6e2530264924ab744a9f Mon Sep 17 00:00:00 2001 From: julianknodt Date: Tue, 3 Aug 2021 15:28:13 -0700 Subject: [PATCH] net/portmapper: move pcp code to separate file This moves all the PCP code to a separate file in preparation for portmapping with PCP. Signed-off-by: julianknodt --- net/portmapper/pcp.go | 94 ++++++++++++++++++++++++++++++++++++ net/portmapper/portmapper.go | 73 ---------------------------- 2 files changed, 94 insertions(+), 73 deletions(-) create mode 100644 net/portmapper/pcp.go diff --git a/net/portmapper/pcp.go b/net/portmapper/pcp.go new file mode 100644 index 000000000..d9f6af175 --- /dev/null +++ b/net/portmapper/pcp.go @@ -0,0 +1,94 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package portmapper + +import ( + "crypto/rand" + "encoding/binary" + + "inet.af/netaddr" +) + +// References: +// +// https://www.rfc-editor.org/rfc/pdfrfc/rfc6887.txt.pdf +// https://tools.ietf.org/html/rfc6887 + +// PCP constants +const ( + pcpVersion = 2 + pcpPort = 5351 + + pcpMapLifetimeSec = 7200 // TODO does the RFC recommend anything? This is taken from PMP. + + pcpCodeOK = 0 + pcpCodeNotAuthorized = 2 + + pcpOpReply = 0x80 // OR'd into request's op code on response + pcpOpAnnounce = 0 + pcpOpMap = 1 + + pcpUDPMapping = 17 // portmap UDP + pcpTCPMapping = 6 // portmap TCP +) + +// pcpMapRequest generates a PCP packet with a MAP opcode. +func pcpMapRequest(myIP netaddr.IP, mapToLocalPort int, delete bool) []byte { + const udpProtoNumber = 17 + lifetimeSeconds := uint32(1) + if delete { + lifetimeSeconds = 0 + } + const opMap = 1 + + // 24 byte header + 36 byte map opcode + pkt := make([]byte, (32+32+128)/8+(96+8+24+16+16+128)/8) + + // The header (https://tools.ietf.org/html/rfc6887#section-7.1) + pkt[0] = 2 // version + pkt[1] = opMap + binary.BigEndian.PutUint32(pkt[4:8], lifetimeSeconds) + myIP16 := myIP.As16() + 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)) + v4unspec := netaddr.MustParseIP("0.0.0.0") + v4unspec16 := v4unspec.As16() + copy(mapOp[20:], v4unspec16[:]) + return pkt +} + +// pcpAnnounceRequest generates a PCP packet with an ANNOUNCE opcode. +func pcpAnnounceRequest(myIP netaddr.IP) []byte { + // See https://tools.ietf.org/html/rfc6887#section-7.1 + pkt := make([]byte, 24) + pkt[0] = pcpVersion // version + pkt[1] = pcpOpAnnounce + myIP16 := myIP.As16() + copy(pkt[8:], myIP16[:]) + return pkt +} + +type pcpResponse struct { + OpCode uint8 + ResultCode uint8 + Lifetime uint32 + Epoch uint32 +} + +func parsePCPResponse(b []byte) (res pcpResponse, ok bool) { + if len(b) < 24 || b[0] != pcpVersion { + return + } + res.OpCode = b[1] + res.ResultCode = b[3] + res.Lifetime = binary.BigEndian.Uint32(b[4:]) + res.Epoch = binary.BigEndian.Uint32(b[8:]) + return res, true +} diff --git a/net/portmapper/portmapper.go b/net/portmapper/portmapper.go index d08ddbd77..bc38204fb 100644 --- a/net/portmapper/portmapper.go +++ b/net/portmapper/portmapper.go @@ -8,7 +8,6 @@ package portmapper import ( "context" - "crypto/rand" "encoding/binary" "errors" "fmt" @@ -36,7 +35,6 @@ var ( // References: // // NAT-PMP: https://tools.ietf.org/html/rfc6886 -// PCP: https://tools.ietf.org/html/rfc6887 // portMapServiceTimeout is the time we wait for port mapping // services (UPnP, NAT-PMP, PCP) to respond before we give up and @@ -669,77 +667,6 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) { } } -const ( - pcpVersion = 2 - pcpPort = 5351 - - pcpCodeOK = 0 - pcpCodeNotAuthorized = 2 - - pcpOpReply = 0x80 // OR'd into request's op code on response - pcpOpAnnounce = 0 - pcpOpMap = 1 -) - -// pcpAnnounceRequest generates a PCP packet with an ANNOUNCE opcode. -func pcpAnnounceRequest(myIP netaddr.IP) []byte { - // See https://tools.ietf.org/html/rfc6887#section-7.1 - pkt := make([]byte, 24) - pkt[0] = pcpVersion // version - pkt[1] = pcpOpAnnounce - myIP16 := myIP.As16() - copy(pkt[8:], myIP16[:]) - return pkt -} - -// pcpMapRequest generates a PCP packet with a MAP opcode. -func pcpMapRequest(myIP netaddr.IP, mapToLocalPort int, delete bool) []byte { - const udpProtoNumber = 17 - lifetimeSeconds := uint32(1) - if delete { - lifetimeSeconds = 0 - } - const opMap = 1 - - // 24 byte header + 36 byte map opcode - pkt := make([]byte, (32+32+128)/8+(96+8+24+16+16+128)/8) - - // The header (https://tools.ietf.org/html/rfc6887#section-7.1) - pkt[0] = 2 // version - pkt[1] = opMap - binary.BigEndian.PutUint32(pkt[4:8], lifetimeSeconds) - myIP16 := myIP.As16() - 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)) - v4unspec := netaddr.MustParseIP("0.0.0.0") - v4unspec16 := v4unspec.As16() - copy(mapOp[20:], v4unspec16[:]) - return pkt -} - -type pcpResponse struct { - OpCode uint8 - ResultCode uint8 - Lifetime uint32 - Epoch uint32 -} - -func parsePCPResponse(b []byte) (res pcpResponse, ok bool) { - if len(b) < 24 || b[0] != pcpVersion { - return - } - res.OpCode = b[1] - res.ResultCode = b[3] - res.Lifetime = binary.BigEndian.Uint32(b[4:]) - res.Epoch = binary.BigEndian.Uint32(b[8:]) - return res, true -} - var pmpReqExternalAddrPacket = []byte{0, 0} // version 0, opcode 0 = "Public address request" const (