mapport.cpp raw

   1  // Copyright (c) 2011-2022 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 <limenka-build-config.h> // IWYU pragma: keep
   6  
   7  #include <mapport.h>
   8  
   9  #include <clientversion.h>
  10  #include <common/netif.h>
  11  #include <common/pcp.h>
  12  #include <common/system.h>
  13  #include <logging.h>
  14  #include <net.h>
  15  #include <netaddress.h>
  16  #include <netbase.h>
  17  #include <random.h>
  18  #include <util/thread.h>
  19  #include <util/threadinterrupt.h>
  20  
  21  #ifdef USE_UPNP
  22  #include <cstddef>  // workaround missing include in miniupnpc 2.3.3
  23  #include <miniupnpc/miniupnpc.h>
  24  #include <miniupnpc/upnpcommands.h>
  25  #include <miniupnpc/upnperrors.h>
  26  // The minimum supported miniUPnPc API version is set to 17. This excludes
  27  // versions with known vulnerabilities.
  28  static_assert(MINIUPNPC_API_VERSION >= 17, "miniUPnPc API version >= 17 assumed");
  29  #endif // USE_UPNP
  30  
  31  #include <atomic>
  32  #include <cassert>
  33  #include <chrono>
  34  #include <functional>
  35  #include <string>
  36  #include <thread>
  37  
  38  static CThreadInterrupt g_mapport_interrupt;
  39  static std::thread g_mapport_thread;
  40  static std::atomic_uint g_mapport_enabled_protos{MapPortProtoFlag::NONE};
  41  static std::atomic<MapPortProtoFlag> g_mapport_current_proto{MapPortProtoFlag::NONE};
  42  
  43  using namespace std::chrono_literals;
  44  static constexpr auto PORT_MAPPING_REANNOUNCE_PERIOD{20min};
  45  static constexpr auto PORT_MAPPING_RETRY_PERIOD{5min};
  46  
  47  static bool ProcessPCP()
  48  {
  49      // The same nonce is used for all mappings, this is allowed by the spec, and simplifies keeping track of them.
  50      PCPMappingNonce pcp_nonce;
  51      GetRandBytes(pcp_nonce);
  52  
  53      bool ret = false;
  54      bool no_resources = false;
  55      const uint16_t private_port = GetListenPort();
  56      // Multiply the reannounce period by two, as we'll try to renew approximately halfway.
  57      const uint32_t requested_lifetime = std::chrono::seconds(PORT_MAPPING_REANNOUNCE_PERIOD * 2).count();
  58      uint32_t actual_lifetime = 0;
  59      std::chrono::milliseconds sleep_time;
  60  
  61      // Local functor to handle result from PCP/NATPMP mapping.
  62      auto handle_mapping = [&](std::variant<MappingResult, MappingError> &res) -> void {
  63          if (MappingResult* mapping = std::get_if<MappingResult>(&res)) {
  64              LogPrintLevel(BCLog::NET, BCLog::Level::Info, "portmap: Added mapping %s\n", mapping->ToString());
  65              AddLocal(mapping->external, LOCAL_MAPPED);
  66              ret = true;
  67              actual_lifetime = std::min(actual_lifetime, mapping->lifetime);
  68          } else if (MappingError *err = std::get_if<MappingError>(&res)) {
  69              // Detailed error will already have been logged internally in respective Portmap function.
  70              if (*err == MappingError::NO_RESOURCES) {
  71                  no_resources = true;
  72              }
  73          }
  74      };
  75  
  76      do {
  77          actual_lifetime = requested_lifetime;
  78          no_resources = false; // Set to true if there was any "no resources" error.
  79          ret = false; // Set to true if any mapping succeeds.
  80  
  81          // IPv4
  82          std::optional<CNetAddr> gateway4 = QueryDefaultGateway(NET_IPV4);
  83          if (!gateway4) {
  84              LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: Could not determine IPv4 default gateway\n");
  85          } else {
  86              LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: gateway [IPv4]: %s\n", gateway4->ToStringAddr());
  87  
  88              // Open a port mapping on whatever local address we have toward the gateway.
  89              struct in_addr inaddr_any;
  90              inaddr_any.s_addr = htonl(INADDR_ANY);
  91              auto res = PCPRequestPortMap(pcp_nonce, *gateway4, CNetAddr(inaddr_any), private_port, requested_lifetime, g_mapport_interrupt);
  92              MappingError* pcp_err = std::get_if<MappingError>(&res);
  93              if (pcp_err && *pcp_err == MappingError::UNSUPP_VERSION) {
  94                  LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: Got unsupported PCP version response, falling back to NAT-PMP\n");
  95                  res = NATPMPRequestPortMap(*gateway4, private_port, requested_lifetime, g_mapport_interrupt);
  96              }
  97              handle_mapping(res);
  98          }
  99  
 100          // IPv6
 101          std::optional<CNetAddr> gateway6 = QueryDefaultGateway(NET_IPV6);
 102          if (!gateway6) {
 103              LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: Could not determine IPv6 default gateway\n");
 104          } else {
 105              LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "portmap: gateway [IPv6]: %s\n", gateway6->ToStringAddr());
 106  
 107              // Try to open pinholes for all routable local IPv6 addresses.
 108              for (const auto &addr: GetLocalAddresses()) {
 109                  if (!addr.IsRoutable() || !addr.IsIPv6()) continue;
 110                  auto res = PCPRequestPortMap(pcp_nonce, *gateway6, addr, private_port, requested_lifetime, g_mapport_interrupt);
 111                  handle_mapping(res);
 112              }
 113          }
 114  
 115          // Log message if we got NO_RESOURCES.
 116          if (no_resources) {
 117              LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "portmap: At least one mapping failed because of a NO_RESOURCES error. This usually indicates that the port is already used on the router. If this is the only instance of limenka running on the network, this will resolve itself automatically. Otherwise, you might want to choose a different P2P port to prevent this conflict.\n");
 118          }
 119  
 120          // Sanity-check returned lifetime.
 121          if (actual_lifetime < 30) {
 122              LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "portmap: Got impossibly short mapping lifetime of %d seconds\n", actual_lifetime);
 123              return false;
 124          }
 125          // RFC6887 11.2.1 recommends that clients send their first renewal packet at a time chosen with uniform random
 126          // distribution in the range 1/2 to 5/8 of expiration time.
 127          std::chrono::seconds sleep_time_min(actual_lifetime / 2);
 128          std::chrono::seconds sleep_time_max(actual_lifetime * 5 / 8);
 129          sleep_time = sleep_time_min + FastRandomContext().randrange<std::chrono::milliseconds>(sleep_time_max - sleep_time_min);
 130      } while (ret && g_mapport_interrupt.sleep_for(sleep_time));
 131  
 132      // We don't delete the mappings when the thread is interrupted because this would add additional complexity, so
 133      // we rather just choose a fairly short expiry time.
 134  
 135      return ret;
 136  }
 137  
 138  #ifdef USE_UPNP
 139  static bool ProcessUpnp()
 140  {
 141      bool ret = false;
 142      std::string port = strprintf("%u", GetListenPort());
 143      const char * multicastif = nullptr;
 144      const char * minissdpdpath = nullptr;
 145      struct UPNPDev * devlist = nullptr;
 146      char lanaddr[64];
 147  
 148      int error = 0;
 149      devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error);
 150  
 151      struct UPNPUrls urls;
 152      struct IGDdatas data;
 153      int r;
 154  #if MINIUPNPC_API_VERSION <= 17
 155      r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
 156  #else
 157      r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr), nullptr, 0);
 158  #endif
 159      if (r == 1)
 160      {
 161          if (fDiscover) {
 162              char externalIPAddress[40];
 163              r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress);
 164              if (r != UPNPCOMMAND_SUCCESS) {
 165                  LogPrintf("UPnP: GetExternalIPAddress() returned %d\n", r);
 166              } else {
 167                  if (externalIPAddress[0]) {
 168                      std::optional<CNetAddr> resolved{LookupHost(externalIPAddress, false)};
 169                      if (resolved.has_value()) {
 170                          LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved->ToStringAddr());
 171                          AddLocal(resolved.value(), LOCAL_MAPPED);
 172                      }
 173                  } else {
 174                      LogPrintf("UPnP: GetExternalIPAddress failed.\n");
 175                  }
 176              }
 177          }
 178  
 179          std::string strDesc = CLIENT_NAME " " + FormatFullVersion();
 180  
 181          do {
 182              r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", nullptr, "0");
 183  
 184              if (r != UPNPCOMMAND_SUCCESS) {
 185                  ret = false;
 186                  LogPrintf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", port, port, lanaddr, r, strupnperror(r));
 187                  break;
 188              } else {
 189                  ret = true;
 190                  LogPrintf("UPnP Port Mapping successful.\n");
 191              }
 192          } while (g_mapport_interrupt.sleep_for(PORT_MAPPING_REANNOUNCE_PERIOD));
 193          g_mapport_interrupt.reset();
 194  
 195          r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", nullptr);
 196          LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r);
 197          freeUPNPDevlist(devlist); devlist = nullptr;
 198          FreeUPNPUrls(&urls);
 199      } else {
 200          LogPrintf("No valid UPnP IGDs found\n");
 201          freeUPNPDevlist(devlist); devlist = nullptr;
 202          if (r != 0)
 203              FreeUPNPUrls(&urls);
 204      }
 205  
 206      return ret;
 207  }
 208  #endif // USE_UPNP
 209  
 210  static void ThreadMapPort()
 211  {
 212      bool ok;
 213      do {
 214          ok = false;
 215  
 216          // High priority protocol.
 217          if (g_mapport_enabled_protos & MapPortProtoFlag::PCP) {
 218              g_mapport_current_proto = MapPortProtoFlag::PCP;
 219              ok = ProcessPCP();
 220              if (ok) continue;
 221          }
 222  
 223  #ifdef USE_UPNP
 224          // Low priority protocol.
 225          if (g_mapport_enabled_protos & MapPortProtoFlag::UPNP) {
 226              g_mapport_current_proto = MapPortProtoFlag::UPNP;
 227              ok = ProcessUpnp();
 228              if (ok) continue;
 229          }
 230  #endif // USE_UPNP
 231  
 232          g_mapport_current_proto = MapPortProtoFlag::NONE;
 233          if (g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
 234              return;
 235          }
 236  
 237      } while (ok || g_mapport_interrupt.sleep_for(PORT_MAPPING_RETRY_PERIOD));
 238  }
 239  
 240  void StartThreadMapPort()
 241  {
 242      if (!g_mapport_thread.joinable()) {
 243          assert(!g_mapport_interrupt);
 244          g_mapport_thread = std::thread(&util::TraceThread, "mapport", &ThreadMapPort);
 245      }
 246  }
 247  
 248  static void DispatchMapPort()
 249  {
 250      if (g_mapport_current_proto == MapPortProtoFlag::NONE && g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
 251          return;
 252      }
 253  
 254      if (g_mapport_current_proto == MapPortProtoFlag::NONE && g_mapport_enabled_protos != MapPortProtoFlag::NONE) {
 255          StartThreadMapPort();
 256          return;
 257      }
 258  
 259      if (g_mapport_current_proto != MapPortProtoFlag::NONE && g_mapport_enabled_protos == MapPortProtoFlag::NONE) {
 260          InterruptMapPort();
 261          StopMapPort();
 262          return;
 263      }
 264  
 265      if (g_mapport_enabled_protos & g_mapport_current_proto) {
 266          // Enabling another protocol does not cause switching from the currently used one.
 267          return;
 268      }
 269  
 270      assert(g_mapport_thread.joinable());
 271      assert(!g_mapport_interrupt);
 272      // Interrupt a protocol-specific loop in the ThreadUpnp() or in the ThreadPCP()
 273      // to force trying the next protocol in the ThreadMapPort() loop.
 274      g_mapport_interrupt();
 275  }
 276  
 277  static void MapPortProtoSetEnabled(MapPortProtoFlag proto, bool enabled)
 278  {
 279      if (enabled) {
 280          g_mapport_enabled_protos |= proto;
 281      } else {
 282          g_mapport_enabled_protos &= ~proto;
 283      }
 284  }
 285  
 286  bool MapPortIsProtoEnabled(const MapPortProtoFlag proto)
 287  {
 288      return g_mapport_enabled_protos & proto;
 289  }
 290  
 291  void StartMapPort(bool use_upnp, bool use_pcp)
 292  {
 293      MapPortProtoSetEnabled(MapPortProtoFlag::UPNP, use_upnp);
 294      MapPortProtoSetEnabled(MapPortProtoFlag::PCP, use_pcp);
 295      DispatchMapPort();
 296  }
 297  
 298  void InterruptMapPort()
 299  {
 300      g_mapport_enabled_protos = MapPortProtoFlag::NONE;
 301      if (g_mapport_thread.joinable()) {
 302          g_mapport_interrupt();
 303      }
 304  }
 305  
 306  void StopMapPort()
 307  {
 308      if (g_mapport_thread.joinable()) {
 309          g_mapport_thread.join();
 310          g_mapport_interrupt.reset();
 311      }
 312  }
 313