14 Commits

Author SHA1 Message Date
471224b043 Merge branch 'beta' - b0.3 2025-12-29 19:07:16 +01:00
714aa52f98 Merge branch 'dev' into beta 2025-12-29 19:06:59 +01:00
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
30 changed files with 102 additions and 467 deletions

View File

@@ -3,7 +3,7 @@
## ASIO C++ Library ## ASIO C++ Library
- **Name:** ASIO (standalone) - **Name:** ASIO (standalone)
- **Website:** https://think-async.com/Asio/ - **Website:** https://think-async.com/Asio/
- **Copyright:** (c) 2003-2026 Christopher M. Kohlhoff - **Copyright:** (c) 2003-2025 Christopher M. Kohlhoff
- **License:** Boost Software License, Version 1.0 - **License:** Boost Software License, Version 1.0
- **License Text:** See `third_party/asio/LICENSE_1_0.txt` - **License Text:** See `third_party/asio/LICENSE_1_0.txt`
@@ -12,14 +12,14 @@ This project uses the standalone version of the ASIO C++ library for asynchronou
## CXXOPTS C++ Library ## CXXOPTS C++ Library
- **Name:** cxxopts - **Name:** cxxopts
- **Website:** https://github.com/jarro2783/cxxopts/ - **Website:** https://github.com/jarro2783/cxxopts/
- **Copyright:** (c) 2014-2026 Christopher M. Kohlhoff - **Copyright:** (c) 2014-2025 Christopher M. Kohlhoff
- **License:** MIT License - **License:** MIT License
- **License Text:** See `third_party/cxxopts/LICENSE_1_0.txt` - **License Text:** See `third_party/cxxopts/LICENSE_1_0.txt`
## Wintun C++ Library ## Wintun C++ Library
- **Name:** wintun - **Name:** wintun
- **Website:** https://www.wintun.net/ - **Website:** https://www.wintun.net/
- **Copyright:** (c) 2018-2026 WireGuard LLC - **Copyright:** (c) 2018-2025 WireGuard LLC
- **License:** MIT License OR GPL-2.0 License - **License:** MIT License OR GPL-2.0 License
- **License Text:** See `third_party/wintun/` - **License Text:** See `third_party/wintun/`
- **Utilized Under:** MIT License - **Utilized Under:** MIT License

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.0.1 VERSION 0.3.0
LANGUAGES CXX LANGUAGES CXX
) )
@@ -50,7 +50,7 @@ endif()
FetchContent_Declare( FetchContent_Declare(
Sodium Sodium
GIT_REPOSITORY https://github.com/robinlinden/libsodium-cmake.git GIT_REPOSITORY https://github.com/robinlinden/libsodium-cmake.git
GIT_TAG e5b985ad0dd235d8c4307ea3a385b45e76c74c6a GIT_TAG e5b985ad0dd235d8c4307ea3a385b45e76c74c6a # Last updated at 2025-04-13
) )
set(SODIUM_DISABLE_TESTS ON CACHE BOOL "" FORCE) set(SODIUM_DISABLE_TESTS ON CACHE BOOL "" FORCE)

151
README.md
View File

