From b90d0fa1cd616be5d3fc1d59f0a0c4e7c2d2f828 Mon Sep 17 00:00:00 2001 From: Encritary Date: Sun, 6 Jul 2025 14:14:08 +0300 Subject: [PATCH] Implement RakNet protocol version handling --- src/generic/Session.php | 8 ++++++ src/server/PendingConnection.php | 36 ++++++++++++++++++++++++ src/server/Server.php | 7 +++-- src/server/ServerEventListener.php | 2 +- src/server/ServerSession.php | 3 +- src/server/UnconnectedMessageHandler.php | 23 ++++++++++++++- 6 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 src/server/PendingConnection.php diff --git a/src/generic/Session.php b/src/generic/Session.php index 2235d15e..1bba29aa 100644 --- a/src/generic/Session.php +++ b/src/generic/Session.php @@ -53,6 +53,8 @@ abstract class Session{ private int $id; + private int $protocol; + private float $lastUpdate; private float $disconnectionTime = 0; @@ -75,6 +77,7 @@ public function __construct( InternetAddress $address, int $clientId, int $mtuSize, + int $protocol, int $recvMaxSplitParts = PHP_INT_MAX, int $recvMaxConcurrentSplits = PHP_INT_MAX ){ @@ -84,6 +87,7 @@ public function __construct( $this->logger = new \PrefixedLogger($logger, "Session: " . $address->toString()); $this->address = $address; $this->id = $clientId; + $this->protocol = $protocol; $this->lastUpdate = microtime(true); @@ -167,6 +171,10 @@ public function getID() : int{ return $this->id; } + public function getProtocol() : int{ + return $this->protocol; + } + public function getState() : int{ return $this->state; } diff --git a/src/server/PendingConnection.php b/src/server/PendingConnection.php new file mode 100644 index 00000000..b3449946 --- /dev/null +++ b/src/server/PendingConnection.php @@ -0,0 +1,36 @@ + + * + * RakLib is not affiliated with Jenkins Software LLC nor RakNet. + * + * RakLib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +declare(strict_types=1); + +namespace raklib\server; + +class PendingConnection{ + + private float $createdAt; + + public function __construct( + private int $protocol + ){ + $this->createdAt = microtime(true); + } + + public function getProtocol() : int{ + return $this->protocol; + } + + public function getCreatedAt() : float{ + return $this->createdAt; + } +} diff --git a/src/server/Server.php b/src/server/Server.php index f9c3fb5c..b8af5b47 100644 --- a/src/server/Server.php +++ b/src/server/Server.php @@ -182,6 +182,7 @@ private function tick() : void{ $this->removeSessionInternal($session); } } + $this->unconnectedMessageHandler->update($time); $this->ipSec = []; @@ -410,7 +411,7 @@ public function sessionExists(InternetAddress $address) : bool{ return isset($this->sessionsByAddress[$address->toString()]); } - public function createSession(InternetAddress $address, int $clientId, int $mtuSize) : ServerSession{ + public function createSession(InternetAddress $address, int $clientId, int $mtuSize, int $protocol) : ServerSession{ $existingSession = $this->sessionsByAddress[$address->toString()] ?? null; if($existingSession !== null){ $existingSession->forciblyDisconnect(DisconnectReason::CLIENT_RECONNECT); @@ -424,7 +425,7 @@ public function createSession(InternetAddress $address, int $clientId, int $mtuS $this->nextSessionId &= 0x7fffffff; //we don't expect more than 2 billion simultaneous connections, and this fits in 4 bytes } - $session = new ServerSession($this, $this->logger, clone $address, $clientId, $mtuSize, $this->nextSessionId, $this->recvMaxSplitParts, $this->recvMaxConcurrentSplits); + $session = new ServerSession($this, $this->logger, clone $address, $clientId, $mtuSize, $protocol, $this->nextSessionId, $this->recvMaxSplitParts, $this->recvMaxConcurrentSplits); $this->sessionsByAddress[$address->toString()] = $session; $this->sessions[$this->nextSessionId] = $session; $this->logger->debug("Created session for $address with MTU size $mtuSize"); @@ -438,7 +439,7 @@ private function removeSessionInternal(ServerSession $session) : void{ public function openSession(ServerSession $session) : void{ $address = $session->getAddress(); - $this->eventListener->onClientConnect($session->getInternalId(), $address->getIp(), $address->getPort(), $session->getID()); + $this->eventListener->onClientConnect($session->getInternalId(), $address->getIp(), $address->getPort(), $session->getID(), $session->getProtocol()); } private function checkSessions() : void{ diff --git a/src/server/ServerEventListener.php b/src/server/ServerEventListener.php index 9e2d4d4b..7e72206d 100644 --- a/src/server/ServerEventListener.php +++ b/src/server/ServerEventListener.php @@ -20,7 +20,7 @@ interface ServerEventListener{ - public function onClientConnect(int $sessionId, string $address, int $port, int $clientID) : void; + public function onClientConnect(int $sessionId, string $address, int $port, int $clientID, int $protocol) : void; /** * @param int $reason one of the DisconnectReason constants diff --git a/src/server/ServerSession.php b/src/server/ServerSession.php index 13e29184..a85eb04e 100644 --- a/src/server/ServerSession.php +++ b/src/server/ServerSession.php @@ -40,13 +40,14 @@ public function __construct( InternetAddress $address, int $clientId, int $mtuSize, + int $protocol, int $internalId, int $recvMaxSplitParts = self::DEFAULT_MAX_SPLIT_PART_COUNT, int $recvMaxConcurrentSplits = self::DEFAULT_MAX_CONCURRENT_SPLIT_COUNT ){ $this->server = $server; $this->internalId = $internalId; - parent::__construct($logger, $address, $clientId, $mtuSize, $recvMaxSplitParts, $recvMaxConcurrentSplits); + parent::__construct($logger, $address, $clientId, $mtuSize, $protocol, $recvMaxSplitParts, $recvMaxConcurrentSplits); } /** diff --git a/src/server/UnconnectedMessageHandler.php b/src/server/UnconnectedMessageHandler.php index 2ab7badb..2225fb1f 100644 --- a/src/server/UnconnectedMessageHandler.php +++ b/src/server/UnconnectedMessageHandler.php @@ -42,6 +42,8 @@ class UnconnectedMessageHandler{ * @phpstan-var \SplFixedArray */ private \SplFixedArray $packetPool; + /** @var PendingConnection[] */ + private array $pendingConnections = []; public function __construct( private Server $server, @@ -81,6 +83,8 @@ private function handle(OfflineMessage $packet, InternetAddress $address) : bool $this->server->sendPacket(IncompatibleProtocolVersion::create($this->protocolAcceptor->getPrimaryVersion(), $this->server->getID()), $address); $this->server->getLogger()->notice("Refused connection from $address due to incompatible RakNet protocol version (version $packet->protocol)"); }else{ + $this->pendingConnections[$address->toString()] = new PendingConnection($packet->protocol); + //IP header size (20 bytes) + UDP header size (8 bytes) $this->server->sendPacket(OpenConnectionReply1::create($this->server->getID(), false, $packet->mtuSize + 28), $address); } @@ -97,9 +101,17 @@ private function handle(OfflineMessage $packet, InternetAddress $address) : bool $this->server->getLogger()->debug("Not creating session for $address due to session already opened"); return true; } + if(!isset($this->pendingConnections[$address->toString()])){ + // we should always except OpenConnectionRequest1 before OpenConnectionRequeqst2 + $this->server->getLogger()->debug("Not creating session for $address due to no pending connection");; + return true; + } + $pendingConnection = $this->pendingConnections[$address->toString()]; + unset($this->pendingConnections[$address->toString()]); + $mtuSize = min($packet->mtuSize, $this->server->getMaxMtuSize()); //Max size, do not allow creating large buffers to fill server memory $this->server->sendPacket(OpenConnectionReply2::create($this->server->getID(), $address, $mtuSize, false), $address); - $this->server->createSession($address, $packet->clientID, $mtuSize); + $this->server->createSession($address, $packet->clientID, $mtuSize, $pendingConnection->getProtocol()); }else{ $this->server->getLogger()->debug("Not creating session for $address due to mismatched port, expected " . $this->server->getPort() . ", got " . $packet->serverAddress->getPort()); } @@ -110,6 +122,15 @@ private function handle(OfflineMessage $packet, InternetAddress $address) : bool return true; } + public function update(float $time) : void{ + foreach($this->pendingConnections as $address => $pendingConnection){ + if($pendingConnection->getCreatedAt() + 10 < $time){ + $this->server->getLogger()->debug("Timed out pending connection from $address"); + unset($this->pendingConnections[$address]); + } + } + } + /** * @phpstan-param class-string $class */