network-reader tidying

This commit is contained in:
Eric 2022-08-23 10:26:03 -07:00
parent 816e9b993b
commit 6749e9ee5e
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
6 changed files with 366 additions and 371 deletions

View File

@ -3995,26 +3995,26 @@
"assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e",
"assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/b2/e5/0ee0561e16257a32830645239f34",
"ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a",
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/b7/97/058ec9b0958eb0c16d4b73e46c77",
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/fc/87/a43180a273071e2297727dae55e0",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ab/84/d648830f95c5adaa26e259267524",
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a3/af/3c0f8a3884cd4896172790e49789",
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f1/53/567de5c38f219eea734d5d780cfe",
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/0e/29/2b6acec2f043af8ffa28405518ba",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ec/e1/5b85a04e38545aa015c92819b1cc",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/62/6d/8440e0fccd5482c6cf06f5f8eb74",
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/17/41/98b75e3c0dd041875081cb230c08",
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/df/6e/8992acdcf955eec1df42f97c6559",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cd/2e/a7985d25068b8c44bf64123131c2",
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/be/f1/e13ed7948ffd0da65cbe0528ea94",
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/1d/b6/58b8b43dc5071445e0a9d666487a",
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c6/fe/fa0c92655a72b7c4eadb3d12ba32",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e1/23/5572f9e4822e6798a0f1a249dd5a",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/42/4f/609cd8f582a6e7f26772816a60c7",
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/db/f3/e9270a5d334a7f97f11cc151552a",
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ae/67/9c59c7e046c568adc89819afc445",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/60/9a/8e4789f6f0b4f394a00043ab8ec7",
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/25/c6/2d9856f3b32b71c3b222786edadb",
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/79/79/0a70bdd5f157f35fcf756a187db5",
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/d1/d8/96e209445da48374062ed2b2aa7e",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1e/aa/26758a2a412fd07628f1c1435c3f",
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/9f/98/acfca6199ed45238036a1f2f14f8",
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/3d/fa/64690be1890135b82a302c6fce22",
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/33/ae/00c76bb8d2048e9c110f698b8a20",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8a/91/3e25e98763c1a80bc2d3b49b6c24",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b7/60/2ad75fe1d4c776e220ab49a985a3",
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ab/82/bf728df59dbcde87f8514d26bf19",
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/4f/fa/e5a5ba4ea3dff37c05e93ac99f30",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/df/36/174625b380539106b67d5bf67373",
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/20/fe/077a7249888f313bc1251c85001b",
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f9/eb/5e6c3396ea7ea88aa35eea3313ff",
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/63/cc/7647e6afc47bc16cab506bfbc32f",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/24/84/2f5eeb0c76bb594a60f9d1cbad96",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1f/fb/a7f13102bc81737fa5a624ddb96e",
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/3f/a5/dca6a30b4a2e0bdeab62f746d16f",
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/58/fb/6a1b9965d43820d72449f1771b43",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/4a/dd/3e69ddf532e414418ef2b1bed182",
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/8b/03/f80c40e0efbbdf73d372eb78c7b2",
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6a/91/ddafd190b867e23110b18ddce9ab",
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/07/5f/246e5ef83e81f516aa82033470c5",
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/18/04/ed17d0ac8813451038b4831dd3c1",

View File

@ -1,4 +1,4 @@
### 1.7.7 (build 20702, api 7, 2022-08-23)
### 1.7.7 (build 20704, api 7, 2022-08-23)
- Added `ba.app.meta.load_exported_classes()` for loading classes discovered by the meta subsystem cleanly in a background thread.
- Improved logging of missing playlist game types.
- Some ba.Lstr functionality can now be used in background threads.

View File

