diff --git a/.efrocachemap b/.efrocachemap index f8f00ed5..850ae930 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -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", diff --git a/CHANGELOG.md b/CHANGELOG.md index 82d3a7e8..5276555f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/src/ballistica/ballistica.cc b/src/ballistica/ballistica.cc index 8d4ab157..95680479 100644 --- a/src/ballistica/ballistica.cc +++ b/src/ballistica/ballistica.cc @@ -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. diff --git a/src/ballistica/game/connection/connection_set.cc b/src/ballistica/game/connection/connection_set.cc index c5592d39..08ccaacb 100644 --- a/src/ballistica/game/connection/connection_set.cc +++ b/src/ballistica/game/connection/connection_set.cc @@ -453,9 +453,9 @@ auto ConnectionSet::UDPConnectionPacket(const std::vector& 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); diff --git a/src/ballistica/networking/network_reader.cc b/src/ballistica/networking/network_reader.cc index 14334ee5..ba1655f9 100644 --- a/src/ballistica/networking/network_reader.cc +++ b/src/ballistica/networking/network_reader.cc @@ -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(usid.size()); + msg[10] = static_cast(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 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(); @@ -96,139 +178,11 @@ auto NetworkReader::RunThread() -> int { std::unique_lock lock(paused_mutex_); paused_cv_.wait(lock, [this] { return (!paused_); }); } - { - // This needs to be locked during any socket-descriptor changes/writes. - std::lock_guard 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(&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(&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(&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(&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(&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 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(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 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 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(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 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 lock(sd_mutex_); - char msg[1] = {BA_PACKET_SIMPLE_PONG}; - sendto(sd, msg, 1, 0, reinterpret_cast(&from), - from_size); - break; - } - case BA_PACKET_JSON_PING: { - if (rresult2 > 1) { - std::vector 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 msg(1 + response.size()); - msg[0] = BA_PACKET_JSON_PONG; - memcpy(msg.data() + 1, response.c_str(), - response.size()); - std::lock_guard lock(sd_mutex_); - sendto(sd, msg.data(), - static_cast_check_fit( - msg.size()), - 0, reinterpret_cast(&from), - from_size); - } - } - break; - } - case BA_PACKET_JSON_PONG: { - if (rresult2 > 1) { - std::vector 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(buffer), rresult2, - reinterpret_cast(&from), - static_cast(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 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(usid.size()); - msg[10] = static_cast(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 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(&from), + from_size); + break; } + case BA_PACKET_JSON_PING: { + if (rresult2 > 1) { + std::vector 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 msg(1 + response.size()); + msg[0] = BA_PACKET_JSON_PONG; + memcpy(msg.data() + 1, response.c_str(), response.size()); + std::lock_guard lock(sd_mutex_); + sendto( + sd, msg.data(), + static_cast_check_fit(msg.size()), + 0, reinterpret_cast(&from), from_size); + } + } + break; + } + case BA_PACKET_JSON_PONG: { + if (rresult2 > 1) { + std::vector 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(buffer), rresult2, + reinterpret_cast(&from), + static_cast(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 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 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(&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(&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(&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 diff --git a/src/ballistica/networking/network_reader.h b/src/ballistica/networking/network_reader.h index e6ff0bd5..a21b1ab6 100644 --- a/src/ballistica/networking/network_reader.h +++ b/src/ballistica/networking/network_reader.h @@ -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 {