13 Commits

Author SHA1 Message Date
07458c348b Merge branch 'beta' 2026-01-18 19:55:13 +01:00
833629486e Merge branch 'beta'; Version 1.0.1 2026-01-18 19:48:02 +01:00
cbfbcaa153 Merge branch 'beta' 2026-01-11 20:32:46 +01:00
e101f5ddd6 Merge branch 'beta' - Version 1.0.0 2026-01-01 17:21:28 +01:00
c715a43a10 Merge branch 'beta' - Version 1.0.0 2026-01-01 16:33:21 +01:00
f99036c523 Merge branch 'beta' 2025-12-29 20:28:34 +01:00
471224b043 Merge branch 'beta' - b0.3 2025-12-29 19:07:16 +01:00
cb0f674c52 Merge branch 'beta' - Version b0.1
macOS Support
2025-12-08 17:38:05 +01:00
33bbd7cce6 Merge branch 'beta' - Alpha 0.6
This version adds Dynamic IP assignment based on config.
2025-12-02 18:47:58 +01:00
f9c5c56a1b Merge branch 'beta'
This is the merge of version a0.5 into master.
This version adds general authentication of the client and server, and control of connection via key whitelisting.
Also added loading of keypairs via a config file system.
2025-11-28 19:31:01 +01:00
17dd504a7a Merge pull request 'First working alpha, version a0.4' (#7) from beta into master
Reviewed-on: #7
2025-11-18 20:09:11 +00:00
9f52bdd54c Merge pull request 'beta' (#4) from beta into master
Reviewed-on: #4
2025-11-10 15:58:29 +00:00
29e90938c5 Merge pull request 'beta - Update License' (#2) from beta into master
Reviewed-on: #2
2025-11-10 15:15:31 +00:00
22 changed files with 186 additions and 527 deletions

View File

@@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.16)
# If MAJOR is 0, and MINOR > 0, Version is BETA # If MAJOR is 0, and MINOR > 0, Version is BETA
project(ColumnLynx project(ColumnLynx
VERSION 1.1.0 VERSION 1.0.1
LANGUAGES CXX LANGUAGES CXX
) )

View File

@@ -213,8 +213,6 @@ ColumnLynx makes use of both **TCP** and **UDP**. **TCP** is used for the initia
It operates on port **48042** for both TCP and UDP. It operates on port **48042** for both TCP and UDP.
Current protocol version is **2**.
Generally, all transmission is done in **little-endian byte order**, since pretty much every single modern architecture uses it by default. The only exemption to this is the **transmission of IP addresses** (for the **Virtual Interface**), which is **big-endian**. Generally, all transmission is done in **little-endian byte order**, since pretty much every single modern architecture uses it by default. The only exemption to this is the **transmission of IP addresses** (for the **Virtual Interface**), which is **big-endian**.
### Handshake Procedure ### Handshake Procedure
@@ -233,7 +231,7 @@ The Client now generates a random aesKey (32 bytes long)
C: HANDSHAKE_EXCHANGE_KEY <aesKey Encrypted with Server Public Key> C: HANDSHAKE_EXCHANGE_KEY <aesKey Encrypted with Server Public Key>
The Server now assigns a local 4 byte session ID in the Session Registry. The Server now assigns a local 8 byte session ID in the Session Registry.
S: HANDSHAKE_EXCHANGE_KEY_CONFIRM <Assigned SessionID> S: HANDSHAKE_EXCHANGE_KEY_CONFIRM <Assigned SessionID>
``` ```
@@ -244,7 +242,7 @@ The **Client** and **Server** have now securely exchanged a symmetric **AES Key*
Packet exchange and the general data tunneling is done via **Standard UDP** (*see the **UDP Packet** in **Data***). Packet exchange and the general data tunneling is done via **Standard UDP** (*see the **UDP Packet** in **Data***).
The **header** of the sent packet always includes a **12 byte nonce** derived from a random **4 byte base nonce** and the **send count** to ensure a unique nonce, used to obscure the **encrypted payload / data** and the **Session ID** assigned by the server to the client (4 bytes). This makes the header **16 bytes long**. The **header** of the sent packet always includes a **random 12 byte nonce** used to obscure the **encrypted payload / data** and the **Session ID** assigned by the server to the client (8 bytes). This makes the header **20 bytes long**.
The **payload / data** of the sent packet is **always encrypted** using the exchanged **AES Key** and obscured using the **random nonce**. The **payload / data** of the sent packet is **always encrypted** using the exchanged **AES Key** and obscured using the **random nonce**.
@@ -300,7 +298,7 @@ The **Data** is generally just the **raw underlying packet** forwarded to the se
| Type | Length | Name | Description | | Type | Length | Name | Description |
|:-----|:-------|:-----|:------------| |:-----|:-------|:-----|:------------|
| uint8_t | 12 bytes | **Header** - Nonce | Random nonce to obfuscate encrypted contents | | uint8_t | 12 bytes | **Header** - Nonce | Random nonce to obfuscate encrypted contents |
| uint32_t | 4 bytes | **Header** - Session ID | The unique and random session identifier for the client | | uint64_t | 8 bytes | **Header** - Session ID | The unique and random session identifier for the client |
| uint8_t | variable | Data | General data / payload | | uint8_t | variable | Data | General data / payload |
## Misc. ## Misc.

View File

@@ -1,102 +0,0 @@
// client_session.hpp - Client Session data for ColumnLynx
// Copyright (C) 2026 DcruBro
// Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once
#include <memory>
#include <columnlynx/common/libsodium_wrapper.hpp>
#include <array>
#include <columnlynx/common/net/virtual_interface.hpp>
#include <shared_mutex>
namespace ColumnLynx {
struct ClientState {
std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper;
SymmetricKey aesKey;
bool insecureMode;
std::string configPath;
std::shared_ptr<Net::VirtualInterface> virtualInterface;
uint32_t sessionID;
uint64_t recv_cnt;
uint64_t send_cnt;
uint32_t noncePrefix;
~ClientState() { sodium_memzero(aesKey.data(), aesKey.size()); }
ClientState(const ClientState&) = delete;
ClientState& operator=(const ClientState&) = delete;
ClientState(ClientState&&) = default;
ClientState& operator=(ClientState&&) = default;
explicit ClientState() = default;
explicit ClientState(std::shared_ptr<Utils::LibSodiumWrapper> sodium, SymmetricKey& k, bool insecure,
std::string& config, std::shared_ptr<Net::VirtualInterface> tun, uint32_t session, uint64_t recv, uint64_t send)
: sodiumWrapper(sodium), aesKey(k), insecureMode(insecure), configPath(config), virtualInterface(tun), sessionID(session), recv_cnt(recv), send_cnt(send) {}
};
class ClientSession {
public:
// Return a reference to the Client Session instance
static ClientSession& getInstance() { static ClientSession instance; return instance; }
// Return the current client state
std::shared_ptr<ClientState> getClientState() const;
// Set the client state
void setClientState(std::shared_ptr<ClientState> state);
// Get the wrapper for libsodium
const std::shared_ptr<Utils::LibSodiumWrapper>& getSodiumWrapper() const;
// Get the AES key
const SymmetricKey& getAESKey() const;
// Get whether insecure mode is enabled
bool isInsecureMode() const;
// Get the config path
const std::string& getConfigPath() const;
// Get the virtual interface
const std::shared_ptr<Net::VirtualInterface>& getVirtualInterface() const;
// Get the session ID
uint32_t getSessionID() const;
uint64_t getRecvCount() const {
std::shared_lock lock(mMutex);
return mClientState->recv_cnt;
}
uint64_t getSendCount() const {
std::shared_lock lock(mMutex);
return mClientState->send_cnt;
}
uint32_t getNoncePrefix() const {
std::shared_lock lock(mMutex);
return mClientState->noncePrefix;
}
// Setters
void setSodiumWrapper(std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper);
void setAESKey(const SymmetricKey& aesKey);
void setInsecureMode(bool insecureMode);
void setConfigPath(const std::string& configPath);
void setVirtualInterface(std::shared_ptr<Net::VirtualInterface> virtualInterface);
void setSessionID(uint32_t sessionID);
void incrementRecvCount() {
std::unique_lock lock(mMutex);
mClientState->recv_cnt++;
}
void incrementSendCount() {
std::unique_lock lock(mMutex);
mClientState->send_cnt++;
}
void setNoncePrefix(uint32_t prefix) {
std::unique_lock lock(mMutex);
mClientState->noncePrefix = prefix;
}
private:
mutable std::shared_mutex mMutex;
std::shared_ptr<struct ClientState> mClientState{nullptr};
};
}

View File

@@ -17,7 +17,6 @@
#include <string> #include <string>
#include <columnlynx/common/net/protocol_structs.hpp> #include <columnlynx/common/net/protocol_structs.hpp>
#include <columnlynx/common/net/virtual_interface.hpp> #include <columnlynx/common/net/virtual_interface.hpp>
#include <columnlynx/client/client_session.hpp>
using asio::ip::tcp; using asio::ip::tcp;
@@ -26,20 +25,28 @@ namespace ColumnLynx::Net::TCP {
public: public:
TCPClient(asio::io_context& ioContext, TCPClient(asio::io_context& ioContext,
const std::string& host, const std::string& host,
const std::string& port) const std::string& port,
std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper,
std::shared_ptr<std::array<uint8_t, 32>> aesKey,
std::shared_ptr<uint64_t> sessionIDRef,
bool insecureMode,
std::string& configPath,
std::shared_ptr<VirtualInterface> tun = nullptr)
: :
mResolver(ioContext), mResolver(ioContext),
mSocket(ioContext), mSocket(ioContext),
mHost(host), mHost(host),
mPort(port), mPort(port),
mLibSodiumWrapper(sodiumWrapper),
mGlobalKeyRef(aesKey),
mSessionIDRef(sessionIDRef),
mInsecureMode(insecureMode),
mHeartbeatTimer(mSocket.get_executor()), mHeartbeatTimer(mSocket.get_executor()),
mLastHeartbeatReceived(std::chrono::steady_clock::now()), mLastHeartbeatReceived(std::chrono::steady_clock::now()),
mLastHeartbeatSent(std::chrono::steady_clock::now()) mLastHeartbeatSent(std::chrono::steady_clock::now()),
mTun(tun),
mConfigDirPath(configPath)
{ {
// Get initial client config
std::string configPath = ClientSession::getInstance().getConfigPath();
std::shared_ptr<Utils::LibSodiumWrapper> mLibSodiumWrapper = ClientSession::getInstance().getSodiumWrapper();
// Preload the config map // Preload the config map
mRawClientConfig = Utils::getConfigMap(configPath + "client_config"); mRawClientConfig = Utils::getConfigMap(configPath + "client_config");
@@ -97,14 +104,20 @@ namespace ColumnLynx::Net::TCP {
std::string mHost, mPort; std::string mHost, mPort;
uint8_t mServerPublicKey[32]; // Assuming 256-bit public key uint8_t mServerPublicKey[32]; // Assuming 256-bit public key
std::array<uint8_t, 32> mSubmittedChallenge{}; std::array<uint8_t, 32> mSubmittedChallenge{};
uint32_t mConnectionSessionID; std::shared_ptr<Utils::LibSodiumWrapper> mLibSodiumWrapper;
uint64_t mConnectionSessionID;
SymmetricKey mConnectionAESKey; SymmetricKey mConnectionAESKey;
std::shared_ptr<std::array<uint8_t, 32>> mGlobalKeyRef; // Reference to global AES key
std::shared_ptr<uint64_t> mSessionIDRef; // Reference to global Session ID
bool mInsecureMode; // Insecure mode flag
asio::steady_timer mHeartbeatTimer; asio::steady_timer mHeartbeatTimer;
std::chrono::steady_clock::time_point mLastHeartbeatReceived; std::chrono::steady_clock::time_point mLastHeartbeatReceived;
std::chrono::steady_clock::time_point mLastHeartbeatSent; std::chrono::steady_clock::time_point mLastHeartbeatSent;
int mMissedHeartbeats = 0; int mMissedHeartbeats = 0;
bool mIsHostDomain; bool mIsHostDomain;
Protocol::TunConfig mTunConfig; Protocol::TunConfig mTunConfig;
std::shared_ptr<VirtualInterface> mTun = nullptr;
std::unordered_map<std::string, std::string> mRawClientConfig; std::unordered_map<std::string, std::string> mRawClientConfig;
std::string mConfigDirPath;
}; };
} }

View File

@@ -10,15 +10,17 @@
#include <columnlynx/common/libsodium_wrapper.hpp> #include <columnlynx/common/libsodium_wrapper.hpp>
#include <array> #include <array>
#include <columnlynx/common/net/virtual_interface.hpp> #include <columnlynx/common/net/virtual_interface.hpp>
#include <columnlynx/client/client_session.hpp>
namespace ColumnLynx::Net::UDP { namespace ColumnLynx::Net::UDP {
class UDPClient { class UDPClient {
public: public:
UDPClient(asio::io_context& ioContext, UDPClient(asio::io_context& ioContext,
const std::string& host, const std::string& host,
const std::string& port) const std::string& port,
: mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port) std::shared_ptr<std::array<uint8_t, 32>> aesKeyRef,
std::shared_ptr<uint64_t> sessionIDRef,
std::shared_ptr<VirtualInterface> tunRef = nullptr)
: mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port), mAesKeyRef(aesKeyRef), mSessionIDRef(sessionIDRef), mTunRef(tunRef)
{ {
mStartReceive(); mStartReceive();
} }
@@ -41,6 +43,9 @@ namespace ColumnLynx::Net::UDP {
asio::ip::udp::endpoint mRemoteEndpoint; asio::ip::udp::endpoint mRemoteEndpoint;
std::string mHost; std::string mHost;
std::string mPort; std::string mPort;
std::shared_ptr<std::array<uint8_t, 32>> mAesKeyRef;
std::shared_ptr<uint64_t> mSessionIDRef;
std::shared_ptr<VirtualInterface> mTunRef = nullptr;
std::array<uint8_t, 2048> mRecvBuffer; // Adjust size as needed std::array<uint8_t, 2048> mRecvBuffer; // Adjust size as needed
}; };
} }

View File

@@ -27,9 +27,8 @@ namespace ColumnLynx::Net {
std::chrono::steady_clock::time_point expires{}; // Time of expiry std::chrono::steady_clock::time_point expires{}; // Time of expiry
uint32_t clientTunIP; // Assigned IP uint32_t clientTunIP; // Assigned IP
uint32_t serverTunIP; // Server IP uint32_t serverTunIP; // Server IP
uint32_t sessionID; // Session ID uint64_t sessionID; // Session ID
Nonce base_nonce{}; Nonce base_nonce{};
uint32_t noncePrefix;
~SessionState() { sodium_memzero(aesKey.data(), aesKey.size()); } ~SessionState() { sodium_memzero(aesKey.data(), aesKey.size()); }
SessionState(const SessionState&) = delete; SessionState(const SessionState&) = delete;
@@ -37,7 +36,7 @@ namespace ColumnLynx::Net {
SessionState(SessionState&&) = default; SessionState(SessionState&&) = default;
SessionState& operator=(SessionState&&) = default; SessionState& operator=(SessionState&&) = default;
explicit SessionState(const SymmetricKey& k, std::chrono::seconds ttl = std::chrono::hours(24), uint32_t clientIP = 0, uint32_t serverIP = 0, uint32_t id = 0) : aesKey(k), clientTunIP(clientIP), serverTunIP(serverIP), sessionID(id) { explicit SessionState(const SymmetricKey& k, std::chrono::seconds ttl = std::chrono::hours(24), uint32_t clientIP = 0, uint32_t serverIP = 0, uint64_t id = 0) : aesKey(k), clientTunIP(clientIP), serverTunIP(serverIP), sessionID(id) {
expires = created + ttl; expires = created + ttl;
} }
@@ -45,11 +44,6 @@ namespace ColumnLynx::Net {
void setUDPEndpoint(const asio::ip::udp::endpoint& ep) { void setUDPEndpoint(const asio::ip::udp::endpoint& ep) {
udpEndpoint = ep; udpEndpoint = ep;
} }
void setBaseNonce() {
Utils::debug("Generating random base nonce for session " + std::to_string(sessionID));
randombytes_buf(base_nonce.data(), base_nonce.size());
}
}; };
class SessionRegistry { class SessionRegistry {
@@ -58,19 +52,19 @@ namespace ColumnLynx::Net {
static SessionRegistry& getInstance() { static SessionRegistry instance; return instance; } static SessionRegistry& getInstance() { static SessionRegistry instance; return instance; }
// Insert or replace a session entry // Insert or replace a session entry
void put(uint32_t sessionID, std::shared_ptr<SessionState> state); void put(uint64_t sessionID, std::shared_ptr<SessionState> state);
// Lookup a session entry by session ID // Lookup a session entry by session ID
std::shared_ptr<const SessionState> get(uint32_t sessionID) const; std::shared_ptr<const SessionState> get(uint64_t sessionID) const;
// Lookup a session entry by IPv4 // Lookup a session entry by IPv4
std::shared_ptr<const SessionState> getByIP(uint32_t ip) const; std::shared_ptr<const SessionState> getByIP(uint32_t ip) const;
// Get a snapshot of the Session Registry // Get a snapshot of the Session Registry
std::unordered_map<uint32_t, std::shared_ptr<SessionState>> snapshot() const; std::unordered_map<uint64_t, std::shared_ptr<SessionState>> snapshot() const;
// Remove a session by ID // Remove a session by ID
void erase(uint32_t sessionID); void erase(uint64_t sessionID);
// Cleanup expired sessions // Cleanup expired sessions
void cleanupExpired(); void cleanupExpired();
@@ -78,23 +72,21 @@ namespace ColumnLynx::Net {
// Get the number of registered sessions // Get the number of registered sessions
int size() const; int size() const;
bool exists(uint32_t sessionID) const;
// IP management // IP management
// Get the lowest available IPv4 address; Returns 0 if none available // Get the lowest available IPv4 address; Returns 0 if none available
uint32_t getFirstAvailableIP(uint32_t baseIP, uint8_t mask) const; uint32_t getFirstAvailableIP(uint32_t baseIP, uint8_t mask) const;
// Lock IP to session ID; Do NOT call before put() - You will segfault! // Lock IP to session ID; Do NOT call before put() - You will segfault!
void lockIP(uint32_t sessionID, uint32_t ip); void lockIP(uint64_t sessionID, uint32_t ip);
// Unlock IP from session ID // Unlock IP from session ID
void deallocIP(uint32_t sessionID); void deallocIP(uint64_t sessionID);
private: private:
mutable std::shared_mutex mMutex; mutable std::shared_mutex mMutex;
std::unordered_map<uint32_t, std::shared_ptr<SessionState>> mSessions; std::unordered_map<uint64_t, std::shared_ptr<SessionState>> mSessions;
std::unordered_map<uint32_t, uint32_t> mSessionIPs; std::unordered_map<uint64_t, uint32_t> mSessionIPs;
std::unordered_map<uint32_t, std::shared_ptr<SessionState>> mIPSessions; std::unordered_map<uint32_t, std::shared_ptr<SessionState>> mIPSessions;
}; };
} }

View File

@@ -65,6 +65,27 @@ namespace ColumnLynx::Utils {
return std::string(reinterpret_cast<const char*>(data), length); return std::string(reinterpret_cast<const char*>(data), length);
} }
inline constexpr uint64_t cbswap64(uint64_t x) {
return ((x & 0x00000000000000FFULL) << 56) |
((x & 0x000000000000FF00ULL) << 40) |
((x & 0x0000000000FF0000ULL) << 24) |
((x & 0x00000000FF000000ULL) << 8) |
((x & 0x000000FF00000000ULL) >> 8) |
((x & 0x0000FF0000000000ULL) >> 24) |
((x & 0x00FF000000000000ULL) >> 40) |
((x & 0xFF00000000000000ULL) >> 56);
}
// host -> big-endian (for little-endian hosts) - 64 bit
inline constexpr uint64_t chtobe64(uint64_t x) {
return cbswap64(x);
}
// big-endian -> host (for little-endian hosts) - 64 bit
inline constexpr uint64_t cbe64toh(uint64_t x) {
return cbswap64(x);
}
template <typename T> template <typename T>
T cbswap128(const T& x) { T cbswap128(const T& x) {
static_assert(sizeof(T) == 16, "cbswap128 requires a 128-bit type"); static_assert(sizeof(T) == 16, "cbswap128 requires a 128-bit type");

View File

@@ -18,7 +18,6 @@
#include <columnlynx/common/net/session_registry.hpp> #include <columnlynx/common/net/session_registry.hpp>
#include <columnlynx/common/net/protocol_structs.hpp> #include <columnlynx/common/net/protocol_structs.hpp>
#include <columnlynx/common/net/virtual_interface.hpp> #include <columnlynx/common/net/virtual_interface.hpp>
#include <columnlynx/server/server_session.hpp>
namespace ColumnLynx::Net::TCP { namespace ColumnLynx::Net::TCP {
class TCPConnection : public std::enable_shared_from_this<TCPConnection> { class TCPConnection : public std::enable_shared_from_this<TCPConnection> {
@@ -27,9 +26,12 @@ namespace ColumnLynx::Net::TCP {
static pointer create( static pointer create(
asio::ip::tcp::socket socket, asio::ip::tcp::socket socket,
std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper,
std::unordered_map<std::string, std::string>* serverConfig,
std::string configDirPath,
std::function<void(pointer)> onDisconnect) std::function<void(pointer)> onDisconnect)
{ {
auto conn = pointer(new TCPConnection(std::move(socket))); auto conn = pointer(new TCPConnection(std::move(socket), sodiumWrapper, serverConfig, configDirPath));
conn->mOnDisconnect = std::move(onDisconnect); conn->mOnDisconnect = std::move(onDisconnect);
return conn; return conn;
} }
@@ -44,17 +46,20 @@ namespace ColumnLynx::Net::TCP {
void disconnect(bool echo = true); void disconnect(bool echo = true);
// Get the assigned session ID // Get the assigned session ID
uint32_t getSessionID() const; uint64_t getSessionID() const;
// Get the assigned AES key; You should probably access this via the Session Registry instead // Get the assigned AES key; You should probably access this via the Session Registry instead
std::array<uint8_t, 32> getAESKey() const; std::array<uint8_t, 32> getAESKey() const;
private: private:
TCPConnection(asio::ip::tcp::socket socket) TCPConnection(asio::ip::tcp::socket socket, std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper, std::unordered_map<std::string, std::string>* serverConfig, std::string configDirPath)
: :
mHandler(std::make_shared<MessageHandler>(std::move(socket))), mHandler(std::make_shared<MessageHandler>(std::move(socket))),
mLibSodiumWrapper(sodiumWrapper),
mRawServerConfig(serverConfig),
mHeartbeatTimer(mHandler->socket().get_executor()), mHeartbeatTimer(mHandler->socket().get_executor()),
mLastHeartbeatReceived(std::chrono::steady_clock::now()), mLastHeartbeatReceived(std::chrono::steady_clock::now()),
mLastHeartbeatSent(std::chrono::steady_clock::now()) mLastHeartbeatSent(std::chrono::steady_clock::now()),
mConfigDirPath(configDirPath)
{} {}
// Start the heartbeat routine // Start the heartbeat routine
@@ -64,13 +69,16 @@ namespace ColumnLynx::Net::TCP {
std::shared_ptr<MessageHandler> mHandler; std::shared_ptr<MessageHandler> mHandler;
std::function<void(std::shared_ptr<TCPConnection>)> mOnDisconnect; std::function<void(std::shared_ptr<TCPConnection>)> mOnDisconnect;
std::shared_ptr<Utils::LibSodiumWrapper> mLibSodiumWrapper;
std::unordered_map<std::string, std::string>* mRawServerConfig;
std::array<uint8_t, 32> mConnectionAESKey; std::array<uint8_t, 32> mConnectionAESKey;
uint32_t mConnectionSessionID; uint64_t mConnectionSessionID;
AsymPublicKey mConnectionPublicKey; AsymPublicKey mConnectionPublicKey;
asio::steady_timer mHeartbeatTimer; asio::steady_timer mHeartbeatTimer;
std::chrono::steady_clock::time_point mLastHeartbeatReceived; std::chrono::steady_clock::time_point mLastHeartbeatReceived;
std::chrono::steady_clock::time_point mLastHeartbeatSent; std::chrono::steady_clock::time_point mLastHeartbeatSent;
int mMissedHeartbeats = 0; int mMissedHeartbeats = 0;
std::string mRemoteIP; // Cached remote IP to avoid calling remote_endpoint() on closed sockets std::string mRemoteIP; // Cached remote IP to avoid calling remote_endpoint() on closed sockets
std::string mConfigDirPath;
}; };
} }

View File

@@ -17,23 +17,29 @@
#include <columnlynx/server/net/tcp/tcp_connection.hpp> #include <columnlynx/server/net/tcp/tcp_connection.hpp>
#include <columnlynx/common/libsodium_wrapper.hpp> #include <columnlynx/common/libsodium_wrapper.hpp>
#include <columnlynx/common/net/protocol_structs.hpp> #include <columnlynx/common/net/protocol_structs.hpp>
#include <columnlynx/server/server_session.hpp>
namespace ColumnLynx::Net::TCP { namespace ColumnLynx::Net::TCP {
class TCPServer { class TCPServer {
public: public:
TCPServer(asio::io_context& ioContext, TCPServer(asio::io_context& ioContext,
uint16_t port) uint16_t port,
std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper,
std::shared_ptr<bool> hostRunning,
std::string& configPath,
bool ipv4Only = false)
: mIoContext(ioContext), : mIoContext(ioContext),
mAcceptor(ioContext) mAcceptor(ioContext),
mSodiumWrapper(sodiumWrapper),
mHostRunning(hostRunning),
mConfigDirPath(configPath)
{ {
// Preload the config map // Preload the config map
mRawServerConfig = Utils::getConfigMap(configPath + "server_config", {"NETWORK", "SUBNET_MASK"});
asio::error_code ec_open, ec_v6only, ec_bind; asio::error_code ec_open, ec_v6only, ec_bind;
bool isIPv4Only = ServerSession::getInstance().isIPv4Only(); if (!ipv4Only) {
if (!isIPv4Only) {
// Try IPv6 (dual-stack if supported) // Try IPv6 (dual-stack if supported)
asio::ip::tcp::endpoint endpoint_v6(asio::ip::tcp::v6(), port); asio::ip::tcp::endpoint endpoint_v6(asio::ip::tcp::v6(), port);
@@ -49,8 +55,8 @@ namespace ColumnLynx::Net::TCP {
} }
// If IPv6 bind failed OR IPv6 open failed OR forced IPv4-only // If IPv6 bind failed OR IPv6 open failed OR forced IPv4-only
if (isIPv4Only || ec_open || ec_bind) { if (ipv4Only || ec_open || ec_bind) {
if (!isIPv4Only) if (!ipv4Only)
Utils::warn("TCP: IPv6 unavailable (open=" + ec_open.message() + Utils::warn("TCP: IPv6 unavailable (open=" + ec_open.message() +
", bind=" + ec_bind.message() + ", bind=" + ec_bind.message() +
"), falling back to IPv4 only"); "), falling back to IPv4 only");
@@ -78,6 +84,10 @@ namespace ColumnLynx::Net::TCP {
asio::io_context &mIoContext; asio::io_context &mIoContext;
asio::ip::tcp::acceptor mAcceptor; asio::ip::tcp::acceptor mAcceptor;
std::unordered_set<TCPConnection::pointer> mClients; std::unordered_set<TCPConnection::pointer> mClients;
std::shared_ptr<Utils::LibSodiumWrapper> mSodiumWrapper;
std::shared_ptr<bool> mHostRunning;
std::unordered_map<std::string, std::string> mRawServerConfig;
std::string mConfigDirPath;
}; };
} }

View File

@@ -9,18 +9,16 @@
#include <columnlynx/common/utils.hpp> #include <columnlynx/common/utils.hpp>
#include <array> #include <array>
#include <columnlynx/common/net/virtual_interface.hpp> #include <columnlynx/common/net/virtual_interface.hpp>
#include <columnlynx/common/libsodium_wrapper.hpp>
#include <columnlynx/server/server_session.hpp>
namespace ColumnLynx::Net::UDP { namespace ColumnLynx::Net::UDP {
class UDPServer { class UDPServer {
public: public:
UDPServer(asio::io_context& ioContext, uint16_t port) UDPServer(asio::io_context& ioContext, uint16_t port, std::shared_ptr<bool> hostRunning, bool ipv4Only = false, std::shared_ptr<VirtualInterface> tun = nullptr)
: mSocket(ioContext) : mSocket(ioContext), mHostRunning(hostRunning), mTun(tun)
{ {
asio::error_code ec_open, ec_v6only, ec_bind; asio::error_code ec_open, ec_v6only, ec_bind;
if (!mIpv4Only) { if (!ipv4Only) {
asio::ip::udp::endpoint endpoint_v6(asio::ip::udp::v6(), port); asio::ip::udp::endpoint endpoint_v6(asio::ip::udp::v6(), port);
// Try opening IPv6 socket // Try opening IPv6 socket
@@ -36,8 +34,8 @@ namespace ColumnLynx::Net::UDP {
} }
// Fallback to IPv4 if IPv6 is unusable // Fallback to IPv4 if IPv6 is unusable
if (mIpv4Only || ec_open || ec_bind) { if (ipv4Only || ec_open || ec_bind) {
if (!mIpv4Only) { if (!ipv4Only) {
Utils::warn( Utils::warn(
"UDP: IPv6 unavailable (open=" + ec_open.message() + "UDP: IPv6 unavailable (open=" + ec_open.message() +
", bind=" + ec_bind.message() + ", bind=" + ec_bind.message() +
@@ -61,7 +59,7 @@ namespace ColumnLynx::Net::UDP {
void stop(); void stop();
// Send UDP data to an endpoint; Fetched via the Session Registry // Send UDP data to an endpoint; Fetched via the Session Registry
void sendData(uint32_t sessionID, const std::string& data); void sendData(const uint64_t sessionID, const std::string& data);
private: private:
// Start receiving UDP data // Start receiving UDP data
@@ -72,7 +70,7 @@ namespace ColumnLynx::Net::UDP {
asio::ip::udp::socket mSocket; asio::ip::udp::socket mSocket;
asio::ip::udp::endpoint mRemoteEndpoint; asio::ip::udp::endpoint mRemoteEndpoint;
std::array<uint8_t, 2048> mRecvBuffer; // 2048 seems stable std::array<uint8_t, 2048> mRecvBuffer; // 2048 seems stable
bool mIpv4Only = ServerSession::getInstance().isIPv4Only(); std::shared_ptr<bool> mHostRunning;
const std::shared_ptr<VirtualInterface> mTun = ServerSession::getInstance().getVirtualInterface(); std::shared_ptr<VirtualInterface> mTun;
}; };
} }

View File

@@ -1,62 +0,0 @@
// server_session.hpp - Client Session data for ColumnLynx
// Copyright (C) 2026 DcruBro
// Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once
#include <memory>
#include <columnlynx/common/libsodium_wrapper.hpp>
#include <array>
#include <columnlynx/common/net/virtual_interface.hpp>
#include <shared_mutex>
namespace ColumnLynx {
struct ServerState {
std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper;
std::shared_ptr<Net::VirtualInterface> virtualInterface;
std::string configPath;
std::unordered_map<std::string, std::string> serverConfig;
bool ipv4Only;
bool hostRunning;
~ServerState() = default;
ServerState(const ServerState&) = delete;
ServerState& operator=(const ServerState&) = delete;
ServerState(ServerState&&) = default;
ServerState& operator=(ServerState&&) = default;
explicit ServerState() = default;
};
class ServerSession {
public:
// Return a reference to the Server Session instance
static ServerSession& getInstance() { static ServerSession instance; return instance; }
// Return the current server state
std::shared_ptr<ServerState> getServerState() const;
// Set the server state
void setServerState(std::shared_ptr<ServerState> state);
// Getters
std::shared_ptr<Utils::LibSodiumWrapper> getSodiumWrapper() const;
const std::string& getConfigPath() const;
const std::unordered_map<std::string, std::string>& getRawServerConfig() const;
const std::shared_ptr<Net::VirtualInterface>& getVirtualInterface() const;
bool isIPv4Only() const;
bool isHostRunning() const;
// Setters
void setSodiumWrapper(std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper);
void setConfigPath(const std::string& configPath);
void setRawServerConfig(const std::unordered_map<std::string, std::string>& config);
void setVirtualInterface(std::shared_ptr<Net::VirtualInterface> tun);
void setIPv4Only(bool ipv4Only);
void setHostRunning(bool hostRunning);
private:
mutable std::shared_mutex mMutex;
std::shared_ptr<struct ServerState> mServerState{nullptr};
};
}

View File

@@ -1,71 +0,0 @@
// client_session.cpp - Client Session data for ColumnLynx
// Copyright (C) 2026 DcruBro
// Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#include <columnlynx/client/client_session.hpp>
namespace ColumnLynx {
std::shared_ptr<ClientState> ClientSession::getClientState() const {
std::shared_lock lock(mMutex);
return mClientState;
}
void ClientSession::setClientState(std::shared_ptr<ClientState> state) {
std::unique_lock lock(mMutex);
mClientState = state;
}
const std::shared_ptr<Utils::LibSodiumWrapper>& ClientSession::getSodiumWrapper() const {
return getClientState()->sodiumWrapper;
}
const SymmetricKey& ClientSession::getAESKey() const {
return getClientState()->aesKey;
}
bool ClientSession::isInsecureMode() const {
return getClientState()->insecureMode;
}
const std::string& ClientSession::getConfigPath() const {
return getClientState()->configPath;
}
const std::shared_ptr<Net::VirtualInterface>& ClientSession::getVirtualInterface() const {
return getClientState()->virtualInterface;
}
uint32_t ClientSession::getSessionID() const {
return getClientState()->sessionID;
}
void ClientSession::setSodiumWrapper(std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper) {
std::unique_lock lock(mMutex);
mClientState->sodiumWrapper = sodiumWrapper;
}
void ClientSession::setAESKey(const SymmetricKey& aesKey) {
std::unique_lock lock(mMutex);
mClientState->aesKey = aesKey;
}
void ClientSession::setInsecureMode(bool insecureMode) {
std::unique_lock lock(mMutex);
mClientState->insecureMode = insecureMode;
}
void ClientSession::setConfigPath(const std::string& configPath) {
std::unique_lock lock(mMutex);
mClientState->configPath = configPath;
}
void ClientSession::setVirtualInterface(std::shared_ptr<Net::VirtualInterface> virtualInterface) {
std::unique_lock lock(mMutex);
mClientState->virtualInterface = virtualInterface;
}
void ClientSession::setSessionID(uint32_t sessionID) {
std::unique_lock lock(mMutex);
mClientState->sessionID = sessionID;
}
}

View File

@@ -11,8 +11,6 @@
#include <columnlynx/client/net/udp/udp_client.hpp> #include <columnlynx/client/net/udp/udp_client.hpp>
#include <cxxopts.hpp> #include <cxxopts.hpp>
#include <columnlynx/common/net/virtual_interface.hpp> #include <columnlynx/common/net/virtual_interface.hpp>
#include <columnlynx/client/client_session.hpp>
#include <thread>
#if defined(__WIN32__) #if defined(__WIN32__)
#include <windows.h> #include <windows.h>
@@ -101,37 +99,24 @@ int main(int argc, char** argv) {
#endif #endif
} }
struct ClientState initialState{};
initialState.configPath = configPath;
initialState.insecureMode = insecureMode;
initialState.send_cnt = 0;
initialState.recv_cnt = 0;
randombytes_buf(&initialState.noncePrefix, sizeof(uint32_t)); // Randomize nonce prefix
std::shared_ptr<VirtualInterface> tun = std::make_shared<VirtualInterface>(optionsObj["interface"].as<std::string>()); std::shared_ptr<VirtualInterface> tun = std::make_shared<VirtualInterface>(optionsObj["interface"].as<std::string>());
log("Using virtual interface: " + tun->getName()); log("Using virtual interface: " + tun->getName());
initialState.virtualInterface = tun;
std::shared_ptr<LibSodiumWrapper> sodiumWrapper = std::make_shared<LibSodiumWrapper>(); std::shared_ptr<LibSodiumWrapper> sodiumWrapper = std::make_shared<LibSodiumWrapper>();
debug("Public Key: " + Utils::bytesToHexString(sodiumWrapper->getPublicKey(), 32)); debug("Public Key: " + Utils::bytesToHexString(sodiumWrapper->getPublicKey(), 32));
debug("Private Key: " + Utils::bytesToHexString(sodiumWrapper->getPrivateKey(), 64)); debug("Private Key: " + Utils::bytesToHexString(sodiumWrapper->getPrivateKey(), 64));
initialState.sodiumWrapper = sodiumWrapper;
std::array<uint8_t, 32> aesKey = std::array<uint8_t, 32>(); std::shared_ptr<std::array<uint8_t, 32>> aesKey = std::make_shared<std::array<uint8_t, 32>>();
aesKey.fill(0); // Defualt zeroed state until modified by handshake aesKey->fill(0); // Defualt zeroed state until modified by handshake
uint32_t sessionID = 0; std::shared_ptr<uint64_t> sessionID = std::make_shared<uint64_t>(0);
initialState.aesKey = aesKey;
initialState.sessionID = sessionID;
ColumnLynx::ClientSession::getInstance().setClientState(std::make_shared<ClientState>(std::move(initialState))); // Set initial state
if (insecureMode) { if (insecureMode) {
warn("You have started the client with the --ignore-whitelist. This means that the client will NOT attempt to verify the server's public key. This is INSECURE and SHOULDN'T be used!"); warn("You have started the client with the --ignore-whitelist. This means that the client will NOT attempt to verify the server's public key. This is INSECURE and SHOULDN'T be used!");
} }
asio::io_context io; asio::io_context io;
auto client = std::make_shared<ColumnLynx::Net::TCP::TCPClient>(io, host, port); // TODO: Move to ClientSession state auto client = std::make_shared<ColumnLynx::Net::TCP::TCPClient>(io, host, port, sodiumWrapper, aesKey, sessionID, insecureMode, configPath, tun);
auto udpClient = std::make_shared<ColumnLynx::Net::UDP::UDPClient>(io, host, port); auto udpClient = std::make_shared<ColumnLynx::Net::UDP::UDPClient>(io, host, port, aesKey, sessionID, tun);
client->start(); client->start();
udpClient->start(); udpClient->start();

View File

@@ -46,13 +46,10 @@ namespace ColumnLynx::Net::TCP {
std::vector<uint8_t> payload; std::vector<uint8_t> payload;
payload.reserve(1 + crypto_box_PUBLICKEYBYTES); payload.reserve(1 + crypto_box_PUBLICKEYBYTES);
payload.push_back(Utils::protocolVersion()); payload.push_back(Utils::protocolVersion());
Utils::log("Using protocol version: " + std::to_string(Utils::protocolVersion()));
/*payload.insert(payload.end(), /*payload.insert(payload.end(),
mLibSodiumWrapper->getXPublicKey(), mLibSodiumWrapper->getXPublicKey(),
mLibSodiumWrapper->getXPublicKey() + crypto_box_PUBLICKEYBYTES mLibSodiumWrapper->getXPublicKey() + crypto_box_PUBLICKEYBYTES
);*/ );*/
const auto& mLibSodiumWrapper = ClientSession::getInstance().getSodiumWrapper();
payload.insert(payload.end(), payload.insert(payload.end(),
mLibSodiumWrapper->getPublicKey(), mLibSodiumWrapper->getPublicKey(),
mLibSodiumWrapper->getPublicKey() + crypto_sign_PUBLICKEYBYTES mLibSodiumWrapper->getPublicKey() + crypto_sign_PUBLICKEYBYTES
@@ -134,8 +131,10 @@ namespace ColumnLynx::Net::TCP {
mHandler->socket().close(ec); mHandler->socket().close(ec);
mConnected = false; mConnected = false;
ClientSession::getInstance().setAESKey({}); // Clear AES key with all zeros mGlobalKeyRef = nullptr;
ClientSession::getInstance().setSessionID(0); if (mSessionIDRef) {
*mSessionIDRef = 0;
}
return; return;
} }
@@ -156,10 +155,9 @@ namespace ColumnLynx::Net::TCP {
Utils::log("Received server identity. Public Key: " + hexServerPub); Utils::log("Received server identity. Public Key: " + hexServerPub);
// Verify pubkey against whitelisted_keys // Verify pubkey against whitelisted_keys
const std::string& configPath = ClientSession::getInstance().getConfigPath(); std::vector<std::string> whitelistedKeys = Utils::getWhitelistedKeys(mConfigDirPath);
std::vector<std::string> whitelistedKeys = Utils::getWhitelistedKeys(configPath);
if (std::find(whitelistedKeys.begin(), whitelistedKeys.end(), Utils::bytesToHexString(mServerPublicKey, 32)) == whitelistedKeys.end()) { // Key verification is handled in later steps of the handshake if (std::find(whitelistedKeys.begin(), whitelistedKeys.end(), Utils::bytesToHexString(mServerPublicKey, 32)) == whitelistedKeys.end()) { // Key verification is handled in later steps of the handshake
if (!ClientSession::getInstance().isInsecureMode()) { if (!mInsecureMode) {
Utils::error("Server public key not in whitelisted_keys. Terminating connection."); Utils::error("Server public key not in whitelisted_keys. Terminating connection.");
disconnect(); disconnect();
return; return;
@@ -195,14 +193,16 @@ namespace ColumnLynx::Net::TCP {
// Generate AES key and send confirmation // Generate AES key and send confirmation
mConnectionAESKey = Utils::LibSodiumWrapper::generateRandom256Bit(); mConnectionAESKey = Utils::LibSodiumWrapper::generateRandom256Bit();
ClientSession::getInstance().setAESKey(mConnectionAESKey); if (mGlobalKeyRef) { // Copy to the global reference
std::copy(mConnectionAESKey.begin(), mConnectionAESKey.end(), mGlobalKeyRef->begin());
}
AsymNonce nonce{}; AsymNonce nonce{};
randombytes_buf(nonce.data(), nonce.size()); randombytes_buf(nonce.data(), nonce.size());
// TODO: This is pretty redundant, it should return the required type directly // TODO: This is pretty redundant, it should return the required type directly
std::array<uint8_t, 32> arrayPrivateKey; std::array<uint8_t, 32> arrayPrivateKey;
std::copy(ClientSession::getInstance().getSodiumWrapper()->getXPrivateKey(), std::copy(mLibSodiumWrapper->getXPrivateKey(),
ClientSession::getInstance().getSodiumWrapper()->getXPrivateKey() + 32, mLibSodiumWrapper->getXPrivateKey() + 32,
arrayPrivateKey.begin()); arrayPrivateKey.begin());
std::vector<uint8_t> encr = Utils::LibSodiumWrapper::encryptAsymmetric( std::vector<uint8_t> encr = Utils::LibSodiumWrapper::encryptAsymmetric(
@@ -245,18 +245,19 @@ namespace ColumnLynx::Net::TCP {
std::memcpy(&mConnectionSessionID, decrypted.data(), sizeof(mConnectionSessionID)); std::memcpy(&mConnectionSessionID, decrypted.data(), sizeof(mConnectionSessionID));
std::memcpy(&mTunConfig, decrypted.data() + sizeof(mConnectionSessionID), sizeof(Protocol::TunConfig)); std::memcpy(&mTunConfig, decrypted.data() + sizeof(mConnectionSessionID), sizeof(Protocol::TunConfig));
mConnectionSessionID = ntohl(mConnectionSessionID); mConnectionSessionID = Utils::cbe64toh(mConnectionSessionID);
Utils::log("Connection established with Session ID: " + std::to_string(mConnectionSessionID)); Utils::log("Connection established with Session ID: " + std::to_string(mConnectionSessionID));
ClientSession::getInstance().setSessionID(mConnectionSessionID); if (mSessionIDRef) { // Copy to the global reference
*mSessionIDRef = mConnectionSessionID;
}
uint32_t clientIP = ntohl(mTunConfig.clientIP); uint32_t clientIP = ntohl(mTunConfig.clientIP);
uint32_t serverIP = ntohl(mTunConfig.serverIP); uint32_t serverIP = ntohl(mTunConfig.serverIP);
uint8_t prefixLen = mTunConfig.prefixLength; uint8_t prefixLen = mTunConfig.prefixLength;
uint16_t mtu = mTunConfig.mtu; uint16_t mtu = mTunConfig.mtu;
const auto& mTun = ClientSession::getInstance().getVirtualInterface();
if (mTun) { if (mTun) {
mTun->configureIP(clientIP, serverIP, prefixLen, mtu); mTun->configureIP(clientIP, serverIP, prefixLen, mtu);
} }

View File

@@ -46,14 +46,9 @@ namespace ColumnLynx::Net::UDP {
void UDPClient::sendMessage(const std::string& data) { void UDPClient::sendMessage(const std::string& data) {
UDPPacketHeader hdr{}; UDPPacketHeader hdr{};
uint8_t nonce[12]; randombytes_buf(hdr.nonce.data(), hdr.nonce.size());
uint32_t prefix = ClientSession::getInstance().getNoncePrefix();
uint64_t sendCount = ClientSession::getInstance().getSendCount();
memcpy(nonce, &prefix, sizeof(uint32_t)); // Prefix nonce with client-specific random value
memcpy(nonce + sizeof(uint32_t), &sendCount, sizeof(uint64_t)); // Use send count as nonce suffix to ensure uniqueness
std::copy_n(nonce, 12, hdr.nonce.data());
if (ClientSession::getInstance().getAESKey().empty() || ClientSession::getInstance().getSessionID() == 0) { if (mAesKeyRef == nullptr || mSessionIDRef == nullptr) {
Utils::error("UDP Client AES key or Session ID reference is null!"); Utils::error("UDP Client AES key or Session ID reference is null!");
return; return;
} }
@@ -62,28 +57,24 @@ namespace ColumnLynx::Net::UDP {
auto encryptedPayload = Utils::LibSodiumWrapper::encryptMessage( auto encryptedPayload = Utils::LibSodiumWrapper::encryptMessage(
reinterpret_cast<const uint8_t*>(data.data()), data.size(), reinterpret_cast<const uint8_t*>(data.data()), data.size(),
ClientSession::getInstance().getAESKey(), hdr.nonce, "udp-data" *mAesKeyRef, hdr.nonce, "udp-data"
//std::string(reinterpret_cast<const char*>(&mSessionIDRef), sizeof(uint64_t)) //std::string(reinterpret_cast<const char*>(&mSessionIDRef), sizeof(uint64_t))
); );
std::vector<uint8_t> packet; std::vector<uint8_t> packet;
packet.reserve(sizeof(UDPPacketHeader) + encryptedPayload.size()); packet.reserve(sizeof(UDPPacketHeader) + sizeof(uint64_t) + encryptedPayload.size());
packet.insert(packet.end(), packet.insert(packet.end(),
reinterpret_cast<uint8_t*>(&hdr), reinterpret_cast<uint8_t*>(&hdr),
reinterpret_cast<uint8_t*>(&hdr) + sizeof(UDPPacketHeader) reinterpret_cast<uint8_t*>(&hdr) + sizeof(UDPPacketHeader)
); );
uint32_t sessionID = static_cast<uint32_t>(ClientSession::getInstance().getSessionID());
uint32_t sessionIDNet = sessionID;
packet.insert(packet.end(), packet.insert(packet.end(),
reinterpret_cast<uint8_t*>(&sessionIDNet), reinterpret_cast<uint8_t*>(mSessionIDRef.get()),
reinterpret_cast<uint8_t*>(&sessionIDNet) + sizeof(uint32_t) reinterpret_cast<uint8_t*>(mSessionIDRef.get()) + sizeof(uint64_t)
); );
packet.insert(packet.end(), encryptedPayload.begin(), encryptedPayload.end()); packet.insert(packet.end(), encryptedPayload.begin(), encryptedPayload.end());
mSocket.send_to(asio::buffer(packet), mRemoteEndpoint); mSocket.send_to(asio::buffer(packet), mRemoteEndpoint);
Utils::debug("Sent UDP packet of size " + std::to_string(packet.size())); Utils::debug("Sent UDP packet of size " + std::to_string(packet.size()));
ClientSession::getInstance().incrementSendCount();
} }
void UDPClient::stop() { void UDPClient::stop() {
@@ -116,7 +107,7 @@ namespace ColumnLynx::Net::UDP {
} }
void UDPClient::mHandlePacket(std::size_t bytes) { void UDPClient::mHandlePacket(std::size_t bytes) {
if (bytes < sizeof(UDPPacketHeader) + sizeof(uint32_t)) { if (bytes < sizeof(UDPPacketHeader) + sizeof(uint64_t)) {
Utils::warn("UDP Client received packet too small to process."); Utils::warn("UDP Client received packet too small to process.");
return; return;
} }
@@ -126,28 +117,27 @@ namespace ColumnLynx::Net::UDP {
std::memcpy(&hdr, mRecvBuffer.data(), sizeof(UDPPacketHeader)); std::memcpy(&hdr, mRecvBuffer.data(), sizeof(UDPPacketHeader));
// Parse session ID // Parse session ID
uint32_t sessionIDNet; uint64_t sessionID;
std::memcpy(&sessionIDNet, mRecvBuffer.data() + sizeof(UDPPacketHeader), sizeof(uint32_t)); std::memcpy(&sessionID, mRecvBuffer.data() + sizeof(UDPPacketHeader), sizeof(uint64_t));
uint32_t sessionID = ntohl(sessionIDNet);
if (sessionID != ClientSession::getInstance().getSessionID()) { if (sessionID != *mSessionIDRef) {
Utils::warn("This packet that isn't for me! Dropping!"); Utils::warn("Got packet that isn't for me! Dropping!");
return; return;
} }
// Decrypt payload // Decrypt payload
std::vector<uint8_t> ciphertext( std::vector<uint8_t> ciphertext(
mRecvBuffer.begin() + sizeof(UDPPacketHeader) + sizeof(uint32_t), mRecvBuffer.begin() + sizeof(UDPPacketHeader) + sizeof(uint64_t),
mRecvBuffer.begin() + bytes mRecvBuffer.begin() + bytes
); );
if (ClientSession::getInstance().getAESKey().empty()) { if (mAesKeyRef == nullptr) {
Utils::error("UDP Client AES key reference is null!"); Utils::error("UDP Client AES key reference is null!");
return; return;
} }
std::vector<uint8_t> plaintext = Utils::LibSodiumWrapper::decryptMessage( std::vector<uint8_t> plaintext = Utils::LibSodiumWrapper::decryptMessage(
ciphertext.data(), ciphertext.size(), ClientSession::getInstance().getAESKey(), hdr.nonce, "udp-data" ciphertext.data(), ciphertext.size(), *mAesKeyRef, hdr.nonce, "udp-data"
//std::string(reinterpret_cast<const char*>(&mSessionIDRef), sizeof(uint64_t)) //std::string(reinterpret_cast<const char*>(&mSessionIDRef), sizeof(uint64_t))
); );
@@ -159,7 +149,6 @@ namespace ColumnLynx::Net::UDP {
Utils::debug("UDP Client received packet from " + mRemoteEndpoint.address().to_string() + " - Packet size: " + std::to_string(bytes)); Utils::debug("UDP Client received packet from " + mRemoteEndpoint.address().to_string() + " - Packet size: " + std::to_string(bytes));
// Write to TUN // Write to TUN
const auto& mTunRef = ClientSession::getInstance().getVirtualInterface();
if (mTunRef) { if (mTunRef) {
mTunRef->writePacket(plaintext); mTunRef->writePacket(plaintext);
} }

View File

@@ -5,13 +5,13 @@
#include <columnlynx/common/net/session_registry.hpp> #include <columnlynx/common/net/session_registry.hpp>
namespace ColumnLynx::Net { namespace ColumnLynx::Net {
void SessionRegistry::put(uint32_t sessionID, std::shared_ptr<SessionState> state) { void SessionRegistry::put(uint64_t sessionID, std::shared_ptr<SessionState> state) {
std::unique_lock lock(mMutex); std::unique_lock lock(mMutex);
mSessions[sessionID] = std::move(state); mSessions[sessionID] = std::move(state);
mIPSessions[mSessions[sessionID]->clientTunIP] = mSessions[sessionID]; mIPSessions[mSessions[sessionID]->clientTunIP] = mSessions[sessionID];
} }
std::shared_ptr<const SessionState> SessionRegistry::get(uint32_t sessionID) const { std::shared_ptr<const SessionState> SessionRegistry::get(uint64_t sessionID) const {
std::shared_lock lock(mMutex); std::shared_lock lock(mMutex);
auto it = mSessions.find(sessionID); auto it = mSessions.find(sessionID);
return (it == mSessions.end()) ? nullptr : it->second; return (it == mSessions.end()) ? nullptr : it->second;
@@ -23,14 +23,14 @@ namespace ColumnLynx::Net {
return (it == mIPSessions.end()) ? nullptr : it->second; return (it == mIPSessions.end()) ? nullptr : it->second;
} }
std::unordered_map<uint32_t, std::shared_ptr<SessionState>> SessionRegistry::snapshot() const { std::unordered_map<uint64_t, std::shared_ptr<SessionState>> SessionRegistry::snapshot() const {
std::unordered_map<uint32_t, std::shared_ptr<SessionState>> snap; std::unordered_map<uint64_t, std::shared_ptr<SessionState>> snap;
std::shared_lock lock(mMutex); std::shared_lock lock(mMutex);
snap = mSessions; snap = mSessions;
return snap; return snap;
} }
void SessionRegistry::erase(uint32_t sessionID) { void SessionRegistry::erase(uint64_t sessionID) {
std::unique_lock lock(mMutex); std::unique_lock lock(mMutex);
mSessions.erase(sessionID); mSessions.erase(sessionID);
} }
@@ -60,11 +60,6 @@ namespace ColumnLynx::Net {
return static_cast<int>(mSessions.size()); return static_cast<int>(mSessions.size());
} }
bool SessionRegistry::exists(uint32_t sessionID) const {
std::shared_lock lock(mMutex);
return mSessions.find(sessionID) != mSessions.end();
}
uint32_t SessionRegistry::getFirstAvailableIP(uint32_t baseIP, uint8_t mask) const { uint32_t SessionRegistry::getFirstAvailableIP(uint32_t baseIP, uint8_t mask) const {
std::shared_lock lock(mMutex); std::shared_lock lock(mMutex);
@@ -82,7 +77,7 @@ namespace ColumnLynx::Net {
return 0; return 0;
} }
void SessionRegistry::lockIP(uint32_t sessionID, uint32_t ip) { void SessionRegistry::lockIP(uint64_t sessionID, uint32_t ip) {
std::unique_lock lock(mMutex); std::unique_lock lock(mMutex);
mSessionIPs[sessionID] = ip; mSessionIPs[sessionID] = ip;
@@ -92,7 +87,7 @@ namespace ColumnLynx::Net {
mIPSessions[ip] = mSessions.find(sessionID)->second; mIPSessions[ip] = mSessions.find(sessionID)->second;
} }
void SessionRegistry::deallocIP(uint32_t sessionID) { void SessionRegistry::deallocIP(uint64_t sessionID) {
std::unique_lock lock(mMutex); std::unique_lock lock(mMutex);
auto it = mSessionIPs.find(sessionID); auto it = mSessionIPs.find(sessionID);

View File

@@ -85,7 +85,7 @@ namespace ColumnLynx::Utils {
} }
std::string getVersion() { std::string getVersion() {
return "1.1.0"; return "1.0.1";
} }
unsigned short serverPort() { unsigned short serverPort() {
@@ -93,7 +93,7 @@ namespace ColumnLynx::Utils {
} }
unsigned char protocolVersion() { unsigned char protocolVersion() {
return 2; return 1;
} }
std::string bytesToHexString(const uint8_t* bytes, size_t length) { std::string bytesToHexString(const uint8_t* bytes, size_t length) {

View File

@@ -15,7 +15,6 @@
#include <unordered_map> #include <unordered_map>
#include <cxxopts.hpp> #include <cxxopts.hpp>
#include <columnlynx/common/net/virtual_interface.hpp> #include <columnlynx/common/net/virtual_interface.hpp>
#include <columnlynx/server/server_session.hpp>
#if defined(__WIN32__) #if defined(__WIN32__)
#include <windows.h> #include <windows.h>
@@ -70,8 +69,6 @@ int main(int argc, char** argv) {
//WintunInitialize(); //WintunInitialize();
#endif #endif
struct ServerState serverState{};
// Get the config path, ENV > CLI > /etc/columnlynx // Get the config path, ENV > CLI > /etc/columnlynx
std::string configPath = optionsObj["config-dir"].as<std::string>(); std::string configPath = optionsObj["config-dir"].as<std::string>();
const char* envConfigPath = std::getenv("COLUMNLYNX_CONFIG_DIR"); const char* envConfigPath = std::getenv("COLUMNLYNX_CONFIG_DIR");
@@ -87,23 +84,11 @@ int main(int argc, char** argv) {
#endif #endif
} }
serverState.configPath = configPath; std::unordered_map<std::string, std::string> config = Utils::getConfigMap(configPath + "server_config");
#if defined(DEBUG)
std::unordered_map<std::string, std::string> config = Utils::getConfigMap(configPath + "server_config", { "NETWORK", "SUBNET_MASK" });
#else
// A production server should never use random keys. If the config file cannot be read or does not contain keys, the server will fail to start.
std::unordered_map<std::string, std::string> config = Utils::getConfigMap(configPath + "server_config", { "NETWORK", "SUBNET_MASK", "SERVER_PUBLIC_KEY", "SERVER_PRIVATE_KEY" });
#endif
serverState.serverConfig = config;
std::shared_ptr<VirtualInterface> tun = std::make_shared<VirtualInterface>(optionsObj["interface"].as<std::string>()); std::shared_ptr<VirtualInterface> tun = std::make_shared<VirtualInterface>(optionsObj["interface"].as<std::string>());
log("Using virtual interface: " + tun->getName()); log("Using virtual interface: " + tun->getName());
// Store a reference to the tun in the serverState, it will increment and keep a safe reference (we love shared_ptrs)
serverState.virtualInterface = tun;
// Generate a temporary keypair, replace with actual CA signed keys later (Note, these are stored in memory) // Generate a temporary keypair, replace with actual CA signed keys later (Note, these are stored in memory)
std::shared_ptr<LibSodiumWrapper> sodiumWrapper = std::make_shared<LibSodiumWrapper>(); std::shared_ptr<LibSodiumWrapper> sodiumWrapper = std::make_shared<LibSodiumWrapper>();
@@ -132,24 +117,19 @@ int main(int argc, char** argv) {
log("Server public key: " + bytesToHexString(sodiumWrapper->getPublicKey(), crypto_sign_PUBLICKEYBYTES)); log("Server public key: " + bytesToHexString(sodiumWrapper->getPublicKey(), crypto_sign_PUBLICKEYBYTES));
serverState.sodiumWrapper = sodiumWrapper; std::shared_ptr<bool> hostRunning = std::make_shared<bool>(true);
serverState.ipv4Only = ipv4Only;
serverState.hostRunning = true;
// Store the global state; from now on, it should only be accessed through the ServerSession singleton, which will ensure thread safety with its internal mutex
ServerSession::getInstance().setServerState(std::make_shared<ServerState>(std::move(serverState)));
asio::io_context io; asio::io_context io;
auto server = std::make_shared<TCPServer>(io, serverPort()); auto server = std::make_shared<TCPServer>(io, serverPort(), sodiumWrapper, hostRunning, configPath, ipv4Only);
auto udpServer = std::make_shared<UDPServer>(io, serverPort()); auto udpServer = std::make_shared<UDPServer>(io, serverPort(), hostRunning, ipv4Only, tun);
asio::signal_set signals(io, SIGINT, SIGTERM); asio::signal_set signals(io, SIGINT, SIGTERM);
signals.async_wait([&](const std::error_code&, int) { signals.async_wait([&](const std::error_code&, int) {
log("Received termination signal. Shutting down server gracefully."); log("Received termination signal. Shutting down server gracefully.");
done = 1; done = 1;
asio::post(io, [&]() { asio::post(io, [&]() {
ServerSession::getInstance().setHostRunning(false); *hostRunning = false;
server->stop(); server->stop();
udpServer->stop(); udpServer->stop();
}); });

View File

@@ -66,7 +66,7 @@ namespace ColumnLynx::Net::TCP {
Utils::log("Initiated graceful disconnect (half-close) to " + mRemoteIP); Utils::log("Initiated graceful disconnect (half-close) to " + mRemoteIP);
} }
uint32_t TCPConnection::getSessionID() const { uint64_t TCPConnection::getSessionID() const {
return mConnectionSessionID; return mConnectionSessionID;
} }
@@ -145,7 +145,7 @@ namespace ColumnLynx::Net::TCP {
Utils::debug("Key attempted connect: " + Utils::bytesToHexString(signPk.data(), signPk.size())); Utils::debug("Key attempted connect: " + Utils::bytesToHexString(signPk.data(), signPk.size()));
std::vector<std::string> whitelistedKeys = Utils::getWhitelistedKeys(ServerSession::getInstance().getConfigPath()); std::vector<std::string> whitelistedKeys = Utils::getWhitelistedKeys(mConfigDirPath);
if (std::find(whitelistedKeys.begin(), whitelistedKeys.end(), Utils::bytesToHexString(signPk.data(), signPk.size())) == whitelistedKeys.end()) { if (std::find(whitelistedKeys.begin(), whitelistedKeys.end(), Utils::bytesToHexString(signPk.data(), signPk.size())) == whitelistedKeys.end()) {
Utils::warn("Non-whitelisted client attempted to connect, terminating. Client IP: " + reqAddr); Utils::warn("Non-whitelisted client attempted to connect, terminating. Client IP: " + reqAddr);
@@ -156,7 +156,7 @@ namespace ColumnLynx::Net::TCP {
Utils::debug("Client " + reqAddr + " passed authorized_keys"); Utils::debug("Client " + reqAddr + " passed authorized_keys");
mHandler->sendMessage(ServerMessageType::HANDSHAKE_IDENTIFY, Utils::uint8ArrayToString(ServerSession::getInstance().getSodiumWrapper()->getPublicKey(), crypto_sign_PUBLICKEYBYTES)); // This public key should always exist mHandler->sendMessage(ServerMessageType::HANDSHAKE_IDENTIFY, Utils::uint8ArrayToString(mLibSodiumWrapper->getPublicKey(), crypto_sign_PUBLICKEYBYTES)); // This public key should always exist
break; break;
} }
case ClientMessageType::HANDSHAKE_CHALLENGE: { case ClientMessageType::HANDSHAKE_CHALLENGE: {
@@ -169,7 +169,7 @@ namespace ColumnLynx::Net::TCP {
// Sign the challenge // Sign the challenge
Signature sig = Utils::LibSodiumWrapper::signMessage( Signature sig = Utils::LibSodiumWrapper::signMessage(
challengeData, sizeof(challengeData), challengeData, sizeof(challengeData),
ServerSession::getInstance().getSodiumWrapper()->getPrivateKey() mLibSodiumWrapper->getPrivateKey()
); );
mHandler->sendMessage(ServerMessageType::HANDSHAKE_CHALLENGE_RESPONSE, Utils::uint8ArrayToString(sig.data(), sig.size())); // Placeholder response mHandler->sendMessage(ServerMessageType::HANDSHAKE_CHALLENGE_RESPONSE, Utils::uint8ArrayToString(sig.data(), sig.size())); // Placeholder response
@@ -191,8 +191,8 @@ namespace ColumnLynx::Net::TCP {
std::memcpy(ciphertext.data(), data.data() + nonce.size(), ciphertext.size()); std::memcpy(ciphertext.data(), data.data() + nonce.size(), ciphertext.size());
try { try {
std::array<uint8_t, 32> arrayPrivateKey; std::array<uint8_t, 32> arrayPrivateKey;
std::copy(ServerSession::getInstance().getSodiumWrapper()->getXPrivateKey(), std::copy(mLibSodiumWrapper->getXPrivateKey(),
ServerSession::getInstance().getSodiumWrapper()->getXPrivateKey() + 32, mLibSodiumWrapper->getXPrivateKey() + 32,
arrayPrivateKey.begin()); arrayPrivateKey.begin());
// Decrypt the AES key using the client's public key and server's private key // Decrypt the AES key using the client's public key and server's private key
@@ -211,18 +211,14 @@ namespace ColumnLynx::Net::TCP {
std::memcpy(mConnectionAESKey.data(), decrypted.data(), decrypted.size()); std::memcpy(mConnectionAESKey.data(), decrypted.data(), decrypted.size());
// Make a Session ID - unique and not zero (zero is reserved for invalid sessions) // Make a Session ID
do {
randombytes_buf(&mConnectionSessionID, sizeof(mConnectionSessionID)); randombytes_buf(&mConnectionSessionID, sizeof(mConnectionSessionID));
} while (SessionRegistry::getInstance().exists(mConnectionSessionID) || mConnectionSessionID == 0); // Regenerate if it already exists or is zero (zero is reserved for invalid sessions)
// Encrypt the Session ID with the established AES key (using symmetric encryption, nonce can be all zeros for this purpose) // Encrypt the Session ID with the established AES key (using symmetric encryption, nonce can be all zeros for this purpose)
Nonce symNonce{}; // All zeros Nonce symNonce{}; // All zeros
const auto& serverConfig = ServerSession::getInstance().getRawServerConfig(); 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
std::string networkString = serverConfig.find("NETWORK")->second; // The load check guarantees that this value exists
uint8_t configMask = std::stoi(serverConfig.find("SUBNET_MASK")->second); // Same deal here
uint32_t baseIP = Net::VirtualInterface::stringToIpv4(networkString); uint32_t baseIP = Net::VirtualInterface::stringToIpv4(networkString);
@@ -249,11 +245,11 @@ namespace ColumnLynx::Net::TCP {
tunConfig.dns1 = htonl(0x08080808); // 8.8.8.8 tunConfig.dns1 = htonl(0x08080808); // 8.8.8.8
tunConfig.dns2 = 0; tunConfig.dns2 = 0;
uint32_t sessionIDNet = htonl(mConnectionSessionID); uint64_t sessionIDNet = Utils::chtobe64(mConnectionSessionID);
std::vector<uint8_t> payload(sizeof(uint32_t) + sizeof(tunConfig)); std::vector<uint8_t> payload(sizeof(uint64_t) + sizeof(tunConfig));
std::memcpy(payload.data(), &sessionIDNet, sizeof(uint32_t)); std::memcpy(payload.data(), &sessionIDNet, sizeof(uint64_t));
std::memcpy(payload.data() + sizeof(uint32_t), &tunConfig, sizeof(tunConfig)); std::memcpy(payload.data() + sizeof(uint64_t), &tunConfig, sizeof(tunConfig));
std::vector<uint8_t> encryptedPayload = Utils::LibSodiumWrapper::encryptMessage( std::vector<uint8_t> encryptedPayload = Utils::LibSodiumWrapper::encryptMessage(
payload.data(), payload.size(), payload.data(), payload.size(),
@@ -265,7 +261,6 @@ namespace ColumnLynx::Net::TCP {
// Add to session registry // Add to session registry
Utils::log("Handshake with " + reqAddr + " completed successfully. Session ID assigned (" + std::to_string(mConnectionSessionID) + ")."); Utils::log("Handshake with " + reqAddr + " completed successfully. Session ID assigned (" + std::to_string(mConnectionSessionID) + ").");
auto session = std::make_shared<SessionState>(mConnectionAESKey, std::chrono::hours(12), clientIP, htonl(0x0A0A0001), mConnectionSessionID); auto session = std::make_shared<SessionState>(mConnectionAESKey, std::chrono::hours(12), clientIP, htonl(0x0A0A0001), mConnectionSessionID);
session->setBaseNonce(); // Set it
SessionRegistry::getInstance().put(mConnectionSessionID, std::move(session)); SessionRegistry::getInstance().put(mConnectionSessionID, std::move(session));
SessionRegistry::getInstance().lockIP(mConnectionSessionID, clientIP); SessionRegistry::getInstance().lockIP(mConnectionSessionID, clientIP);

View File

@@ -27,13 +27,16 @@ namespace ColumnLynx::Net::TCP {
} }
Utils::error("Accept failed: " + ec.message()); Utils::error("Accept failed: " + ec.message());
// Try again only if still running // Try again only if still running
if (ServerSession::getInstance().isHostRunning() && mAcceptor.is_open()) if (mHostRunning && *mHostRunning && mAcceptor.is_open())
mStartAccept(); mStartAccept();
return; return;
} }
auto client = TCPConnection::create( auto client = TCPConnection::create(
std::move(socket), std::move(socket),
mSodiumWrapper,
&mRawServerConfig,
mConfigDirPath,
[this](std::shared_ptr<TCPConnection> c) { [this](std::shared_ptr<TCPConnection> c) {
mClients.erase(c); mClients.erase(c);
Utils::log("Client removed."); Utils::log("Client removed.");
@@ -43,7 +46,7 @@ namespace ColumnLynx::Net::TCP {
client->start(); client->start();
Utils::log("Accepted new client connection."); Utils::log("Accepted new client connection.");
if (ServerSession::getInstance().isHostRunning() && mAcceptor.is_open()) if (mHostRunning && *mHostRunning && mAcceptor.is_open())
mStartAccept(); mStartAccept();
} }
); );

View File

@@ -16,27 +16,26 @@ namespace ColumnLynx::Net::UDP {
if (ec) { if (ec) {
if (ec == asio::error::operation_aborted) return; // Socket closed if (ec == asio::error::operation_aborted) return; // Socket closed
// Other recv error // Other recv error
if (ServerSession::getInstance().isHostRunning()) mStartReceive(); if (mHostRunning && *mHostRunning) mStartReceive();
return; return;
} }
if (bytes > 0) mHandlePacket(bytes); if (bytes > 0) mHandlePacket(bytes);
if (ServerSession::getInstance().isHostRunning()) mStartReceive(); if (mHostRunning && *mHostRunning) mStartReceive();
} }
); );
} }
void UDPServer::mHandlePacket(std::size_t bytes) { void UDPServer::mHandlePacket(std::size_t bytes) {
if (bytes < sizeof(UDPPacketHeader) + sizeof(uint32_t)) if (bytes < sizeof(UDPPacketHeader))
return; return;
const auto* hdr = reinterpret_cast<UDPPacketHeader*>(mRecvBuffer.data()); const auto* hdr = reinterpret_cast<UDPPacketHeader*>(mRecvBuffer.data());
// Get plaintext session ID (first 4 bytes after header, in network byte order) // Get plaintext session ID (assuming first 8 bytes after nonce (header))
uint32_t sessionIDNet = 0; uint64_t sessionID = 0;
std::memcpy(&sessionIDNet, mRecvBuffer.data() + sizeof(UDPPacketHeader), sizeof(uint32_t)); std::memcpy(&sessionID, mRecvBuffer.data() + sizeof(UDPPacketHeader), sizeof(uint64_t));
uint32_t sessionID = sessionIDNet; // ntohl(sessionIDNet); --- IGNORE ---
auto it = mRecvBuffer.begin() + sizeof(UDPPacketHeader) + sizeof(uint32_t); auto it = mRecvBuffer.begin() + sizeof(UDPPacketHeader) + sizeof(uint64_t);
std::vector<uint8_t> encryptedPayload(it, mRecvBuffer.begin() + bytes); std::vector<uint8_t> encryptedPayload(it, mRecvBuffer.begin() + bytes);
// Get associated session state // Get associated session state
@@ -55,7 +54,7 @@ namespace ColumnLynx::Net::UDP {
encryptedPayload.data(), encryptedPayload.size(), encryptedPayload.data(), encryptedPayload.size(),
session->aesKey, session->aesKey,
hdr->nonce, "udp-data" hdr->nonce, "udp-data"
//std::string(reinterpret_cast<const char*>(&sessionID), sizeof(uint32_t)) //std::string(reinterpret_cast<const char*>(&sessionID), sizeof(uint64_t))
); );
Utils::debug("Passed decryption"); Utils::debug("Passed decryption");
@@ -77,7 +76,7 @@ namespace ColumnLynx::Net::UDP {
} }
} }
void UDPServer::sendData(uint32_t sessionID, const std::string& data) { void UDPServer::sendData(const uint64_t sessionID, const std::string& data) {
// Find the IPv4/IPv6 endpoint for the session // Find the IPv4/IPv6 endpoint for the session
std::shared_ptr<const SessionState> session = SessionRegistry::getInstance().get(sessionID); std::shared_ptr<const SessionState> session = SessionRegistry::getInstance().get(sessionID);
if (!session) { if (!session) {
@@ -93,29 +92,23 @@ namespace ColumnLynx::Net::UDP {
// Prepare packet // Prepare packet
UDPPacketHeader hdr{}; UDPPacketHeader hdr{};
uint8_t nonce[12]; randombytes_buf(hdr.nonce.data(), hdr.nonce.size());
uint32_t prefix = session->noncePrefix;
uint64_t sendCount = const_cast<SessionState*>(session.get())->send_ctr.fetch_add(1, std::memory_order_relaxed);
memcpy(nonce, &prefix, sizeof(uint32_t)); // Prefix nonce
memcpy(nonce + sizeof(uint32_t), &sendCount, sizeof(uint64_t)); // Use send count as nonce suffix to ensure uniqueness
std::copy_n(nonce, 12, hdr.nonce.data());
auto encryptedPayload = Utils::LibSodiumWrapper::encryptMessage( auto encryptedPayload = Utils::LibSodiumWrapper::encryptMessage(
reinterpret_cast<const uint8_t*>(data.data()), data.size(), reinterpret_cast<const uint8_t*>(data.data()), data.size(),
session->aesKey, hdr.nonce, "udp-data" session->aesKey, hdr.nonce, "udp-data"
//std::string(reinterpret_cast<const char*>(&sessionID), sizeof(uint32_t)) //std::string(reinterpret_cast<const char*>(&sessionID), sizeof(uint64_t))
); );
std::vector<uint8_t> packet; std::vector<uint8_t> packet;
packet.reserve(sizeof(UDPPacketHeader) + sizeof(uint32_t) + encryptedPayload.size()); packet.reserve(sizeof(UDPPacketHeader) + sizeof(uint64_t) + encryptedPayload.size());
packet.insert(packet.end(), packet.insert(packet.end(),
reinterpret_cast<uint8_t*>(&hdr), reinterpret_cast<uint8_t*>(&hdr),
reinterpret_cast<uint8_t*>(&hdr) + sizeof(UDPPacketHeader) reinterpret_cast<uint8_t*>(&hdr) + sizeof(UDPPacketHeader)
); );
uint32_t sessionIDNet = htonl(sessionID);
packet.insert(packet.end(), packet.insert(packet.end(),
reinterpret_cast<const uint8_t*>(&sessionIDNet), reinterpret_cast<const uint8_t*>(&sessionID),
reinterpret_cast<const uint8_t*>(&sessionIDNet) + sizeof(sessionIDNet) reinterpret_cast<const uint8_t*>(&sessionID) + sizeof(sessionID)
); );
packet.insert(packet.end(), encryptedPayload.begin(), encryptedPayload.end()); packet.insert(packet.end(), encryptedPayload.begin(), encryptedPayload.end());

View File

@@ -1,92 +0,0 @@
// server_session.cpp - Client Session data for ColumnLynx
// Copyright (C) 2026 DcruBro
// Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#include <columnlynx/server/server_session.hpp>
namespace ColumnLynx {
std::shared_ptr<ServerState> ServerSession::getServerState() const {
std::shared_lock lock(mMutex);
return mServerState;
}
void ServerSession::setServerState(std::shared_ptr<ServerState> state) {
std::unique_lock lock(mMutex);
mServerState = std::move(state);
}
std::shared_ptr<Utils::LibSodiumWrapper> ServerSession::getSodiumWrapper() const {
std::shared_lock lock(mMutex);
return mServerState ? mServerState->sodiumWrapper : nullptr;
}
const std::string& ServerSession::getConfigPath() const {
static const std::string emptyString;
std::shared_ptr<ServerState> state = getServerState();
return state ? state->configPath : emptyString;
}
const std::unordered_map<std::string, std::string>& ServerSession::getRawServerConfig() const {
static const std::unordered_map<std::string, std::string> emptyMap;
std::shared_ptr<ServerState> state = getServerState();
return state ? state->serverConfig : emptyMap;
}
const std::shared_ptr<Net::VirtualInterface>& ServerSession::getVirtualInterface() const {
static const std::shared_ptr<Net::VirtualInterface> nullTun = nullptr;
std::shared_ptr<ServerState> state = getServerState();
return state ? state->virtualInterface : nullTun;
}
bool ServerSession::isIPv4Only() const {
std::shared_ptr<ServerState> state = getServerState();
return state ? state->ipv4Only : false;
}
bool ServerSession::isHostRunning() const {
std::shared_ptr<ServerState> state = getServerState();
return state ? state->hostRunning : false;
}
void ServerSession::setSodiumWrapper(std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper) {
std::unique_lock lock(mMutex);
if (!mServerState)
mServerState = std::make_shared<ServerState>();
mServerState->sodiumWrapper = std::move(sodiumWrapper);
}
void ServerSession::setConfigPath(const std::string& configPath) {
std::unique_lock lock(mMutex);
if (!mServerState)
mServerState = std::make_shared<ServerState>();
mServerState->configPath = configPath;
}
void ServerSession::setRawServerConfig(const std::unordered_map<std::string, std::string>& config) {
std::unique_lock lock(mMutex);
if (!mServerState)
mServerState = std::make_shared<ServerState>();
mServerState->serverConfig = config;
}
void ServerSession::setVirtualInterface(std::shared_ptr<Net::VirtualInterface> tun) {
std::unique_lock lock(mMutex);
if (!mServerState)
mServerState = std::make_shared<ServerState>();
mServerState->virtualInterface = std::move(tun);
}
void ServerSession::setIPv4Only(bool ipv4Only) {
std::unique_lock lock(mMutex);
if (!mServerState)
mServerState = std::make_shared<ServerState>();
mServerState->ipv4Only = ipv4Only;
}
void ServerSession::setHostRunning(bool hostRunning) {
std::unique_lock lock(mMutex);
if (!mServerState)
mServerState = std::make_shared<ServerState>();
mServerState->hostRunning = hostRunning;
}
}