From eb7f4930e218e5f7588cc24b88a15ac34a5c5420 Mon Sep 17 00:00:00 2001 From: DcruBro Date: Tue, 2 Dec 2025 16:25:42 +0100 Subject: [PATCH] Test dynamic IPv4 + Subnet masks --- README.md | 4 ++++ .../columnlynx/common/net/session_registry.hpp | 16 +++++++++------- .../columnlynx/common/net/virtual_interface.hpp | 10 ++++++++++ include/columnlynx/common/utils.hpp | 3 ++- .../server/net/tcp/tcp_connection.hpp | 8 ++++++-- .../columnlynx/server/net/tcp/tcp_server.hpp | 2 +- src/common/utils.cpp | 12 +++++++++++- src/server/server/net/tcp/tcp_connection.cpp | 17 ++++++++++++++--- src/server/server/net/tcp/tcp_server.cpp | 3 ++- 9 files changed, 59 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 449024b..034a632 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,16 @@ Configurating the server and client are are relatively easy. Currently (since th - **SERVER_PUBLIC_KEY** (Hex String): The public key to be used - **SERVER_PRIVATE_KEY** (Hex String): The private key to be used +- **NETWORK** (IPv4 Format): The network IPv4 to be used (Server Interface still needs to be configured manually) +- **SUBNET_MASK** (Integer): The subnet mask to be used (ensure proper length, it will not be checked) **Example:** ``` SERVER_PUBLIC_KEY=787B648046F10DDD0B77A6303BE42D859AA65C52F5708CC3C58EB5691F217C7B SERVER_PRIVATE_KEY=778604245F57B847E63BD85DE8208FF1A127FB559895195928C3987E246B77B8787B648046F10DDD0B77A6303BE42D859AA65C52F5708CC3C58EB5691F217C7B +NETWORK=10.10.0.0 +SUBNET_MASK=24 ```
diff --git a/include/columnlynx/common/net/session_registry.hpp b/include/columnlynx/common/net/session_registry.hpp index 20939ba..25617e6 100644 --- a/include/columnlynx/common/net/session_registry.hpp +++ b/include/columnlynx/common/net/session_registry.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -109,22 +110,23 @@ namespace ColumnLynx::Net { return static_cast(mSessions.size()); } - // IP management (simple for /24 subnet) + // IP management // Get the lowest available IPv4 address; Returns 0 if none available - uint32_t getFirstAvailableIP() const { + uint32_t getFirstAvailableIP(uint32_t baseIP, uint8_t mask) const { std::shared_lock lock(mMutex); - uint32_t baseIP = 0x0A0A0002; // 10.10.0.2 + + uint32_t hostSpace = (1u << (32 - mask)) - 2; // Usable hosts - // TODO: Expand to support larger subnets - for (uint32_t offset = 0; offset < 254; offset++) { + // Skip 0 (network) and 1 (server reserved), start at 2 + for (uint32_t offset = 2; offset <= hostSpace; offset++) { uint32_t candidateIP = baseIP + offset; if (mSessionIPs.find(candidateIP) == mSessionIPs.end()) { return candidateIP; } } - - return 0; // Unavailable + + return 0; // No available IPs } // Lock an IP as assigned to a specific session diff --git a/include/columnlynx/common/net/virtual_interface.hpp b/include/columnlynx/common/net/virtual_interface.hpp index cf3d2d4..1b19bff 100644 --- a/include/columnlynx/common/net/virtual_interface.hpp +++ b/include/columnlynx/common/net/virtual_interface.hpp @@ -60,6 +60,16 @@ namespace ColumnLynx::Net { return std::string(buf); } + static inline uint32_t stringToIpv4(const std::string &ipStr) { + struct in_addr addr; + + if (inet_pton(AF_INET, ipStr.c_str(), &addr) != 1) { + return 0; // "0.0.0.0" + } + + return ntohl(addr.s_addr); + } + static inline uint32_t prefixLengthToNetmask(uint8_t prefixLen) { if (prefixLen == 0) return 0; uint32_t mask = (0xFFFFFFFF << (32 - prefixLen)) & 0xFFFFFFFF; diff --git a/include/columnlynx/common/utils.hpp b/include/columnlynx/common/utils.hpp index ad84710..97014fa 100644 --- a/include/columnlynx/common/utils.hpp +++ b/include/columnlynx/common/utils.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #ifdef _WIN32 @@ -94,5 +95,5 @@ namespace ColumnLynx::Utils { } // Returns the config file in an unordered_map format. This purely reads the config file, you still need to parse it manually. - std::unordered_map getConfigMap(std::string path); + std::unordered_map getConfigMap(std::string path, std::vector requiredKeys = {}); }; \ No newline at end of file diff --git a/include/columnlynx/server/net/tcp/tcp_connection.hpp b/include/columnlynx/server/net/tcp/tcp_connection.hpp index 771c2e7..4b93140 100644 --- a/include/columnlynx/server/net/tcp/tcp_connection.hpp +++ b/include/columnlynx/server/net/tcp/tcp_connection.hpp @@ -17,6 +17,7 @@ #include #include #include +#include namespace ColumnLynx::Net::TCP { class TCPConnection : public std::enable_shared_from_this { @@ -26,9 +27,10 @@ namespace ColumnLynx::Net::TCP { static pointer create( asio::ip::tcp::socket socket, std::shared_ptr sodiumWrapper, + std::unordered_map* serverConfig, std::function onDisconnect) { - auto conn = pointer(new TCPConnection(std::move(socket), sodiumWrapper)); + auto conn = pointer(new TCPConnection(std::move(socket), sodiumWrapper, serverConfig)); conn->mOnDisconnect = std::move(onDisconnect); return conn; } @@ -48,10 +50,11 @@ namespace ColumnLynx::Net::TCP { std::array getAESKey() const; private: - TCPConnection(asio::ip::tcp::socket socket, std::shared_ptr sodiumWrapper) + TCPConnection(asio::ip::tcp::socket socket, std::shared_ptr sodiumWrapper, std::unordered_map* serverConfig) : mHandler(std::make_shared(std::move(socket))), mLibSodiumWrapper(sodiumWrapper), + mRawServerConfig(serverConfig), mHeartbeatTimer(mHandler->socket().get_executor()), mLastHeartbeatReceived(std::chrono::steady_clock::now()), mLastHeartbeatSent(std::chrono::steady_clock::now()) @@ -65,6 +68,7 @@ namespace ColumnLynx::Net::TCP { std::shared_ptr mHandler; std::function)> mOnDisconnect; std::shared_ptr mLibSodiumWrapper; + std::unordered_map* mRawServerConfig; std::array mConnectionAESKey; uint64_t mConnectionSessionID; AsymPublicKey mConnectionPublicKey; diff --git a/include/columnlynx/server/net/tcp/tcp_server.hpp b/include/columnlynx/server/net/tcp/tcp_server.hpp index 5a0fd70..364a727 100644 --- a/include/columnlynx/server/net/tcp/tcp_server.hpp +++ b/include/columnlynx/server/net/tcp/tcp_server.hpp @@ -32,7 +32,7 @@ namespace ColumnLynx::Net::TCP { mHostRunning(hostRunning) { // Preload the config map - mRawServerConfig = Utils::getConfigMap("server_config"); + mRawServerConfig = Utils::getConfigMap("server_config", {"NETWORK", "SUBNET_MASK"}); asio::error_code ec; diff --git a/src/common/utils.cpp b/src/common/utils.cpp index eff056b..03e0797 100644 --- a/src/common/utils.cpp +++ b/src/common/utils.cpp @@ -118,7 +118,7 @@ namespace ColumnLynx::Utils { return out; } - std::unordered_map getConfigMap(std::string path) { + std::unordered_map getConfigMap(std::string path, std::vector requiredKeys) { // TODO: Currently re-reads every time. std::vector readLines; @@ -129,6 +129,16 @@ namespace ColumnLynx::Utils { readLines.push_back(line); } + if (!requiredKeys.empty()) { + // Check if they exist using unordered_set magic + std::unordered_set setA(readLines.begin(), readLines.end()); + for (std::string x : requiredKeys) { + if (!setA.count(x)) { + throw std::runtime_error("Config doesn't contain all required keys! (Missing: '" + x + "')"); + } + } + } + // Parse them into the struct std::unordered_map config; char delimiter = '='; diff --git a/src/server/server/net/tcp/tcp_connection.cpp b/src/server/server/net/tcp/tcp_connection.cpp index 891cf63..71d2f0d 100644 --- a/src/server/server/net/tcp/tcp_connection.cpp +++ b/src/server/server/net/tcp/tcp_connection.cpp @@ -202,7 +202,18 @@ namespace ColumnLynx::Net::TCP { // Encrypt the Session ID with the established AES key (using symmetric encryption, nonce can be all zeros for this purpose) Nonce symNonce{}; // All zeros - uint32_t clientIP = SessionRegistry::getInstance().getFirstAvailableIP(); + std::string networkString = mRawServerConfig->find("NETWORK")->second; // The load check guarantees that this value exists + uint8_t configMask = std::stoi(mRawServerConfig->find("SUBNET_MASK")->second); // Same deal here + + uint32_t baseIP = Net::VirtualInterface::stringToIpv4(networkString); + + if (baseIP == 0) { + Utils::warn("Your NETWORK value in the server configuration is malformed! I will not be able to accept connections! (Connection " + reqAddr + " was killed)"); + disconnect(); + return; + } + + uint32_t clientIP = SessionRegistry::getInstance().getFirstAvailableIP(baseIP, configMask); if (clientIP == 0) { Utils::warn("Out of available IPs! Disconnecting client " + reqAddr); @@ -214,8 +225,8 @@ namespace ColumnLynx::Net::TCP { tunConfig.version = Utils::protocolVersion(); tunConfig.prefixLength = 24; tunConfig.mtu = 1420; - tunConfig.serverIP = htonl(0x0A0A0001); // 10.10.0.1 - tunConfig.clientIP = htonl(clientIP); // 10.10.0.X + tunConfig.serverIP = htonl(baseIP + 1); // e.g. 10.10.0.1 + tunConfig.clientIP = htonl(clientIP); // e.g. 10.10.0.X tunConfig.dns1 = htonl(0x08080808); // 8.8.8.8 tunConfig.dns2 = 0; diff --git a/src/server/server/net/tcp/tcp_server.cpp b/src/server/server/net/tcp/tcp_server.cpp index 3f71670..05735a9 100644 --- a/src/server/server/net/tcp/tcp_server.cpp +++ b/src/server/server/net/tcp/tcp_server.cpp @@ -31,10 +31,11 @@ namespace ColumnLynx::Net::TCP { mStartAccept(); return; } - + auto client = TCPConnection::create( std::move(socket), mSodiumWrapper, + &mRawServerConfig, [this](std::shared_ptr c) { mClients.erase(c); Utils::log("Client removed.");