/sunshine/upnp.cpp

https://github.com/loki-47-6F-64/sunshine · C++ · 184 lines · 146 code · 37 blank · 1 comment · 19 complexity · 4ee83f213fde34b0415ac747025ba627 MD5 · raw file

  1. #include <miniupnpc/miniupnpc.h>
  2. #include <miniupnpc/upnpcommands.h>
  3. #include "config.h"
  4. #include "confighttp.h"
  5. #include "main.h"
  6. #include "network.h"
  7. #include "nvhttp.h"
  8. #include "rtsp.h"
  9. #include "stream.h"
  10. #include "upnp.h"
  11. #include "utility.h"
  12. using namespace std::literals;
  13. namespace upnp {
  14. constexpr auto INET6_ADDRESS_STRLEN = 46;
  15. constexpr auto IPv4 = 0;
  16. constexpr auto IPv6 = 1;
  17. using device_t = util::safe_ptr<UPNPDev, freeUPNPDevlist>;
  18. KITTY_USING_MOVE_T(urls_t, UPNPUrls, , {
  19. FreeUPNPUrls(&el);
  20. });
  21. struct mapping_t {
  22. struct {
  23. std::string wan;
  24. std::string lan;
  25. } port;
  26. std::string description;
  27. bool tcp;
  28. };
  29. void unmap(
  30. const urls_t &urls,
  31. const IGDdatas &data,
  32. std::vector<mapping_t>::const_reverse_iterator begin,
  33. std::vector<mapping_t>::const_reverse_iterator end) {
  34. BOOST_LOG(debug) << "Unmapping UPNP ports"sv;
  35. for(auto it = begin; it != end; ++it) {
  36. auto status = UPNP_DeletePortMapping(
  37. urls->controlURL,
  38. data.first.servicetype,
  39. it->port.wan.c_str(),
  40. it->tcp ? "TCP" : "UDP",
  41. nullptr);
  42. if(status) {
  43. BOOST_LOG(warning) << "Failed to unmap port ["sv << it->port.wan << "] to port ["sv << it->port.lan << "]: error code ["sv << status << ']';
  44. break;
  45. }
  46. }
  47. }
  48. class deinit_t : public platf::deinit_t {
  49. public:
  50. using iter_t = std::vector<mapping_t>::const_reverse_iterator;
  51. deinit_t(urls_t &&urls, IGDdatas data, std::vector<mapping_t> &&mapping)
  52. : urls { std::move(urls) }, data { data }, mapping { std::move(mapping) } {}
  53. ~deinit_t() {
  54. BOOST_LOG(info) << "Unmapping UPNP ports..."sv;
  55. unmap(urls, data, std::rbegin(mapping), std::rend(mapping));
  56. }
  57. urls_t urls;
  58. IGDdatas data;
  59. std::vector<mapping_t> mapping;
  60. };
  61. static std::string_view status_string(int status) {
  62. switch(status) {
  63. case 0:
  64. return "No IGD device found"sv;
  65. case 1:
  66. return "Valid IGD device found"sv;
  67. case 2:
  68. return "A UPnP device has been found, but it wasn't recognized as an IGD"sv;
  69. }
  70. return "Unknown status"sv;
  71. }
  72. std::unique_ptr<platf::deinit_t> start() {
  73. int err {};
  74. device_t device { upnpDiscover(2000, nullptr, nullptr, 0, IPv4, 2, &err) };
  75. if(!device || err) {
  76. BOOST_LOG(error) << "Couldn't discover any UPNP devices"sv;
  77. return nullptr;
  78. }
  79. for(auto dev = device.get(); dev != nullptr; dev = dev->pNext) {
  80. BOOST_LOG(debug) << "Found device: "sv << dev->descURL;
  81. }
  82. std::array<char, INET6_ADDRESS_STRLEN> lan_addr;
  83. std::array<char, INET6_ADDRESS_STRLEN> wan_addr;
  84. urls_t urls;
  85. IGDdatas data;
  86. auto status = UPNP_GetValidIGD(device.get(), &urls.el, &data, lan_addr.data(), lan_addr.size());
  87. if(status != 1) {
  88. BOOST_LOG(error) << status_string(status);
  89. return nullptr;
  90. }
  91. BOOST_LOG(debug) << "Found valid IGD device: "sv << urls->rootdescURL;
  92. if(UPNP_GetExternalIPAddress(urls->controlURL, data.first.servicetype, wan_addr.data())) {
  93. BOOST_LOG(warning) << "Could not get external ip"sv;
  94. }
  95. else {
  96. BOOST_LOG(debug) << "Found external ip: "sv << wan_addr.data();
  97. if(config::nvhttp.external_ip.empty()) {
  98. config::nvhttp.external_ip = wan_addr.data();
  99. }
  100. }
  101. if(!config::sunshine.flags[config::flag::UPNP]) {
  102. return nullptr;
  103. }
  104. auto rtsp = std::to_string(map_port(stream::RTSP_SETUP_PORT));
  105. auto video = std::to_string(map_port(stream::VIDEO_STREAM_PORT));
  106. auto audio = std::to_string(map_port(stream::AUDIO_STREAM_PORT));
  107. auto control = std::to_string(map_port(stream::CONTROL_PORT));
  108. auto gs_http = std::to_string(map_port(nvhttp::PORT_HTTP));
  109. auto gs_https = std::to_string(map_port(nvhttp::PORT_HTTPS));
  110. auto wm_http = std::to_string(map_port(confighttp::PORT_HTTPS));
  111. std::vector<mapping_t> mappings {
  112. { rtsp, rtsp, "RTSP setup port"s, true },
  113. { video, video, "Video stream port"s, false },
  114. { audio, audio, "Control stream port"s, false },
  115. { control, control, "Audio stream port"s, false },
  116. { gs_http, gs_http, "Gamestream http port"s, true },
  117. { gs_https, gs_https, "Gamestream https port"s, true },
  118. };
  119. // Only map port for the Web Manager if it is configured to accept connection from WAN
  120. if(net::from_enum_string(config::nvhttp.origin_web_ui_allowed) > net::LAN) {
  121. mappings.emplace_back(mapping_t { wm_http, wm_http, "Sunshine Web UI port"s, true });
  122. }
  123. auto it = std::begin(mappings);
  124. status = 0;
  125. for(; it != std::end(mappings); ++it) {
  126. status = UPNP_AddPortMapping(
  127. urls->controlURL,
  128. data.first.servicetype,
  129. it->port.wan.c_str(),
  130. it->port.lan.c_str(),
  131. lan_addr.data(),
  132. it->description.c_str(),
  133. it->tcp ? "TCP" : "UDP",
  134. nullptr,
  135. "86400");
  136. if(status) {
  137. BOOST_LOG(error) << "Failed to map port ["sv << it->port.wan << "] to port ["sv << it->port.lan << "]: error code ["sv << status << ']';
  138. break;
  139. }
  140. }
  141. if(status) {
  142. unmap(urls, data, std::make_reverse_iterator(it), std::rend(mappings));
  143. return nullptr;
  144. }
  145. return std::make_unique<deinit_t>(std::move(urls), data, std::move(mappings));
  146. }
  147. } // namespace upnp