pcp_tests.cpp raw

   1  // Copyright (c) 2024-present The Bitcoin Core developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <common/pcp.h>
   6  #include <netbase.h>
   7  #include <test/util/logging.h>
   8  #include <test/util/common.h>
   9  #include <test/util/setup_common.h>
  10  #include <util/time.h>
  11  
  12  #include <boost/test/unit_test.hpp>
  13  
  14  #include <algorithm>
  15  #include <deque>
  16  
  17  using namespace std::literals;
  18  
  19  static CThreadInterrupt g_interrupt;
  20  
  21  /// UDP test server operation.
  22  struct TestOp {
  23      std::chrono::milliseconds delay;
  24      enum Op {
  25          SEND, // Expect send (with optional data)
  26          RECV, // Expect receive (with data)
  27          NOP,  // Just delay
  28      } op;
  29      std::vector<uint8_t> data;
  30  
  31      //! Injected error.
  32      //! Set this field to a non-zero value to return the errno code from send or receive operation.
  33      int error;
  34  
  35      TestOp(std::chrono::milliseconds delay_in, Op op_in, const std::vector<uint8_t> &data_in, int error_in):
  36          delay(delay_in), op(op_in), data(data_in), error(error_in) {}
  37  };
  38  
  39  /// Save the value of CreateSock and restore when the test ends.
  40  class PCPTestingSetup : public BasicTestingSetup
  41  {
  42  public:
  43      explicit PCPTestingSetup(const ChainType chainType = ChainType::MAIN,
  44                               TestOpts opts = {})
  45          : BasicTestingSetup{chainType, opts},
  46            m_create_sock_orig{CreateSock}
  47      {
  48          const std::optional<CService> local_ipv4{Lookup("192.168.0.6", 1, false)};
  49          const std::optional<CService> local_ipv6{Lookup("2a10:1234:5678:9abc:def0:1234:5678:9abc", 1, false)};
  50          const std::optional<CService> gateway_ipv4{Lookup("192.168.0.1", 1, false)};
  51          const std::optional<CService> gateway_ipv6{Lookup("2a10:1234:5678:9abc:def0:0000:0000:0000", 1, false)};
  52          BOOST_REQUIRE(local_ipv4 && local_ipv6 && gateway_ipv4 && gateway_ipv6);
  53          default_local_ipv4 = *local_ipv4;
  54          default_local_ipv6 = *local_ipv6;
  55          default_gateway_ipv4 = *gateway_ipv4;
  56          default_gateway_ipv6 = *gateway_ipv6;
  57  
  58          struct in_addr inaddr_any;
  59          inaddr_any.s_addr = htonl(INADDR_ANY);
  60          bind_any_ipv4 = CNetAddr(inaddr_any);
  61      }
  62  
  63      ~PCPTestingSetup()
  64      {
  65          CreateSock = m_create_sock_orig;
  66          MockableSteadyClock::ClearMockTime();
  67      }
  68  
  69      // Default testing nonce.
  70      static constexpr PCPMappingNonce TEST_NONCE{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc};
  71      // Default network addresses.
  72      CNetAddr default_local_ipv4;
  73      CNetAddr default_local_ipv6;
  74      CNetAddr default_gateway_ipv4;
  75      CNetAddr default_gateway_ipv6;
  76      // IPv4 bind
  77      CNetAddr bind_any_ipv4;
  78  private:
  79      const decltype(CreateSock) m_create_sock_orig;
  80  };
  81  
  82  /** Simple scripted UDP server emulation for testing.
  83   */
  84  class PCPTestSock final : public Sock
  85  {
  86  public:
  87      // Note: we awkwardly mark all methods as const, and properties as mutable,
  88      // because Sock expects all networking calls to be const.
  89      explicit PCPTestSock(const CNetAddr &local_ip, const CNetAddr &gateway_ip, const std::vector<TestOp> &script)
  90          : Sock{INVALID_SOCKET},
  91            m_script(script),
  92            m_local_ip(local_ip),
  93            m_gateway_ip(gateway_ip)
  94      {
  95          PrepareOp();
  96      }
  97  
  98      PCPTestSock& operator=(Sock&& other) override
  99      {
 100          assert(false && "Move of Sock into PCPTestSock not allowed.");
 101          return *this;
 102      }
 103  
 104      ssize_t Send(const void* data, size_t len, int) const override {
 105          if (!m_connected) return -1;
 106          std::span in_pkt = std::span(static_cast<const uint8_t*>(data), len);
 107          if (AtEndOfScript() || CurOp().op != TestOp::SEND) {
 108              // Ignore sends after end of script, or sends when we expect a receive.
 109              FailScript();
 110              return len;
 111          }
 112          if (CurOp().error) return -1; // Inject failure
 113          if (CurOp().data.empty() || std::ranges::equal(CurOp().data, in_pkt)) {
 114              AdvanceOp();
 115          } else {
 116              // Wrong send, fail script
 117              FailScript();
 118          }
 119          return len;
 120      }
 121  
 122      ssize_t Recv(void* buf, size_t len, int flags) const override
 123      {
 124          if (!m_connected || AtEndOfScript() || CurOp().op != TestOp::RECV || m_time_left != 0s) {
 125              return -1;
 126          }
 127          if (CurOp().error) return -1; // Inject failure
 128          const auto &recv_pkt = CurOp().data;
 129          const size_t consume_bytes{std::min(len, recv_pkt.size())};
 130          std::memcpy(buf, recv_pkt.data(), consume_bytes);
 131          if ((flags & MSG_PEEK) == 0) {
 132              AdvanceOp();
 133          }
 134          return consume_bytes;
 135      }
 136  
 137      int Connect(const sockaddr* sa, socklen_t sa_len) const override {
 138          CService service;
 139          if (service.SetSockAddr(sa, sa_len) && service == CService(m_gateway_ip, 5351)) {
 140              if (m_bound.IsBindAny()) { // If bind-any, bind to local ip.
 141                  m_bound = CService(m_local_ip, 0);
 142              }
 143              if (m_bound.GetPort() == 0) { // If no port assigned, assign port 1.
 144                  m_bound = CService(m_bound, 1);
 145              }
 146              m_connected = true;
 147              return 0;
 148          }
 149          return -1;
 150      }
 151  
 152      int Bind(const sockaddr* sa, socklen_t sa_len) const override {
 153          CService service;
 154          if (service.SetSockAddr(sa, sa_len)) {
 155              // Can only bind to one of our local ips
 156              if (!service.IsBindAny() && service != m_local_ip) {
 157                  return -1;
 158              }
 159              m_bound = service;
 160              return 0;
 161          }
 162          return -1;
 163      }
 164  
 165      int Listen(int) const override { return -1; }
 166  
 167      std::unique_ptr<Sock> Accept(sockaddr* addr, socklen_t* addr_len) const override
 168      {
 169          return nullptr;
 170      };
 171  
 172      int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const override
 173      {
 174          return -1;
 175      }
 176  
 177      int SetSockOpt(int, int, const void*, socklen_t) const override { return 0; }
 178  
 179      int GetSockName(sockaddr* name, socklen_t* name_len) const override
 180      {
 181          // Return the address we've been bound to.
 182          return m_bound.GetSockAddr(name, name_len) ? 0 : -1;
 183      }
 184  
 185      bool SetNonBlocking() const override { return true; }
 186  
 187      bool IsSelectable() const override { return true; }
 188  
 189      bool Wait(std::chrono::milliseconds timeout,
 190                Event requested,
 191                Event* occurred = nullptr) const override
 192      {
 193          // Only handles receive events.
 194          if (AtEndOfScript() || requested != Sock::RecvEvent) {
 195              m_clock += timeout;
 196          } else {
 197              std::chrono::milliseconds delay = std::min(m_time_left, timeout);
 198              m_clock += delay;
 199              m_time_left -= delay;
 200              if (CurOp().op == TestOp::RECV && m_time_left == 0s && occurred != nullptr) {
 201                  *occurred = Sock::RecvEvent;
 202              }
 203              if (CurOp().op == TestOp::NOP) {
 204                  // This was a pure delay operation, move to the next op.
 205                  AdvanceOp();
 206              }
 207          }
 208          return true;
 209      }
 210  
 211      bool WaitMany(std::chrono::milliseconds timeout, EventsPerSock& events_per_sock) const override
 212      {
 213          return false;
 214      }
 215  
 216      bool IsConnected(std::string&) const override
 217      {
 218          return true;
 219      }
 220  
 221  private:
 222      const std::vector<TestOp> m_script;
 223      mutable size_t m_script_ptr = 0;
 224      mutable std::chrono::milliseconds m_time_left;
 225      mutable FakeSteadyClock m_clock{};
 226      mutable bool m_connected{false};
 227      mutable CService m_bound;
 228      mutable CNetAddr m_local_ip;
 229      mutable CNetAddr m_gateway_ip;
 230  
 231      bool AtEndOfScript() const { return m_script_ptr == m_script.size(); }
 232      const TestOp &CurOp() const {
 233          BOOST_REQUIRE(m_script_ptr < m_script.size());
 234          return m_script[m_script_ptr];
 235      }
 236  
 237      void PrepareOp() const {
 238          if (AtEndOfScript()) return;
 239          m_time_left = CurOp().delay;
 240      }
 241  
 242      void AdvanceOp() const
 243      {
 244          m_script_ptr += 1;
 245          PrepareOp();
 246      }
 247  
 248      void FailScript() const { m_script_ptr = m_script.size(); }
 249  };
 250  
 251  BOOST_FIXTURE_TEST_SUITE(pcp_tests, PCPTestingSetup)
 252  
 253  // NAT-PMP IPv4 good-weather scenario.
 254  BOOST_AUTO_TEST_CASE(natpmp_ipv4)
 255  {
 256      const std::vector<TestOp> script{
 257          {
 258              0ms, TestOp::SEND,
 259              {
 260                  0x00, 0x00, // version, opcode (request external IP)
 261              }, 0
 262          },
 263          {
 264              2ms, TestOp::RECV,
 265              {
 266                  0x00, 0x80, 0x00, 0x00, // version, opcode (external IP), result code (success)
 267                  0x66, 0xfd, 0xa1, 0xee, // seconds sinds start of epoch
 268                  0x01, 0x02, 0x03, 0x04, // external IP address
 269              }, 0
 270          },
 271          {
 272              0ms, TestOp::SEND,
 273              {
 274                  0x00, 0x02, 0x00, 0x00, // version, opcode (request map TCP)
 275                  0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
 276                  0x00, 0x00, 0x03, 0xe8, // requested mapping lifetime in seconds
 277              }, 0
 278          },
 279          {
 280              2ms, TestOp::RECV,
 281              {
 282                  0x00, 0x82, 0x00, 0x00, // version, opcode (mapped TCP)
 283                  0x66, 0xfd, 0xa1, 0xee, // seconds sinds start of epoch
 284                  0x04, 0xd2, 0x04, 0xd2, // internal port, mapped external port
 285                  0x00, 0x00, 0x01, 0xf4, // mapping lifetime in seconds
 286              }, 0
 287          },
 288      };
 289      CreateSock = [this, &script](int domain, int type, int protocol) {
 290          if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
 291          return std::unique_ptr<PCPTestSock>();
 292      };
 293  
 294      auto res = NATPMPRequestPortMap(default_gateway_ipv4, 1234, 1000, g_interrupt, 1, 200ms);
 295  
 296      MappingResult* mapping = std::get_if<MappingResult>(&res);
 297      BOOST_REQUIRE(mapping);
 298      BOOST_CHECK_EQUAL(mapping->version, 0);
 299      BOOST_CHECK_EQUAL(mapping->internal.ToStringAddrPort(), "192.168.0.6:1234");
 300      BOOST_CHECK_EQUAL(mapping->external.ToStringAddrPort(), "1.2.3.4:1234");
 301      BOOST_CHECK_EQUAL(mapping->lifetime, 500);
 302  }
 303  
 304  // PCP IPv4 good-weather scenario.
 305  BOOST_AUTO_TEST_CASE(pcp_ipv4)
 306  {
 307      const std::vector<TestOp> script{
 308          {
 309              0ms, TestOp::SEND,
 310              {
 311                  0x02, 0x01, 0x00, 0x00, // version, opcode
 312                  0x00, 0x00, 0x03, 0xe8, // lifetime
 313                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x06, // internal IP
 314                  0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
 315                  0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
 316                  0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
 317                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, // suggested external IP
 318              }, 0
 319          },
 320          {
 321              250ms, TestOp::RECV, // 250ms delay before answer
 322              {
 323                  0x02, 0x81, 0x00, 0x00, // version, opcode, result success
 324                  0x00, 0x00, 0x01, 0xf4, // granted lifetime
 325                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
 326                  0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
 327                  0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
 328                  0x04, 0xd2, 0x04, 0xd2, // internal port, assigned external port
 329                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x02, 0x03, 0x04, // assigned external IP
 330              }, 0
 331          },
 332      };
 333      CreateSock = [this, &script](int domain, int type, int protocol) {
 334          if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
 335          return std::unique_ptr<PCPTestSock>();
 336      };
 337  
 338      auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv4, bind_any_ipv4, 1234, 1000, g_interrupt, 1, 1000ms);
 339  
 340      MappingResult* mapping = std::get_if<MappingResult>(&res);
 341      BOOST_REQUIRE(mapping);
 342      BOOST_CHECK_EQUAL(mapping->version, 2);
 343      BOOST_CHECK_EQUAL(mapping->internal.ToStringAddrPort(), "192.168.0.6:1234");
 344      BOOST_CHECK_EQUAL(mapping->external.ToStringAddrPort(), "1.2.3.4:1234");
 345      BOOST_CHECK_EQUAL(mapping->lifetime, 500);
 346  }
 347  
 348  // PCP IPv6 good-weather scenario.
 349  BOOST_AUTO_TEST_CASE(pcp_ipv6)
 350  {
 351      const std::vector<TestOp> script{
 352          {
 353              0ms, TestOp::SEND,
 354              {
 355                  0x02, 0x01, 0x00, 0x00, // version, opcode
 356                  0x00, 0x00, 0x03, 0xe8, // lifetime
 357                  0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // internal IP
 358                  0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
 359                  0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
 360                  0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
 361                  0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // suggested external IP
 362              }, 0
 363          },
 364          {
 365              500ms, TestOp::RECV, // 500ms delay before answer
 366              {
 367                  0x02, 0x81, 0x00, 0x00, // version, opcode, result success
 368                  0x00, 0x00, 0x01, 0xf4, // granted lifetime
 369                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
 370                  0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
 371                  0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
 372                  0x04, 0xd2, 0x04, 0xd2, // internal port, assigned external port
 373                  0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // suggested external IP
 374              }, 0
 375          },
 376      };
 377      CreateSock = [this, &script](int domain, int type, int protocol) {
 378          if (domain == AF_INET6 && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv6, default_gateway_ipv6, script);
 379          return std::unique_ptr<PCPTestSock>();
 380      };
 381  
 382      auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv6, default_local_ipv6, 1234, 1000, g_interrupt, 1, 1000ms);
 383  
 384      MappingResult* mapping = std::get_if<MappingResult>(&res);
 385      BOOST_REQUIRE(mapping);
 386      BOOST_CHECK_EQUAL(mapping->version, 2);
 387      BOOST_CHECK_EQUAL(mapping->internal.ToStringAddrPort(), "[2a10:1234:5678:9abc:def0:1234:5678:9abc]:1234");
 388      BOOST_CHECK_EQUAL(mapping->external.ToStringAddrPort(), "[2a10:1234:5678:9abc:def0:1234:5678:9abc]:1234");
 389      BOOST_CHECK_EQUAL(mapping->lifetime, 500);
 390  }
 391  
 392  // PCP timeout.
 393  BOOST_AUTO_TEST_CASE(pcp_timeout)
 394  {
 395      const std::vector<TestOp> script{};
 396      CreateSock = [this, &script](int domain, int type, int protocol) {
 397          if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
 398          return std::unique_ptr<PCPTestSock>();
 399      };
 400  
 401      ASSERT_DEBUG_LOG("pcp: Retrying (1)");
 402      ASSERT_DEBUG_LOG("pcp: Retrying (2)");
 403      ASSERT_DEBUG_LOG("pcp: Timeout");
 404  
 405      auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv4, bind_any_ipv4, 1234, 1000, g_interrupt, 3, 2000ms);
 406  
 407      MappingError* err = std::get_if<MappingError>(&res);
 408      BOOST_REQUIRE(err);
 409      BOOST_CHECK_EQUAL(*err, MappingError::NETWORK_ERROR);
 410  }
 411  
 412  // PCP failure receiving (router sends ICMP port closed).
 413  BOOST_AUTO_TEST_CASE(pcp_connrefused)
 414  {
 415      const std::vector<TestOp> script{
 416          {
 417              0ms, TestOp::SEND,
 418              { // May send anything.
 419              }, 0
 420          },
 421          {
 422              0ms, TestOp::RECV,
 423              {
 424              }, ECONNREFUSED
 425          },
 426      };
 427      CreateSock = [this, &script](int domain, int type, int protocol) {
 428          if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
 429          return std::unique_ptr<PCPTestSock>();
 430      };
 431  
 432      ASSERT_DEBUG_LOG("pcp: Could not receive response");
 433  
 434      auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv4, bind_any_ipv4, 1234, 1000, g_interrupt, 3, 2000ms);
 435  
 436      MappingError* err = std::get_if<MappingError>(&res);
 437      BOOST_REQUIRE(err);
 438      BOOST_CHECK_EQUAL(*err, MappingError::NETWORK_ERROR);
 439  }
 440  
 441  // PCP IPv6 success after one timeout.
 442  BOOST_AUTO_TEST_CASE(pcp_ipv6_timeout_success)
 443  {
 444      const std::vector<TestOp> script{
 445          {
 446              0ms, TestOp::SEND,
 447              {
 448                  0x02, 0x01, 0x00, 0x00, // version, opcode
 449                  0x00, 0x00, 0x03, 0xe8, // lifetime
 450                  0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // internal IP
 451                  0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
 452                  0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
 453                  0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
 454                  0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // suggested external IP
 455              }, 0
 456          },
 457          {
 458              2001ms, TestOp::NOP, // Takes longer to respond than timeout of 2000ms
 459              {}, 0
 460          },
 461          {
 462              0ms, TestOp::SEND, // Repeated send (try 2)
 463              {
 464                  0x02, 0x01, 0x00, 0x00, // version, opcode
 465                  0x00, 0x00, 0x03, 0xe8, // lifetime
 466                  0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // internal IP
 467                  0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
 468                  0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
 469                  0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
 470                  0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // suggested external IP
 471              }, 0
 472          },
 473          {
 474              200ms, TestOp::RECV, // This time we're in time
 475              {
 476                  0x02, 0x81, 0x00, 0x00, // version, opcode, result success
 477                  0x00, 0x00, 0x01, 0xf4, // granted lifetime
 478                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
 479                  0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
 480                  0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
 481                  0x04, 0xd2, 0x04, 0xd2, // internal port, assigned external port
 482                  0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // suggested external IP
 483              }, 0
 484          },
 485      };
 486      CreateSock = [this, &script](int domain, int type, int protocol) {
 487          if (domain == AF_INET6 && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv6, default_gateway_ipv6, script);
 488          return std::unique_ptr<PCPTestSock>();
 489      };
 490  
 491      ASSERT_DEBUG_LOG("pcp: Retrying (1)");
 492      ASSERT_DEBUG_LOG("pcp: Timeout");
 493  
 494      auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv6, default_local_ipv6, 1234, 1000, g_interrupt, 2, 2000ms);
 495  
 496      BOOST_CHECK(std::get_if<MappingResult>(&res));
 497  }
 498  
 499  // PCP IPv4 failure (no resources).
 500  BOOST_AUTO_TEST_CASE(pcp_ipv4_fail_no_resources)
 501  {
 502      const std::vector<TestOp> script{
 503          {
 504              0ms, TestOp::SEND,
 505              {
 506                  0x02, 0x01, 0x00, 0x00, // version, opcode
 507                  0x00, 0x00, 0x03, 0xe8, // lifetime
 508                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x06, // internal IP
 509                  0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
 510                  0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
 511                  0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
 512                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, // suggested external IP
 513              }, 0
 514          },
 515          {
 516              500ms, TestOp::RECV,
 517              {
 518                  0x02, 0x81, 0x00, 0x08, // version, opcode, result 0x08: no resources
 519                  0x00, 0x00, 0x00, 0x00, // granted lifetime
 520                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
 521                  0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
 522                  0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
 523                  0x04, 0xd2, 0x00, 0x00, // internal port, assigned external port
 524                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // assigned external IP
 525              }, 0
 526          },
 527      };
 528      CreateSock = [this, &script](int domain, int type, int protocol) {
 529          if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
 530          return std::unique_ptr<PCPTestSock>();
 531      };
 532  
 533      auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv4, bind_any_ipv4, 1234, 1000, g_interrupt, 3, 1000ms);
 534  
 535      MappingError* err = std::get_if<MappingError>(&res);
 536      BOOST_REQUIRE(err);
 537      BOOST_CHECK_EQUAL(*err, MappingError::NO_RESOURCES);
 538  }
 539  
 540  // PCP IPv4 failure (test NATPMP downgrade scenario).
 541  BOOST_AUTO_TEST_CASE(pcp_ipv4_fail_unsupported_version)
 542  {
 543      const std::vector<TestOp> script{
 544          {
 545              0ms, TestOp::SEND,
 546              {
 547                  0x02, 0x01, 0x00, 0x00, // version, opcode
 548                  0x00, 0x00, 0x03, 0xe8, // lifetime
 549                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x06, // internal IP
 550                  0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
 551                  0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
 552                  0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
 553                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, // suggested external IP
 554              }, 0
 555          },
 556          {
 557              500ms, TestOp::RECV,
 558              {
 559                  0x00, 0x81, 0x00, 0x01, // version, opcode, result 0x01: unsupported version
 560                  0x00, 0x00, 0x00, 0x00,
 561              }, 0
 562          },
 563      };
 564      CreateSock = [this, &script](int domain, int type, int protocol) {
 565          if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
 566          return std::unique_ptr<PCPTestSock>();
 567      };
 568  
 569      auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv4, bind_any_ipv4, 1234, 1000, g_interrupt, 3, 1000ms);
 570  
 571      MappingError* err = std::get_if<MappingError>(&res);
 572      BOOST_REQUIRE(err);
 573      BOOST_CHECK_EQUAL(*err, MappingError::UNSUPP_VERSION);
 574  }
 575  
 576  // NAT-PMP IPv4 protocol error scenarii.
 577  BOOST_AUTO_TEST_CASE(natpmp_protocol_error)
 578  {
 579      // First scenario: non-0 result code when requesting external IP.
 580      std::vector<TestOp> script{
 581          {
 582              0ms, TestOp::SEND,
 583              {
 584                  0x00, 0x00, // version, opcode (request external IP)
 585              }, 0
 586          },
 587          {
 588              2ms, TestOp::RECV,
 589              {
 590                  0x00, 0x80, 0x00, 0x42, // version, opcode (external IP), result code (*NOT* success)
 591                  0x66, 0xfd, 0xa1, 0xee, // seconds sinds start of epoch
 592                  0x01, 0x02, 0x03, 0x04, // external IP address
 593              }, 0
 594          },
 595      };
 596      CreateSock = [this, &script](int domain, int type, int protocol) {
 597          if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
 598          return std::unique_ptr<PCPTestSock>();
 599      };
 600  
 601      auto res = NATPMPRequestPortMap(default_gateway_ipv4, 1234, 1000, g_interrupt, 1, 200ms);
 602  
 603      MappingError* err = std::get_if<MappingError>(&res);
 604      BOOST_REQUIRE(err);
 605      BOOST_CHECK_EQUAL(*err, MappingError::PROTOCOL_ERROR);
 606  
 607      // First scenario: non-0 result code when requesting port mapping.
 608      script = {
 609          {
 610              0ms, TestOp::SEND,
 611              {
 612                  0x00, 0x00, // version, opcode (request external IP)
 613              }, 0
 614          },
 615          {
 616              2ms, TestOp::RECV,
 617              {
 618                  0x00, 0x80, 0x00, 0x00, // version, opcode (external IP), result code (success)
 619                  0x66, 0xfd, 0xa1, 0xee, // seconds sinds start of epoch
 620                  0x01, 0x02, 0x03, 0x04, // external IP address
 621              }, 0
 622          },
 623          {
 624              0ms, TestOp::SEND,
 625              {
 626                  0x00, 0x02, 0x00, 0x00, // version, opcode (request map TCP)
 627                  0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
 628                  0x00, 0x00, 0x03, 0xe8, // requested mapping lifetime in seconds
 629              }, 0
 630          },
 631          {
 632              2ms, TestOp::RECV,
 633              {
 634                  0x00, 0x82, 0x00, 0x43, // version, opcode (mapped TCP)
 635                  0x66, 0xfd, 0xa1, 0xee, // seconds sinds start of epoch
 636                  0x04, 0xd2, 0x04, 0xd2, // internal port, mapped external port
 637                  0x00, 0x00, 0x01, 0xf4, // mapping lifetime in seconds
 638              }, 0
 639          },
 640      };
 641      CreateSock = [this, &script](int domain, int type, int protocol) {
 642          if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
 643          return std::unique_ptr<PCPTestSock>();
 644      };
 645  
 646      res = NATPMPRequestPortMap(default_gateway_ipv4, 1234, 1000, g_interrupt, 1, 200ms);
 647  
 648      err = std::get_if<MappingError>(&res);
 649      BOOST_REQUIRE(err);
 650      BOOST_CHECK_EQUAL(*err, MappingError::PROTOCOL_ERROR);
 651  }
 652  
 653  // PCP IPv4 protocol error scenario.
 654  BOOST_AUTO_TEST_CASE(pcp_protocol_error)
 655  {
 656      const std::vector<TestOp> script{
 657          {
 658              0ms, TestOp::SEND,
 659              {
 660                  0x02, 0x01, 0x00, 0x00, // version, opcode
 661                  0x00, 0x00, 0x03, 0xe8, // lifetime
 662                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x06, // internal IP
 663                  0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
 664                  0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
 665                  0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
 666                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, // suggested external IP
 667              }, 0
 668          },
 669          {
 670              250ms, TestOp::RECV, // 250ms delay before answer
 671              {
 672                  0x02, 0x81, 0x00, 0x42, // version, opcode, result error
 673                  0x00, 0x00, 0x01, 0xf4, // granted lifetime
 674                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
 675                  0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
 676                  0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
 677                  0x04, 0xd2, 0x04, 0xd2, // internal port, assigned external port
 678                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x02, 0x03, 0x04, // assigned external IP
 679              }, 0
 680          },
 681      };
 682      CreateSock = [this, &script](int domain, int type, int protocol) {
 683          if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
 684          return std::unique_ptr<PCPTestSock>();
 685      };
 686  
 687      auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv4, bind_any_ipv4, 1234, 1000, g_interrupt, 1, 1000ms);
 688  
 689      MappingError* err = std::get_if<MappingError>(&res);
 690      BOOST_REQUIRE(err);
 691      BOOST_CHECK_EQUAL(*err, MappingError::PROTOCOL_ERROR);
 692  }
 693  
 694  BOOST_AUTO_TEST_SUITE_END()
 695  
 696