@@ -18,155 +18,22 @@ This simplicity-focused design approach allows us to make an efficient, low-over
## Configuration ## Configuration
Configurating the server and client are are relatively easy. Currently (since the project is in alpha), the configuration files **must be in your system-specific config location** (which can be overriden via a CLI argument or the **COLUMNLYNX_CONFIG_DIR** Environment Variable). Configurating the server and client are are relatively easy. Currently (since the project is in alpha), the configuration files **must be in the same directory as the working directory**.
The defaults depends on your system.
For the server:
- Linux: **/etc/columnlynx**
- macOS: **/etc/columnlynx**
- Windows: **C:\ProgramData\ColumnLynx**
For the client:
- Linux: **~/.config/columnlynx**
- macOS: **~/Library/Application Support/columnlynx**
- Windows: **C:\Users\USERNAME\AppData\Local\ColumnLynx**
### Getting a keypair
Release builds of the software force you to specify your own keypairs. That's why you need to generate a keypair with some other software that you can use.
This guide will show a generation example with openssl:
#### Generate a keypair:
```bash
openssl genpkey -algorithm ED25519 -out key.pem
```
#### Extract the **Private Key Seed**:
```bash
openssl pkey -in key.pem -outform DER | tail -c 32 | xxd -p -c 32
# Output example: 9f3a2b6c0f8e4d1a7c3e9a4b5d2f8c6e1a9d0b7e3f4c2a8e6d5b1f0a3c4e
```
#### Extract the **Raw Public Key**:
```bash
openssl pkey -in key.pem -pubout -outform DER | tail -c 32 | xxd -p -c 32
# Output example: 1c9d4f7a3b2e8a6d0f5c9b1e4d8a7f3c6e2b1a9d5f4c8e0a7b3d6c9f2e
```
You can then set these keys accordingly in the **server_config** and **client_config** files.
### Server Setup (Linux Server ONLY)
#### Creating the Tun Interface
In order for the VPN server to work, you need to create the Tun interface that the VPN will use.
This is the set of commands to create one on Linux. Replace the example 10.10.0.1/24 IPv4 address with the FIRST IPv4 in the Network and Subnet Mask that you set in server_config.
```bash
sudo ip tuntap add dev lynx0 mode tun
sudo ip addr add 10.10.0.1/24 dev lynx0
sudo ip link set dev lynx0 mtu 1420
sudo ip link set dev lynx0 up
```
#### Creating the systemd service
It is highly recommended to **run the server as a systemd service**, as systemd is the primary service manager on Linux.
**1. Create a file for the service**
```bash
sudo touch /etc/systemd/system/columnlynx.service
```
**2. Open the file in your editor of choice**
```bash
sudo nano /etc/systemd/system/columnlynx.service
# OR
sudo vim /etc/systemd/system/columnlynx.service
# OR any other editor of your choice...
```
**3. Configure the service**
**Replace** the **ExecStart** and **WorkingDirectory** paths with the paths where your binaries are stored.
If you configured your tun interface to belong to a custom user, you may also replace the **User** and **Group** with that user, however you must ensure that that user owns the **tun interface**, **config directory in /etc/columnlynx** and the **working directory**.
This is a **simple example** for the **root user** and the executable in **/opt/columnlynx**:
```
[Unit]
Description=ColumnLynx Server Service
After=network.target
[Service]
Type=simple
ExecStart=/opt/columnlynx/columnlynx_server
WorkingDirectory=/opt/columnlynx
User=root
Group=root
Restart=on-failure
StandardOutput=append:/var/log/columnlynx.log
StandardError=append:/var/log/columnlynx.err
[Install]
WantedBy=multi-user.target
```
**4. Reload systemd and enable the service**
```bash
sudo systemctl daemon-reload
sudo systemctl enable columnlynx.service
sudo systemctl start columnlynx.service
```
#### Set firewall rules
This part greatly depends on your firewall of choice. Generally you just need to **allow port 48042 on both TCP and UDP** (Both IPv4 and IPv6).
This example is for **UFW**:
```bash
sudo ufw allow 48042
sudo ufw reload
```
#### IPTables rules for forwarding (Optional)
In addition to creating the interface, you'll also need to make some **iptables** rules if you want to be able to **send traffic to foreign networks** (more like a *commercial VPN*).
You can do these as such (example with NFT IPTABLES):
- Enable the **generic IPv4 forwarding**:
```bash
sudo sysctl net.ipv4.ip_forward=1
```
- Create the masquerade (**Replace the IP subnet** with your own that you set in the config and **replace the interface** with your server's main (NOT *lynx0*) interface):
```bash
sudo nft add table nat
sudo nft add chain nat postroute { type nat hook postrouting priority 100 \; }
sudo nft add rule nat postroute ip saddr 10.10.0.0/24 oifname "eth0" masquerade
```
### Server ### Server
"**server_config**" is a file that contains the server configuration, **one variable per line**. These are the current configuration available variables: "**server_config**" is a file that contains the server configuration, **one variable per line**. These are the current configuration available variables:
- **SERVER_PUBLIC_KEY** (Hex String): The public key to be used - Used for verification - **SERVER_PUBLIC_KEY** (Hex String): The public key to be used
- **SERVER_PRIVATE_KEY** (Hex String): The private key seed to be used - **SERVER_PRIVATE_KEY** (Hex String): The private key to be used
- **NETWORK** (IPv4 Format): The network IPv4 to be used (Server Interface still needs to be configured manually) - **NETWORK** (IPv4 Format): The network IPv4 to be used (Server Interface still needs to be configured manually)
- **SUBNET_MASK** (Integer): The subnet mask to be used (ensure proper length, it will not be checked) - **SUBNET_MASK** (Integer): The subnet mask to be used (ensure proper length, it will not be checked)
**Example:** **Example:**
``` ```
SERVER_PUBLIC_KEY=1c9d4f7a3b2e8a6d0f5c9b1e4d8a7f3c6e2b1a9d5f4c8e0a7b3d6c9f2e SERVER_PUBLIC_KEY=787B648046F10DDD0B77A6303BE42D859AA65C52F5708CC3C58EB5691F217C7B
SERVER_PRIVATE_KEY=9f3a2b6c0f8e4d1a7c3e9a4b5d2f8c6e1a9d0b7e3f4c2a8e6d5b1f0a3c4e SERVER_PRIVATE_KEY=778604245F57B847E63BD85DE8208FF1A127FB559895195928C3987E246B77B8787B648046F10DDD0B77A6303BE42D859AA65C52F5708CC3C58EB5691F217C7B
NETWORK=10.10.0.0 NETWORK=10.10.0.0
SUBNET_MASK=24 SUBNET_MASK=24
``` ```
@@ -186,14 +53,14 @@ SUBNET_MASK=24
"**client_config**" is a file that contains the client configuration, **one variable per line**. These are the current configuration available variables: "**client_config**" is a file that contains the client configuration, **one variable per line**. These are the current configuration available variables:
- **CLIENT_PUBLIC_KEY** (Hex String): The public key to be used - Used for verification - **CLIENT_PUBLIC_KEY** (Hex String): The public key to be used
- **CLIENT_PRIVATE_KEY** (Hex String): The private key seed to be used - **CLIENT_PRIVATE_KEY** (Hex String): The private key to be used
**Example:** **Example:**
``` ```
CLIENT_PUBLIC_KEY=1c9d4f7a3b2e8a6d0f5c9b1e4d8a7f3c6e2b1a9d5f4c8e0a7b3d6c9f2e CLIENT_PUBLIC_KEY=8CC8BE1A9D24639D0492EF143E84E2BD4C757C9B3B687E7035173EBFCA8FEDDA
CLIENT_PRIVATE_KEY=9f3a2b6c0f8e4d1a7c3e9a4b5d2f8c6e1a9d0b7e3f4c2a8e6d5b1f0a3c4e CLIENT_PRIVATE_KEY=9B486A5B1509FA216F9EEFED85CACF2384E9D902A76CC979BFA143C18B869F5C8CC8BE1A9D24639D0492EF143E84E2BD4C757C9B3B687E7035173EBFCA8FEDDA
``` ```
<hr></hr> <hr></hr>

View File

@@ -1,5 +1,5 @@
// tcp_client.hpp - TCP Client for ColumnLynx // tcp_client.hpp - TCP Client for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once #pragma once
@@ -14,7 +14,6 @@
#include <algorithm> #include <algorithm>
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include <string>
#include <columnlynx/common/net/protocol_structs.hpp> #include <columnlynx/common/net/protocol_structs.hpp>
#include <columnlynx/common/net/virtual_interface.hpp> #include <columnlynx/common/net/virtual_interface.hpp>
@@ -30,7 +29,6 @@ namespace ColumnLynx::Net::TCP {
std::shared_ptr<std::array<uint8_t, 32>> aesKey, std::shared_ptr<std::array<uint8_t, 32>> aesKey,
std::shared_ptr<uint64_t> sessionIDRef, std::shared_ptr<uint64_t> sessionIDRef,
bool insecureMode, bool insecureMode,
std::string& configPath,
std::shared_ptr<VirtualInterface> tun = nullptr) std::shared_ptr<VirtualInterface> tun = nullptr)
: :
mResolver(ioContext), mResolver(ioContext),
@@ -44,11 +42,10 @@ namespace ColumnLynx::Net::TCP {
mHeartbeatTimer(mSocket.get_executor()), mHeartbeatTimer(mSocket.get_executor()),
mLastHeartbeatReceived(std::chrono::steady_clock::now()), mLastHeartbeatReceived(std::chrono::steady_clock::now()),
mLastHeartbeatSent(std::chrono::steady_clock::now()), mLastHeartbeatSent(std::chrono::steady_clock::now()),
mTun(tun), mTun(tun)
mConfigDirPath(configPath)
{ {
// Preload the config map // Preload the config map
mRawClientConfig = Utils::getConfigMap(configPath + "client_config"); mRawClientConfig = Utils::getConfigMap("client_config");
auto itPubkey = mRawClientConfig.find("CLIENT_PUBLIC_KEY"); auto itPubkey = mRawClientConfig.find("CLIENT_PUBLIC_KEY");
auto itPrivkey = mRawClientConfig.find("CLIENT_PRIVATE_KEY"); auto itPrivkey = mRawClientConfig.find("CLIENT_PRIVATE_KEY");
@@ -57,22 +54,16 @@ namespace ColumnLynx::Net::TCP {
Utils::log("Loading keypair from config file."); Utils::log("Loading keypair from config file.");
PublicKey pk; PublicKey pk;
PrivateSeed seed; PrivateKey sk;
std::copy_n(Utils::hexStringToBytes(itPrivkey->second).begin(), seed.size(), seed.begin()); // This is extremely stupid, but the C++ compiler has forced my hand (I would've just used to_array, but fucking asio decls) std::copy_n(Utils::hexStringToBytes(itPrivkey->second).begin(), sk.size(), sk.begin()); // This is extremely stupid, but the C++ compiler has forced my hand (I would've just used to_array, but fucking asio decls)
std::copy_n(Utils::hexStringToBytes(itPubkey->second).begin(), pk.size(), pk.begin()); std::copy_n(Utils::hexStringToBytes(itPubkey->second).begin(), pk.size(), pk.begin());
if (!mLibSodiumWrapper->recomputeKeys(seed, pk)) { mLibSodiumWrapper->setKeys(pk, sk);
throw std::runtime_error("Failed to recompute keypair from config file values!");
}
Utils::debug("Newly-Loaded Public Key: " + Utils::bytesToHexString(mLibSodiumWrapper->getPublicKey(), 32)); Utils::debug("Newly-Loaded Public Key: " + Utils::bytesToHexString(mLibSodiumWrapper->getPublicKey(), 32));
} else { } else {
#if defined(DEBUG)
Utils::warn("No keypair found in config file! Using random key."); Utils::warn("No keypair found in config file! Using random key.");
#else
throw std::runtime_error("No keypair found in config file! Cannot start client without keys.");
#endif
} }
} }
@@ -118,6 +109,5 @@ namespace ColumnLynx::Net::TCP {
Protocol::TunConfig mTunConfig; Protocol::TunConfig mTunConfig;
std::shared_ptr<VirtualInterface> mTun = nullptr; std::shared_ptr<VirtualInterface> mTun = nullptr;
std::unordered_map<std::string, std::string> mRawClientConfig; std::unordered_map<std::string, std::string> mRawClientConfig;
std::string mConfigDirPath;
}; };
} }

