pcp_tests.cpp raw

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