Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions include/Network/network.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#pragma once
#include <filesystem>
#include <string>
#include <unordered_map>
#include <unordered_set>

#ifdef __linux__
#include "linuxfixes.h"
Expand Down Expand Up @@ -56,3 +58,9 @@ void UDPClientMain(const std::string& IP, int Port);
void TCPGameServer(const std::string& IP, int Port);
bool SecurityWarning();
void CoreSend(std::string data);
void ServerSend(std::string Data, bool Rel);
extern uint64_t DVSock;
extern std::unordered_set<std::string> activeVehicles; // set of active serverVehicleIDs for checking if direct vehicle sockets are valid
extern std::unordered_map<std::string, int> vehiclePortMap; // maps a serverVehicleID to the port of its vehicle socket
void DVSend(std::string_view Data, int Port);
void DVClientMain(const std::string& IP, int Port);
21 changes: 21 additions & 0 deletions src/Network/Core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,27 @@ void Parse(std::string Data, SOCKET CSocket) {
});
break;
}
case 'V': //register active vehicles for direct vehicle sockets
if (Data.length() < 3) {
debug("(Core) Failed to parse serverVehicleID from data: " + Data);
Data.clear();
break;
}

if (SubCode == 'a') {
std::string serverVehicleID = Data.substr(3);
debug("(Core) Registering vehicle: " + serverVehicleID);
activeVehicles.insert(serverVehicleID);
} else if (SubCode == 'd') {
std::string serverVehicleID = Data.substr(3);
debug("(Core) Unregistering vehicle: " + serverVehicleID);
activeVehicles.erase(serverVehicleID);
vehiclePortMap.erase(serverVehicleID);
} else {
debug("(Core) Invalid V packet SubCode: " + SubCode);
}
Data.clear();
break;
default:
Data.clear();
break;
Expand Down
125 changes: 125 additions & 0 deletions src/Network/DirectVehicleConnection.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
Copyright (C) 2026 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/

#include "Network/network.hpp"
#include <stdexcept>

#if defined(_WIN32)
#include <ws2tcpip.h>
#elif defined(__linux__)
#include "linuxfixes.h"
#include <arpa/inet.h>
#include <cstring>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#endif

#include "Logger.h"
#include <array>
#include <string>

SOCKET DVSock = -1;
sockaddr_in ToVehicle;
std::unordered_set<std::string> activeVehicles;
std::unordered_map<std::string, int> vehiclePortMap;

void DVSend(std::string_view Data, int Port) {
if (DVSock == -1)
return;
ToVehicle.sin_port = htons(Port);
int sendOk = sendto(DVSock, Data.data(), int(Data.size()), 0, (sockaddr*)&ToVehicle, sizeof(ToVehicle));
if (sendOk == SOCKET_ERROR)
error("(Direct VE) Failed to send data. Error Code : " + std::to_string(WSAGetLastError()));
}

void DVRcv() {
sockaddr_in FromVehicle;
socklen_t size = sizeof(FromVehicle);
ZeroMemory(&FromVehicle, size);
static thread_local std::array<char, 10240> Ret {};
if (DVSock == -1)
return;
int32_t Rcv = recvfrom(DVSock, Ret.data(), Ret.size() - 1, 0, (sockaddr*)&FromVehicle, &size);
if (Rcv == SOCKET_ERROR)
return;
Ret[Rcv] = 0;

std::string Data = std::string(Ret.data(), Rcv);
size_t first = Data.find(':');
if (first == std::string::npos) {
debug("(Direct VE) Failed to parse serverVehicleID from data: " + Data);
return;
}
first += 1;
size_t len = Data.find(':', first);
if (len != std::string::npos) {
len -= first;
}
std::string serverVehicleID = Data.substr(first, len);
if (activeVehicles.contains(serverVehicleID)) {
int port = ntohs(FromVehicle.sin_port);
auto portIter = vehiclePortMap.find(serverVehicleID);
if (portIter != vehiclePortMap.end()) {
if (portIter->second == port) {
ServerSend(Data, false);
} else {
debug("(Direct VE) Received data for vehicle " + serverVehicleID + " from wrong port: " + std::to_string(port) + " != " + std::to_string(portIter->second));
}
} else {
debug("(Direct VE) Registering port for vehicle " + serverVehicleID + ": " + std::to_string(port));
vehiclePortMap.insert({ serverVehicleID, port });

ServerSend(Data, false);
}
} else {
debug("(Direct VE) Received data from unregistered vehicle: " + serverVehicleID);
}
}