View File

@@ -1,5 +1,5 @@
// udp_client.hpp - UDP Client for ColumnLynx // udp_client.hpp - UDP Client for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once #pragma once

View File

@@ -1,5 +1,5 @@
// libsodium_wrapper.hpp - Libsodium Wrapper for ColumnLynx // libsodium_wrapper.hpp - Libsodium Wrapper for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once #pragma once
@@ -17,7 +17,6 @@
namespace ColumnLynx { namespace ColumnLynx {
using PublicKey = std::array<uint8_t, crypto_sign_PUBLICKEYBYTES>; // Ed25519 using PublicKey = std::array<uint8_t, crypto_sign_PUBLICKEYBYTES>; // Ed25519
using PrivateKey = std::array<uint8_t, crypto_sign_SECRETKEYBYTES>; // Ed25519 using PrivateKey = std::array<uint8_t, crypto_sign_SECRETKEYBYTES>; // Ed25519
using PrivateSeed = std::array<uint8_t, crypto_sign_SEEDBYTES>; // 32 bytes
using Signature = std::array<uint8_t, crypto_sign_BYTES>; // 64 bytes using Signature = std::array<uint8_t, crypto_sign_BYTES>; // 64 bytes
using SymmetricKey = std::array<uint8_t, crypto_aead_chacha20poly1305_ietf_KEYBYTES>; // 32 bytes using SymmetricKey = std::array<uint8_t, crypto_aead_chacha20poly1305_ietf_KEYBYTES>; // 32 bytes
using Nonce = std::array<uint8_t, crypto_aead_chacha20poly1305_ietf_NPUBBYTES>; // 12 bytes using Nonce = std::array<uint8_t, crypto_aead_chacha20poly1305_ietf_NPUBBYTES>; // 12 bytes
@@ -54,9 +53,6 @@ namespace ColumnLynx::Utils {
} }
} }
// Recompute the keypair from a given private seed; Will return false on failure
bool recomputeKeys(PrivateSeed privateSeed, PublicKey storedPubKey);
// Helper section // Helper section
// Generates a random 256-bit (32-byte) array // Generates a random 256-bit (32-byte) array

View File

@@ -1,5 +1,5 @@
// protocol_structs.hpp - Network Protocol Structures // protocol_structs.hpp - Network Protocol Structures
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once #pragma once

View File

@@ -1,5 +1,5 @@
// session_registry.hpp - Session Registry for ColumnLynx // session_registry.hpp - Session Registry for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once #pragma once

View File

@@ -1,5 +1,5 @@
// net_helper.hpp - Network Helper Functions for ColumnLynx // net_helper.hpp - Network Helper Functions for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once #pragma once

View File

@@ -1,5 +1,5 @@
// tcp_message_handler.hpp - TCP Message Handler for ColumnLynx // tcp_message_handler.hpp - TCP Message Handler for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once #pragma once

View File

@@ -1,5 +1,5 @@
// tcp_message_type.hpp - TCP Message Types for ColumnLynx // tcp_message_type.hpp - TCP Message Types for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once #pragma once

View File

@@ -1,5 +1,5 @@
// udp_message_type.hpp - UDP Message Types for ColumnLynx // udp_message_type.hpp - UDP Message Types for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once #pragma once

View File

