Add IPv6 support (#349)

Adds IPv6 support.

FreeBSD users beware: From this point forward, you will have to set
`sysctl net.inet6.ip6.v6only=0` so that the BeamMP-Server continues to
work. The server also prints this information.
This commit is contained in:
SaltySnail 2024-08-20 20:35:48 +02:00 committed by GitHub
commit f5f6b8534d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 62 additions and 19 deletions

View File

@ -88,7 +88,7 @@ public:
void ClearCars(); void ClearCars();
[[nodiscard]] int GetID() const { return mID; } [[nodiscard]] int GetID() const { return mID; }
[[nodiscard]] int GetUnicycleID() const { return mUnicycleID; } [[nodiscard]] int GetUnicycleID() const { return mUnicycleID; }
[[nodiscard]] bool IsConnected() const { return mIsConnected; } [[nodiscard]] bool IsUDPConnected() const { return mIsUDPConnected; }
[[nodiscard]] bool IsSynced() const { return mIsSynced; } [[nodiscard]] bool IsSynced() const { return mIsSynced; }
[[nodiscard]] bool IsSyncing() const { return mIsSyncing; } [[nodiscard]] bool IsSyncing() const { return mIsSyncing; }
[[nodiscard]] bool IsGuest() const { return mIsGuest; } [[nodiscard]] bool IsGuest() const { return mIsGuest; }
@ -100,7 +100,7 @@ public:
[[nodiscard]] const std::queue<std::vector<uint8_t>>& MissedPacketQueue() const { return mPacketsSync; } [[nodiscard]] const std::queue<std::vector<uint8_t>>& MissedPacketQueue() const { return mPacketsSync; }
[[nodiscard]] size_t MissedPacketQueueSize() const { return mPacketsSync.size(); } [[nodiscard]] size_t MissedPacketQueueSize() const { return mPacketsSync.size(); }
[[nodiscard]] std::mutex& MissedPacketQueueMutex() const { return mMissedPacketsMutex; } [[nodiscard]] std::mutex& MissedPacketQueueMutex() const { return mMissedPacketsMutex; }
void SetIsConnected(bool NewIsConnected) { mIsConnected = NewIsConnected; } void SetIsUDPConnected(bool NewIsConnected) { mIsUDPConnected = NewIsConnected; }
[[nodiscard]] TServer& Server() const; [[nodiscard]] TServer& Server() const;
void UpdatePingTime(); void UpdatePingTime();
int SecondsSinceLastPing(); int SecondsSinceLastPing();
@ -109,7 +109,7 @@ private:
void InsertVehicle(int ID, const std::string& Data); void InsertVehicle(int ID, const std::string& Data);
TServer& mServer; TServer& mServer;
bool mIsConnected = false; bool mIsUDPConnected = false;
bool mIsSynced = false; bool mIsSynced = false;
bool mIsSyncing = false; bool mIsSyncing = false;
mutable std::mutex mMissedPacketsMutex; mutable std::mutex mMissedPacketsMutex;

View File

@ -298,7 +298,7 @@ void LuaAPI::MP::Sleep(size_t Ms) {
bool LuaAPI::MP::IsPlayerConnected(int ID) { bool LuaAPI::MP::IsPlayerConnected(int ID) {
auto MaybeClient = GetClient(Engine->Server(), ID); auto MaybeClient = GetClient(Engine->Server(), ID);
if (MaybeClient && !MaybeClient.value().expired()) { if (MaybeClient && !MaybeClient.value().expired()) {
return MaybeClient.value().lock()->IsConnected(); return MaybeClient.value().lock()->IsUDPConnected();
} else { } else {
return false; return false;
} }

View File

@ -543,7 +543,7 @@ void TConsole::Command_Status(const std::string&, const std::vector<std::string>
if (!Client.expired()) { if (!Client.expired()) {
auto Locked = Client.lock(); auto Locked = Client.lock();
CarCount += Locked->GetCarCount(); CarCount += Locked->GetCarCount();
ConnectedCount += Locked->IsConnected() ? 1 : 0; ConnectedCount += Locked->IsUDPConnected() ? 1 : 0;
GuestCount += Locked->IsGuest() ? 1 : 0; GuestCount += Locked->IsGuest() ? 1 : 0;
SyncedCount += Locked->IsSynced() ? 1 : 0; SyncedCount += Locked->IsSynced() ? 1 : 0;
SyncingCount += Locked->IsSyncing() ? 1 : 0; SyncingCount += Locked->IsSyncing() ? 1 : 0;

View File

@ -27,6 +27,8 @@
#include <array> #include <array>
#include <boost/asio/ip/address.hpp> #include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/address_v4.hpp> #include <boost/asio/ip/address_v4.hpp>
#include <boost/asio/ip/address_v6.hpp>
#include <boost/asio/ip/v6_only.hpp>
#include <cstring> #include <cstring>
#include <zlib.h> #include <zlib.h>
@ -81,7 +83,8 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
void TNetwork::UDPServerMain() { void TNetwork::UDPServerMain() {
RegisterThread("UDPServer"); RegisterThread("UDPServer");
ip::udp::endpoint UdpListenEndpoint(ip::address::from_string("0.0.0.0"), Application::Settings.getAsInt(Settings::Key::General_Port)); // listen on all ipv6 addresses
ip::udp::endpoint UdpListenEndpoint(ip::address::from_string("::"), Application::Settings.getAsInt(Settings::Key::General_Port));
boost::system::error_code ec; boost::system::error_code ec;
mUDPSock.open(UdpListenEndpoint.protocol(), ec); mUDPSock.open(UdpListenEndpoint.protocol(), ec);
if (ec) { if (ec) {
@ -89,6 +92,12 @@ void TNetwork::UDPServerMain() {
std::this_thread::sleep_for(std::chrono::seconds(5)); std::this_thread::sleep_for(std::chrono::seconds(5));
Application::GracefullyShutdown(); Application::GracefullyShutdown();
} }
// set IP_V6ONLY to false to allow both v4 and v6
boost::asio::ip::v6_only option(false);
mUDPSock.set_option(option, ec);
if (ec) {
beammp_warnf("Failed to unset IP_V6ONLY on UDP, only IPv6 will work: {}", ec.message());
}
mUDPSock.bind(UdpListenEndpoint, ec); mUDPSock.bind(UdpListenEndpoint, ec);
if (ec) { if (ec) {
beammp_error("bind() failed: " + ec.message()); beammp_error("bind() failed: " + ec.message());
@ -100,8 +109,8 @@ void TNetwork::UDPServerMain() {
+ std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + (" Clients")); + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + (" Clients"));
while (!Application::IsShuttingDown()) { while (!Application::IsShuttingDown()) {
try { try {
ip::udp::endpoint client {}; ip::udp::endpoint remote_client_ep {};
std::vector<uint8_t> Data = UDPRcvFromClient(client); // Receives any data from Socket std::vector<uint8_t> Data = UDPRcvFromClient(remote_client_ep);
auto Pos = std::find(Data.begin(), Data.end(), ':'); auto Pos = std::find(Data.begin(), Data.end(), ':');
if (Data.empty() || Pos > Data.begin() + 2) if (Data.empty() || Pos > Data.begin() + 2)
continue; continue;
@ -117,16 +126,31 @@ void TNetwork::UDPServerMain() {
} }
if (Client->GetID() == ID) { if (Client->GetID() == ID) {
Client->SetUDPAddr(client); // not initialized yet
Client->SetIsConnected(true); if (Client->GetUDPAddr() == ip::udp::endpoint {} || !Client->IsUDPConnected()) {
Data.erase(Data.begin(), Data.begin() + 2); // same IP (just a sanity check)
mServer.GlobalParser(ClientPtr, std::move(Data), mPPSMonitor, *this); if (remote_client_ep.address() == Client->GetTCPSock().remote_endpoint().address()) {
Client->SetUDPAddr(remote_client_ep);
Client->SetIsUDPConnected(true);
beammp_debugf("UDP connected for client {}", ID);
} else {
beammp_debugf("Denied initial UDP packet due to IP mismatch");
return false;
}
}
if (Client->GetUDPAddr() == remote_client_ep) {
Data.erase(Data.begin(), Data.begin() + 2);
mServer.GlobalParser(ClientPtr, std::move(Data), mPPSMonitor, *this);
} else {
beammp_debugf("Ignored UDP packet due to remote address mismatch");
return false;
}
} }
return true; return true;
}); });
} catch (const std::exception& e) { } catch (const std::exception& e) {
beammp_error(("fatal: ") + std::string(e.what())); beammp_warnf("Failed to receive/parse packet via UDP: {}", e.what());
} }
} }
} }
@ -134,7 +158,10 @@ void TNetwork::UDPServerMain() {
void TNetwork::TCPServerMain() { void TNetwork::TCPServerMain() {
RegisterThread("TCPServer"); RegisterThread("TCPServer");
ip::tcp::endpoint ListenEp(ip::address::from_string("0.0.0.0"), Application::Settings.getAsInt(Settings::Key::General_Port)); // listen on all ipv6 addresses
auto port = uint16_t(Application::Settings.getAsInt(Settings::Key::General_Port));
ip::tcp::endpoint ListenEp(ip::address::from_string("::"), port);
beammp_infof("Listening on 0.0.0.0:{0} and [::]:{0}", port);
ip::tcp::socket Listener(mServer.IoCtx()); ip::tcp::socket Listener(mServer.IoCtx());
boost::system::error_code ec; boost::system::error_code ec;
Listener.open(ListenEp.protocol(), ec); Listener.open(ListenEp.protocol(), ec);
@ -142,6 +169,16 @@ void TNetwork::TCPServerMain() {
beammp_errorf("Failed to open socket: {}", ec.message()); beammp_errorf("Failed to open socket: {}", ec.message());
return; return;
} }
// set IP_V6ONLY to false to allow both v4 and v6
boost::asio::ip::v6_only option(false);
Listener.set_option(option, ec);
if (ec) {
beammp_warnf("Failed to unset IP_V6ONLY on TCP, only IPv6 will work: {}", ec.message());
}
#if defined(BEAMMP_FREEBSD)
beammp_warnf("WARNING: On FreeBSD, for IPv4 to work, you must run `sysctl net.inet6.ip6.v6only=0`!");
beammp_debugf("This is due to an annoying detail in the *BSDs: In the name of security, unsetting the IPV6_V6ONLY option does not work by default (but does not fail???), as it allows IPv4 mapped IPv6 like ::ffff:127.0.0.1, which they deem a security issue. For more information, see RFC 2553, section 3.7.");
#endif
socket_base::linger LingerOpt {}; socket_base::linger LingerOpt {};
LingerOpt.enabled(false); LingerOpt.enabled(false);
Listener.set_option(LingerOpt, ec); Listener.set_option(LingerOpt, ec);
@ -170,13 +207,13 @@ void TNetwork::TCPServerMain() {
ip::tcp::endpoint ClientEp; ip::tcp::endpoint ClientEp;
ip::tcp::socket ClientSocket = Acceptor.accept(ClientEp, ec); ip::tcp::socket ClientSocket = Acceptor.accept(ClientEp, ec);
if (ec) { if (ec) {
beammp_errorf("failed to accept: {}", ec.message()); beammp_errorf("Failed to accept() new client: {}", ec.message());
} }
TConnection Conn { std::move(ClientSocket), ClientEp }; TConnection Conn { std::move(ClientSocket), ClientEp };
std::thread ID(&TNetwork::Identify, this, std::move(Conn)); std::thread ID(&TNetwork::Identify, this, std::move(Conn));
ID.detach(); // TODO: Add to a queue and attempt to join periodically ID.detach(); // TODO: Add to a queue and attempt to join periodically
} catch (const std::exception& e) { } catch (const std::exception& e) {
beammp_error("fatal: " + std::string(e.what())); beammp_errorf("Exception in accept routine: {}", e.what());
} }
} while (!Application::IsShuttingDown()); } while (!Application::IsShuttingDown());
} }
@ -257,8 +294,14 @@ std::string HashPassword(const std::string& str) {
std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) { std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
auto Client = CreateClient(std::move(RawConnection.Socket)); auto Client = CreateClient(std::move(RawConnection.Socket));
Client->SetIdentifier("ip", RawConnection.SockAddr.address().to_string()); std::string ip = "";
beammp_tracef("This thread is ip {}", RawConnection.SockAddr.address().to_string()); if (RawConnection.SockAddr.address().to_v6().is_v4_mapped()) {
ip = boost::asio::ip::make_address_v4(ip::v4_mapped_t::v4_mapped, RawConnection.SockAddr.address().to_v6()).to_string();
} else {
ip = RawConnection.SockAddr.address().to_string();
}
Client->SetIdentifier("ip", ip);
beammp_tracef("This thread is ip {} ({})", ip, RawConnection.SockAddr.address().to_v6().is_v4_mapped() ? "IPv4 mapped IPv6" : "IPv6");
beammp_info("Identifying new ClientConnection..."); beammp_info("Identifying new ClientConnection...");
@ -978,7 +1021,7 @@ void TNetwork::SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self
} }
bool TNetwork::UDPSend(TClient& Client, std::vector<uint8_t> Data) { bool TNetwork::UDPSend(TClient& Client, std::vector<uint8_t> Data) {
if (!Client.IsConnected() || Client.IsDisconnected()) { if (!Client.IsUDPConnected() || Client.IsDisconnected()) {
// this can happen if we try to send a packet to a client that is either // this can happen if we try to send a packet to a client that is either
// 1. not yet fully connected, or // 1. not yet fully connected, or
// 2. disconnected and not yet fully removed // 2. disconnected and not yet fully removed