From 5c8409b3127df4772236bd70f299183304f63e3e Mon Sep 17 00:00:00 2001 From: DcruBro Date: Thu, 13 Nov 2025 08:31:46 +0100 Subject: [PATCH 1/4] Added a basic TUN, testing the implementation. --- .../columnlynx/client/net/tcp/tcp_client.hpp | 7 +- .../columnlynx/client/net/udp/udp_client.hpp | 7 +- .../common/net/protocol_structs.hpp | 21 +++ .../common/net/session_registry.hpp | 52 +++++++- .../common/net/virtual_interface.hpp | 25 +++- .../server/net/tcp/tcp_connection.hpp | 1 + .../columnlynx/server/net/tcp/tcp_server.hpp | 1 + .../columnlynx/server/net/udp/udp_server.hpp | 9 +- src/client/main.cpp | 34 +++-- src/client/net/tcp/tcp_client.cpp | 14 +- src/client/net/udp/udp_client.cpp | 5 + src/common/virtual_interface.cpp | 122 +++++++++++++++--- src/server/main.cpp | 28 +++- src/server/server/net/tcp/tcp_connection.cpp | 28 +++- src/server/server/net/udp/udp_server.cpp | 8 +- 15 files changed, 310 insertions(+), 52 deletions(-) create mode 100644 include/columnlynx/common/net/protocol_structs.hpp diff --git a/include/columnlynx/client/net/tcp/tcp_client.hpp b/include/columnlynx/client/net/tcp/tcp_client.hpp index ae6fbd6..a0eabcb 100644 --- a/include/columnlynx/client/net/tcp/tcp_client.hpp +++ b/include/columnlynx/client/net/tcp/tcp_client.hpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include using asio::ip::tcp; @@ -25,7 +27,8 @@ namespace ColumnLynx::Net::TCP { Utils::LibSodiumWrapper* sodiumWrapper, std::array* aesKey, uint64_t* sessionIDRef, - bool* insecureMode) + bool* insecureMode, + VirtualInterface* tun = nullptr) : mResolver(ioContext), mSocket(ioContext), @@ -70,5 +73,7 @@ namespace ColumnLynx::Net::TCP { std::chrono::steady_clock::time_point mLastHeartbeatSent; int mMissedHeartbeats = 0; bool mIsHostDomain; + Protocol::TunConfig mTunConfig; + VirtualInterface* mTun = nullptr; }; } \ No newline at end of file diff --git a/include/columnlynx/client/net/udp/udp_client.hpp b/include/columnlynx/client/net/udp/udp_client.hpp index 0d804ec..ad21fda 100644 --- a/include/columnlynx/client/net/udp/udp_client.hpp +++ b/include/columnlynx/client/net/udp/udp_client.hpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace ColumnLynx::Net::UDP { class UDPClient { @@ -17,8 +18,9 @@ namespace ColumnLynx::Net::UDP { const std::string& host, const std::string& port, std::array* aesKeyRef, - uint64_t* sessionIDRef) - : mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port), mAesKeyRef(aesKeyRef), mSessionIDRef(sessionIDRef) { mStartReceive(); } + uint64_t* sessionIDRef, + VirtualInterface* tunRef = nullptr) + : mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port), mAesKeyRef(aesKeyRef), mSessionIDRef(sessionIDRef), mTunRef(tunRef) { mStartReceive(); } void start(); void sendMessage(const std::string& data = ""); @@ -35,6 +37,7 @@ namespace ColumnLynx::Net::UDP { std::string mPort; std::array* mAesKeyRef; uint64_t* mSessionIDRef; + VirtualInterface* mTunRef; std::array mRecvBuffer; // Adjust size as needed }; } \ No newline at end of file diff --git a/include/columnlynx/common/net/protocol_structs.hpp b/include/columnlynx/common/net/protocol_structs.hpp new file mode 100644 index 0000000..dbbc54f --- /dev/null +++ b/include/columnlynx/common/net/protocol_structs.hpp @@ -0,0 +1,21 @@ +// protocol_structs.hpp - Network Protocol Structures +// Copyright (C) 2025 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 + +namespace ColumnLynx::Protocol { + #pragma pack(push, 1) + struct TunConfig { + uint8_t version; + uint8_t prefixLength; + uint16_t mtu; + uint32_t serverIP; + uint32_t clientIP; + uint32_t dns1; + uint32_t dns2; + }; + #pragma pack(pop) +} \ No newline at end of file diff --git a/include/columnlynx/common/net/session_registry.hpp b/include/columnlynx/common/net/session_registry.hpp index ad21a2f..533d0ba 100644 --- a/include/columnlynx/common/net/session_registry.hpp +++ b/include/columnlynx/common/net/session_registry.hpp @@ -21,6 +21,9 @@ namespace ColumnLynx::Net { std::atomic sendCounter{0}; std::chrono::steady_clock::time_point created = std::chrono::steady_clock::now(); std::chrono::steady_clock::time_point expires{}; + uint32_t clientTunIP; + uint32_t serverTunIP; + uint64_t sessionID; Nonce base_nonce{}; ~SessionState() { sodium_memzero(aesKey.data(), aesKey.size()); } @@ -29,7 +32,7 @@ namespace ColumnLynx::Net { SessionState(SessionState&&) = default; SessionState& operator=(SessionState&&) = default; - explicit SessionState(const SymmetricKey& k, std::chrono::seconds ttl = std::chrono::hours(24)) : aesKey(k) { + 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; } @@ -46,6 +49,7 @@ namespace ColumnLynx::Net { void put(uint64_t sessionID, std::shared_ptr state) { std::unique_lock lock(mMutex); mSessions[sessionID] = std::move(state); + mIPSessions[mSessions[sessionID]->clientTunIP] = mSessions[sessionID]; } // Lookup @@ -55,6 +59,12 @@ namespace ColumnLynx::Net { return (it == mSessions.end()) ? nullptr : it->second; } + std::shared_ptr getByIP(uint32_t ip) const { + std::shared_lock lock(mMutex); + auto it = mIPSessions.find(ip); + return (it == mIPSessions.end()) ? nullptr : it->second; + } + std::unordered_map> snapshot() const { std::unordered_map> snap; std::shared_lock lock(mMutex); @@ -79,10 +89,50 @@ namespace ColumnLynx::Net { ++it; } } + + for (auto it = mIPSessions.begin(); it != mIPSessions.end(); ) { + if (it->second && it->second->expires <= now) { + it = mIPSessions.erase(it); + } else { + ++it; + } + } + } + + int size() const { + std::shared_lock lock(mMutex); + return static_cast(mSessions.size()); + } + + // IP management (simple for /24 subnet) + + uint32_t getFirstAvailableIP() const { + std::shared_lock lock(mMutex); + uint32_t baseIP = 0x0A0A0002; // 10.10.0.2 + + // TODO: Expand to support larger subnets + for (uint32_t offset = 0; offset < 254; offset++) { + uint32_t candidateIP = baseIP + offset; + if (mSessionIPs.find(candidateIP) == mSessionIPs.end()) { + return candidateIP; + } + } + } + + void lockIP(uint64_t sessionID, uint32_t ip) { + std::unique_lock lock(mMutex); + mSessionIPs[sessionID] = ip; + } + + void deallocIP(uint64_t sessionID) { + std::unique_lock lock(mMutex); + mSessionIPs.erase(sessionID); } private: mutable std::shared_mutex mMutex; std::unordered_map> mSessions; + std::unordered_map mSessionIPs; + std::unordered_map> mIPSessions; }; } \ No newline at end of file diff --git a/include/columnlynx/common/net/virtual_interface.hpp b/include/columnlynx/common/net/virtual_interface.hpp index 6cdf874..a3dcb76 100644 --- a/include/columnlynx/common/net/virtual_interface.hpp +++ b/include/columnlynx/common/net/virtual_interface.hpp @@ -17,6 +17,7 @@ #include #include #include + #include #elif defined(__APPLE__) #include #include @@ -24,8 +25,11 @@ #include #include #include + #include #elif defined(_WIN32) #include + #include + #include #include #pragma comment(lib, "advapi32.lib") #endif @@ -36,12 +40,31 @@ namespace ColumnLynx::Net { explicit VirtualInterface(const std::string& ifName); ~VirtualInterface(); + bool configureIP(uint32_t clientIP, uint32_t serverIP, + uint8_t prefixLen, uint16_t mtu); + std::vector readPacket(); void writePacket(const std::vector& packet); const std::string& getName() const; - int getFd() const; // for ASIO integration (on POSIX) + int getFd() const; // For ASIO integration (on POSIX) + + static inline std::string ipToString(uint32_t ip) { + struct in_addr addr; + addr.s_addr = ip; // expected in network byte order + + char buf[INET_ADDRSTRLEN]; + if (!inet_ntop(AF_INET, &addr, buf, sizeof(buf))) + return "0.0.0.0"; + + return std::string(buf); + } + private: + bool mApplyLinuxIP(uint32_t clientIP, uint32_t serverIP, uint8_t prefixLen, uint16_t mtu); + bool mApplyMacOSIP(uint32_t clientIP, uint32_t serverIP, uint8_t prefixLen, uint16_t mtu); + bool mApplyWindowsIP(uint32_t clientIP, uint32_t serverIP, uint8_t prefixLen, uint16_t mtu); + std::string mIfName; int mFd; // POSIX #if defined(_WIN32) diff --git a/include/columnlynx/server/net/tcp/tcp_connection.hpp b/include/columnlynx/server/net/tcp/tcp_connection.hpp index 7681b97..0ac06bf 100644 --- a/include/columnlynx/server/net/tcp/tcp_connection.hpp +++ b/include/columnlynx/server/net/tcp/tcp_connection.hpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace ColumnLynx::Net::TCP { class TCPConnection : public std::enable_shared_from_this { diff --git a/include/columnlynx/server/net/tcp/tcp_server.hpp b/include/columnlynx/server/net/tcp/tcp_server.hpp index 599a5ad..50a890f 100644 --- a/include/columnlynx/server/net/tcp/tcp_server.hpp +++ b/include/columnlynx/server/net/tcp/tcp_server.hpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace ColumnLynx::Net::TCP { diff --git a/include/columnlynx/server/net/udp/udp_server.hpp b/include/columnlynx/server/net/udp/udp_server.hpp index be7a47a..640bb9c 100644 --- a/include/columnlynx/server/net/udp/udp_server.hpp +++ b/include/columnlynx/server/net/udp/udp_server.hpp @@ -8,12 +8,13 @@ #include #include #include +#include namespace ColumnLynx::Net::UDP { class UDPServer { public: - UDPServer(asio::io_context& ioContext, uint16_t port, bool* hostRunning, bool ipv4Only = false) - : mSocket(ioContext), mHostRunning(hostRunning) + UDPServer(asio::io_context& ioContext, uint16_t port, bool* hostRunning, bool ipv4Only = false, VirtualInterface* tun = nullptr) + : mSocket(ioContext), mHostRunning(hostRunning), mTun(tun) { asio::error_code ec; @@ -43,13 +44,15 @@ namespace ColumnLynx::Net::UDP { void stop(); + void sendData(const uint64_t sessionID, const std::string& data); + private: void mStartReceive(); void mHandlePacket(std::size_t bytes); - void mSendData(const uint64_t sessionID, const std::string& data); asio::ip::udp::socket mSocket; asio::ip::udp::endpoint mRemoteEndpoint; std::array mRecvBuffer; // Adjust size as needed bool* mHostRunning; + VirtualInterface* mTun; }; } \ No newline at end of file diff --git a/src/client/main.cpp b/src/client/main.cpp index 33cf9a2..9d93866 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -15,6 +15,7 @@ using asio::ip::tcp; using namespace ColumnLynx::Utils; using namespace ColumnLynx::Net; +using namespace ColumnLynx; volatile sig_atomic_t done = 0; @@ -62,7 +63,7 @@ int main(int argc, char** argv) { WintunInitialize(); #endif - VirtualInterface tun("columnlynxtun0"); + VirtualInterface tun("utun1"); log("Using virtual interface: " + tun.getName()); LibSodiumWrapper sodiumWrapper = LibSodiumWrapper(); @@ -71,8 +72,8 @@ int main(int argc, char** argv) { uint64_t sessionID = 0; asio::io_context io; - auto client = std::make_shared(io, host, port, &sodiumWrapper, &aesKey, &sessionID, &insecureMode); - auto udpClient = std::make_shared(io, host, port, &aesKey, &sessionID); + auto client = std::make_shared(io, host, port, &sodiumWrapper, &aesKey, &sessionID, &insecureMode, &tun); + auto udpClient = std::make_shared(io, host, port, &aesKey, &sessionID, &tun); client->start(); udpClient->start(); @@ -88,18 +89,25 @@ int main(int argc, char** argv) { // Client is running // TODO: SIGINT or SIGTERM seems to not kill this instantly! while ((client->isConnected() || !client->isHandshakeComplete()) && !done) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Temp wait + auto packet = tun.readPacket(); - if (client->isHandshakeComplete()) { - // Send a test UDP message every 5 seconds after handshake is complete - static auto lastSendTime = std::chrono::steady_clock::now(); - auto now = std::chrono::steady_clock::now(); - if (std::chrono::duration_cast(now - lastSendTime).count() >= 5) { - udpClient->sendMessage("Hello from UDP client!"); - lastSendTime = now; - } - } + Nonce nonce{}; + randombytes_buf(nonce.data(), nonce.size()); + + auto ciphertext = LibSodiumWrapper::encryptMessage( + packet.data(), packet.size(), + aesKey, + nonce, + "udp-data" + ); + + std::vector udpPayload; + udpPayload.insert(udpPayload.end(), nonce.begin(), nonce.end()); + udpPayload.insert(udpPayload.end(), reinterpret_cast(&sessionID), reinterpret_cast(&sessionID) + sizeof(sessionID)); + udpPayload.insert(udpPayload.end(), ciphertext.begin(), ciphertext.end()); + udpClient->sendMessage(std::string(udpPayload.begin(), udpPayload.end())); } + log("Client shutting down."); udpClient->stop(); client->disconnect(); diff --git a/src/client/net/tcp/tcp_client.cpp b/src/client/net/tcp/tcp_client.cpp index d5388a3..c6185e7 100644 --- a/src/client/net/tcp/tcp_client.cpp +++ b/src/client/net/tcp/tcp_client.cpp @@ -239,19 +239,29 @@ namespace ColumnLynx::Net::TCP { mConnectionAESKey, symNonce ); - if (decrypted.size() != sizeof(mConnectionSessionID)) { - Utils::error("Decrypted session ID has invalid size. Terminating connection."); + if (decrypted.size() != sizeof(mConnectionSessionID) + sizeof(Protocol::TunConfig)) { + Utils::error("Decrypted config has invalid size. Terminating connection."); disconnect(); return; } std::memcpy(&mConnectionSessionID, decrypted.data(), sizeof(mConnectionSessionID)); + std::memcpy(&mTunConfig, decrypted.data() + sizeof(mConnectionSessionID), sizeof(Protocol::TunConfig)); Utils::log("Connection established with Session ID: " + std::to_string(mConnectionSessionID)); if (mSessionIDRef) { // Copy to the global reference *mSessionIDRef = mConnectionSessionID; } + uint32_t clientIP = ntohl(mTunConfig.clientIP); + uint32_t serverIP = ntohl(mTunConfig.serverIP); + uint8_t prefixLen = mTunConfig.prefixLength; + uint16_t mtu = mTunConfig.mtu; + + if (mTun) { + mTun->configureIP(clientIP, serverIP, prefixLen, mtu); + } + mHandshakeComplete = true; } diff --git a/src/client/net/udp/udp_client.cpp b/src/client/net/udp/udp_client.cpp index c5cdc5c..e012e8a 100644 --- a/src/client/net/udp/udp_client.cpp +++ b/src/client/net/udp/udp_client.cpp @@ -107,5 +107,10 @@ namespace ColumnLynx::Net::UDP { } Utils::log("UDP Client received packet from " + mRemoteEndpoint.address().to_string() + " - Packet size: " + std::to_string(bytes)); + + // Write to TUN + if (mTunRef) { + mTunRef->writePacket(plaintext); + } } } \ No newline at end of file diff --git a/src/common/virtual_interface.cpp b/src/common/virtual_interface.cpp index 2d43a7b..f544cac 100644 --- a/src/common/virtual_interface.cpp +++ b/src/common/virtual_interface.cpp @@ -14,37 +14,37 @@ namespace ColumnLynx::Net { mFd = open("/dev/net/tun", O_RDWR); if (mFd < 0) throw std::runtime_error("Failed to open /dev/net/tun: " + std::string(strerror(errno))); - + struct ifreq ifr {}; ifr.ifr_flags = IFF_TUN | IFF_NO_PI; std::strncpy(ifr.ifr_name, ifName.c_str(), IFNAMSIZ); - + if (ioctl(mFd, TUNSETIFF, &ifr) < 0) { close(mFd); throw std::runtime_error("TUNSETIFF failed: " + std::string(strerror(errno))); } - + #elif defined(__APPLE__) // ---- macOS: UTUN (system control socket) ---- mFd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL); if (mFd < 0) throw std::runtime_error("socket(PF_SYSTEM) failed: " + std::string(strerror(errno))); - + struct ctl_info ctlInfo {}; std::strncpy(ctlInfo.ctl_name, UTUN_CONTROL_NAME, sizeof(ctlInfo.ctl_name)); if (ioctl(mFd, CTLIOCGINFO, &ctlInfo) == -1) throw std::runtime_error("ioctl(CTLIOCGINFO) failed: " + std::string(strerror(errno))); - + struct sockaddr_ctl sc {}; sc.sc_len = sizeof(sc); sc.sc_family = AF_SYSTEM; sc.ss_sysaddr = AF_SYS_CONTROL; sc.sc_id = ctlInfo.ctl_id; sc.sc_unit = 0; // utun0 (0 = auto-assign) - + if (connect(mFd, (struct sockaddr*)&sc, sizeof(sc)) < 0) throw std::runtime_error("connect(AF_SYS_CONTROL) failed: " + std::string(strerror(errno))); - + // Retrieve actual utun device name struct sockaddr_storage addr; socklen_t addrlen = sizeof(addr); @@ -54,28 +54,28 @@ namespace ColumnLynx::Net { } else { mIfName = "utunX"; } - + #elif defined(_WIN32) // ---- Windows: Wintun (WireGuard virtual adapter) ---- WINTUN_ADAPTER_HANDLE adapter = WintunOpenAdapter(L"ColumnLynx", std::wstring(ifName.begin(), ifName.end()).c_str()); if (!adapter) throw std::runtime_error("Wintun adapter not found or not installed"); - + WINTUN_SESSION_HANDLE session = WintunStartSession(adapter, 0x200000); // ring buffer size if (!session) throw std::runtime_error("Failed to start Wintun session"); - + mHandle = WintunGetReadWaitEvent(session); mFd = -1; // not used on Windows mIfName = ifName; - + #else throw std::runtime_error("Unsupported platform"); #endif } - + // ------------------------------ Destructor ------------------------------ VirtualInterface::~VirtualInterface() { #if defined(__linux__) || defined(__APPLE__) @@ -87,7 +87,7 @@ namespace ColumnLynx::Net { // WintunEndSession(mSession); #endif } - + // ------------------------------ Read ------------------------------ std::vector VirtualInterface::readPacket() { #if defined(__linux__) || defined(__APPLE__) @@ -97,7 +97,7 @@ namespace ColumnLynx::Net { throw std::runtime_error("read() failed: " + std::string(strerror(errno))); buf.resize(n); return buf; - + #elif defined(_WIN32) WINTUN_PACKET* packet = WintunReceivePacket(mSession, nullptr); if (!packet) return {}; @@ -108,14 +108,14 @@ namespace ColumnLynx::Net { return {}; #endif } - + // ------------------------------ Write ------------------------------ void VirtualInterface::writePacket(const std::vector& packet) { #if defined(__linux__) || defined(__APPLE__) ssize_t n = write(mFd, packet.data(), packet.size()); if (n < 0) throw std::runtime_error("write() failed: " + std::string(strerror(errno))); - + #elif defined(_WIN32) WINTUN_PACKET* tx = WintunAllocateSendPacket(mSession, (DWORD)packet.size()); if (!tx) throw std::runtime_error("WintunAllocateSendPacket failed"); @@ -123,9 +123,93 @@ namespace ColumnLynx::Net { WintunSendPacket(mSession, tx); #endif } - + // ------------------------------ Accessors ------------------------------ const std::string& VirtualInterface::getName() const { return mIfName; } - + int VirtualInterface::getFd() const { return mFd; } -} \ No newline at end of file + + // ------------------------------------------------------------ + // IP CONFIGURATION + // ------------------------------------------------------------ + bool VirtualInterface::configureIP(uint32_t clientIP, uint32_t serverIP, + uint8_t prefixLen, uint16_t mtu) + { + #if defined(__linux__) + return mApplyLinuxIP(clientIP, serverIP, prefixLen, mtu); + #elif defined(__APPLE__) + return mApplyMacOSIP(clientIP, serverIP, prefixLen, mtu); + #elif defined(_WIN32) + return mApplyWindowsIP(clientIP, serverIP, prefixLen, mtu); + #else + return false; + #endif + } + + // ------------------------------------------------------------ + // Linux + // ------------------------------------------------------------ + bool VirtualInterface::mApplyLinuxIP(uint32_t clientIP, uint32_t serverIP, + uint8_t prefixLen, uint16_t mtu) + { + char cmd[512]; + + std::string ipStr = ipToString(clientIP); + std::string peerStr = ipToString(serverIP); + + snprintf(cmd, sizeof(cmd), + "ip addr add %s/%d peer %s dev %s", + ipStr.c_str(), prefixLen, peerStr.c_str(), mIfName.c_str()); + system(cmd); + + snprintf(cmd, sizeof(cmd), + "ip link set dev %s up mtu %d", mIfName.c_str(), mtu); + system(cmd); + + return true; + } + + // ------------------------------------------------------------ + // macOS (utun) + // ------------------------------------------------------------ + bool VirtualInterface::mApplyMacOSIP(uint32_t clientIP, uint32_t serverIP, + uint8_t prefixLen, uint16_t mtu) + { + char cmd[512]; + + std::string ipStr = ipToString(clientIP); + std::string peerStr = ipToString(serverIP); + + snprintf(cmd, sizeof(cmd), + "ifconfig utun0 %s %s mtu %d up", + ipStr.c_str(), peerStr.c_str(), mtu); + system(cmd); + + return true; + } + + // ------------------------------------------------------------ + // Windows (Wintun) + // ------------------------------------------------------------ + bool VirtualInterface::mApplyWindowsIP(uint32_t clientIP, uint32_t serverIP, + uint8_t prefixLen, uint16_t mtu) + { + #ifdef _WIN32 + char ip[32], gw[32]; + strcpy(ip, ipToString(clientIP).c_str()); + strcpy(gw, ipToString(serverIP).c_str()); + + char cmd[256]; + snprintf(cmd, sizeof(cmd), + "netsh interface ip set address name=\"%s\" static %s %d.%d.%d.%d", + mIfName.c_str(), ip, + (prefixLen <= 8) ? ((prefixLen << 3) & 255) : 255, + 255, 255, 255); + system(cmd); + + return true; + #else + return false; + #endif + } +} // namespace ColumnLynx::Net \ No newline at end of file diff --git a/src/server/main.cpp b/src/server/main.cpp index 0477c67..ff20232 100644 --- a/src/server/main.cpp +++ b/src/server/main.cpp @@ -11,11 +11,14 @@ #include #include #include +#include using asio::ip::tcp; using namespace ColumnLynx::Utils; using namespace ColumnLynx::Net::TCP; using namespace ColumnLynx::Net::UDP; +using namespace ColumnLynx::Net; +using namespace ColumnLynx; volatile sig_atomic_t done = 0; @@ -54,6 +57,13 @@ int main(int argc, char** argv) { log("ColumnLynx Server, Version " + getVersion()); log("This software is licensed under the GPLv2 only OR the GPLv3. See LICENSES/ for details."); +#if defined(__WIN32__) + WintunInitialize(); +#endif + + VirtualInterface tun("utun0"); + log("Using virtual interface: " + tun.getName()); + // Generate a temporary keypair, replace with actual CA signed keys later (Note, these are stored in memory) LibSodiumWrapper sodiumWrapper = LibSodiumWrapper(); log("Server public key: " + bytesToHexString(sodiumWrapper.getPublicKey(), crypto_sign_PUBLICKEYBYTES)); @@ -64,7 +74,7 @@ int main(int argc, char** argv) { asio::io_context io; auto server = std::make_shared(io, serverPort(), &sodiumWrapper, &hostRunning, ipv4Only); - auto udpServer = std::make_shared(io, serverPort(), &hostRunning, ipv4Only); + auto udpServer = std::make_shared(io, serverPort(), &hostRunning, ipv4Only, &tun); asio::signal_set signals(io, SIGINT, SIGTERM); signals.async_wait([&](const std::error_code&, int) { @@ -87,7 +97,21 @@ int main(int argc, char** argv) { log("Server started on port " + std::to_string(serverPort())); while (!done) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + auto packet = tun.readPacket(); + if (packet.empty()) { + continue; + } + + const uint8_t* ip = packet.data(); + uint32_t dstIP = ntohl(*(uint32_t*)(ip + 16)); // IPv4 destination address offset in IPv6-mapped header + + auto session = SessionRegistry::getInstance().getByIP(dstIP); + if (!session) { + Utils::warn("TUN: No session found for destination IP " + VirtualInterface::ipToString(dstIP)); + continue; + } + + udpServer->sendData(session->sessionID, std::string(packet.begin(), packet.end())); } log("Shutting down server..."); diff --git a/src/server/server/net/tcp/tcp_connection.cpp b/src/server/server/net/tcp/tcp_connection.cpp index 196a7cd..4c0282f 100644 --- a/src/server/server/net/tcp/tcp_connection.cpp +++ b/src/server/server/net/tcp/tcp_connection.cpp @@ -41,6 +41,9 @@ namespace ColumnLynx::Net::TCP { mHandler->socket().shutdown(asio::ip::tcp::socket::shutdown_both, ec); mHandler->socket().close(ec); + SessionRegistry::getInstance().erase(mConnectionSessionID); + SessionRegistry::getInstance().deallocIP(mConnectionSessionID); + Utils::log("Closed connection to " + ip); if (mOnDisconnect) { @@ -174,16 +177,33 @@ 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 - std::vector encryptedSessionID = Utils::LibSodiumWrapper::encryptMessage( - reinterpret_cast(&mConnectionSessionID), sizeof(mConnectionSessionID), + + uint32_t clientIP = SessionRegistry::getInstance().getFirstAvailableIP(); + Protocol::TunConfig tunConfig{}; + 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.dns1 = htonl(0x08080808); // 8.8.8.8 + tunConfig.dns2 = 0; + + SessionRegistry::getInstance().lockIP(mConnectionSessionID, clientIP); + + std::vector payload(sizeof(uint64_t) + sizeof(tunConfig)); + std::memcpy(payload.data(), &mConnectionSessionID, sizeof(uint64_t)); + std::memcpy(payload.data() + sizeof(uint64_t), &tunConfig, sizeof(tunConfig)); + + std::vector encryptedPayload = Utils::LibSodiumWrapper::encryptMessage( + payload.data(), payload.size(), mConnectionAESKey, symNonce ); - mHandler->sendMessage(ServerMessageType::HANDSHAKE_EXCHANGE_KEY_CONFIRM, Utils::uint8ArrayToString(encryptedSessionID.data(), encryptedSessionID.size())); + mHandler->sendMessage(ServerMessageType::HANDSHAKE_EXCHANGE_KEY_CONFIRM, Utils::uint8ArrayToString(encryptedPayload.data(), encryptedPayload.size())); // Add to session registry Utils::log("Handshake with " + reqAddr + " completed successfully. Session ID assigned."); - auto session = std::make_shared(mConnectionAESKey, std::chrono::hours(12)); + auto session = std::make_shared(mConnectionAESKey, std::chrono::hours(12), clientIP, htonl(0x0A0A0001), mConnectionSessionID); SessionRegistry::getInstance().put(mConnectionSessionID, std::move(session)); } catch (const std::exception& e) { diff --git a/src/server/server/net/udp/udp_server.cpp b/src/server/server/net/udp/udp_server.cpp index 1afa8a7..161737f 100644 --- a/src/server/server/net/udp/udp_server.cpp +++ b/src/server/server/net/udp/udp_server.cpp @@ -62,16 +62,16 @@ namespace ColumnLynx::Net::UDP { // For now, just log the decrypted payload std::string payloadStr(plaintext.begin(), plaintext.end()); Utils::log("UDP: Received packet from " + mRemoteEndpoint.address().to_string() + " - Payload: " + payloadStr); - - // TODO: Process the packet payload, for now just echo back - mSendData(sessionID, std::string(plaintext.begin(), plaintext.end())); + if (mTun) { + mTun->writePacket(plaintext); // Send to virtual interface + } } catch (...) { Utils::warn("UDP: Failed to decrypt payload from " + mRemoteEndpoint.address().to_string()); return; } } - void UDPServer::mSendData(const uint64_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 std::shared_ptr session = SessionRegistry::getInstance().get(sessionID); if (!session) { -- 2.43.0 From c85f622a60614742bb89230209b8276e6dc2eba1 Mon Sep 17 00:00:00 2001 From: DcruBro Date: Thu, 13 Nov 2025 15:12:00 +0100 Subject: [PATCH 2/4] test2 --- include/columnlynx/client/net/tcp/tcp_client.hpp | 4 ++-- include/columnlynx/client/net/udp/udp_client.hpp | 4 ++-- include/columnlynx/server/net/udp/udp_server.hpp | 4 ++-- panic_dump.txt | 1 + src/client/main.cpp | 10 +++++----- src/server/main.cpp | 10 ++++++---- src/server/server/net/udp/udp_server.cpp | 1 + 7 files changed, 19 insertions(+), 15 deletions(-) create mode 100644 panic_dump.txt diff --git a/include/columnlynx/client/net/tcp/tcp_client.hpp b/include/columnlynx/client/net/tcp/tcp_client.hpp index a0eabcb..965b7fc 100644 --- a/include/columnlynx/client/net/tcp/tcp_client.hpp +++ b/include/columnlynx/client/net/tcp/tcp_client.hpp @@ -28,7 +28,7 @@ namespace ColumnLynx::Net::TCP { std::array* aesKey, uint64_t* sessionIDRef, bool* insecureMode, - VirtualInterface* tun = nullptr) + std::shared_ptr tun = nullptr) : mResolver(ioContext), mSocket(ioContext), @@ -74,6 +74,6 @@ namespace ColumnLynx::Net::TCP { int mMissedHeartbeats = 0; bool mIsHostDomain; Protocol::TunConfig mTunConfig; - VirtualInterface* mTun = nullptr; + std::shared_ptr mTun = nullptr; }; } \ No newline at end of file diff --git a/include/columnlynx/client/net/udp/udp_client.hpp b/include/columnlynx/client/net/udp/udp_client.hpp index ad21fda..eddaec9 100644 --- a/include/columnlynx/client/net/udp/udp_client.hpp +++ b/include/columnlynx/client/net/udp/udp_client.hpp @@ -19,7 +19,7 @@ namespace ColumnLynx::Net::UDP { const std::string& port, std::array* aesKeyRef, uint64_t* sessionIDRef, - VirtualInterface* tunRef = nullptr) + std::shared_ptr tunRef = nullptr) : mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port), mAesKeyRef(aesKeyRef), mSessionIDRef(sessionIDRef), mTunRef(tunRef) { mStartReceive(); } void start(); @@ -37,7 +37,7 @@ namespace ColumnLynx::Net::UDP { std::string mPort; std::array* mAesKeyRef; uint64_t* mSessionIDRef; - VirtualInterface* mTunRef; + std::shared_ptr mTunRef = nullptr; std::array mRecvBuffer; // Adjust size as needed }; } \ No newline at end of file diff --git a/include/columnlynx/server/net/udp/udp_server.hpp b/include/columnlynx/server/net/udp/udp_server.hpp index 640bb9c..ab99610 100644 --- a/include/columnlynx/server/net/udp/udp_server.hpp +++ b/include/columnlynx/server/net/udp/udp_server.hpp @@ -13,7 +13,7 @@ namespace ColumnLynx::Net::UDP { class UDPServer { public: - UDPServer(asio::io_context& ioContext, uint16_t port, bool* hostRunning, bool ipv4Only = false, VirtualInterface* tun = nullptr) + UDPServer(asio::io_context& ioContext, uint16_t port, bool* hostRunning, bool ipv4Only = false, std::shared_ptr tun = nullptr) : mSocket(ioContext), mHostRunning(hostRunning), mTun(tun) { asio::error_code ec; @@ -53,6 +53,6 @@ namespace ColumnLynx::Net::UDP { asio::ip::udp::endpoint mRemoteEndpoint; std::array mRecvBuffer; // Adjust size as needed bool* mHostRunning; - VirtualInterface* mTun; + std::shared_ptr mTun; }; } \ No newline at end of file diff --git a/panic_dump.txt b/panic_dump.txt new file mode 100644 index 0000000..851c75c --- /dev/null +++ b/panic_dump.txt @@ -0,0 +1 @@ += \ No newline at end of file diff --git a/src/client/main.cpp b/src/client/main.cpp index 9d93866..bcd4b6e 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -63,8 +63,8 @@ int main(int argc, char** argv) { WintunInitialize(); #endif - VirtualInterface tun("utun1"); - log("Using virtual interface: " + tun.getName()); + std::shared_ptr tun = std::make_shared("utun0"); + log("Using virtual interface: " + tun->getName()); LibSodiumWrapper sodiumWrapper = LibSodiumWrapper(); @@ -72,8 +72,8 @@ int main(int argc, char** argv) { uint64_t sessionID = 0; asio::io_context io; - auto client = std::make_shared(io, host, port, &sodiumWrapper, &aesKey, &sessionID, &insecureMode, &tun); - auto udpClient = std::make_shared(io, host, port, &aesKey, &sessionID, &tun); + auto client = std::make_shared(io, host, port, &sodiumWrapper, &aesKey, &sessionID, &insecureMode, tun); + auto udpClient = std::make_shared(io, host, port, &aesKey, &sessionID, tun); client->start(); udpClient->start(); @@ -89,7 +89,7 @@ int main(int argc, char** argv) { // Client is running // TODO: SIGINT or SIGTERM seems to not kill this instantly! while ((client->isConnected() || !client->isHandshakeComplete()) && !done) { - auto packet = tun.readPacket(); + auto packet = tun->readPacket(); Nonce nonce{}; randombytes_buf(nonce.data(), nonce.size()); diff --git a/src/server/main.cpp b/src/server/main.cpp index ff20232..b2d5b2f 100644 --- a/src/server/main.cpp +++ b/src/server/main.cpp @@ -61,8 +61,8 @@ int main(int argc, char** argv) { WintunInitialize(); #endif - VirtualInterface tun("utun0"); - log("Using virtual interface: " + tun.getName()); + std::shared_ptr tun = std::make_shared("utun0"); + log("Using virtual interface: " + tun->getName()); // Generate a temporary keypair, replace with actual CA signed keys later (Note, these are stored in memory) LibSodiumWrapper sodiumWrapper = LibSodiumWrapper(); @@ -74,7 +74,7 @@ int main(int argc, char** argv) { asio::io_context io; auto server = std::make_shared(io, serverPort(), &sodiumWrapper, &hostRunning, ipv4Only); - auto udpServer = std::make_shared(io, serverPort(), &hostRunning, ipv4Only, &tun); + auto udpServer = std::make_shared(io, serverPort(), &hostRunning, ipv4Only, tun); asio::signal_set signals(io, SIGINT, SIGTERM); signals.async_wait([&](const std::error_code&, int) { @@ -84,6 +84,8 @@ int main(int argc, char** argv) { hostRunning = false; server->stop(); udpServer->stop(); + tun.reset(); + tun = nullptr; }); }); @@ -97,7 +99,7 @@ int main(int argc, char** argv) { log("Server started on port " + std::to_string(serverPort())); while (!done) { - auto packet = tun.readPacket(); + auto packet = tun->readPacket(); if (packet.empty()) { continue; } diff --git a/src/server/server/net/udp/udp_server.cpp b/src/server/server/net/udp/udp_server.cpp index 161737f..de5ca67 100644 --- a/src/server/server/net/udp/udp_server.cpp +++ b/src/server/server/net/udp/udp_server.cpp @@ -62,6 +62,7 @@ namespace ColumnLynx::Net::UDP { // For now, just log the decrypted payload std::string payloadStr(plaintext.begin(), plaintext.end()); Utils::log("UDP: Received packet from " + mRemoteEndpoint.address().to_string() + " - Payload: " + payloadStr); + if (mTun) { mTun->writePacket(plaintext); // Send to virtual interface } -- 2.43.0 From b37a999274e8d2bb4110c545247397c6bcdd9989 Mon Sep 17 00:00:00 2001 From: DcruBro Date: Thu, 13 Nov 2025 15:43:46 +0100 Subject: [PATCH 3/4] Fixed crash trigger on Ctrl+C (errno == EINTR check) --- .../columnlynx/client/net/tcp/tcp_client.hpp | 3 +- .../columnlynx/client/net/udp/udp_client.hpp | 5 ++- panic_dump.txt | 1 - src/client/main.cpp | 38 ++++++++----------- src/client/net/udp/udp_client.cpp | 1 + src/common/virtual_interface.cpp | 6 ++- src/server/main.cpp | 2 - 7 files changed, 28 insertions(+), 28 deletions(-) delete mode 100644 panic_dump.txt diff --git a/include/columnlynx/client/net/tcp/tcp_client.hpp b/include/columnlynx/client/net/tcp/tcp_client.hpp index 965b7fc..820f534 100644 --- a/include/columnlynx/client/net/tcp/tcp_client.hpp +++ b/include/columnlynx/client/net/tcp/tcp_client.hpp @@ -40,7 +40,8 @@ namespace ColumnLynx::Net::TCP { mInsecureMode(insecureMode), mHeartbeatTimer(mSocket.get_executor()), mLastHeartbeatReceived(std::chrono::steady_clock::now()), - mLastHeartbeatSent(std::chrono::steady_clock::now()) + mLastHeartbeatSent(std::chrono::steady_clock::now()), + mTun(tun) {} void start(); diff --git a/include/columnlynx/client/net/udp/udp_client.hpp b/include/columnlynx/client/net/udp/udp_client.hpp index eddaec9..3eed06e 100644 --- a/include/columnlynx/client/net/udp/udp_client.hpp +++ b/include/columnlynx/client/net/udp/udp_client.hpp @@ -20,7 +20,10 @@ namespace ColumnLynx::Net::UDP { std::array* aesKeyRef, uint64_t* sessionIDRef, std::shared_ptr tunRef = nullptr) - : mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port), mAesKeyRef(aesKeyRef), mSessionIDRef(sessionIDRef), mTunRef(tunRef) { mStartReceive(); } + : mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port), mAesKeyRef(aesKeyRef), mSessionIDRef(sessionIDRef), mTunRef(tunRef) + { + mStartReceive(); + } void start(); void sendMessage(const std::string& data = ""); diff --git a/panic_dump.txt b/panic_dump.txt deleted file mode 100644 index 851c75c..0000000 --- a/panic_dump.txt +++ /dev/null @@ -1 +0,0 @@ -= \ No newline at end of file diff --git a/src/client/main.cpp b/src/client/main.cpp index bcd4b6e..c81c043 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -21,7 +21,7 @@ volatile sig_atomic_t done = 0; void signalHandler(int signum) { if (signum == SIGINT || signum == SIGTERM) { - log("Received termination signal. Shutting down client."); + //log("Received termination signal. Shutting down client."); done = 1; } } @@ -63,7 +63,7 @@ int main(int argc, char** argv) { WintunInitialize(); #endif - std::shared_ptr tun = std::make_shared("utun0"); + std::shared_ptr tun = std::make_shared("utun1"); log("Using virtual interface: " + tun->getName()); LibSodiumWrapper sodiumWrapper = LibSodiumWrapper(); @@ -82,37 +82,31 @@ int main(int argc, char** argv) { std::thread ioThread([&io]() { io.run(); }); - ioThread.detach(); + //ioThread.join(); log("Client connected to " + host + ":" + port); - + // Client is running - // TODO: SIGINT or SIGTERM seems to not kill this instantly! while ((client->isConnected() || !client->isHandshakeComplete()) && !done) { auto packet = tun->readPacket(); - - Nonce nonce{}; - randombytes_buf(nonce.data(), nonce.size()); - - auto ciphertext = LibSodiumWrapper::encryptMessage( - packet.data(), packet.size(), - aesKey, - nonce, - "udp-data" - ); - - std::vector udpPayload; - udpPayload.insert(udpPayload.end(), nonce.begin(), nonce.end()); - udpPayload.insert(udpPayload.end(), reinterpret_cast(&sessionID), reinterpret_cast(&sessionID) + sizeof(sessionID)); - udpPayload.insert(udpPayload.end(), ciphertext.begin(), ciphertext.end()); - udpClient->sendMessage(std::string(udpPayload.begin(), udpPayload.end())); + if (!client->isConnected() || done) { + break; // Bail out if connection died or signal set while blocked + } + + if (packet.empty()) { + continue; + } + + udpClient->sendMessage(std::string(packet.begin(), packet.end())); } log("Client shutting down."); udpClient->stop(); client->disconnect(); io.stop(); - ioThread.join(); + + if (ioThread.joinable()) + ioThread.join(); } catch (const std::exception& e) { error("Client error: " + std::string(e.what())); diff --git a/src/client/net/udp/udp_client.cpp b/src/client/net/udp/udp_client.cpp index e012e8a..61cf0f8 100644 --- a/src/client/net/udp/udp_client.cpp +++ b/src/client/net/udp/udp_client.cpp @@ -6,6 +6,7 @@ namespace ColumnLynx::Net::UDP { void UDPClient::start() { + // TODO: Add IPv6 auto endpoints = mResolver.resolve(asio::ip::udp::v4(), mHost, mPort); mRemoteEndpoint = *endpoints.begin(); mSocket.open(asio::ip::udp::v4()); diff --git a/src/common/virtual_interface.cpp b/src/common/virtual_interface.cpp index f544cac..76a0c67 100644 --- a/src/common/virtual_interface.cpp +++ b/src/common/virtual_interface.cpp @@ -93,8 +93,12 @@ namespace ColumnLynx::Net { #if defined(__linux__) || defined(__APPLE__) std::vector buf(4096); ssize_t n = read(mFd, buf.data(), buf.size()); - if (n < 0) + if (n < 0) { + if (errno == EINTR) { + return {}; // Interrupted, return empty + } throw std::runtime_error("read() failed: " + std::string(strerror(errno))); + } buf.resize(n); return buf; diff --git a/src/server/main.cpp b/src/server/main.cpp index b2d5b2f..c32074e 100644 --- a/src/server/main.cpp +++ b/src/server/main.cpp @@ -84,8 +84,6 @@ int main(int argc, char** argv) { hostRunning = false; server->stop(); udpServer->stop(); - tun.reset(); - tun = nullptr; }); }); -- 2.43.0 From 5a5f830cd98f79707b173733acbd94ab2e95b22e Mon Sep 17 00:00:00 2001 From: DcruBro Date: Thu, 13 Nov 2025 15:45:24 +0100 Subject: [PATCH 4/4] Change tun interface, change to different name at some point --- src/client/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/main.cpp b/src/client/main.cpp index c81c043..a331bf1 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -63,7 +63,7 @@ int main(int argc, char** argv) { WintunInitialize(); #endif - std::shared_ptr tun = std::make_shared("utun1"); + std::shared_ptr tun = std::make_shared("utun0"); log("Using virtual interface: " + tun->getName()); LibSodiumWrapper sodiumWrapper = LibSodiumWrapper(); -- 2.43.0