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