Inside Bitcoin
Opening the selected file…
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2024-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or https://www.opensource.org/licenses/mit-license.php.5#include <common/pcp.h>7#include <compat/compat.h>8#include <crypto/common.h>9#include <crypto/hex_base.h>10#include <netaddress.h>11#include <netbase.h>12#include <tinyformat.h>13#include <util/check.h>14#include <util/log.h>15#include <util/sock.h>16#include <util/string.h>17#include <util/threadinterrupt.h>18#include <util/time.h>20#include <algorithm>21#include <atomic>22#include <compare>23#include <cstring>24#include <functional>25#include <map>26#include <memory>27#include <optional>28#include <span>29#include <utility>30#include <vector>32namespace {34// RFC6886 NAT-PMP and RFC6887 Port Control Protocol (PCP) implementation.35// NAT-PMP and PCP use network byte order (big-endian).37// NAT-PMP (v0) protocol constants.38//! NAT-PMP uses a fixed server port number (RFC6887 section 1.1).39constexpr uint16_t NATPMP_SERVER_PORT = 5351;40//! Version byte for NATPMP (RFC6886 1.1)41constexpr uint8_t NATPMP_VERSION = 0;42//! Request opcode base (RFC6886 3).43constexpr uint8_t NATPMP_REQUEST = 0x00;44//! Response opcode base (RFC6886 3).45constexpr uint8_t NATPMP_RESPONSE = 0x80;46//! Get external address (RFC6886 3.2)47constexpr uint8_t NATPMP_OP_GETEXTERNAL = 0x00;48//! Map TCP port (RFC6886 3.3)49constexpr uint8_t NATPMP_OP_MAP_TCP = 0x02;50//! Shared request header size in bytes.51constexpr size_t NATPMP_REQUEST_HDR_SIZE = 2;52//! Shared response header (minimum) size in bytes.53constexpr size_t NATPMP_RESPONSE_HDR_SIZE = 8;54//! GETEXTERNAL request size in bytes, including header (RFC6886 3.2).55constexpr size_t NATPMP_GETEXTERNAL_REQUEST_SIZE = NATPMP_REQUEST_HDR_SIZE + 0;56//! GETEXTERNAL response size in bytes, including header (RFC6886 3.2).57constexpr size_t NATPMP_GETEXTERNAL_RESPONSE_SIZE = NATPMP_RESPONSE_HDR_SIZE + 4;58//! MAP request size in bytes, including header (RFC6886 3.3).59constexpr size_t NATPMP_MAP_REQUEST_SIZE = NATPMP_REQUEST_HDR_SIZE + 10;60//! MAP response size in bytes, including header (RFC6886 3.3).61constexpr size_t NATPMP_MAP_RESPONSE_SIZE = NATPMP_RESPONSE_HDR_SIZE + 8;63// Shared header offsets (RFC6886 3.2, 3.3), relative to start of packet.64//! Offset of version field in packets.65constexpr size_t NATPMP_HDR_VERSION_OFS = 0;66//! Offset of opcode field in packets67constexpr size_t NATPMP_HDR_OP_OFS = 1;68//! Offset of result code in packets. Result codes are 16 bit in NAT-PMP instead of 8 bit in PCP.69constexpr size_t NATPMP_RESPONSE_HDR_RESULT_OFS = 2;71// GETEXTERNAL response offsets (RFC6886 3.2), relative to start of packet.72//! Returned external address73constexpr size_t NATPMP_GETEXTERNAL_RESPONSE_IP_OFS = 8;75// MAP request offsets (RFC6886 3.3), relative to start of packet.76//! Internal port to be mapped.77constexpr size_t NATPMP_MAP_REQUEST_INTERNAL_PORT_OFS = 4;78//! Suggested external port for mapping.79constexpr size_t NATPMP_MAP_REQUEST_EXTERNAL_PORT_OFS = 6;80//! Requested port mapping lifetime in seconds.81constexpr size_t NATPMP_MAP_REQUEST_LIFETIME_OFS = 8;83// MAP response offsets (RFC6886 3.3), relative to start of packet.84//! Internal port for mapping (will match internal port of request).85constexpr size_t NATPMP_MAP_RESPONSE_INTERNAL_PORT_OFS = 8;86//! External port for mapping.87constexpr size_t NATPMP_MAP_RESPONSE_EXTERNAL_PORT_OFS = 10;88//! Created port mapping lifetime in seconds.89constexpr size_t NATPMP_MAP_RESPONSE_LIFETIME_OFS = 12;91// Relevant NETPMP result codes (RFC6886 3.5).92//! Result code representing success status.93constexpr uint8_t NATPMP_RESULT_SUCCESS = 0;94//! Result code representing unsupported version.95constexpr uint8_t NATPMP_RESULT_UNSUPP_VERSION = 1;96//! Result code representing not authorized (router doesn't support port mapping).97constexpr uint8_t NATPMP_RESULT_NOT_AUTHORIZED = 2;98//! Result code representing lack of resources.99constexpr uint8_t NATPMP_RESULT_NO_RESOURCES = 4;101//! Mapping of NATPMP result code to string (RFC6886 3.5). Result codes <=2 match PCP.102const std::map<uint16_t, std::string> NATPMP_RESULT_STR{103{0, "SUCCESS"},104{1, "UNSUPP_VERSION"},105{2, "NOT_AUTHORIZED"},106{3, "NETWORK_FAILURE"},107{4, "NO_RESOURCES"},108{5, "UNSUPP_OPCODE"},109};111// PCP (v2) protocol constants.112//! Maximum packet size in bytes (RFC6887 section 7).113constexpr size_t PCP_MAX_SIZE = 1100;114//! PCP uses a fixed server port number (RFC6887 section 19.1). Shared with NAT-PMP.115constexpr uint16_t PCP_SERVER_PORT = NATPMP_SERVER_PORT;116//! Version byte. 0 is NAT-PMP (RFC6886), 1 is forbidden, 2 for PCP (RFC6887).117constexpr uint8_t PCP_VERSION = 2;118//! PCP Request Header. See RFC6887 section 7.1. Shared with NAT-PMP.119constexpr uint8_t PCP_REQUEST = NATPMP_REQUEST; // R = 0120//! PCP Response Header. See RFC6887 section 7.2. Shared with NAT-PMP.121constexpr uint8_t PCP_RESPONSE = NATPMP_RESPONSE; // R = 1122//! Map opcode. See RFC6887 section 19.2123constexpr uint8_t PCP_OP_MAP = 0x01;124//! TCP protocol number (IANA).125constexpr uint16_t PCP_PROTOCOL_TCP = 6;126//! Request and response header size in bytes (RFC6887 section 7.1).127constexpr size_t PCP_HDR_SIZE = 24;128//! Map request and response size in bytes (RFC6887 section 11.1).129constexpr size_t PCP_MAP_SIZE = 36;131// Header offsets shared between request and responses (RFC6887 7.1, 7.2), relative to start of packet.132//! Version field (1 byte).133constexpr size_t PCP_HDR_VERSION_OFS = NATPMP_HDR_VERSION_OFS;134//! Opcode field (1 byte).135constexpr size_t PCP_HDR_OP_OFS = NATPMP_HDR_OP_OFS;136//! Requested lifetime (request), granted lifetime (response) (4 bytes).137constexpr size_t PCP_HDR_LIFETIME_OFS = 4;139// Request header offsets (RFC6887 7.1), relative to start of packet.140//! PCP client's IP address (16 bytes).141constexpr size_t PCP_REQUEST_HDR_IP_OFS = 8;143// Response header offsets (RFC6887 7.2), relative to start of packet.144//! Result code (1 byte).145constexpr size_t PCP_RESPONSE_HDR_RESULT_OFS = 3;147// MAP request/response offsets (RFC6887 11.1), relative to start of opcode-specific data.148//! Mapping nonce (12 bytes).149constexpr size_t PCP_MAP_NONCE_OFS = 0;150//! Protocol (1 byte).151constexpr size_t PCP_MAP_PROTOCOL_OFS = 12;152//! Internal port for mapping (2 bytes).153constexpr size_t PCP_MAP_INTERNAL_PORT_OFS = 16;154//! Suggested external port (request), assigned external port (response) (2 bytes).155constexpr size_t PCP_MAP_EXTERNAL_PORT_OFS = 18;156//! Suggested external IP (request), assigned external IP (response) (16 bytes).157constexpr size_t PCP_MAP_EXTERNAL_IP_OFS = 20;159//! Result code representing success (RFC6887 7.4), shared with NAT-PMP.160constexpr uint8_t PCP_RESULT_SUCCESS = NATPMP_RESULT_SUCCESS;161//! Result code representing not authorized (RFC6887 7.4), shared with NAT-PMP.162constexpr uint8_t PCP_RESULT_NOT_AUTHORIZED = NATPMP_RESULT_NOT_AUTHORIZED;163//! Result code representing lack of resources (RFC6887 7.4).164constexpr uint8_t PCP_RESULT_NO_RESOURCES = 8;166//! Mapping of PCP result code to string (RFC6887 7.4). Result codes <=2 match NAT-PMP.167const std::map<uint8_t, std::string> PCP_RESULT_STR{168{0, "SUCCESS"},169{1, "UNSUPP_VERSION"},170{2, "NOT_AUTHORIZED"},171{3, "MALFORMED_REQUEST"},172{4, "UNSUPP_OPCODE"},173{5, "UNSUPP_OPTION"},174{6, "MALFORMED_OPTION"},175{7, "NETWORK_FAILURE"},176{8, "NO_RESOURCES"},177{9, "UNSUPP_PROTOCOL"},178{10, "USER_EX_QUOTA"},179{11, "CANNOT_PROVIDE_EXTERNAL"},180{12, "ADDRESS_MISMATCH"},181{13, "EXCESSIVE_REMOTE_PEER"},182};184//! Return human-readable string from NATPMP result code.185std::string NATPMPResultString(uint16_t result_code)186{187auto result_i = NATPMP_RESULT_STR.find(result_code);188return strprintf("%s (code %d)", result_i == NATPMP_RESULT_STR.end() ? "(unknown)" : result_i->second, result_code);189}191//! Return human-readable string from PCP result code.192std::string PCPResultString(uint8_t result_code)193{194auto result_i = PCP_RESULT_STR.find(result_code);195return strprintf("%s (code %d)", result_i == PCP_RESULT_STR.end() ? "(unknown)" : result_i->second, result_code);196}198//! Wrap address in IPv6 according to RFC6887. wrapped_addr needs to be able to store 16 bytes.199[[nodiscard]] bool PCPWrapAddress(std::span<uint8_t> wrapped_addr, const CNetAddr &addr)200{201Assume(wrapped_addr.size() == ADDR_IPV6_SIZE);202if (addr.IsIPv4()) {203struct in_addr addr4;204if (!addr.GetInAddr(&addr4)) return false;205// Section 5: "When the address field holds an IPv4 address, an IPv4-mapped IPv6 address [RFC4291] is used (::ffff:0:0/96)."206std::memcpy(wrapped_addr.data(), IPV4_IN_IPV6_PREFIX.data(), IPV4_IN_IPV6_PREFIX.size());207std::memcpy(wrapped_addr.data() + IPV4_IN_IPV6_PREFIX.size(), &addr4, ADDR_IPV4_SIZE);208return true;209} else if (addr.IsIPv6()) {210struct in6_addr addr6;211if (!addr.GetIn6Addr(&addr6)) return false;212std::memcpy(wrapped_addr.data(), &addr6, ADDR_IPV6_SIZE);213return true;214} else {215return false;216}217}219//! Unwrap PCP-encoded address according to RFC6887.220CNetAddr PCPUnwrapAddress(std::span<const uint8_t> wrapped_addr)221{222Assume(wrapped_addr.size() == ADDR_IPV6_SIZE);223if (util::HasPrefix(wrapped_addr, IPV4_IN_IPV6_PREFIX)) {224struct in_addr addr4;225std::memcpy(&addr4, wrapped_addr.data() + IPV4_IN_IPV6_PREFIX.size(), ADDR_IPV4_SIZE);226return CNetAddr(addr4);227} else {228struct in6_addr addr6;229std::memcpy(&addr6, wrapped_addr.data(), ADDR_IPV6_SIZE);230return CNetAddr(addr6);231}232}234//! PCP or NAT-PMP send-receive loop.235std::optional<std::vector<uint8_t>> PCPSendRecv(Sock &sock, const std::string &protocol, std::span<const uint8_t> request, int num_tries,236std::chrono::milliseconds timeout_per_try,237std::function<bool(std::span<const uint8_t>)> check_packet,238CThreadInterrupt& interrupt)239{240using namespace std::chrono;241// UDP is a potentially lossy protocol, so we try to send again a few times.242uint8_t response[PCP_MAX_SIZE];243bool got_response = false;244int recvsz = 0;245for (int ntry = 0; !got_response && ntry < num_tries; ++ntry) {246if (ntry > 0) {247LogDebug(BCLog::NET, "%s: Retrying (%d)\n", protocol, ntry);248}249// Dispatch packet to gateway.250if (sock.Send(request.data(), request.size(), 0) != static_cast<ssize_t>(request.size())) {
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.