diff --git a/CMakeLists.txt b/CMakeLists.txt index d4e640f..bca8dbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.16) # If MAJOR is 0, and MINOR > 0, Version is BETA project(ColumnLynx - VERSION 0.0.6 + VERSION 0.1.0 LANGUAGES CXX ) diff --git a/include/columnlynx/common/net/session_registry.hpp b/include/columnlynx/common/net/session_registry.hpp index 11449ae..762dc05 100644 --- a/include/columnlynx/common/net/session_registry.hpp +++ b/include/columnlynx/common/net/session_registry.hpp @@ -10,6 +10,9 @@ #include #include #include +#include +#include +#include #include #include @@ -49,107 +52,36 @@ namespace ColumnLynx::Net { static SessionRegistry& getInstance() { static SessionRegistry instance; return instance; } // Insert or replace a session entry - 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]; - } + void put(uint64_t sessionID, std::shared_ptr state); // Lookup a session entry by session ID - std::shared_ptr get(uint64_t sessionID) const { - std::shared_lock lock(mMutex); - auto it = mSessions.find(sessionID); - return (it == mSessions.end()) ? nullptr : it->second; - } + std::shared_ptr get(uint64_t sessionID) const; // Lookup a session entry by IPv4 - 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::shared_ptr getByIP(uint32_t ip) const; // Get a snapshot of the Session Registry - std::unordered_map> snapshot() const { - std::unordered_map> snap; - std::shared_lock lock(mMutex); - snap = mSessions; - return snap; - } + std::unordered_map> snapshot() const; // Remove a session by ID - void erase(uint64_t sessionID) { - std::unique_lock lock(mMutex); - mSessions.erase(sessionID); - } + void erase(uint64_t sessionID); // Cleanup expired sessions - void cleanupExpired() { - std::unique_lock lock(mMutex); - auto now = std::chrono::steady_clock::now(); - for (auto it = mSessions.begin(); it != mSessions.end(); ) { - if (it->second && it->second->expires <= now) { - it = mSessions.erase(it); - } else { - ++it; - } - } - - for (auto it = mIPSessions.begin(); it != mIPSessions.end(); ) { - if (it->second && it->second->expires <= now) { - it = mIPSessions.erase(it); - } else { - ++it; - } - } - } + void cleanupExpired(); // Get the number of registered sessions - int size() const { - std::shared_lock lock(mMutex); - return static_cast(mSessions.size()); - } + int size() const; // IP management // Get the lowest available IPv4 address; Returns 0 if none available - uint32_t getFirstAvailableIP(uint32_t baseIP, uint8_t mask) const { - std::shared_lock lock(mMutex); + uint32_t getFirstAvailableIP(uint32_t baseIP, uint8_t mask) const; - uint32_t hostCount = (1u << (32 - mask)); - uint32_t firstHost = 2; - uint32_t lastHost = hostCount - 2; + // Lock IP to session ID; Do NOT call before put() - You will segfault! + void lockIP(uint64_t sessionID, uint32_t ip); - for (uint32_t offset = firstHost; offset <= lastHost; offset++) { - uint32_t candidateIP = baseIP + offset; - if (mIPSessions.find(candidateIP) == mIPSessions.end()) { - return candidateIP; - } - } - - return 0; - } - - void lockIP(uint64_t sessionID, uint32_t ip) { - std::unique_lock lock(mMutex); - mSessionIPs[sessionID] = ip; - - /*if (mIPSessions.find(sessionID) == mIPSessions.end()) { - Utils::debug("yikes"); - }*/ - mIPSessions[ip] = mSessions.find(sessionID)->second; - } - - void deallocIP(uint64_t sessionID) { - std::unique_lock lock(mMutex); - - auto it = mSessionIPs.find(sessionID); - if (it != mSessionIPs.end()) { - uint32_t ip = it->second; - mIPSessions.erase(ip); - mSessionIPs.erase(it); - } - } + // Unlock IP from session ID + void deallocIP(uint64_t sessionID); private: mutable std::shared_mutex mMutex; diff --git a/include/columnlynx/common/net/virtual_interface.hpp b/include/columnlynx/common/net/virtual_interface.hpp index 1b19bff..dca574d 100644 --- a/include/columnlynx/common/net/virtual_interface.hpp +++ b/include/columnlynx/common/net/virtual_interface.hpp @@ -26,6 +26,7 @@ #include #include #include + #include #elif defined(_WIN32) #include #include @@ -49,9 +50,13 @@ namespace ColumnLynx::Net { const std::string& getName() const; int getFd() const; // For ASIO integration (on POSIX) - static inline std::string ipv4ToString(uint32_t ip) { + static inline std::string ipv4ToString(uint32_t ip, bool flip = true) { struct in_addr addr; - addr.s_addr = htonl(ip); + + if (flip) + addr.s_addr = htonl(ip); + else + addr.s_addr = ip; char buf[INET_ADDRSTRLEN]; if (!inet_ntop(AF_INET, &addr, buf, sizeof(buf))) diff --git a/src/client/main.cpp b/src/client/main.cpp index feffdaa..d5666a5 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -107,7 +107,7 @@ int main(int argc, char** argv) { if (packet.empty()) { continue; } - + udpClient->sendMessage(std::string(packet.begin(), packet.end())); } diff --git a/src/common/session_registry.cpp b/src/common/session_registry.cpp new file mode 100644 index 0000000..1c91445 --- /dev/null +++ b/src/common/session_registry.cpp @@ -0,0 +1,100 @@ +// session_registry.cpp - Session Registry for ColumnLynx +// 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. + +#include + +namespace ColumnLynx::Net { + void SessionRegistry::put(uint64_t sessionID, std::shared_ptr state) { + std::unique_lock lock(mMutex); + mSessions[sessionID] = std::move(state); + mIPSessions[mSessions[sessionID]->clientTunIP] = mSessions[sessionID]; + } + + std::shared_ptr SessionRegistry::get(uint64_t sessionID) const { + std::shared_lock lock(mMutex); + auto it = mSessions.find(sessionID); + return (it == mSessions.end()) ? nullptr : it->second; + } + + std::shared_ptr SessionRegistry::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> SessionRegistry::snapshot() const { + std::unordered_map> snap; + std::shared_lock lock(mMutex); + snap = mSessions; + return snap; + } + + void SessionRegistry::erase(uint64_t sessionID) { + std::unique_lock lock(mMutex); + mSessions.erase(sessionID); + } + + void SessionRegistry::cleanupExpired() { + std::unique_lock lock(mMutex); + auto now = std::chrono::steady_clock::now(); + for (auto it = mSessions.begin(); it != mSessions.end(); ) { + if (it->second && it->second->expires <= now) { + it = mSessions.erase(it); + } else { + ++it; + } + } + + for (auto it = mIPSessions.begin(); it != mIPSessions.end(); ) { + if (it->second && it->second->expires <= now) { + it = mIPSessions.erase(it); + } else { + ++it; + } + } + } + + int SessionRegistry::size() const { + std::shared_lock lock(mMutex); + return static_cast(mSessions.size()); + } + + uint32_t SessionRegistry::getFirstAvailableIP(uint32_t baseIP, uint8_t mask) const { + std::shared_lock lock(mMutex); + + uint32_t hostCount = (1u << (32 - mask)); + uint32_t firstHost = 2; + uint32_t lastHost = hostCount - 2; + + for (uint32_t offset = firstHost; offset <= lastHost; offset++) { + uint32_t candidateIP = baseIP + offset; + if (mIPSessions.find(candidateIP) == mIPSessions.end()) { + return candidateIP; + } + } + + return 0; + } + + void SessionRegistry::lockIP(uint64_t sessionID, uint32_t ip) { + std::unique_lock lock(mMutex); + mSessionIPs[sessionID] = ip; + + /*if (mIPSessions.find(sessionID) == mIPSessions.end()) { + Utils::debug("yikes"); + }*/ + mIPSessions[ip] = mSessions.find(sessionID)->second; + } + + void SessionRegistry::deallocIP(uint64_t sessionID) { + std::unique_lock lock(mMutex); + + auto it = mSessionIPs.find(sessionID); + if (it != mSessionIPs.end()) { + uint32_t ip = it->second; + mIPSessions.erase(ip); + mSessionIPs.erase(it); + } + } +} \ No newline at end of file diff --git a/src/common/utils.cpp b/src/common/utils.cpp index 7a1c992..73c7c51 100644 --- a/src/common/utils.cpp +++ b/src/common/utils.cpp @@ -49,7 +49,7 @@ namespace ColumnLynx::Utils { } std::string getVersion() { - return "a0.6"; + return "b0.1"; } unsigned short serverPort() { diff --git a/src/common/virtual_interface.cpp b/src/common/virtual_interface.cpp index b19f3a6..e2a860e 100644 --- a/src/common/virtual_interface.cpp +++ b/src/common/virtual_interface.cpp @@ -28,6 +28,7 @@ namespace ColumnLynx::Net { #elif defined(__APPLE__) // ---- macOS: UTUN (system control socket) ---- + // TL;DR: macOS doesn't really have a "device file" for TUN/TAP like Linux. Instead we have to request a "system control socket" from the kernel. mFd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL); if (mFd < 0) throw std::runtime_error("socket(PF_SYSTEM) failed: " + std::string(strerror(errno))); @@ -42,7 +43,7 @@ namespace ColumnLynx::Net { sc.sc_family = AF_SYSTEM; sc.ss_sysaddr = AF_SYS_CONTROL; sc.sc_id = ctlInfo.ctl_id; - sc.sc_unit = 0; // lynx0 (0 = auto-assign) + sc.sc_unit = 0; // 0 = auto-assign next utunX if (connect(mFd, (struct sockaddr*)&sc, sizeof(sc)) < 0) { if (errno == EPERM) @@ -50,16 +51,17 @@ namespace ColumnLynx::Net { 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); - if (getsockname(mFd, (struct sockaddr*)&addr, &addrlen) == 0) { - const struct sockaddr_ctl* addr_ctl = (const struct sockaddr_ctl*)&addr; - mIfName = "utun" + std::to_string(addr_ctl->sc_unit - 1); + // Retrieve actual utun device name via UTUN_OPT_IFNAME + char ifname[IFNAMSIZ]; + socklen_t ifname_len = sizeof(ifname); + if (getsockopt(mFd, SYSPROTO_CONTROL, UTUN_OPT_IFNAME, ifname, &ifname_len) == 0) { + mIfName = ifname; // Update to actual assigned name } else { - mIfName = "utunX"; + mIfName = "utun0"; // Fallback (should not happen) } + Utils::log("VirtualInterface: opened macOS UTUN: " + mIfName); + #elif defined(_WIN32) // ---- Windows: Wintun (WireGuard virtual adapter) ---- WINTUN_ADAPTER_HANDLE adapter = @@ -95,24 +97,73 @@ namespace ColumnLynx::Net { // ------------------------------ Read ------------------------------ std::vector VirtualInterface::readPacket() { - #if defined(__linux__) || defined(__APPLE__) + #if defined(__linux__) + + // Linux TUN: blocking read is fine, unblocks on fd close / EINTR std::vector buf(4096); ssize_t n = read(mFd, buf.data(), buf.size()); if (n < 0) { if (errno == EINTR) { - return {}; // Interrupted, return empty + return {}; // Interrupted, just return empty } throw std::runtime_error("read() failed: " + std::string(strerror(errno))); } buf.resize(n); return buf; + #elif defined(__APPLE__) + + // macOS utun: must poll, or read() can block forever + std::vector buf(4096); + + struct pollfd pfd; + pfd.fd = mFd; + pfd.events = POLLIN; + + // timeout in ms; keep it small so shutdown is responsive + int ret = poll(&pfd, 1, 200); + + if (ret == 0) { + // No data yet + return {}; + } + + if (ret < 0) { + if (errno == EINTR) { + return {}; // Interrupted by signal + } + throw std::runtime_error("poll() failed: " + std::string(strerror(errno))); + } + + if (!(pfd.revents & POLLIN)) { + return {}; + } + + ssize_t n = read(mFd, buf.data(), buf.size()); + if (n <= 0) { + // 0 or -1: treat as EOF or transient; you can decide how aggressive to be + return {}; + } + + if (n > 4) { + // Drop macOS UTUN header (4 bytes) + std::memmove(buf.data(), buf.data() + 4, n - 4); + buf.resize(n - 4); + } else { + return {}; + } + + return buf; + #elif defined(_WIN32) + WINTUN_PACKET* packet = WintunReceivePacket(mSession, nullptr); if (!packet) return {}; + std::vector buf(packet->Data, packet->Data + packet->Length); WintunReleaseReceivePacket(mSession, packet); return buf; + #else return {}; #endif @@ -120,16 +171,48 @@ namespace ColumnLynx::Net { // ------------------------------ Write ------------------------------ void VirtualInterface::writePacket(const std::vector& packet) { - #if defined(__linux__) || defined(__APPLE__) + #if defined(__linux__) + + // Linux TUN expects raw IP packet ssize_t n = write(mFd, packet.data(), packet.size()); if (n < 0) throw std::runtime_error("write() failed: " + std::string(strerror(errno))); + #elif defined(__APPLE__) + + if (packet.empty()) + return; + + // Detect IPv4 or IPv6 + uint8_t version = packet[0] >> 4; + uint32_t af; + + if (version == 4) { + af = htonl(AF_INET); + } else if (version == 6) { + af = htonl(AF_INET6); + } else { + throw std::runtime_error("writePacket(): unknown IP version"); + } + + // Prepend 4-byte AF header + std::vector out(packet.size() + 4); + memcpy(out.data(), &af, 4); + memcpy(out.data() + 4, packet.data(), packet.size()); + + ssize_t n = write(mFd, out.data(), out.size()); + if (n < 0) + throw std::runtime_error("utun 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"); + if (!tx) + throw std::runtime_error("WintunAllocateSendPacket failed"); + memcpy(tx->Data, packet.data(), packet.size()); WintunSendPacket(mSession, tx); + #endif } @@ -195,7 +278,8 @@ namespace ColumnLynx::Net { std::string ipStr = ipv4ToString(clientIP); std::string peerStr = ipv4ToString(serverIP); - std::string prefixStr = ipv4ToString(prefixLen); + std::string prefixStr = ipv4ToString(prefixLengthToNetmask(prefixLen), false); + Utils::debug("Prefix string: " + prefixStr); // Reset snprintf(cmd, sizeof(cmd), @@ -212,7 +296,7 @@ namespace ColumnLynx::Net { // Set snprintf(cmd, sizeof(cmd), - "ifconfig %s %s %s mtu %d netmask %s up", + "ifconfig %s inet %s %s mtu %d netmask %s up", mIfName.c_str(), ipStr.c_str(), peerStr.c_str(), mtu, prefixStr.c_str()); system(cmd); diff --git a/src/server/server/net/tcp/tcp_connection.cpp b/src/server/net/tcp/tcp_connection.cpp similarity index 100% rename from src/server/server/net/tcp/tcp_connection.cpp rename to src/server/net/tcp/tcp_connection.cpp diff --git a/src/server/server/net/tcp/tcp_server.cpp b/src/server/net/tcp/tcp_server.cpp similarity index 100% rename from src/server/server/net/tcp/tcp_server.cpp rename to src/server/net/tcp/tcp_server.cpp diff --git a/src/server/server/net/udp/udp_server.cpp b/src/server/net/udp/udp_server.cpp similarity index 100% rename from src/server/server/net/udp/udp_server.cpp rename to src/server/net/udp/udp_server.cpp