28constexpr uint16_t NATPMP_SERVER_PORT = 5351;
30constexpr uint8_t NATPMP_VERSION = 0;
32constexpr uint8_t NATPMP_REQUEST = 0x00;
34constexpr uint8_t NATPMP_RESPONSE = 0x80;
36constexpr uint8_t NATPMP_OP_GETEXTERNAL = 0x00;
38constexpr uint8_t NATPMP_OP_MAP_TCP = 0x02;
40constexpr size_t NATPMP_REQUEST_HDR_SIZE = 2;
42constexpr size_t NATPMP_RESPONSE_HDR_SIZE = 8;
44constexpr size_t NATPMP_GETEXTERNAL_REQUEST_SIZE = NATPMP_REQUEST_HDR_SIZE + 0;
46constexpr size_t NATPMP_GETEXTERNAL_RESPONSE_SIZE =
47 NATPMP_RESPONSE_HDR_SIZE + 4;
49constexpr size_t NATPMP_MAP_REQUEST_SIZE = NATPMP_REQUEST_HDR_SIZE + 10;
51constexpr size_t NATPMP_MAP_RESPONSE_SIZE = NATPMP_RESPONSE_HDR_SIZE + 8;
55constexpr size_t NATPMP_HDR_VERSION_OFS = 0;
57constexpr size_t NATPMP_HDR_OP_OFS = 1;
60constexpr size_t NATPMP_RESPONSE_HDR_RESULT_OFS = 2;
64constexpr size_t NATPMP_GETEXTERNAL_RESPONSE_IP_OFS = 8;
68constexpr size_t NATPMP_MAP_REQUEST_INTERNAL_PORT_OFS = 4;
70constexpr size_t NATPMP_MAP_REQUEST_EXTERNAL_PORT_OFS = 6;
72constexpr size_t NATPMP_MAP_REQUEST_LIFETIME_OFS = 8;
76constexpr size_t NATPMP_MAP_RESPONSE_INTERNAL_PORT_OFS = 8;
78constexpr size_t NATPMP_MAP_RESPONSE_EXTERNAL_PORT_OFS = 10;
80constexpr size_t NATPMP_MAP_RESPONSE_LIFETIME_OFS = 12;
84constexpr uint8_t NATPMP_RESULT_SUCCESS = 0;
86constexpr uint8_t NATPMP_RESULT_UNSUPP_VERSION = 1;
89constexpr uint8_t NATPMP_RESULT_NOT_AUTHORIZED = 2;
91constexpr uint8_t NATPMP_RESULT_NO_RESOURCES = 4;
95const std::map<uint16_t, std::string> NATPMP_RESULT_STR{
96 {0,
"SUCCESS"}, {1,
"UNSUPP_VERSION"}, {2,
"NOT_AUTHORIZED"},
97 {3,
"NETWORK_FAILURE"}, {4,
"NO_RESOURCES"}, {5,
"UNSUPP_OPCODE"},
102constexpr size_t PCP_MAX_SIZE = 1100;
105constexpr uint16_t PCP_SERVER_PORT = NATPMP_SERVER_PORT;
107constexpr uint8_t PCP_VERSION = 2;
109constexpr uint8_t PCP_REQUEST = NATPMP_REQUEST;
111constexpr uint8_t PCP_RESPONSE = NATPMP_RESPONSE;
113constexpr uint8_t PCP_OP_MAP = 0x01;
115constexpr uint16_t PCP_PROTOCOL_TCP = 6;
117constexpr size_t PCP_HDR_SIZE = 24;
119constexpr size_t PCP_MAP_SIZE = 36;
124constexpr size_t PCP_HDR_VERSION_OFS = NATPMP_HDR_VERSION_OFS;
126constexpr size_t PCP_HDR_OP_OFS = NATPMP_HDR_OP_OFS;
128constexpr size_t PCP_HDR_LIFETIME_OFS = 4;
132constexpr size_t PCP_REQUEST_HDR_IP_OFS = 8;
136constexpr size_t PCP_RESPONSE_HDR_RESULT_OFS = 3;
141constexpr size_t PCP_MAP_NONCE_OFS = 0;
143constexpr size_t PCP_MAP_PROTOCOL_OFS = 12;
145constexpr size_t PCP_MAP_INTERNAL_PORT_OFS = 16;
148constexpr size_t PCP_MAP_EXTERNAL_PORT_OFS = 18;
151constexpr size_t PCP_MAP_EXTERNAL_IP_OFS = 20;
154constexpr uint8_t PCP_RESULT_SUCCESS = NATPMP_RESULT_SUCCESS;
156constexpr uint8_t PCP_RESULT_NOT_AUTHORIZED = NATPMP_RESULT_NOT_AUTHORIZED;
158constexpr uint8_t PCP_RESULT_NO_RESOURCES = 8;
162const std::map<uint8_t, std::string> PCP_RESULT_STR{
163 {0,
"SUCCESS"}, {1,
"UNSUPP_VERSION"},
164 {2,
"NOT_AUTHORIZED"}, {3,
"MALFORMED_REQUEST"},
165 {4,
"UNSUPP_OPCODE"}, {5,
"UNSUPP_OPTION"},
166 {6,
"MALFORMED_OPTION"}, {7,
"NETWORK_FAILURE"},
167 {8,
"NO_RESOURCES"}, {9,
"UNSUPP_PROTOCOL"},
168 {10,
"USER_EX_QUOTA"}, {11,
"CANNOT_PROVIDE_EXTERNAL"},
169 {12,
"ADDRESS_MISMATCH"}, {13,
"EXCESSIVE_REMOTE_PEER"},
173std::string NATPMPResultString(uint16_t result_code) {
174 auto result_i = NATPMP_RESULT_STR.find(result_code);
176 result_i == NATPMP_RESULT_STR.end() ?
"(unknown)"
182std::string PCPResultString(uint8_t result_code) {
183 auto result_i = PCP_RESULT_STR.find(result_code);
185 result_i == PCP_RESULT_STR.end() ?
"(unknown)"
196 struct in_addr addr4;
209 struct in6_addr addr6;
223 struct in_addr addr4;
228 struct in6_addr addr6;
234std::optional<std::vector<uint8_t>>
235PCPSendRecv(
Sock &sock,
const std::string &protocol,
237 std::chrono::milliseconds timeout_per_try,
241 using namespace std::chrono;
244 bool got_response =
false;
246 for (
int ntry = 0; !got_response && ntry < num_tries; ++ntry) {
249 "%s: Retrying (%d)\n", protocol, ntry);
253 static_cast<ssize_t
>(request.
size())) {
255 "%s: Could not send request: %s\n", protocol,
265 auto deadline = cur_time + timeout_per_try;
266 while ((cur_time = time_point_cast<milliseconds>(
274 "%s: Could not wait on socket: %s\n", protocol,
290 "%s: Could not receive response: %s\n", protocol,
296 "%s: Received response of %d bytes: %s\n", protocol,
309 "%s: Giving up after %d tries\n", protocol, num_tries);
317std::variant<MappingResult, MappingError>
320 std::chrono::milliseconds timeout_per_try) {
321 struct sockaddr_storage dest_addr;
322 socklen_t dest_addrlen =
sizeof(
struct sockaddr_storage);
325 "natpmp: Requesting port mapping port %d from gateway %s\n",
329 if (!
CService(gateway, PCP_SERVER_PORT)
330 .GetSockAddr((
struct sockaddr *)&dest_addr, &dest_addrlen)) {
333 if (dest_addr.ss_family != AF_INET) {
338 auto sock{
CreateSock(AF_INET, SOCK_DGRAM, IPPROTO_UDP)};
341 "natpmp: Could not create UDP socket: %s\n",
347 if (sock->
Connect((
struct sockaddr *)&dest_addr, dest_addrlen) != 0) {
349 "natpmp: Could not connect to gateway: %s\n",
356 struct sockaddr_in internal;
357 socklen_t internal_addrlen =
sizeof(
struct sockaddr_in);
358 if (sock->
GetSockName((
struct sockaddr *)&internal, &internal_addrlen) !=
361 "natpmp: Could not get sock name: %s\n",
367 std::vector<uint8_t> request(NATPMP_GETEXTERNAL_REQUEST_SIZE);
368 request[NATPMP_HDR_VERSION_OFS] = NATPMP_VERSION;
369 request[NATPMP_HDR_OP_OFS] = NATPMP_REQUEST | NATPMP_OP_GETEXTERNAL;
371 auto recv_res = PCPSendRecv(
372 *sock,
"natpmp", request, num_tries, timeout_per_try,
374 if (
response.size() < NATPMP_GETEXTERNAL_RESPONSE_SIZE) {
375 LogPrintLevel(BCLog::NET, BCLog::Level::Warning,
376 "natpmp: Response too small\n");
381 if (
response[NATPMP_HDR_VERSION_OFS] != NATPMP_VERSION ||
383 (NATPMP_RESPONSE | NATPMP_OP_GETEXTERNAL)) {
385 "natpmp: Response to wrong command\n");
394 struct in_addr external_addr;
396 const std::span<const uint8_t>
response = *recv_res;
399 uint16_t result_code =
401 if (result_code != NATPMP_RESULT_SUCCESS) {
404 "natpmp: Getting external address failed with result %s\n",
405 NATPMPResultString(result_code));
409 std::memcpy(&external_addr,
410 response.data() + NATPMP_GETEXTERNAL_RESPONSE_IP_OFS,
417 request = std::vector<uint8_t>(NATPMP_MAP_REQUEST_SIZE);
418 request[NATPMP_HDR_VERSION_OFS] = NATPMP_VERSION;
419 request[NATPMP_HDR_OP_OFS] = NATPMP_REQUEST | NATPMP_OP_MAP_TCP;
420 WriteBE16(request.data() + NATPMP_MAP_REQUEST_INTERNAL_PORT_OFS, port);
421 WriteBE16(request.data() + NATPMP_MAP_REQUEST_EXTERNAL_PORT_OFS, port);
422 WriteBE32(request.data() + NATPMP_MAP_REQUEST_LIFETIME_OFS, lifetime);
424 recv_res = PCPSendRecv(
425 *sock,
"natpmp", request, num_tries, timeout_per_try,
427 if (
response.size() < NATPMP_MAP_RESPONSE_SIZE) {
428 LogPrintLevel(BCLog::NET, BCLog::Level::Warning,
429 "natpmp: Response too small\n");
434 if (
response[0] != NATPMP_VERSION ||
435 response[1] != (NATPMP_RESPONSE | NATPMP_OP_MAP_TCP)) {
437 "natpmp: Response to wrong command\n");
443 response.data() + NATPMP_MAP_RESPONSE_INTERNAL_PORT_OFS);
444 if (internal_port != port) {
446 "natpmp: Response port doesn't match request\n");
456 const std::span<uint8_t>
response = *recv_res;
459 uint16_t result_code =
461 if (result_code != NATPMP_RESULT_SUCCESS) {
462 if (result_code == NATPMP_RESULT_NOT_AUTHORIZED) {
463 static std::atomic<bool> warned{
false};
464 if (!warned.exchange(
true)) {
465 LogWarning(
"natpmp: Port mapping failed with result %s\n",
466 NATPMPResultString(result_code));
469 "natpmp: Port mapping failed with result %s\n",
470 NATPMPResultString(result_code));
473 LogWarning(
"natpmp: Port mapping failed with result %s\n",
474 NATPMPResultString(result_code));
476 if (result_code == NATPMP_RESULT_NO_RESOURCES) {
482 uint32_t lifetime_ret =
484 uint16_t external_port =
487 CService(external_addr, external_port),
493std::variant<MappingResult, MappingError>
495 const CNetAddr &bind, uint16_t port, uint32_t lifetime,
497 std::chrono::milliseconds timeout_per_try) {
498 struct sockaddr_storage dest_addr, bind_addr;
499 socklen_t dest_addrlen =
sizeof(
struct sockaddr_storage),
500 bind_addrlen =
sizeof(
struct sockaddr_storage);
504 "pcp: Requesting port mapping for addr %s port %d from gateway %s\n",
508 if (!
CService(gateway, PCP_SERVER_PORT)
509 .GetSockAddr((
struct sockaddr *)&dest_addr, &dest_addrlen)) {
512 if (!
CService(bind, 0).GetSockAddr((
struct sockaddr *)&bind_addr,
516 if (dest_addr.ss_family != bind_addr.ss_family) {
521 auto sock{
CreateSock(dest_addr.ss_family, SOCK_DGRAM, IPPROTO_UDP)};
524 "pcp: Could not create UDP socket: %s\n",
531 if (sock->
Bind((
struct sockaddr *)&bind_addr, bind_addrlen) != 0) {
533 "pcp: Could not bind to address: %s\n",
539 if (sock->
Connect((
struct sockaddr *)&dest_addr, dest_addrlen) != 0) {
541 "pcp: Could not connect to gateway: %s\n",
550 struct sockaddr_storage internal_addr;
551 socklen_t internal_addrlen =
sizeof(
struct sockaddr_storage);
552 if (sock->
GetSockName((
struct sockaddr *)&internal_addr,
553 &internal_addrlen) != 0) {
555 "pcp: Could not get sock name: %s\n",
560 if (!internal.
SetSockAddr((
struct sockaddr *)&internal_addr,
565 "pcp: Internal address after connect: %s\n",
572 std::vector<uint8_t> request(PCP_HDR_SIZE + PCP_MAP_SIZE);
575 request[ofs + PCP_HDR_VERSION_OFS] = PCP_VERSION;
576 request[ofs + PCP_HDR_OP_OFS] = PCP_REQUEST | PCP_OP_MAP;
577 WriteBE32(request.data() + ofs + PCP_HDR_LIFETIME_OFS, lifetime);
590 std::memcpy(request.data() + ofs + PCP_MAP_NONCE_OFS, nonce.data(),
592 request[ofs + PCP_MAP_PROTOCOL_OFS] = PCP_PROTOCOL_TCP;
593 WriteBE16(request.data() + ofs + PCP_MAP_INTERNAL_PORT_OFS, port);
594 WriteBE16(request.data() + ofs + PCP_MAP_EXTERNAL_PORT_OFS, port);
595 if (!PCPWrapAddress(
Span(request).subspan(ofs + PCP_MAP_EXTERNAL_IP_OFS,
602 Assume(ofs == request.size());
605 bool is_natpmp =
false;
606 auto recv_res = PCPSendRecv(
607 *sock,
"pcp", request, num_tries, timeout_per_try,
611 if (
response.size() == NATPMP_RESPONSE_HDR_SIZE &&
612 response[PCP_HDR_VERSION_OFS] == NATPMP_VERSION &&
613 response[PCP_RESPONSE_HDR_RESULT_OFS] ==
614 NATPMP_RESULT_UNSUPP_VERSION) {
619 if (
response.size() < (PCP_HDR_SIZE + PCP_MAP_SIZE)) {
620 LogPrintLevel(BCLog::NET, BCLog::Level::Warning,
621 "pcp: Response too small\n");
626 if (
response[PCP_HDR_VERSION_OFS] != PCP_VERSION ||
627 response[PCP_HDR_OP_OFS] != (PCP_RESPONSE | PCP_OP_MAP)) {
629 "pcp: Response to wrong command\n");
636 if (!std::ranges::equal(
637 response.subspan(PCP_HDR_SIZE + PCP_MAP_NONCE_OFS,
641 "pcp: Mapping nonce mismatch\n");
646 uint8_t protocol =
response[PCP_HDR_SIZE + 12];
647 uint16_t internal_port =
649 if (protocol != PCP_PROTOCOL_TCP || internal_port != port) {
652 "pcp: Response protocol or port doesn't match request\n");
668 const std::span<const uint8_t>
response = *recv_res;
672 uint8_t result_code =
response[PCP_RESPONSE_HDR_RESULT_OFS];
674 uint16_t external_port =
678 if (result_code != PCP_RESULT_SUCCESS) {
679 if (result_code == PCP_RESULT_NOT_AUTHORIZED) {
680 static std::atomic<bool> warned{
false};
681 if (!warned.exchange(
true)) {
682 LogWarning(
"pcp: Mapping failed with result %s\n",
683 PCPResultString(result_code));
686 PCPResultString(result_code));
689 LogWarning(
"pcp: Mapping failed with result %s\n",
690 PCPResultString(result_code));
692 if (result_code == PCP_RESULT_NO_RESOURCES) {
699 CService(external_addr, external_port), lifetime_ret);
705 "%s:%s -> %s (for %ds)",
version == NATPMP_VERSION ?
"natpmp" :
"pcp",
#define Assume(val)
Assume is the identity function.
std::string ToStringAddr() const
bool GetIn6Addr(struct in6_addr *pipv6Addr) const
Try to get our IPv6 address.
bool GetInAddr(struct in_addr *pipv4Addr) const
Try to get our IPv4 address.
A combination of a network address (CNetAddr) and a (TCP) port.
bool SetSockAddr(const struct sockaddr *paddr, socklen_t addrlen)
Set CService from a network sockaddr.
std::string ToStringAddrPort() const
A helper class for interruptible sleeps.
RAII helper class that manages a socket and closes it automatically when it goes out of scope.
virtual ssize_t Send(const void *data, size_t len, int flags) const
send(2) wrapper.
virtual int Bind(const sockaddr *addr, socklen_t addr_len) const
bind(2) wrapper.
virtual bool Wait(std::chrono::milliseconds timeout, Event requested, Event *occurred=nullptr) const
Wait for readiness for input (recv) or output (send).
virtual int GetSockName(sockaddr *name, socklen_t *name_len) const
getsockname(2) wrapper.
static constexpr Event RECV
If passed to Wait(), then it will wait for readiness to read from the socket.
virtual int Connect(const sockaddr *addr, socklen_t addr_len) const
connect(2) wrapper.
virtual ssize_t Recv(void *buf, size_t len, int flags) const
recv(2) wrapper.
A Span is an object that can refer to a contiguous sequence of objects.
constexpr std::size_t size() const noexcept
constexpr C * data() const noexcept
#define WSAGetLastError()
static void WriteBE32(uint8_t *ptr, uint32_t x)
static uint16_t ReadBE16(const uint8_t *ptr)
static uint32_t ReadBE32(const uint8_t *ptr)
static void WriteBE16(uint8_t *ptr, uint16_t x)
std::string HexStr(const Span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
#define LogPrintLevel(category, level,...)
#define LogDebug(category,...)
bool HasPrefix(const T1 &obj, const std::array< uint8_t, PREFIX_LEN > &prefix)
Check whether a container begins with the given prefix.
static constexpr size_t ADDR_IPV4_SIZE
Size of IPv4 address (in bytes).
static const std::array< uint8_t, 12 > IPV4_IN_IPV6_PREFIX
Prefix of an IPv6 address when it contains an embedded IPv4 address.
static constexpr size_t ADDR_IPV6_SIZE
Size of IPv6 address (in bytes).
std::function< std::unique_ptr< Sock >(int, int, int)> CreateSock
Socket factory.
std::variant< MappingResult, MappingError > NATPMPRequestPortMap(const CNetAddr &gateway, uint16_t port, uint32_t lifetime, CThreadInterrupt &interrupt, int num_tries, std::chrono::milliseconds timeout_per_try)
Try to open a port using RFC 6886 NAT-PMP.
std::variant< MappingResult, MappingError > PCPRequestPortMap(const PCPMappingNonce &nonce, const CNetAddr &gateway, const CNetAddr &bind, uint16_t port, uint32_t lifetime, CThreadInterrupt &interrupt, int num_tries, std::chrono::milliseconds timeout_per_try)
Try to open a port using RFC 6887 Port Control Protocol (PCP).
std::array< uint8_t, PCP_MAP_NONCE_SIZE > PCPMappingNonce
PCP mapping nonce.
constexpr size_t PCP_MAP_NONCE_SIZE
Mapping nonce size in bytes (see RFC6887 section 11.1).
@ PROTOCOL_ERROR
Any kind of protocol-level error, except unsupported version or no resources.
@ NO_RESOURCES
No resources available (port probably already mapped).
@ UNSUPP_VERSION
Unsupported protocol version.
@ NETWORK_ERROR
Any kind of network-level error.
std::string NetworkErrorString(int err)
Return readable error string for a network error code.
Span(T *, EndOrSize) -> Span< T >
Successful response to a port mapping.
CService external
External host:port.
uint32_t lifetime
Granted lifetime of binding (seconds).
CService internal
Internal host:port.
std::string ToString() const
Format mapping as string for logging.
uint8_t version
Protocol version, one of NATPMP_VERSION or PCP_VERSION.
static time_point now() noexcept
Return current system time or mocked time, if set.