mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-01 03:57:17 +01:00
A PSP keeps what arrives in the socket's buffer, of the size given to PdpCreate, and drops a datagram that doesn't fit: a buffer of 0 gets nothing, one of 2048 bytes only some of ten 1000-byte datagrams (pspautotests net/adhoc/pair/flow). Each transport now only hands over the datagrams that have arrived (PdpTakeDatagram), into a queue per socket that is filled every millisecond and whenever the game looks, and PdpRecv, its blocking retry, GetPdpStat, PollSocket and game mode read from that. This replaces each transport's own receive paths, so the relay now reports the sender with NOT_ENOUGH_SPACE too, and P2P's blocking receive no longer misreads a stale errno. A datagram to ourselves goes straight into our own socket's queue, as on a PSP, so it's there at once through the relay too (net/adhoc/term). Also from comparing the transports: - P2P: a blocking Accept of a connection from outside the group keeps waiting instead of returning -1. - Relay: an accepted socket gets an id of its own, not its listener's, which blocking calls are told apart by. - ALERT in PollSocket comes from the socket in both transports. - No send/receive timeouts are set on the game's sockets: all host sockets are non-blocking, and in relay mode the ids aren't host sockets at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1063 lines
38 KiB
C++
1063 lines
38 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::PdpSendTo(int id, const SceNetEtherAddr *mac, uint16_t dport, const sockaddr_in &target, const void *data, int len, int *error) {
|
|
// Out of the group, it goes into the air and reaches nobody (pspautotests net/adhoc/pair/vanish).
|
|
if (offAdhocNetwork() && !isLocalMac(mac)) {
|
|
return len;
|
|
}
|
|
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;
|
|
}
|
|
|
|
bool P2PTransport::PdpTakeDatagram(int id, SceNetEtherAddr *mac, uint16_t *port, std::vector<u8> &data) {
|
|
int fd = adhocSockets[id - 1]->data.pdp.id;
|
|
while (true) {
|
|
struct sockaddr_in sin {};
|
|
socklen_t sinlen = sizeof(sin);
|
|
// On Windows this can fail with WSAECONNRESET when an earlier sendto's destination was unreachable;
|
|
// that's nothing waiting too.
|
|
int received = recvfrom(fd, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_NOSIGNAL, (struct sockaddr *)&sin, &sinlen);
|
|
if (received == SOCKET_ERROR) {
|
|
return false;
|
|
}
|
|
// Only from someone in the group (another app could be sending to the same port), and out of the
|
|
// group, from nobody else (pspautotests net/adhoc/pair/vanish).
|
|
if (!resolveIP(sin.sin_addr.s_addr, mac) || (offAdhocNetwork() && !isLocalMac(mac))) {
|
|
continue;
|
|
}
|
|
*port = ntohs(sin.sin_port) - portOffset;
|
|
data.assign(dummyPeekBuf64k, dummyPeekBuf64k + received);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// send() and recv() on a PTP socket, as a PSP's radio would see them (pspautotests net/adhoc/pair/vanish):
|
|
// out of the group, a send goes nowhere and nothing arrives, and a connection to a peer that has left the
|
|
// network fails once its retransmits would have run out.
|
|
static int ptpSendToPeer(AdhocSocket *sock, const char *data, int len, int *error) {
|
|
if (offAdhocNetwork()) {
|
|
*error = 0;
|
|
return len;
|
|
}
|
|
if (ptpPeerGone(sock)) {
|
|
*error = ECONNRESET;
|
|
return SOCKET_ERROR;
|
|
}
|
|
int sent = send(sock->data.ptp.id, data, len, MSG_NOSIGNAL);
|
|
*error = socket_errno;
|
|
if (sent > 0) {
|
|
ptpNoteSend(sock);
|
|
}
|
|
return sent;
|
|
}
|
|
|
|
static int ptpRecvFromPeer(AdhocSocket *sock, char *buf, int len, int *error) {
|
|
if (offAdhocNetwork()) {
|
|
*error = EAGAIN;
|
|
return SOCKET_ERROR;
|
|
}
|
|
if (ptpPeerGone(sock)) {
|
|
*error = ECONNRESET;
|
|
return SOCKET_ERROR;
|
|
}
|
|
int received = recv(sock->data.ptp.id, buf, len, MSG_NOSIGNAL);
|
|
*error = socket_errno;
|
|
return received;
|
|
}
|
|
|
|
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 = ptpSendToPeer(sock, (const char*)req.buffer, *req.length, &sockerr);
|
|
|
|
// 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 = ptpRecvFromPeer(sock, (char*)req.buffer, std::max(0, *req.length), &sockerr);
|
|
|
|
// 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
|
|
int newid = 0;
|
|
if (ret > 0) {
|
|
// Fails (and closes the connection) when it comes from someone not in the group (yet); then keep
|
|
// waiting, like the relay transport.
|
|
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) {
|
|
// Not getpeername alone: on Windows it succeeds while the connect is still in progress, even one that
|
|
// is about to be refused.
|
|
return PtpConnectionUp(sock);
|
|
}
|
|
|
|
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) {
|
|
int fd = sock->data.ptp.id;
|
|
*error = 0;
|
|
#if !defined(_WIN32)
|
|
if (fd < 0 || fd >= FD_SETSIZE) {
|
|
*error = EBADF;
|
|
return SOCKET_ERROR;
|
|
}
|
|
#endif
|
|
// Windows never makes a socket whose connect failed writable: the failure (like a refusal) shows in the
|
|
// exception set, and the reason in SO_ERROR.
|
|
fd_set writefds, exceptfds;
|
|
FD_ZERO(&writefds);
|
|
FD_ZERO(&exceptfds);
|
|
FD_SET(fd, &writefds);
|
|
FD_SET(fd, &exceptfds);
|
|
timeval tv = {};
|
|
int ret = select(fd + 1, nullptr, &writefds, &exceptfds, &tv);
|
|
if (ret < 0) {
|
|
*error = socket_errno;
|
|
return ret;
|
|
}
|
|
if (ret > 0) {
|
|
// Elsewhere a failed connect is writable, with its reason in SO_ERROR too.
|
|
int soError = 0;
|
|
socklen_t soErrorLen = sizeof(soError);
|
|
getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&soError, &soErrorLen);
|
|
if (soError != 0 || FD_ISSET(fd, &exceptfds)) {
|
|
*error = soError != 0 ? soError : ECONNREFUSED;
|
|
return SOCKET_ERROR;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
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::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 = ptpSendToPeer(socket, data, *len, &error);
|
|
|
|
// 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)
|
|
ptpSetEstablished(socket);
|
|
|
|
// 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 = ptpRecvFromPeer(socket, (char*)buf, std::max(0, *len), &error);
|
|
|
|
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)
|
|
ptpSetEstablished(socket);
|
|
|
|
// 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;
|
|
}
|