Compare commits
9 Commits
640a751f9b
...
b0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| cb0f674c52 | |||
| a2ecc589f8 | |||
| b50b594d68 | |||
| 842752cd88 | |||
| 33bbd7cce6 | |||
| f9c5c56a1b | |||
| 17dd504a7a | |||
| 9f52bdd54c | |||
| 29e90938c5 |
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <sodium.h>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <asio.hpp>
|
||||
#include <columnlynx/common/utils.hpp>
|
||||
#include <columnlynx/common/libsodium_wrapper.hpp>
|
||||
|
||||
@@ -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<SessionState> 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<SessionState> state);
|
||||
|
||||
// Lookup a session entry by session ID
|
||||
std::shared_ptr<const SessionState> 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<const SessionState> get(uint64_t sessionID) const;
|
||||
|
||||
// Lookup a session entry by IPv4
|
||||
std::shared_ptr<const SessionState> 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<const SessionState> getByIP(uint32_t ip) const;
|
||||
|
||||
// Get a snapshot of the Session Registry
|
||||
std::unordered_map<uint64_t, std::shared_ptr<SessionState>> snapshot() const {
|
||||
std::unordered_map<uint64_t, std::shared_ptr<SessionState>> snap;
|
||||
std::shared_lock lock(mMutex);
|
||||
snap = mSessions;
|
||||
return snap;
|
||||
}
|
||||
std::unordered_map<uint64_t, std::shared_ptr<SessionState>> 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<int>(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;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/poll.h>
|
||||
#elif defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#include <ws2tcpip.h>
|
||||
@@ -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;
|
||||
|
||||
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)))
|
||||
|
||||
100
src/common/session_registry.cpp
Normal file
100
src/common/session_registry.cpp
Normal file
@@ -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 <columnlynx/common/net/session_registry.hpp>
|
||||
|
||||
namespace ColumnLynx::Net {
|
||||
void SessionRegistry::put(uint64_t sessionID, std::shared_ptr<SessionState> state) {
|
||||
std::unique_lock lock(mMutex);
|
||||
mSessions[sessionID] = std::move(state);
|
||||
mIPSessions[mSessions[sessionID]->clientTunIP] = mSessions[sessionID];
|
||||
}
|
||||
|
||||
std::shared_ptr<const SessionState> 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<const SessionState> 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<uint64_t, std::shared_ptr<SessionState>> SessionRegistry::snapshot() const {
|
||||
std::unordered_map<uint64_t, std::shared_ptr<SessionState>> 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<int>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ namespace ColumnLynx::Utils {
|
||||
}
|
||||
|
||||
std::string getVersion() {
|
||||
return "a0.6";
|
||||
return "b0.1";
|
||||
}
|
||||
|
||||
unsigned short serverPort() {
|
||||
|
||||
@@ -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<uint8_t> VirtualInterface::readPacket() {
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
#if defined(__linux__)
|
||||
|
||||
// Linux TUN: blocking read is fine, unblocks on fd close / EINTR
|
||||
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t>& 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<uint8_t> 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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user