UPnP.cpp 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. // Copyright 2017 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #ifdef USE_UPNP
  4. #include "Common/UPnP.h"
  5. #include "Common/Logging/Log.h"
  6. #include <array>
  7. #include <cstdlib>
  8. #include <cstring>
  9. #include <miniupnpc.h>
  10. #include <miniwget.h>
  11. #include <string>
  12. #include <thread>
  13. #include <upnpcommands.h>
  14. #include <upnperrors.h>
  15. #include <vector>
  16. static UPNPUrls s_urls;
  17. static IGDdatas s_data;
  18. static std::array<char, 20> s_our_ip;
  19. static u16 s_mapped = 0;
  20. static std::thread s_thread;
  21. // called from ---UPnP--- thread
  22. // discovers the IGD
  23. static bool InitUPnP()
  24. {
  25. static bool s_inited = false;
  26. static bool s_error = false;
  27. // Don't init if already inited
  28. if (s_inited)
  29. return true;
  30. // Don't init if it failed before
  31. if (s_error)
  32. return false;
  33. s_urls = {};
  34. s_data = {};
  35. // Find all UPnP devices
  36. int upnperror = 0;
  37. std::unique_ptr<UPNPDev, decltype(&freeUPNPDevlist)> devlist(nullptr, freeUPNPDevlist);
  38. #if MINIUPNPC_API_VERSION >= 14
  39. devlist.reset(upnpDiscover(2000, nullptr, nullptr, 0, 0, 2, &upnperror));
  40. #else
  41. devlist.reset(upnpDiscover(2000, nullptr, nullptr, 0, 0, &upnperror));
  42. #endif
  43. if (!devlist)
  44. {
  45. if (upnperror == UPNPDISCOVER_SUCCESS)
  46. {
  47. WARN_LOG_FMT(NETPLAY, "No UPnP devices could be found.");
  48. }
  49. else
  50. {
  51. WARN_LOG_FMT(NETPLAY, "An error occurred trying to discover UPnP devices: {}",
  52. strupnperror(upnperror));
  53. }
  54. s_error = true;
  55. return false;
  56. }
  57. // Look for the IGD
  58. bool found_valid_igd = false;
  59. for (UPNPDev* dev = devlist.get(); dev; dev = dev->pNext)
  60. {
  61. if (!std::strstr(dev->st, "InternetGatewayDevice"))
  62. continue;
  63. int desc_xml_size = 0;
  64. std::unique_ptr<char, decltype(&std::free)> desc_xml(nullptr, std::free);
  65. int statusCode = 200;
  66. #if MINIUPNPC_API_VERSION >= 16
  67. desc_xml.reset(
  68. static_cast<char*>(miniwget_getaddr(dev->descURL, &desc_xml_size, s_our_ip.data(),
  69. static_cast<int>(s_our_ip.size()), 0, &statusCode)));
  70. #else
  71. desc_xml.reset(static_cast<char*>(miniwget_getaddr(
  72. dev->descURL, &desc_xml_size, s_our_ip.data(), static_cast<int>(s_our_ip.size()), 0)));
  73. #endif
  74. if (desc_xml && statusCode == 200)
  75. {
  76. parserootdesc(desc_xml.get(), desc_xml_size, &s_data);
  77. GetUPNPUrls(&s_urls, &s_data, dev->descURL, 0);
  78. found_valid_igd = true;
  79. NOTICE_LOG_FMT(NETPLAY, "Got info from IGD at {}.", dev->descURL);
  80. break;
  81. }
  82. else
  83. {
  84. WARN_LOG_FMT(NETPLAY, "Error getting info from IGD at {}.", dev->descURL);
  85. }
  86. }
  87. if (!found_valid_igd)
  88. WARN_LOG_FMT(NETPLAY, "Could not find a valid IGD in the discovered UPnP devices.");
  89. s_inited = true;
  90. return true;
  91. }
  92. // called from ---UPnP--- thread
  93. // Attempt to stop portforwarding.
  94. // --
  95. // NOTE: It is important that this happens! A few very crappy routers
  96. // apparently do not delete UPnP mappings on their own, so if you leave them
  97. // hanging, the NVRAM will fill with portmappings, and eventually all UPnP
  98. // requests will fail silently, with the only recourse being a factory reset.
  99. // --
  100. static bool UnmapPort(const u16 port)
  101. {
  102. std::string port_str = std::to_string(port);
  103. UPNP_DeletePortMapping(s_urls.controlURL, s_data.first.servicetype, port_str.c_str(), "UDP",
  104. nullptr);
  105. return true;
  106. }
  107. // called from ---UPnP--- thread
  108. // Attempt to portforward!
  109. static bool MapPort(const char* addr, const u16 port)
  110. {
  111. if (s_mapped > 0)
  112. UnmapPort(s_mapped);
  113. std::string port_str = std::to_string(port);
  114. int result = UPNP_AddPortMapping(
  115. s_urls.controlURL, s_data.first.servicetype, port_str.c_str(), port_str.c_str(), addr,
  116. (std::string("dolphin-emu UDP on ") + addr).c_str(), "UDP", nullptr, nullptr);
  117. if (result != 0)
  118. return false;
  119. s_mapped = port;
  120. return true;
  121. }
  122. // UPnP thread: try to map a port
  123. static void MapPortThread(const u16 port)
  124. {
  125. if (InitUPnP() && MapPort(s_our_ip.data(), port))
  126. {
  127. NOTICE_LOG_FMT(NETPLAY, "Successfully mapped port {} to {}.", port, s_our_ip.data());
  128. return;
  129. }
  130. WARN_LOG_FMT(NETPLAY, "Failed to map port {} to {}.", port, s_our_ip.data());
  131. }
  132. // UPnP thread: try to unmap a port
  133. static void UnmapPortThread()
  134. {
  135. if (s_mapped > 0)
  136. UnmapPort(s_mapped);
  137. }
  138. void Common::UPnP::TryPortmapping(u16 port)
  139. {
  140. if (s_thread.joinable())
  141. s_thread.join();
  142. s_thread = std::thread(&MapPortThread, port);
  143. }
  144. void Common::UPnP::StopPortmapping()
  145. {
  146. if (s_thread.joinable())
  147. s_thread.join();
  148. s_thread = std::thread(&UnmapPortThread);
  149. s_thread.join();
  150. }
  151. #endif