16 Commits

Author SHA1 Message Date
0299b03d9c Merge branch 'dev' into beta - Version 1.1.0
This version introduces protocol version 2:
 - UDP Headers stripped to 16 Bytes, allowing more throughput
 - Nonce is derived, but unique every packet - allows faster sending
2026-02-10 19:33:56 +01:00
204f89006f Merge branch 'dev' into beta 2026-01-18 19:54:59 +01:00
57d260976c Merge branch 'dev' into beta 2026-01-18 19:47:48 +01:00
4fa26d51d0 Merge branch 'dev' into beta 2026-01-11 20:32:28 +01:00
e1118ccafe Merge branch 'dev' into beta 2026-01-01 17:21:15 +01:00
00f72e1a64 Merge branch 'dev' into beta - Version 1.0.0 2026-01-01 16:32:59 +01:00
3cd99243ad Version 1.0.0 2026-01-01 16:32:14 +01:00
8f536abe77 Merge branch 'dev' into beta - Version 1.0.0 2026-01-01 16:23:37 +01:00
3eadd41a00 Merge branch 'dev' into beta 2025-12-29 20:28:15 +01:00
714aa52f98 Merge branch 'dev' into beta 2025-12-29 19:06:59 +01:00
a2ecc589f8 Merge branch 'dev' into beta - Version b0.1 2025-12-08 17:37:44 +01:00
640a751f9b Merge branch 'dev' into beta - Alpha 0.6
This version adds dynamic IP assignment based on config.
2025-12-02 18:46:28 +01:00
a08dba5b59 Merge branch 'dev' into beta
This is the merge of version a0.5 into beta.
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:27:15 +01:00
4ba59fb23f Merge pull request 'First working alpha, version a0.4.' (#6) from dev into beta
Reviewed-on: #6
2025-11-18 20:07:30 +00:00
9e5e728438 Merge pull request 'Add legal clarification' (#3) from dev into beta
Reviewed-on: #3
2025-11-10 15:58:18 +00:00
d20bee9e60 Merge pull request 'Update license' (#1) from dev into beta
Reviewed-on: #1
2025-11-10 15:15:10 +00:00
9 changed files with 50 additions and 92 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.1 VERSION 1.1.0
LANGUAGES CXX LANGUAGES CXX
) )

View File

@@ -11,7 +11,6 @@
#include <columnlynx/common/utils.hpp> #include <columnlynx/common/utils.hpp>
#include <columnlynx/common/libsodium_wrapper.hpp> #include <columnlynx/common/libsodium_wrapper.hpp>
#include <array> #include <array>
#include <atomic>
#include <algorithm> #include <algorithm>
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
@@ -90,8 +89,8 @@ namespace ColumnLynx::Net::TCP {
// TODO: Move ptrs to smart ptrs // TODO: Move ptrs to smart ptrs
std::atomic<bool> mConnected{false}; bool mConnected = false;
std::atomic<bool> mHandshakeComplete{false}; bool mHandshakeComplete = false;
tcp::resolver mResolver; tcp::resolver mResolver;
tcp::socket mSocket; tcp::socket mSocket;
std::shared_ptr<MessageHandler> mHandler; std::shared_ptr<MessageHandler> mHandler;

View File

@@ -39,6 +39,6 @@ namespace ColumnLynx::Net::TCP {
std::array<uint8_t, 3> mHeader{}; // [type][lenHigh][lenLow] std::array<uint8_t, 3> mHeader{}; // [type][lenHigh][lenLow]
std::vector<uint8_t> mBody; std::vector<uint8_t> mBody;
std::function<void(AnyMessageType, std::string)> mOnMessage; std::function<void(AnyMessageType, std::string)> mOnMessage;
std::function<void(const asio::error_code&)> mOnDisconnect; std::function<void(asio::error_code&)> mOnDisconnect;
}; };
} }

View File

