17#include <util/threadinterrupt.h>
39constexpr uint16_t NATPMP_SERVER_PORT = 5351;
41constexpr uint8_t NATPMP_VERSION = 0;
43constexpr uint8_t NATPMP_REQUEST = 0x00;
45constexpr uint8_t NATPMP_RESPONSE = 0x80;
47constexpr uint8_t NATPMP_OP_GETEXTERNAL = 0x00;
49constexpr uint8_t NATPMP_OP_MAP_TCP = 0x02;
51constexpr size_t NATPMP_REQUEST_HDR_SIZE = 2;
53constexpr size_t NATPMP_RESPONSE_HDR_SIZE = 8;
55constexpr size_t NATPMP_GETEXTERNAL_REQUEST_SIZE = NATPMP_REQUEST_HDR_SIZE + 0;
57constexpr size_t NATPMP_GETEXTERNAL_RESPONSE_SIZE = NATPMP_RESPONSE_HDR_SIZE + 4;
59constexpr size_t NATPMP_MAP_REQUEST_SIZE = NATPMP_REQUEST_HDR_SIZE + 10;
61constexpr size_t NATPMP_MAP_RESPONSE_SIZE = NATPMP_RESPONSE_HDR_SIZE + 8;
65constexpr size_t NATPMP_HDR_VERSION_OFS = 0;
67constexpr size_t NATPMP_HDR_OP_OFS = 1;
69constexpr size_t NATPMP_RESPONSE_HDR_RESULT_OFS = 2;
73constexpr size_t NATPMP_GETEXTERNAL_RESPONSE_IP_OFS = 8;
77constexpr size_t NATPMP_MAP_REQUEST_INTERNAL_PORT_OFS = 4;
79constexpr size_t NATPMP_MAP_REQUEST_EXTERNAL_PORT_OFS = 6;
81constexpr size_t NATPMP_MAP_REQUEST_LIFETIME_OFS = 8;
85constexpr size_t NATPMP_MAP_RESPONSE_INTERNAL_PORT_OFS = 8;
87constexpr size_t NATPMP_MAP_RESPONSE_EXTERNAL_PORT_OFS = 10;
89constexpr size_t NATPMP_MAP_RESPONSE_LIFETIME_OFS = 12;
93constexpr uint8_t NATPMP_RESULT_SUCCESS = 0;
95constexpr uint8_t NATPMP_RESULT_UNSUPP_VERSION = 1;
97constexpr uint8_t NATPMP_RESULT_NOT_AUTHORIZED = 2;
99constexpr uint8_t NATPMP_RESULT_NO_RESOURCES = 4;
102const std::map<uint16_t, std::string> NATPMP_RESULT_STR{
104 {1,
"UNSUPP_VERSION"},
105 {2,
"NOT_AUTHORIZED"},
106 {3,
"NETWORK_FAILURE"},
108 {5,
"UNSUPP_OPCODE"},
113constexpr size_t PCP_MAX_SIZE = 1100;
115constexpr uint16_t PCP_SERVER_PORT = NATPMP_SERVER_PORT;
117constexpr uint8_t PCP_VERSION = 2;
119constexpr uint8_t PCP_REQUEST = NATPMP_REQUEST;
121constexpr uint8_t PCP_RESPONSE = NATPMP_RESPONSE;
123constexpr uint8_t PCP_OP_MAP = 0x01;
125constexpr uint16_t PCP_PROTOCOL_TCP = 6;
127constexpr size_t PCP_HDR_SIZE = 24;
129constexpr size_t PCP_MAP_SIZE = 36;
133constexpr size_t PCP_HDR_VERSION_OFS = NATPMP_HDR_VERSION_OFS;
135constexpr size_t PCP_HDR_OP_OFS = NATPMP_HDR_OP_OFS;
137constexpr size_t PCP_HDR_LIFETIME_OFS = 4;
141constexpr size_t PCP_REQUEST_HDR_IP_OFS = 8;
145constexpr size_t PCP_RESPONSE_HDR_RESULT_OFS = 3;
149constexpr size_t PCP_MAP_NONCE_OFS = 0;
151constexpr size_t PCP_MAP_PROTOCOL_OFS = 12;
153constexpr size_t PCP_MAP_INTERNAL_PORT_OFS = 16;
155constexpr size_t PCP_MAP_EXTERNAL_PORT_OFS = 18;
157constexpr size_t PCP_MAP_EXTERNAL_IP_OFS = 20;
160constexpr uint8_t PCP_RESULT_SUCCESS = NATPMP_RESULT_SUCCESS;
162constexpr uint8_t PCP_RESULT_NOT_AUTHORIZED = NATPMP_RESULT_NOT_AUTHORIZED;
164constexpr uint8_t PCP_RESULT_NO_RESOURCES = 8;
167const std::map<uint8_t, std::string> PCP_RESULT_STR{
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"},
177 {9,
"UNSUPP_PROTOCOL"},
178 {10,
"USER_EX_QUOTA"},
179 {11,
"CANNOT_PROVIDE_EXTERNAL"},
180 {12,
"ADDRESS_MISMATCH"},
181 {13,
"EXCESSIVE_REMOTE_PEER"},
185std::string NATPMPResultString(uint16_t result_code)
187 auto result_i = NATPMP_RESULT_STR.find(result_code);
188 return strprintf(
"%s (code %d)", result_i == NATPMP_RESULT_STR.end() ?
"(unknown)" : result_i->second, result_code);
192std::string PCPResultString(uint8_t result_code)
194 auto result_i = PCP_RESULT_STR.find(result_code);
195 return strprintf(
"%s (code %d)", result_i == PCP_RESULT_STR.end() ?
"(unknown)" : result_i->second, result_code);
199[[nodiscard]]
bool PCPWrapAddress(std::span<uint8_t> wrapped_addr,
const CNetAddr &addr)
203 struct in_addr addr4;
204 if (!addr.
GetInAddr(&addr4))
return false;
209 }
else if (addr.
IsIPv6()) {
210 struct in6_addr addr6;
220CNetAddr PCPUnwrapAddress(std::span<const uint8_t> wrapped_addr)
224 struct in_addr addr4;
228 struct in6_addr addr6;
235std::optional<std::vector<uint8_t>> PCPSendRecv(
Sock &sock,
const std::string &protocol, std::span<const uint8_t> request,
int num_tries,
236 std::chrono::milliseconds timeout_per_try,
237 std::function<
bool(std::span<const uint8_t>)> check_packet,
240 using namespace std::chrono;
242 uint8_t response[PCP_MAX_SIZE];
243 bool got_response =
false;
245 for (
int ntry = 0; !got_response && ntry < num_tries; ++ntry) {
250 if (sock.
Send(request.data(), request.size(), 0) !=
static_cast<ssize_t
>(request.size())) {
257 auto deadline = cur_time + timeout_per_try;
259 if (interrupt)
return std::nullopt;
276 LogDebug(
BCLog::NET,
"%s: Received response of %d bytes: %s\n", protocol, recvsz,
HexStr(std::span(response, recvsz)));
278 if (check_packet(std::span<uint8_t>(response, recvsz))) {
288 return std::vector<uint8_t>(response, response + recvsz);
295 struct sockaddr_storage dest_addr;
296 socklen_t dest_addrlen =
sizeof(
struct sockaddr_storage);
305 auto sock{
CreateSock(AF_INET, SOCK_DGRAM, IPPROTO_UDP)};
312 if (sock->
Connect((
struct sockaddr*)&dest_addr, dest_addrlen) != 0) {
318 struct sockaddr_in internal;
319 socklen_t internal_addrlen =
sizeof(
struct sockaddr_in);
320 if (sock->
GetSockName((
struct sockaddr*)&internal, &internal_addrlen) != 0) {
326 std::vector<uint8_t> request(NATPMP_GETEXTERNAL_REQUEST_SIZE);
327 request[NATPMP_HDR_VERSION_OFS] = NATPMP_VERSION;
328 request[NATPMP_HDR_OP_OFS] = NATPMP_REQUEST | NATPMP_OP_GETEXTERNAL;
330 auto recv_res = PCPSendRecv(*sock,
"natpmp", request, num_tries, timeout_per_try,
331 [&](
const std::span<const uint8_t> response) ->
bool {
332 if (response.size() < NATPMP_GETEXTERNAL_RESPONSE_SIZE) {
333 LogWarning(
"natpmp: Response too small\n");
336 if (response[NATPMP_HDR_VERSION_OFS] != NATPMP_VERSION || response[NATPMP_HDR_OP_OFS] != (NATPMP_RESPONSE | NATPMP_OP_GETEXTERNAL)) {
337 LogWarning(
"natpmp: Response to wrong command\n");
344 struct in_addr external_addr;
346 const std::span<const uint8_t> response = *recv_res;
348 Assume(response.size() >= NATPMP_GETEXTERNAL_RESPONSE_SIZE);
349 uint16_t result_code =
ReadBE16(response.data() + NATPMP_RESPONSE_HDR_RESULT_OFS);
350 if (result_code != NATPMP_RESULT_SUCCESS) {
351 LogWarning(
"natpmp: Getting external address failed with result %s\n", NATPMPResultString(result_code));
355 std::memcpy(&external_addr, response.data() + NATPMP_GETEXTERNAL_RESPONSE_IP_OFS,
ADDR_IPV4_SIZE);
361 request = std::vector<uint8_t>(NATPMP_MAP_REQUEST_SIZE);
362 request[NATPMP_HDR_VERSION_OFS] = NATPMP_VERSION;
363 request[NATPMP_HDR_OP_OFS] = NATPMP_REQUEST | NATPMP_OP_MAP_TCP;
364 WriteBE16(request.data() + NATPMP_MAP_REQUEST_INTERNAL_PORT_OFS, port);
365 WriteBE16(request.data() + NATPMP_MAP_REQUEST_EXTERNAL_PORT_OFS, port);
366 WriteBE32(request.data() + NATPMP_MAP_REQUEST_LIFETIME_OFS, lifetime);
368 recv_res = PCPSendRecv(*sock,
"natpmp", request, num_tries, timeout_per_try,
369 [&](
const std::span<const uint8_t> response) ->
bool {
370 if (response.size() < NATPMP_MAP_RESPONSE_SIZE) {
371 LogWarning(
"natpmp: Response too small\n");
374 if (response[0] != NATPMP_VERSION || response[1] != (NATPMP_RESPONSE | NATPMP_OP_MAP_TCP)) {
375 LogWarning(
"natpmp: Response to wrong command\n");
378 uint16_t internal_port =
ReadBE16(response.data() + NATPMP_MAP_RESPONSE_INTERNAL_PORT_OFS);
379 if (internal_port != port) {
380 LogWarning(
"natpmp: Response port doesn't match request\n");
388 const std::span<uint8_t> response = *recv_res;
390 Assume(response.size() >= NATPMP_MAP_RESPONSE_SIZE);
391 uint16_t result_code =
ReadBE16(response.data() + NATPMP_RESPONSE_HDR_RESULT_OFS);
392 if (result_code != NATPMP_RESULT_SUCCESS) {
393 if (result_code == NATPMP_RESULT_NOT_AUTHORIZED) {
394 static std::atomic<bool> warned{
false};
395 if (!warned.exchange(
true)) {
396 LogWarning(
"natpmp: Port mapping failed with result %s\n", NATPMPResultString(result_code));
398 LogDebug(
BCLog::NET,
"natpmp: Port mapping failed with result %s\n", NATPMPResultString(result_code));
401 LogWarning(
"natpmp: Port mapping failed with result %s\n", NATPMPResultString(result_code));
403 if (result_code == NATPMP_RESULT_NO_RESOURCES) {
409 uint32_t lifetime_ret =
ReadBE32(response.data() + NATPMP_MAP_RESPONSE_LIFETIME_OFS);
410 uint16_t external_port =
ReadBE16(response.data() + NATPMP_MAP_RESPONSE_EXTERNAL_PORT_OFS);
419 struct sockaddr_storage dest_addr, bind_addr;
420 socklen_t dest_addrlen =
sizeof(
struct sockaddr_storage), bind_addrlen =
sizeof(
struct sockaddr_storage);
430 auto sock{
CreateSock(dest_addr.ss_family, SOCK_DGRAM, IPPROTO_UDP)};
438 if (sock->
Bind((
struct sockaddr*)&bind_addr, bind_addrlen) != 0) {
444 if (sock->
Connect((
struct sockaddr*)&dest_addr, dest_addrlen) != 0) {
452 struct sockaddr_storage internal_addr;
453 socklen_t internal_addrlen =
sizeof(
struct sockaddr_storage);
454 if (sock->
GetSockName((
struct sockaddr*)&internal_addr, &internal_addrlen) != 0) {
465 std::vector<uint8_t> request(PCP_HDR_SIZE + PCP_MAP_SIZE);
468 request[ofs + PCP_HDR_VERSION_OFS] = PCP_VERSION;
469 request[ofs + PCP_HDR_OP_OFS] = PCP_REQUEST | PCP_OP_MAP;
470 WriteBE32(request.data() + ofs + PCP_HDR_LIFETIME_OFS, lifetime);
479 request[ofs + PCP_MAP_PROTOCOL_OFS] = PCP_PROTOCOL_TCP;
480 WriteBE16(request.data() + ofs + PCP_MAP_INTERNAL_PORT_OFS, port);
481 WriteBE16(request.data() + ofs + PCP_MAP_EXTERNAL_PORT_OFS, port);
485 Assume(ofs == request.size());
488 bool is_natpmp =
false;
489 auto recv_res = PCPSendRecv(*sock,
"pcp", request, num_tries, timeout_per_try,
490 [&](
const std::span<const uint8_t> response) ->
bool {
492 if (response.size() == NATPMP_RESPONSE_HDR_SIZE && response[PCP_HDR_VERSION_OFS] == NATPMP_VERSION && response[PCP_RESPONSE_HDR_RESULT_OFS] == NATPMP_RESULT_UNSUPP_VERSION) {
496 if (response.size() < (PCP_HDR_SIZE + PCP_MAP_SIZE)) {
497 LogWarning(
"pcp: Response too small\n");
500 if (response[PCP_HDR_VERSION_OFS] != PCP_VERSION || response[PCP_HDR_OP_OFS] != (PCP_RESPONSE | PCP_OP_MAP)) {
501 LogWarning(
"pcp: Response to wrong command\n");
510 uint8_t protocol = response[PCP_HDR_SIZE + 12];
511 uint16_t internal_port =
ReadBE16(response.data() + PCP_HDR_SIZE + 16);
512 if (protocol != PCP_PROTOCOL_TCP || internal_port != port) {
513 LogWarning(
"pcp: Response protocol or port doesn't match request\n");
527 const std::span<const uint8_t> response = *recv_res;
530 Assume(response.size() >= (PCP_HDR_SIZE + PCP_MAP_SIZE));
531 uint8_t result_code = response[PCP_RESPONSE_HDR_RESULT_OFS];
532 uint32_t lifetime_ret =
ReadBE32(response.data() + PCP_HDR_LIFETIME_OFS);
533 uint16_t external_port =
ReadBE16(response.data() + PCP_HDR_SIZE + PCP_MAP_EXTERNAL_PORT_OFS);
534 CNetAddr external_addr{PCPUnwrapAddress(response.subspan(PCP_HDR_SIZE + PCP_MAP_EXTERNAL_IP_OFS,
ADDR_IPV6_SIZE))};
535 if (result_code != PCP_RESULT_SUCCESS) {
536 if (result_code == PCP_RESULT_NOT_AUTHORIZED) {
537 static std::atomic<bool> warned{
false};
538 if (!warned.exchange(
true)) {
539 LogWarning(
"pcp: Mapping failed with result %s\n", PCPResultString(result_code));
541 LogDebug(
BCLog::NET,
"pcp: Mapping failed with result %s\n", PCPResultString(result_code));
544 LogWarning(
"pcp: Mapping failed with result %s\n", PCPResultString(result_code));
546 if (result_code == PCP_RESULT_NO_RESOURCES) {
558 return strprintf(
"%s:%s -> %s (for %ds)",
559 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 (or CJDNS) 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.
static constexpr Event RecvEvent
If passed to Wait(), then it will wait for readiness to read from the socket.
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.
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.
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).
#define WSAGetLastError()
void WriteBE32(B *ptr, uint32_t x)
void WriteBE16(B *ptr, uint16_t x)
uint16_t ReadBE16(const B *ptr)
uint32_t ReadBE32(const B *ptr)
std::string HexStr(const std::span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
#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.
constexpr size_t ADDR_IPV4_SIZE
Size of IPv4 address (in bytes).
constexpr size_t ADDR_IPV6_SIZE
Size of IPv6 address (in bytes).
constexpr std::array< uint8_t, 12 > IPV4_IN_IPV6_PREFIX
Prefix of an IPv6 address when it contains an embedded IPv4 address.
std::function< std::unique_ptr< Sock >(int, int, int)> CreateSock
Socket factory.
std::array< uint8_t, PCP_MAP_NONCE_SIZE > PCPMappingNonce
PCP mapping nonce. Arbitrary data chosen by the client to identify a mapping.
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.
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.