mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-10-28 20:48:42 +01:00
* p2p crypto schema * adding p2p keyring * p2p framing and testing * fixups * test fix * missing definations * implementation of host functions * tcp/transport of p2p packets * improvements in binding * guest_net ,improvements and test cases * all mighty clang * fixing macOS tests failures * misc * net_rewrite : net_errors , types and translation schemas * updated fixed chunks transfer on windows * bye bye old net * small updates for the new net * settings and tests * net lib first draft * allow more than 1 P2P instances on from same pc * improved shadnet replies * possible fix for jojo * cleanup * move net files to libaries/net * fixed macOS tests * mighty clang * fixed cmakelists * don't cosume inviation event on client (test code) * fix macOS tests * Large multi-file rework for Signaling and Matching2 network handling / optimization. Consolidates Matching2-signaling and signaling to one process (signaling_handler) General cleanup and consolidation of matching2 context management > nphandler Removal of a lot SIgnaling overall structure cleanup. Matching2: added get and set roommemberdatainternal and the proper type / shadnet call / request / reply. Consolidated signaling/matching2 typedefs to np_types2 Utilized Codex as a reviewer and for general cleanup. * all mighty clang * fixed member attributes * fixed MAPPED address for matching signaling * Fixed sceNpMatching2SignalingGetLocalNetInfo * fixups on net core/library based on fw * improved errno * spilt p2p to net_p2p * some cleanups * fix for localinfo * clang * fix up * cleanups * cleanups * added KernelFstat * fixed fstat * one more fix for fstat... * cleanup old deprecated code * cleanup * General logging cleanup across NP changes. Thanks Stephen! * cleanup based on stephen's suggestions * fixup * fixing tests * blah * fixup * improve memory pools * cleaunp * force https disable option * clang * @stephen's fixes * added network field in settings and ported old network ones --------- Co-authored-by: Metr1k <blackkn1ght1991@gmail.com>
1932 lines
86 KiB
C++
1932 lines
86 KiB
C++
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
// The new libSceNet (src/core/libraries/net) driven through its exports, exactly as a game
|
|
// calls them: guest sockaddrs, guest constants, ORBIS_NET_ERROR_* returns and sceNetErrnoLoc.
|
|
// P2P runs over the real wire format against a second transport standing in for another
|
|
// console.
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <cstring>
|
|
#include <random>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#ifndef _WIN32
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include "core/libraries/error_codes.h"
|
|
#include "core/libraries/net/net.h"
|
|
#include "core/libraries/net/net_p2p.h"
|
|
#include "core/libraries/net/net_error.h"
|
|
#include "core/libraries/net/net_log.h"
|
|
#include "core/libraries/net/net_translate.h"
|
|
#include "core/loader/symbols_resolver.h"
|
|
#include "core/net/p2p_codec.h"
|
|
|
|
using namespace Libraries::Net;
|
|
using namespace std::chrono_literals;
|
|
namespace Host = Core::Net::Host;
|
|
namespace P2P = Core::Net::P2P;
|
|
|
|
namespace {
|
|
|
|
OrbisNetSockaddrIn Loopback(u16 port, u16 vport = 0) {
|
|
OrbisNetSockaddrIn in{};
|
|
in.sin_len = sizeof(in);
|
|
in.sin_family = ORBIS_NET_AF_INET;
|
|
in.sin_port = sceNetHtons(port);
|
|
in.sin_addr = sceNetHtonl(0x7F000001);
|
|
in.sin_vport = sceNetHtons(vport);
|
|
return in;
|
|
}
|
|
|
|
OrbisNetSockaddr* Guest(OrbisNetSockaddrIn* in) {
|
|
return reinterpret_cast<OrbisNetSockaddr*>(in);
|
|
}
|
|
|
|
double Millis(std::chrono::steady_clock::time_point since) {
|
|
return std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - since)
|
|
.count();
|
|
}
|
|
|
|
class NetLib : public ::testing::Test {
|
|
protected:
|
|
static void SetUpTestSuite() {
|
|
ASSERT_EQ(sceNetInit(), ORBIS_OK);
|
|
}
|
|
static void TearDownTestSuite() {
|
|
EXPECT_EQ(sceNetTerm(), ORBIS_OK);
|
|
}
|
|
|
|
/// A TCP listener on an ephemeral loopback port; returns its port.
|
|
static u16 Listen(OrbisNetId* listener) {
|
|
*listener = sceNetSocket("listener", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
EXPECT_GT(*listener, 0);
|
|
auto addr = Loopback(0);
|
|
EXPECT_EQ(sceNetBind(*listener, Guest(&addr), sizeof(addr)), ORBIS_OK);
|
|
EXPECT_EQ(sceNetListen(*listener, 4), ORBIS_OK);
|
|
OrbisNetSockaddrIn bound{};
|
|
u32 len = sizeof(bound);
|
|
EXPECT_EQ(sceNetGetsockname(*listener, Guest(&bound), &len), ORBIS_OK);
|
|
return sceNetNtohs(bound.sin_port);
|
|
}
|
|
|
|
static s32 GetInt(OrbisNetId s, s32 level, s32 name) {
|
|
s32 value = -1;
|
|
u32 len = sizeof(value);
|
|
EXPECT_EQ(sceNetGetsockopt(s, level, name, &value, &len), ORBIS_OK);
|
|
EXPECT_EQ(len, sizeof(value));
|
|
return value;
|
|
}
|
|
|
|
static s32 SetInt(OrbisNetId s, s32 level, s32 name, s32 value) {
|
|
return sceNetSetsockopt(s, level, name, &value, sizeof(value));
|
|
}
|
|
struct ScopedPool {
|
|
s32 id = sceNetPoolCreate("pool", 16 * 1024, 0);
|
|
~ScopedPool() {
|
|
EXPECT_EQ(sceNetPoolDestroy(id), ORBIS_OK);
|
|
}
|
|
};
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
// Helpers and error reporting
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
TEST_F(NetLib, ErrorCodesAndErrno) {
|
|
EXPECT_EQ(sceNetSocketClose(12345), ORBIS_NET_ERROR_EBADF);
|
|
EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EBADF);
|
|
// sceNetSocket: "invalid socket type or protocol family" is EPROTONOSUPPORT.
|
|
EXPECT_EQ(sceNetSocket("bad", 99, ORBIS_NET_SOCK_STREAM, 0), ORBIS_NET_ERROR_EPROTONOSUPPORT);
|
|
EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EPROTONOSUPPORT);
|
|
// The PS4 stack is IPv4-only.
|
|
EXPECT_EQ(sceNetSocket("v6", ORBIS_NET_AF_INET6, ORBIS_NET_SOCK_DGRAM, 0),
|
|
ORBIS_NET_ERROR_EPROTONOSUPPORT);
|
|
// A protocol that does not fit the type, and an over-long debug name.
|
|
EXPECT_EQ(sceNetSocket("tcp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, ORBIS_NET_IPPROTO_UDP),
|
|
ORBIS_NET_ERROR_BASE | ORBIS_NET_EPROTOTYPE);
|
|
const OrbisNetId tcp =
|
|
sceNetSocket("tcp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, ORBIS_NET_IPPROTO_TCP);
|
|
EXPECT_GT(tcp, 0);
|
|
sceNetSocketClose(tcp);
|
|
EXPECT_EQ(sceNetSocket("0123456789abcdefghijklmnopqrstuvwxyz", ORBIS_NET_AF_INET,
|
|
ORBIS_NET_SOCK_DGRAM, 0),
|
|
ORBIS_NET_ERROR_ENAMETOOLONG);
|
|
|
|
// errno is per thread.
|
|
std::thread([] { EXPECT_EQ(*sceNetErrnoLoc(), 0); }).join();
|
|
}
|
|
|
|
TEST_F(NetLib, LogFormatting) {
|
|
// What the log shows for errors, addresses, options and epoll events.
|
|
EXPECT_EQ(ErrnoName(ORBIS_NET_EWOULDBLOCK), "EWOULDBLOCK");
|
|
EXPECT_EQ(ErrnoName(ORBIS_NET_ECONNREFUSED), "ECONNREFUSED");
|
|
EXPECT_EQ(ErrnoName(250), "E250");
|
|
EXPECT_EQ(ErrorCodeName(ORBIS_NET_ERROR_RESOLVER_ENODNS), "RESOLVER_ENODNS");
|
|
EXPECT_EQ(ErrorCodeName(0x12345), "0x12345");
|
|
auto in = Loopback(8080, 3658);
|
|
EXPECT_EQ(FormatSockaddr(Guest(&in), sizeof(in)), "127.0.0.1:8080 vport 3658");
|
|
in.sin_vport = 0;
|
|
EXPECT_EQ(FormatSockaddr(Guest(&in), sizeof(in)), "127.0.0.1:8080");
|
|
EXPECT_EQ(FormatSockaddr(nullptr, 0), "null");
|
|
EXPECT_EQ(SocketTypeName(ORBIS_NET_SOCK_DGRAM_P2P), "DGRAM_P2P");
|
|
EXPECT_EQ(OptionName(ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO), "SOL_SOCKET/SO_NBIO");
|
|
EXPECT_EQ(OptionName(ORBIS_NET_IPPROTO_TCP, 0x77), "IPPROTO_TCP/option 0x77");
|
|
EXPECT_EQ(OptionName(0x55, 1), "level 0x55/option 0x1");
|
|
EXPECT_EQ(EpollEventsName(ORBIS_NET_EPOLLIN | ORBIS_NET_EPOLLDESCID), "IN|DESCID");
|
|
EXPECT_EQ(EpollEventsName(0), "0");
|
|
}
|
|
|
|
TEST_F(NetLib, InitAndTerm) {
|
|
EXPECT_EQ(sceNetInit(), ORBIS_OK);
|
|
EXPECT_EQ(sceNetTerm(), ORBIS_OK);
|
|
EXPECT_EQ(sceNetTerm(), ORBIS_OK);
|
|
EXPECT_EQ(sceNetInit(), ORBIS_OK);
|
|
// Nothing was released: sockets still work.
|
|
const OrbisNetId s = sceNetSocket("after", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
EXPECT_GT(s, 0);
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLib, ErrorTableValues) {
|
|
EXPECT_EQ(ORBIS_NET_ERROR_ECANCELED, static_cast<int>(0x80410155));
|
|
EXPECT_EQ(ORBIS_NET_ECANCELED, 85);
|
|
EXPECT_EQ(ToOrbisErrno(Host::Error::Canceled), 85);
|
|
EXPECT_EQ(ORBIS_NET_ERROR_EBUSY, static_cast<int>(0x80410110));
|
|
EXPECT_EQ(ORBIS_NET_ERROR_RESOLVER_ENOTINIT, static_cast<int>(0x804101ec));
|
|
EXPECT_EQ(ORBIS_NET_ERROR_ENOTTY, static_cast<int>(0x80410119));
|
|
EXPECT_EQ(ORBIS_NET_ERROR_ENOTBLK, static_cast<int>(0x8041010f));
|
|
}
|
|
|
|
TEST_F(NetLib, AbortWithNothingWaitingIsENOTBLK) {
|
|
const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
EXPECT_EQ(sceNetSocketAbort(s, 0), ORBIS_NET_ERROR_ENOTBLK);
|
|
EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_ENOTBLK);
|
|
// With a preservation flag it is kept for the next call instead: not an error.
|
|
EXPECT_EQ(sceNetSocketAbort(s, 1 /* RCV_PRESERVATION */), ORBIS_OK);
|
|
char buf[4];
|
|
EXPECT_EQ(sceNetRecv(s, buf, sizeof(buf), 0), ORBIS_NET_ERROR_EINTR);
|
|
|
|
const OrbisNetId ep = sceNetEpollCreate("ep", 0);
|
|
EXPECT_EQ(sceNetEpollAbort(ep, 0), ORBIS_NET_ERROR_ENOTBLK);
|
|
EXPECT_EQ(sceNetEpollAbort(ep, 1 /* PRESERVATION */), ORBIS_OK);
|
|
sceNetEpollDestroy(ep);
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLib, UnconnectedDatagramSendNeedsAnAddress) {
|
|
const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
EXPECT_EQ(sceNetSend(s, "x", 1, 0), ORBIS_NET_ERROR_EDESTADDRREQ); // every host alike
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLib, ByteOrder) {
|
|
EXPECT_EQ(sceNetHtons(0x1234), 0x3412);
|
|
EXPECT_EQ(sceNetHtonl(0x12345678u), 0x78563412u);
|
|
EXPECT_EQ(sceNetHtonll(0x0102030405060708ull), 0x0807060504030201ull);
|
|
EXPECT_EQ(sceNetNtohll(sceNetHtonll(0xDEADBEEFCAFEF00Dull)), 0xDEADBEEFCAFEF00Dull);
|
|
EXPECT_EQ(sceNetNtohl(sceNetHtonl(42)), 42u);
|
|
EXPECT_EQ(sceNetNtohs(sceNetHtons(42)), 42);
|
|
}
|
|
|
|
TEST_F(NetLib, InetPtonNtop) {
|
|
u32 v4 = 0;
|
|
EXPECT_EQ(sceNetInetPton(ORBIS_NET_AF_INET, "192.168.1.20", &v4), 1);
|
|
EXPECT_EQ(sceNetNtohl(v4), 0xC0A80114u);
|
|
EXPECT_EQ(sceNetInetPton(ORBIS_NET_AF_INET, "not an address", &v4), 0);
|
|
EXPECT_EQ(sceNetInetPton(99, "1.2.3.4", &v4), ORBIS_NET_ERROR_EAFNOSUPPORT);
|
|
|
|
char text[64];
|
|
EXPECT_STREQ(sceNetInetNtop(ORBIS_NET_AF_INET, &v4, text, sizeof(text)), "192.168.1.20");
|
|
EXPECT_EQ(sceNetInetNtop(ORBIS_NET_AF_INET, &v4, text, 4), nullptr); // too small
|
|
EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_ENOSPC);
|
|
|
|
u8 v6[16];
|
|
EXPECT_EQ(sceNetInetPton(ORBIS_NET_AF_INET6, "2001:db8::8:800:200c:417a", v6), 1);
|
|
EXPECT_STREQ(sceNetInetNtop(ORBIS_NET_AF_INET6, v6, text, sizeof(text)),
|
|
"2001:db8::8:800:200c:417a");
|
|
}
|
|
|
|
TEST_F(NetLib, RegisterLibRuns) {
|
|
Core::Loader::SymbolsResolver symbols;
|
|
RegisterLib(&symbols); // every NID registers without touching emulator state
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
// Native sockets
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
TEST_F(NetLib, TcpThroughExports) {
|
|
OrbisNetId listener = 0;
|
|
const u16 port = Listen(&listener);
|
|
|
|
OrbisNetId accepted = -1;
|
|
OrbisNetSockaddrIn peer{};
|
|
u32 peer_len = sizeof(peer);
|
|
std::thread server([&] { accepted = sceNetAccept(listener, Guest(&peer), &peer_len); });
|
|
|
|
const OrbisNetId client = sceNetSocket("client", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
auto to = Loopback(port);
|
|
to.sin_len = 0; // games often leave sa_len unset
|
|
ASSERT_EQ(sceNetConnect(client, Guest(&to), sizeof(to)), ORBIS_OK);
|
|
server.join();
|
|
ASSERT_GT(accepted, 0);
|
|
|
|
// The accepted peer address comes back in guest layout, sa_len included.
|
|
EXPECT_EQ(peer_len, sizeof(OrbisNetSockaddrIn));
|
|
EXPECT_EQ(peer.sin_len, sizeof(OrbisNetSockaddrIn));
|
|
EXPECT_EQ(peer.sin_family, ORBIS_NET_AF_INET);
|
|
EXPECT_EQ(peer.sin_addr, sceNetHtonl(0x7F000001));
|
|
|
|
OrbisNetSockaddrIn remote{};
|
|
u32 remote_len = sizeof(remote);
|
|
ASSERT_EQ(sceNetGetpeername(client, Guest(&remote), &remote_len), ORBIS_OK);
|
|
EXPECT_EQ(sceNetNtohs(remote.sin_port), port);
|
|
|
|
EXPECT_EQ(sceNetSend(client, "hello", 5, 0), 5);
|
|
char buf[16]{};
|
|
EXPECT_EQ(sceNetRecv(accepted, buf, sizeof(buf), 0), 5);
|
|
EXPECT_EQ(std::memcmp(buf, "hello", 5), 0);
|
|
|
|
EXPECT_EQ(sceNetShutdown(client, ORBIS_NET_SHUT_WR), ORBIS_OK);
|
|
EXPECT_EQ(sceNetRecv(accepted, buf, sizeof(buf), 0), 0); // end of stream
|
|
EXPECT_EQ(sceNetShutdown(client, 7), ORBIS_NET_ERROR_EINVAL);
|
|
|
|
sceNetSocketClose(client);
|
|
sceNetSocketClose(accepted);
|
|
sceNetSocketClose(listener);
|
|
}
|
|
|
|
TEST_F(NetLib, UdpWithAddresses) {
|
|
const OrbisNetId a = sceNetSocket("a", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
const OrbisNetId b = sceNetSocket("b", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
auto any = Loopback(0);
|
|
sceNetBind(a, Guest(&any), sizeof(any));
|
|
sceNetBind(b, Guest(&any), sizeof(any));
|
|
OrbisNetSockaddrIn a_addr{}, b_addr{};
|
|
u32 len = sizeof(a_addr);
|
|
sceNetGetsockname(a, Guest(&a_addr), &len);
|
|
len = sizeof(b_addr);
|
|
sceNetGetsockname(b, Guest(&b_addr), &len);
|
|
|
|
EXPECT_EQ(sceNetSendto(a, "datagram", 8, 0, Guest(&b_addr), sizeof(b_addr)), 8);
|
|
char buf[32]{};
|
|
OrbisNetSockaddrIn from{};
|
|
u32 from_len = sizeof(from);
|
|
// PEEK leaves the datagram queued.
|
|
EXPECT_EQ(sceNetRecvfrom(b, buf, sizeof(buf), ORBIS_NET_MSG_PEEK, Guest(&from), &from_len), 8);
|
|
EXPECT_EQ(sceNetRecvfrom(b, buf, sizeof(buf), 0, Guest(&from), &from_len), 8);
|
|
EXPECT_EQ(from.sin_port, a_addr.sin_port);
|
|
EXPECT_EQ(from.sin_len, sizeof(OrbisNetSockaddrIn));
|
|
|
|
// Nothing left: MSG_DONTWAIT and SO_NBIO both give EWOULDBLOCK.
|
|
EXPECT_EQ(sceNetRecvfrom(b, buf, sizeof(buf), ORBIS_NET_MSG_DONTWAIT, nullptr, nullptr),
|
|
ORBIS_NET_ERROR_EWOULDBLOCK);
|
|
EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EWOULDBLOCK);
|
|
EXPECT_EQ(SetInt(b, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, 1), ORBIS_OK);
|
|
EXPECT_EQ(sceNetRecv(b, buf, sizeof(buf), 0), ORBIS_NET_ERROR_EWOULDBLOCK);
|
|
sceNetSocketClose(a);
|
|
sceNetSocketClose(b);
|
|
}
|
|
|
|
TEST_F(NetLib, SocketOptions) {
|
|
const OrbisNetId s = sceNetSocket("opts", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
|
|
// Guest-owned options.
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_TYPE),
|
|
static_cast<int>(ORBIS_NET_SOCK_STREAM));
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO), 0);
|
|
SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, 1);
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO), 1);
|
|
SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO, 250000);
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO), 250000);
|
|
SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, 1);
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR), 1);
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ERROR), 0);
|
|
EXPECT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_TYPE, 2),
|
|
ORBIS_NET_ERROR_BASE | ORBIS_NET_ENOPROTOOPT);
|
|
|
|
// Host options, mapped by name (IP_TTL is 4 on the PS4, 2 on Linux).
|
|
EXPECT_EQ(SetInt(s, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_TTL, 42), ORBIS_OK);
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_TTL), 42);
|
|
EXPECT_EQ(SetInt(s, ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_NODELAY, 1), ORBIS_OK);
|
|
EXPECT_NE(GetInt(s, ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_NODELAY), 0);
|
|
EXPECT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF, 32768), ORBIS_OK);
|
|
EXPECT_GE(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF), 32768);
|
|
|
|
OrbisNetLinger linger{1, 5};
|
|
EXPECT_EQ(
|
|
sceNetSetsockopt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_LINGER, &linger, sizeof(linger)),
|
|
ORBIS_OK);
|
|
OrbisNetLinger back{};
|
|
u32 len = sizeof(back);
|
|
EXPECT_EQ(sceNetGetsockopt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_LINGER, &back, &len),
|
|
ORBIS_OK);
|
|
EXPECT_NE(back.l_onoff, 0);
|
|
EXPECT_EQ(back.l_linger, 5);
|
|
|
|
// Every documented option is known: anything else is ENOPROTOOPT, at every level.
|
|
EXPECT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, 0x7777, 1), ORBIS_NET_ERROR_ENOPROTOOPT);
|
|
EXPECT_EQ(SetInt(s, ORBIS_NET_IPPROTO_TCP, 0x77, 1), ORBIS_NET_ERROR_ENOPROTOOPT);
|
|
EXPECT_EQ(SetInt(s, ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_KEEPLISTEN, 1), ORBIS_OK);
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_KEEPLISTEN), 1);
|
|
EXPECT_EQ(SetInt(s, 12345, 1, 1), ORBIS_NET_ERROR_ENOPROTOOPT);
|
|
EXPECT_EQ(sceNetSetsockopt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, nullptr, 4),
|
|
ORBIS_NET_ERROR_EFAULT);
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLib, StoredOptionsReadBack) {
|
|
const OrbisNetId s = sceNetSocket("opts", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
const struct {
|
|
s32 level;
|
|
s32 name;
|
|
s32 value;
|
|
} options[] = {
|
|
{ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_CONNECTTIMEO, 5000000},
|
|
{ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ACCEPTTIMEO, 3000000},
|
|
{ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, 1},
|
|
{ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ONESBCAST, 1},
|
|
{ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_TTLCHK, 1},
|
|
{ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_MSS_TO_ADVERTISE, 1200},
|
|
};
|
|
for (const auto& o : options) {
|
|
EXPECT_EQ(GetInt(s, o.level, o.name), 0) << o.name; // unset reads as 0
|
|
EXPECT_EQ(SetInt(s, o.level, o.name, o.value), ORBIS_OK) << o.name;
|
|
EXPECT_EQ(GetInt(s, o.level, o.name), o.value) << o.name;
|
|
}
|
|
// Timeouts: <= 0 disables them.
|
|
EXPECT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_CONNECTTIMEO, -5), ORBIS_OK);
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_CONNECTTIMEO), 0);
|
|
// MSS to advertise: 0 or at least 216.
|
|
EXPECT_EQ(SetInt(s, ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_MSS_TO_ADVERTISE, 100),
|
|
ORBIS_NET_ERROR_EINVAL);
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLib, OptionRulesFollowTheReference) {
|
|
const OrbisNetId tcp = sceNetSocket("tcp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
const OrbisNetId udp = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
s32 value = 0;
|
|
u32 len = sizeof(value);
|
|
// "get not possible" / "set not possible": ENOPROTOOPT.
|
|
EXPECT_EQ(sceNetGetsockopt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NAME, &value, &len),
|
|
ORBIS_NET_ERROR_ENOPROTOOPT);
|
|
EXPECT_EQ(SetInt(tcp, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_MAXTTL, 5),
|
|
ORBIS_NET_ERROR_ENOPROTOOPT);
|
|
EXPECT_EQ(SetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ERROR_EX, 0),
|
|
ORBIS_NET_ERROR_ENOPROTOOPT);
|
|
EXPECT_EQ(GetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ERROR_EX), 0);
|
|
// Options for other socket kinds: EPROCUNAVAIL ...
|
|
EXPECT_EQ(SetInt(udp, ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_NODELAY, 1),
|
|
ORBIS_NET_ERROR_EPROCUNAVAIL);
|
|
EXPECT_EQ(SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_KEEPALIVE, 1),
|
|
ORBIS_NET_ERROR_EPROCUNAVAIL);
|
|
EXPECT_EQ(SetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USECRYPTO, 1),
|
|
ORBIS_NET_ERROR_EPROCUNAVAIL); // P2P only
|
|
// ... except SO_BROADCAST, which "other socket types are not affected" by.
|
|
EXPECT_EQ(SetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_BROADCAST, 1), ORBIS_OK);
|
|
// Priority: only 8 to 23 may be set.
|
|
EXPECT_EQ(SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_PRIORITY, 7), ORBIS_NET_ERROR_EINVAL);
|
|
EXPECT_EQ(SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_PRIORITY, 23), ORBIS_OK);
|
|
// Buffers: at most 512 KiB.
|
|
EXPECT_EQ(SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF, 4 << 20), ORBIS_OK);
|
|
EXPECT_EQ(GetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF), 512 * 1024);
|
|
// Multicast TTL and loop take an unsigned char or an int.
|
|
const u8 ttl = 4;
|
|
EXPECT_EQ(sceNetSetsockopt(udp, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_MULTICAST_TTL, &ttl, 1),
|
|
ORBIS_OK);
|
|
EXPECT_EQ(GetInt(udp, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_MULTICAST_TTL), 4);
|
|
u8 loop = 7;
|
|
len = 1;
|
|
EXPECT_EQ(sceNetGetsockopt(udp, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_MULTICAST_LOOP, &loop, &len),
|
|
ORBIS_OK);
|
|
EXPECT_EQ(loop, 1); // default: loopback
|
|
// BSD SO_RCVTIMEO (libkernel setsockopt): struct timeval.
|
|
const s64 tv[2] = {1, 500000};
|
|
EXPECT_EQ(sceNetSetsockopt(udp, ORBIS_NET_SOL_SOCKET, 0x1006, tv, sizeof(tv)), ORBIS_OK);
|
|
EXPECT_EQ(GetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO), 1500000);
|
|
s64 back[2]{};
|
|
len = sizeof(back);
|
|
EXPECT_EQ(sceNetGetsockopt(udp, ORBIS_NET_SOL_SOCKET, 0x1006, back, &len), ORBIS_OK);
|
|
EXPECT_EQ(back[0], 1);
|
|
EXPECT_EQ(back[1], 500000);
|
|
// IP_DONTFRAG reaches the host and reads back.
|
|
EXPECT_EQ(SetInt(udp, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_DONTFRAG, 1), ORBIS_OK);
|
|
EXPECT_EQ(GetInt(udp, ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_DONTFRAG), 1);
|
|
// Policy: 0-15 or ORBIS_NET_SOCK_POLICY_NA (-1).
|
|
EXPECT_EQ(SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_POLICY, ORBIS_NET_SOCK_POLICY_NA),
|
|
ORBIS_OK);
|
|
EXPECT_EQ(SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_POLICY, 16), ORBIS_NET_ERROR_EINVAL);
|
|
|
|
// UDP_SND_ON_SUSPEND: UDP only, and not together with connect.
|
|
OrbisNetSockaddrIn dest = Loopback(9);
|
|
char payload[] = "bye";
|
|
OrbisNetUdpSndOnSuspend suspend{1, Guest(&dest), sizeof(dest), payload, 3};
|
|
EXPECT_EQ(sceNetSetsockopt(tcp, ORBIS_NET_IPPROTO_UDP, ORBIS_NET_UDP_SND_ON_SUSPEND, &suspend,
|
|
sizeof(suspend)),
|
|
ORBIS_NET_ERROR_EPROCUNAVAIL);
|
|
suspend.datalen = 513;
|
|
EXPECT_EQ(sceNetSetsockopt(udp, ORBIS_NET_IPPROTO_UDP, ORBIS_NET_UDP_SND_ON_SUSPEND, &suspend,
|
|
sizeof(suspend)),
|
|
ORBIS_NET_ERROR_EINVAL);
|
|
suspend.datalen = 3;
|
|
ASSERT_EQ(sceNetSetsockopt(udp, ORBIS_NET_IPPROTO_UDP, ORBIS_NET_UDP_SND_ON_SUSPEND, &suspend,
|
|
sizeof(suspend)),
|
|
ORBIS_OK);
|
|
OrbisNetUdpSndOnSuspend back_suspend{};
|
|
len = sizeof(back_suspend);
|
|
EXPECT_EQ(sceNetGetsockopt(udp, ORBIS_NET_IPPROTO_UDP, ORBIS_NET_UDP_SND_ON_SUSPEND,
|
|
&back_suspend, &len),
|
|
ORBIS_OK);
|
|
EXPECT_EQ(back_suspend.onoff, 1);
|
|
EXPECT_EQ(sceNetConnect(udp, Guest(&dest), sizeof(dest)), ORBIS_NET_ERROR_EPROCUNAVAIL);
|
|
suspend.onoff = 0;
|
|
sceNetSetsockopt(udp, ORBIS_NET_IPPROTO_UDP, ORBIS_NET_UDP_SND_ON_SUSPEND, &suspend,
|
|
sizeof(suspend));
|
|
ASSERT_EQ(sceNetConnect(udp, Guest(&dest), sizeof(dest)), ORBIS_OK);
|
|
suspend.onoff = 1;
|
|
EXPECT_EQ(sceNetSetsockopt(udp, ORBIS_NET_IPPROTO_UDP, ORBIS_NET_UDP_SND_ON_SUSPEND, &suspend,
|
|
sizeof(suspend)),
|
|
ORBIS_NET_ERROR_EPROCUNAVAIL); // after connect
|
|
sceNetSocketClose(tcp);
|
|
sceNetSocketClose(udp);
|
|
}
|
|
|
|
TEST_F(NetLib, ConnectAndAcceptTimeouts) {
|
|
// A listener that never accepts, with a backlog of one, so the second connect hangs.
|
|
// Instead, an address nothing answers (TEST-NET-1): the SYN goes unanswered.
|
|
const OrbisNetId s = sceNetSocket("tcp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
EXPECT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_CONNECTTIMEO, 200000), ORBIS_OK);
|
|
OrbisNetSockaddrIn nowhere = Loopback(9);
|
|
nowhere.sin_addr = sceNetHtonl(0xC0000201); // 192.0.2.1
|
|
auto start = std::chrono::steady_clock::now();
|
|
const s32 r = sceNetConnect(s, Guest(&nowhere), sizeof(nowhere));
|
|
EXPECT_LT(r, 0);
|
|
// Where the host has no route (or something answers for it), the connect fails at once;
|
|
// only an unanswered SYN shows the timeout.
|
|
if (Millis(start) >= 100.0) {
|
|
EXPECT_EQ(r, ORBIS_NET_ERROR_EWOULDBLOCK);
|
|
EXPECT_LT(Millis(start), 3000.0);
|
|
}
|
|
sceNetSocketClose(s);
|
|
|
|
OrbisNetId listener = 0;
|
|
Listen(&listener);
|
|
EXPECT_EQ(SetInt(listener, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_ACCEPTTIMEO, 150000), ORBIS_OK);
|
|
start = std::chrono::steady_clock::now();
|
|
EXPECT_EQ(sceNetAccept(listener, nullptr, nullptr), ORBIS_NET_ERROR_EWOULDBLOCK);
|
|
EXPECT_GE(Millis(start), 140.0);
|
|
// Connecting a listening socket is EOPNOTSUPP.
|
|
auto self = Loopback(1);
|
|
EXPECT_EQ(sceNetConnect(listener, Guest(&self), sizeof(self)), ORBIS_NET_ERROR_EOPNOTSUPP);
|
|
// TCP to the broadcast address: EACCES.
|
|
const OrbisNetId t = sceNetSocket("tcp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
auto broadcast = Loopback(80);
|
|
broadcast.sin_addr = ORBIS_NET_INADDR_BROADCAST;
|
|
EXPECT_EQ(sceNetConnect(t, Guest(&broadcast), sizeof(broadcast)), ORBIS_NET_ERROR_EACCES);
|
|
sceNetSocketClose(t);
|
|
sceNetSocketClose(listener);
|
|
}
|
|
|
|
TEST_F(NetLib, WaitallPeeklenAndConnectedSendto) {
|
|
OrbisNetId listener = 0;
|
|
const u16 port = Listen(&listener);
|
|
const OrbisNetId client = sceNetSocket("c", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
auto to = Loopback(port);
|
|
ASSERT_EQ(sceNetConnect(client, Guest(&to), sizeof(to)), ORBIS_OK);
|
|
const OrbisNetId server = sceNetAccept(listener, nullptr, nullptr);
|
|
ASSERT_GT(server, 0);
|
|
|
|
// MSG_WAITALL waits for the whole length even when it arrives in pieces.
|
|
std::thread writer([&] {
|
|
for (const char* piece : {"ab", "cd", "ef"}) {
|
|
std::this_thread::sleep_for(30ms);
|
|
sceNetSend(client, piece, 2, 0);
|
|
}
|
|
});
|
|
char buf[8]{};
|
|
EXPECT_EQ(sceNetRecv(server, buf, 6, ORBIS_NET_MSG_WAITALL), 6);
|
|
writer.join();
|
|
EXPECT_STREQ(buf, "abcdef");
|
|
// ... and returns what it has at FIN.
|
|
sceNetSend(client, "xyz", 3, 0);
|
|
sceNetShutdown(client, ORBIS_NET_SHUT_WR);
|
|
std::memset(buf, 0, sizeof(buf));
|
|
EXPECT_EQ(sceNetRecv(server, buf, 8, ORBIS_NET_MSG_WAITALL), 3);
|
|
|
|
// MSG_PEEKLEN: the size waiting, with no buffer.
|
|
const OrbisNetId udp = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
auto any = Loopback(0);
|
|
sceNetBind(udp, Guest(&any), sizeof(any));
|
|
OrbisNetSockaddrIn self{};
|
|
u32 len = sizeof(self);
|
|
sceNetGetsockname(udp, Guest(&self), &len);
|
|
sceNetSendto(udp, "12345", 5, 0, Guest(&self), sizeof(self));
|
|
std::this_thread::sleep_for(20ms);
|
|
EXPECT_EQ(sceNetRecv(udp, nullptr, 1500, ORBIS_NET_MSG_PEEKLEN), 5);
|
|
EXPECT_EQ(sceNetRecv(udp, buf, sizeof(buf), 0), 5); // still there
|
|
|
|
// A destination for a connected socket: EISCONN.
|
|
ASSERT_EQ(sceNetConnect(udp, Guest(&self), sizeof(self)), ORBIS_OK);
|
|
EXPECT_EQ(sceNetSendto(udp, "x", 1, 0, Guest(&self), sizeof(self)), ORBIS_NET_ERROR_EISCONN);
|
|
EXPECT_EQ(sceNetSend(udp, "x", 1, 0), 1);
|
|
sceNetSocketClose(udp);
|
|
sceNetSocketClose(server);
|
|
sceNetSocketClose(client);
|
|
sceNetSocketClose(listener);
|
|
}
|
|
|
|
TEST_F(NetLib, SendPreservationAgain) {
|
|
// An aborted send that already moved data returns the partial count; with
|
|
// SND_PRESERVATION_AGAIN the send after it fails with EINTR once.
|
|
OrbisNetId listener = 0;
|
|
const u16 port = Listen(&listener);
|
|
const OrbisNetId client = sceNetSocket("c", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
auto to = Loopback(port);
|
|
ASSERT_EQ(sceNetConnect(client, Guest(&to), sizeof(to)), ORBIS_OK);
|
|
const OrbisNetId server = sceNetAccept(listener, nullptr, nullptr); // never reads
|
|
std::vector<u8> big(64 << 20);
|
|
s32 sent = 0;
|
|
std::thread sender([&] { sent = sceNetSend(client, big.data(), big.size(), 0); });
|
|
std::this_thread::sleep_for(300ms); // the buffers fill, the send blocks
|
|
EXPECT_EQ(sceNetSocketAbort(client, 0x4 /* SND_PRESERVATION_AGAIN */), ORBIS_OK);
|
|
sender.join();
|
|
EXPECT_GT(sent, 0);
|
|
EXPECT_LT(sent, static_cast<s32>(big.size()));
|
|
EXPECT_EQ(sceNetSend(client, "x", 1, ORBIS_NET_MSG_DONTWAIT), ORBIS_NET_ERROR_EINTR);
|
|
EXPECT_NE(sceNetSend(client, "x", 1, ORBIS_NET_MSG_DONTWAIT), ORBIS_NET_ERROR_EINTR); // once
|
|
sceNetSocketClose(server);
|
|
sceNetSocketClose(client);
|
|
sceNetSocketClose(listener);
|
|
}
|
|
|
|
TEST_F(NetLib, ReservedPortsCannotBeBound) {
|
|
const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
for (const u16 port : {80, 5353, 8545, 9300, 45000}) {
|
|
auto addr = Loopback(port);
|
|
EXPECT_EQ(sceNetBind(s, Guest(&addr), sizeof(addr)), ORBIS_NET_ERROR_EACCES) << port;
|
|
}
|
|
auto ephemeral = Loopback(0);
|
|
EXPECT_EQ(sceNetBind(s, Guest(&ephemeral), sizeof(ephemeral)), ORBIS_OK);
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLib, PS4BufferSizesAndDatagramLimit) {
|
|
const OrbisNetId tcp = sceNetSocket("tcp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
EXPECT_EQ(GetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDBUF), 32768);
|
|
EXPECT_EQ(GetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF), 65536);
|
|
EXPECT_EQ(SetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF, 20000), ORBIS_OK);
|
|
EXPECT_EQ(GetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF), 20000); // not the host's
|
|
EXPECT_EQ(SetInt(tcp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDBUF, 0), ORBIS_NET_ERROR_EINVAL);
|
|
sceNetSocketClose(tcp);
|
|
|
|
const OrbisNetId udp = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
EXPECT_EQ(GetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDBUF), 9216);
|
|
EXPECT_EQ(GetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF), 40960);
|
|
auto any = Loopback(0);
|
|
sceNetBind(udp, Guest(&any), sizeof(any));
|
|
OrbisNetSockaddrIn self{};
|
|
u32 len = sizeof(self);
|
|
sceNetGetsockname(udp, Guest(&self), &len);
|
|
|
|
// A datagram larger than the send buffer fails with EMSGSIZE, as on FreeBSD.
|
|
std::vector<u8> big(9217);
|
|
EXPECT_EQ(sceNetSendto(udp, big.data(), 9216, 0, Guest(&self), sizeof(self)), 9216);
|
|
EXPECT_EQ(sceNetSendto(udp, big.data(), big.size(), 0, Guest(&self), sizeof(self)),
|
|
ORBIS_NET_ERROR_EMSGSIZE);
|
|
EXPECT_EQ(SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDBUF, 20000), ORBIS_OK);
|
|
EXPECT_EQ(sceNetSendto(udp, big.data(), big.size(), 0, Guest(&self), sizeof(self)),
|
|
static_cast<s32>(big.size()));
|
|
sceNetSocketClose(udp);
|
|
}
|
|
|
|
TEST_F(NetLib, DebugNamePolicyAndPriority) {
|
|
const OrbisNetId s = sceNetSocket("echo_server", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
// SO_NAME cannot be read back; sceNetGetSockInfo shows it.
|
|
OrbisNetSockInfo info{};
|
|
ASSERT_EQ(sceNetGetSockInfo(s, &info, 1, 0), 1);
|
|
EXPECT_STREQ(info.name, "echo_server");
|
|
|
|
// At most 31 characters: a longer name is ENAMETOOLONG and leaves the old one.
|
|
const char long_name[] = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
EXPECT_EQ(
|
|
sceNetSetsockopt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NAME, long_name, sizeof(long_name)),
|
|
ORBIS_NET_ERROR_ENAMETOOLONG);
|
|
const char name31[] = "0123456789abcdefghijklmnopqrstu";
|
|
ASSERT_EQ(sceNetSetsockopt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NAME, name31, sizeof(name31)),
|
|
ORBIS_OK);
|
|
sceNetGetSockInfo(s, &info, 1, 0);
|
|
EXPECT_STREQ(info.name, name31);
|
|
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_POLICY), 0);
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_PRIORITY), 16);
|
|
SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_POLICY, 3);
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_POLICY), 3);
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLib, AcceptedSocketsInheritTheListenersOptions) {
|
|
OrbisNetId listener;
|
|
const u16 port = Listen(&listener);
|
|
ASSERT_EQ(SetInt(listener, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO, 250000), ORBIS_OK);
|
|
ASSERT_EQ(SetInt(listener, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDTIMEO, 125000), ORBIS_OK);
|
|
ASSERT_EQ(SetInt(listener, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, 1), ORBIS_OK);
|
|
const OrbisNetId client = sceNetSocket("client", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
auto to = Loopback(port);
|
|
ASSERT_EQ(sceNetConnect(client, Guest(&to), sizeof(to)), ORBIS_OK);
|
|
const OrbisNetId accepted = sceNetAccept(listener, nullptr, nullptr);
|
|
ASSERT_GT(accepted, 0);
|
|
// As FreeBSD's sonewconn: timeouts and socket-level flags come from the listener.
|
|
EXPECT_EQ(GetInt(accepted, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO), 250000);
|
|
EXPECT_EQ(GetInt(accepted, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDTIMEO), 125000);
|
|
EXPECT_EQ(GetInt(accepted, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR), 1);
|
|
sceNetSocketClose(accepted);
|
|
sceNetSocketClose(client);
|
|
sceNetSocketClose(listener);
|
|
}
|
|
|
|
TEST_F(NetLib, ReusePortIsItsOwnOption) {
|
|
const OrbisNetId a = sceNetSocket("a", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
const OrbisNetId b = sceNetSocket("b", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
ASSERT_EQ(SetInt(a, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, 1), ORBIS_OK);
|
|
ASSERT_EQ(SetInt(b, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, 1), ORBIS_OK);
|
|
EXPECT_EQ(GetInt(a, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT), 1);
|
|
EXPECT_EQ(GetInt(a, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR), 0); // untouched
|
|
// Both bind the same port (an explicit one: the PS4 reserves 40000 and up, where hosts
|
|
// hand out ephemeral ports).
|
|
auto bound = Loopback(static_cast<u16>(20000 + std::random_device{}() % 10000));
|
|
ASSERT_EQ(sceNetBind(a, Guest(&bound), sizeof(bound)), ORBIS_OK);
|
|
EXPECT_EQ(sceNetBind(b, Guest(&bound), sizeof(bound)), ORBIS_OK);
|
|
// Without it, the port is taken.
|
|
const OrbisNetId c = sceNetSocket("c", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
EXPECT_EQ(sceNetBind(c, Guest(&bound), sizeof(bound)), ORBIS_NET_ERROR_EADDRINUSE);
|
|
sceNetSocketClose(a);
|
|
sceNetSocketClose(b);
|
|
sceNetSocketClose(c);
|
|
}
|
|
|
|
TEST_F(NetLib, LingerOnCloseFollowsTheGuestMode) {
|
|
// A connected pair whose receiver never reads, and a sender with unsent data queued.
|
|
const auto unsent_pair = [&](OrbisNetId* listener, OrbisNetId* sender, OrbisNetId* receiver) {
|
|
const u16 port = Listen(listener);
|
|
// A small, fixed receive buffer (no autotuning), so the data really stays unsent.
|
|
SetInt(*listener, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF, 4096);
|
|
*sender = sceNetSocket("sender", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
SetInt(*sender, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDBUF, 16 * 1024);
|
|
auto to = Loopback(port);
|
|
ASSERT_EQ(sceNetConnect(*sender, Guest(&to), sizeof(to)), ORBIS_OK);
|
|
*receiver = sceNetAccept(*listener, nullptr, nullptr);
|
|
ASSERT_GT(*receiver, 0);
|
|
SetInt(*sender, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, 1);
|
|
// Fill until the socket stays full across a pause. One EWOULDBLOCK is not enough:
|
|
// macOS keeps growing the receive buffer and drains the send queue right after.
|
|
std::vector<char> chunk(64 * 1024, 'x');
|
|
bool stuck = false;
|
|
for (int round = 0; round < 200 && !stuck; ++round) {
|
|
bool progressed = false;
|
|
for (int i = 0; i < 4096; ++i) {
|
|
if (sceNetSend(*sender, chunk.data(), chunk.size(), 0) < 0) {
|
|
break;
|
|
}
|
|
progressed = true;
|
|
}
|
|
ASSERT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EWOULDBLOCK);
|
|
stuck = !progressed && round > 0;
|
|
std::this_thread::sleep_for(20ms);
|
|
}
|
|
ASSERT_TRUE(stuck) << "the receiver kept taking data";
|
|
};
|
|
const OrbisNetLinger linger{1, 1};
|
|
|
|
// Non-blocking guest socket: close returns at once and the data goes out in the background.
|
|
OrbisNetId listener, sender, receiver;
|
|
unsent_pair(&listener, &sender, &receiver);
|
|
ASSERT_EQ(sceNetSetsockopt(sender, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_LINGER, &linger,
|
|
sizeof(linger)),
|
|
ORBIS_OK);
|
|
auto start = std::chrono::steady_clock::now();
|
|
EXPECT_EQ(sceNetSocketClose(sender), ORBIS_OK);
|
|
EXPECT_LT(std::chrono::steady_clock::now() - start, 500ms);
|
|
sceNetSocketClose(receiver);
|
|
sceNetSocketClose(listener);
|
|
|
|
// Blocking guest socket: close waits up to the linger time.
|
|
unsent_pair(&listener, &sender, &receiver);
|
|
SetInt(sender, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, 0);
|
|
ASSERT_EQ(sceNetSetsockopt(sender, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_LINGER, &linger,
|
|
sizeof(linger)),
|
|
ORBIS_OK);
|
|
start = std::chrono::steady_clock::now();
|
|
EXPECT_EQ(sceNetSocketClose(sender), ORBIS_OK);
|
|
const auto waited = std::chrono::steady_clock::now() - start;
|
|
EXPECT_GE(waited, 500ms);
|
|
EXPECT_LT(waited, 3s);
|
|
sceNetSocketClose(receiver);
|
|
sceNetSocketClose(listener);
|
|
}
|
|
|
|
TEST_F(NetLib, PoolIdsAndRetailOnlyFeatures) {
|
|
// Pool errors are returned without touching sce_net_errno.
|
|
*sceNetErrnoLoc() = 0;
|
|
EXPECT_EQ(sceNetPoolCreate("small", 4095, 0), ORBIS_NET_ERROR_EINVAL); // at least 4 KiB
|
|
EXPECT_EQ(sceNetPoolCreate("flags", 4096, 1), ORBIS_NET_ERROR_EINVAL);
|
|
EXPECT_EQ(sceNetPoolCreate("0123456789abcdefghijklmnopqrstuvwxyz", 4096, 0),
|
|
ORBIS_NET_ERROR_ENAMETOOLONG);
|
|
EXPECT_EQ(*sceNetErrnoLoc(), 0);
|
|
|
|
std::vector<s32> pools;
|
|
for (int i = 0; i < 32; ++i) {
|
|
pools.push_back(sceNetPoolCreate("pool", 4096, 0));
|
|
}
|
|
EXPECT_EQ(pools.front(), 3000);
|
|
EXPECT_EQ(pools[30], 3030);
|
|
EXPECT_EQ(pools.back(), ORBIS_NET_ERROR_ENFILE); // at most 31 pools
|
|
pools.pop_back();
|
|
EXPECT_EQ(sceNetPoolDestroy(3005), ORBIS_OK);
|
|
EXPECT_EQ(sceNetPoolDestroy(3005), ORBIS_NET_ERROR_EBADF);
|
|
EXPECT_EQ(sceNetPoolCreate("again", 4096, 0), 3005); // lowest free id
|
|
|
|
// A pool with a resolver created from it is in use until the resolver is destroyed.
|
|
const OrbisNetId rid = sceNetResolverCreate("user", 3005, 0);
|
|
ASSERT_GT(rid, 0);
|
|
EXPECT_EQ(sceNetPoolDestroy(3005), ORBIS_NET_ERROR_ENOTEMPTY);
|
|
EXPECT_EQ(sceNetResolverDestroy(rid), ORBIS_OK);
|
|
for (const s32 id : pools) {
|
|
EXPECT_EQ(sceNetPoolDestroy(id), ORBIS_OK);
|
|
}
|
|
|
|
// Network emulation is not available on retail units.
|
|
EXPECT_EQ(sceNetEmulationSet(), ORBIS_NET_ERROR_EOPNOTSUPP);
|
|
EXPECT_EQ(sceNetEmulationGet(), ORBIS_NET_ERROR_EOPNOTSUPP);
|
|
}
|
|
|
|
TEST_F(NetLib, RecvTimeoutAndAbort) {
|
|
const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
auto any = Loopback(0);
|
|
sceNetBind(s, Guest(&any), sizeof(any));
|
|
char buf[4];
|
|
|
|
SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO, 200000); // microseconds
|
|
auto start = std::chrono::steady_clock::now();
|
|
EXPECT_EQ(sceNetRecv(s, buf, sizeof(buf), 0), ORBIS_NET_ERROR_EWOULDBLOCK);
|
|
EXPECT_GE(Millis(start), 190.0);
|
|
EXPECT_LT(Millis(start), 2000.0);
|
|
|
|
SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO, 0);
|
|
std::thread aborter([&] {
|
|
std::this_thread::sleep_for(100ms);
|
|
EXPECT_EQ(sceNetSocketAbort(s, 1 /* RCV_PRESERVATION */), ORBIS_OK);
|
|
});
|
|
start = std::chrono::steady_clock::now();
|
|
EXPECT_EQ(sceNetRecv(s, buf, sizeof(buf), 0), ORBIS_NET_ERROR_EINTR);
|
|
EXPECT_LT(Millis(start), 2000.0);
|
|
aborter.join();
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLib, EpollThroughExports) {
|
|
const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
auto any = Loopback(0);
|
|
sceNetBind(s, Guest(&any), sizeof(any));
|
|
OrbisNetSockaddrIn self{};
|
|
u32 len = sizeof(self);
|
|
sceNetGetsockname(s, Guest(&self), &len);
|
|
|
|
const OrbisNetId ep = sceNetEpollCreate("ep", 0);
|
|
ASSERT_GT(ep, 0);
|
|
OrbisNetEpollEvent ev{};
|
|
ev.events = ORBIS_NET_EPOLLIN;
|
|
ev.data.data_u64 = 0xC0FFEE;
|
|
ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, s, &ev), ORBIS_OK);
|
|
EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, s, &ev), ORBIS_NET_ERROR_EEXIST);
|
|
EXPECT_EQ(sceNetEpollControl(ep, 9, s, &ev), ORBIS_NET_ERROR_EINVAL);
|
|
EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_MOD, s, nullptr), ORBIS_NET_ERROR_EINVAL);
|
|
|
|
OrbisNetEpollEvent out[4]{};
|
|
EXPECT_EQ(sceNetEpollWait(ep, out, 4, 10000), 0); // 10 ms, nothing yet
|
|
sceNetSendto(s, "x", 1, 0, Guest(&self), sizeof(self));
|
|
ASSERT_EQ(sceNetEpollWait(ep, out, 4, 1000000), 1);
|
|
EXPECT_EQ(out[0].events, ORBIS_NET_EPOLLIN);
|
|
EXPECT_EQ(out[0].ident, static_cast<u64>(s));
|
|
EXPECT_EQ(out[0].data.data_u64, 0xC0FFEEu);
|
|
EXPECT_EQ(sceNetEpollWait(ep, out, 0, 0), ORBIS_NET_ERROR_EINVAL);
|
|
|
|
std::thread aborter([&] {
|
|
std::this_thread::sleep_for(100ms);
|
|
sceNetEpollAbort(ep, 1 /* PRESERVATION */);
|
|
});
|
|
char buf[4];
|
|
sceNetRecv(s, buf, sizeof(buf), 0); // drain, so the wait below would block forever
|
|
EXPECT_EQ(sceNetEpollWait(ep, out, 4, -1), ORBIS_NET_ERROR_EINTR);
|
|
aborter.join();
|
|
|
|
EXPECT_EQ(sceNetEpollDestroy(ep), ORBIS_OK);
|
|
EXPECT_EQ(sceNetEpollDestroy(ep), ORBIS_NET_ERROR_EBADF);
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLib, EpollErrorCodesMatchThePS4) {
|
|
EXPECT_EQ(sceNetEpollCreate("flags", 1), ORBIS_NET_ERROR_EINVAL); // no flags defined
|
|
|
|
const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
const OrbisNetId ep = sceNetEpollCreate("ep", 0);
|
|
ASSERT_GT(ep, 0);
|
|
OrbisNetEpollEvent ev{};
|
|
ev.events = ORBIS_NET_EPOLLIN;
|
|
|
|
// DEL of an unregistered id: EBADF, where Linux would say ENOENT.
|
|
EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_DEL, s, nullptr), ORBIS_NET_ERROR_EBADF);
|
|
EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EBADF);
|
|
// MOD of an unregistered id registers it
|
|
EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_MOD, s, &ev), ORBIS_OK);
|
|
EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_DEL, s, nullptr), ORBIS_OK);
|
|
// ADD/MOD need event bits.
|
|
OrbisNetEpollEvent none{};
|
|
EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, s, &none), ORBIS_NET_ERROR_EINVAL);
|
|
|
|
// Delete takes no event.
|
|
ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, s, &ev), ORBIS_OK);
|
|
EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_DEL, s, &ev), ORBIS_NET_ERROR_EINVAL);
|
|
EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_DEL, s, nullptr), ORBIS_OK);
|
|
|
|
sceNetEpollDestroy(ep);
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLib, EpollIdKindsNamesAndAbortFlags) {
|
|
const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
const OrbisNetId ep = sceNetEpollCreate("ep", 0);
|
|
const OrbisNetId other = sceNetEpollCreate("other", 0);
|
|
ASSERT_GT(ep, 0);
|
|
ASSERT_GT(other, 0);
|
|
EXPECT_EQ(sceNetEpollCreate("0123456789abcdefghijklmnopqrstuvwxyz", 0),
|
|
ORBIS_NET_ERROR_ENAMETOOLONG);
|
|
|
|
OrbisNetEpollEvent ev{};
|
|
ev.events = ORBIS_NET_EPOLLIN;
|
|
// Another epoll cannot be waited on; a socket is not an epoll; a free id is nothing.
|
|
EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, other, &ev), ORBIS_NET_ERROR_EPERM);
|
|
EXPECT_EQ(sceNetEpollControl(s, ORBIS_NET_EPOLL_CTL_ADD, s, &ev), ORBIS_NET_ERROR_EBADF);
|
|
EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, 12345, &ev), ORBIS_NET_ERROR_EBADF);
|
|
EXPECT_EQ(sceNetEpollDestroy(s), ORBIS_NET_ERROR_EPERM);
|
|
EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EPERM);
|
|
OrbisNetEpollEvent out[2]{};
|
|
EXPECT_EQ(sceNetEpollWait(s, out, 2, 0), ORBIS_NET_ERROR_EBADF);
|
|
EXPECT_EQ(sceNetEpollAbort(s, 0), ORBIS_NET_ERROR_EBADF);
|
|
|
|
// Only ORBIS_NET_EPOLL_ABORT_FLAG_PRESERVATION; with it, the next wait fails even though an
|
|
// event is there.
|
|
EXPECT_EQ(sceNetEpollAbort(ep, 2), ORBIS_NET_ERROR_EINVAL);
|
|
ev.events = ORBIS_NET_EPOLLIN | ORBIS_NET_EPOLLOUT;
|
|
ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, s, &ev), ORBIS_OK);
|
|
auto any = Loopback(0);
|
|
sceNetBind(s, Guest(&any), sizeof(any));
|
|
OrbisNetSockaddrIn self{};
|
|
u32 len = sizeof(self);
|
|
sceNetGetsockname(s, Guest(&self), &len);
|
|
sceNetSendto(s, "x", 1, 0, Guest(&self), sizeof(self));
|
|
std::this_thread::sleep_for(20ms);
|
|
EXPECT_EQ(sceNetEpollAbort(ep, 1), ORBIS_OK);
|
|
EXPECT_EQ(sceNetEpollWait(ep, out, 2, 0), ORBIS_NET_ERROR_EINTR);
|
|
// A UDP socket reports OUT too, it is always writable.
|
|
ASSERT_EQ(sceNetEpollWait(ep, out, 2, 1'000'000), 1);
|
|
EXPECT_EQ(out[0].events, ORBIS_NET_EPOLLIN | ORBIS_NET_EPOLLOUT);
|
|
|
|
sceNetEpollDestroy(other);
|
|
sceNetEpollDestroy(ep);
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLib, EpollReportsAbortedSocketsAsHangup) {
|
|
const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
auto any = Loopback(0);
|
|
sceNetBind(s, Guest(&any), sizeof(any));
|
|
const OrbisNetId ep = sceNetEpollCreate("ep", 0);
|
|
OrbisNetEpollEvent ev{};
|
|
ev.events = ORBIS_NET_EPOLLIN;
|
|
ev.data.data_u64 = 7;
|
|
ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, s, &ev), ORBIS_OK);
|
|
|
|
// Nobody waiting anywhere: nothing to abort.
|
|
EXPECT_EQ(sceNetSocketAbort(s, 0), ORBIS_NET_ERROR_ENOTBLK);
|
|
|
|
std::atomic<s32> abort_result{1};
|
|
std::thread aborter([&] {
|
|
std::this_thread::sleep_for(100ms);
|
|
abort_result = sceNetSocketAbort(s, 0);
|
|
});
|
|
OrbisNetEpollEvent out[2]{};
|
|
ASSERT_EQ(sceNetEpollWait(ep, out, 2, 5'000'000), 1);
|
|
aborter.join();
|
|
EXPECT_EQ(abort_result.load(), ORBIS_OK); // the epoll wait counts as waiting
|
|
EXPECT_EQ(out[0].events, ORBIS_NET_EPOLLHUP);
|
|
EXPECT_EQ(out[0].ident, static_cast<u64>(s));
|
|
EXPECT_EQ(out[0].data.data_u64, 7u);
|
|
// Reported once; the registration keeps working.
|
|
EXPECT_EQ(sceNetEpollWait(ep, out, 2, 10'000), 0);
|
|
|
|
sceNetEpollDestroy(ep);
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLib, ResolverNeedsEpollInToBeReported) {
|
|
ScopedPool pool;
|
|
const OrbisNetId rid = sceNetResolverCreate("resolver", pool.id, 0);
|
|
const OrbisNetId ep = sceNetEpollCreate("ep", 0);
|
|
OrbisNetEpollEvent ev{};
|
|
ev.events = ORBIS_NET_EPOLLOUT; // ignored for a resolver: nothing is waited for
|
|
ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, rid, &ev), ORBIS_OK);
|
|
OrbisNetInAddr addr{};
|
|
ASSERT_EQ(sceNetResolverStartNtoa(rid, "127.0.0.1", &addr, 0, 0, ORBIS_NET_RESOLVER_ASYNC),
|
|
ORBIS_OK);
|
|
OrbisNetEpollEvent out[2]{};
|
|
EXPECT_EQ(sceNetEpollWait(ep, out, 2, 200'000), 0);
|
|
// Asking for ORBIS_NET_EPOLLIN afterwards reports the finished lookup.
|
|
ev.events = ORBIS_NET_EPOLLIN;
|
|
ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_MOD, rid, &ev), ORBIS_OK);
|
|
ASSERT_EQ(sceNetEpollWait(ep, out, 2, 5'000'000), 1);
|
|
EXPECT_EQ(out[0].events, ORBIS_NET_EPOLLIN | ORBIS_NET_EPOLLDESCID);
|
|
EXPECT_EQ(sceNetEpollWait(ep, out, 2, 10'000), 0); // once
|
|
sceNetEpollDestroy(ep);
|
|
sceNetResolverDestroy(rid);
|
|
}
|
|
|
|
TEST_F(NetLib, SendmsgGathersAndRecvmsgScatters) {
|
|
const OrbisNetId s = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
auto any = Loopback(0);
|
|
sceNetBind(s, Guest(&any), sizeof(any));
|
|
OrbisNetSockaddrIn self{};
|
|
u32 len = sizeof(self);
|
|
sceNetGetsockname(s, Guest(&self), &len);
|
|
|
|
char a[] = "hello ", b[] = "world";
|
|
OrbisNetIovec out_iov[] = {{a, 6}, {b, 5}};
|
|
OrbisNetMsghdr out{};
|
|
out.msg_name = &self;
|
|
out.msg_namelen = sizeof(self);
|
|
out.msg_iov = out_iov;
|
|
out.msg_iovlen = 2;
|
|
ASSERT_EQ(sceNetSendmsg(s, &out, 0), 11); // one datagram
|
|
|
|
char x[4]{}, y[16]{};
|
|
OrbisNetIovec in_iov[] = {{x, 4}, {y, sizeof(y)}};
|
|
OrbisNetSockaddrIn from{};
|
|
OrbisNetMsghdr in{};
|
|
in.msg_name = &from;
|
|
in.msg_namelen = sizeof(from);
|
|
in.msg_iov = in_iov;
|
|
in.msg_iovlen = 2;
|
|
in.msg_controllen = 99;
|
|
ASSERT_EQ(sceNetRecvmsg(s, &in, 0), 11);
|
|
EXPECT_EQ(std::string(x, 4), "hell");
|
|
EXPECT_STREQ(y, "o world");
|
|
EXPECT_EQ(in.msg_namelen, sizeof(from));
|
|
EXPECT_EQ(from.sin_port, self.sin_port);
|
|
EXPECT_EQ(in.msg_controllen, 99u); // "not used": left alone
|
|
|
|
in.msg_iovlen = -1;
|
|
EXPECT_EQ(sceNetRecvmsg(s, &in, 0), ORBIS_NET_ERROR_EINVAL);
|
|
in.msg_iovlen = 1025; // at most 1024 elements
|
|
EXPECT_EQ(sceNetRecvmsg(s, &in, 0), ORBIS_NET_ERROR_EMSGSIZE);
|
|
in.msg_iovlen = 2;
|
|
EXPECT_EQ(sceNetRecvmsg(s, &in, ORBIS_NET_MSG_PEEKLEN), ORBIS_NET_ERROR_EINVAL);
|
|
EXPECT_EQ(sceNetSendmsg(s, nullptr, 0), ORBIS_NET_ERROR_EFAULT);
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLib, UnixDatagramSockets) {
|
|
#ifdef _WIN32
|
|
GTEST_SKIP() << "Windows has no AF_UNIX datagram sockets";
|
|
#else
|
|
const std::string path = "/tmp/shadps4_net_test_" + std::to_string(::getpid());
|
|
::unlink(path.c_str());
|
|
OrbisNetSockaddrUn addr{};
|
|
addr.sun_len = sizeof(addr);
|
|
addr.sun_family = ORBIS_NET_AF_UNIX;
|
|
std::strncpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path) - 1);
|
|
auto* guest = reinterpret_cast<OrbisNetSockaddr*>(&addr);
|
|
|
|
const OrbisNetId server = sceNetSocket("unix", ORBIS_NET_AF_UNIX, ORBIS_NET_SOCK_DGRAM, 0);
|
|
ASSERT_GT(server, 0);
|
|
ASSERT_EQ(sceNetBind(server, guest, sizeof(addr)), ORBIS_OK);
|
|
const OrbisNetId client = sceNetSocket("unix", ORBIS_NET_AF_UNIX, ORBIS_NET_SOCK_DGRAM, 0);
|
|
EXPECT_EQ(sceNetSendto(client, "hi", 2, 0, guest, sizeof(addr)), 2);
|
|
char buf[8]{};
|
|
EXPECT_EQ(sceNetRecv(server, buf, sizeof(buf), 0), 2);
|
|
EXPECT_STREQ(buf, "hi");
|
|
|
|
OrbisNetSockaddrUn name{};
|
|
u32 len = sizeof(name);
|
|
ASSERT_EQ(sceNetGetsockname(server, reinterpret_cast<OrbisNetSockaddr*>(&name), &len),
|
|
ORBIS_OK);
|
|
EXPECT_EQ(name.sun_family, ORBIS_NET_AF_UNIX);
|
|
EXPECT_EQ(std::string(name.sun_path), path);
|
|
EXPECT_EQ(sceNetSocket("unix", ORBIS_NET_AF_UNIX, ORBIS_NET_SOCK_DGRAM_P2P, 0),
|
|
ORBIS_NET_ERROR_EPROTONOSUPPORT);
|
|
sceNetSocketClose(client);
|
|
sceNetSocketClose(server);
|
|
::unlink(path.c_str());
|
|
#endif
|
|
}
|
|
|
|
TEST_F(NetLib, SockInfoDescribesSockets) {
|
|
// No buffer and s < 0: the number of sockets.
|
|
EXPECT_EQ(sceNetGetSockInfo(-1, nullptr, 0, 0), 0);
|
|
const OrbisNetId udp = sceNetSocket("sockinfo_udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
auto any = Loopback(0);
|
|
sceNetBind(udp, Guest(&any), sizeof(any));
|
|
SetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, 1);
|
|
const OrbisNetId tcp =
|
|
sceNetSocket("sockinfo_tcp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
EXPECT_EQ(sceNetGetSockInfo(-1, nullptr, 0, 0), 2);
|
|
|
|
OrbisNetSockInfo info{};
|
|
ASSERT_EQ(sceNetGetSockInfo(udp, &info, 1, 0), 1);
|
|
EXPECT_STREQ(info.name, "sockinfo_udp");
|
|
EXPECT_EQ(info.s, udp);
|
|
EXPECT_EQ(info.socket_type, ORBIS_NET_SOCK_DGRAM);
|
|
EXPECT_EQ(info.local_adr.inaddr_addr, sceNetHtonl(0x7F000001));
|
|
EXPECT_NE(info.local_port, 0);
|
|
EXPECT_EQ(info.flags, ORBIS_NET_SOCKINFO_F_SELF | ORBIS_NET_SOCKINFO_F_NONBLOCK);
|
|
EXPECT_EQ(info.recv_buffer_size, 40960);
|
|
EXPECT_EQ(info.state, ORBIS_NET_SOCKINFO_STATE_OPENED);
|
|
|
|
OrbisNetSockInfo all[4]{};
|
|
ASSERT_EQ(sceNetGetSockInfo(-1, all, 4, 0), 2);
|
|
EXPECT_EQ(all[1].s, tcp);
|
|
EXPECT_EQ(all[1].socket_type, ORBIS_NET_SOCK_STREAM);
|
|
EXPECT_EQ(sceNetGetSockInfo(-1, all, 1, 0), 1); // no more than asked for
|
|
|
|
EXPECT_EQ(sceNetGetSockInfo(9999, &info, 1, 0), ORBIS_NET_ERROR_EBADF);
|
|
EXPECT_EQ(sceNetGetSockInfo(udp, &info, 0, 0), ORBIS_NET_ERROR_EINVAL);
|
|
sceNetSocketClose(udp);
|
|
sceNetSocketClose(tcp);
|
|
}
|
|
|
|
TEST_F(NetLib, ResolverLooksUpNames) {
|
|
ScopedPool pool;
|
|
const OrbisNetId rid = sceNetResolverCreate("resolver", pool.id, 0);
|
|
ASSERT_GT(rid, 0);
|
|
EXPECT_EQ(sceNetResolverCreate("bad", pool.id, 1), ORBIS_NET_ERROR_EINVAL);
|
|
EXPECT_EQ(sceNetResolverCreate("bad", 0, 0), ORBIS_NET_ERROR_EBADF);
|
|
EXPECT_EQ(sceNetResolverCreate("bad", 3099, 0), ORBIS_NET_ERROR_EBADF);
|
|
EXPECT_EQ(sceNetResolverCreate("0123456789abcdef0123456789abcdef", pool.id, 0),
|
|
ORBIS_NET_ERROR_ENAMETOOLONG);
|
|
EXPECT_EQ(sceNetShowRouteWithMemory(0), ORBIS_NET_ERROR_EINVAL);
|
|
EXPECT_EQ(sceNetShowRouteWithMemory(3099), ORBIS_NET_ERROR_EBADF);
|
|
EXPECT_EQ(sceNetShowRouteWithMemory(pool.id), ORBIS_OK);
|
|
|
|
// Synchronous: a numeric address needs no DNS.
|
|
OrbisNetInAddr addr{};
|
|
ASSERT_EQ(sceNetResolverStartNtoa(rid, "127.0.0.1", &addr, 0, 0, 0), ORBIS_OK);
|
|
EXPECT_EQ(addr.inaddr_addr, sceNetHtonl(0x7F000001));
|
|
OrbisNetResolverInfo info{};
|
|
ASSERT_EQ(sceNetResolverStartNtoaMultipleRecords(rid, "127.0.0.2", &info, 0, 0, 0), ORBIS_OK);
|
|
EXPECT_EQ(info.records, 1u);
|
|
EXPECT_EQ(info.recordsv4, 1u);
|
|
EXPECT_EQ(info.addrs[0].af, static_cast<u32>(ORBIS_NET_AF_INET));
|
|
EXPECT_EQ(info.addrs[0].u.addr.inaddr_addr, sceNetHtonl(0x7F000002));
|
|
|
|
// Asynchronous: completion shows up in an epoll as ORBIS_NET_EPOLLDESCID.
|
|
const OrbisNetId ep = sceNetEpollCreate("resolver", 0);
|
|
OrbisNetEpollEvent ev{};
|
|
ev.events = ORBIS_NET_EPOLLIN;
|
|
ev.data.data_u64 = 42;
|
|
ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, rid, &ev), ORBIS_OK);
|
|
addr = {};
|
|
ASSERT_EQ(sceNetResolverStartNtoa(rid, "127.0.0.3", &addr, 0, 0, ORBIS_NET_RESOLVER_ASYNC),
|
|
ORBIS_OK);
|
|
OrbisNetEpollEvent out[2]{};
|
|
ASSERT_EQ(sceNetEpollWait(ep, out, 2, 5'000'000), 1);
|
|
EXPECT_EQ(out[0].events, ORBIS_NET_EPOLLIN | ORBIS_NET_EPOLLDESCID);
|
|
EXPECT_EQ(out[0].ident, static_cast<u64>(rid));
|
|
EXPECT_EQ(out[0].data.data_u64, 42u);
|
|
s32 status = -1;
|
|
ASSERT_EQ(sceNetResolverGetError(rid, &status), ORBIS_OK);
|
|
EXPECT_EQ(status, 0);
|
|
EXPECT_EQ(addr.inaddr_addr, sceNetHtonl(0x7F000003));
|
|
EXPECT_EQ(sceNetResolverGetError(rid, nullptr), ORBIS_NET_ERROR_EINVAL);
|
|
|
|
// Offline, names fail with ENODNS and the status says so. Addresses, "localhost" and a
|
|
// trailing dot are handled locally, unless DISABLE_IPADDRESS sends an address to DNS.
|
|
SetSystemHooks({.is_online = [] { return false; }});
|
|
EXPECT_EQ(sceNetResolverStartNtoa(rid, "example.com", &addr, 0, 0, 0),
|
|
ORBIS_NET_ERROR_RESOLVER_ENODNS);
|
|
EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_RESOLVER_ENODNS);
|
|
sceNetResolverGetError(rid, &status);
|
|
EXPECT_EQ(status, ORBIS_NET_ERROR_RESOLVER_ENODNS);
|
|
ASSERT_EQ(sceNetResolverStartNtoa(rid, "10.1.2.3", &addr, 0, 0, 0), ORBIS_OK);
|
|
EXPECT_EQ(addr.inaddr_addr, sceNetHtonl(0x0A010203));
|
|
ASSERT_EQ(sceNetResolverStartNtoa(rid, "localhost.", &addr, 0, 0, 0), ORBIS_OK);
|
|
EXPECT_EQ(addr.inaddr_addr, sceNetHtonl(ORBIS_NET_INADDR_LOOPBACK));
|
|
EXPECT_EQ(sceNetResolverStartNtoa(rid, "10.1.2.3", &addr, 0, 0,
|
|
ORBIS_NET_RESOLVER_START_NTOA_DISABLE_IPADDRESS),
|
|
ORBIS_NET_ERROR_RESOLVER_ENODNS);
|
|
EXPECT_EQ(sceNetResolverStartNtoa(rid, "", &addr, 0, 0, 0), ORBIS_NET_ERROR_EINVAL);
|
|
OrbisNetResolverInfo records{};
|
|
EXPECT_EQ(sceNetResolverStartNtoaMultipleRecordsEx(rid, "10.1.2.3", &records, 0, 0, 0x2000000),
|
|
ORBIS_OK);
|
|
EXPECT_EQ(records.records, 1u);
|
|
EXPECT_EQ(sceNetResolverStartNtoaMultipleRecordsEx(rid, "10.1.2.3", &records, 0, 0, 0x1000000),
|
|
ORBIS_NET_ERROR_RESOLVER_ENORECORD);
|
|
EXPECT_EQ(sceNetResolverStartNtoaMultipleRecordsEx(rid, "10.1.2.3", &records, 0, 0, 0x4),
|
|
ORBIS_NET_ERROR_EINVAL);
|
|
SetSystemHooks({});
|
|
|
|
// Destroying removes it from the epoll and frees the id.
|
|
EXPECT_EQ(sceNetResolverDestroy(rid), ORBIS_OK);
|
|
EXPECT_EQ(sceNetResolverDestroy(rid), ORBIS_NET_ERROR_EBADF);
|
|
EXPECT_EQ(sceNetResolverStartNtoa(rid, "127.0.0.1", &addr, 0, 0, 0), ORBIS_NET_ERROR_EBADF);
|
|
EXPECT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_DEL, rid, nullptr), ORBIS_NET_ERROR_EBADF);
|
|
sceNetEpollDestroy(ep);
|
|
}
|
|
|
|
TEST_F(NetLib, EtherPoolsAndStatistics) {
|
|
OrbisNetEtherAddr mac{};
|
|
ASSERT_EQ(sceNetEtherStrton("00:1A:2b:3c:4D:5e", &mac), ORBIS_OK);
|
|
EXPECT_EQ(mac.data[1], 0x1a);
|
|
EXPECT_EQ(mac.data[5], 0x5e);
|
|
char text[ORBIS_NET_ETHER_ADDRSTRLEN]{};
|
|
ASSERT_EQ(sceNetEtherNtostr(&mac, text, sizeof(text)), ORBIS_OK);
|
|
EXPECT_STREQ(text, "00:1a:2b:3c:4d:5e");
|
|
EXPECT_EQ(sceNetEtherNtostr(&mac, text, 17), ORBIS_NET_ERROR_ENOSPC);
|
|
EXPECT_EQ(sceNetEtherStrton("00:1a:2b", &mac), ORBIS_NET_ERROR_EINVAL);
|
|
EXPECT_EQ(sceNetEtherStrton("00:1a:2b:3c:4d:5e:6f", &mac), ORBIS_NET_ERROR_EINVAL);
|
|
|
|
const s32 pool = sceNetPoolCreate("pool", 64 * 1024, 0);
|
|
OrbisNetMemoryPoolStats stats{};
|
|
ASSERT_EQ(sceNetGetMemoryPoolStats(pool, &stats), ORBIS_OK);
|
|
EXPECT_EQ(stats.pool_size, 64u * 1024u);
|
|
EXPECT_EQ(stats.current_inuse_size, 0u);
|
|
EXPECT_EQ(sceNetGetMemoryPoolStats(pool, nullptr), ORBIS_NET_ERROR_EINVAL);
|
|
EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EINVAL);
|
|
sceNetPoolDestroy(pool);
|
|
EXPECT_EQ(sceNetGetMemoryPoolStats(pool, &stats), ORBIS_NET_ERROR_EBADF);
|
|
EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EBADF);
|
|
|
|
OrbisNetStatisticsInfo info{};
|
|
ASSERT_EQ(sceNetGetStatisticsInfo(&info, 0), ORBIS_OK);
|
|
EXPECT_GT(info.kernel_mem_free_size, 0);
|
|
EXPECT_GT(info.libnet_mem_free_size, 0);
|
|
}
|
|
|
|
TEST_F(NetLib, EpollReportsPeerCloseAsReadable) {
|
|
OrbisNetId listener = 0;
|
|
const u16 port = Listen(&listener);
|
|
OrbisNetId accepted = -1;
|
|
std::thread server([&] { accepted = sceNetAccept(listener, nullptr, nullptr); });
|
|
const OrbisNetId client = sceNetSocket("client", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
auto to = Loopback(port);
|
|
ASSERT_EQ(sceNetConnect(client, Guest(&to), sizeof(to)), ORBIS_OK);
|
|
server.join();
|
|
ASSERT_GT(accepted, 0);
|
|
|
|
const OrbisNetId ep = sceNetEpollCreate("ep", 0);
|
|
OrbisNetEpollEvent ev{};
|
|
ev.events = ORBIS_NET_EPOLLIN;
|
|
ASSERT_EQ(sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, accepted, &ev), ORBIS_OK);
|
|
|
|
// The PS4 epoll is kqueue read/write filters: the peer closing shows up as IN only, and
|
|
// the read returns the end of stream.
|
|
sceNetSocketClose(client);
|
|
OrbisNetEpollEvent out[2]{};
|
|
ASSERT_EQ(sceNetEpollWait(ep, out, 2, 2'000'000), 1);
|
|
EXPECT_EQ(out[0].events, ORBIS_NET_EPOLLIN);
|
|
char buf[4];
|
|
EXPECT_EQ(sceNetRecv(accepted, buf, sizeof(buf), 0), 0);
|
|
|
|
sceNetEpollDestroy(ep);
|
|
sceNetSocketClose(accepted);
|
|
sceNetSocketClose(listener);
|
|
}
|
|
|
|
TEST_F(NetLib, SmallHelpers) {
|
|
u32 random = 0;
|
|
EXPECT_EQ(sceNetGetRandom(&random), ORBIS_OK);
|
|
EXPECT_EQ(sceNetGetRandom(nullptr), ORBIS_NET_ERROR_EINVAL);
|
|
|
|
u64 t1 = 0;
|
|
u64 t2 = 0;
|
|
sceNetGetSystemTime(&t1);
|
|
std::this_thread::sleep_for(2ms);
|
|
sceNetGetSystemTime(&t2);
|
|
EXPECT_GT(t2, t1);
|
|
sceNetGetSystemTime(nullptr); // ignored
|
|
|
|
EXPECT_STREQ(sceNetGetIfName(0), "lo0");
|
|
EXPECT_STREQ(sceNetGetIfName(1), "eth0");
|
|
EXPECT_STREQ(sceNetGetIfName(10), "");
|
|
EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EINVAL);
|
|
|
|
EXPECT_EQ(sceNetMemoryAllocate(0, 0), nullptr);
|
|
auto* zeroed = static_cast<u8*>(sceNetMemoryAllocate(64, 2));
|
|
ASSERT_NE(zeroed, nullptr);
|
|
EXPECT_EQ(std::count(zeroed, zeroed + 64, u8{0}), 64);
|
|
sceNetMemoryFree(zeroed);
|
|
|
|
EXPECT_EQ(sceNetUsleep(-1), ORBIS_NET_ERROR_EINVAL);
|
|
EXPECT_EQ(sceNetUsleep(10), ORBIS_OK);
|
|
}
|
|
|
|
TEST_F(NetLib, IoctlAndAddressEdgeCasesOnNativeSockets) {
|
|
const OrbisNetId udp = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
auto any = Loopback(0);
|
|
ASSERT_EQ(sceNetBind(udp, Guest(&any), sizeof(any)), ORBIS_OK);
|
|
OrbisNetSockaddrIn self{};
|
|
u32 len = sizeof(self);
|
|
sceNetGetsockname(udp, Guest(&self), &len);
|
|
|
|
// FIONBIO and FIONREAD reach the socket like they would the kernel.
|
|
s32 on = 1;
|
|
ASSERT_EQ(sceNetIoctl(udp, 0x8004667e, &on), ORBIS_OK);
|
|
EXPECT_EQ(GetInt(udp, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO), 1);
|
|
s32 ready = -1;
|
|
ASSERT_EQ(sceNetIoctl(udp, 0x4004667f, &ready), ORBIS_OK);
|
|
EXPECT_EQ(ready, 0);
|
|
sceNetSendto(udp, "12345", 5, 0, Guest(&self), sizeof(self));
|
|
std::this_thread::sleep_for(20ms);
|
|
ASSERT_EQ(sceNetIoctl(udp, 0x4004667f, &ready), ORBIS_OK);
|
|
EXPECT_GE(ready, 5);
|
|
EXPECT_EQ(sceNetIoctl(udp, 0x12345678, &ready), ORBIS_NET_ERROR_EINVAL);
|
|
|
|
// An address buffer without a length pointer is ignored, not EFAULT.
|
|
char buf[8];
|
|
OrbisNetSockaddrIn from{};
|
|
EXPECT_EQ(sceNetRecvfrom(udp, buf, sizeof(buf), 0, Guest(&from), nullptr), 5);
|
|
sceNetSocketClose(udp);
|
|
|
|
// A connected stream ignores a destination address.
|
|
OrbisNetId listener = 0;
|
|
const u16 port = Listen(&listener);
|
|
OrbisNetId accepted = -1;
|
|
std::thread server([&] { accepted = sceNetAccept(listener, nullptr, nullptr); });
|
|
const OrbisNetId client = sceNetSocket("client", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
auto to = Loopback(port);
|
|
ASSERT_EQ(sceNetConnect(client, Guest(&to), sizeof(to)), ORBIS_OK);
|
|
server.join();
|
|
ASSERT_GT(accepted, 0);
|
|
EXPECT_EQ(sceNetSendto(client, "hi", 2, 0, Guest(&to), sizeof(to)), 2);
|
|
EXPECT_EQ(sceNetRecv(accepted, buf, sizeof(buf), 0), 2);
|
|
sceNetSocketClose(client);
|
|
sceNetSocketClose(accepted);
|
|
sceNetSocketClose(listener);
|
|
}
|
|
|
|
TEST_F(NetLib, DnsInfoOverride) {
|
|
std::array<u32, 2> dns{};
|
|
EXPECT_EQ(sceNetGetDnsInfo(nullptr, 0), ORBIS_NET_ERROR_EINVAL);
|
|
EXPECT_EQ(sceNetGetDnsInfo(dns.data(), 1), ORBIS_NET_ERROR_EINVAL);
|
|
|
|
const std::array<u32, 2> mine{sceNetHtonl(0x0A000001), 0};
|
|
EXPECT_EQ(sceNetSetDnsInfo(mine.data(), 0), ORBIS_OK);
|
|
EXPECT_EQ(sceNetGetDnsInfo(dns.data(), 0), 1); // the count
|
|
EXPECT_EQ(dns, mine);
|
|
const std::array<u32, 2> no_primary{0, sceNetHtonl(0x0A000001)};
|
|
EXPECT_EQ(sceNetSetDnsInfo(no_primary.data(), 0), ORBIS_NET_ERROR_EINVAL);
|
|
EXPECT_EQ(sceNetSetDnsInfo(nullptr, 0), ORBIS_OK); // clears it
|
|
|
|
std::array<u8, 32> dns6{};
|
|
std::array<u8, 32> mine6{};
|
|
mine6[0] = 0x20;
|
|
mine6[15] = 1;
|
|
EXPECT_EQ(sceNetGetDns6Info(dns6.data(), 0), 0);
|
|
EXPECT_EQ(sceNetSetDns6Info(mine6.data(), 0), ORBIS_OK);
|
|
EXPECT_EQ(sceNetGetDns6Info(dns6.data(), 0), 1);
|
|
EXPECT_EQ(dns6, mine6);
|
|
EXPECT_EQ(sceNetSetDns6Info(nullptr, 0), ORBIS_OK);
|
|
}
|
|
|
|
TEST_F(NetLib, ResolverReverseLookupAndPreservedAbort) {
|
|
ScopedPool pool;
|
|
const OrbisNetId rid = sceNetResolverCreate("aton", pool.id, 0);
|
|
ASSERT_GT(rid, 0);
|
|
// A preserved abort fails the next lookup of its kind only.
|
|
EXPECT_EQ(sceNetResolverAbort(rid, ORBIS_NET_RESOLVER_ABORT_FLAG_ATON_PRESERVATION), ORBIS_OK);
|
|
OrbisNetInAddr addr{};
|
|
EXPECT_EQ(sceNetResolverStartNtoa(rid, "127.0.0.1", &addr, 0, 0, 0), ORBIS_OK);
|
|
const OrbisNetInAddr loopback{sceNetHtonl(ORBIS_NET_INADDR_LOOPBACK)};
|
|
char name[256]{};
|
|
EXPECT_EQ(sceNetResolverStartAton(rid, &loopback, name, sizeof(name), 0, 0, 0),
|
|
ORBIS_NET_ERROR_EINTR);
|
|
// Then the reverse lookup works: 127.0.0.1 is "localhost", answered locally.
|
|
ASSERT_EQ(sceNetResolverStartAton(rid, &loopback, name, sizeof(name), 0, 0, 0), ORBIS_OK);
|
|
EXPECT_STREQ(name, "localhost");
|
|
char tiny[2]{};
|
|
EXPECT_EQ(sceNetResolverStartAton(rid, &loopback, tiny, sizeof(tiny), 0, 0, 0),
|
|
ORBIS_NET_ERROR_RESOLVER_ENOSPACE);
|
|
// Host names longer than 255 characters are refused.
|
|
const std::string long_name(256, 'a');
|
|
EXPECT_EQ(sceNetResolverStartNtoa(rid, long_name.c_str(), &addr, 0, 0, 0),
|
|
ORBIS_NET_ERROR_EINVAL);
|
|
sceNetResolverDestroy(rid);
|
|
}
|
|
|
|
TEST_F(NetLib, MacAddressComesFromTheHost) {
|
|
OrbisNetEtherAddr mac{};
|
|
EXPECT_EQ(sceNetGetMacAddress(nullptr, 0), ORBIS_NET_ERROR_EINVAL);
|
|
SetSystemHooks({.mac_address = [](std::array<u8, 6>* out) {
|
|
*out = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
|
|
return true;
|
|
}});
|
|
ASSERT_EQ(sceNetGetMacAddress(&mac, 0), ORBIS_OK);
|
|
EXPECT_EQ(mac.data[5], 0x55);
|
|
SetSystemHooks({});
|
|
ASSERT_EQ(sceNetGetMacAddress(&mac, 0), ORBIS_OK);
|
|
EXPECT_EQ(mac.data[5], 0x00); // no hook: zeros
|
|
}
|
|
|
|
TEST_F(NetLib, P2PTransportLifecycleHooks) {
|
|
u16 started = 0;
|
|
u16 stopped = 0;
|
|
int starts = 0;
|
|
u16 port_setting = 0; // any free port
|
|
SetSystemHooks({
|
|
.p2p_port = [&] { return port_setting; },
|
|
.p2p_started =
|
|
[&](u16 port) {
|
|
started = port;
|
|
++starts;
|
|
},
|
|
.p2p_stopped = [&](u16 port) { stopped = port; },
|
|
.public_addr = [] { return sceNetHtonl(0xC6336401); }, // 198.51.100.1
|
|
});
|
|
EXPECT_EQ(GetP2PConfiguredPort(), 0); // the setting wins over ConfigureP2P
|
|
EXPECT_EQ(GetP2PAdvertisedAddr(), sceNetHtonl(0xC6336401));
|
|
EXPECT_FALSE(P2PTransportIsReady());
|
|
|
|
// First use starts it and tells the hook the port it got; later uses do not restart it.
|
|
ASSERT_TRUE(EnsureP2PTransport());
|
|
EXPECT_NE(started, 0);
|
|
EXPECT_EQ(started, GetP2PBoundPort());
|
|
EXPECT_EQ(GetP2PAdvertisedPort(), started);
|
|
EXPECT_TRUE(EnsureP2PTransport());
|
|
EXPECT_EQ(starts, 1);
|
|
|
|
StopP2P();
|
|
EXPECT_EQ(stopped, started);
|
|
EXPECT_FALSE(P2PTransportIsReady());
|
|
EXPECT_EQ(GetP2PAdvertisedPort(), 0); // not running: the configured port
|
|
|
|
// A port that is taken, e.g. by another instance on the same PC: the transport moves to a
|
|
// nearby free port, and that is the one advertised and forwarded.
|
|
const OrbisNetId blocker = sceNetSocket("blocker", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
OrbisNetSockaddrIn at{};
|
|
at.sin_len = sizeof(at);
|
|
at.sin_family = ORBIS_NET_AF_INET;
|
|
bool bound = false;
|
|
for (u16 port = 20000; port < 30000 && !bound; port += 97) {
|
|
at.sin_port = sceNetHtons(port);
|
|
bound = sceNetBind(blocker, Guest(&at), sizeof(at)) == ORBIS_OK;
|
|
}
|
|
ASSERT_TRUE(bound);
|
|
port_setting = sceNetNtohs(at.sin_port);
|
|
ASSERT_TRUE(EnsureP2PTransport());
|
|
EXPECT_NE(GetP2PBoundPort(), port_setting);
|
|
EXPECT_GT(GetP2PBoundPort(), port_setting);
|
|
EXPECT_LE(GetP2PBoundPort(), port_setting + 16);
|
|
EXPECT_EQ(started, GetP2PBoundPort());
|
|
EXPECT_EQ(GetP2PAdvertisedPort(), started);
|
|
EXPECT_EQ(starts, 2);
|
|
sceNetSocketClose(blocker);
|
|
|
|
// An explicit StartP2P gets exactly the port it asks for, or nothing.
|
|
StopP2P();
|
|
const OrbisNetId blocker2 =
|
|
sceNetSocket("blocker2", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
ASSERT_EQ(sceNetBind(blocker2, Guest(&at), sizeof(at)), ORBIS_OK);
|
|
EXPECT_EQ(StartP2P(port_setting), 0);
|
|
sceNetSocketClose(blocker2);
|
|
EXPECT_EQ(StartP2P(port_setting), port_setting);
|
|
|
|
StopP2P();
|
|
SetSystemHooks({});
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
// P2P, over the real wire format
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
class NetLibP2P : public NetLib {
|
|
protected:
|
|
static void SetUpTestSuite() {
|
|
NetLib::SetUpTestSuite();
|
|
// The transport starts with the first P2P socket, on the configured port.
|
|
ConfigureP2P(0, 0); // any free port for the test
|
|
EXPECT_FALSE(P2PTransportIsReady());
|
|
const OrbisNetId first =
|
|
sceNetSocket("first", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0);
|
|
EXPECT_GT(first, 0);
|
|
EXPECT_TRUE(P2PTransportIsReady());
|
|
sceNetSocketClose(first);
|
|
local_keys_ = GetP2PKeyring();
|
|
local_port_ = GetP2PBoundPort();
|
|
ASSERT_NE(local_port_, 0);
|
|
Host::Error error;
|
|
remote_keys_ = std::make_shared<P2P::Keyring>();
|
|
remote_ =
|
|
P2P::Transport::Create(AF_INET, P2P::Endpoint::IPv4("127.0.0.1", 0),
|
|
std::make_unique<P2P::FramingCodec>(remote_keys_), {}, &error);
|
|
ASSERT_TRUE(remote_);
|
|
}
|
|
static void TearDownTestSuite() {
|
|
StopP2P();
|
|
remote_.reset();
|
|
NetLib::TearDownTestSuite();
|
|
}
|
|
|
|
static P2P::Endpoint LocalEndpoint() {
|
|
return P2P::Endpoint::IPv4("127.0.0.1", local_port_);
|
|
}
|
|
|
|
static inline u16 local_port_ = 0;
|
|
static inline std::shared_ptr<P2P::Keyring> local_keys_;
|
|
static inline std::shared_ptr<P2P::Keyring> remote_keys_;
|
|
static inline std::shared_ptr<P2P::Transport> remote_;
|
|
};
|
|
|
|
// shadNet's STUN server is a plain UDP socket. Signaling reaches it as a P2P datagram from and
|
|
// to vport 0xFFFF, and it answers the same way.
|
|
TEST_F(NetLibP2P, SignalingReachesShadNetStunServer) {
|
|
const OrbisNetId server = sceNetSocket("server", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
ASSERT_GT(server, 0);
|
|
OrbisNetSockaddrIn addr{};
|
|
addr.sin_len = sizeof(addr);
|
|
addr.sin_family = ORBIS_NET_AF_INET;
|
|
addr.sin_addr = sceNetHtonl(0x7F000001);
|
|
ASSERT_EQ(sceNetBind(server, Guest(&addr), sizeof(addr)), ORBIS_OK);
|
|
u32 len = sizeof(addr);
|
|
ASSERT_EQ(sceNetGetsockname(server, Guest(&addr), &len), ORBIS_OK);
|
|
|
|
const std::array<u8, 6> header{0xFF, 0x80 | 3, 0xFF, 0xFF, 0xFF, 0xFF};
|
|
const std::array<u8, 21> ping{0x01, 'S', 't', 'e', 'p', 'h', 'e', 'n'};
|
|
ASSERT_EQ(P2PSignalingSendTo(ping.data(), ping.size(), addr.sin_addr, addr.sin_port),
|
|
static_cast<int>(ping.size()));
|
|
std::array<u8, 64> got{};
|
|
OrbisNetSockaddrIn from{};
|
|
u32 from_len = sizeof(from);
|
|
ASSERT_EQ(sceNetRecvfrom(server, got.data(), got.size(), 0, Guest(&from), &from_len),
|
|
static_cast<int>(header.size() + ping.size()));
|
|
EXPECT_TRUE(std::equal(header.begin(), header.end(), got.begin()));
|
|
EXPECT_TRUE(std::equal(ping.begin(), ping.end(), got.begin() + header.size()));
|
|
EXPECT_EQ(sceNetNtohs(from.sin_port), local_port_);
|
|
|
|
// STUN echo: header, then [ext ip 4][ext port 2]
|
|
std::vector<u8> echo(header.begin(), header.end());
|
|
const std::array<u8, 6> body{
|
|
0x7F, 0, 0, 1, static_cast<u8>(local_port_ >> 8), static_cast<u8>(local_port_)};
|
|
echo.insert(echo.end(), body.begin(), body.end());
|
|
ASSERT_EQ(sceNetSendto(server, echo.data(), echo.size(), 0, Guest(&from), sizeof(from)),
|
|
static_cast<int>(echo.size()));
|
|
std::array<u8, 64> reply{};
|
|
u32 reply_addr = 0;
|
|
u16 reply_port = 0;
|
|
int n = -1;
|
|
for (int i = 0; i < 200 && n < 0; ++i) {
|
|
n = P2PSignalingRecvFrom(reply.data(), reply.size(), &reply_addr, &reply_port);
|
|
if (n < 0) {
|
|
std::this_thread::sleep_for(5ms);
|
|
}
|
|
}
|
|
ASSERT_EQ(n, static_cast<int>(body.size()));
|
|
EXPECT_TRUE(std::equal(body.begin(), body.end(), reply.begin()));
|
|
EXPECT_EQ(reply_port, addr.sin_port);
|
|
sceNetSocketClose(server);
|
|
}
|
|
|
|
TEST_F(NetLibP2P, DatagramWakesEpollAndReportsVport) {
|
|
const OrbisNetId s = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0);
|
|
ASSERT_GT(s, 0);
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_TYPE),
|
|
static_cast<int>(ORBIS_NET_SOCK_DGRAM_P2P));
|
|
auto bind_addr = Loopback(local_port_, 3001);
|
|
ASSERT_EQ(sceNetBind(s, Guest(&bind_addr), sizeof(bind_addr)), ORBIS_OK);
|
|
|
|
OrbisNetSockaddrIn name{};
|
|
u32 len = sizeof(name);
|
|
ASSERT_EQ(sceNetGetsockname(s, Guest(&name), &len), ORBIS_OK);
|
|
EXPECT_EQ(sceNetNtohs(name.sin_port), local_port_);
|
|
EXPECT_EQ(sceNetNtohs(name.sin_vport), 3001);
|
|
|
|
const OrbisNetId ep = sceNetEpollCreate("p2p", 0);
|
|
OrbisNetEpollEvent ev{};
|
|
ev.events = ORBIS_NET_EPOLLIN;
|
|
ev.data.data_u64 = 77;
|
|
sceNetEpollControl(ep, ORBIS_NET_EPOLL_CTL_ADD, s, &ev);
|
|
|
|
auto remote = remote_->CreateDatagram();
|
|
remote->Bind(4001);
|
|
std::thread sender([&] {
|
|
std::this_thread::sleep_for(150ms);
|
|
const u8 msg[] = "ping";
|
|
const auto to = LocalEndpoint();
|
|
remote->SendTo(msg, &to, 3001);
|
|
});
|
|
OrbisNetEpollEvent out[2]{};
|
|
EXPECT_EQ(sceNetEpollWait(ep, out, 2, -1), 1); // infinite wait wakes on P2P data
|
|
sender.join();
|
|
EXPECT_EQ(out[0].data.data_u64, 77u);
|
|
|
|
char buf[16]{};
|
|
OrbisNetSockaddrIn from{};
|
|
u32 from_len = sizeof(from);
|
|
EXPECT_EQ(sceNetRecvfrom(s, buf, sizeof(buf), 0, Guest(&from), &from_len), 5);
|
|
EXPECT_STREQ(buf, "ping");
|
|
EXPECT_EQ(sceNetNtohs(from.sin_port), remote_->BoundPort()); // UDP port
|
|
EXPECT_EQ(sceNetNtohs(from.sin_vport), 4001); // virtual port
|
|
|
|
// Reply through the guest API to the address it reported.
|
|
EXPECT_EQ(sceNetSendto(s, "pong", 4, 0, Guest(&from), from_len), 4);
|
|
u8 reply[8]{};
|
|
size_t got = 0;
|
|
for (int i = 0; i < 200 && got == 0; ++i) {
|
|
got = remote->RecvFrom(reply, false, nullptr, nullptr).bytes;
|
|
std::this_thread::sleep_for(5ms);
|
|
}
|
|
EXPECT_EQ(got, 4u);
|
|
|
|
sceNetEpollDestroy(ep);
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLibP2P, PortRangesAndDefaults) {
|
|
const OrbisNetId dgram = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0);
|
|
// Reserved virtual ports cannot be bound.
|
|
for (const u16 vport : {5353, 32768, 60000}) {
|
|
auto addr = Loopback(local_port_, vport);
|
|
EXPECT_EQ(sceNetBind(dgram, Guest(&addr), sizeof(addr)), ORBIS_NET_ERROR_EACCES) << vport;
|
|
}
|
|
// Ephemeral virtual ports come from 32768-49999.
|
|
auto any = Loopback(local_port_, 0);
|
|
ASSERT_EQ(sceNetBind(dgram, Guest(&any), sizeof(any)), ORBIS_OK);
|
|
OrbisNetSockaddrIn name{};
|
|
u32 len = sizeof(name);
|
|
sceNetGetsockname(dgram, Guest(&name), &len);
|
|
EXPECT_GE(sceNetNtohs(name.sin_vport), 32768);
|
|
EXPECT_LE(sceNetNtohs(name.sin_vport), 49999);
|
|
EXPECT_EQ(GetInt(dgram, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDBUF), 9216);
|
|
EXPECT_EQ(GetInt(dgram, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF), 40960);
|
|
std::vector<u8> big(9217);
|
|
auto to = Loopback(remote_->BoundPort(), 4000);
|
|
EXPECT_EQ(sceNetSendto(dgram, big.data(), big.size(), 0, Guest(&to), sizeof(to)),
|
|
ORBIS_NET_ERROR_EMSGSIZE);
|
|
sceNetSocketClose(dgram);
|
|
|
|
// Stream ports follow the TCP table.
|
|
const OrbisNetId stream = sceNetSocket("p2ps", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM_P2P, 0);
|
|
auto reserved = Loopback(1000, 0);
|
|
EXPECT_EQ(sceNetBind(stream, Guest(&reserved), sizeof(reserved)), ORBIS_NET_ERROR_EACCES);
|
|
auto allowed = Loopback(30000, 0);
|
|
EXPECT_EQ(sceNetBind(stream, Guest(&allowed), sizeof(allowed)), ORBIS_OK);
|
|
EXPECT_EQ(GetInt(stream, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_SNDBUF), 32768);
|
|
EXPECT_EQ(GetInt(stream, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF), 65536);
|
|
sceNetSocketClose(stream);
|
|
}
|
|
|
|
TEST_F(NetLibP2P, AddressesAreCheckedStrictly) {
|
|
const OrbisNetId dgram = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0);
|
|
auto to = Loopback(remote_->BoundPort(), 4300);
|
|
// Connecting needs a bound socket.
|
|
EXPECT_EQ(sceNetConnect(dgram, Guest(&to), sizeof(to)), ORBIS_NET_ERROR_EINVAL);
|
|
|
|
// Bind: exactly 16 bytes; the UDP port named; no multicast address. sa_len may be 0, as
|
|
// games leave it.
|
|
auto addr = Loopback(local_port_, 3300);
|
|
EXPECT_EQ(sceNetBind(dgram, Guest(&addr), 32), ORBIS_NET_ERROR_EINVAL);
|
|
addr.sin_family = ORBIS_NET_AF_INET6;
|
|
EXPECT_EQ(sceNetBind(dgram, Guest(&addr), sizeof(addr)), ORBIS_NET_ERROR_EAFNOSUPPORT);
|
|
addr.sin_family = ORBIS_NET_AF_INET;
|
|
auto no_udp_port = Loopback(0, 3300);
|
|
EXPECT_EQ(sceNetBind(dgram, Guest(&no_udp_port), sizeof(no_udp_port)),
|
|
ORBIS_NET_ERROR_EADDRNOTAVAIL);
|
|
auto multicast = Loopback(local_port_, 3300);
|
|
multicast.sin_addr = sceNetHtonl(0xE0000001); // 224.0.0.1
|
|
EXPECT_EQ(sceNetBind(dgram, Guest(&multicast), sizeof(multicast)),
|
|
ORBIS_NET_ERROR_EADDRNOTAVAIL);
|
|
addr.sin_len = 0;
|
|
ASSERT_EQ(sceNetBind(dgram, Guest(&addr), sizeof(addr)), ORBIS_OK);
|
|
EXPECT_EQ(sceNetBind(dgram, Guest(&addr), sizeof(addr)), ORBIS_NET_ERROR_EINVAL); // once
|
|
|
|
// Sendto and connect need an address and both ports.
|
|
auto no_vport = Loopback(remote_->BoundPort(), 0);
|
|
EXPECT_EQ(sceNetSendto(dgram, "x", 1, 0, Guest(&no_vport), sizeof(no_vport)),
|
|
ORBIS_NET_ERROR_EADDRNOTAVAIL); // "0 was set to ... sin_vport"
|
|
EXPECT_EQ(sceNetSendto(dgram, "x", 1, 0, Guest(&to), 20), ORBIS_NET_ERROR_EINVAL);
|
|
EXPECT_EQ(sceNetConnect(dgram, Guest(&no_vport), sizeof(no_vport)),
|
|
ORBIS_NET_ERROR_EADDRNOTAVAIL);
|
|
ASSERT_EQ(sceNetConnect(dgram, Guest(&to), sizeof(to)), ORBIS_OK);
|
|
EXPECT_EQ(sceNetConnect(dgram, Guest(&to), sizeof(to)), ORBIS_NET_ERROR_EISCONN);
|
|
sceNetSocketClose(dgram);
|
|
|
|
// Streams: listen needs a bind (connect binds implicitly, see
|
|
// StreamConnectBindsImplicitly); the UDP port is 0 or the P2P port.
|
|
const OrbisNetId stream = sceNetSocket("p2ps", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM_P2P, 0);
|
|
EXPECT_EQ(sceNetListen(stream, 4), ORBIS_NET_ERROR_EINVAL);
|
|
auto peer = Loopback(5000, remote_->BoundPort());
|
|
auto odd_udp_port = Loopback(30100, 1234);
|
|
EXPECT_EQ(sceNetBind(stream, Guest(&odd_udp_port), sizeof(odd_udp_port)),
|
|
ORBIS_NET_ERROR_EINVAL);
|
|
auto well_known = Loopback(30100, 3658);
|
|
ASSERT_EQ(sceNetBind(stream, Guest(&well_known), sizeof(well_known)), ORBIS_OK);
|
|
auto nowhere = Loopback(0, 0);
|
|
nowhere.sin_addr = 0;
|
|
EXPECT_EQ(sceNetConnect(stream, Guest(&nowhere), sizeof(nowhere)),
|
|
ORBIS_NET_ERROR_EADDRNOTAVAIL);
|
|
// Bound to a non-zero TCP port: a listener, which cannot connect (EPROTO).
|
|
EXPECT_EQ(sceNetConnect(stream, Guest(&peer), sizeof(peer)),
|
|
ORBIS_NET_ERROR_BASE | ORBIS_NET_EPROTO);
|
|
EXPECT_EQ(sceNetListen(stream, 4), ORBIS_OK);
|
|
EXPECT_EQ(sceNetConnect(stream, Guest(&peer), sizeof(peer)), ORBIS_NET_ERROR_EOPNOTSUPP);
|
|
sceNetSocketClose(stream);
|
|
// Datagram P2P sockets cannot listen or accept.
|
|
const OrbisNetId dgram2 = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0);
|
|
EXPECT_EQ(sceNetAccept(dgram2, nullptr, nullptr), ORBIS_NET_ERROR_EOPNOTSUPP);
|
|
sceNetSocketClose(dgram2);
|
|
}
|
|
|
|
TEST_F(NetLibP2P, ConnectTimeout) {
|
|
// The peer is a plain UDP socket that drops everything: the SYN is never answered.
|
|
const OrbisNetId sink = sceNetSocket("sink", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
auto any = Loopback(0);
|
|
sceNetBind(sink, Guest(&any), sizeof(any));
|
|
OrbisNetSockaddrIn sink_addr{};
|
|
u32 len = sizeof(sink_addr);
|
|
sceNetGetsockname(sink, Guest(&sink_addr), &len);
|
|
|
|
const OrbisNetId s = sceNetSocket("p2ps", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM_P2P, 0);
|
|
auto self = Loopback(0, 0);
|
|
ASSERT_EQ(sceNetBind(s, Guest(&self), sizeof(self)), ORBIS_OK);
|
|
ASSERT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_CONNECTTIMEO, 200000), ORBIS_OK);
|
|
auto to = Loopback(5000, sceNetNtohs(sink_addr.sin_port));
|
|
const auto start = std::chrono::steady_clock::now();
|
|
EXPECT_EQ(sceNetConnect(s, Guest(&to), sizeof(to)), ORBIS_NET_ERROR_EWOULDBLOCK);
|
|
EXPECT_GE(Millis(start), 190.0);
|
|
EXPECT_LT(Millis(start), 2000.0);
|
|
sceNetSocketClose(s);
|
|
sceNetSocketClose(sink);
|
|
}
|
|
|
|
TEST_F(NetLibP2P, IoctlInstallsKeysAndCommunicationId) {
|
|
const OrbisNetId p2p = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0);
|
|
std::array<u8, 36> record{};
|
|
for (u8 i = 0; i < 16; ++i) {
|
|
record[i] = i;
|
|
}
|
|
ASSERT_EQ(sceNetIoctl(p2p, 0x801050d8, record.data()), ORBIS_OK);
|
|
EXPECT_EQ(GetP2PKeyring()->GetCommunicationId(),
|
|
(P2P::CommunicationId{0x54, 0x33, 0x12, 0x2f}));
|
|
ASSERT_EQ(sceNetIoctl(p2p, 0x200050d9, nullptr), ORBIS_OK); // clear it again
|
|
EXPECT_FALSE(GetP2PKeyring()->GetCommunicationId());
|
|
|
|
u16 np_port = 0;
|
|
ASSERT_EQ(sceNetIoctl(p2p, 0xc00250cf, &np_port), ORBIS_OK);
|
|
EXPECT_EQ(sceNetNtohs(np_port), local_port_);
|
|
|
|
EXPECT_EQ(sceNetIoctl(p2p, 0x802450ff, record.data()), ORBIS_NET_ERROR_EINVAL);
|
|
const OrbisNetId udp = sceNetSocket("udp", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
|
|
EXPECT_EQ(sceNetIoctl(udp, 0x801050d8, record.data()), ORBIS_NET_ERROR_EINVAL);
|
|
sceNetSocketClose(udp);
|
|
sceNetSocketClose(p2p);
|
|
}
|
|
|
|
TEST_F(NetLibP2P, PerMessageEncryption) {
|
|
const OrbisNetId s = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0);
|
|
auto bind_addr = Loopback(local_port_, 3200);
|
|
sceNetBind(s, Guest(&bind_addr), sizeof(bind_addr));
|
|
auto remote = remote_->CreateDatagram();
|
|
remote->Bind(4200);
|
|
auto to = Loopback(remote_->BoundPort(), 4200);
|
|
const auto received = [&] {
|
|
u8 buf[16]{};
|
|
for (int i = 0; i < 40; ++i) {
|
|
if (const auto r = remote->RecvFrom(buf, false, nullptr, nullptr); r.bytes != 0) {
|
|
return std::string(reinterpret_cast<char*>(buf), r.bytes);
|
|
}
|
|
std::this_thread::sleep_for(5ms);
|
|
}
|
|
return std::string();
|
|
};
|
|
|
|
P2P::P2PKey key;
|
|
key.value.fill(0x6b);
|
|
local_keys_->SetWildcardKey(key);
|
|
// MSG_USECRYPTO on this message only: a receiver without the key drops it ...
|
|
EXPECT_EQ(sceNetSendto(s, "secret", 6, ORBIS_NET_MSG_USECRYPTO, Guest(&to), sizeof(to)), 6);
|
|
EXPECT_EQ(received(), "");
|
|
// ... one with the key decrypts it.
|
|
remote_keys_->SetWildcardKey(key);
|
|
EXPECT_EQ(sceNetSendto(s, "secret", 6, ORBIS_NET_MSG_USECRYPTO, Guest(&to), sizeof(to)), 6);
|
|
EXPECT_EQ(received(), "secret");
|
|
// Without the flag the same socket sends in the clear, which the receiver also accepts.
|
|
EXPECT_EQ(sceNetSendto(s, "plain", 5, 0, Guest(&to), sizeof(to)), 5);
|
|
EXPECT_EQ(received(), "plain");
|
|
|
|
local_keys_->SetWildcardKey(std::nullopt);
|
|
remote_keys_->SetWildcardKey(std::nullopt);
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLibP2P, NpChannelsAreKeptApart) {
|
|
const u32 loopback = sceNetHtonl(0x7F000001);
|
|
const u16 port = sceNetHtons(local_port_);
|
|
const auto packet = [](u8 type) { return std::array<u8, 6>{'S', 'H', 'A', 'D', type, 0x42}; };
|
|
const auto control = packet(0x10);
|
|
const auto matching2 = packet(0x21);
|
|
const auto handshake = packet(0x07);
|
|
const std::array<u8, 3> other{'x', 'y', 'z'};
|
|
ASSERT_EQ(P2PControlSendTo(control.data(), 6, loopback, port), 6);
|
|
ASSERT_EQ(P2PMatching2SendTo(matching2.data(), 6, loopback, port), 6);
|
|
ASSERT_EQ(P2PSignalingSendTo(handshake.data(), 6, loopback, port), 6);
|
|
ASSERT_EQ(P2PSignalingSendTo(other.data(), 3, loopback, port), 3);
|
|
std::this_thread::sleep_for(100ms);
|
|
|
|
u8 buf[16]{};
|
|
u32 from_addr = 0;
|
|
u16 from_port = 0;
|
|
ASSERT_EQ(P2PControlRecvFrom(buf, sizeof(buf), &from_addr, &from_port), 6);
|
|
EXPECT_EQ(buf[4], 0x10);
|
|
EXPECT_EQ(from_addr, loopback);
|
|
EXPECT_EQ(from_port, port);
|
|
EXPECT_EQ(P2PControlRecvFrom(buf, sizeof(buf), nullptr, nullptr), -1); // nothing else
|
|
ASSERT_EQ(P2PMatching2RecvFrom(buf, sizeof(buf), nullptr, nullptr), 6);
|
|
EXPECT_EQ(buf[4], 0x21);
|
|
ASSERT_EQ(P2PSignalingRecvFrom(buf, sizeof(buf), nullptr, nullptr), 6);
|
|
EXPECT_EQ(buf[4], 0x07);
|
|
ASSERT_EQ(P2PSignalingRecvFrom(buf, sizeof(buf), nullptr, nullptr), 3);
|
|
EXPECT_EQ(P2PSignalingRecvFrom(buf, sizeof(buf), nullptr, nullptr), -1);
|
|
|
|
EXPECT_TRUE(EnsureP2PTransport());
|
|
EXPECT_EQ(GetP2PConfiguredPort(), 0); // what the test configured
|
|
EXPECT_EQ(GetP2PAdvertisedAddr(), 0u);
|
|
}
|
|
|
|
// JoJo: Eyes of Heaven connects STREAM_P2P sockets without binding them, which works on the
|
|
// PS4. Connect binds them to a free port, as BSD TCP does.
|
|
TEST_F(NetLibP2P, StreamConnectBindsImplicitly) {
|
|
auto listener = remote_->CreateStream();
|
|
ASSERT_EQ(listener->Bind(5100), Host::Error::Ok);
|
|
ASSERT_EQ(listener->Listen(4), Host::Error::Ok);
|
|
|
|
const OrbisNetId s = sceNetSocket("p2ps", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM_P2P, 0);
|
|
ASSERT_GT(s, 0);
|
|
auto to = Loopback(5100, remote_->BoundPort());
|
|
ASSERT_EQ(sceNetConnect(s, Guest(&to), sizeof(to)), ORBIS_OK); // no bind first
|
|
|
|
OrbisNetSockaddrIn self{};
|
|
u32 len = sizeof(self);
|
|
ASSERT_EQ(sceNetGetsockname(s, Guest(&self), &len), ORBIS_OK);
|
|
EXPECT_GE(sceNetNtohs(self.sin_port), 49152); // ephemeral, as with an explicit bind to 0
|
|
EXPECT_EQ(sceNetNtohs(self.sin_vport), local_port_);
|
|
|
|
std::shared_ptr<P2P::StreamSocket> server;
|
|
Host::Error error;
|
|
for (int i = 0; i < 400 && !server; ++i) {
|
|
server = listener->Accept(nullptr, nullptr, &error);
|
|
std::this_thread::sleep_for(5ms);
|
|
}
|
|
ASSERT_TRUE(server);
|
|
ASSERT_EQ(sceNetSend(s, "jojo", 4, 0), 4);
|
|
std::array<u8, 8> got{};
|
|
size_t n = 0;
|
|
for (int i = 0; i < 400 && n == 0; ++i) {
|
|
n = server->Recv(std::span<u8>(got), false).bytes;
|
|
if (n == 0) {
|
|
std::this_thread::sleep_for(5ms);
|
|
}
|
|
}
|
|
EXPECT_EQ(n, 4u);
|
|
EXPECT_EQ(std::memcmp(got.data(), "jojo", 4), 0);
|
|
sceNetSocketClose(s);
|
|
|
|
// Datagram P2P sockets still have to be bound first.
|
|
const OrbisNetId dgram = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0);
|
|
auto dgram_to = Loopback(remote_->BoundPort(), 4300);
|
|
EXPECT_EQ(sceNetConnect(dgram, Guest(&dgram_to), sizeof(dgram_to)), ORBIS_NET_ERROR_EINVAL);
|
|
sceNetSocketClose(dgram);
|
|
}
|
|
|
|
TEST_F(NetLibP2P, LingerIsKeptWholeOnP2PStreams) {
|
|
const OrbisNetId s = sceNetSocket("p2ps", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM_P2P, 0);
|
|
ASSERT_GT(s, 0);
|
|
const OrbisNetLinger set{1, 7};
|
|
ASSERT_EQ(sceNetSetsockopt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_LINGER, &set, sizeof(set)),
|
|
ORBIS_OK);
|
|
OrbisNetLinger got{};
|
|
u32 len = sizeof(got);
|
|
ASSERT_EQ(sceNetGetsockopt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_LINGER, &got, &len), ORBIS_OK);
|
|
EXPECT_EQ(len, sizeof(got)); // the struct, not an int
|
|
EXPECT_EQ(got.l_onoff, 1);
|
|
EXPECT_EQ(got.l_linger, 7);
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
TEST_F(NetLibP2P, StreamConnectSendRecv) {
|
|
auto listener = remote_->CreateStream();
|
|
ASSERT_EQ(listener->Bind(5000), Host::Error::Ok);
|
|
ASSERT_EQ(listener->Listen(4), Host::Error::Ok);
|
|
|
|
const OrbisNetId s = sceNetSocket("p2ps", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM_P2P, 0);
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_TYPE),
|
|
static_cast<int>(ORBIS_NET_SOCK_STREAM_P2P));
|
|
auto self_addr = Loopback(0, 0); // any TCP port, the P2P UDP port
|
|
ASSERT_EQ(sceNetBind(s, Guest(&self_addr), sizeof(self_addr)), ORBIS_OK);
|
|
// Stream layout: sin_port = the TCP port, sin_vport = the peer's UDP port.
|
|
auto to = Loopback(5000, remote_->BoundPort());
|
|
ASSERT_EQ(sceNetConnect(s, Guest(&to), sizeof(to)), ORBIS_OK); // blocking handshake
|
|
|
|
OrbisNetSockaddrIn peer{};
|
|
u32 len = sizeof(peer);
|
|
ASSERT_EQ(sceNetGetpeername(s, Guest(&peer), &len), ORBIS_OK);
|
|
EXPECT_EQ(sceNetNtohs(peer.sin_port), 5000);
|
|
EXPECT_EQ(sceNetNtohs(peer.sin_vport), remote_->BoundPort());
|
|
EXPECT_EQ(peer.sin_addr, sceNetHtonl(0x7F000001));
|
|
|
|
OrbisNetSockaddrIn self{};
|
|
len = sizeof(self);
|
|
ASSERT_EQ(sceNetGetsockname(s, Guest(&self), &len), ORBIS_OK);
|
|
EXPECT_GE(sceNetNtohs(self.sin_port), 49152); // ephemeral TCP-over-UDPP2P port
|
|
EXPECT_EQ(sceNetNtohs(self.sin_vport), local_port_);
|
|
|
|
std::shared_ptr<P2P::StreamSocket> server;
|
|
Host::Error error;
|
|
for (int i = 0; i < 400 && !server; ++i) {
|
|
server = listener->Accept(nullptr, nullptr, &error);
|
|
std::this_thread::sleep_for(5ms);
|
|
}
|
|
ASSERT_TRUE(server);
|
|
|
|
std::vector<u8> data(200 * 1024);
|
|
for (size_t i = 0; i < data.size(); ++i) {
|
|
data[i] = static_cast<u8>(i * 31);
|
|
}
|
|
// A blocking send larger than both buffers completes only while the peer reads, so it
|
|
// runs on its own thread, as a game's network thread would.
|
|
s32 sent = 0;
|
|
std::thread sender([&] { sent = sceNetSend(s, data.data(), data.size(), 0); });
|
|
std::vector<u8> got;
|
|
u8 buf[8192];
|
|
for (int i = 0; i < 2000 && got.size() < data.size(); ++i) {
|
|
const auto r = server->Recv(buf, false);
|
|
got.insert(got.end(), buf, buf + r.bytes);
|
|
if (r.bytes == 0) {
|
|
std::this_thread::sleep_for(1ms);
|
|
}
|
|
}
|
|
sender.join();
|
|
EXPECT_EQ(sent, static_cast<s32>(data.size()));
|
|
EXPECT_TRUE(got == data) << "received " << got.size();
|
|
|
|
server->Send(std::span<const u8>(reinterpret_cast<const u8*>("reply"), 5));
|
|
char reply[8]{};
|
|
EXPECT_EQ(sceNetRecv(s, reply, sizeof(reply), 0), 5); // blocking recv
|
|
EXPECT_STREQ(reply, "reply");
|
|
|
|
sceNetSocketClose(s);
|
|
listener->Close();
|
|
}
|
|
|
|
TEST_F(NetLibP2P, ProtectionOptions) {
|
|
const OrbisNetId s = sceNetSocket("p2p", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0);
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USECRYPTO), 0);
|
|
EXPECT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USECRYPTO, 1), ORBIS_OK);
|
|
EXPECT_EQ(SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USESIGNATURE, 1), ORBIS_OK);
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USECRYPTO), 1);
|
|
SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USECRYPTO, 0);
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USECRYPTO), 0);
|
|
EXPECT_EQ(GetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USESIGNATURE), 1); // independent
|
|
|
|
// Encrypted + signed end to end once both sides hold the key.
|
|
P2P::P2PKey key;
|
|
key.value.fill(0x33);
|
|
local_keys_->SetWildcardKey(key);
|
|
remote_keys_->SetWildcardKey(key);
|
|
SetInt(s, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_USECRYPTO, 1);
|
|
auto bind_addr = Loopback(local_port_, 3100);
|
|
sceNetBind(s, Guest(&bind_addr), sizeof(bind_addr));
|
|
auto remote = remote_->CreateDatagram();
|
|
remote->Bind(4100);
|
|
auto to = Loopback(remote_->BoundPort(), 4100);
|
|
EXPECT_EQ(sceNetSendto(s, "secret", 6, 0, Guest(&to), sizeof(to)), 6);
|
|
u8 buf[16]{};
|
|
size_t got = 0;
|
|
for (int i = 0; i < 200 && got == 0; ++i) {
|
|
got = remote->RecvFrom(buf, false, nullptr, nullptr).bytes;
|
|
std::this_thread::sleep_for(5ms);
|
|
}
|
|
EXPECT_EQ(got, 6u);
|
|
EXPECT_EQ(std::memcmp(buf, "secret", 6), 0);
|
|
local_keys_->SetWildcardKey(std::nullopt);
|
|
remote_keys_->SetWildcardKey(std::nullopt);
|
|
sceNetSocketClose(s);
|
|
}
|
|
|
|
} // namespace
|