void DVClientMain(const std::string& IP, int Port) {
debug("(Direct VE) Starting direct vehicle client on adress " + IP + ":" + std::to_string(Port));

#ifdef _WIN32
WSADATA data;
if (WSAStartup(514, &data)) {
error("(Direct VE) Can't start Winsock!");
return;
}
#endif
sockaddr_in DVListenAddr;
ZeroMemory(&DVListenAddr, sizeof(DVListenAddr));
DVListenAddr.sin_family = AF_INET;
DVListenAddr.sin_port = htons(Port);
inet_pton(AF_INET, IP.c_str(), &DVListenAddr.sin_addr);

ZeroMemory(&ToVehicle, sizeof(ToVehicle));
ToVehicle.sin_family = AF_INET;
ToVehicle.sin_addr = DVListenAddr.sin_addr;

DVSock = socket(AF_INET, SOCK_DGRAM, 0);
if (DVSock == INVALID_SOCKET) {
error("(Direct VE) Socket creation failed with error: " + std::to_string(WSAGetLastError()));
KillSocket(DVSock);
WSACleanup();
return;
}
if (bind(DVSock, (const sockaddr*)&DVListenAddr, sizeof(DVListenAddr)) == SOCKET_ERROR) {
error("(Direct VE) Socket bind failed with error: " + std::to_string(WSAGetLastError()));
KillSocket(DVSock);
WSACleanup();
return;
}
debug("(Direct VE) Starting direct vehicle receive loop");
while (!TCPTerminate) {
DVRcv();
}
debug("(Direct VE) Direct vehicle receive loop done");
KillSocket(DVSock);
WSACleanup();
activeVehicles.clear();
vehiclePortMap.clear();
}
46 changes: 45 additions & 1 deletion src/Network/GlobalHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ void NetReset() {
KillSocket(GSocket);
}
GSocket = -1;
if (DVSock != (SOCKET)(-1)) {
debug("Terminating direct vehicle Socket: " + std::to_string(DVSock));
KillSocket(DVSock);
}
DVSock = -1;
}

SOCKET SetupListener() {
Expand Down Expand Up @@ -189,6 +194,7 @@ int ClientID = -1;
void ParserAsync(std::string_view Data) {
if (Data.empty())
return;
bool tryDirectVehicleSocket = false;
char Code = Data.at(0), SubCode = 0;
if (Data.length() > 1)
SubCode = Data.at(1);
Expand All @@ -206,10 +212,41 @@ void ParserAsync(std::string_view Data) {
return;
case 'U':
magic = Data.substr(1);
break;
case 'R': //controller sync
case 'W': //electrics
case 'V': //inputs
case 'Y': //powertrain
case 'X': //nodes
case 'Z': //position
tryDirectVehicleSocket = true;
break;
default:
break;
}
GameSend(Data);
if (tryDirectVehicleSocket) {
size_t first = Data.find(':');
if (first == std::string::npos) {
GameSend(Data);
return;
}
first += 1;
size_t len = Data.find(':', first);
if (len != std::string::npos) {
len -= first;
}
std::string serverVehicleID = std::string(Data.substr(first, len));
auto portIter = vehiclePortMap.find(serverVehicleID);
if (portIter != vehiclePortMap.end()) {
DVSend(Data, portIter->second);
}
else {
GameSend(Data);
}
}
else {
GameSend(Data);
}
}
void ServerParser(std::string_view Data) {
ParserAsync(Data);
Expand All @@ -226,6 +263,7 @@ void TCPGameServer(const std::string& IP, int Port) {
GSocket = SetupListener();
std::unique_ptr<std::thread> ClientThread {};
std::unique_ptr<std::thread> NetMainThread {};
std::unique_ptr<std::thread> DirectVehicleThread {};
while (!TCPTerminate && GSocket != -1) {
debug("MAIN LOOP OF GAME SERVER");
GConnected = false;
Expand All @@ -248,6 +286,7 @@ void TCPGameServer(const std::string& IP, int Port) {
GConnected = true;
if (CServer) {
NetMainThread = std::make_unique<std::thread>(NetMain, IP, Port);
DirectVehicleThread = std::make_unique<std::thread>(DVClientMain, "127.0.0.1", options.port + 2);
CServer = false;
}
int32_t Size, Rcv;
Expand Down Expand Up @@ -280,6 +319,11 @@ void TCPGameServer(const std::string& IP, int Port) {
NetMainThread->join();
debug("Net main thread done");
}
if (DirectVehicleThread) {
debug("Waiting for direct vehicle thread");
DirectVehicleThread->join();
debug("Direct vehicle thread done");
}
if (CSocket != SOCKET_ERROR)
KillSocket(CSocket);
debug("END OF GAME SERVER");
Expand Down
Loading