mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-07 21:23:29 +02:00
The two long-standing bug reports had a shared root: the service loop could end up in a state it never left. - Exit hang: UPNP_CMD_EXIT was queued alongside port requests and only acted on when it reached the front. A request that couldn't complete was never popped, so exit sat behind it forever and join() blocked indefinitely. Exit is a flag now, checked before anything else. - CPU spin: wait_for() with a predicate returns immediately when the predicate already holds, so a stuck queue head meant a tight loop. sceNetInet's bind() queues UPnP_Add regardless of the setting, so this hit whenever UPnP was off and a game used sockets. The loop always blocks now, and requests are dropped while UPnP is off. - Failed discovery was retried every 5s forever, each time a full 2s SSDP round plus an error toast. Now backs off 5s -> 300s and reports once. Other things found while in here: - Every failed Initialize() leaked a UPNPUrls + IGDdatas, so ~every 5 seconds for anyone with UPnP on and no router. The manual miniwget/parserootdesc/ GetUPNPUrls block was also redundant - UPNP_GetValidIGD does all of it and memsets over the result, leaking the URLs and costing an extra HTTP round trip per attempt. - UPNP_GetValidIGD's status was never checked, so we could go DONE with no usable IGD and hand a NULL controlURL to UPNP_GetConnectionTypeInfo. - miniupnpc's strncpy into the port-mapping-entry buffers doesn't guarantee a terminator; an 80-char description ran std::string off the end of desc[80]. - Add() marked another app's port "taken" only after our own add succeeded, so a failed add left their mapping deleted and never restored. - Clear() walked the router's entire table at exit, one HTTP round trip per index. It now deletes only what we know we mapped, and the exit cleanup has a time budget so an unreachable router can't stall shutdown. - The in-flight request stayed in the queue during the router call, so a same-port request arriving concurrently could erase it and be dropped unexecuted. - The queue is bounded, and last-write-wins per port collapses the churn from games that rebind in a loop. - The mapping description is built when the request is queued rather than read off g_paramSFO from the UPnP thread later. The thread now only exists while the setting is on - turning it off makes it remove its mappings and exit, turning it on starts one. That means __UPnPInit() has to run after the config is actually loaded; g_Config.Init() only builds a lookup table. QueueRequest() reconciles too, so a per-game config or a libretro core option enabling UPnP works without a notify at every call site. The settings checkbox is disabled in-game, since sceNet latches related settings at boot and a game that already mapped its ports wouldn't cope with them disappearing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DCPmm7FoQUoqrbMdhfqhQ2
146 lines
4.8 KiB
C++
146 lines
4.8 KiB
C++
// Copyright (c) 2013- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
// Most of the code are based on https://github.com/RJ/libportfwd and updated to the latest miniupnp library
|
|
// All credit goes to him and the official miniupnp project! http://miniupnp.free.fr/
|
|
|
|
|
|
#pragma once
|
|
|
|
#ifdef USE_SYSTEM_MINIUPNPC
|
|
#include <miniupnpc/miniwget.h>
|
|
#include <miniupnpc/miniupnpc.h>
|
|
#include <miniupnpc/upnpcommands.h>
|
|
#else
|
|
#ifndef MINIUPNP_STATICLIB
|
|
#define MINIUPNP_STATICLIB
|
|
#endif
|
|
#include "ext/miniupnp/miniupnpc/include/miniwget.h"
|
|
#include "ext/miniupnp/miniupnpc/include/miniupnpc.h"
|
|
#include "ext/miniupnp/miniupnpc/include/upnpcommands.h"
|
|
#endif
|
|
|
|
#include <string>
|
|
#include <deque>
|
|
|
|
#define IP_PROTOCOL_TCP "TCP"
|
|
#define IP_PROTOCOL_UDP "UDP"
|
|
|
|
enum {
|
|
UPNP_INITSTATE_NONE = 0,
|
|
UPNP_INITSTATE_BUSY = 1,
|
|
UPNP_INITSTATE_DONE = 2,
|
|
};
|
|
|
|
enum {
|
|
UPNP_CMD_ADD = 0,
|
|
UPNP_CMD_REMOVE = 1,
|
|
};
|
|
|
|
struct UPnPArgs {
|
|
int cmd = UPNP_CMD_ADD;
|
|
std::string protocol;
|
|
unsigned short port = 0;
|
|
unsigned short intport = 0;
|
|
// Description to register the mapping under. Built when the request is queued, on the thread
|
|
// that owns the game state, since the UPnP service thread can't safely read it later.
|
|
std::string desc;
|
|
// How many times we've failed to reach the router about this request. Bounded so a request
|
|
// can't get retried forever, blocking everything queued behind it.
|
|
int attempts = 0;
|
|
};
|
|
|
|
struct PortMap {
|
|
bool taken;
|
|
std::string protocol;
|
|
std::string extPort_str;
|
|
std::string intPort_str;
|
|
std::string lanip;
|
|
std::string remoteHost;
|
|
std::string desc;
|
|
std::string duration;
|
|
std::string enabled;
|
|
};
|
|
|
|
// Only ever touched by the UPnP service thread (see PortManager.cpp). Don't call into it
|
|
// from anywhere else - queue a request with UPnP_Add()/UPnP_Remove() instead.
|
|
class PortManager {
|
|
public:
|
|
// Discover a router and pick up any mappings we left behind earlier.
|
|
// timeout: milliseconds to wait for a router to respond.
|
|
bool Initialize(unsigned int timeout = 2000);
|
|
|
|
int GetInitState() const { return m_InitState; }
|
|
|
|
// Add a port & protocol (TCP, UDP or vendor-defined) to map for forwarding (intport = 0 : same as [external] port)
|
|
bool Add(const char *protocol, unsigned short port, unsigned short intport, const std::string &desc);
|
|
|
|
// Remove a port mapping (external port)
|
|
bool Remove(const char *protocol, unsigned short port);
|
|
|
|
// Drops our mappings, restores any that we took over, and resets to the uninitialized state.
|
|
// budgetSeconds bounds how long we're willing to keep talking to the router: a router that has
|
|
// gone away answers with socket timeouts, which would otherwise stall app exit for a long time.
|
|
void Shutdown(double budgetSeconds = 3.0);
|
|
|
|
private:
|
|
// Retrieves port lists mapped by PPSSPP for current LAN IP & other's applications
|
|
bool RefreshPortList();
|
|
|
|
// Removes the port mappings we know PPSSPP created (including leftovers from previous crashes,
|
|
// which RefreshPortList() picks up at init time).
|
|
bool Clear();
|
|
|
|
// Restore ports mapped by others that were taken by PPSSPP, better used after Clear()
|
|
bool Restore();
|
|
|
|
// Uninitialize/Reset the state
|
|
void Terminate();
|
|
|
|
bool HaveControlURL() const;
|
|
// True once the current operation has used up its time budget, see Shutdown().
|
|
bool OutOfTime() const;
|
|
|
|
UPNPUrls m_urls{};
|
|
IGDdatas m_datas{};
|
|
bool m_urlsValid = false;
|
|
|
|
int m_InitState = UPNP_INITSTATE_NONE;
|
|
int m_LocalPort = UPNP_LOCAL_PORT_ANY;
|
|
double m_deadline = 0.0;
|
|
std::string m_lanip;
|
|
std::string m_leaseDuration;
|
|
std::deque<std::pair<std::string, std::string>> m_portList;
|
|
std::deque<PortMap> m_otherPortList;
|
|
};
|
|
|
|
extern PortManager g_PortManager;
|
|
|
|
void __UPnPInit(unsigned int timeout_ms);
|
|
void __UPnPShutdown();
|
|
|
|
// Add a port & protocol (TCP, UDP or vendor-defined) to map for forwarding (intport = 0 : same as [external] port)
|
|
void UPnP_Add(const char *protocol, unsigned short port, unsigned short intport = 0);
|
|
|
|
// Remove a port mapping (external port)
|
|
void UPnP_Remove(const char *protocol, unsigned short port);
|
|
|
|
// Wakes the UPnP service thread immediately, without queuing a request - call this after
|
|
// changing the enable setting so it can connect (or tear down its mappings) right away
|
|
// instead of waiting for the next retry.
|
|
void UPnP_Notify();
|