2017-08-07 08:26:01 +02:00
|
|
|
// Copyright 2017 Dolphin Emulator Project
|
2021-07-05 03:22:19 +02:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2017-08-07 08:26:01 +02:00
|
|
|
|
|
|
|
#ifdef USE_UPNP
|
|
|
|
|
|
|
|
#include "Common/UPnP.h"
|
|
|
|
|
|
|
|
#include "Common/Logging/Log.h"
|
|
|
|
|
2017-08-07 08:41:48 +02:00
|
|
|
#include <array>
|
2017-08-07 08:45:39 +02:00
|
|
|
#include <cstdlib>
|
2017-08-07 08:26:01 +02:00
|
|
|
#include <cstring>
|
|
|
|
#include <miniupnpc.h>
|
|
|
|
#include <miniwget.h>
|
|
|
|
#include <string>
|
|
|
|
#include <thread>
|
|
|
|
#include <upnpcommands.h>
|
2021-05-14 16:42:08 +02:00
|
|
|
#include <upnperrors.h>
|
2017-08-07 08:45:39 +02:00
|
|
|
#include <vector>
|
2017-08-07 08:26:01 +02:00
|
|
|
|
2017-08-07 08:37:17 +02:00
|
|
|
static UPNPUrls s_urls;
|
|
|
|
static IGDdatas s_data;
|
2017-08-07 08:41:48 +02:00
|
|
|
static std::array<char, 20> s_our_ip;
|
2017-08-07 08:37:17 +02:00
|
|
|
static u16 s_mapped = 0;
|
|
|
|
static std::thread s_thread;
|
2017-08-07 08:26:01 +02:00
|
|
|
|
|
|
|
// called from ---UPnP--- thread
|
|
|
|
// discovers the IGD
|
2017-08-07 08:43:24 +02:00
|
|
|
static bool InitUPnP()
|
2017-08-07 08:26:01 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
2017-08-14 03:45:54 +02:00
|
|
|
s_urls = {};
|
|
|
|
s_data = {};
|
2017-08-07 08:26:01 +02:00
|
|
|
|
|
|
|
// Find all UPnP devices
|
2017-08-07 20:34:53 +02:00
|
|
|
int upnperror = 0;
|
2017-08-07 08:26:01 +02:00
|
|
|
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)
|
|
|
|
{
|
2021-05-14 16:42:08 +02:00
|
|
|
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));
|
|
|
|
}
|
2017-08-07 08:26:01 +02:00
|
|
|
|
|
|
|
s_error = true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Look for the IGD
|
2021-05-14 16:42:08 +02:00
|
|
|
bool found_valid_igd = false;
|
2017-08-07 08:26:01 +02:00
|
|
|
for (UPNPDev* dev = devlist.get(); dev; dev = dev->pNext)
|
|
|
|
{
|
2017-08-07 20:34:08 +02:00
|
|
|
if (!std::strstr(dev->st, "InternetGatewayDevice"))
|
|
|
|
continue;
|
2017-08-07 08:26:01 +02:00
|
|
|
|
2017-08-07 08:58:00 +02:00
|
|
|
int desc_xml_size = 0;
|
|
|
|
std::unique_ptr<char, decltype(&std::free)> desc_xml(nullptr, std::free);
|
2017-08-07 08:26:01 +02:00
|
|
|
int statusCode = 200;
|
|
|
|
#if MINIUPNPC_API_VERSION >= 16
|
2017-08-07 08:58:00 +02:00
|
|
|
desc_xml.reset(
|
|
|
|
static_cast<char*>(miniwget_getaddr(dev->descURL, &desc_xml_size, s_our_ip.data(),
|
2017-08-07 08:41:48 +02:00
|
|
|
static_cast<int>(s_our_ip.size()), 0, &statusCode)));
|
|
|
|
#else
|
2017-08-07 08:58:00 +02:00
|
|
|
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)));
|
2017-08-07 08:26:01 +02:00
|
|
|
#endif
|
2017-08-07 08:58:00 +02:00
|
|
|
if (desc_xml && statusCode == 200)
|
2017-08-07 08:26:01 +02:00
|
|
|
{
|
2017-08-07 08:58:00 +02:00
|
|
|
parserootdesc(desc_xml.get(), desc_xml_size, &s_data);
|
2017-08-07 08:37:17 +02:00
|
|
|
GetUPNPUrls(&s_urls, &s_data, dev->descURL, 0);
|
2017-08-07 08:26:01 +02:00
|
|
|
|
2021-05-14 16:42:08 +02:00
|
|
|
found_valid_igd = true;
|
2020-10-23 20:41:30 +02:00
|
|
|
NOTICE_LOG_FMT(NETPLAY, "Got info from IGD at {}.", dev->descURL);
|
2017-08-07 08:26:01 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-10-23 20:41:30 +02:00
|
|
|
WARN_LOG_FMT(NETPLAY, "Error getting info from IGD at {}.", dev->descURL);
|
2017-08-07 08:26:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-14 16:42:08 +02:00
|
|
|
if (!found_valid_igd)
|
|
|
|
WARN_LOG_FMT(NETPLAY, "Could not find a valid IGD in the discovered UPnP devices.");
|
|
|
|
|
2017-08-07 08:26:01 +02:00
|
|
|
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.
|
|
|
|
// --
|
2017-08-07 08:43:24 +02:00
|
|
|
static bool UnmapPort(const u16 port)
|
2017-08-07 08:26:01 +02:00
|
|
|
{
|
2017-08-07 08:45:39 +02:00
|
|
|
std::string port_str = std::to_string(port);
|
2017-08-07 08:37:17 +02:00
|
|
|
UPNP_DeletePortMapping(s_urls.controlURL, s_data.first.servicetype, port_str.c_str(), "UDP",
|
|
|
|
nullptr);
|
2017-08-07 08:26:01 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// called from ---UPnP--- thread
|
|
|
|
// Attempt to portforward!
|
2017-08-07 08:43:24 +02:00
|
|
|
static bool MapPort(const char* addr, const u16 port)
|
2017-08-07 08:26:01 +02:00
|
|
|
{
|
2017-08-07 08:37:17 +02:00
|
|
|
if (s_mapped > 0)
|
2017-08-07 08:43:24 +02:00
|
|
|
UnmapPort(s_mapped);
|
2017-08-07 08:26:01 +02:00
|
|
|
|
2017-08-07 08:45:39 +02:00
|
|
|
std::string port_str = std::to_string(port);
|
2017-08-07 08:26:01 +02:00
|
|
|
int result = UPNP_AddPortMapping(
|
2017-08-07 08:41:48 +02:00
|
|
|
s_urls.controlURL, s_data.first.servicetype, port_str.c_str(), port_str.c_str(), addr,
|
2017-08-07 08:37:17 +02:00
|
|
|
(std::string("dolphin-emu UDP on ") + addr).c_str(), "UDP", nullptr, nullptr);
|
2017-08-07 08:26:01 +02:00
|
|
|
|
|
|
|
if (result != 0)
|
|
|
|
return false;
|
|
|
|
|
2017-08-07 08:37:17 +02:00
|
|
|
s_mapped = port;
|
2017-08-07 08:26:01 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// UPnP thread: try to map a port
|
2017-08-07 08:43:24 +02:00
|
|
|
static void MapPortThread(const u16 port)
|
2017-08-07 08:26:01 +02:00
|
|
|
{
|
2017-08-07 08:43:24 +02:00
|
|
|
if (InitUPnP() && MapPort(s_our_ip.data(), port))
|
2017-08-07 08:26:01 +02:00
|
|
|
{
|
2020-10-23 20:41:30 +02:00
|
|
|
NOTICE_LOG_FMT(NETPLAY, "Successfully mapped port {} to {}.", port, s_our_ip.data());
|
2017-08-07 08:26:01 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-23 20:41:30 +02:00
|
|
|
WARN_LOG_FMT(NETPLAY, "Failed to map port {} to {}.", port, s_our_ip.data());
|
2017-08-07 08:26:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// UPnP thread: try to unmap a port
|
2017-08-07 08:43:24 +02:00
|
|
|
static void UnmapPortThread()
|
2017-08-07 08:26:01 +02:00
|
|
|
{
|
2017-08-07 08:37:17 +02:00
|
|
|
if (s_mapped > 0)
|
2017-08-07 08:43:24 +02:00
|
|
|
UnmapPort(s_mapped);
|
2017-08-07 08:26:01 +02:00
|
|
|
}
|
|
|
|
|
2023-04-12 20:15:56 +02:00
|
|
|
void Common::UPnP::TryPortmapping(u16 port)
|
2017-08-07 08:26:01 +02:00
|
|
|
{
|
2017-08-07 08:37:17 +02:00
|
|
|
if (s_thread.joinable())
|
|
|
|
s_thread.join();
|
2017-08-07 08:43:24 +02:00
|
|
|
s_thread = std::thread(&MapPortThread, port);
|
2017-08-07 08:26:01 +02:00
|
|
|
}
|
|
|
|
|
2023-04-12 20:15:56 +02:00
|
|
|
void Common::UPnP::StopPortmapping()
|
2017-08-07 08:26:01 +02:00
|
|
|
{
|
2017-08-07 08:37:17 +02:00
|
|
|
if (s_thread.joinable())
|
|
|
|
s_thread.join();
|
2017-08-07 08:43:24 +02:00
|
|
|
s_thread = std::thread(&UnmapPortThread);
|
2017-08-07 08:37:17 +02:00
|
|
|
s_thread.join();
|
2017-08-07 08:26:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|