Files
ppsspp/Core/HLE/AdhocTransportP2P.cpp
T
Henrik RydgårdandClaude Opus 5.5 958d76ea77 sceNetAdhoc: Move the two transports into their own files
RelayTransport, with the aemu_postoffice helpers only it uses, goes to
AdhocTransportRelay.cpp, and P2PTransport to AdhocTransportP2P.cpp. The
helpers they share with sceNetAdhoc.cpp are declared in
AdhocTransport.h. Moved as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:09:35 +01:00

1244 lines
49 KiB
C++

// 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 <mutex>
#include <string>
#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<int>(port + static_cast<int>(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<int>(port + static_cast<int>(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<int>(portOffset) >= 65536 || static_cast<int>(boundport) - static_cast<int>(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<int>(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<int>(sport + static_cast<int>(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<int>(portOffset) >= 65536 || static_cast<int>(boundport) - static_cast<int>(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<int>(sport + static_cast<int>(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<int>(portOffset) >= 65536 || static_cast<int>(boundport) - static_cast<int>(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;
}