From 3386543a9c3244db5f36d267a0ca44c95c97910e Mon Sep 17 00:00:00 2001 From: Michael M Date: Sun, 6 Aug 2017 23:26:01 -0700 Subject: [PATCH] Common: extract UPnP namespace from NetPlayServer --- Source/Core/Common/CMakeLists.txt | 1 + Source/Core/Common/Common.vcxproj | 2 + Source/Core/Common/Common.vcxproj.filters | 2 + Source/Core/Common/UPnP.cpp | 173 ++++++++++++++++++++++ Source/Core/Common/UPnP.h | 17 +++ Source/Core/Core/NetPlayServer.cpp | 152 +------------------ Source/Core/Core/NetPlayServer.h | 15 -- 7 files changed, 198 insertions(+), 164 deletions(-) create mode 100644 Source/Core/Common/UPnP.cpp create mode 100644 Source/Core/Common/UPnP.h diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 322bf5d641..3b9951ede2 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -37,6 +37,7 @@ set(SRCS Thread.cpp Timer.cpp TraversalClient.cpp + UPnP.cpp Version.cpp x64ABI.cpp x64Emitter.cpp diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj index 9466fda774..651808e5f7 100644 --- a/Source/Core/Common/Common.vcxproj +++ b/Source/Core/Common/Common.vcxproj @@ -151,6 +151,7 @@ + @@ -202,6 +203,7 @@ + diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters index 4b48650ec1..c7ceb7adcb 100644 --- a/Source/Core/Common/Common.vcxproj.filters +++ b/Source/Core/Common/Common.vcxproj.filters @@ -94,6 +94,7 @@ + GL @@ -307,6 +308,7 @@ + Logging diff --git a/Source/Core/Common/UPnP.cpp b/Source/Core/Common/UPnP.cpp new file mode 100644 index 0000000000..6dc84b0dca --- /dev/null +++ b/Source/Core/Common/UPnP.cpp @@ -0,0 +1,173 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#ifdef USE_UPNP + +#include "Common/UPnP.h" + +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" + +#include +#include +#include +#include +#include +#include + +static struct UPNPUrls m_upnp_urls; +static struct IGDdatas m_upnp_data; +static std::string m_upnp_ourip; +static u16 m_upnp_mapped = 0; +static std::thread m_upnp_thread; + +// called from ---UPnP--- thread +// discovers the IGD +static bool initUPnP() +{ + static bool s_inited = false; + static bool s_error = false; + + std::vector igds; + int descXMLsize = 0, upnperror = 0; + char cIP[20]; + + // Don't init if already inited + if (s_inited) + return true; + + // Don't init if it failed before + if (s_error) + return false; + + std::memset(&m_upnp_urls, 0, sizeof(UPNPUrls)); + std::memset(&m_upnp_data, 0, sizeof(IGDdatas)); + + // Find all UPnP devices + std::unique_ptr 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) + { + WARN_LOG(NETPLAY, "An error occurred trying to discover UPnP devices."); + + s_error = true; + + return false; + } + + // Look for the IGD + for (UPNPDev* dev = devlist.get(); dev; dev = dev->pNext) + { + if (std::strstr(dev->st, "InternetGatewayDevice")) + igds.push_back(dev); + } + + for (const UPNPDev* dev : igds) + { + std::unique_ptr descXML(nullptr, std::free); + int statusCode = 200; +#if MINIUPNPC_API_VERSION >= 16 + descXML.reset(static_cast( + miniwget_getaddr(dev->descURL, &descXMLsize, cIP, sizeof(cIP), 0, &statusCode))); +#else + descXML.reset( + static_cast(miniwget_getaddr(dev->descURL, &descXMLsize, cIP, sizeof(cIP), 0))); +#endif + if (descXML && statusCode == 200) + { + parserootdesc(descXML.get(), descXMLsize, &m_upnp_data); + GetUPNPUrls(&m_upnp_urls, &m_upnp_data, dev->descURL, 0); + + m_upnp_ourip = cIP; + + NOTICE_LOG(NETPLAY, "Got info from IGD at %s.", dev->descURL); + break; + } + else + { + WARN_LOG(NETPLAY, "Error getting info from IGD at %s.", dev->descURL); + } + } + + 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 UPnPUnmapPort(const u16 port) +{ + std::string port_str = StringFromFormat("%d", port); + UPNP_DeletePortMapping(m_upnp_urls.controlURL, m_upnp_data.first.servicetype, port_str.c_str(), + "UDP", nullptr); + + return true; +} + +// called from ---UPnP--- thread +// Attempt to portforward! +static bool UPnPMapPort(const std::string& addr, const u16 port) +{ + if (m_upnp_mapped > 0) + UPnPUnmapPort(m_upnp_mapped); + + std::string port_str = StringFromFormat("%d", port); + int result = UPNP_AddPortMapping( + m_upnp_urls.controlURL, m_upnp_data.first.servicetype, port_str.c_str(), port_str.c_str(), + addr.c_str(), (std::string("dolphin-emu UDP on ") + addr).c_str(), "UDP", nullptr, nullptr); + + if (result != 0) + return false; + + m_upnp_mapped = port; + + return true; +} + +// UPnP thread: try to map a port +static void mapPortThread(const u16 port) +{ + if (initUPnP() && UPnPMapPort(m_upnp_ourip, port)) + { + NOTICE_LOG(NETPLAY, "Successfully mapped port %d to %s.", port, m_upnp_ourip.c_str()); + return; + } + + WARN_LOG(NETPLAY, "Failed to map port %d to %s.", port, m_upnp_ourip.c_str()); +} + +// UPnP thread: try to unmap a port +static void unmapPortThread() +{ + if (m_upnp_mapped > 0) + UPnPUnmapPort(m_upnp_mapped); +} + +void UPnP::TryPortmapping(u16 port) +{ + if (m_upnp_thread.joinable()) + m_upnp_thread.join(); + m_upnp_thread = std::thread(&mapPortThread, port); +} + +void UPnP::StopPortmapping() +{ + if (m_upnp_thread.joinable()) + m_upnp_thread.join(); + m_upnp_thread = std::thread(&unmapPortThread); + m_upnp_thread.join(); +} + +#endif diff --git a/Source/Core/Common/UPnP.h b/Source/Core/Common/UPnP.h new file mode 100644 index 0000000000..d4d9191fb1 --- /dev/null +++ b/Source/Core/Common/UPnP.h @@ -0,0 +1,17 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#ifdef USE_UPNP + +#include "Common/CommonTypes.h" + +namespace UPnP +{ +void TryPortmapping(u16 port); +void StopPortmapping(); +} + +#endif diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index 5eaddab1e2..11a05e069b 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -18,6 +18,7 @@ #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/StringUtil.h" +#include "Common/UPnP.h" #include "Core/ConfigManager.h" #include "Core/HW/Sram.h" #include "Core/NetPlayClient.h" //for NetPlayUI @@ -57,10 +58,7 @@ NetPlayServer::~NetPlayServer() } #ifdef USE_UPNP - if (m_upnp_thread.joinable()) - m_upnp_thread.join(); - m_upnp_thread = std::thread(&NetPlayServer::unmapPortThread); - m_upnp_thread.join(); + UPnP::StopPortmapping(); #endif } @@ -935,153 +933,9 @@ std::vector> NetPlayServer::GetInterfaceList } #ifdef USE_UPNP -#include -#include -#include - -struct UPNPUrls NetPlayServer::m_upnp_urls; -struct IGDdatas NetPlayServer::m_upnp_data; -std::string NetPlayServer::m_upnp_ourip; -u16 NetPlayServer::m_upnp_mapped = 0; -std::thread NetPlayServer::m_upnp_thread; - // called from ---GUI--- thread void NetPlayServer::TryPortmapping(u16 port) { - if (m_upnp_thread.joinable()) - m_upnp_thread.join(); - m_upnp_thread = std::thread(&NetPlayServer::mapPortThread, port); -} - -// UPnP thread: try to map a port -void NetPlayServer::mapPortThread(const u16 port) -{ - if (initUPnP() && UPnPMapPort(m_upnp_ourip, port)) - { - NOTICE_LOG(NETPLAY, "Successfully mapped port %d to %s.", port, m_upnp_ourip.c_str()); - return; - } - - WARN_LOG(NETPLAY, "Failed to map port %d to %s.", port, m_upnp_ourip.c_str()); -} - -// UPnP thread: try to unmap a port -void NetPlayServer::unmapPortThread() -{ - if (m_upnp_mapped > 0) - UPnPUnmapPort(m_upnp_mapped); -} - -// called from ---UPnP--- thread -// discovers the IGD -bool NetPlayServer::initUPnP() -{ - static bool s_inited = false; - static bool s_error = false; - - std::vector igds; - int descXMLsize = 0, upnperror = 0; - char cIP[20]; - - // Don't init if already inited - if (s_inited) - return true; - - // Don't init if it failed before - if (s_error) - return false; - - memset(&m_upnp_urls, 0, sizeof(UPNPUrls)); - memset(&m_upnp_data, 0, sizeof(IGDdatas)); - - // Find all UPnP devices - std::unique_ptr 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) - { - WARN_LOG(NETPLAY, "An error occurred trying to discover UPnP devices."); - - s_error = true; - - return false; - } - - // Look for the IGD - for (UPNPDev* dev = devlist.get(); dev; dev = dev->pNext) - { - if (strstr(dev->st, "InternetGatewayDevice")) - igds.push_back(dev); - } - - for (const UPNPDev* dev : igds) - { - std::unique_ptr descXML(nullptr, std::free); - int statusCode = 200; -#if MINIUPNPC_API_VERSION >= 16 - descXML.reset(static_cast( - miniwget_getaddr(dev->descURL, &descXMLsize, cIP, sizeof(cIP), 0, &statusCode))); -#else - descXML.reset( - static_cast(miniwget_getaddr(dev->descURL, &descXMLsize, cIP, sizeof(cIP), 0))); -#endif - if (descXML && statusCode == 200) - { - parserootdesc(descXML.get(), descXMLsize, &m_upnp_data); - GetUPNPUrls(&m_upnp_urls, &m_upnp_data, dev->descURL, 0); - - m_upnp_ourip = cIP; - - NOTICE_LOG(NETPLAY, "Got info from IGD at %s.", dev->descURL); - break; - } - else - { - WARN_LOG(NETPLAY, "Error getting info from IGD at %s.", dev->descURL); - } - } - - s_inited = true; - return true; -} - -// called from ---UPnP--- thread -// Attempt to portforward! -bool NetPlayServer::UPnPMapPort(const std::string& addr, const u16 port) -{ - if (m_upnp_mapped > 0) - UPnPUnmapPort(m_upnp_mapped); - - std::string port_str = StringFromFormat("%d", port); - int result = UPNP_AddPortMapping( - m_upnp_urls.controlURL, m_upnp_data.first.servicetype, port_str.c_str(), port_str.c_str(), - addr.c_str(), (std::string("dolphin-emu UDP on ") + addr).c_str(), "UDP", nullptr, nullptr); - - if (result != 0) - return false; - - m_upnp_mapped = port; - - 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. -// -- -bool NetPlayServer::UPnPUnmapPort(const u16 port) -{ - std::string port_str = StringFromFormat("%d", port); - UPNP_DeletePortMapping(m_upnp_urls.controlURL, m_upnp_data.first.servicetype, port_str.c_str(), - "UDP", nullptr); - - return true; + UPnP::TryPortmapping(port); } #endif diff --git a/Source/Core/Core/NetPlayServer.h b/Source/Core/Core/NetPlayServer.h index a4adacdcba..cf8d2e958a 100644 --- a/Source/Core/Core/NetPlayServer.h +++ b/Source/Core/Core/NetPlayServer.h @@ -123,19 +123,4 @@ private: ENetHost* m_server = nullptr; TraversalClient* m_traversal_client = nullptr; NetPlayUI* m_dialog = nullptr; - -#ifdef USE_UPNP - static void mapPortThread(const u16 port); - static void unmapPortThread(); - - static bool initUPnP(); - static bool UPnPMapPort(const std::string& addr, const u16 port); - static bool UPnPUnmapPort(const u16 port); - - static struct UPNPUrls m_upnp_urls; - static struct IGDdatas m_upnp_data; - static std::string m_upnp_ourip; - static u16 m_upnp_mapped; - static std::thread m_upnp_thread; -#endif };