123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- // Copyright 2017 Dolphin Emulator Project
- // SPDX-License-Identifier: GPL-2.0-or-later
- #ifdef USE_UPNP
- #include "Common/UPnP.h"
- #include "Common/Logging/Log.h"
- #include <array>
- #include <cstdlib>
- #include <cstring>
- #include <miniupnpc.h>
- #include <miniwget.h>
- #include <string>
- #include <thread>
- #include <upnpcommands.h>
- #include <upnperrors.h>
- #include <vector>
- static UPNPUrls s_urls;
- static IGDdatas s_data;
- static std::array<char, 20> s_our_ip;
- static u16 s_mapped = 0;
- static std::thread s_thread;
- // called from ---UPnP--- thread
- // discovers the IGD
- static bool InitUPnP()
- {
- static bool s_inited = false;
- static bool s_error = false;
- // Don't init if already inited
- if (s_inited)
- return true;
- // Don't init if it failed before
- if (s_error)
- return false;
- s_urls = {};
- s_data = {};
- // Find all UPnP devices
- int upnperror = 0;
- std::unique_ptr<UPNPDev, decltype(&freeUPNPDevlist)> devlist(nullptr, freeUPNPDevlist);
- #if MINIUPNPC_API_VERSION >= 14
- devlist.reset(upnpDiscover(2000, nullptr, nullptr, 0, 0, 2, &upnperror));
- #else
- devlist.reset(upnpDiscover(2000, nullptr, nullptr, 0, 0, &upnperror));
- #endif
- if (!devlist)
- {
- if (upnperror == UPNPDISCOVER_SUCCESS)
- {
- WARN_LOG_FMT(NETPLAY, "No UPnP devices could be found.");
- }
- else
- {
- WARN_LOG_FMT(NETPLAY, "An error occurred trying to discover UPnP devices: {}",
- strupnperror(upnperror));
- }
- s_error = true;
- return false;
- }
- // Look for the IGD
- bool found_valid_igd = false;
- for (UPNPDev* dev = devlist.get(); dev; dev = dev->pNext)
- {
- if (!std::strstr(dev->st, "InternetGatewayDevice"))
- continue;
- int desc_xml_size = 0;
- std::unique_ptr<char, decltype(&std::free)> desc_xml(nullptr, std::free);
- int statusCode = 200;
- #if MINIUPNPC_API_VERSION >= 16
- desc_xml.reset(
- static_cast<char*>(miniwget_getaddr(dev->descURL, &desc_xml_size, s_our_ip.data(),
- static_cast<int>(s_our_ip.size()), 0, &statusCode)));
- #else
- desc_xml.reset(static_cast<char*>(miniwget_getaddr(
- dev->descURL, &desc_xml_size, s_our_ip.data(), static_cast<int>(s_our_ip.size()), 0)));
- #endif
- if (desc_xml && statusCode == 200)
- {
- parserootdesc(desc_xml.get(), desc_xml_size, &s_data);
- GetUPNPUrls(&s_urls, &s_data, dev->descURL, 0);
- found_valid_igd = true;
- NOTICE_LOG_FMT(NETPLAY, "Got info from IGD at {}.", dev->descURL);
- break;
- }
- else
- {
- WARN_LOG_FMT(NETPLAY, "Error getting info from IGD at {}.", dev->descURL);
- }
- }
- if (!found_valid_igd)
- WARN_LOG_FMT(NETPLAY, "Could not find a valid IGD in the discovered UPnP devices.");
- s_inited = true;
- return true;
- }
- // called from ---UPnP--- thread
- // Attempt to stop portforwarding.
- // --
- // NOTE: It is important that this happens! A few very crappy routers
- // apparently do not delete UPnP mappings on their own, so if you leave them
- // hanging, the NVRAM will fill with portmappings, and eventually all UPnP
- // requests will fail silently, with the only recourse being a factory reset.
- // --
- static bool UnmapPort(const u16 port)
- {
- std::string port_str = std::to_string(port);
- UPNP_DeletePortMapping(s_urls.controlURL, s_data.first.servicetype, port_str.c_str(), "UDP",
- nullptr);
- return true;
- }
- // called from ---UPnP--- thread
- // Attempt to portforward!
- static bool MapPort(const char* addr, const u16 port)
- {
- if (s_mapped > 0)
- UnmapPort(s_mapped);
- std::string port_str = std::to_string(port);
- int result = UPNP_AddPortMapping(
- s_urls.controlURL, s_data.first.servicetype, port_str.c_str(), port_str.c_str(), addr,
- (std::string("dolphin-emu UDP on ") + addr).c_str(), "UDP", nullptr, nullptr);
- if (result != 0)
- return false;
- s_mapped = port;
- return true;
- }
- // UPnP thread: try to map a port
- static void MapPortThread(const u16 port)
- {
- if (InitUPnP() && MapPort(s_our_ip.data(), port))
- {
- NOTICE_LOG_FMT(NETPLAY, "Successfully mapped port {} to {}.", port, s_our_ip.data());
- return;
- }
- WARN_LOG_FMT(NETPLAY, "Failed to map port {} to {}.", port, s_our_ip.data());
- }
- // UPnP thread: try to unmap a port
- static void UnmapPortThread()
- {
- if (s_mapped > 0)
- UnmapPort(s_mapped);
- }
- void Common::UPnP::TryPortmapping(u16 port)
- {
- if (s_thread.joinable())
- s_thread.join();
- s_thread = std::thread(&MapPortThread, port);
- }
- void Common::UPnP::StopPortmapping()
- {
- if (s_thread.joinable())
- s_thread.join();
- s_thread = std::thread(&UnmapPortThread);
- s_thread.join();
- }
- #endif
|