Documentation
¶
Overview ¶
Package pcp decodes PCP (Port Control Protocol) messages per RFC 6887. PCP is the modern NAT/firewall configuration protocol that supersedes NAT-PMP (RFC 6886) and adds IPv6 support, peer-mapping (for hole-punching), and a more flexible TLV-options envelope. Universal in residential broadband CPE (every ASUS / Netgear / Fritz!Box router / CGNAT enforcement at carriers since ~2014); used directly by uTorrent / qBittorrent / Tailscale's `libpcp` / libnatpmp / miniupnpd to request external port mappings on behalf of inbound-listening applications.
Wrap-vs-native judgement
Native. RFC 6887 is fully public. PCP has a tight 24-byte common header (Version + R-bit + Opcode + per-direction fields) followed by an opcode-specific body and an optional TLV options walker. No crypto at the parse layer.
What this package covers
**24-byte common header** (RFC 6887 §7.1):
byte 0: Version (must be 2 for PCP).
byte 1: R-bit (high bit; 0 = Request from client, 1 = Response from server) + low 7 bits = **Opcode** with **3-entry name table**: 0 ANNOUNCE / 1 MAP / 2 PEER.
For **Requests** (R=0):
2-byte Reserved.
4-byte Requested Lifetime (seconds; 0 = delete mapping).
16-byte PCP Client IP Address (IPv4-mapped or IPv6).
For **Responses** (R=1):
1-byte Reserved.
1-byte **Result Code** with **14-entry name table** (RFC 6887 §7.4): 0 SUCCESS / 1 UNSUPP_VERSION / 2 NOT_AUTHORIZED / 3 MALFORMED_REQUEST / 4 UNSUPP_OPCODE / 5 UNSUPP_OPTION / 6 MALFORMED_OPTION / 7 NETWORK_FAILURE / 8 NO_RESOURCES / 9 UNSUPP_PROTOCOL / 10 USER_EX_QUOTA / 11 CANNOT_PROVIDE_EXTERNAL / 12 ADDRESS_MISMATCH / 13 EXCESSIVE_REMOTE_PEERS.
4-byte Lifetime (granted; or error retry-after on negative Result Code).
4-byte Epoch Time (server's monotonic re-anchor counter).
12-byte Reserved.
**MAP opcode body** (Opcode 1; RFC 6887 §11):
12-byte Mapping Nonce (client-generated cookie for request/response correlation).
1-byte Protocol (IP proto number; 0 = "all protocols").
3-byte Reserved.
2-byte Internal Port (the port the client wants to receive on).
2-byte Suggested External Port (client hint; server may pick differently).
16-byte Suggested External IP Address (client hint; server may pick differently).
**PEER opcode body** (Opcode 2; RFC 6887 §12) — same as MAP plus a remote-peer tuple appended:
2-byte Remote Peer Port.
2-byte Reserved.
16-byte Remote Peer IP Address.
**ANNOUNCE opcode** (Opcode 0) — no opcode-specific body; the common header alone signals server epoch reset (clients must refresh all mappings when received).
**Options walker** (RFC 6887 §7.3) — optional TLV records appended after the opcode body. Each option:
1-byte Option Code (high bit = mandatory).
1-byte Reserved.
2-byte Option Length (uint16 BE; excludes header).
Padded to 4-byte boundary. **6-entry option code name table** (RFC 6887 + 7488
6970 + 7843): 1 THIRD_PARTY (request mapping on behalf of another IP) / 2 PREFER_FAILURE (don't downgrade to a different port if requested unavailable) / 3 FILTER (restrict the mapping to a specific peer) / 4 NAT64_PREFIX (DS-Lite / NAT64 prefix discovery) / 5 PORT_SET (request multiple consecutive ports as one mapping).
What this package does NOT cover (deliberately out of scope)
UDP framing — feed PCP bytes after the UDP header strip. PCP runs on UDP destination port 5351 (server side); clients listen on UDP 5350 for ANNOUNCE multicasts.
NAT-PMP (RFC 6886) — the predecessor protocol that PCP supersedes (8-byte messages, IPv4-only, simpler). Could share most decoder logic but has a different envelope; deferred.
PCP Authentication (RFC 7652) — optional auth extension via 2 additional option types (5 ASMAP_CAPA, 6 NONCE) — surfaced as raw hex via the generic option walker.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type MapBody ¶
type MapBody struct {
MappingNonceHex string `json:"mapping_nonce_hex"`
Protocol int `json:"protocol"`
ProtocolName string `json:"protocol_name"`
InternalPort int `json:"internal_port"`
SuggestedExternalPort int `json:"suggested_external_port"`
SuggestedExternalAddress string `json:"suggested_external_address"`
}
MapBody is the decoded body of a MAP-opcode message.
type Option ¶
type Option struct {
Code int `json:"code"`
CodeName string `json:"code_name"`
Mandatory bool `json:"mandatory"`
Length int `json:"length"`
ValueHex string `json:"value_hex,omitempty"`
}
Option is one TLV record from the options walker.
type PeerBody ¶
type PeerBody struct {
MapBody
RemotePeerPort int `json:"remote_peer_port"`
RemotePeerAddress string `json:"remote_peer_address"`
}
PeerBody is the decoded body of a PEER-opcode message.
type RequestHeader ¶
type RequestHeader struct {
RequestedLifetimeSec uint32 `json:"requested_lifetime_seconds"`
ClientIPAddress string `json:"pcp_client_ip_address"`
}
RequestHeader is the request-specific portion of the common header (R-bit = 0).
type ResponseHeader ¶
type ResponseHeader struct {
ResultCode int `json:"result_code"`
ResultCodeName string `json:"result_code_name"`
LifetimeSec uint32 `json:"lifetime_seconds"`
EpochTime uint32 `json:"epoch_time"`
}
ResponseHeader is the response-specific portion of the common header (R-bit = 1).
type Result ¶
type Result struct {
Version int `json:"version"`
IsResponse bool `json:"is_response"`
Opcode int `json:"opcode"`
OpcodeName string `json:"opcode_name"`
TotalBytes int `json:"total_bytes"`
// Per-direction header fields.
RequestHeader *RequestHeader `json:"request_header,omitempty"`
ResponseHeader *ResponseHeader `json:"response_header,omitempty"`
// Per-opcode bodies.
MapBody *MapBody `json:"map_body,omitempty"`
PeerBody *PeerBody `json:"peer_body,omitempty"`
Options []Option `json:"options,omitempty"`
Notes []string `json:"notes,omitempty"`
}
Result is the top-level decoded view of a PCP message.