@ -21,7 +21,7 @@
namespace ballistica {
// These are set automatically via script; don't modify them here.
const int kAppBuildNumber = 20702;
const int kAppBuildNumber = 20704;
const char* kAppVersion = "1.7.7";
// Our standalone globals.

View File

@ -453,9 +453,9 @@ auto ConnectionSet::UDPConnectionPacket(const std::vector<uint8_t>& data_in,
// Client is telling us (host) that it wants to disconnect.
uint8_t client_id = data[1];
if (!VerifyClientAddr(client_id, addr)) {
break;
}
// if (!VerifyClientAddr(client_id, addr)) {
// break;
// }
// Wipe that client out (if it still exists).
PushClientDisconnectedCall(client_id);

View File

@ -84,6 +84,88 @@ void NetworkReader::PokeSelf() {
}
}
static auto HandleJSONPing(const std::string& data_str) -> std::string {
cJSON* data = cJSON_Parse(data_str.c_str());
if (data == nullptr) {
return "";
}
cJSON_Delete(data);
// Ok lets include some basic info that might be pertinent to someone pinging
// us. Currently that includes our current/max connection count.
char buffer[256];
int party_size = 0;
int party_size_max = 10;
if (g_python != nullptr) {
party_size = g_game->public_party_size();
party_size_max = g_game->public_party_max_size();
}
snprintf(buffer, sizeof(buffer), R"({"b":%d,"ps":%d,"psmx":%d})",
kAppBuildNumber, party_size, party_size_max);
return buffer;
}
static auto HandleGameQuery(const char* buffer, size_t size,
struct sockaddr_storage* from) -> void {
if (size == 5) {
// If we're already in a party, don't advertise since they
// wouldn't be able to join us anyway.
if (g_game->connections()->has_connection_to_host()) {
return;
}
// Pull the query id from the packet.
uint32_t query_id;
memcpy(&query_id, buffer + 1, 4);
// Ship them a response packet containing the query id,
// our protocol version, our unique-session-id, and our
// player_spec.
char msg[400];
std::string usid = GetAppInstanceUUID();
std::string player_spec_string;
// If we're signed in, send our account spec.
// Otherwise just send a dummy made with our device name.
player_spec_string = PlayerSpec::GetAccountPlayerSpec().GetSpecString();
// This should always be the case (len needs to be 1 byte)
BA_PRECONDITION_FATAL(player_spec_string.size() < 256);
BA_PRECONDITION_FATAL(!usid.empty());
if (usid.size() > 100) {
Log("had to truncate session-id; shouldn't happen");
usid.resize(100);
}
if (usid.empty()) {
usid = "error";
}
msg[0] = BA_PACKET_GAME_QUERY_RESPONSE;
memcpy(msg + 1, &query_id, 4);
uint32_t protocol_version = kProtocolVersion;
memcpy(msg + 5, &protocol_version, 4);
msg[9] = static_cast<char>(usid.size());
msg[10] = static_cast<char>(player_spec_string.size());
memcpy(msg + 11, usid.c_str(), usid.size());
memcpy(msg + 11 + usid.size(), player_spec_string.c_str(),
player_spec_string.size());
size_t msg_len = 11 + player_spec_string.size() + usid.size();
BA_PRECONDITION_FATAL(msg_len <= sizeof(msg));
std::vector<uint8_t> msg_buffer(msg_len);
memcpy(msg_buffer.data(), msg, msg_len);
g_network_write_module->PushSendToCall(msg_buffer, SockAddr(*from));
} else {
Log("Error: Got invalid game-query packet of len " + std::to_string(size)
+ "; expected 5.");
}
}
auto NetworkReader::RunThread() -> int {
if (!HeadlessMode()) {
remote_server_ = std::make_unique<RemoteAppServer>();
@ -96,139 +178,11 @@ auto NetworkReader::RunThread() -> int {
std::unique_lock<std::mutex> lock(paused_mutex_);
paused_cv_.wait(lock, [this] { return (!paused_); });
}
{
// This needs to be locked during any socket-descriptor changes/writes.
std::lock_guard<std::mutex> lock(sd_mutex_);
int result;
int print_port_unavailable = false;
int initial_requested_port = port4_;
sd4_ = socket(AF_INET, SOCK_DGRAM, 0);
if (sd4_ < 0) {
Log("ERROR: Unable to open host socket; errno "
+ g_platform->GetSocketErrorString());
} else {
g_platform->SetSocketNonBlocking(sd4_);
// Bind to local server port.
struct sockaddr_in serv_addr {};
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // NOLINT
// Try our requested port for v4, then go with any available if that
// doesn't work.
serv_addr.sin_port = htons(port4_); // NOLINT
result = ::bind(sd4_, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if (result != 0) {
// If we're headless then we abort here; we're useless if we don't get
// the port we wanted.
if (HeadlessMode()) {
Log("FATAL ERROR: unable to bind to requested udp port "
+ std::to_string(port4_) + " (ipv4)");
exit(1);
}
// Primary ipv4 bind failed; try on any port as a backup.
print_port_unavailable = true;
serv_addr.sin_port = htons(0); // NOLINT
result =
::bind(sd4_, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
// Wuh oh; no ipv6 for us i guess.
if (result != 0) {
g_platform->CloseSocket(sd4_);
sd4_ = -1;
}
}
}
// See what v4 port we actually wound up with.
if (sd4_ != -1) {
struct sockaddr_in sa {};
socklen_t sa_len = sizeof(sa);
if (getsockname(sd4_, reinterpret_cast<sockaddr*>(&sa), &sa_len) == 0) {
port4_ = ntohs(sa.sin_port); // NOLINT
// Aim for a v6 port to match whatever we wound up with on the v4
// side.
port6_ = port4_;
}
}
// Ok now lets try to create an ipv6 socket on the same port.
// (its actually possible to just create a v6 socket and let the OSs
// dual-stack support provide v4 connectivity too, but that's not
// available everywhere (win XP, etc) so let's do this for now.
sd6_ = socket(AF_INET6, SOCK_DGRAM, 0);
if (sd6_ < 0) {
Log("ERROR: Unable to open ipv6 socket: "
+ g_platform->GetSocketErrorString());
} else {
// Since we're explicitly creating both a v4 and v6 socket, tell the v6
// to *not* do both itself (not sure if this is necessary; on mac it
// still seems to come up.. but apparently that's not always the case).
int on = 1;
if (setsockopt(sd6_, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<char*>(&on), sizeof(on))
== -1) {
Log("error setting socket as ipv6-only");
}
g_platform->SetSocketNonBlocking(sd6_);
struct sockaddr_in6 serv_addr {};
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin6_family = AF_INET6;
serv_addr.sin6_port = htons(port6_); // NOLINT
serv_addr.sin6_addr = in6addr_any;
result = ::bind(sd6_, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if (result != 0) {
if (HeadlessMode()) {
Log("FATAL ERROR: unable to bind to requested udp port "
+ std::to_string(port6_) + " (ipv6)");
exit(1);
}
// Primary ipv6 bind failed; try backup.
// We don't care if our random backup ports don't match; only if our
// target port failed.
if (port6_ == initial_requested_port) {
print_port_unavailable = true;
}
serv_addr.sin6_port = htons(0); // NOLINT
result =
::bind(sd6_, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if (result != 0) {
// Wuh oh; no ipv6 for us i guess.
g_platform->CloseSocket(sd6_);
sd6_ = -1;
}
}
}
// See what v6 port we actually wound up with.
if (sd6_ != -1) {
struct sockaddr_in sa {};
socklen_t sa_len = sizeof(sa);
if (getsockname(sd6_, reinterpret_cast<sockaddr*>(&sa), &sa_len) == 0) {
port6_ = ntohs(sa.sin_port); // NOLINT
}
}
if (print_port_unavailable) {
// FIXME - use translations here
ScreenMessage("Unable to bind udp port "
+ std::to_string(initial_requested_port)
+ "; some network functionality may fail.",
{1, 0.5f, 0});
Log("Unable to bind udp port " + std::to_string(initial_requested_port)
+ "; some network functionality may fail.",
true, false);
}
}
char buffer[10000];
OpenSockets();
// Now just listen and forward messages along.
char buffer[10000];
while (true) {
struct sockaddr_storage from {};
socklen_t from_size = sizeof(from);
@ -272,207 +226,140 @@ auto NetworkReader::RunThread() -> int {
Log("Error on select: " + g_platform->GetSocketErrorString());
}
} else {
int* fds[2] = {&sd4_, &sd6_};
// Wait for any data on either of our sockets.
for (auto& fd : fds) {
int& sd(*fd);
if (sd != -1) {
if (FD_ISSET(sd, &readset)) {
ssize_t rresult =
recvfrom(sd, buffer, sizeof(buffer), 0,
reinterpret_cast<sockaddr*>(&from), &from_size);
if (rresult == 0) {
Log("ERROR: NetworkReader Recv got length 0; this shouldn't "
"happen");
} else if (rresult == -1) {
for (int sd : {sd4_, sd6_}) {
if (sd == -1 || !(FD_ISSET(sd, &readset))) {
continue;
}
ssize_t rresult =
recvfrom(sd, buffer, sizeof(buffer), 0,
reinterpret_cast<sockaddr*>(&from), &from_size);
if (rresult == 0) {
Log("ERROR: NetworkReader Recv got length 0; this shouldn't "
"happen");
} else if (rresult == -1) {
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(sd_mutex_);
// If either of our sockets goes down lets close *both* of
// them.
if (sd4_ != -1) {
g_platform->CloseSocket(sd4_);
sd4_ = -1;
}
if (sd6_ != -1) {
g_platform->CloseSocket(sd6_);
sd6_ = -1;
}
} else {
assert(from_size >= 0);
auto rresult2{static_cast<size_t>(rresult)};
// If we get *any* data while paused, kill both our
// sockets (we ping ourself for this purpose).
if (paused_) {
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(sd_mutex_);
if (sd4_ != -1) {
g_platform->CloseSocket(sd4_);
sd4_ = -1;
}
if (sd6_ != -1) {
g_platform->CloseSocket(sd6_);
sd6_ = -1;
}
break;
}
switch (buffer[0]) {
case BA_PACKET_POKE:
break;
case BA_PACKET_SIMPLE_PING: {
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(sd_mutex_);
// If either of our sockets goes down lets close *both* of
// them.
if (sd4_ != -1) {
g_platform->CloseSocket(sd4_);
sd4_ = -1;
}
if (sd6_ != -1) {
g_platform->CloseSocket(sd6_);
sd6_ = -1;
}
} else {
assert(from_size >= 0);
auto rresult2 = static_cast<size_t>(rresult);
// If we get *any* data while paused, kill both our
// sockets (we ping ourself for this purpose).
if (paused_) {
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(sd_mutex_);
if (sd4_ != -1) {
g_platform->CloseSocket(sd4_);
sd4_ = -1;
}
if (sd6_ != -1) {
g_platform->CloseSocket(sd6_);
sd6_ = -1;
}
break;
}
switch (buffer[0]) {
case BA_PACKET_POKE:
break;
case BA_PACKET_SIMPLE_PING: {
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(sd_mutex_);
char msg[1] = {BA_PACKET_SIMPLE_PONG};
sendto(sd, msg, 1, 0, reinterpret_cast<sockaddr*>(&from),
from_size);
break;
}
case BA_PACKET_JSON_PING: {
if (rresult2 > 1) {
std::vector<char> s_buffer(rresult2);
memcpy(s_buffer.data(), buffer + 1, rresult2 - 1);
s_buffer[rresult2 - 1] = 0; // terminate string
std::string response = HandleJSONPing(s_buffer.data());
if (!response.empty()) {
std::vector<char> msg(1 + response.size());
msg[0] = BA_PACKET_JSON_PONG;
memcpy(msg.data() + 1, response.c_str(),
response.size());
std::lock_guard<std::mutex> lock(sd_mutex_);
sendto(sd, msg.data(),
static_cast_check_fit<socket_send_length_t>(
msg.size()),
0, reinterpret_cast<sockaddr*>(&from),
from_size);
}
}
break;
}
case BA_PACKET_JSON_PONG: {
if (rresult2 > 1) {
std::vector<char> s_buffer(rresult2);
memcpy(s_buffer.data(), buffer + 1, rresult2 - 1);
s_buffer[rresult2 - 1] = 0; // terminate string
cJSON* data = cJSON_Parse(s_buffer.data());
if (data != nullptr) {
cJSON_Delete(data);
}
}
break;
}
case BA_PACKET_REMOTE_PING:
case BA_PACKET_REMOTE_PONG:
case BA_PACKET_REMOTE_ID_REQUEST:
case BA_PACKET_REMOTE_ID_RESPONSE:
case BA_PACKET_REMOTE_DISCONNECT:
case BA_PACKET_REMOTE_STATE:
case BA_PACKET_REMOTE_STATE2:
case BA_PACKET_REMOTE_STATE_ACK:
case BA_PACKET_REMOTE_DISCONNECT_ACK:
case BA_PACKET_REMOTE_GAME_QUERY:
case BA_PACKET_REMOTE_GAME_RESPONSE:
// These packets are associated with the remote app; let the
// remote server handle them.
if (remote_server_) {
remote_server_->HandleData(
sd, reinterpret_cast<uint8_t*>(buffer), rresult2,
reinterpret_cast<sockaddr*>(&from),
static_cast<size_t>(from_size));
}
break;
case BA_PACKET_CLIENT_REQUEST:
case BA_PACKET_CLIENT_ACCEPT:
case BA_PACKET_CLIENT_DENY:
case BA_PACKET_CLIENT_DENY_ALREADY_IN_PARTY:
case BA_PACKET_CLIENT_DENY_VERSION_MISMATCH:
case BA_PACKET_CLIENT_DENY_PARTY_FULL:
case BA_PACKET_DISCONNECT_FROM_CLIENT_REQUEST:
case BA_PACKET_DISCONNECT_FROM_CLIENT_ACK:
case BA_PACKET_DISCONNECT_FROM_HOST_REQUEST:
case BA_PACKET_DISCONNECT_FROM_HOST_ACK:
case BA_PACKET_CLIENT_GAMEPACKET_COMPRESSED:
case BA_PACKET_HOST_GAMEPACKET_COMPRESSED: {
// These messages are associated with udp host/client
// connections.. pass them to the game thread to wrangle.
std::vector<uint8_t> msg_buffer(rresult2);
memcpy(&(msg_buffer[0]), buffer, rresult2);
g_game->connections()->PushUDPConnectionPacketCall(
msg_buffer, SockAddr(from));
break;
}
case BA_PACKET_GAME_QUERY: {
if (rresult2 == 5) {
// If we're already in a party, don't advertise since they
// wouldn't be able to join us anyway.
if (g_game->connections()->has_connection_to_host()) {
break;
}
// Pull the query id from the packet.
uint32_t query_id;
memcpy(&query_id, buffer + 1, 4);
// Ship them a response packet containing the query id,
// our protocol version, our unique-session-id, and our
// player_spec.
char msg[400];
std::string usid = GetAppInstanceUUID();
std::string player_spec_string;
// If we're signed in, send our account spec.
// Otherwise just send a dummy made with our device name.
player_spec_string =
PlayerSpec::GetAccountPlayerSpec().GetSpecString();
// This should always be the case (len needs to be 1 byte)
BA_PRECONDITION_FATAL(player_spec_string.size() < 256);
BA_PRECONDITION_FATAL(!usid.empty());
if (usid.size() > 100) {
Log("had to truncate session-id; shouldn't happen");
usid.resize(100);
}
if (usid.empty()) {
usid = "error";
}
msg[0] = BA_PACKET_GAME_QUERY_RESPONSE;
memcpy(msg + 1, &query_id, 4);
uint32_t protocol_version = kProtocolVersion;
memcpy(msg + 5, &protocol_version, 4);
msg[9] = static_cast<char>(usid.size());
msg[10] = static_cast<char>(player_spec_string.size());
memcpy(msg + 11, usid.c_str(), usid.size());
memcpy(msg + 11 + usid.size(), player_spec_string.c_str(),
player_spec_string.size());
size_t msg_len =
11 + player_spec_string.size() + usid.size();
BA_PRECONDITION_FATAL(msg_len <= sizeof(msg));
std::vector<uint8_t> msg_buffer(msg_len);
memcpy(msg_buffer.data(), msg, msg_len);
g_network_write_module->PushSendToCall(msg_buffer,
SockAddr(from));
break;
} else {
Log("Error: Got invalid game-query packet of len "
+ std::to_string(rresult2) + "; expected 5.");
}
break;
}
default:
break;
}
char msg[1] = {BA_PACKET_SIMPLE_PONG};
sendto(sd, msg, 1, 0, reinterpret_cast<sockaddr*>(&from),
from_size);
break;
}
case BA_PACKET_JSON_PING: {
if (rresult2 > 1) {
std::vector<char> s_buffer(rresult2);
memcpy(s_buffer.data(), buffer + 1, rresult2 - 1);
s_buffer[rresult2 - 1] = 0; // terminate string
std::string response = HandleJSONPing(s_buffer.data());
if (!response.empty()) {
std::vector<char> msg(1 + response.size());
msg[0] = BA_PACKET_JSON_PONG;
memcpy(msg.data() + 1, response.c_str(), response.size());
std::lock_guard<std::mutex> lock(sd_mutex_);
sendto(
sd, msg.data(),
static_cast_check_fit<socket_send_length_t>(msg.size()),
0, reinterpret_cast<sockaddr*>(&from), from_size);
}
}
break;
}
case BA_PACKET_JSON_PONG: {
if (rresult2 > 1) {
std::vector<char> s_buffer(rresult2);
memcpy(s_buffer.data(), buffer + 1, rresult2 - 1);
s_buffer[rresult2 - 1] = 0; // terminate string
cJSON* data = cJSON_Parse(s_buffer.data());
if (data != nullptr) {
cJSON_Delete(data);
}
}
break;
}
case BA_PACKET_REMOTE_PING:
case BA_PACKET_REMOTE_PONG:
case BA_PACKET_REMOTE_ID_REQUEST:
case BA_PACKET_REMOTE_ID_RESPONSE:
case BA_PACKET_REMOTE_DISCONNECT:
case BA_PACKET_REMOTE_STATE:
case BA_PACKET_REMOTE_STATE2:
case BA_PACKET_REMOTE_STATE_ACK:
case BA_PACKET_REMOTE_DISCONNECT_ACK:
case BA_PACKET_REMOTE_GAME_QUERY:
case BA_PACKET_REMOTE_GAME_RESPONSE:
// These packets are associated with the remote app; let the
// remote server handle them.
if (remote_server_) {
remote_server_->HandleData(
sd, reinterpret_cast<uint8_t*>(buffer), rresult2,
reinterpret_cast<sockaddr*>(&from),
static_cast<size_t>(from_size));
}
break;
case BA_PACKET_CLIENT_REQUEST:
case BA_PACKET_CLIENT_ACCEPT:
case BA_PACKET_CLIENT_DENY:
case BA_PACKET_CLIENT_DENY_ALREADY_IN_PARTY:
case BA_PACKET_CLIENT_DENY_VERSION_MISMATCH:
case BA_PACKET_CLIENT_DENY_PARTY_FULL:
case BA_PACKET_DISCONNECT_FROM_CLIENT_REQUEST:
case BA_PACKET_DISCONNECT_FROM_CLIENT_ACK:
case BA_PACKET_DISCONNECT_FROM_HOST_REQUEST:
case BA_PACKET_DISCONNECT_FROM_HOST_ACK:
case BA_PACKET_CLIENT_GAMEPACKET_COMPRESSED:
case BA_PACKET_HOST_GAMEPACKET_COMPRESSED: {
// These messages are associated with udp host/client
// connections.. pass them to the game thread to wrangle.
std::vector<uint8_t> msg_buffer(rresult2);
memcpy(&(msg_buffer[0]), buffer, rresult2);
g_game->connections()->PushUDPConnectionPacketCall(
msg_buffer, SockAddr(from));
break;
}
case BA_PACKET_GAME_QUERY: {
HandleGameQuery(buffer, rresult2, &from);
break;
}
default:
break;
}
}
}
@ -489,27 +376,135 @@ auto NetworkReader::RunThread() -> int {
}
}
NetworkReader::~NetworkReader() = default;
auto NetworkReader::OpenSockets() -> void {
// This needs to be locked during any socket-descriptor changes/writes.
std::lock_guard<std::mutex> lock(sd_mutex_);
auto NetworkReader::HandleJSONPing(const std::string& data_str) -> std::string {
cJSON* data = cJSON_Parse(data_str.c_str());
if (data == nullptr) {
return "";
}
cJSON_Delete(data);
int result;
int print_port_unavailable = false;
int initial_requested_port = port4_;
// Ok lets include some basic info that might be pertinent to someone pinging
// us. Currently that includes our current/max connection count.
char buffer[256];
int party_size = 0;
int party_size_max = 10;
if (g_python != nullptr) {
party_size = g_game->public_party_size();
party_size_max = g_game->public_party_max_size();
sd4_ = socket(AF_INET, SOCK_DGRAM, 0);
if (sd4_ < 0) {
Log("ERROR: Unable to open host socket; errno "
+ g_platform->GetSocketErrorString());
} else {
g_platform->SetSocketNonBlocking(sd4_);
// Bind to local server port.
struct sockaddr_in serv_addr {};
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // NOLINT
// Try our requested port for v4, then go with any available if that
// doesn't work.
serv_addr.sin_port = htons(port4_); // NOLINT
result = ::bind(sd4_, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if (result != 0) {
// If we're headless then we abort here; we're useless if we don't get
// the port we wanted.
if (HeadlessMode()) {
Log("FATAL ERROR: unable to bind to requested udp port "
+ std::to_string(port4_) + " (ipv4)");
exit(1);
}
// Primary ipv4 bind failed; try on any port as a backup.
print_port_unavailable = true;
serv_addr.sin_port = htons(0); // NOLINT
result = ::bind(sd4_, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
// Wuh oh; no ipv6 for us i guess.
if (result != 0) {
g_platform->CloseSocket(sd4_);
sd4_ = -1;
}
}
}
// See what v4 port we actually wound up with.
if (sd4_ != -1) {
struct sockaddr_in sa {};
socklen_t sa_len = sizeof(sa);
if (getsockname(sd4_, reinterpret_cast<sockaddr*>(&sa), &sa_len) == 0) {
port4_ = ntohs(sa.sin_port); // NOLINT
// Aim for a v6 port to match whatever we wound up with on the v4
// side.
port6_ = port4_;
}
}
// Ok now lets try to create an ipv6 socket on the same port.
// (its actually possible to just create a v6 socket and let the OSs
// dual-stack support provide v4 connectivity too, but that's not
// available everywhere (win XP, etc) so let's do this for now.
sd6_ = socket(AF_INET6, SOCK_DGRAM, 0);
if (sd6_ < 0) {
Log("ERROR: Unable to open ipv6 socket: "
+ g_platform->GetSocketErrorString());
} else {
// Since we're explicitly creating both a v4 and v6 socket, tell the v6
// to *not* do both itself (not sure if this is necessary; on mac it
// still seems to come up.. but apparently that's not always the case).
int on = 1;
if (setsockopt(sd6_, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<char*>(&on), sizeof(on))
== -1) {
Log("error setting socket as ipv6-only");
}
g_platform->SetSocketNonBlocking(sd6_);
struct sockaddr_in6 serv_addr {};
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin6_family = AF_INET6;
serv_addr.sin6_port = htons(port6_); // NOLINT
serv_addr.sin6_addr = in6addr_any;
result = ::bind(sd6_, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if (result != 0) {
if (HeadlessMode()) {
Log("FATAL ERROR: unable to bind to requested udp port "
+ std::to_string(port6_) + " (ipv6)");
exit(1);
}
// Primary ipv6 bind failed; try backup.
// We don't care if our random backup ports don't match; only if our
// target port failed.
if (port6_ == initial_requested_port) {
print_port_unavailable = true;
}
serv_addr.sin6_port = htons(0); // NOLINT
result = ::bind(sd6_, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if (result != 0) {
// Wuh oh; no ipv6 for us i guess.
g_platform->CloseSocket(sd6_);
sd6_ = -1;
}
}
}
// See what v6 port we actually wound up with.
if (sd6_ != -1) {
struct sockaddr_in sa {};
socklen_t sa_len = sizeof(sa);
if (getsockname(sd6_, reinterpret_cast<sockaddr*>(&sa), &sa_len) == 0) {
port6_ = ntohs(sa.sin_port); // NOLINT
}
}
if (print_port_unavailable) {
// FIXME - use translations here
ScreenMessage("Unable to bind udp port "
+ std::to_string(initial_requested_port)
+ "; some network functionality may fail.",
{1, 0.5f, 0});
Log("Unable to bind udp port " + std::to_string(initial_requested_port)
+ "; some network functionality may fail.",
true, false);
}
snprintf(buffer, sizeof(buffer), R"({"b":%d,"ps":%d,"psmx":%d})",
kAppBuildNumber, party_size, party_size_max);
return buffer;
}
NetworkReader::~NetworkReader() = default;
} // namespace ballistica

View File

@ -32,7 +32,7 @@ class NetworkReader {
auto sd6() const { return sd6_; }
private:
auto HandleJSONPing(const std::string& data) -> std::string;
auto OpenSockets() -> void;
auto PokeSelf() -> void;
auto RunThread() -> int;
static auto RunThreadStatic(void* self) -> int {