// main.cpp - Client entry point 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 #include #include #include #include #include #include #include #include using asio::ip::tcp; using namespace ColumnLynx::Utils; using namespace ColumnLynx::Net; using namespace ColumnLynx; 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 struct sigaction action; memset(&action, 0, sizeof(struct sigaction)); action.sa_handler = signalHandler; sigaction(SIGINT, &action, nullptr); sigaction(SIGTERM, &action, nullptr); PanicHandler::init(); cxxopts::Options options("columnlynx_client", "ColumnLynx Client Application"); options.add_options() ("h,help", "Print help") ("s,server", "Server address", cxxopts::value()->default_value("127.0.0.1")) ("p,port", "Server port", cxxopts::value()->default_value(std::to_string(serverPort()))) ("allow-selfsigned", "Allow self-signed certificates", cxxopts::value()->default_value("false")); bool insecureMode = options.parse(argc, argv).count("allow-selfsigned") > 0; auto result = options.parse(argc, argv); if (result.count("help")) { std::cout << options.help() << std::endl; return 0; } auto host = result["server"].as(); auto port = std::to_string(result["port"].as()); try { log("ColumnLynx Client, 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("utun1"); log("Using virtual interface: " + tun.getName()); LibSodiumWrapper sodiumWrapper = LibSodiumWrapper(); std::array aesKey = {0}; // Defualt zeroed state until modified by handshake 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); client->start(); udpClient->start(); // Run the IO context in a separate thread std::thread ioThread([&io]() { io.run(); }); ioThread.detach(); 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())); } log("Client shutting down."); udpClient->stop(); client->disconnect(); io.stop(); ioThread.join(); } catch (const std::exception& e) { error("Client error: " + std::string(e.what())); } }