diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index ae05e867f9..149d601135 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -501,6 +501,9 @@ add_library(Core STATIC HLE/sceNetApctl.h HLE/sceNetResolver.cpp HLE/sceNetResolver.h + HLE/AdhocTransport.h + HLE/AdhocTransportP2P.cpp + HLE/AdhocTransportRelay.cpp HLE/proAdhoc.h HLE/proAdhoc.cpp HLE/proAdhocServer.h diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 30b7d563a2..110af2d0a8 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -650,6 +650,8 @@ + + @@ -949,6 +951,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index a97d899e4f..7d6b3c8cc1 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -529,6 +529,12 @@ HLE\Libraries + + HLE\Libraries + + + HLE\Libraries + HLE\Libraries @@ -1902,6 +1908,9 @@ Debugger + + HLE\Libraries + HLE\Libraries diff --git a/Core/HLE/AdhocTransport.h b/Core/HLE/AdhocTransport.h new file mode 100644 index 0000000000..efd56d0ce4 --- /dev/null +++ b/Core/HLE/AdhocTransport.h @@ -0,0 +1,218 @@ +// Copyright (c) 2012- 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/. + +#pragma once + +#include "Core/HLE/proAdhoc.h" +#include + +#include "Core/HLE/sceNetAdhoc.h" + +// What a transport found when polling a socket. +struct AdhocPollState { + bool readable = false; + bool writable = false; + bool error = false; + // An alert raised on the socket earlier (P2P reports it in poll, the relay doesn't). + bool alerted = false; +}; + +// How ad hoc sockets reach the other consoles: through the aemu_postoffice relay, or directly over the +// host's own UDP/TCP sockets (P2P). The sceNetAdhoc functions do everything a PSP does the same either +// way, and call one of these for the parts that depend on the transport. Transport() picks the one in +// use, which follows serverHasRelay. +// +// Ids are PSP socket IDs (1-based slots in adhocSockets). Unless said otherwise, the arguments, the +// socket and its alerts have been checked. +class AdhocTransport { +public: + virtual ~AdhocTransport() {} + + // Frees the host side of a socket (not the AdhocSocket), when all are deleted at once. + virtual void CloseHostSocket(AdhocSocket *sock) = 0; + // Whether the connection to a local ad hoc server should be bound to our own local IP, so that + // instances on one machine look like different consoles to it. + virtual bool BindServerConnectionToLocalIp() = 0; + + // PDP + + // Opens the host side of a PDP socket and registers it in adhocSockets. Returns the socket ID, or an + // error. + virtual int PdpCreate(const SceNetEtherAddr *saddr, int port, int bufferSize, u32 flag, bool isClient, bool enetReliable) = 0; + // Closes it and frees its slot. + virtual int PdpDelete(int id) = 0; + // Sends one datagram to one peer, whose address resolveMAC() has filled into target. Returns the + // number of bytes sent, or SOCKET_ERROR with an errno value in *error. + virtual int PdpSendTo(int id, const SceNetEtherAddr *mac, uint16_t dport, const sockaddr_in &target, const void *data, int len, int *error) = 0; + // The rest of sceNetAdhocPdpRecv. + virtual int PdpRecv(int id, SceNetEtherAddr *saddr, uint16_t *sport, void *buf, int *len, u32 timeout, int flag) = 0; + // The rest of a blocking receive's retry. + virtual int DoBlockingPdpRecv(AdhocSocketRequest &req, s64 &result) = 0; + // Updates rcv_sb_cc, the bytes waiting to be received, for sceNetAdhocGetPdpStat. + virtual void PdpUpdateRecvStat(int id, AdhocSocket *sock) = 0; + // Whether game mode should try sending to / receiving from its socket now. + virtual bool PdpReadyToSend(AdhocSocket *sock) = 0; + virtual bool PdpReadyToRecv(AdhocSocket *sock) = 0; + + // PTP + + // The host side of sceNetAdhocPtpOpen and sceNetAdhocPtpListen once the ports have been checked. + virtual int PtpOpen(const SceNetEtherAddr *saddr, int sport, const SceNetEtherAddr *daddr, int dport, int bufsize, int rexmt_int, int rexmt_cnt, int flag) = 0; + virtual int PtpListen(const SceNetEtherAddr *saddr, int sport, int bufsize, int rexmt_int, int rexmt_cnt, int backlog, bool isClient, int flag) = 0; + // Closes it and frees its slot. + virtual int PtpClose(int id) = 0; + // The rest of sceNetAdhocPtpSend / PtpRecv / PtpAccept, and their blocking retries. + virtual int PtpSend(int id, const char *data, int *len, int timeout, int flag) = 0; + virtual int PtpRecv(int id, void *buf, int *len, int timeout, int flag) = 0; + virtual int PtpAccept(int id, SceNetEtherAddr *addr, uint16_t *port, int timeout, int flag) = 0; + virtual int DoBlockingPtpSend(AdhocSocketRequest &req, s64 &result) = 0; + virtual int DoBlockingPtpRecv(AdhocSocketRequest &req, s64 &result) = 0; + virtual int DoBlockingPtpAccept(AdhocSocketRequest &req, s64 &result) = 0; + // Starts connecting to sin. Returns 0 if connected, or SOCKET_ERROR with an errno value in *error. + virtual int PtpConnect(int id, const sockaddr_in &sin, int *error) = 0; + // Checks on a connect in progress: > 0 when it can be verified, or SOCKET_ERROR / 0 with *error. + virtual int PtpConnectPoll(AdhocSocket *sock, int *error) = 0; + // Confirms a connection PtpConnect or PtpConnectPoll reported (ret), filling in the peer's address. + virtual int PtpConnectVerify(int id, sockaddr_in *sin, int ret, int *error) = 0; + // Whether a connect that failed with error is still on its way. + virtual bool PtpConnectPending(int error) = 0; + // Whether the connection a socket is making is up now. + virtual bool PtpConnectionUp(AdhocSocket *sock) = 0; + // Whether the connection PtpOpen started has come up (checked before connecting again). + virtual bool PtpConnectedSinceOpen(AdhocSocket *sock) = 0; + // Updates rcv_sb_cc, the bytes waiting to be received, for sceNetAdhocGetPtpStat. + virtual void PtpUpdateRecvStat(AdhocSocket *sock) = 0; + // Sends what's buffered. Returns 0 or an errno value. + virtual int PtpFlush(int socketId) = 0; + + // Polling + + // Fills in states[i] for each valid socket in sds, waiting up to timeout (microseconds) where the + // transport can wait. Returns a negative value on failure. + virtual int PollWait(SceNetAdhocPollSd *sds, int count, int timeout, AdhocPollState *states) = 0; + // For a PTP socket still connecting: notices the connection coming up, or being refused. + virtual void PollCheckConnecting(int id, AdhocSocket *sock, const AdhocPollState &state) = 0; + // For a readable connected PTP socket: whether that's because the peer closed it. + virtual bool PtpPeerClosed(AdhocSocket *sock) = 0; +}; + +// Shared with the transports, from sceNetAdhoc.cpp. + +#define AEMU_POSTOFFICE_PORT 27313 +#define AEMU_POSTOFFICE_ID_BASE 2048 +#define RELAY_FAILURE_DISABLE_THRESHOLD_SEC 15 + +extern std::chrono::time_point relayLastFailure; +extern bool trackingRelayFailure; +extern bool relayDisabled; +extern bool relayFirstConnect; + +uint16_t offset_port_simple(uint16_t port); +uint16_t reverse_port_simple(uint16_t port); +bool offAdhocNetwork(); +bool isLocalMac(const SceNetEtherAddr *mac); +void ptpSetEstablished(AdhocSocket *sock); +bool ptpPeerGone(const AdhocSocket *sock); +void ptpNoteSend(AdhocSocket *sock); +// The next free slot in adhocSockets, or MAX_SOCKET. +int FindFreeAdhocSocketSlot(); +uint16_t get_unused_pdp_port(); +uint16_t get_random_unused_ptp_port(); +int WaitBlockingAdhocSocket(u64 threadSocketId, int type, int pspSocketId, void* buffer, s32_le* len, u32 timeoutUS, SceNetEtherAddr* remoteMAC, u16_le* remotePort, const char* reason); +int AcceptPtpSocket(int ptpId, int newsocket, sockaddr_in& peeraddr, SceNetEtherAddr* addr, u16_le* port); +int RecreatePtpSocket(int ptpId); +int NetAdhocPtp_Connect(int id, int timeout, int flag, bool allowForcedConnect = true); + +// In AdhocTransportRelay.cpp and AdhocTransportP2P.cpp. +class RelayTransport : public AdhocTransport { +public: + void CloseHostSocket(AdhocSocket *sock) override; + + int PdpCreate(const SceNetEtherAddr *saddr, int port, int bufferSize, u32 flag, bool isClient, bool enetReliable) override; + int PdpDelete(int id) override; + int PdpSendTo(int id, const SceNetEtherAddr *mac, uint16_t dport, const sockaddr_in &target, const void *data, int len, int *error) override; + int PdpRecv(int id, SceNetEtherAddr *saddr, uint16_t *sport, void *buf, int *len, u32 timeout, int flag) override; + int DoBlockingPdpRecv(AdhocSocketRequest &req, s64 &result) override; + void PdpUpdateRecvStat(int id, AdhocSocket *sock) override; + + int PtpOpen(const SceNetEtherAddr *saddr, int sport, const SceNetEtherAddr *daddr, int dport, int bufsize, int rexmt_int, int rexmt_cnt, int flag) override; + int PtpListen(const SceNetEtherAddr *saddr, int sport, int bufsize, int rexmt_int, int rexmt_cnt, int backlog, bool isClient, int flag) override; + int PtpClose(int id) override; + int PtpSend(int id, const char *data, int *len, int timeout, int flag) override; + int PtpRecv(int id, void *buf, int *len, int timeout, int flag) override; + int PtpAccept(int id, SceNetEtherAddr *addr, uint16_t *port, int timeout, int flag) override; + int DoBlockingPtpSend(AdhocSocketRequest &req, s64 &result) override; + int DoBlockingPtpRecv(AdhocSocketRequest &req, s64 &result) override; + int DoBlockingPtpAccept(AdhocSocketRequest &req, s64 &result) override; + int PtpConnect(int id, const sockaddr_in &sin, int *error) override; + int PtpConnectPoll(AdhocSocket *sock, int *error) override; + bool PtpConnectionUp(AdhocSocket *sock) override; + void PtpUpdateRecvStat(AdhocSocket *sock) override; + int PtpFlush(int socketId) override; + + int PollWait(SceNetAdhocPollSd *sds, int count, int timeout, AdhocPollState *states) override; + + bool BindServerConnectionToLocalIp() override { return false; } + bool PdpReadyToSend(AdhocSocket *sock) override { return true; } + bool PdpReadyToRecv(AdhocSocket *sock) override { return true; } + // A connection's handle only comes once it's up, so there's no in-between to check. + int PtpConnectVerify(int id, sockaddr_in *sin, int ret, int *error) override { return ret; } + bool PtpConnectPending(int error) override { return true; } + bool PtpConnectedSinceOpen(AdhocSocket *sock) override { return false; } + void PollCheckConnecting(int id, AdhocSocket *sock, const AdhocPollState &state) override {} + bool PtpPeerClosed(AdhocSocket *sock) override { return false; } +}; + +class P2PTransport : public AdhocTransport { +public: + void CloseHostSocket(AdhocSocket *sock) override; + + int PdpCreate(const SceNetEtherAddr *saddr, int port, int bufferSize, u32 flag, bool isClient, bool enetReliable) override; + int PdpDelete(int id) override; + int PdpSendTo(int id, const SceNetEtherAddr *mac, uint16_t dport, const sockaddr_in &target, const void *data, int len, int *error) override; + int PdpRecv(int id, SceNetEtherAddr *saddr, uint16_t *sport, void *buf, int *len, u32 timeout, int flag) override; + int DoBlockingPdpRecv(AdhocSocketRequest &req, s64 &result) override; + void PdpUpdateRecvStat(int id, AdhocSocket *sock) override; + + int PtpOpen(const SceNetEtherAddr *saddr, int sport, const SceNetEtherAddr *daddr, int dport, int bufsize, int rexmt_int, int rexmt_cnt, int flag) override; + int PtpListen(const SceNetEtherAddr *saddr, int sport, int bufsize, int rexmt_int, int rexmt_cnt, int backlog, bool isClient, int flag) override; + int PtpClose(int id) override; + int PtpSend(int id, const char *data, int *len, int timeout, int flag) override; + int PtpRecv(int id, void *buf, int *len, int timeout, int flag) override; + int PtpAccept(int id, SceNetEtherAddr *addr, uint16_t *port, int timeout, int flag) override; + int DoBlockingPtpSend(AdhocSocketRequest &req, s64 &result) override; + int DoBlockingPtpRecv(AdhocSocketRequest &req, s64 &result) override; + int DoBlockingPtpAccept(AdhocSocketRequest &req, s64 &result) override; + int PtpConnect(int id, const sockaddr_in &sin, int *error) override; + int PtpConnectPoll(AdhocSocket *sock, int *error) override; + bool PtpConnectionUp(AdhocSocket *sock) override; + void PtpUpdateRecvStat(AdhocSocket *sock) override; + int PtpFlush(int socketId) override; + + int PollWait(SceNetAdhocPollSd *sds, int count, int timeout, AdhocPollState *states) override; + + bool BindServerConnectionToLocalIp() override { return true; } + bool PdpReadyToSend(AdhocSocket *sock) override; + bool PdpReadyToRecv(AdhocSocket *sock) override; + int PtpConnectVerify(int id, sockaddr_in *sin, int ret, int *error) override; + bool PtpConnectPending(int error) override; + bool PtpConnectedSinceOpen(AdhocSocket *sock) override; + void PollCheckConnecting(int id, AdhocSocket *sock, const AdhocPollState &state) override; + bool PtpPeerClosed(AdhocSocket *sock) override; +}; + +AdhocTransport &Transport(); diff --git a/Core/HLE/AdhocTransportP2P.cpp b/Core/HLE/AdhocTransportP2P.cpp new file mode 100644 index 0000000000..5693d83d7c --- /dev/null +++ b/Core/HLE/AdhocTransportP2P.cpp @@ -0,0 +1,1243 @@ +// Copyright (c) 2012- 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/. + +// The P2P transport: ad hoc sockets as the host's own UDP (PDP) and TCP (PTP) sockets, straight to the +// other consoles. + +#include "ppsspp_config.h" + +#include +#include + +#include "Common/Net/SocketCompat.h" +#include "Common/Data/Text/I18n.h" +#include "Common/System/OSD.h" +#include "Common/TimeUtil.h" + +#include "Core/Config.h" +#include "Core/CoreTiming.h" +#include "Core/Util/PortManager.h" +#include "Core/HLE/HLE.h" +#include "Core/HLE/ErrorCodes.h" +#include "Core/HLE/sceKernelThread.h" +#include "Core/HLE/sceNetAdhoc.h" +#include "Core/HLE/AdhocTransport.h" +#include "Core/HLE/NetAdhocCommon.h" +#include "Core/HLE/NetInetConstants.h" + +#ifdef _WIN32 +#undef errno +#define errno WSAGetLastError() +#endif + +void P2PTransport::CloseHostSocket(AdhocSocket *sock) { + int fd = -1; + if (sock->type == SOCK_PTP) + fd = sock->data.ptp.id; + else if (sock->type == SOCK_PDP) + fd = sock->data.pdp.id; + + if (fd > 0) { + // Close Socket + shutdown(fd, SD_RECEIVE); + closesocket(fd); + } +} + +// A connection in progress shows up as established in GetPtpStat, and PollSocket reports CONNECT, as +// soon as it's up (pspautotests net/adhoc/pair/ptpconnect). +// Whether a socket whose connect() returned EISCONN is really connected. macOS answers a repeated +// connect() with EISCONN after the first one was refused too; then getpeername fails and the refusal +// is in SO_ERROR, which goes to *error. +static bool ptpReallyConnected(int fd, int *error) { + struct sockaddr_in sin; + socklen_t sinlen = sizeof(sin); + if (getpeername(fd, (struct sockaddr *)&sin, &sinlen) == 0) { + return true; + } + int soError = 0; + socklen_t soErrorLen = sizeof(soError); + getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&soError, &soErrorLen); + *error = soError != 0 ? soError : ECONNREFUSED; + return false; +} + +int P2PTransport::DoBlockingPdpRecv(AdhocSocketRequest &req, s64 &result) { + auto& pdpsocket = adhocSockets[req.id - 1]->data.pdp; + + int ret; + int sockerr; + SceNetEtherAddr mac; + struct sockaddr_in sin; + socklen_t sinlen; + + sinlen = sizeof(sin); + memset(&sin, 0, sinlen); + + // On Windows: MSG_TRUNC are not supported on recvfrom (socket error WSAEOPNOTSUPP), so we use dummy buffer as an alternative + ret = recvfrom(pdpsocket.id, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_PEEK | MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); + sockerr = socket_errno; + + // Discard packets from IP that can't be translated into MAC address to prevent confusing the game, since the sender MAC won't be updated and may contains invalid/undefined value. + // TODO: In order to discard packets from unresolvable IP (can't be translated into player's MAC) properly, we'll need to manage the socket buffer ourself, + // by reading the whole available data, separates each datagram and discard unresolvable one, so we can calculate the correct number of available data to recv on GetPdpStat too. + // We may also need to implement encryption (or a simple checksum will do) in order to validate the packet to findout whether it came from PPSSPP or a different App that may be sending/broadcasting data to the same port being used by a game + // (in case the IP was resolvable but came from a different App, which will need to be discarded too) + if (ret != SOCKET_ERROR && !resolveIP(sin.sin_addr.s_addr, &mac)) { + // Remove the packet from socket buffer + sinlen = sizeof(sin); + memset(&sin, 0, sinlen); + recvfrom(pdpsocket.id, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); + // Try again later, until timeout reached + u64 now = CoreTiming::GetGlobalTimeUs(); + if (req.timeout != 0 && now - req.startTime > req.timeout) { + result = SCE_NET_ADHOC_ERROR_TIMEOUT; + DEBUG_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i]: Discard Timeout", req.id); + return 0; + } + else + return -1; + } + + // At this point we assumed that the packet is a valid PPSSPP packet. A datagram larger than the + // buffer isn't copied at all (pspautotests net/adhoc/pdp). + + // Note: UDP must not be received partially, otherwise leftover data in socket's buffer will be discarded + if (ret >= 0 && ret <= *req.length) { + sinlen = sizeof(sin); + memset(&sin, 0, sinlen); + ret = recvfrom(pdpsocket.id, (char*)req.buffer, std::max(0, *req.length), MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); + // UDP can also receives 0 data, while on TCP receiving 0 data = connection gracefully closed, but not sure whether PDP can send/recv 0 data or not tho + *req.length = 0; + if (ret >= 0) { + DEBUG_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i:%u]: Received %u bytes from %s:%u\n", req.id, getLocalPort(pdpsocket.id), ret, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); + + // Find Peer MAC + if (resolveIP(sin.sin_addr.s_addr, &mac)) { + // Provide Sender Information + *req.remoteMAC = mac; + *req.remotePort = ntohs(sin.sin_port) - portOffset; + + // Save Length + *req.length = ret; + + // Update last recv timestamp + peerlock.lock(); + auto peer = findFriend(&mac); + if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); + peerlock.unlock(); + } + // Unknown Peer + else { + *req.length = ret; + *req.remotePort = ntohs(sin.sin_port) - portOffset; + + WARN_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i:%u]: Received %i bytes from Unknown Peer %s:%u", req.id, getLocalPort(pdpsocket.id), ret, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); + } + } + result = 0; + } + // On Windows: recvfrom on UDP can get error WSAECONNRESET when previous sendto's destination is unreachable (or destination port is not bound yet), may need to disable SIO_UDP_CONNRESET error + else if (sockerr == EAGAIN || sockerr == EWOULDBLOCK || sockerr == ECONNRESET) { + u64 now = CoreTiming::GetGlobalTimeUs(); + if (req.timeout == 0 || now - req.startTime <= req.timeout) { + // Try again later + return -1; + } + else + result = SCE_NET_ADHOC_ERROR_TIMEOUT; + } + // Returning required buffer size when available data in recv buffer is larger than provided buffer size + else if (ret > *req.length) { + WARN_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i:%u]: Peeked %u/%u bytes from %s:%u\n", req.id, getLocalPort(pdpsocket.id), ret, *req.length, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); + *req.length = ret; + + // Find Peer MAC + if (resolveIP(sin.sin_addr.s_addr, &mac)) { + // Provide Sender Information + *req.remoteMAC = mac; + *req.remotePort = ntohs(sin.sin_port) - portOffset; + + // FIXME: Do we need to update last recv timestamp? eventhough data hasn't been retrieved yet (ie. peeked) + peerlock.lock(); + auto peer = findFriend(&mac); + if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); + peerlock.unlock(); + } + result = SCE_NET_ADHOC_ERROR_NOT_ENOUGH_SPACE; + } + // FIXME: Blocking operation with infinite timeout(0) should never get a TIMEOUT error, right? May be we should return INVALID_ARG instead if it was infinite timeout (0)? + else + result = SCE_NET_ADHOC_ERROR_TIMEOUT; // SCE_NET_ADHOC_ERROR_INVALID_ARG; // SCE_NET_ADHOC_ERROR_DISCONNECTED + + if (ret == SOCKET_ERROR) + DEBUG_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i]: Socket Error (%i)", req.id, sockerr); + + return 0; +} + +int P2PTransport::PdpSendTo(int id, const SceNetEtherAddr *mac, uint16_t dport, const sockaddr_in &target, const void *data, int len, int *error) { + int sent = sendto(adhocSockets[id - 1]->data.pdp.id, (const char *)data, len, MSG_NOSIGNAL, (const struct sockaddr *)&target, sizeof(target)); + *error = socket_errno; + return sent; +} + +int P2PTransport::DoBlockingPtpSend(AdhocSocketRequest &req, s64 &result) { + auto sock = adhocSockets[req.id - 1]; + auto& ptpsocket = sock->data.ptp; + + // Send Data + int ret = 0; + int sockerr = 0; + ret = send(ptpsocket.id, (const char*)req.buffer, *req.length, MSG_NOSIGNAL); + sockerr = socket_errno; + + // Success + if (ret > 0) { + // Save Length + *req.length = ret; + + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpSend[%i:%u]: Sent %u bytes to %s:%u\n", req.id, ptpsocket.lport, ret, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport); + + // Set to Established on successful Send when an attempt to Connect was initiated + if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) + ptpSetEstablished(sock); + + // Return Success + result = 0; + } + else if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK || (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT && (sockerr == ENOTCONN || connectInProgress(sockerr))))) { + u64 now = CoreTiming::GetGlobalTimeUs(); + if (req.timeout == 0 || now - req.startTime <= req.timeout) { + return -1; + } + else + result = SCE_NET_ADHOC_ERROR_TIMEOUT; + } + else { + // Change Socket State. // FIXME: Does Alerted Socket should be closed too? + ptpsocket.state = ADHOC_PTP_STATE_CLOSED; + + // Disconnected + result = SCE_NET_ADHOC_ERROR_DISCONNECTED; + } + + if (ret == SOCKET_ERROR) + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpSend[%i]: Socket Error (%i)", req.id, sockerr); + + return 0; +} + +int P2PTransport::DoBlockingPtpRecv(AdhocSocketRequest &req, s64 &result) { + auto sock = adhocSockets[req.id - 1]; + auto& ptpsocket = sock->data.ptp; + + int ret = 0; + int sockerr = 0; + ret = recv(ptpsocket.id, (char*)req.buffer, std::max(0, *req.length), MSG_NOSIGNAL); + sockerr = socket_errno; + + // Received Data. POSIX: May received 0 bytes when the remote peer already closed the connection. + if (ret > 0) { + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpRecv[%i:%u]: Received %u bytes from %s:%u\n", req.id, ptpsocket.lport, ret, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport); + // Save Length + *req.length = ret; + + // Update last recv timestamp + peerlock.lock(); + auto peer = findFriend(&ptpsocket.paddr); + if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); + peerlock.unlock(); + + // Set to Established on successful Recv when an attempt to Connect was initiated + if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) + ptpSetEstablished(sock); + + result = 0; + } + else if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK || (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT && (sockerr == ENOTCONN || connectInProgress(sockerr))))) { + u64 now = CoreTiming::GetGlobalTimeUs(); + if (req.timeout == 0 || now - req.startTime <= req.timeout) { + return -1; + } + else + result = SCE_NET_ADHOC_ERROR_TIMEOUT; + } + else { + // Change Socket State. // FIXME: Does Alerted Socket should be closed too? + ptpsocket.state = ADHOC_PTP_STATE_CLOSED; + + // Disconnected + result = SCE_NET_ADHOC_ERROR_DISCONNECTED; // SCE_NET_ADHOC_ERROR_INVALID_ARG + } + + if (ret == SOCKET_ERROR) + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpRecv[%i]: Socket Error (%i)", req.id, sockerr); + + return 0; +} + +int P2PTransport::DoBlockingPtpAccept(AdhocSocketRequest &req, s64 &result) { + auto& ptpsocket = adhocSockets[req.id - 1]->data.ptp; + + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + socklen_t sinlen = sizeof(sin); + int ret, sockerr; + + // Check if listening socket is ready to accept + ret = IsSocketReady(ptpsocket.id, true, false, &sockerr); + if (ret > 0) { + // Accept Connection + ret = accept(ptpsocket.id, (struct sockaddr*)&sin, &sinlen); + sockerr = socket_errno; + } + + // Accepted New Connection + if (ret > 0) { + int newid = AcceptPtpSocket(req.id, ret, sin, req.remoteMAC, req.remotePort); + if (newid > 0) + result = newid; + } + else if (ret == 0 || (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK))) { + u64 now = CoreTiming::GetGlobalTimeUs(); + if (req.timeout == 0 || now - req.startTime <= req.timeout) { + return -1; + } + else { + result = SCE_NET_ADHOC_ERROR_TIMEOUT; + } + } + else + result = SCE_NET_ADHOC_ERROR_INVALID_ARG; //SCE_NET_ADHOC_ERROR_TIMEOUT + + if (ret == SOCKET_ERROR) + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpAccept[%i]: Socket Error (%i)", req.id, sockerr); + + return 0; +} + +bool P2PTransport::PtpConnectionUp(AdhocSocket *sock) { + if (IsSocketReady(sock->data.ptp.id, true, true) <= 0) { + return false; + } + struct sockaddr_in sin; + socklen_t sinlen = sizeof(sin); + memset(&sin, 0, sinlen); + // Ensure that the connection really established or not, since "select" alone can't accurately detects it + return getpeername(sock->data.ptp.id, (struct sockaddr*)&sin, &sinlen) != SOCKET_ERROR; +} + +bool P2PTransport::PtpConnectedSinceOpen(AdhocSocket *sock) { + struct sockaddr_in peer; + socklen_t peerLen = sizeof(peer); + return getpeername(sock->data.ptp.id, (struct sockaddr *)&peer, &peerLen) == 0; +} + +int P2PTransport::PtpConnect(int id, const sockaddr_in &sin, int *error) { + int fd = adhocSockets[id - 1]->data.ptp.id; + int ret = connect(fd, (const struct sockaddr *)&sin, sizeof(sin)); + *error = socket_errno; + if (ret == SOCKET_ERROR && *error == EISCONN) { + ptpReallyConnected(fd, error); + } + return ret; +} + +int P2PTransport::PtpConnectPoll(AdhocSocket *sock, int *error) { + return IsSocketReady(sock->data.ptp.id, false, true, error); +} + +int P2PTransport::PtpConnectVerify(int id, sockaddr_in *sin, int ret, int *error) { + auto& ptpsocket = adhocSockets[id - 1]->data.ptp; + socklen_t sinlen = sizeof(*sin); + // Note: "getpeername" shouldn't failed if the connection has been established, but on Windows it may succeed even when "connect" is still in-progress and not accepted yet (ie. "Tales of VS" on Windows) + ret = getpeername(ptpsocket.id, (struct sockaddr*)sin, &sinlen); + if (ret == SOCKET_ERROR) { + int err = socket_errno; + VERBOSE_LOG(Log::sceNet, "sceNetAdhocPtpConnect[%i:%u]: getpeername(%i) error %i, sockerr = %i", id, ptpsocket.lport, ptpsocket.id, err, *error); + *error = err; + } + return ret; +} + +bool P2PTransport::PtpConnectPending(int error) { + return connectInProgress(error); +} + +bool P2PTransport::PdpReadyToSend(AdhocSocket *sock) { + return IsSocketReady(sock->data.pdp.id, false, true) > 0; +} + +bool P2PTransport::PdpReadyToRecv(AdhocSocket *sock) { + return IsSocketReady(sock->data.pdp.id, true, false) > 0; +} + +int P2PTransport::PdpCreate(const SceNetEtherAddr *saddr, int port, int bufferSize, u32 flag, bool isClient, bool enetReliable) { + // Create Internet UDP Socket + // Socket is remapped through adhocSockets + int usocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + if (usocket == INVALID_SOCKET) { + DEBUG_LOG(Log::sceNet, "%s: sock creation failed", __func__); + return ERROR_NET_NO_SPACE; + } + + // Change socket buffer size to be consistent on all platforms. + // Send Buffer should be smaller than Recv Buffer to prevent faster device from flooding slower device too much. + setSockBufferSize(usocket, SO_SNDBUF, bufferSize*5); //PSP_ADHOC_PDP_MFS + // Recv Buffer should be equal or larger than Send Buffer. Using larger Recv Buffer might helped reduces dropped packets during a slowdown, but too large may cause slow performance on Warriors Orochi 2. + setSockBufferSize(usocket, SO_RCVBUF, bufferSize*10); //PSP_ADHOC_PDP_MFS*10 + + // Ignore SIGPIPE when supported (ie. BSD/MacOS) + setSockNoSIGPIPE(usocket, 1); + + // Enable Port Re-use, this will allow binding to an already used port, but only one of them can read the data (shared receive buffer?) + setSockReuseAddrPort(usocket); + + // Disable Connection Reset error on UDP to avoid strange behavior https://stackoverflow.com/questions/34242622/windows-udp-sockets-recvfrom-fails-with-error-10054 + setUDPConnReset(usocket, false); + + // Binding Information for local Port + struct sockaddr_in addr {}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + if (isLocalServer) { + getLocalIp(&addr); + } + uint16_t requestedport = static_cast(port + static_cast(portOffset)); + // Avoid getting random port due to port offset when original port wasn't 0 (ie. original_port + port_offset = 65536 = 0) + if (requestedport == 0 && port > 0) + requestedport = 65535; // Hopefully it will be safe to default it to 65535 since there can't be more than one port that can bumped into 65536 + + // Show a warning about privileged ports + if (requestedport != 0 && requestedport < 1024) { + WARN_LOG(Log::sceNet, "sceNetAdhocPdpCreate - Ports below 1024(ie. %hu) may require Admin Privileges", requestedport); + } + addr.sin_port = htons(requestedport); + + + // Bound Socket to local Port + int iResult = bind(usocket, (struct sockaddr*)&addr, sizeof(addr)); + // A port picked for port 0 may be taken on the host; pick another. + for (int tries = 0; iResult != 0 && isClient && tries < 16; tries++) { + port = get_unused_pdp_port(); + requestedport = static_cast(port + static_cast(portOffset)); + if (requestedport == 0) + requestedport = 65535; + addr.sin_port = htons(requestedport); + iResult = bind(usocket, (struct sockaddr*)&addr, sizeof(addr)); + } + + if (iResult != 0) { + DEBUG_LOG(Log::sceNet, "%s: sock bind failed", __func__); + auto n = GetI18NCategory(I18NCat::NETWORKING); + g_OSD.Show(OSDType::MESSAGE_ERROR, std::string(n->T("Failed to Bind Port")) + " " + std::to_string(port + portOffset) + "\n" + std::string(n->T("Please change your Port Offset")), 0.0f, "portbindfail"); + closesocket(usocket); + return ERROR_NET_NO_SPACE; + } + + // Workaround: Send a dummy 0 size message to AdhocServer IP to make sure the socket actually bound to an address when binded with INADDR_ANY before using getsockname, seems to fix sending from incorrect port issue on MGS:PW on Android + auto server_ip = GetAdhocServerIP(); + addr.sin_addr.s_addr = server_ip.in.sin_addr.s_addr; + addr.sin_port = 0; + sendto(usocket, dummyPeekBuf64k, 0, MSG_NOSIGNAL, (struct sockaddr*)&addr, sizeof(addr)); + // Update sport with the port assigned internal->lport = ntohs(local.sin_port) + socklen_t len = sizeof(addr); + if (getsockname(usocket, (struct sockaddr*)&addr, &len) == 0) { + uint16_t boundport = ntohs(addr.sin_port); + if (port + static_cast(portOffset) >= 65536 || static_cast(boundport) - static_cast(portOffset) <= 0) + WARN_LOG(Log::sceNet, "sceNetAdhocPdpCreate - Wrapped Port Detected: Original(%d) -> Requested(%d), Bound(%d) -> BoundOriginal(%d)", port, requestedport, boundport, boundport - portOffset); + port = boundport - portOffset; + } + + // Allocate Memory for Internal Data + AdhocSocket * internal = (AdhocSocket*)malloc(sizeof(AdhocSocket)); + + if (internal == NULL) { + DEBUG_LOG(Log::sceNet, "%s: out of memory when allocating internal socket tracking", __func__); + closesocket(usocket); + return ERROR_NET_NO_SPACE; + } + + // Find Free Translator Index + // FIXME: We should probably use an increasing index instead of looking for an empty slot from beginning if we want to simulate a real socket id + int i = 0; + i = FindFreeAdhocSocketSlot(); + + if (i >= MAX_SOCKET) { + DEBUG_LOG(Log::sceNet, "%s: out of slot when allocating internal socket tracking", __func__); + free(internal); + closesocket(usocket); + return ERROR_NET_NO_SPACE; + } + + // Clear Memory + memset(internal, 0, sizeof(AdhocSocket)); + + // Socket Type + internal->type = SOCK_PDP; + internal->nonblocking = flag; + internal->buffer_size = bufferSize; + internal->isClient = isClient; + + // Fill in Data + internal->data.pdp.id = usocket; + internal->data.pdp.laddr = *saddr; + internal->data.pdp.lport = port; //getLocalPort(usocket) - portOffset; + + // Link Socket to Translator ID + adhocSockets[i] = internal; + + // Forward Port on Router + //sceNetPortOpen("UDP", port); + UPnP_Add(IP_PROTOCOL_UDP, isOriPort ? port : port + portOffset, port + portOffset); // g_PortManager.Add(IP_PROTOCOL_UDP, isOriPort ? port : port + portOffset, port + portOffset); + + // Switch to non-blocking for futher usage + changeBlockingMode(usocket, 1); + + // Success + INFO_LOG(Log::sceNet, "sceNetAdhocPdpCreate - PSP Socket id: %i, Host Socket id: %i", i + 1, usocket); + return i + 1; +} + +int P2PTransport::PdpRecv(int id, SceNetEtherAddr *saddr, uint16_t *sport, void *buf, int *len, u32 timeout, int flag) { + auto& pdpsocket = adhocSockets[id - 1]->data.pdp; + + // Sender Address + struct sockaddr_in sin; + socklen_t sinlen; + + // Acquire Network Lock + //_acquireNetworkLock(); + + SceNetEtherAddr mac; + int received = 0; + int error = 0; + + int disCnt = 16; + while (--disCnt > 0) + { + // Receive Data. PDP always sent in full size or nothing(failed), recvfrom will always receive in full size as requested (blocking) or failed (non-blocking). If available UDP data is larger than buffer, excess data is lost. + // Should peek first for the available data size if it's more than len return SCE_NET_ADHOC_ERROR_NOT_ENOUGH_SPACE along with required size in len to prevent losing excess data + // On Windows: MSG_TRUNC are not supported on recvfrom (socket error WSAEOPNOTSUPP), so we use dummy buffer as an alternative + sinlen = sizeof(sin); + memset(&sin, 0, sinlen); + received = recvfrom(pdpsocket.id, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_PEEK | MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); + error = socket_errno; + // Discard packets from IP that can't be translated into MAC address to prevent confusing the game, since the sender MAC won't be updated and may contains invalid/undefined value. + // TODO: In order to discard packets from unresolvable IP (can't be translated into player's MAC) properly, we'll need to manage the socket buffer ourself, + // by reading the whole available data, separates each datagram and discard unresolvable one, so we can calculate the correct number of available data to recv on GetPdpStat too. + // We may also need to implement encryption (or a simple checksum will do) in order to validate the packet to findout whether it came from PPSSPP or a different App that may be sending/broadcasting data to the same port being used by a game + // (in case the IP was resolvable but came from a different App, which will need to be discarded too) + // Note: Looping to check too many packets (ie. contiguous) to discard per one non-blocking PdpRecv syscall may cause a slow down + if (received != SOCKET_ERROR && !resolveIP(sin.sin_addr.s_addr, &mac)) { + // Remove the packet from socket buffer + sinlen = sizeof(sin); + memset(&sin, 0, sinlen); + recvfrom(pdpsocket.id, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); + if (flag) { + VERBOSE_LOG(Log::sceNet, "%08x=sceNetAdhocPdpRecv: would block (disc)", SCE_NET_ADHOC_ERROR_WOULD_BLOCK); // Temporary fix to avoid a crash on the Logs due to trying to Logs syscall's argument from another thread (ie. AdhocMatchingInput thread) + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; // hleLogSuccessVerboseX(Log::sceNet, SCE_NET_ADHOC_ERROR_WOULD_BLOCK, "would block (disc)"); + } + else { + // Simulate blocking behaviour with non-blocking socket, and discard more unresolvable packets until timeout reached + u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | pdpsocket.id; + return WaitBlockingAdhocSocket(threadSocketId, PDP_RECV, id, buf, len, timeout, saddr, sport, "pdp recv (disc)"); + } + } + else + break; + } + + // At this point we assumed that the packet is a valid PPSSPP packet + if (received != SOCKET_ERROR && *len < received) { + INFO_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i:%u]: Peeked %u/%u bytes from %s:%u\n", id, getLocalPort(pdpsocket.id), received, *len, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); + + // A datagram larger than the buffer stays queued, and isn't copied (pspautotests net/adhoc/pdp). + // Return the actual available data size + *len = received; + + // Provide Sender Information + *saddr = mac; + *sport = ntohs(sin.sin_port) - portOffset; + + // Update last recv timestamp, may cause disconnection not detected properly tho + peerlock.lock(); + auto peer = findFriend(&mac); + if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); + peerlock.unlock(); + + return hleLogVerbose(Log::sceNet, SCE_NET_ADHOC_ERROR_NOT_ENOUGH_SPACE, "not enough space"); + } + + sinlen = sizeof(sin); + memset(&sin, 0, sinlen); + // On Windows: Socket Error 10014 may happen when buffer size is less than the minimum allowed/required (ie. negative number on Vulcanus Seek and Destroy), the address is not a valid part of the user address space (ie. on the stack or when buffer overflow occurred), or the address is not properly aligned (ie. multiple of 4 on 32bit and multiple of 8 on 64bit) https://stackoverflow.com/questions/861154/winsock-error-code-10014 + received = recvfrom(pdpsocket.id, (char*)buf, std::max(0, *len), MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); + error = socket_errno; + + // On Windows: recvfrom on UDP can get error WSAECONNRESET when previous sendto's destination is unreachable (or destination port is not bound), may need to disable SIO_UDP_CONNRESET + if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || error == ECONNRESET)) { + if (flag == 0) { + // Simulate blocking behaviour with non-blocking socket + u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | pdpsocket.id; + return WaitBlockingAdhocSocket(threadSocketId, PDP_RECV, id, buf, len, timeout, saddr, sport, "pdp recv"); + } + + VERBOSE_LOG(Log::sceNet, "%08x=sceNetAdhocPdpRecv: would block", SCE_NET_ADHOC_ERROR_WOULD_BLOCK); // Temporary fix to avoid a crash on the Logs due to trying to Logs syscall's argument from another thread (ie. AdhocMatchingInput thread) + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; // hleLogSuccessVerboseX(Log::sceNet, SCE_NET_ADHOC_ERROR_WOULD_BLOCK, "would block"); + } + + hleEatMicro(50); + // Received Data. UDP can also receives 0 data, while on TCP 0 data = connection gracefully closed, but not sure about PDP tho + if (received >= 0) { + DEBUG_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i:%u]: Received %u bytes from %s:%u\n", id, getLocalPort(pdpsocket.id), received, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); + + // Find Peer MAC + if (resolveIP(sin.sin_addr.s_addr, &mac)) { + // Provide Sender Information + *saddr = mac; + *sport = ntohs(sin.sin_port) - portOffset; + + // Save Length + *len = received; // The PSP writes the received length back (pspautotests net/adhoc/pdp). + + // Update last recv timestamp, may cause disconnection not detected properly tho + peerlock.lock(); + auto peer = findFriend(&mac); + if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); + peerlock.unlock(); + + // Free Network Lock + //_freeNetworkLock(); + + // Return Success. According to pspsdk-1.0+beta2 returned value is Number of bytes received, but JPCSP returning 0? + return 0; // 0, not the length, as on the PSP (pspautotests net/adhoc/pdp). KH BBS can't see rooms otherwise. + } + + // Free Network Lock + //_freeNetworkLock(); + + //free(tmpbuf); + + // Receiving data from unknown peer? Should never reached here! Unless the Peeked's packet was different than the Recved one (which mean there is a problem) + WARN_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i:%u]: Received %i bytes from Unknown Peer %s:%u", id, getLocalPort(pdpsocket.id), received, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); + if (flag) { + VERBOSE_LOG(Log::sceNet, "%08x=sceNetAdhocPdpRecv: would block (problem)", SCE_NET_ADHOC_ERROR_WOULD_BLOCK); // Temporary fix to avoid a crash on the Logs due to trying to Logs syscall's argument from another thread (ie. AdhocMatchingInput thread) + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; // hleLogSuccessVerboseX(Log::sceNet, SCE_NET_ADHOC_ERROR_WOULD_BLOCK, "would block (problem)"); + } + } + + // Free Network Lock + //_freeNetworkLock(); + + DEBUG_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i:%u]: Result:%i (Error:%i)", id, pdpsocket.lport, received, error); + + // Unexpected error (other than EAGAIN/EWOULDBLOCK/ECONNRESET) or in case the Peeked's packet was different than Recved one, treated as Timeout? + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_TIMEOUT, "timeout?"); +} + +static int hostSocketOf(const AdhocSocket *sock) { + return sock->type == SOCK_PTP ? sock->data.ptp.id : sock->data.pdp.id; +} + +int P2PTransport::PollWait(SceNetAdhocPollSd *sds, int count, int timeout, AdhocPollState *states) { + //WSAPoll only available for Vista or newer, so we'll use an alternative way for XP since Windows doesn't have poll function like *NIX + fd_set readfds, writefds, exceptfds; + int maxfd = 0; + FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); + + for (int i = 0; i < count; i++) { + if (sds[i].id > 0 && sds[i].id <= MAX_SOCKET && adhocSockets[sds[i].id - 1] != NULL) { + int fd = hostSocketOf(adhocSockets[sds[i].id - 1]); + if (fd > maxfd) maxfd = fd; + FD_SET(fd, &readfds); + FD_SET(fd, &writefds); + FD_SET(fd, &exceptfds); + } + } + timeval tmout; + tmout.tv_sec = timeout / 1000000; // seconds + tmout.tv_usec = (timeout % 1000000); // microseconds + int ret = select(maxfd + 1, &readfds, &writefds, &exceptfds, &tmout); + if (ret < 0) { + return ret; + } + for (int i = 0; i < count; i++) { + if (sds[i].id > 0 && sds[i].id <= MAX_SOCKET && adhocSockets[sds[i].id - 1] != NULL) { + auto sock = adhocSockets[sds[i].id - 1]; + int fd = hostSocketOf(sock); + states[i].readable = FD_ISSET(fd, &readfds); + states[i].writable = FD_ISSET(fd, &writefds); + states[i].error = FD_ISSET(fd, &exceptfds); + states[i].alerted = sock->alerted_flags != 0; + } + } + return ret; +} + +// A connection PtpOpen started may have come up since anything looked at it. A refused one reports +// DISCONNECT (pspautotests net/adhoc/poll). +void P2PTransport::PollCheckConnecting(int id, AdhocSocket *sock, const AdhocPollState &state) { + if (!state.writable && !state.error) { + return; + } + int connectError = 0; + if (ptpReallyConnected(sock->data.ptp.id, &connectError)) { + ptpSetEstablished(sock); + } else if (!connectInProgress(connectError)) { + if (RecreatePtpSocket(id) < 0) { + WARN_LOG(Log::sceNet, "sceNetAdhocPollSocket[%i]: Failed to recreate the refused socket", id); + } + sock->data.ptp.state = ADHOC_PTP_STATE_CLOSED; + } +} + +bool P2PTransport::PtpPeerClosed(AdhocSocket *sock) { + char peek; + return recv(sock->data.ptp.id, &peek, 1, MSG_PEEK | MSG_NOSIGNAL) == 0; +} + +int P2PTransport::PdpDelete(int id) { + auto sock = adhocSockets[id - 1]; + + // Close Connection + shutdown(sock->data.pdp.id, SD_RECEIVE); + closesocket(sock->data.pdp.id); + + // Remove Port Forward from Router + //sceNetPortClose("UDP", sock->lport); + //g_PortManager.Remove(IP_PROTOCOL_UDP, isOriPort ? sock->lport : sock->lport + portOffset); // Let's not remove mapping in real-time as it could cause lags/disconnection when joining a room with slow routers + + // Free Memory + free(sock); + + // Free Translation Slot + adhocSockets[id - 1] = NULL; + + // Success + return 0; +} + +void P2PTransport::PdpUpdateRecvStat(int id, AdhocSocket *sock) { + sock->data.pdp.rcv_sb_cc = getAvailToRecv(sock->data.pdp.id, sock->buffer_size); + // There might be a possibility for the data to be taken by the OS, thus FIONREAD returns 0, but can be Received + if (sock->data.pdp.rcv_sb_cc == 0) { + // Let's try to peek the data size + // TODO: May need to filter out packets from an IP that can't be translated to MAC address + struct sockaddr_in sin; + socklen_t sinlen; + sinlen = sizeof(sin); + memset(&sin, 0, sinlen); + int received = recvfrom(sock->data.pdp.id, dummyPeekBuf64k, std::min((u32)dummyPeekBuf64kSize, sock->buffer_size), MSG_PEEK | MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); + if (received > 0) + sock->data.pdp.rcv_sb_cc = received; + } +} + +void P2PTransport::PtpUpdateRecvStat(AdhocSocket *sock) { + // Set available bytes to be received + sock->data.ptp.rcv_sb_cc = getAvailToRecv(sock->data.ptp.id); + // It seems real PSP respecting the socket buffer size arg, so we may need to cap the value to the buffer size arg since we use larger buffer + sock->data.ptp.rcv_sb_cc = std::min(sock->data.ptp.rcv_sb_cc, (u32_le)sock->buffer_size); + // There might be a possibility for the data to be taken by the OS, thus FIONREAD returns 0, but can be Received + if (sock->data.ptp.rcv_sb_cc == 0) { + // Let's try to peek the data size + int received = recv(sock->data.ptp.id, dummyPeekBuf64k, std::min((u32)dummyPeekBuf64kSize, sock->buffer_size), MSG_PEEK | MSG_NOSIGNAL); + if (received > 0) + sock->data.ptp.rcv_sb_cc = received; + } +} + +int P2PTransport::PtpOpen(const SceNetEtherAddr *saddr, int sport, const SceNetEtherAddr *daddr, int dport, int bufsize, int rexmt_int, int rexmt_cnt, int flag) { + bool isClient = false; + // Random Port required + if (sport == 0) { + isClient = true; + //sport 0 should be shifted back to 0 when using offset Phantasy Star Portable 2 use this + sport = -static_cast(portOffset); + } + + // Create Infrastructure Socket (?) + // Socket is remapped through adhocSockets + int tcpsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + // Valid Socket produced + if (tcpsocket > 0) { + // Change socket MSS + setSockMSS(tcpsocket, PSP_ADHOC_PTP_MSS); + + // Change socket buffer size to be consistent on all platforms. + setSockBufferSize(tcpsocket, SO_SNDBUF, bufsize*5); //PSP_ADHOC_PTP_MSS + setSockBufferSize(tcpsocket, SO_RCVBUF, bufsize*10); //PSP_ADHOC_PTP_MSS*10 + + // Enable KeepAlive + setSockKeepAlive(tcpsocket, true, rexmt_int / 1000000L, rexmt_cnt); + + // Ignore SIGPIPE when supported (ie. BSD/MacOS) + setSockNoSIGPIPE(tcpsocket, 1); + + // Enable Port Re-use + setSockReuseAddrPort(tcpsocket); + + // Apply Default Send Timeout Settings to Socket + setSockTimeout(tcpsocket, SO_SNDTIMEO, rexmt_int); + + // Disable Nagle Algo to send immediately. Or may be we shouldn't disable Nagle since there is PtpFlush function? + setSockNoDelay(tcpsocket, 1); + + // Binding Information for local Port + struct sockaddr_in addr {}; + // addr.sin_len = sizeof(addr); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + if (isLocalServer) { + getLocalIp(&addr); + } + uint16_t requestedport = static_cast(sport + static_cast(portOffset)); + // Avoid getting random port due to port offset when original port wasn't 0 (ie. original_port + port_offset = 65536 = 0) + if (requestedport == 0 && sport > 0) + requestedport = 65535; // Hopefully it will be safe to default it to 65535 since there can't be more than one port that can bumped into 65536 + // Show a warning about privileged ports + if (requestedport != 0 && requestedport < 1024) { + WARN_LOG(Log::sceNet, "sceNetAdhocPtpOpen - Ports below 1024(ie. %hu) may require Admin Privileges", requestedport); + } + addr.sin_port = htons(requestedport); + + // Bound Socket to local Port + if (bind(tcpsocket, (struct sockaddr*)&addr, sizeof(addr)) == 0) { + // Update sport with the port assigned internal->lport = ntohs(local.sin_port) + socklen_t len = sizeof(addr); + if (getsockname(tcpsocket, (struct sockaddr*)&addr, &len) == 0) { + uint16_t boundport = ntohs(addr.sin_port); + if (sport + static_cast(portOffset) >= 65536 || static_cast(boundport) - static_cast(portOffset) <= 0) + WARN_LOG(Log::sceNet, "sceNetAdhocPtpOpen - Wrapped Port Detected: Original(%d) -> Requested(%d), Bound(%d) -> BoundOriginal(%d)", sport, requestedport, boundport, boundport - portOffset); + sport = boundport - portOffset; + } + + // Allocate Memory + AdhocSocket* internal = (AdhocSocket*)malloc(sizeof(AdhocSocket)); + + // Allocated Memory + if (internal != NULL) { + // Find Free Translator ID + // FIXME: We should probably use an increasing index instead of looking for an empty slot from beginning if we want to simulate a real socket id + int i = 0; + i = FindFreeAdhocSocketSlot(); + + // Found Free Translator ID + if (i < MAX_SOCKET) { + // Clear Memory + memset(internal, 0, sizeof(AdhocSocket)); + + // Socket Type + internal->type = SOCK_PTP; + internal->retry_interval = rexmt_int; + internal->retry_count = rexmt_cnt; + internal->nonblocking = flag; + internal->buffer_size = bufsize; + internal->isClient = isClient; + + // Copy Infrastructure Socket ID + internal->data.ptp.id = tcpsocket; + + // Copy Address & Port Information + internal->data.ptp.laddr = *saddr; + internal->data.ptp.paddr = *daddr; + internal->data.ptp.lport = sport; + internal->data.ptp.pport = dport; + + // Link PTP Socket + adhocSockets[i] = internal; + + // Add Port Forward to Router. We may not even need to forward this local port, since PtpOpen usually have port 0 (any port) as source port and followed by PtpConnect (which mean acting as Client), right? + //sceNetPortOpen("TCP", sport); + if (!isClient) + UPnP_Add(IP_PROTOCOL_TCP, isOriPort ? sport : sport + portOffset, sport + portOffset); + + // Switch to non-blocking for futher usage + changeBlockingMode(tcpsocket, 1); + + // PtpOpen starts connecting by itself, as on the PSP (see ptp_open_postoffice). + // TODO: Need to handle ECONNREFUSED better on non-Windows, if there are games that never called PtpConnect and only relies on [blocking?] PtpOpen to get connected + NetAdhocPtp_Connect(i + 1, rexmt_int, 1, false); + + // Workaround to give some time to get connected before returning from PtpOpen over high latency + INFO_LOG(Log::sceNet, "sceNetAdhocPtpOpen - PSP Socket id: %i, Host Socket id: %i", i + 1, tcpsocket); + + // Return PTP Socket id + if (g_Config.bForcedFirstConnect && internal->attemptCount == 1) { + return hleDelayResult(hleLogDebug(Log::sceNet, i + 1), "delayed ptpopen", rexmt_int); + } else { + return hleLogDebug(Log::sceNet, i + 1); + } + } + + // Free Memory + free(internal); + } + } + else { + ERROR_LOG(Log::sceNet, "Socket error (%i) when binding port %u", socket_errno, ntohs(addr.sin_port)); + auto n = GetI18NCategory(I18NCat::NETWORKING); + g_OSD.Show(OSDType::MESSAGE_ERROR, + std::string(n->T("Failed to Bind Port")) + " " + std::to_string(sport + portOffset) + "\n" + std::string(n->T("Please change your Port Offset")), 0.0f, "portbindfail"); + } + + // Close Socket + closesocket(tcpsocket); + + // Port not available (exclusively in use?) + return hleLogDebug(Log::sceNet, SCE_NET_ADHOC_ERROR_PORT_NOT_AVAIL, "port not available"); // SCE_NET_ADHOC_ERROR_PORT_IN_USE; // SCE_NET_ADHOC_ERROR_INVALID_PORT; + } + + // Socket not created + return hleLogDebug(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_ARG, "invalid arg"); +} + +int P2PTransport::PtpAccept(int id, SceNetEtherAddr *addr, uint16_t *port, int timeout, int flag) { + auto& ptpsocket = adhocSockets[id - 1]->data.ptp; + + // Address Information + struct sockaddr_in peeraddr; + memset(&peeraddr, 0, sizeof(peeraddr)); + socklen_t peeraddrlen = sizeof(peeraddr); + int error = 0; + + int newsocket = 0; + // Check if listening socket is ready to accept + newsocket = IsSocketReady(ptpsocket.id, true, false, &error); + if (newsocket > 0) { + // Accept Connection + newsocket = accept(ptpsocket.id, (struct sockaddr*)&peeraddr, &peeraddrlen); + error = socket_errno; + } + + if (newsocket == 0 || (newsocket == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK))) { + if (flag == 0) { + // Simulate blocking behaviour with non-blocking socket + u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; + return WaitBlockingAdhocSocket(threadSocketId, PTP_ACCEPT, id, nullptr, nullptr, timeout, addr, port, "ptp accept"); + } + // Prevent spamming Debug Log with retries of non-bocking socket + else { + VERBOSE_LOG(Log::sceNet, "sceNetAdhocPtpAccept[%i]: Socket Error (%i)", id, error); + } + } + + // Accepted New Connection + if (newsocket > 0) { + int newid = AcceptPtpSocket(id, newsocket, peeraddr, addr, port); + if (newid >= 0) + return newid; + } + + // Action would block + if (flag) + return hleLogVerbose(Log::sceNet, SCE_NET_ADHOC_ERROR_WOULD_BLOCK, "would block"); + + // Timeout + return hleLogVerbose(Log::sceNet, SCE_NET_ADHOC_ERROR_TIMEOUT, "timeout"); +} + +int P2PTransport::PtpClose(int id) { + auto socket = adhocSockets[id - 1]; + + // Close Connection + shutdown(socket->data.ptp.id, SD_RECEIVE); + closesocket(socket->data.ptp.id); + + // Remove Port Forward from Router + //sceNetPortClose("TCP", socket->lport); + //g_PortManager.Remove(IP_PROTOCOL_TCP, isOriPort ? socket->lport : socket->lport + portOffset); // Let's not remove mapping in real-time as it could cause lags/disconnection when joining a room with slow routers + + // Free Memory + free(socket); + + // Free Reference + adhocSockets[id - 1] = NULL; + + // Success + return 0; +} + +int P2PTransport::PtpListen(const SceNetEtherAddr *saddr, int sport, int bufsize, int rexmt_int, int rexmt_cnt, int backlog, bool isClient, int flag) { + // Create Infrastructure Socket (?) + // Socket is remapped through adhocSockets + int tcpsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + // Valid Socket produced + if (tcpsocket > 0) { + // Change socket MSS + setSockMSS(tcpsocket, PSP_ADHOC_PTP_MSS); + + // Change socket buffer size to be consistent on all platforms. + setSockBufferSize(tcpsocket, SO_SNDBUF, bufsize*5); //PSP_ADHOC_PTP_MSS + setSockBufferSize(tcpsocket, SO_RCVBUF, bufsize*10); //PSP_ADHOC_PTP_MSS*10 + + // Enable KeepAlive + setSockKeepAlive(tcpsocket, true, rexmt_int / 1000000L, rexmt_cnt); + + // Ignore SIGPIPE when supported (ie. BSD/MacOS) + setSockNoSIGPIPE(tcpsocket, 1); + + // Enable Port Re-use + setSockReuseAddrPort(tcpsocket); + + // Apply Default Receive Timeout Settings to Socket + setSockTimeout(tcpsocket, SO_RCVTIMEO, rexmt_int); + + // Disable Nagle Algo to send immediately. Or may be we shouldn't disable Nagle since there is PtpFlush function? + setSockNoDelay(tcpsocket, 1); + + // Binding Information for local Port + struct sockaddr_in addr {}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + if (isLocalServer) { + getLocalIp(&addr); + } + uint16_t requestedport = static_cast(sport + static_cast(portOffset)); + // Avoid getting random port due to port offset when original port wasn't 0 (ie. original_port + port_offset = 65536 = 0) + if (requestedport == 0 && sport > 0) + requestedport = 65535; // Hopefully it will be safe to default it to 65535 since there can't be more than one port that can bumped into 65536 + // Show a warning about privileged ports + if (requestedport != 0 && requestedport < 1024) { + WARN_LOG(Log::sceNet, "sceNetAdhocPtpListen - Ports below 1024(ie. %hu) may require Admin Privileges", requestedport); + } + addr.sin_port = htons(requestedport); + + int iResult = 0; + // Bound Socket to local Port + if ((iResult = bind(tcpsocket, (struct sockaddr*)&addr, sizeof(addr))) == 0) { + // Update sport with the port assigned internal->lport = ntohs(local.sin_port) + socklen_t len = sizeof(addr); + if (getsockname(tcpsocket, (struct sockaddr*)&addr, &len) == 0) { + uint16_t boundport = ntohs(addr.sin_port); + if (sport + static_cast(portOffset) >= 65536 || static_cast(boundport) - static_cast(portOffset) <= 0) + WARN_LOG(Log::sceNet, "sceNetAdhocPtpListen - Wrapped Port Detected: Original(%d) -> Requested(%d), Bound(%d) -> BoundOriginal(%d)", sport, requestedport, boundport, boundport - portOffset); + sport = boundport - portOffset; + } + // Switch into Listening Mode + if ((iResult = listen(tcpsocket, backlog)) == 0) { + // Allocate Memory + AdhocSocket* internal = (AdhocSocket*)malloc(sizeof(AdhocSocket)); + + // Allocated Memory + if (internal != NULL) { + // Find Free Translator ID + // FIXME: We should probably use an increasing index instead of looking for an empty slot from beginning if we want to simulate a real socket id + int i = 0; + i = FindFreeAdhocSocketSlot(); + + // Found Free Translator ID + if (i < MAX_SOCKET) { + // Clear Memory + memset(internal, 0, sizeof(AdhocSocket)); + + // Socket Type + internal->type = SOCK_PTP; + internal->retry_interval = rexmt_int; + internal->retry_count = rexmt_cnt; + internal->nonblocking = flag; + internal->buffer_size = bufsize; + internal->isClient = isClient; + + // Copy Infrastructure Socket ID + internal->data.ptp.id = tcpsocket; + + // Copy Address & Port Information + internal->data.ptp.laddr = *saddr; + internal->data.ptp.lport = sport; + + // Flag Socket as Listener + internal->data.ptp.state = ADHOC_PTP_STATE_LISTEN; + + // Link PTP Socket + adhocSockets[i] = internal; + + // Add Port Forward to Router + //sceNetPortOpen("TCP", sport); + UPnP_Add(IP_PROTOCOL_TCP, isOriPort ? sport : sport + portOffset, sport + portOffset); + + // Switch to non-blocking for futher usage + changeBlockingMode(tcpsocket, 1); + + // Return PTP Socket id + return hleLogDebug(Log::sceNet, i + 1, "sceNetAdhocPtpListen - PSP Socket id: %i, Host Socket id: %i", i + 1, tcpsocket); + } + + // Free Memory + free(internal); + } + } + } + else { + auto n = GetI18NCategory(I18NCat::NETWORKING); + g_OSD.Show(OSDType::MESSAGE_ERROR, std::string(n->T("Failed to Bind Port")) + " " + std::to_string(sport + portOffset) + "\n" + std::string(n->T("Please change your Port Offset")), 0.0f, "portbindfail"); + } + + if (iResult == SOCKET_ERROR) { + int error = socket_errno; + ERROR_LOG(Log::sceNet, "sceNetAdhocPtpListen[%i]: Socket Error (%i)", sport, error); + } + + // Close Socket + closesocket(tcpsocket); + + // Port not available (exclusively in use?) + return hleLogDebug(Log::sceNet, SCE_NET_ADHOC_ERROR_PORT_NOT_AVAIL, "port not available"); //SCE_NET_ADHOC_ERROR_PORT_IN_USE; // SCE_NET_ADHOC_ERROR_INVALID_PORT; + } + + // Socket not available + return hleLogDebug(Log::sceNet, SCE_NET_ADHOC_ERROR_SOCKET_ID_NOT_AVAIL, "socket id not available"); +} + +int P2PTransport::PtpSend(int id, const char *data, int *len, int timeout, int flag) { + auto socket = adhocSockets[id - 1]; + auto& ptpsocket = socket->data.ptp; + + // Acquire Network Lock + // _acquireNetworkLock(); + + // Send Data + int sent = 0; + int error = 0; + sent = send(ptpsocket.id, data, *len, MSG_NOSIGNAL); + error = socket_errno; + + // Free Network Lock + // _freeNetworkLock(); + + // Success + if (sent > 0) { + hleEatMicro(122); + // Save Length + *len = sent; + + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpSend[%i:%u]: Sent %u bytes to %s:%u\n", id, ptpsocket.lport, sent, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport); + + // Set to Established on successful Send when an attempt to Connect was initiated + if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) + ptpsocket.state = ADHOC_PTP_STATE_ESTABLISHED; + + // Return Success + return 0; + } + + // Non-Critical Error + else if (sent == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT && (error == ENOTCONN || connectInProgress(error))))) { + // Non-Blocking + if (flag) + return hleLogVerbose(Log::sceNet, SCE_NET_ADHOC_ERROR_WOULD_BLOCK, "would block"); + + // Simulate blocking behaviour with non-blocking socket + u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; + return WaitBlockingAdhocSocket(threadSocketId, PTP_SEND, id, (void*)data, len, timeout, nullptr, nullptr, "ptp send"); + } + + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpSend[%i:%u -> %s:%u]: Result:%i (Error:%i)", id, ptpsocket.lport, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport, sent, error); + + // Change Socket State + ptpsocket.state = ADHOC_PTP_STATE_CLOSED; + + // Disconnected + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_DISCONNECTED, "disconnected"); +} + +int P2PTransport::PtpRecv(int id, void *buf, int *len, int timeout, int flag) { + auto socket = adhocSockets[id - 1]; + auto& ptpsocket = socket->data.ptp; + + // Acquire Network Lock + // _acquireNetworkLock(); + + // TODO: Use a different thread (similar to sceIo) for recvfrom, recv & accept to prevent blocking-socket from blocking emulation + int received = 0; + int error = 0; + + // Receive Data. POSIX: May received 0 bytes when the remote peer already closed the connection. + received = recv(ptpsocket.id, (char*)buf, std::max(0, *len), MSG_NOSIGNAL); + error = socket_errno; + + if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT && (error == ENOTCONN || connectInProgress(error))))) { + if (flag == 0) { + // Simulate blocking behaviour with non-blocking socket + u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; + return WaitBlockingAdhocSocket(threadSocketId, PTP_RECV, id, buf, len, timeout, nullptr, nullptr, "ptp recv"); + } + + return hleLogVerbose(Log::sceNet, SCE_NET_ADHOC_ERROR_WOULD_BLOCK, "would block"); + } + + // Free Network Lock + // _freeNetworkLock(); + + hleEatMicro(50); + + // Received Data + if (received > 0) { + // Save Length + *len = received; + + // Update last recv timestamp, may cause disconnection not detected properly tho + peerlock.lock(); + auto peer = findFriend(&ptpsocket.paddr); + if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); + peerlock.unlock(); + + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpRecv[%i:%u]: Received %u bytes from %s:%u\n", id, ptpsocket.lport, received, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport); + + // Set to Established on successful Recv when an attempt to Connect was initiated + if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) + ptpsocket.state = ADHOC_PTP_STATE_ESTABLISHED; + + // Return Success + return 0; + } + + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpRecv[%i:%u]: Result:%i (Error:%i)", id, ptpsocket.lport, received, error); + + if (*len == 0) + return 0; + + // Change Socket State + ptpsocket.state = ADHOC_PTP_STATE_CLOSED; + + // Disconnected + return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_DISCONNECTED, "disconnected"); +} + +int P2PTransport::PtpFlush(int socketId) { + // Get original Nagle algo value + int n = getSockNoDelay(socketId); + + // Disable Nagle Algo to send immediately + setSockNoDelay(socketId, 1); + + // Send Empty Data just to trigger Nagle on/off effect to flush the send buffer, Do we need to trigger this at all or is it automatically flushed? + //changeBlockingMode(socket->id, nonblock); + int ret = send(socketId, "", 0, MSG_NOSIGNAL); + if (ret == SOCKET_ERROR) ret = socket_errno; + //changeBlockingMode(socket->id, 1); + + // Restore/Enable Nagle Algo + setSockNoDelay(socketId, n); + + return ret; +} diff --git a/Core/HLE/AdhocTransportRelay.cpp b/Core/HLE/AdhocTransportRelay.cpp new file mode 100644 index 0000000000..0ed10fc13d --- /dev/null +++ b/Core/HLE/AdhocTransportRelay.cpp @@ -0,0 +1,982 @@ +// Copyright (c) 2012- 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/. + +// The relay transport: ad hoc sockets carried by an aemu_postoffice server. + +#include "ppsspp_config.h" + +#include +#include + +#include "Common/Net/SocketCompat.h" +#include "Common/Data/Text/I18n.h" +#include "Common/System/OSD.h" +#include "Common/StringUtils.h" +#include "Common/TimeUtil.h" + +#include "Core/Config.h" +#include "Core/CoreTiming.h" +#include "Core/Util/PortManager.h" +#include "Core/HLE/HLE.h" +#include "Core/HLE/ErrorCodes.h" +#include "Core/HLE/sceKernelThread.h" +#include "Core/HLE/sceNetAdhoc.h" +#include "Core/HLE/AdhocTransport.h" +#include "Core/HLE/NetAdhocCommon.h" +#include "Core/HLE/NetInetConstants.h" + +#include "ext/aemu_postoffice/client/postoffice_client.h" + +#ifdef _WIN32 +#undef errno +#define errno WSAGetLastError() +#endif + +void RelayTransport::CloseHostSocket(AdhocSocket *sock) { + if (sock->type == SOCK_PTP) { + // sync + if (sock->connectThread != NULL) { + sock->connectThread->join(); + delete sock->connectThread; + } + void *socket = sock->postofficeHandle; + if (socket != NULL) { + if (sock->data.ptp.state == ADHOC_PTP_STATE_LISTEN) { + ptp_listen_close(socket); + } else { + ptp_close(socket); + } + } + } else { + if (sock->postofficeHandle != NULL) { + pdp_delete(sock->postofficeHandle); + } + } +} + +static void handle_relay_connect_failure() { + auto n = GetI18NCategory(I18NCat::NETWORKING); + if (!trackingRelayFailure) { + trackingRelayFailure = true; + relayLastFailure = std::chrono::steady_clock::now(); + } + int been_failing_since_sec = (std::chrono::steady_clock::now() - relayLastFailure) / std::chrono::seconds(1); + if (been_failing_since_sec < RELAY_FAILURE_DISABLE_THRESHOLD_SEC) { + g_OSD.Show(OSDType::MESSAGE_ERROR, n->T("Failed connecting to relay server"), 4.0f, "relay_status"); + } else { + g_OSD.Show(OSDType::MESSAGE_ERROR, ApplySafeSubstitutions(n->T("Failed connecting to relay server for %1 seconds, relay disabled"), RELAY_FAILURE_DISABLE_THRESHOLD_SEC), 10.0f, "relay_status"); + relayDisabled = true; + } +} + +static void handle_relay_connect_success() { + auto n = GetI18NCategory(I18NCat::NETWORKING); + if (trackingRelayFailure) { + g_OSD.Show(OSDType::MESSAGE_SUCCESS, n->T("Connection to relay has recovered"), 2.0f, "relay_status"); + } + if (relayFirstConnect) { + g_OSD.Show(OSDType::MESSAGE_SUCCESS, n->T("Connected to relay"), 2.0f, "relay_status"); + relayFirstConnect = false; + } + trackingRelayFailure = false; +} + +static void *pdp_postoffice_recover(int idx) { + if (relayDisabled) { + return NULL; + } + + AdhocSocket *internal = adhocSockets[idx]; + if (internal->postofficeHandle != NULL) { + return internal->postofficeHandle; + } + + INFO_LOG(Log::sceNet, "%s: recovering pdp socket id %d", __func__, idx + 1); + + struct aemu_postoffice_sock_addr addr; + auto server_ip = GetAdhocServerIP(); + addr.addr = server_ip.in.sin_addr.s_addr; + addr.port = htons(AEMU_POSTOFFICE_PORT); + + SceNetEtherAddr local_mac; + getLocalMac(&local_mac); + + int state; + internal->postofficeHandle = pdp_create_v4(&addr, (const char *)&local_mac, offset_port_simple(internal->data.pdp.lport), netAdhocUseUdp, internal->postofficePdpEnetReliable, NULL, NULL, &state); + if (state != AEMU_POSTOFFICE_CLIENT_OK) { + ERROR_LOG(Log::sceNet, "%s: failed creating pdp socket on aemu postoffice library, %d", __func__, state); + handle_relay_connect_failure(); + } else { + handle_relay_connect_success(); + } + + INFO_LOG(Log::sceNet, "%s: pdp recovery for id %d: %p", __func__, idx + 1, internal->postofficeHandle); + return internal->postofficeHandle; +} + +static int pdp_peek_next_size_postoffice(int idx) { + AdhocSocket *internal = adhocSockets[idx]; + void *pdp_sock = pdp_postoffice_recover(idx); + if (pdp_sock == NULL) { + return 0; + } + int peek_status = pdp_peek_next_size(pdp_sock); + if (peek_status == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { + pdp_delete(internal->postofficeHandle); + internal->postofficeHandle = NULL; + return 0; + } + return peek_status; +} + +static int pdp_recv_postoffice(int idx, SceNetEtherAddr *saddr, uint16_t *sport, void *data, int *len) { + AdhocSocket *internal = adhocSockets[idx]; + void *pdp_sock = pdp_postoffice_recover(idx); + if (pdp_sock == NULL) { + return SOCKET_ERROR; + } + + int sport_copy = 0; + SceNetEtherAddr saddr_copy = {0}; + int len_copy = *len; + + int pdp_recv_status = pdp_recv(pdp_sock, (char *)&saddr_copy, &sport_copy, (char *)data, &len_copy, true); + if (pdp_recv_status == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { + handle_relay_connect_failure(); + pdp_delete(internal->postofficeHandle); + internal->postofficeHandle = NULL; + return SOCKET_ERROR; + } + if (pdp_recv_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK) { + return SOCKET_ERROR; + } + // Out of the group, nothing from anyone else reaches us (pspautotests net/adhoc/pair/vanish). + if (offAdhocNetwork() && !isLocalMac(&saddr_copy)) { + return SOCKET_ERROR; + } + + *len = len_copy; + if (saddr != NULL) { + *saddr = saddr_copy; + } + if (sport != NULL) { + *sport = reverse_port_simple(sport_copy); + } + return 0; +} + +int RelayTransport::DoBlockingPdpRecv(AdhocSocketRequest &req, s64 &result) { + int next_size = pdp_peek_next_size_postoffice(req.id - 1); + if (next_size == 0) { + // no next packet + } else if (next_size > *req.length) { + // next is larger than current buffer + result = SCE_NET_ADHOC_ERROR_NOT_ENOUGH_SPACE; + *req.length = next_size; + return 0; + } else if (pdp_recv_postoffice(req.id - 1, req.remoteMAC, req.remotePort, req.buffer, req.length) == 0) { + // we got data into the request + result = 0; + return 0; + } + + u64 now = CoreTiming::GetGlobalTimeUs(); + if (req.timeout == 0 || now - req.startTime <= req.timeout) { + // Try again later + return -1; + } + result = SCE_NET_ADHOC_ERROR_TIMEOUT; + DEBUG_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i]: Socket Error (%i)", req.id, EAGAIN); + return 0; +} + +int RelayTransport::PdpRecv(int id, SceNetEtherAddr *saddr, uint16_t *sport, void *buf, int *len, u32 timeout, int flag) { + int next_size = pdp_peek_next_size_postoffice(id - 1); + if (next_size == 0) { + // no next packet + } else if (next_size > *len) { + // next is larger than current buffer + *len = next_size; + return SCE_NET_ADHOC_ERROR_NOT_ENOUGH_SPACE; + } else if (pdp_recv_postoffice(id - 1, saddr, sport, buf, len) == 0) { + // we got data + hleEatMicro(50); + return 0; + } + + if (flag == 0) { + // Simulate blocking behaviour with non-blocking socket + u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | adhocSockets[id - 1]->data.pdp.id; + return WaitBlockingAdhocSocket(threadSocketId, PDP_RECV, id, buf, len, timeout, saddr, sport, "pdp recv"); + } + + VERBOSE_LOG(Log::sceNet, "%08x=sceNetAdhocPdpRecv: would block", SCE_NET_ADHOC_ERROR_WOULD_BLOCK); // Temporary fix to avoid a crash on the Logs due to trying to Logs syscall's argument from another thread (ie. AdhocMatchingInput thread) + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; +} + +static int pdp_send_postoffice(int idx, const SceNetEtherAddr *daddr, uint16_t dport, const void *data, int len) { + AdhocSocket *internal = adhocSockets[idx]; + void *pdp_sock = pdp_postoffice_recover(idx); + if (pdp_sock == NULL) { + return SOCKET_ERROR; + } + + // Out of the group, it goes into the air and reaches nobody (pspautotests net/adhoc/pair/vanish). + if (offAdhocNetwork() && !isLocalMac(daddr)) { + return 0; + } + + SceNetEtherAddr fixed_daddr = *daddr; + fixGameMac(&fixed_daddr); + + int pdp_send_status = pdp_send(pdp_sock, (const char *)&fixed_daddr, offset_port_simple(dport), (char *)data, len, true); + if (pdp_send_status == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { + handle_relay_connect_failure(); + pdp_delete(internal->postofficeHandle); + internal->postofficeHandle = NULL; + return SOCKET_ERROR; + } + if (pdp_send_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK) { + return SOCKET_ERROR; + } + if (pdp_send_status == AEMU_POSTOFFICE_CLIENT_OUT_OF_MEMORY) { + // this is pretty critical + ERROR_LOG(Log::sceNet, "%s: critical: huge client buf %d what is going on please fix", __func__, len); + } + return 0; +} + +int RelayTransport::PdpSendTo(int id, const SceNetEtherAddr *mac, uint16_t dport, const sockaddr_in &target, const void *data, int len, int *error) { + if (pdp_send_postoffice(id - 1, mac, dport, data, len) == 0) { + return len; + } + *error = EAGAIN; + return SOCKET_ERROR; +} + +static int ptp_send_postoffice(int idx, const void *data, int *len) { + if (relayDisabled) { + return SOCKET_ERROR; + } + + AdhocSocket *internal = adhocSockets[idx]; + + if (internal->postofficeHandle == NULL) { + // this should only happen on ptp_open sockets, where ptp_connect is still in progress on another thread + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; + } + + // Out of the group, the send succeeds and the data goes nowhere (pspautotests net/adhoc/pair/vanish). + if (offAdhocNetwork()) { + return 0; + } + if (ptpPeerGone(internal)) { + return SOCKET_ERROR; + } + + if (*len > AEMU_POSTOFFICE_PTP_BLOCK_MAX) { + // force fragmentation for giant sends + *len = AEMU_POSTOFFICE_PTP_BLOCK_MAX; + } + + int ptp_send_status = ptp_send(internal->postofficeHandle, (const char *)data, *len, true); + ptpNoteSend(internal); + if (ptp_send_status == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { + // the session is dead, need to be reflected to the other side + // this is not necessarily a relay failure, could simply be the other side disconnecting + //handle_relay_connect_failure(); + return SOCKET_ERROR; + } + if (ptp_send_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK) { + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; + } + if (ptp_send_status == AEMU_POSTOFFICE_CLIENT_OUT_OF_MEMORY) { + // this is pretty critical + ERROR_LOG(Log::sceNet, "%s: critical: huge client buf %d what is going on please fix", __func__, len ? *len : 0); + } + + return 0; +} + +int RelayTransport::DoBlockingPtpSend(AdhocSocketRequest &req, s64 &result) { + auto sock = adhocSockets[req.id - 1]; + auto& ptpsocket = sock->data.ptp; + int ret = ptp_send_postoffice(req.id - 1, req.buffer, req.length); + if (ret == 0) { + // sent + // Set to Established on successful Send when an attempt to Connect was initiated + if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) + ptpSetEstablished(sock); + + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpSend[%i:%u]: Sent %u bytes to %s:%u\n", req.id, ptpsocket.lport, ret, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport); + + result = 0; + return 0; + } + if (ret == SOCKET_ERROR) { + ptpsocket.state = ADHOC_PTP_STATE_CLOSED; + result = SCE_NET_ADHOC_ERROR_DISCONNECTED; + + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpSend[%i]: Socket Error (%i)", req.id, 0); + + return 0; + } + + // SCE_NET_ADHOC_ERROR_WOULD_BLOCK + u64 now = CoreTiming::GetGlobalTimeUs(); + if (req.timeout == 0 || now - req.startTime <= req.timeout) { + return -1; + } + result = SCE_NET_ADHOC_ERROR_TIMEOUT; + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpSend[%i]: Socket Error (%i)", req.id, EAGAIN); + return 0; +} + +static int ptp_recv_postoffice(int idx, void *data, int *len) { + if (relayDisabled) { + return SOCKET_ERROR; + } + + AdhocSocket *internal = adhocSockets[idx]; + + if (internal->postofficeHandle == NULL) { + // this should only happen on ptp_open sockets, where ptp_connect is still in progress on another thread + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; + } + + // Out of the group, nothing arrives, and the connection doesn't fail either (pspautotests + // net/adhoc/pair/vanish). + if (offAdhocNetwork()) { + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; + } + if (ptpPeerGone(internal)) { + return SOCKET_ERROR; + } + + int len_copy = *len; + + int ptp_recv_status = ptp_recv(internal->postofficeHandle, (char *)data, &len_copy, true); + if (ptp_recv_status == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { + // the session is dead, need to be reflected to the other side + // this is not necessarily a relay failure, could simply be the other side disconnecting + //handle_relay_connect_failure(); + return SOCKET_ERROR; + } + if (ptp_recv_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK) { + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; + } + + // AEMU_POSTOFFICE_CLIENT_SESSION_DATA_TRUNC is okay, it just means it has data in it's user space buffer + + *len = len_copy; + return 0; +} + +int RelayTransport::DoBlockingPtpRecv(AdhocSocketRequest &req, s64 &result) { + auto& ptpsocket = adhocSockets[req.id - 1]->data.ptp; + int ret = ptp_recv_postoffice(req.id - 1, req.buffer, req.length); + if (ret == 0) { + // we got data + result = 0; + return 0; + } + if (ret == SOCKET_ERROR) { + // the socket died, let the game know + ptpsocket.state = ADHOC_PTP_STATE_CLOSED; + result = SCE_NET_ADHOC_ERROR_DISCONNECTED; + return 0; + } + + // SCE_NET_ADHOC_ERROR_WOULD_BLOCK + u64 now = CoreTiming::GetGlobalTimeUs(); + if (req.timeout == 0 || now - req.startTime <= req.timeout) { + return -1; + } + result = SCE_NET_ADHOC_ERROR_TIMEOUT; + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpRecv[%i]: Socket Error (%i)", req.id, EAGAIN); + return 0; +} + +static void *ptp_listen_postoffice_recover(int idx) { + if (relayDisabled) { + return NULL; + } + + AdhocSocket *internal = adhocSockets[idx]; + if (internal->postofficeHandle != NULL) { + return internal->postofficeHandle; + } + + INFO_LOG(Log::sceNet, "%s: recovering ptp listen socket id %d", __func__, idx + 1); + + struct aemu_postoffice_sock_addr addr; + auto server_ip = GetAdhocServerIP(); + addr.addr = server_ip.in.sin_addr.s_addr; + addr.port = htons(AEMU_POSTOFFICE_PORT); + + int state; + internal->postofficeHandle = ptp_listen_v4(&addr, (const char*)&internal->data.ptp.laddr, internal->data.ptp.lport + portOffset, netAdhocUseUdp, NULL, NULL, &state); + + if (state != AEMU_POSTOFFICE_CLIENT_OK) { + ERROR_LOG(Log::sceNet, "%s: failed recovering ptp listen socket, %d", __func__, state); + handle_relay_connect_failure(); + } else { + handle_relay_connect_success(); + } + + INFO_LOG(Log::sceNet, "%s: ptp listen recovery for id %d: %p", __func__, idx + 1, internal->postofficeHandle); + + return internal->postofficeHandle; +} + +static int ptp_accept_postoffice(int idx, SceNetEtherAddr *saddr, uint16_t *sport) { + void *ptp_listen_socket = ptp_listen_postoffice_recover(idx); + + if (ptp_listen_socket == NULL) { + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; + } + + int state = 0; + int port_cpy = 0; + SceNetEtherAddr mac_cpy = {0}; + void *new_ptp_socket = ptp_accept(ptp_listen_socket, (char *)&mac_cpy, &port_cpy, true, NULL, NULL, &state); + if (new_ptp_socket == NULL) { + if (state == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { + handle_relay_connect_failure(); + ptp_listen_close(adhocSockets[idx]->postofficeHandle); + adhocSockets[idx]->postofficeHandle = NULL; + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; + } + if (state == AEMU_POSTOFFICE_CLIENT_OUT_OF_MEMORY) { + // pretty critical here + ERROR_LOG(Log::sceNet, "%s: aemu_postoffice ran out of memory to accept a new connection", __func__); + } + // AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; + } + + // we have a new socket + handle_relay_connect_success(); + AdhocSocket *internal = (AdhocSocket *)malloc(sizeof(AdhocSocket)); + if (internal == NULL) { + ERROR_LOG(Log::sceNet, "%s: critical: ran out of heap memory while accepting new connection", __func__); + ptp_close(new_ptp_socket); + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; + } + memset(internal, 0, sizeof(AdhocSocket)); + internal->retry_interval = adhocSockets[idx]->retry_interval; + internal->retry_count = adhocSockets[idx]->retry_count; + internal->attemptCount = 1; // Connected, so a closed socket reports DISCONNECTED (as the native accept does) + + internal->type = SOCK_PTP; + internal->postofficeHandle = new_ptp_socket; + internal->data.ptp.laddr = adhocSockets[idx]->data.ptp.laddr; + internal->data.ptp.lport = adhocSockets[idx]->data.ptp.lport; + internal->data.ptp.paddr = mac_cpy; + internal->data.ptp.pport = reverse_port_simple(port_cpy); + ptpSetEstablished(internal); + internal->data.ptp.rcv_sb_cc = adhocSockets[idx]->data.ptp.rcv_sb_cc; + internal->data.ptp.snd_sb_cc = adhocSockets[idx]->data.ptp.snd_sb_cc; + internal->data.ptp.id = AEMU_POSTOFFICE_ID_BASE + idx; + internal->flags = 0; + internal->connectThread = NULL; + + AdhocSocket **slot = NULL; + int i; + i = FindFreeAdhocSocketSlot(); + if (i < MAX_SOCKET) { + slot = &adhocSockets[i]; + } + + if (slot == NULL) { + ptp_close(new_ptp_socket); + free(internal); + ERROR_LOG(Log::sceNet, "%s: critical: cannot find an empty mapper slot", __func__); + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; + } + + *slot = internal; + + if (saddr != NULL) { + *saddr = mac_cpy; + } + if (sport != NULL) { + *sport = reverse_port_simple(port_cpy); + } + + INFO_LOG(Log::sceNet, "%s: accepted ptp socket with id %d %p", __func__, i + 1, internal->postofficeHandle); + return i + 1; +} + +int RelayTransport::DoBlockingPtpAccept(AdhocSocketRequest &req, s64 &result) { + int ret = ptp_accept_postoffice(req.id - 1, req.remoteMAC, req.remotePort); + if (ret >= 0) { + result = ret; + return 0; + } + + // SCE_NET_ADHOC_ERROR_WOULD_BLOCK + u64 now = CoreTiming::GetGlobalTimeUs(); + if (req.timeout == 0 || now - req.startTime <= req.timeout) { + return -1; + } + result = SCE_NET_ADHOC_ERROR_TIMEOUT; + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpAccept[%i]: Socket Error (%i)", req.id, EAGAIN); + return 0; +} + +static int ptp_connect_postoffice(int idx, const char *caller) { + if (relayDisabled) { + return SCE_NET_ADHOC_ERROR_CONNECTION_REFUSED; + } + + AdhocSocket *internal = adhocSockets[idx]; + + struct aemu_postoffice_sock_addr addr; + auto server_ip = GetAdhocServerIP(); + addr.addr = server_ip.in.sin_addr.s_addr; + addr.port = htons(AEMU_POSTOFFICE_PORT); + + if (internal->postofficeHandle != NULL) { + return 0; + } + + if (internal->connectThreadDone) { + if (internal->connectThread != NULL) { + internal->connectThread->join(); + delete internal->connectThread; + internal->connectThread = NULL; + } + + internal->connectThreadDone = false; + internal->connectThreadResult = 0; + + internal->connectThread = new std::thread([internal, addr, idx] { + int state; + SceNetEtherAddr fixed_daddr = internal->data.ptp.paddr; + fixGameMac(&fixed_daddr); + void *ptp_socket = ptp_connect_v4(&addr, (const char *)&internal->data.ptp.laddr, offset_port_simple(internal->data.ptp.lport), (const char *)&fixed_daddr, offset_port_simple(internal->data.ptp.pport), netAdhocUseUdp, NULL, NULL, &state); + if (ptp_socket == NULL) { + internal->connectThreadResult = SCE_NET_ADHOC_ERROR_CONNECTION_REFUSED; + ERROR_LOG(Log::sceNet, "%s: failed connecting to ptp socket, %d", __func__, state); + // do not count ptp connect failure as relay failure, since it could be the other client not accepting the connection + //handle_relay_connect_failure(); + internal->connectThreadDone = true; + return; + } + // see above, ptp connect result is not used for checking if relay is working + //handle_relay_connect_success(); + internal->postofficeHandle = ptp_socket; + internal->connectThreadResult = 0; + INFO_LOG(Log::sceNet, "%s: connected ptp socket with id %d %p", __func__, idx + 1, internal->postofficeHandle); + internal->connectThreadDone = true; + return; + });; + + DEBUG_LOG(Log::sceNet, "%s: started connect thread on id %d", __func__, idx + 1); + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; + } + + DEBUG_LOG(Log::sceNet, "%s: id %d is connecting", __func__, idx + 1); + return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; +} + +bool RelayTransport::PtpConnectionUp(AdhocSocket *sock) { + return sock->postofficeHandle != NULL; +} + +int RelayTransport::PtpConnect(int id, const sockaddr_in &sin, int *error) { + if (ptp_connect_postoffice(id - 1, __func__) != 0) { + *error = EAGAIN; + return SOCKET_ERROR; + } + return 0; +} + +int RelayTransport::PtpConnectPoll(AdhocSocket *sock, int *error) { + if (sock->postofficeHandle != NULL) { + // the handle is there, we're ready to go + *error = 0; + return 1; + } + if (sock->connectThreadDone && sock->connectThreadResult == SCE_NET_ADHOC_ERROR_CONNECTION_REFUSED) { + *error = ECONNREFUSED; + } else { + *error = EAGAIN; + } + return SOCKET_ERROR; +} + +static int pdp_create_postoffice(const SceNetEtherAddr *saddr, int sport, int bufsize, bool enet_reliable) { + if (relayDisabled) { + return hleLogDebug(Log::sceNet, ERROR_NET_NO_SPACE, "relay disabled"); + } + + AdhocSocket *internal = (AdhocSocket*)malloc(sizeof(AdhocSocket)); + if (internal == NULL) { + return hleLogDebug(Log::sceNet, ERROR_NET_NO_SPACE, "net no space"); + } + memset(internal, 0, sizeof(AdhocSocket)); + + internal->type = SOCK_PDP; + internal->postofficeHandle = NULL; + internal->data.pdp.laddr = *saddr; + internal->data.pdp.lport = sport; + internal->data.pdp.rcv_sb_cc = bufsize; + internal->flags = 0; + internal->lastAttempt = 0; + internal->internalLastAttempt = 0; + internal->postofficePdpEnetReliable = enet_reliable; + + AdhocSocket **free_slot = NULL; + int i; + i = FindFreeAdhocSocketSlot(); + if (i < MAX_SOCKET) { + free_slot = &adhocSockets[i]; + } + if (free_slot == NULL) { + free(internal); + return ERROR_NET_NO_SPACE; + } + + internal->data.pdp.id = AEMU_POSTOFFICE_ID_BASE + i; + + *free_slot = internal; + pdp_postoffice_recover(i); + INFO_LOG(Log::sceNet, "%s: created pdp socket with id %d %p", __func__, i + 1, internal->postofficeHandle); + return i + 1; +} + +int RelayTransport::PdpCreate(const SceNetEtherAddr *saddr, int port, int bufferSize, u32 flag, bool isClient, bool enetReliable) { + return pdp_create_postoffice(saddr, port, bufferSize, enetReliable); +} + +static bool postoffice_can_recv_or_listen_has_request(int idx) { + AdhocSocket *internal = adhocSockets[idx]; + if (internal->type == SOCK_PTP) { + if (internal->data.ptp.state == ADHOC_PTP_STATE_LISTEN) { + void *ptp_listen_handle = ptp_listen_postoffice_recover(idx); + if (ptp_listen_handle == NULL) { + return false; + } + int has_request = ptp_listen_has_request(ptp_listen_handle); + if (has_request == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { + internal->postofficeHandle = NULL; + ptp_listen_close(ptp_listen_handle); + return false; + } + if (has_request) { + return true; + } + } else if (internal->data.ptp.state == ADHOC_PTP_STATE_ESTABLISHED) { + void *ptp_handle = internal->postofficeHandle; + if (ptp_handle == NULL) { + return false; + } + int peek_len = ptp_peek_next_size(ptp_handle); + if (peek_len == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { + return false; + } + if (peek_len > 0) { + return true; + } + } + } else { + void *pdp_handle = pdp_postoffice_recover(idx); + if (pdp_handle == NULL) { + return false; + } + int peek_len = pdp_buffered_data_size(pdp_handle); + if (peek_len == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { + internal->postofficeHandle = NULL; + pdp_delete(pdp_handle); + return false; + } + if (peek_len > 0) { + return true; + } + } + return false; +} + +static bool postoffice_can_send(int idx) { + AdhocSocket *internal = adhocSockets[idx]; + if (internal->type == SOCK_PTP && internal->data.ptp.state == ADHOC_PTP_STATE_ESTABLISHED) { + void *ptp_handle = internal->postofficeHandle; + if (ptp_handle == NULL) { + return false; + } + if (ptp_is_dead(ptp_handle)) { + return false; + } + return ptp_send_buf_not_full(ptp_handle); + } else { + void *pdp_handle = pdp_postoffice_recover(idx); + if (pdp_handle == NULL) { + return false; + } + if (pdp_is_dead(pdp_handle)) { + internal->postofficeHandle = NULL; + pdp_delete(pdp_handle); + return false; + } + return pdp_send_buf_not_full(pdp_handle); + } + return false; +} + +int RelayTransport::PollWait(SceNetAdhocPollSd *sds, int count, int timeout, AdhocPollState *states) { + for (int i = 0; i < count; i++) { + if (sds[i].id > 0 && sds[i].id <= MAX_SOCKET && adhocSockets[sds[i].id - 1] != NULL) { + states[i].readable = postoffice_can_recv_or_listen_has_request(sds[i].id - 1); + states[i].writable = (sds[i].events & ADHOC_EV_SEND) && postoffice_can_send(sds[i].id - 1); + } + } + return 0; +} + +static int pdp_delete_postoffice(int idx) { + AdhocSocket *internal = adhocSockets[idx]; + if (internal->postofficeHandle != NULL) { + pdp_delete(internal->postofficeHandle); + } + adhocSockets[idx] = NULL; + free(internal); + INFO_LOG(Log::sceNet, "%s: closed pdp socket with id %d", __func__, idx + 1); + return 0; +} + +int RelayTransport::PdpDelete(int id) { + return pdp_delete_postoffice(id - 1); +} + +void RelayTransport::PdpUpdateRecvStat(int id, AdhocSocket *sock) { + void *postofficeHandle = pdp_postoffice_recover(id - 1); + if (postofficeHandle != NULL) { + sock->data.pdp.rcv_sb_cc = pdp_buffered_data_size(postofficeHandle); + } +} + +void RelayTransport::PtpUpdateRecvStat(AdhocSocket *sock) { + if (sock->postofficeHandle != NULL) { + sock->data.ptp.rcv_sb_cc = ptp_peek_next_size(sock->postofficeHandle); + } else { + sock->data.ptp.rcv_sb_cc = 0; + } +} + +static int ptp_open_postoffice(const SceNetEtherAddr *saddr, uint16_t sport, const SceNetEtherAddr *daddr, uint16_t dport, uint32_t bufsize, uint32_t rexmt_int, int rexmt_cnt) { + AdhocSocket *internal = (AdhocSocket *)malloc(sizeof(AdhocSocket)); + if (internal == NULL) { + ERROR_LOG(Log::sceNet, "%s: ran out of heap memory trying to open ptp socket", __func__); + return ERROR_NET_NO_SPACE; + } + memset(internal, 0, sizeof(AdhocSocket)); + internal->retry_interval = rexmt_int; + internal->retry_count = rexmt_cnt; + + internal->type = SOCK_PTP; + internal->postofficeHandle = NULL; + internal->data.ptp.state = ADHOC_PTP_STATE_CLOSED; + internal->data.ptp.laddr = *saddr; + internal->data.ptp.lport = sport == 0 ? get_random_unused_ptp_port() : sport; + internal->data.ptp.paddr = *daddr; + internal->data.ptp.pport = dport; + internal->data.ptp.rcv_sb_cc = bufsize; + internal->data.ptp.snd_sb_cc = 0; + internal->flags = 0; + internal->connectThread = NULL; + internal->connectThreadDone = true; + internal->lastAttempt = 0; + internal->internalLastAttempt = 0; + + AdhocSocket **slot = NULL; + int i; + i = FindFreeAdhocSocketSlot(); + if (i < MAX_SOCKET) { + slot = &adhocSockets[i]; + } + + if (slot == NULL) { + free(internal); + ERROR_LOG(Log::sceNet, "%s: cannot find free socket mapper slot while opening ptp socket", __func__); + return ERROR_NET_NO_SPACE; + } + + internal->data.ptp.id = AEMU_POSTOFFICE_ID_BASE + i; + + *slot = internal; + INFO_LOG(Log::sceNet, "%s: created ptp socket with id %d", __func__, i + 1); + + // PtpOpen starts connecting by itself: on the PSP the client is ESTABLISHED right after it (pspautotests + // net/adhoc/ptp). The Warriors sends without calling PtpConnect. + NetAdhocPtp_Connect(i + 1, 0, 1, false); + + return i + 1; +} + +int RelayTransport::PtpOpen(const SceNetEtherAddr *saddr, int sport, const SceNetEtherAddr *daddr, int dport, int bufsize, int rexmt_int, int rexmt_cnt, int flag) { + return ptp_open_postoffice(saddr, sport, daddr, dport, bufsize, rexmt_int, rexmt_cnt); +} + +int RelayTransport::PtpAccept(int id, SceNetEtherAddr *addr, uint16_t *port, int timeout, int flag) { + int newsocket = ptp_accept_postoffice(id - 1, addr, port); + if (newsocket >= 0) { + return newsocket; + } + + // SCE_NET_ADHOC_ERROR_WOULD_BLOCK + if (flag == 0) { + // Simulate blocking behaviour with non-blocking socket + u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | adhocSockets[id - 1]->data.ptp.id; + return WaitBlockingAdhocSocket(threadSocketId, PTP_ACCEPT, id, nullptr, nullptr, timeout, addr, port, "ptp accept"); + } + // Prevent spamming Debug Log with retries of non-bocking socket + VERBOSE_LOG(Log::sceNet, "sceNetAdhocPtpAccept[%i]: Socket Error (%i)", id, EAGAIN); + return hleLogVerbose(Log::sceNet, SCE_NET_ADHOC_ERROR_WOULD_BLOCK, "would block"); +} + +static int ptp_close_postoffice(int idx) { + AdhocSocket *internal = adhocSockets[idx]; + + // sync + if (internal->connectThread != NULL) { + internal->connectThread->join(); + delete internal->connectThread; + } + + void *socket = internal->postofficeHandle; + if (socket != NULL) { + if (internal->data.ptp.state == ADHOC_PTP_STATE_LISTEN) { + ptp_listen_close(socket); + } else { + ptp_close(socket); + } + } + adhocSockets[idx] = NULL; + free(internal); + INFO_LOG(Log::sceNet, "%s: closed ptp socket with id %d", __func__, idx + 1); + return 0; +} + +int RelayTransport::PtpClose(int id) { + return ptp_close_postoffice(id - 1); +} + +static int ptp_listen_postoffice(const SceNetEtherAddr *saddr, uint16_t sport, uint32_t bufsize, uint32_t rexmt_int, int rexmt_cnt) { + if (relayDisabled) { + return ERROR_NET_NO_SPACE; + } + + // server connect delegated to accept + AdhocSocket *internal = (AdhocSocket *)malloc(sizeof(AdhocSocket)); + if (internal == NULL) { + ERROR_LOG(Log::sceNet, "%s: out of heap memory when creating ptp listen socket", __func__); + return ERROR_NET_NO_SPACE; + } + memset(internal, 0, sizeof(AdhocSocket)); + internal->retry_interval = rexmt_int; + internal->retry_count = rexmt_cnt; + + internal->postofficeHandle = NULL; + internal->type = SOCK_PTP; + internal->data.ptp.laddr = *saddr; + internal->data.ptp.lport = sport; + internal->data.ptp.state = ADHOC_PTP_STATE_LISTEN; + internal->data.ptp.rcv_sb_cc = bufsize; + internal->data.ptp.snd_sb_cc = 0; + internal->flags = 0; + internal->connectThread = NULL; + internal->lastAttempt = 0; + internal->internalLastAttempt = 0; + + AdhocSocket **slot = NULL; + int i; + i = FindFreeAdhocSocketSlot(); + if (i < MAX_SOCKET) { + slot = &adhocSockets[i]; + } + + if (slot == NULL) { + ERROR_LOG(Log::sceNet, "%s: out of socket slots when creating adhoc ptp listen socket", __func__); + free(internal); + return ERROR_NET_NO_SPACE; + } + + internal->data.ptp.id = AEMU_POSTOFFICE_ID_BASE + i; + + *slot = internal; + ptp_listen_postoffice_recover(i); + INFO_LOG(Log::sceNet, "%s: created ptp listen socket with id %d %p", __func__, i + 1, internal->postofficeHandle); + return i + 1; +} + +int RelayTransport::PtpListen(const SceNetEtherAddr *saddr, int sport, int bufsize, int rexmt_int, int rexmt_cnt, int backlog, bool isClient, int flag) { + return ptp_listen_postoffice(saddr, sport, bufsize, rexmt_int, rexmt_cnt); +} + +int RelayTransport::PtpSend(int id, const char *data, int *len, int timeout, int flag) { + auto& ptpsocket = adhocSockets[id - 1]->data.ptp; + int sent = ptp_send_postoffice(id - 1, data, len); + if (sent == 0) { + hleEatMicro(122); + return 0; + } + if (sent == SOCKET_ERROR) { + ptpsocket.state = ADHOC_PTP_STATE_CLOSED; + return SCE_NET_ADHOC_ERROR_DISCONNECTED; + } + + // SCE_NET_ADHOC_ERROR_WOULD_BLOCK + if (flag) + return hleLogVerbose(Log::sceNet, SCE_NET_ADHOC_ERROR_WOULD_BLOCK, "would block"); + + // Simulate blocking behaviour with non-blocking socket + u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; + return WaitBlockingAdhocSocket(threadSocketId, PTP_SEND, id, (void*)data, len, timeout, nullptr, nullptr, "ptp send"); +} + +int RelayTransport::PtpRecv(int id, void *buf, int *len, int timeout, int flag) { + auto& ptpsocket = adhocSockets[id - 1]->data.ptp; + int received = ptp_recv_postoffice(id - 1, buf, len); + if (received == 0) { + // we got data + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpRecv[%i:%u]: Result:%i (Error:%i)", id, ptpsocket.lport, received, 0); + hleEatMicro(50); + return 0; + } + if (received == SOCKET_ERROR) { + // the socket died, let the game know + ptpsocket.state = ADHOC_PTP_STATE_CLOSED; + DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpRecv[%i:%u]: Result:%i (Error:%i)", id, ptpsocket.lport, received, 0); + return SCE_NET_ADHOC_ERROR_DISCONNECTED; + } + + // SCE_NET_ADHOC_ERROR_WOULD_BLOCK + if (flag == 0) { + // Simulate blocking behaviour with non-blocking socket + u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; + return WaitBlockingAdhocSocket(threadSocketId, PTP_RECV, id, buf, len, timeout, nullptr, nullptr, "ptp recv"); + } + + return hleLogVerbose(Log::sceNet, SCE_NET_ADHOC_ERROR_WOULD_BLOCK, "would block"); +} + +int RelayTransport::PtpFlush(int socketId) { + // there's no manual flushing in the relay library, it's by default no delay there + return 0; +} diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index ebdc1192e5..da31ee1c22 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -54,6 +54,7 @@ #include "Core/HLE/sceKernelInterrupt.h" #include "Core/HLE/sceKernelMemory.h" #include "Core/HLE/sceNetAdhoc.h" +#include "Core/HLE/AdhocTransport.h" #include "Core/Instance.h" #include "proAdhoc.h" @@ -97,7 +98,6 @@ int adhocConnectionType = ADHOC_CONNECT; int gameModeSocket = (int)INVALID_SOCKET; // UDP/PDP socket? on Master only? int gameModeBuffSize = 0; u8* gameModeBuffer = nullptr; -extern bool serverHasRelay; GameModeArea masterGameModeArea; std::vector replicaGameModeAreas; std::vector requiredGameModeMacs; @@ -446,39 +446,7 @@ void deleteAllAdhocSockets() { if (adhocSockets[i] != NULL) { auto sock = adhocSockets[i]; - if (serverHasRelay) { - if (sock->type == SOCK_PTP) { - // sync - if (sock->connectThread != NULL) { - sock->connectThread->join(); - delete sock->connectThread; - } - void *socket = sock->postofficeHandle; - if (socket != NULL) { - if (sock->data.ptp.state == ADHOC_PTP_STATE_LISTEN) { - ptp_listen_close(socket); - } else { - ptp_close(socket); - } - } - } else { - if (sock->postofficeHandle != NULL) { - pdp_delete(sock->postofficeHandle); - } - } - } else { - int fd = -1; - if (sock->type == SOCK_PTP) - fd = sock->data.ptp.id; - else if (sock->type == SOCK_PDP) - fd = sock->data.pdp.id; - - if (fd > 0) { - // Close Socket - shutdown(fd, SD_RECEIVE); - closesocket(fd); - } - } + Transport().CloseHostSocket(sock); // Free Memory free(adhocSockets[i]); @@ -1521,7 +1489,7 @@ static void StartAdhocctlLogin() { // If Server is at localhost and we are in P2P mode, try to bind socket to specific adapter before connecting to prevent 2nd instance being recognized as already existing 127.0.0.1 by adhocctl server // (may not works in WinXP/2003 for IPv4 due to "Weak End System" model) struct adhocctl_addr_v4 bindAddr = {0}; - const bool bind = isLocalServer && !serverHasRelay; + const bool bind = isLocalServer && Transport().BindServerConnectionToLocalIp(); if (bind) { bindAddr.ip = g_localhostIP.in.sin_addr.s_addr; bindAddr.port = 0; diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index cddca16fdb..6bbbf0da0e 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -57,6 +57,7 @@ #include "Core/HLE/sceKernelModule.h" #include "Core/HLE/sceKernelInterrupt.h" #include "Core/HLE/sceNetAdhoc.h" +#include "Core/HLE/AdhocTransport.h" #include "Core/HLE/sceNet.h" #include "Core/HLE/proAdhocServer.h" #include "Core/HLE/KernelWaitHelpers.h" @@ -154,10 +155,6 @@ int gameModeNotifyEvent = -1; std::thread aemuPostofficeEnetThread; std::atomic aemuPostofficeEnetPumping = false; -#define AEMU_POSTOFFICE_PORT 27313 -#define AEMU_POSTOFFICE_ID_BASE 2048 -#define RELAY_FAILURE_DISABLE_THRESHOLD_SEC 15 - // Without the local network permission, Android 17+ fails sends to LAN peers with EPERM. static void WarnIfLocalNetworkBlocked(int error) { #if PPSSPP_PLATFORM(ANDROID) @@ -373,7 +370,6 @@ static AdhocDataMode AdhocGetServerDataMode(std::string_view server) { int AcceptPtpSocket(int ptpId, int newsocket, sockaddr_in& peeraddr, SceNetEtherAddr* addr, u16_le* port); int PollAdhocSocket(SceNetAdhocPollSd* sds, int count, int timeout, int nonblock); -static int FindFreeAdhocSocketSlot(); // A PSP hands out socket IDs in increasing order (pspautotests net/adhoc/ptp), starting over from 1 // after sceNetAdhocTerm (pspautotests net/adhoc/socketids). static int nextAdhocSocketSlot = 0; @@ -381,7 +377,17 @@ int FlushPtpSocket(int socketId); int RecreatePtpSocket(int ptpId); int NetAdhocGameMode_DeleteMaster(); int NetAdhocctl_ExitGameMode(); -int NetAdhocPtp_Connect(int id, int timeout, int flag, bool allowForcedConnect = true); + + +static RelayTransport relayTransport; +static P2PTransport p2pTransport; + +AdhocTransport &Transport() { + if (serverHasRelay) { + return relayTransport; + } + return p2pTransport; +} // Forward declarations for the savestate mechanism (the matching is sadly not inside its own section) void deleteMatchingEvents(const int matchingId = -1); @@ -472,7 +478,7 @@ static void __GameModeNotify(u64 userdata, int cyclesLate) { if (masterGameModeArea.dataUpdated) { int sentcount = 0; for (auto& gma : replicaGameModeAreas) { - if (!gma.dataSent && (serverHasRelay || IsSocketReady(sock->data.pdp.id, false, true) > 0)) { + if (!gma.dataSent && Transport().PdpReadyToSend(sock)) { u16_le port = ADHOC_GAMEMODE_PORT; auto it = gameModePeerPorts.find(gma.mac); if (it != gameModePeerPorts.end()) @@ -536,7 +542,7 @@ static void __GameModeNotify(u64 userdata, int cyclesLate) { } // Recv new Replica data when available. Every member sends every period, so take all that's there. - for (int packets = 0; packets < ADHOCCTL_GAMEMODE_MAX_MEMBERS * 2 && (serverHasRelay || IsSocketReady(sock->data.pdp.id, true, false) > 0); ++packets) { + for (int packets = 0; packets < ADHOCCTL_GAMEMODE_MAX_MEMBERS * 2 && Transport().PdpReadyToRecv(sock); ++packets) { SceNetEtherAddr sendermac; s32_le senderport = ADHOC_GAMEMODE_PORT; s32_le bufsz = gameModeBuffSize; @@ -770,7 +776,7 @@ int StartGameModeScheduler() { return 0; } -static uint16_t offset_port_simple(uint16_t port) { +uint16_t offset_port_simple(uint16_t port) { if (port == 0) { return 0; } @@ -781,88 +787,13 @@ static uint16_t offset_port_simple(uint16_t port) { return port; } -static uint16_t reverse_port_simple(uint16_t port) { +uint16_t reverse_port_simple(uint16_t port) { return port - portOffset; } -static void handle_relay_connect_failure() { - auto n = GetI18NCategory(I18NCat::NETWORKING); - if (!trackingRelayFailure) { - trackingRelayFailure = true; - relayLastFailure = std::chrono::steady_clock::now(); - } - int been_failing_since_sec = (std::chrono::steady_clock::now() - relayLastFailure) / std::chrono::seconds(1); - if (been_failing_since_sec < RELAY_FAILURE_DISABLE_THRESHOLD_SEC) { - g_OSD.Show(OSDType::MESSAGE_ERROR, n->T("Failed connecting to relay server"), 4.0f, "relay_status"); - } else { - g_OSD.Show(OSDType::MESSAGE_ERROR, ApplySafeSubstitutions(n->T("Failed connecting to relay server for %1 seconds, relay disabled"), RELAY_FAILURE_DISABLE_THRESHOLD_SEC), 10.0f, "relay_status"); - relayDisabled = true; - } -} - -static void handle_relay_connect_success() { - auto n = GetI18NCategory(I18NCat::NETWORKING); - if (trackingRelayFailure) { - g_OSD.Show(OSDType::MESSAGE_SUCCESS, n->T("Connection to relay has recovered"), 2.0f, "relay_status"); - } - if (relayFirstConnect) { - g_OSD.Show(OSDType::MESSAGE_SUCCESS, n->T("Connected to relay"), 2.0f, "relay_status"); - relayFirstConnect = false; - } - trackingRelayFailure = false; -} - -static void *pdp_postoffice_recover(int idx) { - if (relayDisabled) { - return NULL; - } - - AdhocSocket *internal = adhocSockets[idx]; - if (internal->postofficeHandle != NULL) { - return internal->postofficeHandle; - } - - INFO_LOG(Log::sceNet, "%s: recovering pdp socket id %d", __func__, idx + 1); - - struct aemu_postoffice_sock_addr addr; - auto server_ip = GetAdhocServerIP(); - addr.addr = server_ip.in.sin_addr.s_addr; - addr.port = htons(AEMU_POSTOFFICE_PORT); - - SceNetEtherAddr local_mac; - getLocalMac(&local_mac); - - int state; - internal->postofficeHandle = pdp_create_v4(&addr, (const char *)&local_mac, offset_port_simple(internal->data.pdp.lport), netAdhocUseUdp, internal->postofficePdpEnetReliable, NULL, NULL, &state); - if (state != AEMU_POSTOFFICE_CLIENT_OK) { - ERROR_LOG(Log::sceNet, "%s: failed creating pdp socket on aemu postoffice library, %d", __func__, state); - handle_relay_connect_failure(); - } else { - handle_relay_connect_success(); - } - - INFO_LOG(Log::sceNet, "%s: pdp recovery for id %d: %p", __func__, idx + 1, internal->postofficeHandle); - return internal->postofficeHandle; -} - -static int pdp_peek_next_size_postoffice(int idx) { - AdhocSocket *internal = adhocSockets[idx]; - void *pdp_sock = pdp_postoffice_recover(idx); - if (pdp_sock == NULL) { - return 0; - } - int peek_status = pdp_peek_next_size(pdp_sock); - if (peek_status == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { - pdp_delete(internal->postofficeHandle); - internal->postofficeHandle = NULL; - return 0; - } - return peek_status; -} - // Out of any group (from the moment Disconnect is called): a PSP's radio then reaches nobody, and nobody // reaches it. -static bool offAdhocNetwork() { +bool offAdhocNetwork() { return !adhocctlInServerGroup; } @@ -884,51 +815,23 @@ void AbandonGroupJoin() { } } -static bool isLocalMac(const SceNetEtherAddr *mac) { +bool isLocalMac(const SceNetEtherAddr *mac) { SceNetEtherAddr localMac; getLocalMac(&localMac); return isMacMatch(&localMac, mac); } -static void ptpSetEstablished(AdhocSocket *sock) { +void ptpSetEstablished(AdhocSocket *sock) { sock->data.ptp.state = ADHOC_PTP_STATE_ESTABLISHED; sock->ptpEstablishedAt = CoreTiming::GetGlobalTimeUsScaled(); } -// A connection in progress shows up as established in GetPtpStat, and PollSocket reports CONNECT, as -// soon as it's up (pspautotests net/adhoc/pair/ptpconnect). -// Whether a socket whose connect() returned EISCONN is really connected. macOS answers a repeated -// connect() with EISCONN after the first one was refused too; then getpeername fails and the refusal -// is in SO_ERROR, which goes to *error. -static bool ptpReallyConnected(int fd, int *error) { - struct sockaddr_in sin; - socklen_t sinlen = sizeof(sin); - if (getpeername(fd, (struct sockaddr *)&sin, &sinlen) == 0) { - return true; - } - int soError = 0; - socklen_t soErrorLen = sizeof(soError); - getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&soError, &soErrorLen); - *error = soError != 0 ? soError : ECONNREFUSED; - return false; -} - static void ptpUpdateConnecting(AdhocSocket *sock) { if (sock->data.ptp.state != ADHOC_PTP_STATE_SYN_SENT && sock->data.ptp.state != ADHOC_PTP_STATE_SYN_RCVD) { return; } - if (serverHasRelay) { - if (sock->postofficeHandle != NULL) { - ptpSetEstablished(sock); - } - } else if (IsSocketReady(sock->data.ptp.id, true, true) > 0) { - struct sockaddr_in sin; - socklen_t sinlen = sizeof(sin); - memset(&sin, 0, sinlen); - // Ensure that the connection really established or not, since "select" alone can't accurately detects it - if (getpeername(sock->data.ptp.id, (struct sockaddr*)&sin, &sinlen) != SOCKET_ERROR) { - ptpSetEstablished(sock); - } + if (Transport().PtpConnectionUp(sock)) { + ptpSetEstablished(sock); } } @@ -936,7 +839,7 @@ static void ptpUpdateConnecting(AdhocSocket *sock) { // the connection was established, and drops the connection at the first check where that data is older // than (rexmt_cnt + 1) * rexmt_int. A connection that only receives never fails // (pspautotests net/adhoc/pair/vanishtiming). -static bool ptpPeerGone(const AdhocSocket *sock) { +bool ptpPeerGone(const AdhocSocket *sock) { u64 leftAt = peerLeftAt(&sock->data.ptp.paddr); if (leftAt == 0) { return false; @@ -962,7 +865,7 @@ static bool ptpPeerGone(const AdhocSocket *sock) { return lastCheck > unackedSince + limit; } -static void ptpNoteSend(AdhocSocket *sock) { +void ptpNoteSend(AdhocSocket *sock) { u64 now = CoreTiming::GetGlobalTimeUsScaled(); if (peerLeftAt(&sock->data.ptp.paddr) == 0) { sock->ptpSendAfterPeerLeftAt = 0; @@ -972,223 +875,18 @@ static void ptpNoteSend(AdhocSocket *sock) { sock->ptpLastSendAt = now; } -static int pdp_recv_postoffice(int idx, SceNetEtherAddr *saddr, uint16_t *sport, void *data, int *len) { - AdhocSocket *internal = adhocSockets[idx]; - void *pdp_sock = pdp_postoffice_recover(idx); - if (pdp_sock == NULL) { - return SOCKET_ERROR; - } - - int sport_copy = 0; - SceNetEtherAddr saddr_copy = {0}; - int len_copy = *len; - - int pdp_recv_status = pdp_recv(pdp_sock, (char *)&saddr_copy, &sport_copy, (char *)data, &len_copy, true); - if (pdp_recv_status == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { - handle_relay_connect_failure(); - pdp_delete(internal->postofficeHandle); - internal->postofficeHandle = NULL; - return SOCKET_ERROR; - } - if (pdp_recv_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK) { - return SOCKET_ERROR; - } - // Out of the group, nothing from anyone else reaches us (pspautotests net/adhoc/pair/vanish). - if (offAdhocNetwork() && !isLocalMac(&saddr_copy)) { - return SOCKET_ERROR; - } - - *len = len_copy; - if (saddr != NULL) { - *saddr = saddr_copy; - } - if (sport != NULL) { - *sport = reverse_port_simple(sport_copy); - } - return 0; -} - int DoBlockingPdpRecv(AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; if (!sock) { result = SCE_NET_ADHOC_ERROR_SOCKET_DELETED; return 0; } - auto& pdpsocket = sock->data.pdp; if (sock->flags & ADHOC_F_ALERTRECV) { result = SCE_NET_ADHOC_ERROR_SOCKET_ALERTED; sock->alerted_flags |= ADHOC_F_ALERTRECV; return 0; } - - int ret; - int sockerr; - SceNetEtherAddr mac; - struct sockaddr_in sin; - socklen_t sinlen; - - sinlen = sizeof(sin); - memset(&sin, 0, sinlen); - - if (serverHasRelay) { - while(1) { - int next_size = pdp_peek_next_size_postoffice(req.id - 1); - if (next_size == 0) { - // no next packet - ret = SOCKET_ERROR; - sockerr = EAGAIN; - break; - } - if (next_size > *req.length) { - // next is larger than current buffer - result = SCE_NET_ADHOC_ERROR_NOT_ENOUGH_SPACE; - *req.length = next_size; - return 0; - } - ret = pdp_recv_postoffice(req.id - 1, req.remoteMAC, req.remotePort, req.buffer, req.length); - if (ret == 0) { - // we got data into the request - result = 0; - return 0; - } - ret = SOCKET_ERROR; - sockerr = EAGAIN; - break; - } - } else { - // On Windows: MSG_TRUNC are not supported on recvfrom (socket error WSAEOPNOTSUPP), so we use dummy buffer as an alternative - ret = recvfrom(pdpsocket.id, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_PEEK | MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); - sockerr = socket_errno; - } - - // Discard packets from IP that can't be translated into MAC address to prevent confusing the game, since the sender MAC won't be updated and may contains invalid/undefined value. - // TODO: In order to discard packets from unresolvable IP (can't be translated into player's MAC) properly, we'll need to manage the socket buffer ourself, - // by reading the whole available data, separates each datagram and discard unresolvable one, so we can calculate the correct number of available data to recv on GetPdpStat too. - // We may also need to implement encryption (or a simple checksum will do) in order to validate the packet to findout whether it came from PPSSPP or a different App that may be sending/broadcasting data to the same port being used by a game - // (in case the IP was resolvable but came from a different App, which will need to be discarded too) - if (ret != SOCKET_ERROR && !resolveIP(sin.sin_addr.s_addr, &mac)) { - // Remove the packet from socket buffer - sinlen = sizeof(sin); - memset(&sin, 0, sinlen); - recvfrom(pdpsocket.id, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); - // Try again later, until timeout reached - u64 now = CoreTiming::GetGlobalTimeUs(); - if (req.timeout != 0 && now - req.startTime > req.timeout) { - result = SCE_NET_ADHOC_ERROR_TIMEOUT; - DEBUG_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i]: Discard Timeout", req.id); - return 0; - } - else - return -1; - } - - // At this point we assumed that the packet is a valid PPSSPP packet. A datagram larger than the - // buffer isn't copied at all (pspautotests net/adhoc/pdp). - - // Note: UDP must not be received partially, otherwise leftover data in socket's buffer will be discarded - if (ret >= 0 && ret <= *req.length) { - sinlen = sizeof(sin); - memset(&sin, 0, sinlen); - ret = recvfrom(pdpsocket.id, (char*)req.buffer, std::max(0, *req.length), MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); - // UDP can also receives 0 data, while on TCP receiving 0 data = connection gracefully closed, but not sure whether PDP can send/recv 0 data or not tho - *req.length = 0; - if (ret >= 0) { - DEBUG_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i:%u]: Received %u bytes from %s:%u\n", req.id, getLocalPort(pdpsocket.id), ret, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); - - // Find Peer MAC - if (resolveIP(sin.sin_addr.s_addr, &mac)) { - // Provide Sender Information - *req.remoteMAC = mac; - *req.remotePort = ntohs(sin.sin_port) - portOffset; - - // Save Length - *req.length = ret; - - // Update last recv timestamp - peerlock.lock(); - auto peer = findFriend(&mac); - if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); - peerlock.unlock(); - } - // Unknown Peer - else { - *req.length = ret; - *req.remotePort = ntohs(sin.sin_port) - portOffset; - - WARN_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i:%u]: Received %i bytes from Unknown Peer %s:%u", req.id, getLocalPort(pdpsocket.id), ret, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); - } - } - result = 0; - } - // On Windows: recvfrom on UDP can get error WSAECONNRESET when previous sendto's destination is unreachable (or destination port is not bound yet), may need to disable SIO_UDP_CONNRESET error - else if (sockerr == EAGAIN || sockerr == EWOULDBLOCK || sockerr == ECONNRESET) { - u64 now = CoreTiming::GetGlobalTimeUs(); - if (req.timeout == 0 || now - req.startTime <= req.timeout) { - // Try again later - return -1; - } - else - result = SCE_NET_ADHOC_ERROR_TIMEOUT; - } - // Returning required buffer size when available data in recv buffer is larger than provided buffer size - else if (ret > *req.length) { - WARN_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i:%u]: Peeked %u/%u bytes from %s:%u\n", req.id, getLocalPort(pdpsocket.id), ret, *req.length, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); - *req.length = ret; - - // Find Peer MAC - if (resolveIP(sin.sin_addr.s_addr, &mac)) { - // Provide Sender Information - *req.remoteMAC = mac; - *req.remotePort = ntohs(sin.sin_port) - portOffset; - - // FIXME: Do we need to update last recv timestamp? eventhough data hasn't been retrieved yet (ie. peeked) - peerlock.lock(); - auto peer = findFriend(&mac); - if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); - peerlock.unlock(); - } - result = SCE_NET_ADHOC_ERROR_NOT_ENOUGH_SPACE; - } - // FIXME: Blocking operation with infinite timeout(0) should never get a TIMEOUT error, right? May be we should return INVALID_ARG instead if it was infinite timeout (0)? - else - result = SCE_NET_ADHOC_ERROR_TIMEOUT; // SCE_NET_ADHOC_ERROR_INVALID_ARG; // SCE_NET_ADHOC_ERROR_DISCONNECTED - - if (ret == SOCKET_ERROR) - DEBUG_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i]: Socket Error (%i)", req.id, sockerr); - - return 0; -} - -static int pdp_send_postoffice(int idx, const SceNetEtherAddr *daddr, uint16_t dport, const void *data, int len) { - AdhocSocket *internal = adhocSockets[idx]; - void *pdp_sock = pdp_postoffice_recover(idx); - if (pdp_sock == NULL) { - return SOCKET_ERROR; - } - - // Out of the group, it goes into the air and reaches nobody (pspautotests net/adhoc/pair/vanish). - if (offAdhocNetwork() && !isLocalMac(daddr)) { - return 0; - } - - SceNetEtherAddr fixed_daddr = *daddr; - fixGameMac(&fixed_daddr); - - int pdp_send_status = pdp_send(pdp_sock, (const char *)&fixed_daddr, offset_port_simple(dport), (char *)data, len, true); - if (pdp_send_status == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { - handle_relay_connect_failure(); - pdp_delete(internal->postofficeHandle); - internal->postofficeHandle = NULL; - return SOCKET_ERROR; - } - if (pdp_send_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK) { - return SOCKET_ERROR; - } - if (pdp_send_status == AEMU_POSTOFFICE_CLIENT_OUT_OF_MEMORY) { - // this is pretty critical - ERROR_LOG(Log::sceNet, "%s: critical: huge client buf %d what is going on please fix", __func__, len); - } - return 0; + return Transport().DoBlockingPdpRecv(req, result); } int DoBlockingPdpSend(AdhocSocketRequest& req, s64& result, AdhocSendTargets& targetPeers) { @@ -1213,19 +911,8 @@ int DoBlockingPdpSend(AdhocSocketRequest& req, s64& result, AdhocSendTargets& ta target.sin_addr.s_addr = peer->ip; target.sin_port = htons(peer->port + peer->portOffset); - int ret = 0; int sockerr = 0; - if (serverHasRelay) { - ret = pdp_send_postoffice(req.id - 1, &peer->mac, peer->port, req.buffer, targetPeers.length); - if (ret == 0) { - ret = targetPeers.length; - } else { - sockerr = EAGAIN; - } - } else { - ret = sendto(pdpsocket.id, (const char*)req.buffer, targetPeers.length, MSG_NOSIGNAL, (struct sockaddr*)&target, sizeof(target)); - sockerr = socket_errno; - } + int ret = Transport().PdpSendTo(req.id, &peer->mac, peer->port, target, req.buffer, targetPeers.length, &sockerr); if (ret >= 0) { DEBUG_LOG(Log::sceNet, "sceNetAdhocPdpSend[%i:%u](B): Sent %u bytes to %s:%u\n", req.id, getLocalPort(pdpsocket.id), ret, ip2str(target.sin_addr).c_str(), ntohs(target.sin_port)); @@ -1255,169 +942,18 @@ int DoBlockingPdpSend(AdhocSocketRequest& req, s64& result, AdhocSendTargets& ta return 0; } -static int ptp_send_postoffice(int idx, const void *data, int *len) { - if (relayDisabled) { - return SOCKET_ERROR; - } - - AdhocSocket *internal = adhocSockets[idx]; - - if (internal->postofficeHandle == NULL) { - // this should only happen on ptp_open sockets, where ptp_connect is still in progress on another thread - return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; - } - - // Out of the group, the send succeeds and the data goes nowhere (pspautotests net/adhoc/pair/vanish). - if (offAdhocNetwork()) { - return 0; - } - if (ptpPeerGone(internal)) { - return SOCKET_ERROR; - } - - if (*len > AEMU_POSTOFFICE_PTP_BLOCK_MAX) { - // force fragmentation for giant sends - *len = AEMU_POSTOFFICE_PTP_BLOCK_MAX; - } - - int ptp_send_status = ptp_send(internal->postofficeHandle, (const char *)data, *len, true); - ptpNoteSend(internal); - if (ptp_send_status == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { - // the session is dead, need to be reflected to the other side - // this is not necessarily a relay failure, could simply be the other side disconnecting - //handle_relay_connect_failure(); - return SOCKET_ERROR; - } - if (ptp_send_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK) { - return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; - } - if (ptp_send_status == AEMU_POSTOFFICE_CLIENT_OUT_OF_MEMORY) { - // this is pretty critical - ERROR_LOG(Log::sceNet, "%s: critical: huge client buf %d what is going on please fix", __func__, len ? *len : 0); - } - - return 0; -} - int DoBlockingPtpSend(AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; if (!sock) { result = SCE_NET_ADHOC_ERROR_SOCKET_DELETED; return 0; } - auto& ptpsocket = sock->data.ptp; if (sock->flags & ADHOC_F_ALERTSEND) { result = SCE_NET_ADHOC_ERROR_SOCKET_ALERTED; sock->alerted_flags |= ADHOC_F_ALERTSEND; return 0; } - - // Send Data - int ret = 0; - int sockerr = 0; - if (serverHasRelay) { - ret = ptp_send_postoffice(req.id - 1, req.buffer, req.length); - if (ret == 0) { - // sent - // Set to Established on successful Send when an attempt to Connect was initiated - if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) - ptpSetEstablished(sock); - - DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpSend[%i:%u]: Sent %u bytes to %s:%u\n", req.id, ptpsocket.lport, ret, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport); - - result = 0; - return 0; - } - if (ret == SOCKET_ERROR) { - ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - result = SCE_NET_ADHOC_ERROR_DISCONNECTED; - - DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpSend[%i]: Socket Error (%i)", req.id, sockerr); - - return 0; - } - // SCE_NET_ADHOC_ERROR_WOULD_BLOCK - ret = SOCKET_ERROR; - sockerr = EAGAIN; - }else{ - ret = send(ptpsocket.id, (const char*)req.buffer, *req.length, MSG_NOSIGNAL); - sockerr = socket_errno; - } - - // Success - if (ret > 0) { - // Save Length - *req.length = ret; - - DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpSend[%i:%u]: Sent %u bytes to %s:%u\n", req.id, ptpsocket.lport, ret, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport); - - // Set to Established on successful Send when an attempt to Connect was initiated - if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) - ptpSetEstablished(sock); - - // Return Success - result = 0; - } - else if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK || (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT && (sockerr == ENOTCONN || connectInProgress(sockerr))))) { - u64 now = CoreTiming::GetGlobalTimeUs(); - if (req.timeout == 0 || now - req.startTime <= req.timeout) { - return -1; - } - else - result = SCE_NET_ADHOC_ERROR_TIMEOUT; - } - else { - // Change Socket State. // FIXME: Does Alerted Socket should be closed too? - ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - - // Disconnected - result = SCE_NET_ADHOC_ERROR_DISCONNECTED; - } - - if (ret == SOCKET_ERROR) - DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpSend[%i]: Socket Error (%i)", req.id, sockerr); - - return 0; -} - -static int ptp_recv_postoffice(int idx, void *data, int *len) { - if (relayDisabled) { - return SOCKET_ERROR; - } - - AdhocSocket *internal = adhocSockets[idx]; - - if (internal->postofficeHandle == NULL) { - // this should only happen on ptp_open sockets, where ptp_connect is still in progress on another thread - return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; - } - - // Out of the group, nothing arrives, and the connection doesn't fail either (pspautotests - // net/adhoc/pair/vanish). - if (offAdhocNetwork()) { - return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; - } - if (ptpPeerGone(internal)) { - return SOCKET_ERROR; - } - - int len_copy = *len; - - int ptp_recv_status = ptp_recv(internal->postofficeHandle, (char *)data, &len_copy, true); - if (ptp_recv_status == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { - // the session is dead, need to be reflected to the other side - // this is not necessarily a relay failure, could simply be the other side disconnecting - //handle_relay_connect_failure(); - return SOCKET_ERROR; - } - if (ptp_recv_status == AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK) { - return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; - } - - // AEMU_POSTOFFICE_CLIENT_SESSION_DATA_TRUNC is okay, it just means it has data in it's user space buffer - - *len = len_copy; - return 0; + return Transport().DoBlockingPtpSend(req, result); } int DoBlockingPtpRecv(AdhocSocketRequest& req, s64& result) { @@ -1426,185 +962,12 @@ int DoBlockingPtpRecv(AdhocSocketRequest& req, s64& result) { result = SCE_NET_ADHOC_ERROR_SOCKET_DELETED; return 0; } - auto& ptpsocket = sock->data.ptp; if (sock->flags & ADHOC_F_ALERTRECV) { result = SCE_NET_ADHOC_ERROR_SOCKET_ALERTED; sock->alerted_flags |= ADHOC_F_ALERTRECV; return 0; } - - int ret = 0; - int sockerr = 0; - if (serverHasRelay) { - ret = ptp_recv_postoffice(req.id - 1, req.buffer, req.length); - if (ret == 0) { - // we got data - result = 0; - return 0; - } - if (ret == SOCKET_ERROR) { - // the socket died, let the game know - ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - result = SCE_NET_ADHOC_ERROR_DISCONNECTED; - return 0; - } - // SCE_NET_ADHOC_ERROR_WOULD_BLOCK - ret = SOCKET_ERROR; - sockerr = EAGAIN; - } else { - ret = recv(ptpsocket.id, (char*)req.buffer, std::max(0, *req.length), MSG_NOSIGNAL); - sockerr = socket_errno; - } - - // Received Data. POSIX: May received 0 bytes when the remote peer already closed the connection. - if (ret > 0) { - DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpRecv[%i:%u]: Received %u bytes from %s:%u\n", req.id, ptpsocket.lport, ret, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport); - // Save Length - *req.length = ret; - - // Update last recv timestamp - peerlock.lock(); - auto peer = findFriend(&ptpsocket.paddr); - if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); - peerlock.unlock(); - - // Set to Established on successful Recv when an attempt to Connect was initiated - if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) - ptpSetEstablished(sock); - - result = 0; - } - else if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK || (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT && (sockerr == ENOTCONN || connectInProgress(sockerr))))) { - u64 now = CoreTiming::GetGlobalTimeUs(); - if (req.timeout == 0 || now - req.startTime <= req.timeout) { - return -1; - } - else - result = SCE_NET_ADHOC_ERROR_TIMEOUT; - } - else { - // Change Socket State. // FIXME: Does Alerted Socket should be closed too? - ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - - // Disconnected - result = SCE_NET_ADHOC_ERROR_DISCONNECTED; // SCE_NET_ADHOC_ERROR_INVALID_ARG - } - - if (ret == SOCKET_ERROR) - DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpRecv[%i]: Socket Error (%i)", req.id, sockerr); - - return 0; -} - -static void *ptp_listen_postoffice_recover(int idx) { - if (relayDisabled) { - return NULL; - } - - AdhocSocket *internal = adhocSockets[idx]; - if (internal->postofficeHandle != NULL) { - return internal->postofficeHandle; - } - - INFO_LOG(Log::sceNet, "%s: recovering ptp listen socket id %d", __func__, idx + 1); - - struct aemu_postoffice_sock_addr addr; - auto server_ip = GetAdhocServerIP(); - addr.addr = server_ip.in.sin_addr.s_addr; - addr.port = htons(AEMU_POSTOFFICE_PORT); - - int state; - internal->postofficeHandle = ptp_listen_v4(&addr, (const char*)&internal->data.ptp.laddr, internal->data.ptp.lport + portOffset, netAdhocUseUdp, NULL, NULL, &state); - - if (state != AEMU_POSTOFFICE_CLIENT_OK) { - ERROR_LOG(Log::sceNet, "%s: failed recovering ptp listen socket, %d", __func__, state); - handle_relay_connect_failure(); - } else { - handle_relay_connect_success(); - } - - INFO_LOG(Log::sceNet, "%s: ptp listen recovery for id %d: %p", __func__, idx + 1, internal->postofficeHandle); - - return internal->postofficeHandle; -} - -static int ptp_accept_postoffice(int idx, SceNetEtherAddr *saddr, uint16_t *sport) { - void *ptp_listen_socket = ptp_listen_postoffice_recover(idx); - - if (ptp_listen_socket == NULL) { - return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; - } - - int state = 0; - int port_cpy = 0; - SceNetEtherAddr mac_cpy = {0}; - void *new_ptp_socket = ptp_accept(ptp_listen_socket, (char *)&mac_cpy, &port_cpy, true, NULL, NULL, &state); - if (new_ptp_socket == NULL) { - if (state == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { - handle_relay_connect_failure(); - ptp_listen_close(adhocSockets[idx]->postofficeHandle); - adhocSockets[idx]->postofficeHandle = NULL; - return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; - } - if (state == AEMU_POSTOFFICE_CLIENT_OUT_OF_MEMORY) { - // pretty critical here - ERROR_LOG(Log::sceNet, "%s: aemu_postoffice ran out of memory to accept a new connection", __func__); - } - // AEMU_POSTOFFICE_CLIENT_SESSION_WOULD_BLOCK - return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; - } - - // we have a new socket - handle_relay_connect_success(); - AdhocSocket *internal = (AdhocSocket *)malloc(sizeof(AdhocSocket)); - if (internal == NULL) { - ERROR_LOG(Log::sceNet, "%s: critical: ran out of heap memory while accepting new connection", __func__); - ptp_close(new_ptp_socket); - return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; - } - memset(internal, 0, sizeof(AdhocSocket)); - internal->retry_interval = adhocSockets[idx]->retry_interval; - internal->retry_count = adhocSockets[idx]->retry_count; - internal->attemptCount = 1; // Connected, so a closed socket reports DISCONNECTED (as the native accept does) - - internal->type = SOCK_PTP; - internal->postofficeHandle = new_ptp_socket; - internal->data.ptp.laddr = adhocSockets[idx]->data.ptp.laddr; - internal->data.ptp.lport = adhocSockets[idx]->data.ptp.lport; - internal->data.ptp.paddr = mac_cpy; - internal->data.ptp.pport = reverse_port_simple(port_cpy); - ptpSetEstablished(internal); - internal->data.ptp.rcv_sb_cc = adhocSockets[idx]->data.ptp.rcv_sb_cc; - internal->data.ptp.snd_sb_cc = adhocSockets[idx]->data.ptp.snd_sb_cc; - internal->data.ptp.id = AEMU_POSTOFFICE_ID_BASE + idx; - internal->flags = 0; - internal->connectThread = NULL; - - AdhocSocket **slot = NULL; - int i; - i = FindFreeAdhocSocketSlot(); - if (i < MAX_SOCKET) { - slot = &adhocSockets[i]; - } - - if (slot == NULL) { - ptp_close(new_ptp_socket); - free(internal); - ERROR_LOG(Log::sceNet, "%s: critical: cannot find an empty mapper slot", __func__); - return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; - } - - *slot = internal; - - if (saddr != NULL) { - *saddr = mac_cpy; - } - if (sport != NULL) { - *sport = reverse_port_simple(port_cpy); - } - - INFO_LOG(Log::sceNet, "%s: accepted ptp socket with id %d %p", __func__, i + 1, internal->postofficeHandle); - return i + 1; + return Transport().DoBlockingPtpRecv(req, result); } int DoBlockingPtpAccept(AdhocSocketRequest& req, s64& result) { @@ -1613,115 +976,12 @@ int DoBlockingPtpAccept(AdhocSocketRequest& req, s64& result) { result = SCE_NET_ADHOC_ERROR_SOCKET_DELETED; return 0; } - auto& ptpsocket = sock->data.ptp; if (sock->flags & ADHOC_F_ALERTACCEPT) { result = SCE_NET_ADHOC_ERROR_SOCKET_ALERTED; sock->alerted_flags |= ADHOC_F_ALERTACCEPT; return 0; } - - struct sockaddr_in sin; - memset(&sin, 0, sizeof(sin)); - socklen_t sinlen = sizeof(sin); - int ret, sockerr; - - if (serverHasRelay) { - ret = ptp_accept_postoffice(req.id - 1, req.remoteMAC, req.remotePort); - if (ret >= 0) { - result = ret; - return 0; - } else { - ret = SOCKET_ERROR; - sockerr = EAGAIN; - } - } else { - // Check if listening socket is ready to accept - ret = IsSocketReady(ptpsocket.id, true, false, &sockerr); - if (ret > 0) { - // Accept Connection - ret = accept(ptpsocket.id, (struct sockaddr*)&sin, &sinlen); - sockerr = socket_errno; - } - } - - // Accepted New Connection - if (ret > 0) { - int newid = AcceptPtpSocket(req.id, ret, sin, req.remoteMAC, req.remotePort); - if (newid > 0) - result = newid; - } - else if (ret == 0 || (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK))) { - u64 now = CoreTiming::GetGlobalTimeUs(); - if (req.timeout == 0 || now - req.startTime <= req.timeout) { - return -1; - } - else { - result = SCE_NET_ADHOC_ERROR_TIMEOUT; - } - } - else - result = SCE_NET_ADHOC_ERROR_INVALID_ARG; //SCE_NET_ADHOC_ERROR_TIMEOUT - - if (ret == SOCKET_ERROR) - DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpAccept[%i]: Socket Error (%i)", req.id, sockerr); - - return 0; -} - -static int ptp_connect_postoffice(int idx, const char *caller) { - if (relayDisabled) { - return SCE_NET_ADHOC_ERROR_CONNECTION_REFUSED; - } - - AdhocSocket *internal = adhocSockets[idx]; - - struct aemu_postoffice_sock_addr addr; - auto server_ip = GetAdhocServerIP(); - addr.addr = server_ip.in.sin_addr.s_addr; - addr.port = htons(AEMU_POSTOFFICE_PORT); - - if (internal->postofficeHandle != NULL) { - return 0; - } - - if (internal->connectThreadDone) { - if (internal->connectThread != NULL) { - internal->connectThread->join(); - delete internal->connectThread; - internal->connectThread = NULL; - } - - internal->connectThreadDone = false; - internal->connectThreadResult = 0; - - internal->connectThread = new std::thread([internal, addr, idx] { - int state; - SceNetEtherAddr fixed_daddr = internal->data.ptp.paddr; - fixGameMac(&fixed_daddr); - void *ptp_socket = ptp_connect_v4(&addr, (const char *)&internal->data.ptp.laddr, offset_port_simple(internal->data.ptp.lport), (const char *)&fixed_daddr, offset_port_simple(internal->data.ptp.pport), netAdhocUseUdp, NULL, NULL, &state); - if (ptp_socket == NULL) { - internal->connectThreadResult = SCE_NET_ADHOC_ERROR_CONNECTION_REFUSED; - ERROR_LOG(Log::sceNet, "%s: failed connecting to ptp socket, %d", __func__, state); - // do not count ptp connect failure as relay failure, since it could be the other client not accepting the connection - //handle_relay_connect_failure(); - internal->connectThreadDone = true; - return; - } - // see above, ptp connect result is not used for checking if relay is working - //handle_relay_connect_success(); - internal->postofficeHandle = ptp_socket; - internal->connectThreadResult = 0; - INFO_LOG(Log::sceNet, "%s: connected ptp socket with id %d %p", __func__, idx + 1, internal->postofficeHandle); - internal->connectThreadDone = true; - return; - });; - - DEBUG_LOG(Log::sceNet, "%s: started connect thread on id %d", __func__, idx + 1); - return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; - } - - DEBUG_LOG(Log::sceNet, "%s: id %d is connecting", __func__, idx + 1); - return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; + return Transport().DoBlockingPtpAccept(req, result); } int DoBlockingPtpConnect(AdhocSocketRequest& req, s64& result, AdhocSendTargets& targetPeer) { @@ -1745,19 +1005,7 @@ int DoBlockingPtpConnect(AdhocSocketRequest& req, s64& result, AdhocSendTargets& sin.sin_addr.s_addr = targetPeer.peers[0].ip; sin.sin_port = htons(ptpsocket.pport + targetPeer.peers[0].portOffset); - if (serverHasRelay) { - ret = ptp_connect_postoffice(req.id - 1, __func__); - if (ret != 0) { - ret = SOCKET_ERROR; - sockerr = EAGAIN; - } - } else { - ret = connect(ptpsocket.id, (struct sockaddr*)&sin, sizeof(sin)); - sockerr = socket_errno; - if (ret == SOCKET_ERROR && sockerr == EISCONN) { - ptpReallyConnected(ptpsocket.id, &sockerr); - } - } + ret = Transport().PtpConnect(req.id, sin, &sockerr); if (sockerr != 0) DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpConnect[%i:%u]: connect(%i) error = %i", req.id, ptpsocket.lport, ptpsocket.id, sockerr); else @@ -1766,22 +1014,7 @@ int DoBlockingPtpConnect(AdhocSocketRequest& req, s64& result, AdhocSendTargets& // Check the connection state (assuming "connect" has been called before and is in-progress) // Note: On Linux "select" can return > 0 (with SO_ERROR = 0) even when the connection is not accepted yet, thus need "getpeername" to ensure else { - if (serverHasRelay) { - if (sock->postofficeHandle == NULL) { - ret = SOCKET_ERROR; - if (sock->connectThreadDone && sock->connectThreadResult == SCE_NET_ADHOC_ERROR_CONNECTION_REFUSED) { - sockerr = ECONNREFUSED; - } else { - sockerr = EAGAIN; - } - } else { - // the handle is there, we're ready to go - ret = 1; - sockerr = 0; - } - } else { - ret = IsSocketReady(ptpsocket.id, false, true, &sockerr); - } + ret = Transport().PtpConnectPoll(sock, &sockerr); DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpConnect[%i:%u]: Select(%i) = %i, error = %i", req.id, ptpsocket.lport, ptpsocket.id, ret, sockerr); if (sockerr != 0) { DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpConnect[%i:%u]: SelectError(%i) = %i", req.id, ptpsocket.lport, ptpsocket.id, sockerr); @@ -1796,15 +1029,8 @@ int DoBlockingPtpConnect(AdhocSocketRequest& req, s64& result, AdhocSendTargets& } // Check whether the connection has been established or not - if (!serverHasRelay && ret != SOCKET_ERROR) { - socklen_t sinlen = sizeof(sin); - // Note: "getpeername" shouldn't failed if the connection has been established, but on Windows it may succeed even when "connect" is still in-progress and not accepted yet (ie. "Tales of VS" on Windows) - ret = getpeername(ptpsocket.id, (struct sockaddr*)&sin, &sinlen); - if (ret == SOCKET_ERROR) { - int err = socket_errno; - VERBOSE_LOG(Log::sceNet, "sceNetAdhocPtpConnect[%i:%u]: getpeername(%i) error %i, sockerr = %i", req.id, ptpsocket.lport, ptpsocket.id, err, sockerr); - sockerr = err; - } + if (ret != SOCKET_ERROR) { + ret = Transport().PtpConnectVerify(req.id, &sin, ret, &sockerr); } // Update Adhoc Socket state @@ -1815,7 +1041,7 @@ int DoBlockingPtpConnect(AdhocSocketRequest& req, s64& result, AdhocSendTargets& // Done result = 0; } - else if (serverHasRelay || connectInProgress(sockerr) /* || sockerr == 0*/) { + else if (Transport().PtpConnectPending(sockerr) /* || sockerr == 0*/) { ptpsocket.state = ADHOC_PTP_STATE_SYN_SENT; } // On Windows you can call connect again using the same socket after ECONNREFUSED/ETIMEDOUT/ENETUNREACH error, but on non-Windows you'll need to recreate the socket first @@ -2512,50 +1738,10 @@ int sceNetAdhocctlGetState(u32 ptrToStatus) { return hleLogVerbose(Log::sceNet, 0, "state = %d", state); } -static int pdp_create_postoffice(const SceNetEtherAddr *saddr, int sport, int bufsize, bool enet_reliable) { - if (relayDisabled) { - return hleLogDebug(Log::sceNet, ERROR_NET_NO_SPACE, "relay disabled"); - } - - AdhocSocket *internal = (AdhocSocket*)malloc(sizeof(AdhocSocket)); - if (internal == NULL) { - return hleLogDebug(Log::sceNet, ERROR_NET_NO_SPACE, "net no space"); - } - memset(internal, 0, sizeof(AdhocSocket)); - - internal->type = SOCK_PDP; - internal->postofficeHandle = NULL; - internal->data.pdp.laddr = *saddr; - internal->data.pdp.lport = sport; - internal->data.pdp.rcv_sb_cc = bufsize; - internal->flags = 0; - internal->lastAttempt = 0; - internal->internalLastAttempt = 0; - internal->postofficePdpEnetReliable = enet_reliable; - - AdhocSocket **free_slot = NULL; - int i; - i = FindFreeAdhocSocketSlot(); - if (i < MAX_SOCKET) { - free_slot = &adhocSockets[i]; - } - if (free_slot == NULL) { - free(internal); - return ERROR_NET_NO_SPACE; - } - - internal->data.pdp.id = AEMU_POSTOFFICE_ID_BASE + i; - - *free_slot = internal; - pdp_postoffice_recover(i); - INFO_LOG(Log::sceNet, "%s: created pdp socket with id %d %p", __func__, i + 1, internal->postofficeHandle); - return i + 1; -} - // Out of socket IDs is its own error (pspautotests net/adhoc/limits). // The search for a free slot starts after the last one handed out, and wraps around (see // nextAdhocSocketSlot). Returns MAX_SOCKET when all are taken. -static int FindFreeAdhocSocketSlot() { +int FindFreeAdhocSocketSlot() { for (int n = 0; n < MAX_SOCKET; n++) { int i = (nextAdhocSocketSlot + n) % MAX_SOCKET; if (adhocSockets[i] == NULL) { @@ -2581,7 +1767,7 @@ static bool HasFreeAdhocSocketSlot() { } // A free local port for a PDP socket created on port 0, in the range a PSP picks from (0x1000-0x7FFF). -static uint16_t get_unused_pdp_port() { +uint16_t get_unused_pdp_port() { for (int tries = 0; tries < 1000; tries++) { uint16_t test_port = 0x1000 + rand() % 0x7000; bool used = false; @@ -2670,134 +1856,7 @@ int NetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 flag, bool return SCE_NET_ADHOC_ERROR_INVALID_ADDR; } - if (serverHasRelay) - return pdp_create_postoffice(saddr, port, bufferSize, postoffice_enet_pdp_reliable); - - // Create Internet UDP Socket - // Socket is remapped through adhocSockets - int usocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - - if (usocket == INVALID_SOCKET) { - DEBUG_LOG(Log::sceNet, "%s: sock creation failed", __func__); - return ERROR_NET_NO_SPACE; - } - - // Change socket buffer size to be consistent on all platforms. - // Send Buffer should be smaller than Recv Buffer to prevent faster device from flooding slower device too much. - setSockBufferSize(usocket, SO_SNDBUF, bufferSize*5); //PSP_ADHOC_PDP_MFS - // Recv Buffer should be equal or larger than Send Buffer. Using larger Recv Buffer might helped reduces dropped packets during a slowdown, but too large may cause slow performance on Warriors Orochi 2. - setSockBufferSize(usocket, SO_RCVBUF, bufferSize*10); //PSP_ADHOC_PDP_MFS*10 - - // Ignore SIGPIPE when supported (ie. BSD/MacOS) - setSockNoSIGPIPE(usocket, 1); - - // Enable Port Re-use, this will allow binding to an already used port, but only one of them can read the data (shared receive buffer?) - setSockReuseAddrPort(usocket); - - // Disable Connection Reset error on UDP to avoid strange behavior https://stackoverflow.com/questions/34242622/windows-udp-sockets-recvfrom-fails-with-error-10054 - setUDPConnReset(usocket, false); - - // Binding Information for local Port - struct sockaddr_in addr {}; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = INADDR_ANY; - if (isLocalServer) { - getLocalIp(&addr); - } - uint16_t requestedport = static_cast(port + static_cast(portOffset)); - // Avoid getting random port due to port offset when original port wasn't 0 (ie. original_port + port_offset = 65536 = 0) - if (requestedport == 0 && port > 0) - requestedport = 65535; // Hopefully it will be safe to default it to 65535 since there can't be more than one port that can bumped into 65536 - - // Show a warning about privileged ports - if (requestedport != 0 && requestedport < 1024) { - WARN_LOG(Log::sceNet, "sceNetAdhocPdpCreate - Ports below 1024(ie. %hu) may require Admin Privileges", requestedport); - } - addr.sin_port = htons(requestedport); - - - // Bound Socket to local Port - int iResult = bind(usocket, (struct sockaddr*)&addr, sizeof(addr)); - // A port picked for port 0 may be taken on the host; pick another. - for (int tries = 0; iResult != 0 && isClient && tries < 16; tries++) { - port = get_unused_pdp_port(); - requestedport = static_cast(port + static_cast(portOffset)); - if (requestedport == 0) - requestedport = 65535; - addr.sin_port = htons(requestedport); - iResult = bind(usocket, (struct sockaddr*)&addr, sizeof(addr)); - } - - if (iResult != 0) { - DEBUG_LOG(Log::sceNet, "%s: sock bind failed", __func__); - auto n = GetI18NCategory(I18NCat::NETWORKING); - g_OSD.Show(OSDType::MESSAGE_ERROR, std::string(n->T("Failed to Bind Port")) + " " + std::to_string(port + portOffset) + "\n" + std::string(n->T("Please change your Port Offset")), 0.0f, "portbindfail"); - closesocket(usocket); - return ERROR_NET_NO_SPACE; - } - - // Workaround: Send a dummy 0 size message to AdhocServer IP to make sure the socket actually bound to an address when binded with INADDR_ANY before using getsockname, seems to fix sending from incorrect port issue on MGS:PW on Android - auto server_ip = GetAdhocServerIP(); - addr.sin_addr.s_addr = server_ip.in.sin_addr.s_addr; - addr.sin_port = 0; - sendto(usocket, dummyPeekBuf64k, 0, MSG_NOSIGNAL, (struct sockaddr*)&addr, sizeof(addr)); - // Update sport with the port assigned internal->lport = ntohs(local.sin_port) - socklen_t len = sizeof(addr); - if (getsockname(usocket, (struct sockaddr*)&addr, &len) == 0) { - uint16_t boundport = ntohs(addr.sin_port); - if (port + static_cast(portOffset) >= 65536 || static_cast(boundport) - static_cast(portOffset) <= 0) - WARN_LOG(Log::sceNet, "sceNetAdhocPdpCreate - Wrapped Port Detected: Original(%d) -> Requested(%d), Bound(%d) -> BoundOriginal(%d)", port, requestedport, boundport, boundport - portOffset); - port = boundport - portOffset; - } - - // Allocate Memory for Internal Data - AdhocSocket * internal = (AdhocSocket*)malloc(sizeof(AdhocSocket)); - - if (internal == NULL) { - DEBUG_LOG(Log::sceNet, "%s: out of memory when allocating internal socket tracking", __func__); - closesocket(usocket); - return ERROR_NET_NO_SPACE; - } - - // Find Free Translator Index - // FIXME: We should probably use an increasing index instead of looking for an empty slot from beginning if we want to simulate a real socket id - int i = 0; - i = FindFreeAdhocSocketSlot(); - - if (i >= MAX_SOCKET) { - DEBUG_LOG(Log::sceNet, "%s: out of slot when allocating internal socket tracking", __func__); - free(internal); - closesocket(usocket); - return ERROR_NET_NO_SPACE; - } - - // Clear Memory - memset(internal, 0, sizeof(AdhocSocket)); - - // Socket Type - internal->type = SOCK_PDP; - internal->nonblocking = flag; - internal->buffer_size = bufferSize; - internal->isClient = isClient; - - // Fill in Data - internal->data.pdp.id = usocket; - internal->data.pdp.laddr = *saddr; - internal->data.pdp.lport = port; //getLocalPort(usocket) - portOffset; - - // Link Socket to Translator ID - adhocSockets[i] = internal; - - // Forward Port on Router - //sceNetPortOpen("UDP", port); - UPnP_Add(IP_PROTOCOL_UDP, isOriPort ? port : port + portOffset, port + portOffset); // g_PortManager.Add(IP_PROTOCOL_UDP, isOriPort ? port : port + portOffset, port + portOffset); - - // Switch to non-blocking for futher usage - changeBlockingMode(usocket, 1); - - // Success - INFO_LOG(Log::sceNet, "sceNetAdhocPdpCreate - PSP Socket id: %i, Host Socket id: %i", i + 1, usocket); - return i + 1; + return Transport().PdpCreate(saddr, port, bufferSize, flag, isClient, postoffice_enet_pdp_reliable); } /** @@ -2950,20 +2009,8 @@ int sceNetAdhocPdpSend(MIPSState *mips, int id, const char *mac, u32 port, void //_acquireNetworkLock(); // Send Data. UDP are guaranteed to be sent as a whole or nothing(failed if len > SO_MAX_MSG_SIZE), and never be partially sent/recv - int sent = 0; int error = 0; - if (serverHasRelay) { - sent = pdp_send_postoffice(id - 1, daddr, dport, data, len); - if (sent == 0) { - sent = len; - } else { - sent = SOCKET_ERROR; - error = EAGAIN; - } - } else { - sent = sendto(pdpsocket.id, (const char *)data, len, MSG_NOSIGNAL, (struct sockaddr*)&target, sizeof(target)); - error = socket_errno; - } + int sent = Transport().PdpSendTo(id, daddr, dport, target, data, len, &error); if (sent == SOCKET_ERROR) { // Simulate blocking behaviour with non-blocking socket @@ -3064,20 +2111,8 @@ int sceNetAdhocPdpSend(MIPSState *mips, int id, const char *mac, u32 port, void target.sin_addr.s_addr = peer.ip; target.sin_port = htons(dport + peer.portOffset); - int sent = 0; int error = 0; - if (serverHasRelay) { - sent = pdp_send_postoffice(id - 1, &peer.mac, dport, data, len); - if (sent == 0) { - sent = len; - } else { - sent = SOCKET_ERROR; - error = EAGAIN; - } - } else { - sent = sendto(pdpsocket.id, (const char *)data, len, MSG_NOSIGNAL, (struct sockaddr*)&target, sizeof(target)); - error = socket_errno; - } + int sent = Transport().PdpSendTo(id, &peer.mac, dport, target, data, len, &error); if (sent == SOCKET_ERROR) { DEBUG_LOG(Log::sceNet, "Socket Error (%i) on sceNetAdhocPdpSend[%i:%u->%u](BC) [size=%i]", error, id, getLocalPort(pdpsocket.id), ntohs(target.sin_port), len); @@ -3216,171 +2251,7 @@ int sceNetAdhocPdpRecv(MIPSState *mips, int id, void *addr, void * port, void *b return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_SOCKET_ALERTED, "socket alerted"); } - // Sender Address - struct sockaddr_in sin; - socklen_t sinlen; - - // Acquire Network Lock - //_acquireNetworkLock(); - - SceNetEtherAddr mac; - int received = 0; - int error = 0; - - if (serverHasRelay) { - while(1) { - int next_size = pdp_peek_next_size_postoffice(id - 1); - if (next_size == 0) { - // no next packet - received = SOCKET_ERROR; - error = EAGAIN; - break; - } - if (next_size > *len) { - // next is larger than current buffer - *len = next_size; - return SCE_NET_ADHOC_ERROR_NOT_ENOUGH_SPACE; - } - received = pdp_recv_postoffice(id - 1, saddr, sport, buf, len); - if (received == 0) { - // we got data - hleEatMicro(50); - return 0; - } - received = SOCKET_ERROR; - error = EAGAIN; - break; - } - } else { - int disCnt = 16; - while (--disCnt > 0) - { - // Receive Data. PDP always sent in full size or nothing(failed), recvfrom will always receive in full size as requested (blocking) or failed (non-blocking). If available UDP data is larger than buffer, excess data is lost. - // Should peek first for the available data size if it's more than len return SCE_NET_ADHOC_ERROR_NOT_ENOUGH_SPACE along with required size in len to prevent losing excess data - // On Windows: MSG_TRUNC are not supported on recvfrom (socket error WSAEOPNOTSUPP), so we use dummy buffer as an alternative - sinlen = sizeof(sin); - memset(&sin, 0, sinlen); - received = recvfrom(pdpsocket.id, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_PEEK | MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); - error = socket_errno; - // Discard packets from IP that can't be translated into MAC address to prevent confusing the game, since the sender MAC won't be updated and may contains invalid/undefined value. - // TODO: In order to discard packets from unresolvable IP (can't be translated into player's MAC) properly, we'll need to manage the socket buffer ourself, - // by reading the whole available data, separates each datagram and discard unresolvable one, so we can calculate the correct number of available data to recv on GetPdpStat too. - // We may also need to implement encryption (or a simple checksum will do) in order to validate the packet to findout whether it came from PPSSPP or a different App that may be sending/broadcasting data to the same port being used by a game - // (in case the IP was resolvable but came from a different App, which will need to be discarded too) - // Note: Looping to check too many packets (ie. contiguous) to discard per one non-blocking PdpRecv syscall may cause a slow down - if (received != SOCKET_ERROR && !resolveIP(sin.sin_addr.s_addr, &mac)) { - // Remove the packet from socket buffer - sinlen = sizeof(sin); - memset(&sin, 0, sinlen); - recvfrom(pdpsocket.id, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); - if (flag) { - VERBOSE_LOG(Log::sceNet, "%08x=sceNetAdhocPdpRecv: would block (disc)", SCE_NET_ADHOC_ERROR_WOULD_BLOCK); // Temporary fix to avoid a crash on the Logs due to trying to Logs syscall's argument from another thread (ie. AdhocMatchingInput thread) - return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; // hleLogSuccessVerboseX(Log::sceNet, SCE_NET_ADHOC_ERROR_WOULD_BLOCK, "would block (disc)"); - } - else { - // Simulate blocking behaviour with non-blocking socket, and discard more unresolvable packets until timeout reached - u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | pdpsocket.id; - return WaitBlockingAdhocSocket(threadSocketId, PDP_RECV, id, buf, len, timeout, saddr, sport, "pdp recv (disc)"); - } - } - else - break; - } - } - - // At this point we assumed that the packet is a valid PPSSPP packet - if (received != SOCKET_ERROR && *len < received) { - INFO_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i:%u]: Peeked %u/%u bytes from %s:%u\n", id, getLocalPort(pdpsocket.id), received, *len, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); - - // A datagram larger than the buffer stays queued, and isn't copied (pspautotests net/adhoc/pdp). - // Return the actual available data size - *len = received; - - // Provide Sender Information - *saddr = mac; - *sport = ntohs(sin.sin_port) - portOffset; - - // Update last recv timestamp, may cause disconnection not detected properly tho - peerlock.lock(); - auto peer = findFriend(&mac); - if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); - peerlock.unlock(); - - return hleLogVerbose(Log::sceNet, SCE_NET_ADHOC_ERROR_NOT_ENOUGH_SPACE, "not enough space"); - } - - if (!serverHasRelay) { - sinlen = sizeof(sin); - memset(&sin, 0, sinlen); - // On Windows: Socket Error 10014 may happen when buffer size is less than the minimum allowed/required (ie. negative number on Vulcanus Seek and Destroy), the address is not a valid part of the user address space (ie. on the stack or when buffer overflow occurred), or the address is not properly aligned (ie. multiple of 4 on 32bit and multiple of 8 on 64bit) https://stackoverflow.com/questions/861154/winsock-error-code-10014 - received = recvfrom(pdpsocket.id, (char*)buf, std::max(0, *len), MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); - error = socket_errno; - } - - // On Windows: recvfrom on UDP can get error WSAECONNRESET when previous sendto's destination is unreachable (or destination port is not bound), may need to disable SIO_UDP_CONNRESET - if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || error == ECONNRESET)) { - if (flag == 0) { - // Simulate blocking behaviour with non-blocking socket - u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | pdpsocket.id; - return WaitBlockingAdhocSocket(threadSocketId, PDP_RECV, id, buf, len, timeout, saddr, sport, "pdp recv"); - } - - VERBOSE_LOG(Log::sceNet, "%08x=sceNetAdhocPdpRecv: would block", SCE_NET_ADHOC_ERROR_WOULD_BLOCK); // Temporary fix to avoid a crash on the Logs due to trying to Logs syscall's argument from another thread (ie. AdhocMatchingInput thread) - return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; // hleLogSuccessVerboseX(Log::sceNet, SCE_NET_ADHOC_ERROR_WOULD_BLOCK, "would block"); - } - - hleEatMicro(50); - // Received Data. UDP can also receives 0 data, while on TCP 0 data = connection gracefully closed, but not sure about PDP tho - if (received >= 0) { - DEBUG_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i:%u]: Received %u bytes from %s:%u\n", id, getLocalPort(pdpsocket.id), received, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); - - // Find Peer MAC - if (resolveIP(sin.sin_addr.s_addr, &mac)) { - // Provide Sender Information - *saddr = mac; - *sport = ntohs(sin.sin_port) - portOffset; - - // Save Length - *len = received; // The PSP writes the received length back (pspautotests net/adhoc/pdp). - - // Update last recv timestamp, may cause disconnection not detected properly tho - peerlock.lock(); - auto peer = findFriend(&mac); - if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); - peerlock.unlock(); - - // Free Network Lock - //_freeNetworkLock(); - - // Return Success. According to pspsdk-1.0+beta2 returned value is Number of bytes received, but JPCSP returning 0? - return 0; // 0, not the length, as on the PSP (pspautotests net/adhoc/pdp). KH BBS can't see rooms otherwise. - } - - // Free Network Lock - //_freeNetworkLock(); - - //free(tmpbuf); - - // Receiving data from unknown peer? Should never reached here! Unless the Peeked's packet was different than the Recved one (which mean there is a problem) - WARN_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i:%u]: Received %i bytes from Unknown Peer %s:%u", id, getLocalPort(pdpsocket.id), received, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); - if (flag) { - VERBOSE_LOG(Log::sceNet, "%08x=sceNetAdhocPdpRecv: would block (problem)", SCE_NET_ADHOC_ERROR_WOULD_BLOCK); // Temporary fix to avoid a crash on the Logs due to trying to Logs syscall's argument from another thread (ie. AdhocMatchingInput thread) - return SCE_NET_ADHOC_ERROR_WOULD_BLOCK; // hleLogSuccessVerboseX(Log::sceNet, SCE_NET_ADHOC_ERROR_WOULD_BLOCK, "would block (problem)"); - } - } - - // Free Network Lock - //_freeNetworkLock(); - -#ifdef PDP_DIRTY_MAGIC - // Restore Nonblocking Flag for Return Value - if (wouldblock) flag = 1; -#endif - - DEBUG_LOG(Log::sceNet, "sceNetAdhocPdpRecv[%i:%u]: Result:%i (Error:%i)", id, pdpsocket.lport, received, error); - - // Unexpected error (other than EAGAIN/EWOULDBLOCK/ECONNRESET) or in case the Peeked's packet was different than Recved one, treated as Timeout? - return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_TIMEOUT, "timeout?"); + return Transport().PdpRecv(id, saddr, sport, buf, len, timeout, flag); } // Invalid Argument @@ -3416,173 +2287,41 @@ int sceNetAdhocSetSocketAlert(MIPSState *mips, int id, int flag) { return hleDelayResult(hleLogDebug(Log::sceNet, retval), "set socket alert delay", 1000); } -static bool postoffice_can_recv_or_listen_has_request(int idx) { - AdhocSocket *internal = adhocSockets[idx]; - if (internal->type == SOCK_PTP) { - if (internal->data.ptp.state == ADHOC_PTP_STATE_LISTEN) { - void *ptp_listen_handle = ptp_listen_postoffice_recover(idx); - if (ptp_listen_handle == NULL) { - return false; - } - int has_request = ptp_listen_has_request(ptp_listen_handle); - if (has_request == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { - internal->postofficeHandle = NULL; - ptp_listen_close(ptp_listen_handle); - return false; - } - if (has_request) { - return true; - } - } else if (internal->data.ptp.state == ADHOC_PTP_STATE_ESTABLISHED) { - void *ptp_handle = internal->postofficeHandle; - if (ptp_handle == NULL) { - return false; - } - int peek_len = ptp_peek_next_size(ptp_handle); - if (peek_len == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { - return false; - } - if (peek_len > 0) { - return true; - } - } - } else { - void *pdp_handle = pdp_postoffice_recover(idx); - if (pdp_handle == NULL) { - return false; - } - int peek_len = pdp_buffered_data_size(pdp_handle); - if (peek_len == AEMU_POSTOFFICE_CLIENT_SESSION_DEAD) { - internal->postofficeHandle = NULL; - pdp_delete(pdp_handle); - return false; - } - if (peek_len > 0) { - return true; - } - } - return false; -} - -static bool postoffice_can_send(int idx) { - AdhocSocket *internal = adhocSockets[idx]; - if (internal->type == SOCK_PTP && internal->data.ptp.state == ADHOC_PTP_STATE_ESTABLISHED) { - void *ptp_handle = internal->postofficeHandle; - if (ptp_handle == NULL) { - return false; - } - if (ptp_is_dead(ptp_handle)) { - return false; - } - return ptp_send_buf_not_full(ptp_handle); - } else { - void *pdp_handle = pdp_postoffice_recover(idx); - if (pdp_handle == NULL) { - return false; - } - if (pdp_is_dead(pdp_handle)) { - internal->postofficeHandle = NULL; - pdp_delete(pdp_handle); - return false; - } - return pdp_send_buf_not_full(pdp_handle); - } - return false; -} - int PollAdhocSocket(SceNetAdhocPollSd* sds, int count, int timeout, int nonblock) { - //WSAPoll only available for Vista or newer, so we'll use an alternative way for XP since Windows doesn't have poll function like *NIX - fd_set readfds, writefds, exceptfds; - int fd; - int maxfd = 0; - FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); - for (int i = 0; i < count; i++) { sds[i].revents = 0; - // Fill in Socket ID - if (sds[i].id > 0 && sds[i].id <= MAX_SOCKET && adhocSockets[sds[i].id - 1] != NULL) { - auto sock = adhocSockets[sds[i].id - 1]; - if (!sock) { - return SCE_NET_ADHOC_ERROR_SOCKET_DELETED; - } - - if (serverHasRelay) { - continue; - } else { - if (sock->type == SOCK_PTP) { - fd = sock->data.ptp.id; - } - else { - fd = sock->data.pdp.id; - } - } - - if (fd > maxfd) maxfd = fd; - FD_SET(fd, &readfds); - FD_SET(fd, &writefds); - FD_SET(fd, &exceptfds); - } } - timeval tmout; - tmout.tv_sec = timeout / 1000000; // seconds - tmout.tv_usec = (timeout % 1000000); // microseconds - int affectedsockets = 0; + std::vector states(std::max(count, 0)); + int affectedsockets = Transport().PollWait(sds, count, timeout, states.data()); // A disconnected socket makes the call return EXCEPTION_EVENT, whatever else is ready. bool disconnected = false; - if (!serverHasRelay) - affectedsockets = select(maxfd + 1, &readfds, &writefds, &exceptfds, &tmout); if (affectedsockets >= 0) { affectedsockets = 0; for (int i = 0; i < count; i++) { if (sds[i].id > 0 && sds[i].id <= MAX_SOCKET && adhocSockets[sds[i].id - 1] != NULL) { auto sock = adhocSockets[sds[i].id - 1]; + const AdhocPollState &state = states[i]; - if (!serverHasRelay) { - if (sock->type == SOCK_PTP) { - fd = sock->data.ptp.id; - } - else { - fd = sock->data.pdp.id; - } - } - if (serverHasRelay) { - if ((sds[i].events & ADHOC_EV_RECV) && postoffice_can_recv_or_listen_has_request(sds[i].id - 1)) - sds[i].revents |= ADHOC_EV_RECV; - if ((sds[i].events & ADHOC_EV_SEND) && postoffice_can_send(sds[i].id - 1)) - sds[i].revents |= ADHOC_EV_SEND; - } else { - if ((sds[i].events & ADHOC_EV_RECV) && FD_ISSET(fd, &readfds)) - sds[i].revents |= ADHOC_EV_RECV; - if ((sds[i].events & ADHOC_EV_SEND) && FD_ISSET(fd, &writefds)) - sds[i].revents |= ADHOC_EV_SEND; - if (sock->alerted_flags) - sds[i].revents |= ADHOC_EV_ALERT; - } + if ((sds[i].events & ADHOC_EV_RECV) && state.readable) + sds[i].revents |= ADHOC_EV_RECV; + if ((sds[i].events & ADHOC_EV_SEND) && state.writable) + sds[i].revents |= ADHOC_EV_SEND; + if (state.alerted) + sds[i].revents |= ADHOC_EV_ALERT; // Mask certain revents bits with events bits sds[i].revents &= sds[i].events; // What listening and connected PTP sockets report on a PSP (pspautotests net/adhoc/poll): // SEND and FLUSH always, CONNECT once connected, and ACCEPT or RECV when there's something // to take. A connection whose peer has closed reports DISCONNECT alone. - const bool readable = serverHasRelay ? postoffice_can_recv_or_listen_has_request(sds[i].id - 1) : FD_ISSET(fd, &readfds); - // A connection PtpOpen started may have come up since anything looked at it. - // A refused one reports DISCONNECT (pspautotests net/adhoc/poll). - if (sock->type == SOCK_PTP && sock->data.ptp.state == ADHOC_PTP_STATE_SYN_SENT && !serverHasRelay && (FD_ISSET(fd, &writefds) || FD_ISSET(fd, &exceptfds))) { - int connectError = 0; - if (ptpReallyConnected(fd, &connectError)) { - ptpSetEstablished(sock); - } else if (!connectInProgress(connectError)) { - if (RecreatePtpSocket(sds[i].id) < 0) { - WARN_LOG(Log::sceNet, "sceNetAdhocPollSocket[%i]: Failed to recreate the refused socket", sds[i].id); - } - sock->data.ptp.state = ADHOC_PTP_STATE_CLOSED; - } + const bool readable = state.readable; + if (sock->type == SOCK_PTP && sock->data.ptp.state == ADHOC_PTP_STATE_SYN_SENT) { + Transport().PollCheckConnecting(sds[i].id, sock, state); } if (sock->type == SOCK_PTP && sock->data.ptp.state == ADHOC_PTP_STATE_LISTEN) { sds[i].revents = (ADHOC_EV_SEND | ADHOC_EV_FLUSH | (readable ? ADHOC_EV_ACCEPT : 0)) & sds[i].events; } else if (sock->type == SOCK_PTP && sock->data.ptp.state == ADHOC_PTP_STATE_ESTABLISHED) { - char peek; - if (!serverHasRelay && readable && recv(fd, &peek, 1, MSG_PEEK | MSG_NOSIGNAL) == 0) { + if (readable && Transport().PtpPeerClosed(sock)) { sds[i].revents = ADHOC_EV_DISCONNECT; disconnected = true; } else { @@ -3714,17 +2453,6 @@ int sceNetAdhocPollSocket(MIPSState *mips, u32 socketStructAddr, int count, int return hleLogDebug(Log::sceNet, SCE_NET_ADHOC_ERROR_NOT_INITIALIZED, "adhoc not initialized"); } -static int pdp_delete_postoffice(int idx) { - AdhocSocket *internal = adhocSockets[idx]; - if (internal->postofficeHandle != NULL) { - pdp_delete(internal->postofficeHandle); - } - adhocSockets[idx] = NULL; - free(internal); - INFO_LOG(Log::sceNet, "%s: closed pdp socket with id %d", __func__, idx + 1); - return 0; -} - int NetAdhocPdp_Delete(int id, int unknown) { // Library is initialized if (netAdhocInited) { @@ -3735,25 +2463,7 @@ int NetAdhocPdp_Delete(int id, int unknown) { // Valid Socket if (sock != NULL && sock->type == SOCK_PDP) { - if (serverHasRelay) - return pdp_delete_postoffice(id - 1); - - // Close Connection - shutdown(sock->data.pdp.id, SD_RECEIVE); - closesocket(sock->data.pdp.id); - - // Remove Port Forward from Router - //sceNetPortClose("UDP", sock->lport); - //g_PortManager.Remove(IP_PROTOCOL_UDP, isOriPort ? sock->lport : sock->lport + portOffset); // Let's not remove mapping in real-time as it could cause lags/disconnection when joining a room with slow routers - - // Free Memory - free(sock); - - // Free Translation Slot - adhocSockets[id - 1] = NULL; - - // Success - return 0; + return Transport().PdpDelete(id); } // Invalid Socket ID @@ -4658,26 +3368,7 @@ static int sceNetAdhocGetPdpStat(MIPSState *mips, u32 structSize, u32 structAddr // Set available bytes to be received. With FIONREAD There might be ghosting 1 byte in recv buffer when remote peer's socket got closed (ie. Warriors Orochi 2) Attempting to recv this ghost 1 byte will result to socket error 10054 (may need to disable SIO_UDP_CONNRESET error) // It seems real PSP respecting the socket buffer size arg, so we may need to cap the value up to the buffer size arg since we use larger buffer, for PDP/UDP the total size must not contains partial/truncated message to avoid data loss. // TODO: We may need to manage PDP messages ourself by reading each msg 1-by-1 and moving it to our internal buffer(msg array) in order to calculate the correct messages size that can fit into buffer size when there are more than 1 messages in the recv buffer (simulate FIONREAD) - if (serverHasRelay) { - void *postofficeHandle = pdp_postoffice_recover(j); - if (postofficeHandle != NULL) { - sock->data.pdp.rcv_sb_cc = pdp_buffered_data_size(postofficeHandle); - } - } else { - sock->data.pdp.rcv_sb_cc = getAvailToRecv(sock->data.pdp.id, sock->buffer_size); - // There might be a possibility for the data to be taken by the OS, thus FIONREAD returns 0, but can be Received - if (sock->data.pdp.rcv_sb_cc == 0) { - // Let's try to peek the data size - // TODO: May need to filter out packets from an IP that can't be translated to MAC address - struct sockaddr_in sin; - socklen_t sinlen; - sinlen = sizeof(sin); - memset(&sin, 0, sinlen); - int received = recvfrom(sock->data.pdp.id, dummyPeekBuf64k, std::min((u32)dummyPeekBuf64kSize, sock->buffer_size), MSG_PEEK | MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); - if (received > 0) - sock->data.pdp.rcv_sb_cc = received; - } - } + Transport().PdpUpdateRecvStat(j + 1, sock); // Copy Socket Data from Internal Memory memcpy(&buf[i], &sock->data.pdp, sizeof(SceNetAdhocPdpStat)); @@ -4722,6 +3413,7 @@ static int sceNetAdhocGetPdpStat(MIPSState *mips, u32 structSize, u32 structAddr * @param buf PTP Socket List Buffer (can be NULL if you wish to receive Required Length) * @return 0 on success or... ADHOC_INVALID_ARG, ADHOC_NOT_INITIALIZED */ + static int sceNetAdhocGetPtpStat(MIPSState *mips, u32 structSize, u32 structAddr) { // Spams a lot VERBOSE_LOG(Log::sceNet,"sceNetAdhocGetPtpStat(%08x, %08x) at %08x",structSize,structAddr,mips->pc); @@ -4762,25 +3454,7 @@ static int sceNetAdhocGetPtpStat(MIPSState *mips, u32 structSize, u32 structAddr if ( sock != NULL && sock->type == SOCK_PTP) { ptpUpdateConnecting(sock); - if (serverHasRelay) { - if (sock->postofficeHandle != NULL) { - sock->data.ptp.rcv_sb_cc = ptp_peek_next_size(sock->postofficeHandle); - } else { - sock->data.ptp.rcv_sb_cc = 0; - } - }else{ - // Set available bytes to be received - sock->data.ptp.rcv_sb_cc = getAvailToRecv(sock->data.ptp.id); - // It seems real PSP respecting the socket buffer size arg, so we may need to cap the value to the buffer size arg since we use larger buffer - sock->data.ptp.rcv_sb_cc = std::min(sock->data.ptp.rcv_sb_cc, (u32_le)sock->buffer_size); - // There might be a possibility for the data to be taken by the OS, thus FIONREAD returns 0, but can be Received - if (sock->data.ptp.rcv_sb_cc == 0) { - // Let's try to peek the data size - int received = recv(sock->data.ptp.id, dummyPeekBuf64k, std::min((u32)dummyPeekBuf64kSize, sock->buffer_size), MSG_PEEK | MSG_NOSIGNAL); - if (received > 0) - sock->data.ptp.rcv_sb_cc = received; - } - } + Transport().PtpUpdateRecvStat(sock); // Copy Socket Data from internal Memory memcpy(&buf[i], &sock->data.ptp, sizeof(SceNetAdhocPtpStat)); @@ -4909,7 +3583,7 @@ int RecreatePtpSocket(int ptpId) { return 0; } -static uint16_t get_random_unused_ptp_port() { +uint16_t get_random_unused_ptp_port() { uint16_t test_port = rand() % 65534 + 1; auto port_in_use = [&test_port] { for (int i = 0; i < MAX_SOCKET; i++) { @@ -4930,56 +3604,6 @@ static uint16_t get_random_unused_ptp_port() { return test_port; } -static int ptp_open_postoffice(const SceNetEtherAddr *saddr, uint16_t sport, const SceNetEtherAddr *daddr, uint16_t dport, uint32_t bufsize, uint32_t rexmt_int, int rexmt_cnt) { - AdhocSocket *internal = (AdhocSocket *)malloc(sizeof(AdhocSocket)); - if (internal == NULL) { - ERROR_LOG(Log::sceNet, "%s: ran out of heap memory trying to open ptp socket", __func__); - return ERROR_NET_NO_SPACE; - } - memset(internal, 0, sizeof(AdhocSocket)); - internal->retry_interval = rexmt_int; - internal->retry_count = rexmt_cnt; - - internal->type = SOCK_PTP; - internal->postofficeHandle = NULL; - internal->data.ptp.state = ADHOC_PTP_STATE_CLOSED; - internal->data.ptp.laddr = *saddr; - internal->data.ptp.lport = sport == 0 ? get_random_unused_ptp_port() : sport; - internal->data.ptp.paddr = *daddr; - internal->data.ptp.pport = dport; - internal->data.ptp.rcv_sb_cc = bufsize; - internal->data.ptp.snd_sb_cc = 0; - internal->flags = 0; - internal->connectThread = NULL; - internal->connectThreadDone = true; - internal->lastAttempt = 0; - internal->internalLastAttempt = 0; - - AdhocSocket **slot = NULL; - int i; - i = FindFreeAdhocSocketSlot(); - if (i < MAX_SOCKET) { - slot = &adhocSockets[i]; - } - - if (slot == NULL) { - free(internal); - ERROR_LOG(Log::sceNet, "%s: cannot find free socket mapper slot while opening ptp socket", __func__); - return ERROR_NET_NO_SPACE; - } - - internal->data.ptp.id = AEMU_POSTOFFICE_ID_BASE + i; - - *slot = internal; - INFO_LOG(Log::sceNet, "%s: created ptp socket with id %d", __func__, i + 1); - - // PtpOpen starts connecting by itself: on the PSP the client is ESTABLISHED right after it (pspautotests - // net/adhoc/ptp). The Warriors sends without calling PtpConnect. - NetAdhocPtp_Connect(i + 1, 0, 1, false); - - return i + 1; -} - /** * Adhoc Emulator PTP Active Socket Creator * @param saddr Local MAC (Unused) @@ -4992,6 +3616,7 @@ static int ptp_open_postoffice(const SceNetEtherAddr *saddr, uint16_t sport, con * @param flag Bitflags (Unused) * @return Socket ID > 0 on success or... ADHOC_NOT_INITIALIZED, ADHOC_INVALID_ARG, ADHOC_INVALID_ADDR, ADHOC_INVALID_PORT */ + static int sceNetAdhocPtpOpen(MIPSState *mips, const char *srcmac, int sport, const char *dstmac, int dport, int bufsize, int rexmt_int, int rexmt_cnt, int flag) { INFO_LOG(Log::sceNet, "sceNetAdhocPtpOpen(%s, %d, %s, %d, %d, %d, %d, %d) at %08x", mac2str((SceNetEtherAddr*)srcmac).c_str(), sport, mac2str((SceNetEtherAddr*)dstmac).c_str(),dport,bufsize, rexmt_int, rexmt_cnt, flag, mips->pc); if (!g_Config.bEnableWlan) { @@ -4999,7 +3624,6 @@ static int sceNetAdhocPtpOpen(MIPSState *mips, const char *srcmac, int sport, co } SceNetEtherAddr* saddr = (SceNetEtherAddr*)srcmac; SceNetEtherAddr* daddr = (SceNetEtherAddr*)dstmac; - bool isClient = false; // pspautotests net/adhoc/ptp. Zero sizes and retransmit settings are accepted. if (saddr == NULL || daddr == NULL || flag != 0) { @@ -5039,150 +3663,9 @@ static int sceNetAdhocPtpOpen(MIPSState *mips, const char *srcmac, int sport, co return hleLogDebug(Log::sceNet, SCE_NET_ADHOC_ERROR_PORT_IN_USE, "port in use"); } - // Random Port required - if (sport == 0 && !serverHasRelay) { - isClient = true; - //sport 0 should be shifted back to 0 when using offset Phantasy Star Portable 2 use this - sport = -static_cast(portOffset); - } - // Valid Arguments if (bufsize > 0 && rexmt_int >= 0 && rexmt_cnt >= 0) { - if (serverHasRelay) - return ptp_open_postoffice(saddr, sport, daddr, dport, bufsize, rexmt_int, rexmt_cnt); - - // Create Infrastructure Socket (?) - // Socket is remapped through adhocSockets - int tcpsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - - // Valid Socket produced - if (tcpsocket > 0) { - // Change socket MSS - setSockMSS(tcpsocket, PSP_ADHOC_PTP_MSS); - - // Change socket buffer size to be consistent on all platforms. - setSockBufferSize(tcpsocket, SO_SNDBUF, bufsize*5); //PSP_ADHOC_PTP_MSS - setSockBufferSize(tcpsocket, SO_RCVBUF, bufsize*10); //PSP_ADHOC_PTP_MSS*10 - - // Enable KeepAlive - setSockKeepAlive(tcpsocket, true, rexmt_int / 1000000L, rexmt_cnt); - - // Ignore SIGPIPE when supported (ie. BSD/MacOS) - setSockNoSIGPIPE(tcpsocket, 1); - - // Enable Port Re-use - setSockReuseAddrPort(tcpsocket); - - // Apply Default Send Timeout Settings to Socket - setSockTimeout(tcpsocket, SO_SNDTIMEO, rexmt_int); - - // Disable Nagle Algo to send immediately. Or may be we shouldn't disable Nagle since there is PtpFlush function? - setSockNoDelay(tcpsocket, 1); - - // Binding Information for local Port - struct sockaddr_in addr {}; - // addr.sin_len = sizeof(addr); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = INADDR_ANY; - if (isLocalServer) { - getLocalIp(&addr); - } - uint16_t requestedport = static_cast(sport + static_cast(portOffset)); - // Avoid getting random port due to port offset when original port wasn't 0 (ie. original_port + port_offset = 65536 = 0) - if (requestedport == 0 && sport > 0) - requestedport = 65535; // Hopefully it will be safe to default it to 65535 since there can't be more than one port that can bumped into 65536 - // Show a warning about privileged ports - if (requestedport != 0 && requestedport < 1024) { - WARN_LOG(Log::sceNet, "sceNetAdhocPtpOpen - Ports below 1024(ie. %hu) may require Admin Privileges", requestedport); - } - addr.sin_port = htons(requestedport); - - // Bound Socket to local Port - if (bind(tcpsocket, (struct sockaddr*)&addr, sizeof(addr)) == 0) { - // Update sport with the port assigned internal->lport = ntohs(local.sin_port) - socklen_t len = sizeof(addr); - if (getsockname(tcpsocket, (struct sockaddr*)&addr, &len) == 0) { - uint16_t boundport = ntohs(addr.sin_port); - if (sport + static_cast(portOffset) >= 65536 || static_cast(boundport) - static_cast(portOffset) <= 0) - WARN_LOG(Log::sceNet, "sceNetAdhocPtpOpen - Wrapped Port Detected: Original(%d) -> Requested(%d), Bound(%d) -> BoundOriginal(%d)", sport, requestedport, boundport, boundport - portOffset); - sport = boundport - portOffset; - } - - // Allocate Memory - AdhocSocket* internal = (AdhocSocket*)malloc(sizeof(AdhocSocket)); - - // Allocated Memory - if (internal != NULL) { - // Find Free Translator ID - // FIXME: We should probably use an increasing index instead of looking for an empty slot from beginning if we want to simulate a real socket id - int i = 0; - i = FindFreeAdhocSocketSlot(); - - // Found Free Translator ID - if (i < MAX_SOCKET) { - // Clear Memory - memset(internal, 0, sizeof(AdhocSocket)); - - // Socket Type - internal->type = SOCK_PTP; - internal->retry_interval = rexmt_int; - internal->retry_count = rexmt_cnt; - internal->nonblocking = flag; - internal->buffer_size = bufsize; - internal->isClient = isClient; - - // Copy Infrastructure Socket ID - internal->data.ptp.id = tcpsocket; - - // Copy Address & Port Information - internal->data.ptp.laddr = *saddr; - internal->data.ptp.paddr = *daddr; - internal->data.ptp.lport = sport; - internal->data.ptp.pport = dport; - - // Link PTP Socket - adhocSockets[i] = internal; - - // Add Port Forward to Router. We may not even need to forward this local port, since PtpOpen usually have port 0 (any port) as source port and followed by PtpConnect (which mean acting as Client), right? - //sceNetPortOpen("TCP", sport); - if (!isClient) - UPnP_Add(IP_PROTOCOL_TCP, isOriPort ? sport : sport + portOffset, sport + portOffset); - - // Switch to non-blocking for futher usage - changeBlockingMode(tcpsocket, 1); - - // PtpOpen starts connecting by itself, as on the PSP (see ptp_open_postoffice). - // TODO: Need to handle ECONNREFUSED better on non-Windows, if there are games that never called PtpConnect and only relies on [blocking?] PtpOpen to get connected - NetAdhocPtp_Connect(i + 1, rexmt_int, 1, false); - - // Workaround to give some time to get connected before returning from PtpOpen over high latency - INFO_LOG(Log::sceNet, "sceNetAdhocPtpOpen - PSP Socket id: %i, Host Socket id: %i", i + 1, tcpsocket); - - // Return PTP Socket id - if (g_Config.bForcedFirstConnect && internal->attemptCount == 1) { - return hleDelayResult(hleLogDebug(Log::sceNet, i + 1), "delayed ptpopen", rexmt_int); - } else { - return hleLogDebug(Log::sceNet, i + 1); - } - } - - // Free Memory - free(internal); - } - } - else { - ERROR_LOG(Log::sceNet, "Socket error (%i) when binding port %u", socket_errno, ntohs(addr.sin_port)); - auto n = GetI18NCategory(I18NCat::NETWORKING); - g_OSD.Show(OSDType::MESSAGE_ERROR, - std::string(n->T("Failed to Bind Port")) + " " + std::to_string(sport + portOffset) + "\n" + std::string(n->T("Please change your Port Offset")), 0.0f, "portbindfail"); - } - - // Close Socket - closesocket(tcpsocket); - - // Port not available (exclusively in use?) - return hleLogDebug(Log::sceNet, SCE_NET_ADHOC_ERROR_PORT_NOT_AVAIL, "port not available"); // SCE_NET_ADHOC_ERROR_PORT_IN_USE; // SCE_NET_ADHOC_ERROR_INVALID_PORT; - } + return Transport().PtpOpen(saddr, sport, daddr, dport, bufsize, rexmt_int, rexmt_cnt, flag); } // Invalid Arguments @@ -5314,6 +3797,7 @@ int AcceptPtpSocket(int ptpId, int newsocket, sockaddr_in& peeraddr, SceNetEther * @param flag Nonblocking Flag * @return Socket ID >= 0 on success or... ADHOC_NOT_INITIALIZED, ADHOC_INVALID_ARG, ADHOC_INVALID_SOCKET_ID, ADHOC_SOCKET_DELETED, ADHOC_SOCKET_ALERTED, ADHOC_SOCKET_ID_NOT_AVAIL, ADHOC_WOULD_BLOCK, ADHOC_TIMEOUT, ADHOC_NOT_LISTENED, ADHOC_THREAD_ABORTED, NET_INTERNAL */ + static int sceNetAdhocPtpAccept(MIPSState *mips, int id, u32 peerMacAddrPtr, u32 peerPortPtr, int timeout, int flag) { SceNetEtherAddr * addr = NULL; @@ -5355,56 +3839,7 @@ static int sceNetAdhocPtpAccept(MIPSState *mips, int id, u32 peerMacAddrPtr, u32 if (ptpsocket.state == ADHOC_PTP_STATE_LISTEN) { hleEatMicro(50); - // Address Information - struct sockaddr_in peeraddr; - memset(&peeraddr, 0, sizeof(peeraddr)); - socklen_t peeraddrlen = sizeof(peeraddr); - int error = 0; - - int newsocket = 0; - if (serverHasRelay) { - newsocket = ptp_accept_postoffice(id - 1, addr, port); - if (newsocket >= 0) { - return newsocket; - } else { - newsocket = SOCKET_ERROR; - error = EAGAIN; - } - } else { - // Check if listening socket is ready to accept - newsocket = IsSocketReady(ptpsocket.id, true, false, &error); - if (newsocket > 0) { - // Accept Connection - newsocket = accept(ptpsocket.id, (struct sockaddr*)&peeraddr, &peeraddrlen); - error = socket_errno; - } - } - - if (newsocket == 0 || (newsocket == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK))) { - if (flag == 0) { - // Simulate blocking behaviour with non-blocking socket - u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; - return WaitBlockingAdhocSocket(threadSocketId, PTP_ACCEPT, id, nullptr, nullptr, timeout, addr, port, "ptp accept"); - } - // Prevent spamming Debug Log with retries of non-bocking socket - else { - VERBOSE_LOG(Log::sceNet, "sceNetAdhocPtpAccept[%i]: Socket Error (%i)", id, error); - } - } - - // Accepted New Connection - if (newsocket > 0) { - int newid = AcceptPtpSocket(id, newsocket, peeraddr, addr, port); - if (newid >= 0) - return newid; - } - - // Action would block - if (flag) - return hleLogVerbose(Log::sceNet, SCE_NET_ADHOC_ERROR_WOULD_BLOCK, "would block"); - - // Timeout - return hleLogVerbose(Log::sceNet, SCE_NET_ADHOC_ERROR_TIMEOUT, "timeout"); + return Transport().PtpAccept(id, addr, port, timeout, flag); } // Client Socket @@ -5435,12 +3870,8 @@ int NetAdhocPtp_Connect(int id, int timeout, int flag, bool allowForcedConnect) socket->nonblocking = flag; // A connection PtpOpen started may have come up since. - if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT && !serverHasRelay) { - struct sockaddr_in peer; - socklen_t peerLen = sizeof(peer); - if (getpeername(ptpsocket.id, (struct sockaddr *)&peer, &peerLen) == 0) { - ptpSetEstablished(socket); - } + if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT && Transport().PtpConnectedSinceOpen(socket)) { + ptpSetEstablished(socket); } // Connecting a connected socket returns 0 on the PSP, even with the connect alert set (pspautotests @@ -5474,21 +3905,8 @@ int NetAdhocPtp_Connect(int id, int timeout, int flag, bool allowForcedConnect) // Connect Socket to Peer // NOTE: Based on what i read at stackoverflow, The First Non-blocking POSIX connect will always returns EAGAIN/EWOULDBLOCK because it returns without waiting for ACK/handshake, But GvG Next Plus is treating non-blocking PtpConnect just like blocking connect, May be on a real PSP the first non-blocking sceNetAdhocPtpConnect can be successfull? - int connectresult = 0; int errorcode = 0; - if (serverHasRelay) { - connectresult = ptp_connect_postoffice(id - 1, __func__); - if (connectresult != 0) { - connectresult = SOCKET_ERROR; - errorcode = EAGAIN; - } - } else { - connectresult = connect(ptpsocket.id, (struct sockaddr*)&sin, sizeof(sin)); - errorcode = socket_errno; - if (connectresult == SOCKET_ERROR && errorcode == EISCONN) { - ptpReallyConnected(ptpsocket.id, &errorcode); - } - } + int connectresult = Transport().PtpConnect(id, sin, &errorcode); if (connectresult == SOCKET_ERROR) { if (errorcode == EAGAIN || errorcode == EWOULDBLOCK || errorcode == EALREADY || errorcode == EISCONN) @@ -5512,8 +3930,8 @@ int NetAdhocPtp_Connect(int id, int timeout, int flag, bool allowForcedConnect) else if (connectresult == SOCKET_ERROR) { // Connection in Progress, or // ECONNREFUSED = No connection could be made because the target device actively refused it (on Windows/Linux/Android), or no one listening on the remote address (on Linux/Android) thus should try to connect again later (treated similarly to ETIMEDOUT/ENETUNREACH). - if (serverHasRelay || connectInProgress(errorcode) || errorcode == ECONNREFUSED) { - if (serverHasRelay || connectInProgress(errorcode)) + if (Transport().PtpConnectPending(errorcode) || errorcode == ECONNREFUSED) { + if (Transport().PtpConnectPending(errorcode)) { ptpsocket.state = ADHOC_PTP_STATE_SYN_SENT; } @@ -5583,29 +4001,6 @@ static int sceNetAdhocPtpConnect(MIPSState *mips, int id, int timeout, int flag) return NetAdhocPtp_Connect(id, timeout, flag); } -static int ptp_close_postoffice(int idx) { - AdhocSocket *internal = adhocSockets[idx]; - - // sync - if (internal->connectThread != NULL) { - internal->connectThread->join(); - delete internal->connectThread; - } - - void *socket = internal->postofficeHandle; - if (socket != NULL) { - if (internal->data.ptp.state == ADHOC_PTP_STATE_LISTEN) { - ptp_listen_close(socket); - } else { - ptp_close(socket); - } - } - adhocSockets[idx] = NULL; - free(internal); - INFO_LOG(Log::sceNet, "%s: closed ptp socket with id %d", __func__, idx + 1); - return 0; -} - int NetAdhocPtp_Close(int id, int unknown) { // Library is initialized if (netAdhocInited) { @@ -5616,25 +4011,7 @@ int NetAdhocPtp_Close(int id, int unknown) { // Valid Socket if (socket != NULL && socket->type == SOCK_PTP) { - if (serverHasRelay) - return ptp_close_postoffice(id - 1); - - // Close Connection - shutdown(socket->data.ptp.id, SD_RECEIVE); - closesocket(socket->data.ptp.id); - - // Remove Port Forward from Router - //sceNetPortClose("TCP", socket->lport); - //g_PortManager.Remove(IP_PROTOCOL_TCP, isOriPort ? socket->lport : socket->lport + portOffset); // Let's not remove mapping in real-time as it could cause lags/disconnection when joining a room with slow routers - - // Free Memory - free(socket); - - // Free Reference - adhocSockets[id - 1] = NULL; - - // Success - return 0; + return Transport().PtpClose(id); } return SCE_NET_ADHOC_ERROR_INVALID_SOCKET_ID; @@ -5667,54 +4044,6 @@ static int sceNetAdhocPtpClose(MIPSState *mips, int id, int unknown) { return NetAdhocPtp_Close(id, unknown); } -static int ptp_listen_postoffice(const SceNetEtherAddr *saddr, uint16_t sport, uint32_t bufsize, uint32_t rexmt_int, int rexmt_cnt) { - if (relayDisabled) { - return ERROR_NET_NO_SPACE; - } - - // server connect delegated to accept - AdhocSocket *internal = (AdhocSocket *)malloc(sizeof(AdhocSocket)); - if (internal == NULL) { - ERROR_LOG(Log::sceNet, "%s: out of heap memory when creating ptp listen socket", __func__); - return ERROR_NET_NO_SPACE; - } - memset(internal, 0, sizeof(AdhocSocket)); - internal->retry_interval = rexmt_int; - internal->retry_count = rexmt_cnt; - - internal->postofficeHandle = NULL; - internal->type = SOCK_PTP; - internal->data.ptp.laddr = *saddr; - internal->data.ptp.lport = sport; - internal->data.ptp.state = ADHOC_PTP_STATE_LISTEN; - internal->data.ptp.rcv_sb_cc = bufsize; - internal->data.ptp.snd_sb_cc = 0; - internal->flags = 0; - internal->connectThread = NULL; - internal->lastAttempt = 0; - internal->internalLastAttempt = 0; - - AdhocSocket **slot = NULL; - int i; - i = FindFreeAdhocSocketSlot(); - if (i < MAX_SOCKET) { - slot = &adhocSockets[i]; - } - - if (slot == NULL) { - ERROR_LOG(Log::sceNet, "%s: out of socket slots when creating adhoc ptp listen socket", __func__); - free(internal); - return ERROR_NET_NO_SPACE; - } - - internal->data.ptp.id = AEMU_POSTOFFICE_ID_BASE + i; - - *slot = internal; - ptp_listen_postoffice_recover(i); - INFO_LOG(Log::sceNet, "%s: created ptp listen socket with id %d %p", __func__, i + 1, internal->postofficeHandle); - return i + 1; -} - /** * Adhoc Emulator PTP Passive Socket Creator * @param saddr Local MAC (Unused) @@ -5726,6 +4055,7 @@ static int ptp_listen_postoffice(const SceNetEtherAddr *saddr, uint16_t sport, u * @param flag Bitflags (Unused) * @return Socket ID > 0 on success or... ADHOC_NOT_INITIALIZED, ADHOC_INVALID_ARG, ADHOC_INVALID_ADDR, ADHOC_INVALID_PORT, ADHOC_SOCKET_ID_NOT_AVAIL, ADHOC_PORT_NOT_AVAIL, ADHOC_PORT_IN_USE, NET_NO_SPACE */ + static int sceNetAdhocPtpListen(MIPSState *mips, const char *srcmac, int sport, int bufsize, int rexmt_int, int rexmt_cnt, int backlog, int flag) { INFO_LOG(Log::sceNet, "sceNetAdhocPtpListen(%s, %d, %d, %d, %d, %d, %d) at %08x", mac2str((SceNetEtherAddr*)srcmac).c_str(), sport,bufsize,rexmt_int,rexmt_cnt,backlog,flag, mips->pc); if (!g_Config.bEnableWlan) { @@ -5776,138 +4106,7 @@ static int sceNetAdhocPtpListen(MIPSState *mips, const char *srcmac, int sport, // Valid Arguments if (bufsize > 0 && rexmt_int >= 0 && rexmt_cnt >= 0 && backlog >= 0) { - if (serverHasRelay) - return ptp_listen_postoffice(saddr, sport, bufsize, rexmt_int, rexmt_cnt); - - // Create Infrastructure Socket (?) - // Socket is remapped through adhocSockets - int tcpsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - - // Valid Socket produced - if (tcpsocket > 0) { - // Change socket MSS - setSockMSS(tcpsocket, PSP_ADHOC_PTP_MSS); - - // Change socket buffer size to be consistent on all platforms. - setSockBufferSize(tcpsocket, SO_SNDBUF, bufsize*5); //PSP_ADHOC_PTP_MSS - setSockBufferSize(tcpsocket, SO_RCVBUF, bufsize*10); //PSP_ADHOC_PTP_MSS*10 - - // Enable KeepAlive - setSockKeepAlive(tcpsocket, true, rexmt_int / 1000000L, rexmt_cnt); - - // Ignore SIGPIPE when supported (ie. BSD/MacOS) - setSockNoSIGPIPE(tcpsocket, 1); - - // Enable Port Re-use - setSockReuseAddrPort(tcpsocket); - - // Apply Default Receive Timeout Settings to Socket - setSockTimeout(tcpsocket, SO_RCVTIMEO, rexmt_int); - - // Disable Nagle Algo to send immediately. Or may be we shouldn't disable Nagle since there is PtpFlush function? - setSockNoDelay(tcpsocket, 1); - - // Binding Information for local Port - struct sockaddr_in addr {}; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = INADDR_ANY; - if (isLocalServer) { - getLocalIp(&addr); - } - uint16_t requestedport = static_cast(sport + static_cast(portOffset)); - // Avoid getting random port due to port offset when original port wasn't 0 (ie. original_port + port_offset = 65536 = 0) - if (requestedport == 0 && sport > 0) - requestedport = 65535; // Hopefully it will be safe to default it to 65535 since there can't be more than one port that can bumped into 65536 - // Show a warning about privileged ports - if (requestedport != 0 && requestedport < 1024) { - WARN_LOG(Log::sceNet, "sceNetAdhocPtpListen - Ports below 1024(ie. %hu) may require Admin Privileges", requestedport); - } - addr.sin_port = htons(requestedport); - - int iResult = 0; - // Bound Socket to local Port - if ((iResult = bind(tcpsocket, (struct sockaddr*)&addr, sizeof(addr))) == 0) { - // Update sport with the port assigned internal->lport = ntohs(local.sin_port) - socklen_t len = sizeof(addr); - if (getsockname(tcpsocket, (struct sockaddr*)&addr, &len) == 0) { - uint16_t boundport = ntohs(addr.sin_port); - if (sport + static_cast(portOffset) >= 65536 || static_cast(boundport) - static_cast(portOffset) <= 0) - WARN_LOG(Log::sceNet, "sceNetAdhocPtpListen - Wrapped Port Detected: Original(%d) -> Requested(%d), Bound(%d) -> BoundOriginal(%d)", sport, requestedport, boundport, boundport - portOffset); - sport = boundport - portOffset; - } - // Switch into Listening Mode - if ((iResult = listen(tcpsocket, backlog)) == 0) { - // Allocate Memory - AdhocSocket* internal = (AdhocSocket*)malloc(sizeof(AdhocSocket)); - - // Allocated Memory - if (internal != NULL) { - // Find Free Translator ID - // FIXME: We should probably use an increasing index instead of looking for an empty slot from beginning if we want to simulate a real socket id - int i = 0; - i = FindFreeAdhocSocketSlot(); - - // Found Free Translator ID - if (i < MAX_SOCKET) { - // Clear Memory - memset(internal, 0, sizeof(AdhocSocket)); - - // Socket Type - internal->type = SOCK_PTP; - internal->retry_interval = rexmt_int; - internal->retry_count = rexmt_cnt; - internal->nonblocking = flag; - internal->buffer_size = bufsize; - internal->isClient = isClient; - - // Copy Infrastructure Socket ID - internal->data.ptp.id = tcpsocket; - - // Copy Address & Port Information - internal->data.ptp.laddr = *saddr; - internal->data.ptp.lport = sport; - - // Flag Socket as Listener - internal->data.ptp.state = ADHOC_PTP_STATE_LISTEN; - - // Link PTP Socket - adhocSockets[i] = internal; - - // Add Port Forward to Router - //sceNetPortOpen("TCP", sport); - UPnP_Add(IP_PROTOCOL_TCP, isOriPort ? sport : sport + portOffset, sport + portOffset); - - // Switch to non-blocking for futher usage - changeBlockingMode(tcpsocket, 1); - - // Return PTP Socket id - return hleLogDebug(Log::sceNet, i + 1, "sceNetAdhocPtpListen - PSP Socket id: %i, Host Socket id: %i", i + 1, tcpsocket); - } - - // Free Memory - free(internal); - } - } - } - else { - auto n = GetI18NCategory(I18NCat::NETWORKING); - g_OSD.Show(OSDType::MESSAGE_ERROR, std::string(n->T("Failed to Bind Port")) + " " + std::to_string(sport + portOffset) + "\n" + std::string(n->T("Please change your Port Offset")), 0.0f, "portbindfail"); - } - - if (iResult == SOCKET_ERROR) { - int error = socket_errno; - ERROR_LOG(Log::sceNet, "sceNetAdhocPtpListen[%i]: Socket Error (%i)", sport, error); - } - - // Close Socket - closesocket(tcpsocket); - - // Port not available (exclusively in use?) - return hleLogDebug(Log::sceNet, SCE_NET_ADHOC_ERROR_PORT_NOT_AVAIL, "port not available"); //SCE_NET_ADHOC_ERROR_PORT_IN_USE; // SCE_NET_ADHOC_ERROR_INVALID_PORT; - } - - // Socket not available - return hleLogDebug(Log::sceNet, SCE_NET_ADHOC_ERROR_SOCKET_ID_NOT_AVAIL, "socket id not available"); + return Transport().PtpListen(saddr, sport, bufsize, rexmt_int, rexmt_cnt, backlog, isClient, flag); } // Invalid Arguments @@ -5931,6 +4130,7 @@ static int sceNetAdhocPtpListen(MIPSState *mips, const char *srcmac, int sport, * @param flag Nonblocking Flag * @return 0 on success or... ADHOC_NOT_INITIALIZED, ADHOC_INVALID_ARG, ADHOC_INVALID_SOCKET_ID, ADHOC_SOCKET_DELETED, ADHOC_SOCKET_ALERTED, ADHOC_WOULD_BLOCK, ADHOC_TIMEOUT, ADHOC_NOT_CONNECTED, ADHOC_THREAD_ABORTED, ADHOC_INVALID_DATALEN, ADHOC_DISCONNECTED, NET_INTERNAL, NET_NO_SPACE */ + static int sceNetAdhocPtpSend(MIPSState *mips, int id, u32 dataAddr, u32 dataSizeAddr, int timeout, int flag) { DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpSend(%d,%08x,%08x,%d,%d) at %08x", id, dataAddr, dataSizeAddr, timeout, flag, mips->pc); @@ -5972,68 +4172,7 @@ static int sceNetAdhocPtpSend(MIPSState *mips, int id, u32 dataAddr, u32 dataSiz return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_SOCKET_ALERTED, "socket alerted"); } - // Acquire Network Lock - // _acquireNetworkLock(); - - // Send Data - int sent = 0; - int error = 0; - if (serverHasRelay) { - sent = ptp_send_postoffice(id - 1, data, len); - if (sent == 0) { - // sent - hleEatMicro(122); - return 0; - } - if (sent == SOCKET_ERROR) { - ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - return SCE_NET_ADHOC_ERROR_DISCONNECTED; - } - // SCE_NET_ADHOC_ERROR_WOULD_BLOCK - sent = SOCKET_ERROR; - error = EAGAIN; - }else{ - sent = send(ptpsocket.id, data, *len, MSG_NOSIGNAL); - error = socket_errno; - } - - // Free Network Lock - // _freeNetworkLock(); - - // Success - if (sent > 0) { - hleEatMicro(122); - // Save Length - *len = sent; - - DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpSend[%i:%u]: Sent %u bytes to %s:%u\n", id, ptpsocket.lport, sent, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport); - - // Set to Established on successful Send when an attempt to Connect was initiated - if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) - ptpsocket.state = ADHOC_PTP_STATE_ESTABLISHED; - - // Return Success - return 0; - } - - // Non-Critical Error - else if (sent == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT && (error == ENOTCONN || connectInProgress(error))))) { - // Non-Blocking - if (flag) - return hleLogVerbose(Log::sceNet, SCE_NET_ADHOC_ERROR_WOULD_BLOCK, "would block"); - - // Simulate blocking behaviour with non-blocking socket - u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; - return WaitBlockingAdhocSocket(threadSocketId, PTP_SEND, id, (void*)data, len, timeout, nullptr, nullptr, "ptp send"); - } - - DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpSend[%i:%u -> %s:%u]: Result:%i (Error:%i)", id, ptpsocket.lport, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport, sent, error); - - // Change Socket State - ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - - // Disconnected - return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_DISCONNECTED, "disconnected"); + return Transport().PtpSend(id, data, len, timeout, flag); } // Invalid Arguments @@ -6065,6 +4204,7 @@ static int sceNetAdhocPtpSend(MIPSState *mips, int id, u32 dataAddr, u32 dataSiz * @param flag Nonblocking Flag * @return 0 on success or... ADHOC_NOT_INITIALIZED, ADHOC_INVALID_ARG, ADHOC_INVALID_SOCKET_ID, ADHOC_SOCKET_DELETED, ADHOC_SOCKET_ALERTED, ADHOC_WOULD_BLOCK, ADHOC_TIMEOUT, ADHOC_THREAD_ABORTED, ADHOC_DISCONNECTED, NET_INTERNAL */ + static int sceNetAdhocPtpRecv(MIPSState *mips, int id, u32 dataAddr, u32 dataSizeAddr, int timeout, int flag) { DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpRecv(%d,%08x,%08x,%d,%d) at %08x", id, dataAddr, dataSizeAddr, timeout, flag, mips->pc); @@ -6095,82 +4235,7 @@ static int sceNetAdhocPtpRecv(MIPSState *mips, int id, u32 dataAddr, u32 dataSiz return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_SOCKET_ALERTED, "socket alerted"); } - // Acquire Network Lock - // _acquireNetworkLock(); - - // TODO: Use a different thread (similar to sceIo) for recvfrom, recv & accept to prevent blocking-socket from blocking emulation - int received = 0; - int error = 0; - - if (serverHasRelay) { - received = ptp_recv_postoffice(id - 1, buf, len); - if (received == 0) { - // we got data - DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpRecv[%i:%u]: Result:%i (Error:%i)", id, ptpsocket.lport, received, error); - hleEatMicro(50); - return 0; - } - if (received == SOCKET_ERROR) { - // the socket died, let the game know - ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpRecv[%i:%u]: Result:%i (Error:%i)", id, ptpsocket.lport, received, error); - return SCE_NET_ADHOC_ERROR_DISCONNECTED; - } - // SCE_NET_ADHOC_ERROR_WOULD_BLOCK - received = SOCKET_ERROR; - error = EAGAIN; - } else { - // Receive Data. POSIX: May received 0 bytes when the remote peer already closed the connection. - received = recv(ptpsocket.id, (char*)buf, std::max(0, *len), MSG_NOSIGNAL); - error = socket_errno; - } - - if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT && (error == ENOTCONN || connectInProgress(error))))) { - if (flag == 0) { - // Simulate blocking behaviour with non-blocking socket - u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; - return WaitBlockingAdhocSocket(threadSocketId, PTP_RECV, id, buf, len, timeout, nullptr, nullptr, "ptp recv"); - } - - return hleLogVerbose(Log::sceNet, SCE_NET_ADHOC_ERROR_WOULD_BLOCK, "would block"); - } - - // Free Network Lock - // _freeNetworkLock(); - - hleEatMicro(50); - - // Received Data - if (received > 0) { - // Save Length - *len = received; - - // Update last recv timestamp, may cause disconnection not detected properly tho - peerlock.lock(); - auto peer = findFriend(&ptpsocket.paddr); - if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); - peerlock.unlock(); - - DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpRecv[%i:%u]: Received %u bytes from %s:%u\n", id, ptpsocket.lport, received, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport); - - // Set to Established on successful Recv when an attempt to Connect was initiated - if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) - ptpsocket.state = ADHOC_PTP_STATE_ESTABLISHED; - - // Return Success - return 0; - } - - DEBUG_LOG(Log::sceNet, "sceNetAdhocPtpRecv[%i:%u]: Result:%i (Error:%i)", id, ptpsocket.lport, received, error); - - if (*len == 0) - return 0; - - // Change Socket State - ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - - // Disconnected - return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_DISCONNECTED, "disconnected"); + return Transport().PtpRecv(id, buf, len, timeout, flag); } // A connection whose peer has gone stays DISCONNECTED (pspautotests net/adhoc/ptp). @@ -6194,27 +4259,7 @@ static int sceNetAdhocPtpRecv(MIPSState *mips, int id, u32 dataAddr, u32 dataSiz } int FlushPtpSocket(int socketId) { - if (serverHasRelay) { - // there's no manual flushing in the relay library, it's by default no delay there - return 0; - } - - // Get original Nagle algo value - int n = getSockNoDelay(socketId); - - // Disable Nagle Algo to send immediately - setSockNoDelay(socketId, 1); - - // Send Empty Data just to trigger Nagle on/off effect to flush the send buffer, Do we need to trigger this at all or is it automatically flushed? - //changeBlockingMode(socket->id, nonblock); - int ret = send(socketId, "", 0, MSG_NOSIGNAL); - if (ret == SOCKET_ERROR) ret = socket_errno; - //changeBlockingMode(socket->id, 1); - - // Restore/Enable Nagle Algo - setSockNoDelay(socketId, n); - - return ret; + return Transport().PtpFlush(socketId); } /** diff --git a/Tools/adhoc_session.py b/Tools/adhoc_session.py index 81b3af17e3..ee38d87fe4 100644 --- a/Tools/adhoc_session.py +++ b/Tools/adhoc_session.py @@ -396,10 +396,14 @@ def screen_matches(args, image): want = bytes.fromhex(args.color) have = image.convert("RGB").getpixel((x, y)) return max(abs(a - b) for a, b in zip(have, want)) <= args.tolerance - ref = Image.open(script_path(args, args.ref)).convert("RGB") + ref = Image.open(script_path(args, args.ref)) + # A reference with an alpha channel is compared only where it's opaque: text over a moving + # background, say, with only the letters kept. + mask = ref.getchannel("A").point(lambda a: 255 if a >= 128 else 0) if "A" in ref.getbands() else None + ref = ref.convert("RGB") x, y = parse_ints(args.box, 2)[:2] if args.box else (0, 0) region = image.convert("RGB").crop((x, y, x + ref.width, y + ref.height)) - diff = ImageStat.Stat(ImageChops.difference(region, ref)).mean + diff = ImageStat.Stat(ImageChops.difference(region, ref), mask).mean return sum(diff) / len(diff) <= args.tolerance diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 9a39a21b67..131d100d81 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -182,6 +182,7 @@ + @@ -536,6 +537,8 @@ + + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index c62fc9ce84..d47fbb86c8 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -140,6 +140,8 @@ + + @@ -512,6 +514,7 @@ + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 7346a71cc8..b356ab1045 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -808,6 +808,8 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/HLE/proAdhoc.cpp \ $(SRC)/Core/HLE/proAdhocServer.cpp \ $(SRC)/Core/HLE/NetAdhocCommon.cpp \ + $(SRC)/Core/HLE/AdhocTransportP2P.cpp \ + $(SRC)/Core/HLE/AdhocTransportRelay.cpp \ $(SRC)/Core/HLE/sceNetAdhoc.cpp \ $(SRC)/ext/aemu_postoffice/client/postoffice.c \ $(SRC)/ext/aemu_postoffice/client/postoffice_mem_stdc.c \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 1ac69e5d1b..67e21b0b08 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -829,6 +829,8 @@ SOURCES_CXX += \ $(COREDIR)/HLE/sceNet.cpp \ $(COREDIR)/HLE/sceNet_lib.cpp \ $(COREDIR)/HLE/NetAdhocCommon.cpp \ + $(COREDIR)/HLE/AdhocTransportP2P.cpp \ + $(COREDIR)/HLE/AdhocTransportRelay.cpp \ $(COREDIR)/HLE/sceNetAdhoc.cpp \ $(COREDIR)/HLE/sceNetAdhocMatching.cpp \ $(COREDIR)/HLE/sceNetApctl.cpp \