12 Commits

Author SHA1 Message Date
cb0f674c52 Merge branch 'beta' - Version b0.1
macOS Support
2025-12-08 17:38:05 +01:00
a2ecc589f8 Merge branch 'dev' into beta - Version b0.1 2025-12-08 17:37:44 +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
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
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
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
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
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
9f52bdd54c Merge pull request 'beta' (#4) from beta into master
Reviewed-on: #4
2025-11-10 15:58:29 +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
29e90938c5 Merge pull request 'beta - Update License' (#2) from beta into master
Reviewed-on: #2
2025-11-10 15:15:31 +00:00
d20bee9e60 Merge pull request 'Update license' (#1) from dev into beta
Reviewed-on: #1
2025-11-10 15:15:10 +00:00
16 changed files with 118 additions and 372 deletions

1
.gitignore vendored
View File

@@ -13,4 +13,3 @@ CMakeUserPresets.json
build/
.vscode/
.DS_Store

View File

@@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.16)
# If MAJOR is 0, and MINOR > 0, Version is BETA
project(ColumnLynx
VERSION 0.3.0
VERSION 0.1.0
LANGUAGES CXX
)
@@ -80,6 +80,15 @@ FetchContent_MakeAvailable(Sodium)
FetchContent_MakeAvailable(asio)
FetchContent_MakeAvailable(cxxopts)
# OpenSSL
find_package(OpenSSL REQUIRED)
if(OPENSSL_FOUND)
message(STATUS "Found OpenSSL version ${OPENSSL_VERSION}")
include_directories(${OPENSSL_INCLUDE_DIR})
else()
message(FATAL_ERROR "OpenSSL not found")
endif()
# ---------------------------------------------------------
# Output directories
# ---------------------------------------------------------
@@ -99,17 +108,7 @@ endforeach()
# ---------------------------------------------------------
file(GLOB_RECURSE COMMON_SRC CONFIGURE_DEPENDS src/common/*.cpp)
add_library(common STATIC ${COMMON_SRC})
target_link_libraries(common PUBLIC sodium cxxopts::cxxopts)
if (WIN32)
target_link_libraries(common PUBLIC
ws2_32
iphlpapi
advapi32
mswsock
)
endif()
target_link_libraries(common PUBLIC sodium OpenSSL::SSL OpenSSL::Crypto cxxopts::cxxopts)
target_include_directories(common PUBLIC
${PROJECT_SOURCE_DIR}/include
${sodium_SOURCE_DIR}/src/libsodium/include
@@ -123,12 +122,7 @@ target_compile_definitions(common PUBLIC ASIO_STANDALONE)
# ---------------------------------------------------------
file(GLOB_RECURSE CLIENT_SRC CONFIGURE_DEPENDS src/client/*.cpp)
add_executable(client ${CLIENT_SRC})
target_link_libraries(client PRIVATE common sodium cxxopts::cxxopts)
if (WIN32)
target_link_libraries(client PRIVATE
dbghelp
)
endif()
target_link_libraries(client PRIVATE common sodium OpenSSL::SSL OpenSSL::Crypto cxxopts::cxxopts)
target_include_directories(client PRIVATE
${PROJECT_SOURCE_DIR}/include
${sodium_SOURCE_DIR}/src/libsodium/include
@@ -143,12 +137,7 @@ set_target_properties(client PROPERTIES OUTPUT_NAME "columnlynx_client")
# ---------------------------------------------------------
file(GLOB_RECURSE SERVER_SRC CONFIGURE_DEPENDS src/server/*.cpp)
add_executable(server ${SERVER_SRC})
target_link_libraries(server PRIVATE common sodium cxxopts::cxxopts)
if (WIN32)
target_link_libraries(server PRIVATE
dbghelp
)
endif()
target_link_libraries(server PRIVATE common sodium OpenSSL::SSL OpenSSL::Crypto cxxopts::cxxopts)
target_include_directories(server PRIVATE
${PROJECT_SOURCE_DIR}/include
${sodium_SOURCE_DIR}/src/libsodium/include

View File

@@ -11,6 +11,10 @@
#include <columnlynx/common/utils.hpp>
#include <array>
#include <vector>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#include <openssl/pem.h>
#include <openssl/x509v3.h>
#include <memory>
#include <cstring>

View File

@@ -28,14 +28,11 @@
#include <arpa/inet.h>
#include <sys/poll.h>
#elif defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#define WINTUN_STATIC
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <locale>
#include <codecvt>
#include <winsock2.h>
#include <wintun/wintun.h>
#pragma comment(lib, "advapi32.lib")
#endif
namespace ColumnLynx::Net {
@@ -47,8 +44,6 @@ namespace ColumnLynx::Net {
bool configureIP(uint32_t clientIP, uint32_t serverIP,
uint8_t prefixLen, uint16_t mtu);
void resetIP();
std::vector<uint8_t> readPacket();
void writePacket(const std::vector<uint8_t>& packet);
@@ -80,42 +75,6 @@ namespace ColumnLynx::Net {
return ntohl(addr.s_addr);
}
static inline std::string ipv6ToString(IPv6Addr &ip,
bool flip = false)
{
struct in6_addr addr;
if (flip) {
IPv6Addr flipped;
for (size_t i = 0; i < 16; ++i)
flipped[i] = ip[15 - i];
memcpy(addr.s6_addr, flipped.data(), 16);
} else {
memcpy(addr.s6_addr, ip.data(), 16);
}
char buf[INET6_ADDRSTRLEN];
if (!inet_ntop(AF_INET6, &addr, buf, sizeof(buf)))
return "::"; // Fallback
return std::string(buf);
}
static inline IPv6Addr stringToIpv6(const std::string &ipStr)
{
IPv6Addr result{};
struct in6_addr addr;
if (inet_pton(AF_INET6, ipStr.c_str(), &addr) != 1) {
// "::"
result.fill(0);
return result;
}
memcpy(result.data(), addr.s6_addr, 16);
return result;
}
static inline uint32_t prefixLengthToNetmask(uint8_t prefixLen) {
if (prefixLen == 0) return 0;
uint32_t mask = (0xFFFFFFFF << (32 - prefixLen)) & 0xFFFFFFFF;
@@ -130,9 +89,7 @@ namespace ColumnLynx::Net {
std::string mIfName;
int mFd; // POSIX
#if defined(_WIN32)
WINTUN_ADAPTER_HANDLE mAdapter = nullptr;
WINTUN_SESSION_HANDLE mSession = nullptr;
HANDLE mHandle = nullptr;
HANDLE mHandle; // Windows
#endif
};
}

View File

@@ -42,7 +42,7 @@ namespace ColumnLynx::Net::TCP {
// Set callback for disconnects
void setDisconnectCallback(std::function<void(std::shared_ptr<TCPConnection>)> cb);
// Disconnect the client
void disconnect(bool echo = true);
void disconnect();
// Get the assigned session ID
uint64_t getSessionID() const;

View File

@@ -34,33 +34,24 @@ namespace ColumnLynx::Net::TCP {
// Preload the config map
mRawServerConfig = Utils::getConfigMap("server_config", {"NETWORK", "SUBNET_MASK"});
asio::error_code ec_open, ec_v6only, ec_bind;
asio::error_code ec;
if (!ipv4Only) {
// Try IPv6 (dual-stack if supported)
// Try IPv6 first (dual-stack check)
asio::ip::tcp::endpoint endpoint_v6(asio::ip::tcp::v6(), port);
mAcceptor.open(endpoint_v6.protocol(), ec_open);
if (!ec_open) {
// Try enabling dual-stack, but DO NOT treat failure as fatal
mAcceptor.set_option(asio::ip::v6_only(false), ec_v6only);
// Try binding IPv6
mAcceptor.bind(endpoint_v6, ec_bind);
mAcceptor.open(endpoint_v6.protocol(), ec);
if (!ec) {
mAcceptor.set_option(asio::ip::v6_only(false), ec); // Allow dual-stack if possible
mAcceptor.bind(endpoint_v6, ec);
}
}
// If IPv6 bind failed OR IPv6 open failed OR forced IPv4-only
if (ipv4Only || ec_open || ec_bind) {
if (!ipv4Only)
Utils::warn("TCP: IPv6 unavailable (open=" + ec_open.message() +
", bind=" + ec_bind.message() +
"), falling back to IPv4 only");
// Fallback to IPv4 if anything failed
if (ec || ipv4Only) {
Utils::warn("TCP: IPv6 unavailable (" + ec.message() + "), falling back to IPv4 only");
asio::ip::tcp::endpoint endpoint_v4(asio::ip::tcp::v4(), port);
mAcceptor.close(); // guarantee clean state
mAcceptor.close(); // ensure clean state
mAcceptor.open(endpoint_v4.protocol());
mAcceptor.bind(endpoint_v4);
}

View File

@@ -16,37 +16,24 @@ namespace ColumnLynx::Net::UDP {
UDPServer(asio::io_context& ioContext, uint16_t port, std::shared_ptr<bool> hostRunning, bool ipv4Only = false, std::shared_ptr<VirtualInterface> tun = nullptr)
: mSocket(ioContext), mHostRunning(hostRunning), mTun(tun)
{
asio::error_code ec_open, ec_v6only, ec_bind;
asio::error_code ec;
if (!ipv4Only) {
// Try IPv6 first (dual-stack check)
asio::ip::udp::endpoint endpoint_v6(asio::ip::udp::v6(), port);
// Try opening IPv6 socket
mSocket.open(endpoint_v6.protocol(), ec_open);
if (!ec_open) {
// Try enabling dual-stack (non fatal if it fails)
mSocket.set_option(asio::ip::v6_only(false), ec_v6only);
// Attempt bind
mSocket.bind(endpoint_v6, ec_bind);
mSocket.open(endpoint_v6.protocol(), ec);
if (!ec) {
mSocket.set_option(asio::ip::v6_only(false), ec); // Allow dual-stack if possible
mSocket.bind(endpoint_v6, ec);
}
}
// Fallback to IPv4 if IPv6 is unusable
if (ipv4Only || ec_open || ec_bind) {
if (!ipv4Only) {
Utils::warn(
"UDP: IPv6 unavailable (open=" + ec_open.message() +
", bind=" + ec_bind.message() +
"), falling back to IPv4 only"
);
}
// Fallback to IPv4 if anything failed
if (ec || ipv4Only) {
Utils::warn("UDP: IPv6 unavailable (" + ec.message() + "), falling back to IPv4 only");
asio::ip::udp::endpoint endpoint_v4(asio::ip::udp::v4(), port);
mSocket.close();
mSocket = asio::ip::udp::socket(ioContext); // fully reset internal state
mSocket.close(); // ensure clean state
mSocket.open(endpoint_v4.protocol());
mSocket.bind(endpoint_v4);
}

View File

@@ -21,19 +21,18 @@ volatile sig_atomic_t done = 0;
void signalHandler(int signum) {
if (signum == SIGINT || signum == SIGTERM) {
//log("Received termination signal. Shutting down client.");
done = 1;
}
}
int main(int argc, char** argv) {
// Capture SIGINT and SIGTERM for graceful shutdown
#if !defined(_WIN32)
struct sigaction action;
memset(&action, 0, sizeof(struct sigaction));
action.sa_handler = signalHandler;
sigaction(SIGINT, &action, nullptr);
sigaction(SIGTERM, &action, nullptr);
#endif
PanicHandler::init();
@@ -69,7 +68,7 @@ int main(int argc, char** argv) {
log("This software is licensed under the GPLv2 only OR the GPLv3. See LICENSES/ for details.");
#if defined(__WIN32__)
//WintunInitialize();
WintunInitialize();
#endif
std::shared_ptr<VirtualInterface> tun = std::make_shared<VirtualInterface>(optionsObj["interface"].as<std::string>());
@@ -96,18 +95,14 @@ int main(int argc, char** argv) {
});
//ioThread.join();
log("Attempting connection to " + host + ":" + port);
debug("Client connection flag: " + std::to_string(client->isConnected()));
debug("Client handshake flag: " + std::to_string(client->isHandshakeComplete()));
debug("isDone flag: " + std::to_string(done));
log("Client connected to " + host + ":" + port);
// Client is running
while ((client->isConnected() || !client->isHandshakeComplete()) && !done) {
//debug("Client connection flag: " + std::to_string(client->isConnected()));
auto packet = tun->readPacket();
/*if (!client->isConnected() || done) {
if (!client->isConnected() || done) {
break; // Bail out if connection died or signal set while blocked
}*/
}
if (packet.empty()) {
continue;

View File

@@ -3,7 +3,7 @@
// Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#include <columnlynx/client/net/tcp/tcp_client.hpp>
//#include <arpa/inet.h>
#include <arpa/inet.h>
namespace ColumnLynx::Net::TCP {
void TCPClient::start() {
@@ -13,35 +13,22 @@ namespace ColumnLynx::Net::TCP {
if (!ec) {
asio::async_connect(mSocket, endpoints,
[this, self](asio::error_code ec, const tcp::endpoint&) {
if (!ec) {
if (!NetHelper::isExpectedDisconnect(ec)) {
mConnected = true;
Utils::log("Client connected.");
mHandler = std::make_shared<MessageHandler>(std::move(mSocket));
mHandler->onMessage([this](AnyMessageType type, const std::string& data) {
mHandleMessage(static_cast<ServerMessageType>(MessageHandler::toUint8(type)), data);
});
// Close only after peer FIN to avoid RSTs
mHandler->onDisconnect([this](const asio::error_code& ec) {
asio::error_code ec2;
if (mHandler) {
mHandler->socket().close(ec2);
}
mConnected = false;
Utils::log(std::string("Server disconnected: ") + ec.message());
});
mHandler->start();
// Init connection handshake
Utils::log("Sending handshake init to server.");
// Check if hostname or IPv4/IPv6
try {
asio::ip::make_address(mHost);
self->mIsHostDomain = false; // IPv4 or IPv6 literal
} catch (const asio::system_error&) {
self->mIsHostDomain = true; // hostname / domain
}
sockaddr_in addr4{};
sockaddr_in6 addr6{};
self->mIsHostDomain = inet_pton(AF_INET, mHost.c_str(), (void*)(&addr4)) != 1 && inet_pton(AF_INET6, mHost.c_str(), (void*)(&addr6)) != 1; // Voodoo black magic
std::vector<uint8_t> payload;
payload.reserve(1 + crypto_box_PUBLICKEYBYTES);
@@ -59,10 +46,8 @@ namespace ColumnLynx::Net::TCP {
mStartHeartbeat();
} else {
if (!NetHelper::isExpectedDisconnect(ec)) {
Utils::error("Client connect failed: " + ec.message());
}
}
});
} else {
Utils::error("Client resolve failed: " + ec.message());
@@ -92,14 +77,18 @@ namespace ColumnLynx::Net::TCP {
asio::error_code ec;
mHeartbeatTimer.cancel();
// Half-close: stop sending, keep reading until peer FIN
mHandler->socket().shutdown(tcp::socket::shutdown_send, ec);
mHandler->socket().shutdown(tcp::socket::shutdown_both, ec);
if (ec) {
Utils::error("Error during socket shutdown: " + ec.message());
}
// Do not close immediately; rely on onDisconnect to finalize
Utils::log("Client initiated graceful disconnect (half-close).");
mHandler->socket().close(ec);
if (ec) {
Utils::error("Error during socket close: " + ec.message());
}
mConnected = false;
Utils::log("Client disconnected.");
}
}
@@ -280,12 +269,6 @@ namespace ColumnLynx::Net::TCP {
disconnect(false);
}
break;
case ServerMessageType::KILL_CONNECTION:
Utils::warn("Server is killing the connection: " + data);
if (mConnected) {
disconnect(false);
}
break;
default:
Utils::log("Received unknown message type from server.");
break;

View File

@@ -6,41 +6,10 @@
namespace ColumnLynx::Net::UDP {
void UDPClient::start() {
asio::error_code ec;
// Resolve using an unspecified protocol (allows both IPv4 and IPv6)
auto endpoints = mResolver.resolve(
asio::ip::udp::v6(), // Try IPv6 first (dual-stack with v4)
mHost,
mPort,
ec
);
if (ec) {
// If IPv6 fails (host has no AAAA), try IPv4
endpoints = mResolver.resolve(
asio::ip::udp::v4(),
mHost,
mPort,
ec
);
}
if (ec) {
Utils::error("UDP resolve failed: " + ec.message());
return;
}
// Use whichever endpoint resolved
// TODO: Add IPv6
auto endpoints = mResolver.resolve(asio::ip::udp::v4(), mHost, mPort);
mRemoteEndpoint = *endpoints.begin();
// Open socket using the resolved endpoint's protocol
mSocket.open(mRemoteEndpoint.protocol(), ec);
if (ec) {
Utils::error("UDP socket open failed: " + ec.message());
return;
}
mSocket.open(asio::ip::udp::v4());
Utils::log("UDP Client ready to send to " + mRemoteEndpoint.address().to_string() + ":" + std::to_string(mRemoteEndpoint.port()));
}

View File

@@ -43,20 +43,14 @@ namespace ColumnLynx::Net::TCP {
auto self = shared_from_this();
asio::async_read(mSocket, asio::buffer(mHeader),
[this, self](asio::error_code ec, std::size_t) {
if (!ec) {
if (!NetHelper::isExpectedDisconnect(ec)) {
mCurrentType = decodeMessageType(mHeader[0]);
uint16_t len = (mHeader[1] << 8) | mHeader[2];
mReadBody(len);
} else {
if (!NetHelper::isExpectedDisconnect(ec)) {
Utils::error("Header read failed: " + ec.message());
}
// Connection closed, trigger disconnect handler
if (mOnDisconnect) {
mOnDisconnect(ec);
}
}
}
);
}
@@ -67,7 +61,7 @@ namespace ColumnLynx::Net::TCP {
asio::async_read(mSocket, asio::buffer(mBody),
[this, self](asio::error_code ec, std::size_t) {
if (!ec) {
if (!NetHelper::isExpectedDisconnect(ec)) {
std::string payload(mBody.begin(), mBody.end());
// Dispatch based on message type
@@ -77,10 +71,8 @@ namespace ColumnLynx::Net::TCP {
mReadHeader(); // Keep listening
} else {
if (!NetHelper::isExpectedDisconnect(ec)) {
Utils::error("Body read failed: " + ec.message());
}
// Connection closed, trigger disconnect handler
if (mOnDisconnect) {
mOnDisconnect(ec);
}

View File

@@ -49,7 +49,7 @@ namespace ColumnLynx::Utils {
}
std::string getVersion() {
return "b0.3";
return "b0.1";
}
unsigned short serverPort() {

View File

@@ -6,55 +6,6 @@
// This is all fucking voodoo dark magic.
#if defined(_WIN32)
static HMODULE gWintun = nullptr;
static WINTUN_OPEN_ADAPTER_FUNC* pWintunOpenAdapter;
static WINTUN_START_SESSION_FUNC* pWintunStartSession;
static WINTUN_END_SESSION_FUNC* pWintunEndSession;
static WINTUN_GET_READ_WAIT_EVENT_FUNC* pWintunGetReadWaitEvent;
static WINTUN_RECEIVE_PACKET_FUNC* pWintunReceivePacket;
static WINTUN_RELEASE_RECEIVE_PACKET_FUNC* pWintunReleaseReceivePacket;
static WINTUN_ALLOCATE_SEND_PACKET_FUNC* pWintunAllocateSendPacket;
static WINTUN_SEND_PACKET_FUNC* pWintunSendPacket;
static WINTUN_CREATE_ADAPTER_FUNC* pWintunCreateAdapter;
static void InitializeWintun()
{
if (gWintun)
return;
gWintun = LoadLibraryExW(
L"wintun.dll",
nullptr,
LOAD_LIBRARY_SEARCH_APPLICATION_DIR
);
if (!gWintun)
throw std::runtime_error("Failed to load wintun.dll");
#define RESOLVE(name, type) \
p##name = reinterpret_cast<type*>( \
GetProcAddress(gWintun, #name)); \
if (!p##name) \
throw std::runtime_error("Missing Wintun symbol: " #name);
RESOLVE(WintunOpenAdapter, WINTUN_OPEN_ADAPTER_FUNC)
RESOLVE(WintunStartSession, WINTUN_START_SESSION_FUNC)
RESOLVE(WintunEndSession, WINTUN_END_SESSION_FUNC)
RESOLVE(WintunGetReadWaitEvent, WINTUN_GET_READ_WAIT_EVENT_FUNC)
RESOLVE(WintunReceivePacket, WINTUN_RECEIVE_PACKET_FUNC)
RESOLVE(WintunReleaseReceivePacket, WINTUN_RELEASE_RECEIVE_PACKET_FUNC)
RESOLVE(WintunAllocateSendPacket, WINTUN_ALLOCATE_SEND_PACKET_FUNC)
RESOLVE(WintunSendPacket, WINTUN_SEND_PACKET_FUNC)
RESOLVE(WintunCreateAdapter, WINTUN_CREATE_ADAPTER_FUNC)
#undef RESOLVE
}
#endif // _WIN32
namespace ColumnLynx::Net {
// ------------------------------ Constructor ------------------------------
VirtualInterface::VirtualInterface(const std::string& ifName)
@@ -112,33 +63,20 @@ namespace ColumnLynx::Net {
Utils::log("VirtualInterface: opened macOS UTUN: " + mIfName);
#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");
// Convert to Windows' wchar_t* thingy
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
std::wstring wide_string = converter.from_bytes(mIfName);
const wchar_t* wide_c_str = wide_string.c_str();
InitializeWintun();
mAdapter = pWintunOpenAdapter(wide_c_str);
if (!mAdapter) {
mAdapter = pWintunCreateAdapter(
wide_c_str,
L"ColumnLynx",
nullptr
);
}
if (!mAdapter)
throw std::runtime_error("Failed to open or create Wintun adapter (run running as admin)");
mSession = pWintunStartSession(mAdapter, 0x200000);
if (!mSession)
WINTUN_SESSION_HANDLE session =
WintunStartSession(adapter, 0x200000); // ring buffer size
if (!session)
throw std::runtime_error("Failed to start Wintun session");
mHandle = pWintunGetReadWaitEvent(mSession);
mFd = -1;
mHandle = WintunGetReadWaitEvent(session);
mFd = -1; // not used on Windows
mIfName = ifName;
#else
throw std::runtime_error("Unsupported platform");
@@ -151,8 +89,9 @@ namespace ColumnLynx::Net {
if (mFd >= 0)
close(mFd);
#elif defined(_WIN32)
if (mSession)
pWintunEndSession(mSession);
// Wintun sessions need explicit stop
// (assuming you stored the session handle as member)
// WintunEndSession(mSession);
#endif
}
@@ -218,13 +157,11 @@ namespace ColumnLynx::Net {
#elif defined(_WIN32)
DWORD size = 0;
BYTE* packet = pWintunReceivePacket(mSession, &size);
if (!packet)
return {};
WINTUN_PACKET* packet = WintunReceivePacket(mSession, nullptr);
if (!packet) return {};
std::vector<uint8_t> buf(packet, packet + size);
pWintunReleaseReceivePacket(mSession, packet);
std::vector<uint8_t> buf(packet->Data, packet->Data + packet->Length);
WintunReleaseReceivePacket(mSession, packet);
return buf;
#else
@@ -269,16 +206,12 @@ namespace ColumnLynx::Net {
#elif defined(_WIN32)
BYTE* tx = pWintunAllocateSendPacket(
mSession,
static_cast<DWORD>(packet.size())
);
WINTUN_PACKET* tx = WintunAllocateSendPacket(mSession, (DWORD)packet.size());
if (!tx)
throw std::runtime_error("WintunAllocateSendPacket failed");
memcpy(tx, packet.data(), packet.size());
pWintunSendPacket(mSession, tx);
memcpy(tx->Data, packet.data(), packet.size());
WintunSendPacket(mSession, tx);
#endif
}
@@ -305,37 +238,6 @@ namespace ColumnLynx::Net {
#endif
}
void VirtualInterface::resetIP() {
#if defined(__linux__)
char cmd[512];
snprintf(cmd, sizeof(cmd),
"ip addr flush dev %s",
mIfName.c_str()
);
system(cmd);
#elif defined(__APPLE__)
char cmd[512];
snprintf(cmd, sizeof(cmd),
"ifconfig %s inet 0.0.0.0 delete",
mIfName.c_str()
);
system(cmd);
snprintf(cmd, sizeof(cmd),
"ifconfig %s inet6 :: delete",
mIfName.c_str()
);
system(cmd);
#elif defined(_WIN32)
char cmd[256];
snprintf(cmd, sizeof(cmd),
"netsh interface ip set address name=\"%s\" dhcp",
mIfName.c_str()
);
system(cmd);
#endif
}
// ------------------------------------------------------------
// Linux
// ------------------------------------------------------------

View File

@@ -32,13 +32,11 @@ void signalHandler(int signum) {
int main(int argc, char** argv) {
// Capture SIGINT and SIGTERM for graceful shutdown
#if !defined(_WIN32)
struct sigaction action;
memset(&action, 0, sizeof(struct sigaction));
action.sa_handler = signalHandler;
sigaction(SIGINT, &action, nullptr);
sigaction(SIGTERM, &action, nullptr);
#endif
cxxopts::Options options("columnlynx_server", "ColumnLynx Server Application");
@@ -69,20 +67,15 @@ 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
std::unordered_map<std::string, std::string> config = Utils::getConfigMap(optionsObj["config"].as<std::string>());
std::shared_ptr<VirtualInterface> tun = std::make_shared<VirtualInterface>(optionsObj["interface"].as<std::string>());
log("Using virtual interface: " + tun->getName());
// Get network configuration from config file
std::unordered_map<std::string, std::string> config = Utils::getConfigMap(optionsObj["config"].as<std::string>());
std::string networkString = config.find("NETWORK") != config.end() ? config.find("NETWORK")->second : "10.10.0.0";
uint8_t subnetMask = config.find("SUBNET_MASK") != config.end() ? std::stoi(config.find("SUBNET_MASK")->second) : 24;
uint32_t baseIP = VirtualInterface::stringToIpv4(networkString);
uint32_t serverIP = baseIP + 1; // e.g., 10.10.0.1
// Configure the server's TUN interface
tun->configureIP(serverIP, serverIP, subnetMask, 1420);
log("Configured TUN interface with IP " + VirtualInterface::ipv4ToString(serverIP) + "/" + std::to_string(subnetMask));
// 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>();

View File

@@ -11,20 +11,8 @@ namespace ColumnLynx::Net::TCP {
});
mHandler->onDisconnect([this](const asio::error_code& ec) {
// Peer has closed; finalize locally without sending RST
std::string ip = mHandler->socket().remote_endpoint().address().to_string();
Utils::log("Client disconnected: " + ip + " - " + ec.message());
asio::error_code ec2;
mHandler->socket().close(ec2);
SessionRegistry::getInstance().erase(mConnectionSessionID);
SessionRegistry::getInstance().deallocIP(mConnectionSessionID);
Utils::log("Closed connection to " + ip);
if (mOnDisconnect) {
mOnDisconnect(shared_from_this());
}
Utils::log("Client disconnected: " + mHandler->socket().remote_endpoint().address().to_string() + " - " + ec.message());
disconnect();
});
mHandler->start();
@@ -44,21 +32,23 @@ namespace ColumnLynx::Net::TCP {
mOnDisconnect = std::move(cb);
}
void TCPConnection::disconnect(bool echo) {
void TCPConnection::disconnect() {
std::string ip = mHandler->socket().remote_endpoint().address().to_string();
if (echo) {
mHandler->sendMessage(ServerMessageType::GRACEFUL_DISCONNECT, "Server initiated disconnect.");
}
mHeartbeatTimer.cancel();
asio::error_code ec;
// Half-close: stop sending, keep reading until peer FIN
mHandler->socket().shutdown(asio::ip::tcp::socket::shutdown_send, ec);
if (ec) {
Utils::error("Error during socket shutdown: " + ec.message());
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) {
mOnDisconnect(shared_from_this());
}
// Do not close immediately; final cleanup happens in onDisconnect
Utils::log("Initiated graceful disconnect (half-close) to " + ip);
}
uint64_t TCPConnection::getSessionID() const {
@@ -282,11 +272,6 @@ namespace ColumnLynx::Net::TCP {
disconnect();
break;
}
case ClientMessageType::KILL_CONNECTION: {
Utils::warn("Received KILL_CONNECTION from " + reqAddr + ": " + data);
disconnect();
break;
}
default:
Utils::warn("Unhandled message type from " + reqAddr);
break;