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

View File

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

View File

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

View File

@ -27,6 +27,8 @@
#include <array>
#include <boost/asio/ip/address.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 <zlib.h>
@ -81,7 +83,8 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
void TNetwork::UDPServerMain() {
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;
mUDPSock.open(UdpListenEndpoint.protocol(), ec);
if (ec) {
@ -89,6 +92,12 @@ void TNetwork::UDPServerMain() {
std::this_thread::sleep_for(std::chrono::seconds(5));
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);
if (ec) {
beammp_error("bind() failed: " + ec.message());
@ -100,8 +109,8 @@ void TNetwork::UDPServerMain() {
+ std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + (" Clients"));
while (!Application::IsShuttingDown()) {
try {
ip::udp::endpoint client {};
std::vector<uint8_t> Data = UDPRcvFromClient(client); // Receives any data from Socket
ip::udp::endpoint remote_client_ep {};
std::vector<uint8_t> Data = UDPRcvFromClient(remote_client_ep);
auto Pos = std::find(Data.begin(), Data.end(), ':');
if (Data.empty() || Pos > Data.begin() + 2)
continue;
@ -117,16 +126,31 @@ void TNetwork::UDPServerMain() {
}
if (Client->GetID() == ID) {
Client->SetUDPAddr(client);
Client->SetIsConnected(true);
// not initialized yet
if (Client->GetUDPAddr() == ip::udp::endpoint {} || !Client->IsUDPConnected()) {
// same IP (just a sanity check)
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;
});
} 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() {
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());
boost::system::error_code ec;
Listener.open(ListenEp.protocol(), ec);
@ -142,6 +169,16 @@ void TNetwork::TCPServerMain() {
beammp_errorf("Failed to open socket: {}", ec.message());
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 {};
LingerOpt.enabled(false);
Listener.set_option(LingerOpt, ec);
@ -170,13 +207,13 @@ void TNetwork::TCPServerMain() {
ip::tcp::endpoint ClientEp;
ip::tcp::socket ClientSocket = Acceptor.accept(ClientEp, 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 };
std::thread ID(&TNetwork::Identify, this, std::move(Conn));
ID.detach(); // TODO: Add to a queue and attempt to join periodically
} catch (const std::exception& e) {
beammp_error("fatal: " + std::string(e.what()));
beammp_errorf("Exception in accept routine: {}", e.what());
}
} while (!Application::IsShuttingDown());
}
@ -257,8 +294,14 @@ std::string HashPassword(const std::string& str) {
std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
auto Client = CreateClient(std::move(RawConnection.Socket));
Client->SetIdentifier("ip", RawConnection.SockAddr.address().to_string());
beammp_tracef("This thread is ip {}", RawConnection.SockAddr.address().to_string());
std::string ip = "";
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...");
@ -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) {
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
// 1. not yet fully connected, or
// 2. disconnected and not yet fully removed