19#include <util/threadinterrupt.h>
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 = NATPMP_RESPONSE_HDR_SIZE + 4;
48constexpr size_t NATPMP_MAP_REQUEST_SIZE = NATPMP_REQUEST_HDR_SIZE + 10;
50constexpr size_t NATPMP_MAP_RESPONSE_SIZE = NATPMP_RESPONSE_HDR_SIZE + 8;
54constexpr size_t NATPMP_HDR_VERSION_OFS = 0;
56constexpr size_t NATPMP_HDR_OP_OFS = 1;
58constexpr size_t NATPMP_RESPONSE_HDR_RESULT_OFS = 2;
62constexpr size_t NATPMP_GETEXTERNAL_RESPONSE_IP_OFS = 8;
66constexpr size_t NATPMP_MAP_REQUEST_INTERNAL_PORT_OFS = 4;
68constexpr size_t NATPMP_MAP_REQUEST_EXTERNAL_PORT_OFS = 6;
70constexpr size_t NATPMP_MAP_REQUEST_LIFETIME_OFS = 8;
74constexpr size_t NATPMP_MAP_RESPONSE_INTERNAL_PORT_OFS = 8;
76constexpr size_t NATPMP_MAP_RESPONSE_EXTERNAL_PORT_OFS = 10;
78constexpr size_t NATPMP_MAP_RESPONSE_LIFETIME_OFS = 12;
82constexpr uint8_t NATPMP_RESULT_SUCCESS = 0;
84constexpr uint8_t NATPMP_RESULT_UNSUPP_VERSION = 1;
86constexpr uint8_t NATPMP_RESULT_NOT_AUTHORIZED = 2;
88constexpr uint8_t NATPMP_RESULT_NO_RESOURCES = 4;
91const std::map<uint16_t, std::string> NATPMP_RESULT_STR{
93 {1,
"UNSUPP_VERSION"},
94 {2,
"NOT_AUTHORIZED"},
95 {3,
"NETWORK_FAILURE"},
102constexpr size_t PCP_MAX_SIZE = 1100;
104constexpr uint16_t PCP_SERVER_PORT = NATPMP_SERVER_PORT;
106constexpr uint8_t PCP_VERSION = 2;
108constexpr uint8_t PCP_REQUEST = NATPMP_REQUEST;
110constexpr uint8_t PCP_RESPONSE = NATPMP_RESPONSE;
112constexpr uint8_t PCP_OP_MAP = 0x01;
114constexpr uint16_t PCP_PROTOCOL_TCP = 6;
116constexpr size_t PCP_HDR_SIZE = 24;
118constexpr size_t PCP_MAP_SIZE = 36;
122constexpr size_t PCP_HDR_VERSION_OFS = NATPMP_HDR_VERSION_OFS;
124constexpr size_t PCP_HDR_OP_OFS = NATPMP_HDR_OP_OFS;
126constexpr size_t PCP_HDR_LIFETIME_OFS = 4;
130constexpr size_t PCP_REQUEST_HDR_IP_OFS = 8;
134constexpr size_t PCP_RESPONSE_HDR_RESULT_OFS = 3;
138constexpr size_t PCP_MAP_NONCE_OFS = 0;
140constexpr size_t PCP_MAP_PROTOCOL_OFS = 12;
142constexpr size_t PCP_MAP_INTERNAL_PORT_OFS = 16;
144constexpr size_t PCP_MAP_EXTERNAL_PORT_OFS = 18;
146constexpr size_t PCP_MAP_EXTERNAL_IP_OFS = 20;
149constexpr uint8_t PCP_RESULT_SUCCESS = NATPMP_RESULT_SUCCESS;
151constexpr uint8_t PCP_RESULT_NOT_AUTHORIZED = NATPMP_RESULT_NOT_AUTHORIZED;
153constexpr uint8_t PCP_RESULT_NO_RESOURCES = 8;
156const std::map<uint8_t, std::string> PCP_RESULT_STR{
158 {1,
"UNSUPP_VERSION"},
159 {2,
"NOT_AUTHORIZED"},
160 {3,
"MALFORMED_REQUEST"},
161 {4,
"UNSUPP_OPCODE"},
162 {5,
"UNSUPP_OPTION"},
163 {6,
"MALFORMED_OPTION"},
164 {7,
"NETWORK_FAILURE"},
166 {9,
"UNSUPP_PROTOCOL"},
167 {10,
"USER_EX_QUOTA"},
168 {11,
"CANNOT_PROVIDE_EXTERNAL"},
169 {12,
"ADDRESS_MISMATCH"},
170 {13,
"EXCESSIVE_REMOTE_PEER"},
174std::string NATPMPResultString(uint16_t result_code)
176 auto result_i = NATPMP_RESULT_STR.find(result_code);
177 return strprintf(
"%s (code %d)", result_i == NATPMP_RESULT_STR.end() ?
"(unknown)" : result_i->second, result_code);
181std::string PCPResultString(uint8_t result_code)
183 auto result_i = PCP_RESULT_STR.find(result_code);
184 return strprintf(
"%s (code %d)", result_i == PCP_RESULT_STR.end() ?
"(unknown)" : result_i->second, result_code);
188[[nodiscard]]
bool PCPWrapAddress(std::span<uint8_t> wrapped_addr,
const CNetAddr &addr)
192 struct in_addr addr4;
193 if (!addr.
GetInAddr(&addr4))
return false;
198 }
else if (addr.
IsIPv6()) {
199 struct in6_addr addr6;
209CNetAddr PCPUnwrapAddress(std::span<const uint8_t> wrapped_addr)
213 struct in_addr addr4;
217 struct in6_addr addr6;
224std::optional<std::vector<uint8_t>> PCPSendRecv(
Sock &sock,
const std::string &protocol, std::span<const uint8_t> request,
int num_tries,
225 std::chrono::milliseconds timeout_per_try,
226 std::function<
bool(std::span<const uint8_t>)> check_packet,
229 using namespace std::chrono;
231 uint8_t response[PCP_MAX_SIZE];
232 bool got_response =
false;
234 for (
int ntry = 0; !got_response && ntry < num_tries; ++ntry) {
239 if (sock.
Send(request.data(), request.size(), 0) !=
static_cast<ssize_t
>(request.size())) {
246 auto deadline = cur_time + timeout_per_try;
248 if (interrupt)
return std::nullopt;
265 LogDebug(
BCLog::NET,
"%s: Received response of %d bytes: %s\n", protocol, recvsz,
HexStr(std::span(response, recvsz)));
267 if (check_packet(std::span<uint8_t>(response, recvsz))) {
277 return std::vector<uint8_t>(response, response + recvsz);
284 struct sockaddr_storage dest_addr;
285 socklen_t dest_addrlen =
sizeof(
struct sockaddr_storage);
294 auto sock{
CreateSock(AF_INET, SOCK_DGRAM, IPPROTO_UDP)};
301 if (sock->
Connect((
struct sockaddr*)&dest_addr, dest_addrlen) != 0) {
307 struct sockaddr_in internal;
308 socklen_t internal_addrlen =
sizeof(
struct sockaddr_in);
309 if (sock->
GetSockName((
struct sockaddr*)&internal, &internal_addrlen) != 0) {
315 std::vector<uint8_t> request(NATPMP_GETEXTERNAL_REQUEST_SIZE);
316 request[NATPMP_HDR_VERSION_OFS] = NATPMP_VERSION;
317 request[NATPMP_HDR_OP_OFS] = NATPMP_REQUEST | NATPMP_OP_GETEXTERNAL;
319 auto recv_res = PCPSendRecv(*sock,
"natpmp", request, num_tries, timeout_per_try,
320 [&](
const std::span<const uint8_t> response) ->
bool {
321 if (response.size() < NATPMP_GETEXTERNAL_RESPONSE_SIZE) {
322 LogWarning(
"natpmp: Response too small\n");
325 if (response[NATPMP_HDR_VERSION_OFS] != NATPMP_VERSION || response[NATPMP_HDR_OP_OFS] != (NATPMP_RESPONSE | NATPMP_OP_GETEXTERNAL)) {
326 LogWarning(
"natpmp: Response to wrong command\n");
333 struct in_addr external_addr;
335 const std::span<const uint8_t> response = *recv_res;
337 Assume(response.size() >= NATPMP_GETEXTERNAL_RESPONSE_SIZE);
338 uint16_t result_code =
ReadBE16(response.data() + NATPMP_RESPONSE_HDR_RESULT_OFS);
339 if (result_code != NATPMP_RESULT_SUCCESS) {
340 LogWarning(
"natpmp: Getting external address failed with result %s\n", NATPMPResultString(result_code));
344 std::memcpy(&external_addr, response.data() + NATPMP_GETEXTERNAL_RESPONSE_IP_OFS,
ADDR_IPV4_SIZE);
350 request = std::vector<uint8_t>(NATPMP_MAP_REQUEST_SIZE);
351 request[NATPMP_HDR_VERSION_OFS] = NATPMP_VERSION;
352 request[NATPMP_HDR_OP_OFS] = NATPMP_REQUEST | NATPMP_OP_MAP_TCP;
353 WriteBE16(request.data() + NATPMP_MAP_REQUEST_INTERNAL_PORT_OFS, port);
354 WriteBE16(request.data() + NATPMP_MAP_REQUEST_EXTERNAL_PORT_OFS, port);
355 WriteBE32(request.data() + NATPMP_MAP_REQUEST_LIFETIME_OFS, lifetime);
357 recv_res = PCPSendRecv(*sock,
"natpmp", request, num_tries, timeout_per_try,
358 [&](
const std::span<const uint8_t> response) ->
bool {
359 if (response.size() < NATPMP_MAP_RESPONSE_SIZE) {
360 LogWarning(
"natpmp: Response too small\n");
363 if (response[0] != NATPMP_VERSION || response[1] != (NATPMP_RESPONSE | NATPMP_OP_MAP_TCP)) {
364 LogWarning(
"natpmp: Response to wrong command\n");
367 uint16_t internal_port =
ReadBE16(response.data() + NATPMP_MAP_RESPONSE_INTERNAL_PORT_OFS);
368 if (internal_port != port) {
369 LogWarning(
"natpmp: Response port doesn't match request\n");
377 const std::span<uint8_t> response = *recv_res;
379 Assume(response.size() >= NATPMP_MAP_RESPONSE_SIZE);
380 uint16_t result_code =
ReadBE16(response.data() + NATPMP_RESPONSE_HDR_RESULT_OFS);
381 if (result_code != NATPMP_RESULT_SUCCESS) {
382 if (result_code == NATPMP_RESULT_NOT_AUTHORIZED) {
383 static std::atomic<bool> warned{
false};
384 if (!warned.exchange(
true)) {
385 LogWarning(
"natpmp: Port mapping failed with result %s\n", NATPMPResultString(result_code));
387 LogDebug(
BCLog::NET,
"natpmp: Port mapping failed with result %s\n", NATPMPResultString(result_code));
390 LogWarning(
"natpmp: Port mapping failed with result %s\n", NATPMPResultString(result_code));
392 if (result_code == NATPMP_RESULT_NO_RESOURCES) {
398 uint32_t lifetime_ret =
ReadBE32(response.data() + NATPMP_MAP_RESPONSE_LIFETIME_OFS);
399 uint16_t external_port =
ReadBE16(response.data() + NATPMP_MAP_RESPONSE_EXTERNAL_PORT_OFS);
408 struct sockaddr_storage dest_addr, bind_addr;
409 socklen_t dest_addrlen =
sizeof(
struct sockaddr_storage), bind_addrlen =
sizeof(
struct sockaddr_storage);
419 auto sock{
CreateSock(dest_addr.ss_family, SOCK_DGRAM, IPPROTO_UDP)};
427 if (sock->
Bind((
struct sockaddr*)&bind_addr, bind_addrlen) != 0) {
433 if (sock->
Connect((
struct sockaddr*)&dest_addr, dest_addrlen) != 0) {
441 struct sockaddr_storage internal_addr;
442 socklen_t internal_addrlen =
sizeof(
struct sockaddr_storage);
443 if (sock->
GetSockName((
struct sockaddr*)&internal_addr, &internal_addrlen) != 0) {
454 std::vector<uint8_t> request(PCP_HDR_SIZE + PCP_MAP_SIZE);
457 request[ofs + PCP_HDR_VERSION_OFS] = PCP_VERSION;
458 request[ofs + PCP_HDR_OP_OFS] = PCP_REQUEST | PCP_OP_MAP;
459 WriteBE32(request.data() + ofs + PCP_HDR_LIFETIME_OFS, lifetime);
468 request[ofs + PCP_MAP_PROTOCOL_OFS] = PCP_PROTOCOL_TCP;
469 WriteBE16(request.data() + ofs + PCP_MAP_INTERNAL_PORT_OFS, port);
470 WriteBE16(request.data() + ofs + PCP_MAP_EXTERNAL_PORT_OFS, port);
474 Assume(ofs == request.size());
477 bool is_natpmp =
false;
478 auto recv_res = PCPSendRecv(*sock,
"pcp", request, num_tries, timeout_per_try,
479 [&](
const std::span<const uint8_t> response) ->
bool {
481 if (response.size() == NATPMP_RESPONSE_HDR_SIZE && response[PCP_HDR_VERSION_OFS] == NATPMP_VERSION && response[PCP_RESPONSE_HDR_RESULT_OFS] == NATPMP_RESULT_UNSUPP_VERSION) {
485 if (response.size() < (PCP_HDR_SIZE + PCP_MAP_SIZE)) {
486 LogWarning(
"pcp: Response too small\n");
489 if (response[PCP_HDR_VERSION_OFS] != PCP_VERSION || response[PCP_HDR_OP_OFS] != (PCP_RESPONSE | PCP_OP_MAP)) {
490 LogWarning(
"pcp: Response to wrong command\n");
499 uint8_t protocol = response[PCP_HDR_SIZE + 12];
500 uint16_t internal_port =
ReadBE16(response.data() + PCP_HDR_SIZE + 16);
501 if (protocol != PCP_PROTOCOL_TCP || internal_port != port) {
502 LogWarning(
"pcp: Response protocol or port doesn't match request\n");
516 const std::span<const uint8_t> response = *recv_res;
519 Assume(response.size() >= (PCP_HDR_SIZE + PCP_MAP_SIZE));
520 uint8_t result_code = response[PCP_RESPONSE_HDR_RESULT_OFS];
521 uint32_t lifetime_ret =
ReadBE32(response.data() + PCP_HDR_LIFETIME_OFS);
522 uint16_t external_port =
ReadBE16(response.data() + PCP_HDR_SIZE + PCP_MAP_EXTERNAL_PORT_OFS);
523 CNetAddr external_addr{PCPUnwrapAddress(response.subspan(PCP_HDR_SIZE + PCP_MAP_EXTERNAL_IP_OFS,
ADDR_IPV6_SIZE))};
524 if (result_code != PCP_RESULT_SUCCESS) {
525 if (result_code == PCP_RESULT_NOT_AUTHORIZED) {
526 static std::atomic<bool> warned{
false};
527 if (!warned.exchange(
true)) {
528 LogWarning(
"pcp: Mapping failed with result %s\n", PCPResultString(result_code));
530 LogDebug(
BCLog::NET,
"pcp: Mapping failed with result %s\n", PCPResultString(result_code));
533 LogWarning(
"pcp: Mapping failed with result %s\n", PCPResultString(result_code));
535 if (result_code == PCP_RESULT_NO_RESOURCES) {
547 return strprintf(
"%s:%s -> %s (for %ds)",
548 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.
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::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.