@@ -1,5 +1,5 @@
// virtual_interface.hpp - Virtual Interface for Network Communication // virtual_interface.hpp - Virtual Interface for Network Communication
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once #pragma once
@@ -36,12 +36,6 @@
#include <locale> #include <locale>
#include <codecvt> #include <codecvt>
#include <wintun/wintun.h> #include <wintun/wintun.h>
#include <iphlpapi.h>
#include <netioapi.h>
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")
#endif #endif
namespace ColumnLynx::Net { namespace ColumnLynx::Net {
@@ -52,7 +46,7 @@ namespace ColumnLynx::Net {
bool configureIP(uint32_t clientIP, uint32_t serverIP, bool configureIP(uint32_t clientIP, uint32_t serverIP,
uint8_t prefixLen, uint16_t mtu); uint8_t prefixLen, uint16_t mtu);
void resetIP(); void resetIP();
std::vector<uint8_t> readPacket(); std::vector<uint8_t> readPacket();

View File

@@ -1,5 +1,5 @@
// panic_handler.hpp - Panic Handler for ColumnLynx // panic_handler.hpp - Panic Handler for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once #pragma once

View File

@@ -1,5 +1,5 @@
// utils.hpp - Utility functions for ColumnLynx // utils.hpp - Utility functions for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once #pragma once
@@ -29,9 +29,6 @@ namespace ColumnLynx {
} }
namespace ColumnLynx::Utils { namespace ColumnLynx::Utils {
// Converts unix milliseconds to a local ISO 8601 formatted string; Defaults to local time; Will use UTC if local is false.
std::string unixMillisToISO8601(uint64_t unixMillis, bool local = true);
// General log function. Use for logging important information. // General log function. Use for logging important information.
void log(const std::string &msg); void log(const std::string &msg);
// General warning function. Use for logging important warnings. // General warning function. Use for logging important warnings.
@@ -47,7 +44,7 @@ namespace ColumnLynx::Utils {
std::string getVersion(); std::string getVersion();
unsigned short serverPort(); unsigned short serverPort();
unsigned char protocolVersion(); unsigned char protocolVersion();
std::vector<std::string> getWhitelistedKeys(std::string basePath); std::vector<std::string> getWhitelistedKeys();
// Raw byte to hex string conversion helper // Raw byte to hex string conversion helper
std::string bytesToHexString(const uint8_t* bytes, size_t length); std::string bytesToHexString(const uint8_t* bytes, size_t length);

View File

@@ -1,5 +1,5 @@
// tcp_connection.hpp - TCP Connection for ColumnLynx // tcp_connection.hpp - TCP Connection for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once #pragma once
@@ -28,10 +28,9 @@ namespace ColumnLynx::Net::TCP {
asio::ip::tcp::socket socket, asio::ip::tcp::socket socket,
std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper, std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper,
std::unordered_map<std::string, std::string>* serverConfig, std::unordered_map<std::string, std::string>* serverConfig,
std::string configDirPath,
std::function<void(pointer)> onDisconnect) std::function<void(pointer)> onDisconnect)
{ {
auto conn = pointer(new TCPConnection(std::move(socket), sodiumWrapper, serverConfig, configDirPath)); auto conn = pointer(new TCPConnection(std::move(socket), sodiumWrapper, serverConfig));
conn->mOnDisconnect = std::move(onDisconnect); conn->mOnDisconnect = std::move(onDisconnect);
return conn; return conn;
} }
@@ -51,15 +50,14 @@ namespace ColumnLynx::Net::TCP {
std::array<uint8_t, 32> getAESKey() const; std::array<uint8_t, 32> getAESKey() const;
private: private:
TCPConnection(asio::ip::tcp::socket socket, std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper, std::unordered_map<std::string, std::string>* serverConfig, std::string configDirPath) TCPConnection(asio::ip::tcp::socket socket, std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper, std::unordered_map<std::string, std::string>* serverConfig)
: :
mHandler(std::make_shared<MessageHandler>(std::move(socket))), mHandler(std::make_shared<MessageHandler>(std::move(socket))),
mLibSodiumWrapper(sodiumWrapper), mLibSodiumWrapper(sodiumWrapper),
mRawServerConfig(serverConfig), mRawServerConfig(serverConfig),
mHeartbeatTimer(mHandler->socket().get_executor()), mHeartbeatTimer(mHandler->socket().get_executor()),
mLastHeartbeatReceived(std::chrono::steady_clock::now()), mLastHeartbeatReceived(std::chrono::steady_clock::now()),
mLastHeartbeatSent(std::chrono::steady_clock::now()), mLastHeartbeatSent(std::chrono::steady_clock::now())
mConfigDirPath(configDirPath)
{} {}
// Start the heartbeat routine // Start the heartbeat routine
@@ -79,6 +77,5 @@ namespace ColumnLynx::Net::TCP {
std::chrono::steady_clock::time_point mLastHeartbeatSent; std::chrono::steady_clock::time_point mLastHeartbeatSent;
int mMissedHeartbeats = 0; int mMissedHeartbeats = 0;
std::string mRemoteIP; // Cached remote IP to avoid calling remote_endpoint() on closed sockets std::string mRemoteIP; // Cached remote IP to avoid calling remote_endpoint() on closed sockets
std::string mConfigDirPath;
}; };
} }

View File

@@ -1,5 +1,5 @@
// tcp_server.hpp - TCP Server for ColumnLynx // tcp_server.hpp - TCP Server for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once #pragma once
@@ -25,17 +25,14 @@ namespace ColumnLynx::Net::TCP {
TCPServer(asio::io_context& ioContext, TCPServer(asio::io_context& ioContext,
uint16_t port, uint16_t port,
std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper, std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper,
std::shared_ptr<bool> hostRunning, std::shared_ptr<bool> hostRunning, bool ipv4Only = false)
std::string& configPath,
bool ipv4Only = false)
: mIoContext(ioContext), : mIoContext(ioContext),
mAcceptor(ioContext), mAcceptor(ioContext),
mSodiumWrapper(sodiumWrapper), mSodiumWrapper(sodiumWrapper),
mHostRunning(hostRunning), mHostRunning(hostRunning)
mConfigDirPath(configPath)
{ {
// Preload the config map // Preload the config map
mRawServerConfig = Utils::getConfigMap(configPath + "server_config", {"NETWORK", "SUBNET_MASK"}); mRawServerConfig = Utils::getConfigMap("server_config", {"NETWORK", "SUBNET_MASK"});
asio::error_code ec_open, ec_v6only, ec_bind; asio::error_code ec_open, ec_v6only, ec_bind;
@@ -87,7 +84,6 @@ namespace ColumnLynx::Net::TCP {
std::shared_ptr<Utils::LibSodiumWrapper> mSodiumWrapper; std::shared_ptr<Utils::LibSodiumWrapper> mSodiumWrapper;
std::shared_ptr<bool> mHostRunning; std::shared_ptr<bool> mHostRunning;
std::unordered_map<std::string, std::string> mRawServerConfig; std::unordered_map<std::string, std::string> mRawServerConfig;
std::string mConfigDirPath;
}; };
} }

View File

@@ -1,5 +1,5 @@
// udp_server.hpp - UDP Server for ColumnLynx // udp_server.hpp - UDP Server for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#pragma once #pragma once

View File

@@ -1,5 +1,5 @@
// main.cpp - Client entry point for ColumnLynx // main.cpp - Client entry point for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#include <asio.hpp> #include <asio.hpp>
@@ -12,10 +12,6 @@
#include <cxxopts.hpp> #include <cxxopts.hpp>
#include <columnlynx/common/net/virtual_interface.hpp> #include <columnlynx/common/net/virtual_interface.hpp>
#if defined(__WIN32__)
#include <windows.h>
#endif
using asio::ip::tcp; using asio::ip::tcp;
using namespace ColumnLynx::Utils; using namespace ColumnLynx::Utils;
using namespace ColumnLynx::Net; using namespace ColumnLynx::Net;
@@ -52,23 +48,15 @@ int main(int argc, char** argv) {
#else #else
("i,interface", "Override used interface", cxxopts::value<std::string>()->default_value("lynx0")) ("i,interface", "Override used interface", cxxopts::value<std::string>()->default_value("lynx0"))
#endif #endif
("ignore-whitelist", "Ignore if server is not in whitelisted_keys", cxxopts::value<bool>()->default_value("false")) ("allow-selfsigned", "Allow self-signed certificates", cxxopts::value<bool>()->default_value("false"));
#if defined(__WIN32__)
/* Get config dir in LOCALAPPDATA\ColumnLynx\ */
("config-dir", "Override config dir path", cxxopts::value<std::string>()->default_value(std::string((std::getenv("LOCALAPPDATA") ? std::getenv("LOCALAPPDATA") : "C:\\ProgramData")) + "\\ColumnLynx\\"));
#elif defined(__APPLE__)
("config-dir", "Override config dir path", cxxopts::value<std::string>()->default_value(std::string((std::getenv("HOME") ? std::getenv("HOME") : "")) + "/Library/Application Support/ColumnLynx/"));
#else
("config-dir", "Override config dir path", cxxopts::value<std::string>()->default_value(std::string((std::getenv("SUDO_USER") ? "/home/" + std::string(std::getenv("SUDO_USER")) : (std::getenv("HOME") ? std::getenv("HOME") : ""))) + "/.config/columnlynx/"));
#endif
bool insecureMode = options.parse(argc, argv).count("ignore-whitelist") > 0; bool insecureMode = options.parse(argc, argv).count("allow-selfsigned") > 0;
auto optionsObj = options.parse(argc, argv); auto optionsObj = options.parse(argc, argv);
if (optionsObj.count("help")) { if (optionsObj.count("help")) {
std::cout << options.help() << std::endl; std::cout << options.help() << std::endl;
std::cout << "This software is licensed under the GPLv2-only license OR the GPLv3 license.\n"; std::cout << "This software is licensed under the GPLv2-only license OR the GPLv3 license.\n";
std::cout << "Copyright (C) 2026, The ColumnLynx Contributors.\n"; std::cout << "Copyright (C) 2025, The ColumnLynx Contributors.\n";
std::cout << "This software is provided under ABSOLUTELY NO WARRANTY, to the extent permitted by law.\n"; std::cout << "This software is provided under ABSOLUTELY NO WARRANTY, to the extent permitted by law.\n";
return 0; return 0;
} }
@@ -84,21 +72,6 @@ int main(int argc, char** argv) {
//WintunInitialize(); //WintunInitialize();
#endif #endif
// Get the config path, ENV > CLI > /etc/columnlynx
std::string configPath = optionsObj["config-dir"].as<std::string>();
const char* envConfigPath = std::getenv("COLUMNLYNX_CONFIG_DIR");
if (envConfigPath != nullptr) {
configPath = std::string(envConfigPath);
}
if (configPath.back() != '/' && configPath.back() != '\\') {
#if defined(__WIN32__)
configPath += "\\";
#else
configPath += "/";
#endif
}
std::shared_ptr<VirtualInterface> tun = std::make_shared<VirtualInterface>(optionsObj["interface"].as<std::string>()); std::shared_ptr<VirtualInterface> tun = std::make_shared<VirtualInterface>(optionsObj["interface"].as<std::string>());
log("Using virtual interface: " + tun->getName()); log("Using virtual interface: " + tun->getName());
@@ -110,12 +83,8 @@ int main(int argc, char** argv) {
aesKey->fill(0); // Defualt zeroed state until modified by handshake aesKey->fill(0); // Defualt zeroed state until modified by handshake
std::shared_ptr<uint64_t> sessionID = std::make_shared<uint64_t>(0); std::shared_ptr<uint64_t> sessionID = std::make_shared<uint64_t>(0);
if (insecureMode) {
warn("You have started the client with the --ignore-whitelist. This means that the client will NOT attempt to verify the server's public key. This is INSECURE and SHOULDN'T be used!");
}
asio::io_context io; asio::io_context io;
auto client = std::make_shared<ColumnLynx::Net::TCP::TCPClient>(io, host, port, sodiumWrapper, aesKey, sessionID, insecureMode, configPath, tun); auto client = std::make_shared<ColumnLynx::Net::TCP::TCPClient>(io, host, port, sodiumWrapper, aesKey, sessionID, insecureMode, tun);
auto udpClient = std::make_shared<ColumnLynx::Net::UDP::UDPClient>(io, host, port, aesKey, sessionID, tun); auto udpClient = std::make_shared<ColumnLynx::Net::UDP::UDPClient>(io, host, port, aesKey, sessionID, tun);
client->start(); client->start();

View File

@@ -1,5 +1,5 @@
// tcp_client.cpp - TCP Client for ColumnLynx // tcp_client.cpp - TCP Client for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // 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 <columnlynx/client/net/tcp/tcp_client.hpp>
@@ -150,12 +150,11 @@ namespace ColumnLynx::Net::TCP {
void TCPClient::mHandleMessage(ServerMessageType type, const std::string& data) { void TCPClient::mHandleMessage(ServerMessageType type, const std::string& data) {
switch (type) { switch (type) {
case ServerMessageType::HANDSHAKE_IDENTIFY: { case ServerMessageType::HANDSHAKE_IDENTIFY: {
Utils::log("Received server identity: " + data);
std::memcpy(mServerPublicKey, data.data(), std::min(data.size(), sizeof(mServerPublicKey))); std::memcpy(mServerPublicKey, data.data(), std::min(data.size(), sizeof(mServerPublicKey)));
std::string hexServerPub = Utils::bytesToHexString(mServerPublicKey, 32);
Utils::log("Received server identity. Public Key: " + hexServerPub);
// Verify pubkey against whitelisted_keys // Verify pubkey against whitelisted_keys
std::vector<std::string> whitelistedKeys = Utils::getWhitelistedKeys(mConfigDirPath); std::vector<std::string> whitelistedKeys = Utils::getWhitelistedKeys();
if (std::find(whitelistedKeys.begin(), whitelistedKeys.end(), Utils::bytesToHexString(mServerPublicKey, 32)) == whitelistedKeys.end()) { // Key verification is handled in later steps of the handshake if (std::find(whitelistedKeys.begin(), whitelistedKeys.end(), Utils::bytesToHexString(mServerPublicKey, 32)) == whitelistedKeys.end()) { // Key verification is handled in later steps of the handshake
if (!mInsecureMode) { if (!mInsecureMode) {
Utils::error("Server public key not in whitelisted_keys. Terminating connection."); Utils::error("Server public key not in whitelisted_keys. Terminating connection.");

View File

@@ -1,5 +1,5 @@
// udp_client.cpp - UDP Client for ColumnLynx // udp_client.cpp - UDP Client for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // 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/udp/udp_client.hpp> #include <columnlynx/client/net/udp/udp_client.hpp>

View File

@@ -1,5 +1,5 @@
// libsodium_wrapper.cpp - Libsodium Wrapper for ColumnLynx // libsodium_wrapper.cpp - Libsodium Wrapper for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#include <columnlynx/common/libsodium_wrapper.hpp> #include <columnlynx/common/libsodium_wrapper.hpp>
@@ -41,27 +41,4 @@ namespace ColumnLynx::Utils {
randombytes_buf(randbytes.data(), randbytes.size()); randombytes_buf(randbytes.data(), randbytes.size());
return randbytes; return randbytes;
} }
bool LibSodiumWrapper::recomputeKeys(PrivateSeed privateSeed, PublicKey storedPubKey) {
int res = crypto_sign_seed_keypair(mPublicKey.data(), mPrivateKey.data(), privateSeed.data());
if (res != 0) {
return false;
}
// Convert to Curve25519 keys for encryption
res = crypto_sign_ed25519_pk_to_curve25519(mXPublicKey.data(), mPublicKey.data());
res = crypto_sign_ed25519_sk_to_curve25519(mXPrivateKey.data(), mPrivateKey.data());
if (res != 0) {
return false;
}
// Compare to stored for verification
if (sodium_memcmp(mPublicKey.data(), storedPubKey.data(), crypto_sign_PUBLICKEYBYTES) != 0) {
return false;
}
return true;
}
} }

View File

@@ -1,5 +1,5 @@
// session_registry.cpp - Session Registry for ColumnLynx // session_registry.cpp - Session Registry for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // 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> #include <columnlynx/common/net/session_registry.hpp>

View File

@@ -1,5 +1,5 @@
// tcp_message_handler.cpp - TCP Message Handler for ColumnLynx // tcp_message_handler.cpp - TCP Message Handler for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // 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/tcp/tcp_message_handler.hpp> #include <columnlynx/common/net/tcp/tcp_message_handler.hpp>

View File

@@ -1,65 +1,29 @@
// utils.cpp - Utility functions for ColumnLynx // utils.cpp - Utility functions for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#include <columnlynx/common/utils.hpp> #include <columnlynx/common/utils.hpp>
namespace ColumnLynx::Utils { namespace ColumnLynx::Utils {
std::string unixMillisToISO8601(uint64_t unixMillis, bool local) {
using namespace std::chrono;
// Convert milliseconds since epoch to system_clock::time_point
system_clock::time_point tp = system_clock::time_point(milliseconds(unixMillis));
// Convert to time_t for localtime conversion
std::time_t tt = system_clock::to_time_t(tp);
std::tm localTm;
if (local) {
#ifdef _WIN32
localtime_s(&localTm, &tt);
#else
localtime_r(&tt, &localTm);
#endif
} else {
#ifdef _WIN32
gmtime_s(&localTm, &tt);
#else
gmtime_r(&tt, &localTm);
#endif
}
// Format the time to ISO 8601
char buffer[30];
std::strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &localTm);
// Append milliseconds
auto ms = duration_cast<milliseconds>(tp.time_since_epoch()) % 1000;
char iso8601[34];
std::snprintf(iso8601, sizeof(iso8601), "%s.%03lld", buffer, static_cast<long long>(ms.count()));
return std::string(iso8601);
}
void log(const std::string &msg) { void log(const std::string &msg) {
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
std::cout << "\033[0m[" << unixMillisToISO8601(now) << " LOG] " << msg << std::endl; std::cout << "\033[0m[" << std::to_string(now) << " LOG] " << msg << std::endl;
} }
void warn(const std::string &msg) { void warn(const std::string &msg) {
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
std::cerr << "\033[33m[" << unixMillisToISO8601(now) << " WARN] " << msg << "\033[0m" << std::endl; std::cerr << "\033[33m[" << std::to_string(now) << " WARN] " << msg << "\033[0m" << std::endl;
} }
void error(const std::string &msg) { void error(const std::string &msg) {
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
std::cerr << "\033[31m[" << unixMillisToISO8601(now) << " ERROR] " << msg << "\033[0m" << std::endl; std::cerr << "\033[31m[" << std::to_string(now) << " ERROR] " << msg << "\033[0m" << std::endl;
} }
void debug(const std::string &msg) { void debug(const std::string &msg) {
#if DEBUG || _DEBUG #if DEBUG || _DEBUG
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
std::cerr << "\033[95m[" << unixMillisToISO8601(now) << " DEBUG] " << msg << "\033[0m" << std::endl; std::cerr << "\033[95m[" << std::to_string(now) << " DEBUG] " << msg << "\033[0m" << std::endl;
#else #else
return; return;
#endif #endif
@@ -85,7 +49,7 @@ namespace ColumnLynx::Utils {
} }
std::string getVersion() { std::string getVersion() {
return "1.0.1"; return "b0.3";
} }
unsigned short serverPort() { unsigned short serverPort() {
@@ -137,19 +101,14 @@ namespace ColumnLynx::Utils {
return bytes; return bytes;
} }
std::vector<std::string> getWhitelistedKeys(std::string basePath) { std::vector<std::string> getWhitelistedKeys() {
// Currently re-reads the file every time, should be fine. // Currently re-reads the file every time, should be fine.
// Advantage of it is that you don't need to reload the server binary after adding/removing keys. Disadvantage is re-reading the file every time. // Advantage of it is that you don't need to reload the server binary after adding/removing keys. Disadvantage is re-reading the file every time.
// I might redo this part. // I might redo this part.
std::vector<std::string> out; std::vector<std::string> out;
std::ifstream file(basePath + "whitelisted_keys"); std::ifstream file("whitelisted_keys"); // TODO: This is hardcoded for now, make dynamic
if (!file.is_open()) {
warn("Failed to open whitelisted_keys file at path: " + basePath + "whitelisted_keys");
return out;
}
std::string line; std::string line;
while (std::getline(file, line)) { while (std::getline(file, line)) {
@@ -164,10 +123,6 @@ namespace ColumnLynx::Utils {
std::vector<std::string> readLines; std::vector<std::string> readLines;
std::ifstream file(path); std::ifstream file(path);
if (!file.is_open()) {
throw std::runtime_error("Failed to open config file at path: " + path);
}
std::string line; std::string line;
while (std::getline(file, line)) { while (std::getline(file, line)) {

View File

@@ -1,5 +1,5 @@
// virtual_interface.cpp - Virtual Interface for Network Communication // virtual_interface.cpp - Virtual Interface for Network Communication
// Copyright (C) 2026 DcruBro // 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. // 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/virtual_interface.hpp> #include <columnlynx/common/net/virtual_interface.hpp>
@@ -72,7 +72,7 @@ namespace ColumnLynx::Net {
if (ioctl(mFd, TUNSETIFF, &ifr) < 0) { if (ioctl(mFd, TUNSETIFF, &ifr) < 0) {
close(mFd); close(mFd);
throw std::runtime_error("TUNSETIFF failed (try running with sudo): " + std::string(strerror(errno))); throw std::runtime_error("TUNSETIFF failed: " + std::string(strerror(errno)));
} }
#elif defined(__APPLE__) #elif defined(__APPLE__)
@@ -96,7 +96,7 @@ namespace ColumnLynx::Net {
if (connect(mFd, (struct sockaddr*)&sc, sizeof(sc)) < 0) { if (connect(mFd, (struct sockaddr*)&sc, sizeof(sc)) < 0) {
if (errno == EPERM) if (errno == EPERM)
throw std::runtime_error("connect(AF_SYS_CONTROL) failed: Insufficient permissions (try running with sudo)"); throw std::runtime_error("connect(AF_SYS_CONTROL) failed: Insufficient permissions (try running as root)");
throw std::runtime_error("connect(AF_SYS_CONTROL) failed: " + std::string(strerror(errno))); throw std::runtime_error("connect(AF_SYS_CONTROL) failed: " + std::string(strerror(errno)));
} }
@@ -326,23 +326,8 @@ namespace ColumnLynx::Net {
mIfName.c_str() mIfName.c_str()
); );
system(cmd); system(cmd);
// Wipe old routes
//snprintf(cmd, sizeof(cmd),
// "route -n delete -net %s",
// mIfName.c_str()
//);
//system(cmd);
#elif defined(_WIN32) #elif defined(_WIN32)
char cmd[512]; char cmd[256];
// Remove any persistent routes associated with this interface
snprintf(cmd, sizeof(cmd),
"netsh routing ip delete persistentroute all name=\"%s\"",
mIfName.c_str()
);
system(cmd);
// Reset to DHCP
snprintf(cmd, sizeof(cmd), snprintf(cmd, sizeof(cmd),
"netsh interface ip set address name=\"%s\" dhcp", "netsh interface ip set address name=\"%s\" dhcp",
mIfName.c_str() mIfName.c_str()
@@ -413,12 +398,6 @@ namespace ColumnLynx::Net {
mIfName.c_str(), ipStr.c_str(), peerStr.c_str(), mtu, prefixStr.c_str()); mIfName.c_str(), ipStr.c_str(), peerStr.c_str(), mtu, prefixStr.c_str());
system(cmd); system(cmd);
// Host bits are auto-normalized by the kernel on macOS, so we don't need to worry about them not being zeroed out.
snprintf(cmd, sizeof(cmd),
"route -n add -net %s/%d -interface %s",
ipStr.c_str(), prefixLen, mIfName.c_str());
system(cmd);
Utils::log("Executed command: " + std::string(cmd)); Utils::log("Executed command: " + std::string(cmd));
return true; return true;
@@ -433,66 +412,29 @@ namespace ColumnLynx::Net {
uint16_t mtu) uint16_t mtu)
{ {
#ifdef _WIN32 #ifdef _WIN32
// Interface alias → LUID → Index std::string ip = ipv4ToString(clientIP);
std::wstring ifAlias(mIfName.begin(), mIfName.end()); std::string gw = ipv4ToString(serverIP);
std::string mask;
NET_LUID luid; // Convert prefixLen → subnet mask
if (ConvertInterfaceAliasToLuid(ifAlias.c_str(), &luid) != NO_ERROR) uint32_t maskInt = (prefixLen == 0) ? 0 : (0xFFFFFFFF << (32 - prefixLen));
return false; mask = ipv4ToString(maskInt);
NET_IFINDEX ifIndex; char cmd[256];
if (ConvertInterfaceLuidToIndex(&luid, &ifIndex) != NO_ERROR)
return false;
// ssign IPv4 address + prefix // 1. Set the static IP + mask + gateway
MIB_UNICASTIPADDRESS_ROW addr; snprintf(cmd, sizeof(cmd),
InitializeUnicastIpAddressEntry(&addr); "netsh interface ip set address name=\"%s\" static %s %s %s",
mIfName.c_str(), ip.c_str(), mask.c_str(), gw.c_str()
);
system(cmd);
addr.InterfaceIndex = ifIndex; // 2. Set MTU (separate command)
addr.Address.si_family = AF_INET; snprintf(cmd, sizeof(cmd),
addr.Address.Ipv4.sin_addr.s_addr = htonl(clientIP); "netsh interface ipv4 set subinterface \"%s\" mtu=%u store=persistent",
addr.OnLinkPrefixLength = prefixLen; mIfName.c_str(), mtu
addr.DadState = IpDadStatePreferred; );
system(cmd);
if (CreateUnicastIpAddressEntry(&addr) != NO_ERROR)
return false;
// Set MTU
MIB_IFROW ifRow;
ifRow.dwIndex = ifIndex;
if (GetIfEntry(&ifRow) != NO_ERROR)
return false;
ifRow.dwMtu = mtu;
if (SetIfEntry(&ifRow) != NO_ERROR)
return false;
// Add persistent route for VPN network via this interface
uint32_t mask =
(prefixLen == 0) ? 0 : (0xFFFFFFFFu << (32 - prefixLen));
uint32_t network = clientIP & mask;
MIB_IPFORWARD_ROW2 route;
InitializeIpForwardEntry(&route);
route.InterfaceIndex = ifIndex;
route.DestinationPrefix.Prefix.si_family = AF_INET;
route.DestinationPrefix.Prefix.Ipv4.sin_addr.s_addr = htonl(network);
route.DestinationPrefix.PrefixLength = prefixLen;
route.NextHop.si_family = AF_INET;
route.NextHop.Ipv4.sin_addr.s_addr = 0;
route.Metric = 1;
route.Protocol = static_cast<NL_ROUTE_PROTOCOL>(MIB_IPPROTO_NETMGMT);
route.ValidLifetime = 0xFFFFFFFF;
route.PreferredLifetime = 0xFFFFFFFF;
DWORD r = CreateIpForwardEntry2(&route);
if (r != NO_ERROR && r != ERROR_OBJECT_ALREADY_EXISTS)
return false;
return true; return true;
#else #else

View File

@@ -1,5 +1,5 @@
// main.cpp - Server entry point for ColumnLynx // main.cpp - Server entry point for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#include <asio.hpp> #include <asio.hpp>
@@ -16,10 +16,6 @@
#include <cxxopts.hpp> #include <cxxopts.hpp>
#include <columnlynx/common/net/virtual_interface.hpp> #include <columnlynx/common/net/virtual_interface.hpp>
#if defined(__WIN32__)
#include <windows.h>
#endif
using asio::ip::tcp; using asio::ip::tcp;
using namespace ColumnLynx::Utils; using namespace ColumnLynx::Utils;
using namespace ColumnLynx::Net::TCP; using namespace ColumnLynx::Net::TCP;
@@ -41,12 +37,7 @@ int main(int argc, char** argv) {
#else #else
("i,interface", "Override used interface", cxxopts::value<std::string>()->default_value("lynx0")) ("i,interface", "Override used interface", cxxopts::value<std::string>()->default_value("lynx0"))
#endif #endif
#if defined(__WIN32__) ("config", "Override config file path", cxxopts::value<std::string>()->default_value("./server_config"));
/* Get config dir in LOCALAPPDATA\ColumnLynx\ */
("config-dir", "Override config dir path", cxxopts::value<std::string>()->default_value("C:\\ProgramData\\ColumnLynx\\"));
#else
("config-dir", "Override config dir path", cxxopts::value<std::string>()->default_value("/etc/columnlynx"));
#endif
PanicHandler::init(); PanicHandler::init();
@@ -55,7 +46,7 @@ int main(int argc, char** argv) {
if (optionsObj.count("help")) { if (optionsObj.count("help")) {
std::cout << options.help() << std::endl; std::cout << options.help() << std::endl;
std::cout << "This software is licensed under the GPLv2-only license OR the GPLv3 license.\n"; std::cout << "This software is licensed under the GPLv2-only license OR the GPLv3 license.\n";
std::cout << "Copyright (C) 2026, The ColumnLynx Contributors.\n"; std::cout << "Copyright (C) 2025, The ColumnLynx Contributors.\n";
std::cout << "This software is provided under ABSOLUTELY NO WARRANTY, to the extent permitted by law.\n"; std::cout << "This software is provided under ABSOLUTELY NO WARRANTY, to the extent permitted by law.\n";
return 0; return 0;
} }
@@ -69,22 +60,7 @@ int main(int argc, char** argv) {
//WintunInitialize(); //WintunInitialize();
#endif #endif
// Get the config path, ENV > CLI > /etc/columnlynx std::unordered_map<std::string, std::string> config = Utils::getConfigMap(optionsObj["config"].as<std::string>());
std::string configPath = optionsObj["config-dir"].as<std::string>();
const char* envConfigPath = std::getenv("COLUMNLYNX_CONFIG_DIR");
if (envConfigPath != nullptr) {
configPath = std::string(envConfigPath);
}
if (configPath.back() != '/' && configPath.back() != '\\') {
#if defined(__WIN32__)
configPath += "\\";
#else
configPath += "/";
#endif
}
std::unordered_map<std::string, std::string> config = Utils::getConfigMap(configPath + "server_config");
std::shared_ptr<VirtualInterface> tun = std::make_shared<VirtualInterface>(optionsObj["interface"].as<std::string>()); std::shared_ptr<VirtualInterface> tun = std::make_shared<VirtualInterface>(optionsObj["interface"].as<std::string>());
log("Using virtual interface: " + tun->getName()); log("Using virtual interface: " + tun->getName());
@@ -99,20 +75,14 @@ int main(int argc, char** argv) {
log("Loading keypair from config file."); log("Loading keypair from config file.");
PublicKey pk; PublicKey pk;
PrivateSeed seed; PrivateKey sk;
std::copy_n(Utils::hexStringToBytes(itPrivkey->second).begin(), seed.size(), seed.begin()); std::copy_n(Utils::hexStringToBytes(itPrivkey->second).begin(), sk.size(), sk.begin());
std::copy_n(Utils::hexStringToBytes(itPubkey->second).begin(), pk.size(), pk.begin()); std::copy_n(Utils::hexStringToBytes(itPubkey->second).begin(), pk.size(), pk.begin());
if (!sodiumWrapper->recomputeKeys(seed, pk)) { sodiumWrapper->setKeys(pk, sk);
throw std::runtime_error("Failed to recompute keypair from config file values!");
}
} else { } else {
#if defined(DEBUG)
warn("No keypair found in config file! Using random key."); warn("No keypair found in config file! Using random key.");
#else
throw std::runtime_error("No keypair found in config file! Cannot start server without keys.");
#endif
} }
log("Server public key: " + bytesToHexString(sodiumWrapper->getPublicKey(), crypto_sign_PUBLICKEYBYTES)); log("Server public key: " + bytesToHexString(sodiumWrapper->getPublicKey(), crypto_sign_PUBLICKEYBYTES));
@@ -121,7 +91,7 @@ int main(int argc, char** argv) {
asio::io_context io; asio::io_context io;
auto server = std::make_shared<TCPServer>(io, serverPort(), sodiumWrapper, hostRunning, configPath, ipv4Only); auto server = std::make_shared<TCPServer>(io, serverPort(), sodiumWrapper, hostRunning, ipv4Only);
auto udpServer = std::make_shared<UDPServer>(io, serverPort(), hostRunning, ipv4Only, tun); auto udpServer = std::make_shared<UDPServer>(io, serverPort(), hostRunning, ipv4Only, tun);
asio::signal_set signals(io, SIGINT, SIGTERM); asio::signal_set signals(io, SIGINT, SIGTERM);
@@ -153,28 +123,15 @@ int main(int argc, char** argv) {
} }
const uint8_t* ip = packet.data(); const uint8_t* ip = packet.data();
uint32_t srcIP = ntohl(*(uint32_t*)(ip + 12)); // IPv4 source address offset uint32_t dstIP = ntohl(*(uint32_t*)(ip + 16)); // IPv4 destination address offset in IPv6-mapped header
uint32_t dstIP = ntohl(*(uint32_t*)(ip + 16)); // IPv4 destination address offset
// First, check if destination IP is a registered client (e.g., server responding to client or client-to-client) auto session = SessionRegistry::getInstance().getByIP(dstIP);
auto dstSession = SessionRegistry::getInstance().getByIP(dstIP); if (!session) {
if (dstSession) { Utils::warn("TUN: No session found for destination IP " + VirtualInterface::ipv4ToString(dstIP));
// Destination is a registered client, forward to that client's session
udpServer->sendData(dstSession->sessionID, std::string(packet.begin(), packet.end()));
continue; continue;
} }
// Destination is not a registered client, check if source is (for external routing) udpServer->sendData(session->sessionID, std::string(packet.begin(), packet.end()));
auto srcSession = SessionRegistry::getInstance().getByIP(srcIP);
if (srcSession) {
// Source is a registered client, write to TUN interface to forward to external destination
tun->writePacket(packet);
continue;
}
// Neither source nor destination is registered, drop the packet
Utils::warn("TUN: No session found for source IP " + VirtualInterface::ipv4ToString(srcIP) +
" or destination IP " + VirtualInterface::ipv4ToString(dstIP));
} }
log("Shutting down server..."); log("Shutting down server...");

View File

@@ -1,5 +1,5 @@
// tcp_connection.cpp - TCP Connection for ColumnLynx // tcp_connection.cpp - TCP Connection for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#include <columnlynx/server/net/tcp/tcp_connection.hpp> #include <columnlynx/server/net/tcp/tcp_connection.hpp>
@@ -145,7 +145,7 @@ namespace ColumnLynx::Net::TCP {
Utils::debug("Key attempted connect: " + Utils::bytesToHexString(signPk.data(), signPk.size())); Utils::debug("Key attempted connect: " + Utils::bytesToHexString(signPk.data(), signPk.size()));
std::vector<std::string> whitelistedKeys = Utils::getWhitelistedKeys(mConfigDirPath); std::vector<std::string> whitelistedKeys = Utils::getWhitelistedKeys();
if (std::find(whitelistedKeys.begin(), whitelistedKeys.end(), Utils::bytesToHexString(signPk.data(), signPk.size())) == whitelistedKeys.end()) { if (std::find(whitelistedKeys.begin(), whitelistedKeys.end(), Utils::bytesToHexString(signPk.data(), signPk.size())) == whitelistedKeys.end()) {
Utils::warn("Non-whitelisted client attempted to connect, terminating. Client IP: " + reqAddr); Utils::warn("Non-whitelisted client attempted to connect, terminating. Client IP: " + reqAddr);

View File

@@ -1,5 +1,5 @@
// tcp_server.cpp - TCP Server for ColumnLynx // tcp_server.cpp - TCP Server for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#include <columnlynx/server/net/tcp/tcp_server.hpp> #include <columnlynx/server/net/tcp/tcp_server.hpp>
@@ -36,7 +36,6 @@ namespace ColumnLynx::Net::TCP {
std::move(socket), std::move(socket),
mSodiumWrapper, mSodiumWrapper,
&mRawServerConfig, &mRawServerConfig,
mConfigDirPath,
[this](std::shared_ptr<TCPConnection> c) { [this](std::shared_ptr<TCPConnection> c) {
mClients.erase(c); mClients.erase(c);
Utils::log("Client removed."); Utils::log("Client removed.");

View File

@@ -1,5 +1,5 @@
// udp_server.cpp - UDP Server for ColumnLynx // udp_server.cpp - UDP Server for ColumnLynx
// Copyright (C) 2026 DcruBro // 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. // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
#include <columnlynx/server/net/udp/udp_server.hpp> #include <columnlynx/server/net/udp/udp_server.hpp>