@@ -14,25 +14,19 @@ namespace ColumnLynx::Net::TCP {
asio::async_connect(mSocket, endpoints, asio::async_connect(mSocket, endpoints,
[this, self](asio::error_code ec, const tcp::endpoint&) { [this, self](asio::error_code ec, const tcp::endpoint&) {
if (!ec) { if (!ec) {
mConnected.store(true, std::memory_order_relaxed); mConnected = true;
Utils::log("Client connected."); Utils::log("Client connected.");
mHandler = std::make_shared<MessageHandler>(std::move(mSocket)); mHandler = std::make_shared<MessageHandler>(std::move(mSocket));
mHandler->onMessage([weakSelf = weak_from_this()](AnyMessageType type, const std::string& data) { mHandler->onMessage([this](AnyMessageType type, const std::string& data) {
if (auto self = weakSelf.lock()) { mHandleMessage(static_cast<ServerMessageType>(MessageHandler::toUint8(type)), data);
self->mHandleMessage(static_cast<ServerMessageType>(MessageHandler::toUint8(type)), data);
}
}); });
// Close only after peer FIN to avoid RSTs // Close only after peer FIN to avoid RSTs
mHandler->onDisconnect([weakSelf = weak_from_this()](const asio::error_code& ec) { mHandler->onDisconnect([this](const asio::error_code& ec) {
auto self = weakSelf.lock();
if (!self) {
return;
}
asio::error_code ec2; asio::error_code ec2;
if (self->mHandler) { if (mHandler) {
self->mHandler->socket().close(ec2); mHandler->socket().close(ec2);
} }
self->mConnected.store(false, std::memory_order_relaxed); mConnected = false;
Utils::log(std::string("Server disconnected: ") + ec.message()); Utils::log(std::string("Server disconnected: ") + ec.message());
}); });
mHandler->start(); mHandler->start();
@@ -52,7 +46,6 @@ 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
@@ -80,7 +73,7 @@ namespace ColumnLynx::Net::TCP {
} }
void TCPClient::sendMessage(ClientMessageType type, const std::string& data) { void TCPClient::sendMessage(ClientMessageType type, const std::string& data) {
if (!mConnected.load(std::memory_order_relaxed)) { if (!mConnected) {
Utils::error("Cannot send message, client not connected."); Utils::error("Cannot send message, client not connected.");
return; return;
} }
@@ -93,7 +86,7 @@ namespace ColumnLynx::Net::TCP {
} }
void TCPClient::disconnect(bool echo) { void TCPClient::disconnect(bool echo) {
if (mConnected.load(std::memory_order_relaxed) && mHandler) { if (mConnected && mHandler) {
if (echo) { if (echo) {
mHandler->sendMessage(ClientMessageType::GRACEFUL_DISCONNECT, "Goodbye"); mHandler->sendMessage(ClientMessageType::GRACEFUL_DISCONNECT, "Goodbye");
} }
@@ -113,17 +106,17 @@ namespace ColumnLynx::Net::TCP {
} }
bool TCPClient::isHandshakeComplete() const { bool TCPClient::isHandshakeComplete() const {
return mHandshakeComplete.load(std::memory_order_relaxed); return mHandshakeComplete;
} }
bool TCPClient::isConnected() const { bool TCPClient::isConnected() const {
return mConnected.load(std::memory_order_relaxed); return mConnected;
} }
void TCPClient::mStartHeartbeat() { void TCPClient::mStartHeartbeat() {
auto self = shared_from_this(); auto self = shared_from_this();
mHeartbeatTimer.expires_after(std::chrono::seconds(5)); mHeartbeatTimer.expires_after(std::chrono::seconds(5));
mHeartbeatTimer.async_wait([self](const asio::error_code& ec) { mHeartbeatTimer.async_wait([this, self](const asio::error_code& ec) {
if (ec == asio::error::operation_aborted) { if (ec == asio::error::operation_aborted) {
return; // Timer was cancelled return; // Timer was cancelled
} }
@@ -136,11 +129,9 @@ namespace ColumnLynx::Net::TCP {
// Close sockets forcefully, server is dead // Close sockets forcefully, server is dead
asio::error_code ec; asio::error_code ec;
if (self->mHandler) { mHandler->socket().shutdown(tcp::socket::shutdown_both, ec);
self->mHandler->socket().shutdown(tcp::socket::shutdown_both, ec); mHandler->socket().close(ec);
self->mHandler->socket().close(ec); mConnected = false;
}
self->mConnected.store(false, std::memory_order_relaxed);
ClientSession::getInstance().setAESKey({}); // Clear AES key with all zeros ClientSession::getInstance().setAESKey({}); // Clear AES key with all zeros
ClientSession::getInstance().setSessionID(0); ClientSession::getInstance().setSessionID(0);
@@ -269,7 +260,7 @@ namespace ColumnLynx::Net::TCP {
mTun->configureIP(clientIP, serverIP, prefixLen, mtu); mTun->configureIP(clientIP, serverIP, prefixLen, mtu);
} }
mHandshakeComplete.store(true, std::memory_order_relaxed); mHandshakeComplete = true;
} }
break; break;
@@ -284,13 +275,13 @@ namespace ColumnLynx::Net::TCP {
break; break;
case ServerMessageType::GRACEFUL_DISCONNECT: case ServerMessageType::GRACEFUL_DISCONNECT:
Utils::log("Server is disconnecting: " + data); Utils::log("Server is disconnecting: " + data);
if (mConnected.load(std::memory_order_relaxed)) { // Prevent Recursion if (mConnected) { // Prevent Recursion
disconnect(false); disconnect(false);
} }
break; break;
case ServerMessageType::KILL_CONNECTION: case ServerMessageType::KILL_CONNECTION:
Utils::warn("Server is killing the connection: " + data); Utils::warn("Server is killing the connection: " + data);
if (mConnected.load(std::memory_order_relaxed)) { if (mConnected) {
disconnect(false); disconnect(false);
} }
break; break;

View File

@@ -86,14 +86,10 @@ namespace ColumnLynx::Net {
std::unique_lock lock(mMutex); std::unique_lock lock(mMutex);
mSessionIPs[sessionID] = ip; mSessionIPs[sessionID] = ip;
auto it = mSessions.find(sessionID); /*if (mIPSessions.find(sessionID) == mIPSessions.end()) {
if (it == mSessions.end() || !it->second) { Utils::debug("yikes");
Utils::warn("SessionRegistry::lockIP called for unknown session " + std::to_string(sessionID)); }*/
mSessionIPs.erase(sessionID); mIPSessions[ip] = mSessions.find(sessionID)->second;
return;
}
mIPSessions[ip] = it->second;
} }
void SessionRegistry::deallocIP(uint32_t sessionID) { void SessionRegistry::deallocIP(uint32_t sessionID) {

View File

@@ -5,7 +5,6 @@
#include <columnlynx/common/net/tcp/tcp_message_handler.hpp> #include <columnlynx/common/net/tcp/tcp_message_handler.hpp>
#include <columnlynx/common/net/tcp/net_helper.hpp> #include <columnlynx/common/net/tcp/net_helper.hpp>
#include <columnlynx/common/utils.hpp> #include <columnlynx/common/utils.hpp>
#include <memory>
namespace ColumnLynx::Net::TCP { namespace ColumnLynx::Net::TCP {
void MessageHandler::start() { void MessageHandler::start() {
@@ -18,17 +17,17 @@ namespace ColumnLynx::Net::TCP {
return static_cast<uint8_t>(type); return static_cast<uint8_t>(type);
}, type); }, type);
auto data = std::make_shared<std::vector<uint8_t>>(); std::vector<uint8_t> data;
data->push_back(typeByte); data.push_back(typeByte);
uint16_t length = payload.size(); uint16_t length = payload.size();
data->push_back(length >> 8); data.push_back(length >> 8);
data->push_back(length & 0xFF); data.push_back(length & 0xFF);
data->insert(data->end(), payload.begin(), payload.end()); data.insert(data.end(), payload.begin(), payload.end());
auto self = shared_from_this(); auto self = shared_from_this();
asio::async_write(mSocket, asio::buffer(*data), asio::async_write(mSocket, asio::buffer(data),
[self, data](asio::error_code ec, std::size_t) { [self](asio::error_code ec, std::size_t) {
if (ec) { if (ec) {
Utils::error("Send failed: " + ec.message()); Utils::error("Send failed: " + ec.message());
} }

View File

@@ -85,7 +85,7 @@ namespace ColumnLynx::Utils {
} }
std::string getVersion() { std::string getVersion() {
return "1.1.1"; return "1.1.0";
} }
unsigned short serverPort() { unsigned short serverPort() {

View File

@@ -6,7 +6,6 @@
#include <iostream> #include <iostream>
#include <thread> #include <thread>
#include <chrono> #include <chrono>
#include <cstring>
#include <columnlynx/common/utils.hpp> #include <columnlynx/common/utils.hpp>
#include <columnlynx/common/panic_handler.hpp> #include <columnlynx/common/panic_handler.hpp>
#include <columnlynx/server/net/tcp/tcp_server.hpp> #include <columnlynx/server/net/tcp/tcp_server.hpp>
@@ -173,24 +172,9 @@ int main(int argc, char** argv) {
continue; continue;
} }
if (packet.size() < 20) {
Utils::warn("TUN: Dropping packet smaller than IPv4 header (" + std::to_string(packet.size()) + " bytes)");
continue;
}
const uint8_t* ip = packet.data(); const uint8_t* ip = packet.data();
uint8_t ipVersion = (ip[0] >> 4); uint32_t srcIP = ntohl(*(uint32_t*)(ip + 12)); // IPv4 source address offset
if (ipVersion != 4) { uint32_t dstIP = ntohl(*(uint32_t*)(ip + 16)); // IPv4 destination address offset
Utils::debug("TUN: Non-IPv4 packet received (version=" + std::to_string(ipVersion) + "), skipping server IPv4 routing path.");
continue;
}
uint32_t srcIPNet = 0;
uint32_t dstIPNet = 0;
std::memcpy(&srcIPNet, ip + 12, sizeof(srcIPNet)); // IPv4 source address offset
std::memcpy(&dstIPNet, ip + 16, sizeof(dstIPNet)); // IPv4 destination address offset
uint32_t srcIP = ntohl(srcIPNet);
uint32_t dstIP = ntohl(dstIPNet);
// First, check if destination IP is a registered client (e.g., server responding to client or client-to-client) // First, check if destination IP is a registered client (e.g., server responding to client or client-to-client)
auto dstSession = SessionRegistry::getInstance().getByIP(dstIP); auto dstSession = SessionRegistry::getInstance().getByIP(dstIP);

View File

@@ -14,31 +14,23 @@ namespace ColumnLynx::Net::TCP {
Utils::warn("Failed to get remote endpoint: " + std::string(e.what())); Utils::warn("Failed to get remote endpoint: " + std::string(e.what()));
} }
mHandler->onMessage([weakSelf = weak_from_this()](AnyMessageType type, const std::string& data) { mHandler->onMessage([this](AnyMessageType type, const std::string& data) {
if (auto self = weakSelf.lock()) { mHandleMessage(static_cast<ClientMessageType>(MessageHandler::toUint8(type)), data);
self->mHandleMessage(static_cast<ClientMessageType>(MessageHandler::toUint8(type)), data);
}
}); });
mHandler->onDisconnect([weakSelf = weak_from_this()](const asio::error_code& ec) { mHandler->onDisconnect([this](const asio::error_code& ec) {
auto self = weakSelf.lock();
if (!self) {
return;
}
// Peer has closed; finalize locally without sending RST // Peer has closed; finalize locally without sending RST
Utils::log("Client disconnected: " + self->mRemoteIP + " - " + ec.message()); Utils::log("Client disconnected: " + mRemoteIP + " - " + ec.message());
asio::error_code ec2; asio::error_code ec2;
if (self->mHandler) { mHandler->socket().close(ec2);
self->mHandler->socket().close(ec2);
}
SessionRegistry::getInstance().erase(self->mConnectionSessionID); SessionRegistry::getInstance().erase(mConnectionSessionID);
SessionRegistry::getInstance().deallocIP(self->mConnectionSessionID); SessionRegistry::getInstance().deallocIP(mConnectionSessionID);
Utils::log("Closed connection to " + self->mRemoteIP); Utils::log("Closed connection to " + mRemoteIP);
if (self->mOnDisconnect) { if (mOnDisconnect) {
self->mOnDisconnect(self); mOnDisconnect(shared_from_this());
} }
}); });
@@ -85,7 +77,7 @@ namespace ColumnLynx::Net::TCP {
void TCPConnection::mStartHeartbeat() { void TCPConnection::mStartHeartbeat() {
auto self = shared_from_this(); auto self = shared_from_this();
mHeartbeatTimer.expires_after(std::chrono::seconds(5)); mHeartbeatTimer.expires_after(std::chrono::seconds(5));
mHeartbeatTimer.async_wait([self](const asio::error_code& ec) { mHeartbeatTimer.async_wait([this, self](const asio::error_code& ec) {
if (ec == asio::error::operation_aborted) { if (ec == asio::error::operation_aborted) {
return; // Timer was cancelled return; // Timer was cancelled
} }
@@ -98,13 +90,10 @@ namespace ColumnLynx::Net::TCP {
// Remove socket forcefully, client is dead // Remove socket forcefully, client is dead
asio::error_code ec; asio::error_code ec;
if (self->mHandler) { mHandler->socket().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
self->mHandler->socket().shutdown(asio::ip::tcp::socket::shutdown_both, ec); mHandler->socket().close(ec);
self->mHandler->socket().close(ec);
}
SessionRegistry::getInstance().erase(self->mConnectionSessionID); SessionRegistry::getInstance().erase(self->mConnectionSessionID);
SessionRegistry::getInstance().deallocIP(self->mConnectionSessionID);
return; return;
} }