Compare commits
7 Commits
4ba59fb23f
...
auth_test
| Author | SHA1 | Date | |
|---|---|---|---|
| d2242ebbc7 | |||
| 84c00b7bcb | |||
| 7b757c814c | |||
|
|
e3df3cd0a7 | ||
|
|
f776f1fdd1 | ||
|
|
de3ec98363 | ||
| 022debfa5b |
@@ -6,16 +6,20 @@ 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 0.0.4
|
VERSION 0.0.5
|
||||||
LANGUAGES CXX
|
LANGUAGES CXX
|
||||||
)
|
)
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# General C++ setup
|
# General C++ setup
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
#set(CMAKE_CXX_FLAGS_DEBUG "-g")
|
||||||
|
#add_compile_options(${CMAKE_CXX_FLAGS_DEBUG})
|
||||||
|
|
||||||
|
add_compile_definitions(DEBUG=1) # TODO: Forcing for now, add dymanic based on compile flags later
|
||||||
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
#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>
|
||||||
|
|
||||||
@@ -42,19 +43,46 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
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)
|
||||||
{}
|
{
|
||||||
|
// Preload the config map
|
||||||
|
mRawClientConfig = Utils::getConfigMap("client_config");
|
||||||
|
|
||||||
|
if (!mRawClientConfig.empty()) {
|
||||||
|
Utils::debug("Loading the keys");
|
||||||
|
|
||||||
|
PrivateKey sk;
|
||||||
|
PublicKey pk;
|
||||||
|
std::copy_n(Utils::hexStringToBytes(mRawClientConfig.find("CLIENT_PRIVATE_KEY")->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(mRawClientConfig.find("CLIENT_PUBLIC_KEY")->second).begin(), pk.size(), pk.begin());
|
||||||
|
|
||||||
|
mLibSodiumWrapper->setKeys(pk, sk);
|
||||||
|
|
||||||
|
Utils::debug("Newly-Loaded Public Key: " + Utils::bytesToHexString(mLibSodiumWrapper->getPublicKey(), 32));
|
||||||
|
Utils::debug("Newly-Loaded Private Key: " + Utils::bytesToHexString(mLibSodiumWrapper->getPrivateKey(), 64));
|
||||||
|
Utils::debug("Public Encryption Key: " + Utils::bytesToHexString(mLibSodiumWrapper->getXPublicKey(), 32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts the TCP Client and initiaties the handshake
|
||||||
void start();
|
void start();
|
||||||
|
// Sends a TCP message to the server
|
||||||
void sendMessage(ClientMessageType type, const std::string& data = "");
|
void sendMessage(ClientMessageType type, const std::string& data = "");
|
||||||
|
// Attempt to gracefully disconnect from the server
|
||||||
void disconnect(bool echo = true);
|
void disconnect(bool echo = true);
|
||||||
|
|
||||||
|
// Get the handshake status
|
||||||
bool isHandshakeComplete() const;
|
bool isHandshakeComplete() const;
|
||||||
|
// Get the connection status
|
||||||
bool isConnected() const;
|
bool isConnected() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Start the heartbeat routine
|
||||||
void mStartHeartbeat();
|
void mStartHeartbeat();
|
||||||
|
// Handle an incoming TCP message
|
||||||
void mHandleMessage(ServerMessageType type, const std::string& data);
|
void mHandleMessage(ServerMessageType type, const std::string& data);
|
||||||
|
|
||||||
|
// TODO: Move ptrs to smart ptrs
|
||||||
|
|
||||||
bool mConnected = false;
|
bool mConnected = false;
|
||||||
bool mHandshakeComplete = false;
|
bool mHandshakeComplete = false;
|
||||||
tcp::resolver mResolver;
|
tcp::resolver mResolver;
|
||||||
@@ -76,5 +104,6 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
bool mIsHostDomain;
|
bool mIsHostDomain;
|
||||||
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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -25,12 +25,17 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
mStartReceive();
|
mStartReceive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start the UDP client
|
||||||
void start();
|
void start();
|
||||||
|
// Send a UDP message
|
||||||
void sendMessage(const std::string& data = "");
|
void sendMessage(const std::string& data = "");
|
||||||
|
// Stop the UDP client
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Start the UDP listener routine
|
||||||
void mStartReceive();
|
void mStartReceive();
|
||||||
|
// Handle an incoming UDP message
|
||||||
void mHandlePacket(std::size_t bytes);
|
void mHandlePacket(std::size_t bytes);
|
||||||
|
|
||||||
asio::ip::udp::socket mSocket;
|
asio::ip::udp::socket mSocket;
|
||||||
|
|||||||
@@ -35,27 +35,35 @@ namespace ColumnLynx::Utils {
|
|||||||
public:
|
public:
|
||||||
LibSodiumWrapper();
|
LibSodiumWrapper();
|
||||||
|
|
||||||
|
// These are pretty self-explanatory
|
||||||
|
|
||||||
uint8_t* getPublicKey();
|
uint8_t* getPublicKey();
|
||||||
uint8_t* getPrivateKey();
|
uint8_t* getPrivateKey();
|
||||||
uint8_t* getXPublicKey() { return mXPublicKey.data(); }
|
uint8_t* getXPublicKey() { return mXPublicKey.data(); }
|
||||||
uint8_t* getXPrivateKey() { return mXPrivateKey.data(); }
|
uint8_t* getXPrivateKey() { return mXPrivateKey.data(); }
|
||||||
|
|
||||||
|
// Set the Asymmetric signing keypair. This also regenerates the corresponding encryption keypair; Dangerous!
|
||||||
|
void setKeys(PublicKey pk, PrivateKey sk) {
|
||||||
|
mPublicKey = pk;
|
||||||
|
mPrivateKey = sk;
|
||||||
|
|
||||||
|
// Convert to Curve25519 keys for encryption
|
||||||
|
crypto_sign_ed25519_pk_to_curve25519(mXPublicKey.data(), mPublicKey.data());
|
||||||
|
crypto_sign_ed25519_sk_to_curve25519(mXPrivateKey.data(), mPrivateKey.data());
|
||||||
|
}
|
||||||
|
|
||||||
// Helper section
|
// Helper section
|
||||||
|
|
||||||
// Generates a random 256-bit (32-byte) array
|
// Generates a random 256-bit (32-byte) array
|
||||||
static std::array<uint8_t, 32> generateRandom256Bit();
|
static std::array<uint8_t, 32> generateRandom256Bit();
|
||||||
|
|
||||||
|
// Sign a message with the stored private key
|
||||||
static inline Signature signMessage(const uint8_t* msg, size_t len, const PrivateKey& sk) {
|
static inline Signature signMessage(const uint8_t* msg, size_t len, const PrivateKey& sk) {
|
||||||
Signature sig{};
|
Signature sig{};
|
||||||
crypto_sign_detached(sig.data(), nullptr, msg, len, sk.data());
|
crypto_sign_detached(sig.data(), nullptr, msg, len, sk.data());
|
||||||
return sig;
|
return sig;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool verifyMessage(const uint8_t* msg, size_t len,
|
|
||||||
const Signature& sig, const PublicKey& pk) {
|
|
||||||
return crypto_sign_verify_detached(sig.data(), msg, len, pk.data()) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overloads for std::string / std::array
|
// Overloads for std::string / std::array
|
||||||
static inline Signature signMessage(const std::string& msg, const PrivateKey& sk) {
|
static inline Signature signMessage(const std::string& msg, const PrivateKey& sk) {
|
||||||
return signMessage(reinterpret_cast<const uint8_t*>(msg.data()), msg.size(), sk);
|
return signMessage(reinterpret_cast<const uint8_t*>(msg.data()), msg.size(), sk);
|
||||||
@@ -72,6 +80,11 @@ namespace ColumnLynx::Utils {
|
|||||||
return sig;
|
return sig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify a message with a given public key
|
||||||
|
static inline bool verifyMessage(const uint8_t* msg, size_t len, const Signature& sig, const PublicKey& pk) {
|
||||||
|
return crypto_sign_verify_detached(sig.data(), msg, len, pk.data()) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
static inline bool verifyMessage(const std::string& msg, const Signature& sig, const PublicKey& pk) {
|
static inline bool verifyMessage(const std::string& msg, const Signature& sig, const PublicKey& pk) {
|
||||||
return verifyMessage(reinterpret_cast<const uint8_t*>(msg.data()), msg.size(), sig, pk);
|
return verifyMessage(reinterpret_cast<const uint8_t*>(msg.data()), msg.size(), sig, pk);
|
||||||
}
|
}
|
||||||
@@ -86,7 +99,7 @@ namespace ColumnLynx::Utils {
|
|||||||
return crypto_sign_verify_detached(sig.data(), msg, len, pk_raw) == 0;
|
return crypto_sign_verify_detached(sig.data(), msg, len, pk_raw) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt with ChaCha20-Poly1305 (returns ciphertext as bytes)
|
// Encrypt symmetrically with ChaCha20-Poly1305; returns ciphertext as bytes
|
||||||
static inline std::vector<uint8_t> encryptMessage(
|
static inline std::vector<uint8_t> encryptMessage(
|
||||||
const uint8_t* plaintext, size_t len,
|
const uint8_t* plaintext, size_t len,
|
||||||
const SymmetricKey& key, const Nonce& nonce,
|
const SymmetricKey& key, const Nonce& nonce,
|
||||||
@@ -109,7 +122,7 @@ namespace ColumnLynx::Utils {
|
|||||||
return ciphertext;
|
return ciphertext;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt with ChaCha20-Poly1305 (returns plaintext as bytes)
|
// Decrypt symmetrically with ChaCha20-Poly1305; Returns plaintext as bytes
|
||||||
static inline std::vector<uint8_t> decryptMessage(
|
static inline std::vector<uint8_t> decryptMessage(
|
||||||
const uint8_t* ciphertext, size_t len,
|
const uint8_t* ciphertext, size_t len,
|
||||||
const SymmetricKey& key, const Nonce& nonce,
|
const SymmetricKey& key, const Nonce& nonce,
|
||||||
@@ -135,12 +148,14 @@ namespace ColumnLynx::Utils {
|
|||||||
return plaintext;
|
return plaintext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a random nonce
|
||||||
static inline Nonce generateNonce() {
|
static inline Nonce generateNonce() {
|
||||||
Nonce n{};
|
Nonce n{};
|
||||||
randombytes_buf(n.data(), n.size());
|
randombytes_buf(n.data(), n.size());
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Encrypt message asymmetrically; Returns ciphertext as bytes
|
||||||
static inline std::vector<uint8_t> encryptAsymmetric(
|
static inline std::vector<uint8_t> encryptAsymmetric(
|
||||||
const uint8_t* plaintext, size_t len,
|
const uint8_t* plaintext, size_t len,
|
||||||
const AsymNonce& nonce,
|
const AsymNonce& nonce,
|
||||||
@@ -161,6 +176,7 @@ namespace ColumnLynx::Utils {
|
|||||||
return ciphertext;
|
return ciphertext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decrypt message asymmetrically; Returns plaintext as bytes
|
||||||
static inline std::vector<uint8_t> decryptAsymmetric(
|
static inline std::vector<uint8_t> decryptAsymmetric(
|
||||||
const uint8_t* ciphertext, size_t len,
|
const uint8_t* ciphertext, size_t len,
|
||||||
const AsymNonce& nonce,
|
const AsymNonce& nonce,
|
||||||
@@ -184,6 +200,7 @@ namespace ColumnLynx::Utils {
|
|||||||
return plaintext;
|
return plaintext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify a public key (certificate) against system-installed CAs
|
||||||
static inline bool verifyCertificateWithSystemCAs(const std::vector<uint8_t>& cert_der) {
|
static inline bool verifyCertificateWithSystemCAs(const std::vector<uint8_t>& cert_der) {
|
||||||
// Parse DER-encoded certificate
|
// Parse DER-encoded certificate
|
||||||
const unsigned char* p = cert_der.data();
|
const unsigned char* p = cert_der.data();
|
||||||
@@ -225,6 +242,7 @@ namespace ColumnLynx::Utils {
|
|||||||
return result == 1;
|
return result == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract the hostnames (Subject Alternative Names and Common Names) out of a public key (certificate)
|
||||||
static inline std::vector<std::string> getCertificateHostname(const std::vector<uint8_t>& cert_der) {
|
static inline std::vector<std::string> getCertificateHostname(const std::vector<uint8_t>& cert_der) {
|
||||||
std::vector<std::string> names;
|
std::vector<std::string> names;
|
||||||
|
|
||||||
|
|||||||
@@ -14,16 +14,16 @@
|
|||||||
|
|
||||||
namespace ColumnLynx::Net {
|
namespace ColumnLynx::Net {
|
||||||
struct SessionState {
|
struct SessionState {
|
||||||
SymmetricKey aesKey; // Immutable after creation
|
SymmetricKey aesKey; // Agreed-upon AES-256 kes for that session; Immutable after creation
|
||||||
std::atomic<uint64_t> send_ctr{0}; // Per-direction counters
|
std::atomic<uint64_t> send_ctr{0}; // Per-direction counters
|
||||||
std::atomic<uint64_t> recv_ctr{0};
|
std::atomic<uint64_t> recv_ctr{0}; // Per-direction counters
|
||||||
asio::ip::udp::endpoint udpEndpoint;
|
asio::ip::udp::endpoint udpEndpoint; // Deducted IP + Port of that session client
|
||||||
std::atomic<uint64_t> sendCounter{0};
|
std::atomic<uint64_t> sendCounter{0}; // Counter of sent messages
|
||||||
std::chrono::steady_clock::time_point created = std::chrono::steady_clock::now();
|
std::chrono::steady_clock::time_point created = std::chrono::steady_clock::now(); // Time created
|
||||||
std::chrono::steady_clock::time_point expires{};
|
std::chrono::steady_clock::time_point expires{}; // Time of expiry
|
||||||
uint32_t clientTunIP;
|
uint32_t clientTunIP; // Assigned IP
|
||||||
uint32_t serverTunIP;
|
uint32_t serverTunIP; // Server IP
|
||||||
uint64_t sessionID;
|
uint64_t sessionID; // Session ID
|
||||||
Nonce base_nonce{};
|
Nonce base_nonce{};
|
||||||
|
|
||||||
~SessionState() { sodium_memzero(aesKey.data(), aesKey.size()); }
|
~SessionState() { sodium_memzero(aesKey.data(), aesKey.size()); }
|
||||||
@@ -36,6 +36,7 @@ namespace ColumnLynx::Net {
|
|||||||
expires = created + ttl;
|
expires = created + ttl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the UDP endpoint
|
||||||
void setUDPEndpoint(const asio::ip::udp::endpoint& ep) {
|
void setUDPEndpoint(const asio::ip::udp::endpoint& ep) {
|
||||||
udpEndpoint = ep;
|
udpEndpoint = ep;
|
||||||
}
|
}
|
||||||
@@ -43,28 +44,31 @@ namespace ColumnLynx::Net {
|
|||||||
|
|
||||||
class SessionRegistry {
|
class SessionRegistry {
|
||||||
public:
|
public:
|
||||||
|
// Return a reference to the Session Registry instance
|
||||||
static SessionRegistry& getInstance() { static SessionRegistry instance; return instance; }
|
static SessionRegistry& getInstance() { static SessionRegistry instance; return instance; }
|
||||||
|
|
||||||
// Insert or replace
|
// Insert or replace a session entry
|
||||||
void put(uint64_t sessionID, std::shared_ptr<SessionState> state) {
|
void put(uint64_t sessionID, std::shared_ptr<SessionState> state) {
|
||||||
std::unique_lock lock(mMutex);
|
std::unique_lock lock(mMutex);
|
||||||
mSessions[sessionID] = std::move(state);
|
mSessions[sessionID] = std::move(state);
|
||||||
mIPSessions[mSessions[sessionID]->clientTunIP] = mSessions[sessionID];
|
mIPSessions[mSessions[sessionID]->clientTunIP] = mSessions[sessionID];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup
|
// Lookup a session entry by session ID
|
||||||
std::shared_ptr<const SessionState> get(uint64_t sessionID) const {
|
std::shared_ptr<const SessionState> get(uint64_t sessionID) const {
|
||||||
std::shared_lock lock(mMutex);
|
std::shared_lock lock(mMutex);
|
||||||
auto it = mSessions.find(sessionID);
|
auto it = mSessions.find(sessionID);
|
||||||
return (it == mSessions.end()) ? nullptr : it->second;
|
return (it == mSessions.end()) ? nullptr : it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lookup a session entry by IPv4
|
||||||
std::shared_ptr<const SessionState> getByIP(uint32_t ip) const {
|
std::shared_ptr<const SessionState> getByIP(uint32_t ip) const {
|
||||||
std::shared_lock lock(mMutex);
|
std::shared_lock lock(mMutex);
|
||||||
auto it = mIPSessions.find(ip);
|
auto it = mIPSessions.find(ip);
|
||||||
return (it == mIPSessions.end()) ? nullptr : it->second;
|
return (it == mIPSessions.end()) ? nullptr : it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get a snapshot of the Session Registry
|
||||||
std::unordered_map<uint64_t, std::shared_ptr<SessionState>> snapshot() const {
|
std::unordered_map<uint64_t, std::shared_ptr<SessionState>> snapshot() const {
|
||||||
std::unordered_map<uint64_t, std::shared_ptr<SessionState>> snap;
|
std::unordered_map<uint64_t, std::shared_ptr<SessionState>> snap;
|
||||||
std::shared_lock lock(mMutex);
|
std::shared_lock lock(mMutex);
|
||||||
@@ -72,7 +76,7 @@ namespace ColumnLynx::Net {
|
|||||||
return snap;
|
return snap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove
|
// Remove a session by ID
|
||||||
void erase(uint64_t sessionID) {
|
void erase(uint64_t sessionID) {
|
||||||
std::unique_lock lock(mMutex);
|
std::unique_lock lock(mMutex);
|
||||||
mSessions.erase(sessionID);
|
mSessions.erase(sessionID);
|
||||||
@@ -99,6 +103,7 @@ namespace ColumnLynx::Net {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the number of registered sessions
|
||||||
int size() const {
|
int size() const {
|
||||||
std::shared_lock lock(mMutex);
|
std::shared_lock lock(mMutex);
|
||||||
return static_cast<int>(mSessions.size());
|
return static_cast<int>(mSessions.size());
|
||||||
@@ -106,6 +111,7 @@ namespace ColumnLynx::Net {
|
|||||||
|
|
||||||
// IP management (simple for /24 subnet)
|
// IP management (simple for /24 subnet)
|
||||||
|
|
||||||
|
// Get the lowest available IPv4 address; Returns 0 if none available
|
||||||
uint32_t getFirstAvailableIP() const {
|
uint32_t getFirstAvailableIP() const {
|
||||||
std::shared_lock lock(mMutex);
|
std::shared_lock lock(mMutex);
|
||||||
uint32_t baseIP = 0x0A0A0002; // 10.10.0.2
|
uint32_t baseIP = 0x0A0A0002; // 10.10.0.2
|
||||||
@@ -116,14 +122,18 @@ namespace ColumnLynx::Net {
|
|||||||
if (mSessionIPs.find(candidateIP) == mSessionIPs.end()) {
|
if (mSessionIPs.find(candidateIP) == mSessionIPs.end()) {
|
||||||
return candidateIP;
|
return candidateIP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0; // Unavailable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lock an IP as assigned to a specific session
|
||||||
void lockIP(uint64_t sessionID, uint32_t ip) {
|
void lockIP(uint64_t sessionID, uint32_t ip) {
|
||||||
std::unique_lock lock(mMutex);
|
std::unique_lock lock(mMutex);
|
||||||
mSessionIPs[sessionID] = ip;
|
mSessionIPs[sessionID] = ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unlock the IP associated with a given session
|
||||||
void deallocIP(uint64_t sessionID) {
|
void deallocIP(uint64_t sessionID) {
|
||||||
std::unique_lock lock(mMutex);
|
std::unique_lock lock(mMutex);
|
||||||
mSessionIPs.erase(sessionID);
|
mSessionIPs.erase(sessionID);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
namespace ColumnLynx::Net::UDP {
|
namespace ColumnLynx::Net::UDP {
|
||||||
|
// @deprecated
|
||||||
// Shared between server and client
|
// Shared between server and client
|
||||||
enum class MessageType : uint8_t {
|
enum class MessageType : uint8_t {
|
||||||
PING = 0x01,
|
PING = 0x01,
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ namespace ColumnLynx::Utils {
|
|||||||
std::cerr << "Panic trace written to panic_dump.txt\n";
|
std::cerr << "Panic trace written to panic_dump.txt\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gets the current time
|
||||||
static std::string currentTime() {
|
static std::string currentTime() {
|
||||||
std::time_t t = std::time(nullptr);
|
std::time_t t = std::time(nullptr);
|
||||||
char buf[64];
|
char buf[64];
|
||||||
|
|||||||
@@ -7,6 +7,12 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
#include <fstream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
@@ -17,17 +23,27 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace ColumnLynx::Utils {
|
namespace ColumnLynx::Utils {
|
||||||
|
// 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.
|
||||||
void warn(const std::string &msg);
|
void warn(const std::string &msg);
|
||||||
|
// General error function. Use for logging failures and general errors.
|
||||||
void error(const std::string &msg);
|
void error(const std::string &msg);
|
||||||
|
// Debug log function. Use for logging non-important information. These will not print unless the binary is compiled with DEBUG=1
|
||||||
|
void debug(const std::string &msg);
|
||||||
|
|
||||||
|
// Returns the hostname of the running platform.
|
||||||
std::string getHostname();
|
std::string getHostname();
|
||||||
|
// Returns the version of the running release.
|
||||||
std::string getVersion();
|
std::string getVersion();
|
||||||
unsigned short serverPort();
|
unsigned short serverPort();
|
||||||
unsigned char protocolVersion();
|
unsigned char protocolVersion();
|
||||||
|
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);
|
||||||
|
// Hex string to raw byte conversion helper
|
||||||
|
std::vector<uint8_t> hexStringToBytes(const std::string& hex);
|
||||||
|
|
||||||
// uint8_t to raw string conversion helper
|
// uint8_t to raw string conversion helper
|
||||||
template <size_t N>
|
template <size_t N>
|
||||||
@@ -38,4 +54,28 @@ namespace ColumnLynx::Utils {
|
|||||||
inline std::string uint8ArrayToString(const uint8_t* data, size_t length) {
|
inline std::string uint8ArrayToString(const uint8_t* data, size_t length) {
|
||||||
return std::string(reinterpret_cast<const char*>(data), length);
|
return std::string(reinterpret_cast<const char*>(data), length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline constexpr uint64_t cbswap64(uint64_t x) {
|
||||||
|
return ((x & 0x00000000000000FFULL) << 56) |
|
||||||
|
((x & 0x000000000000FF00ULL) << 40) |
|
||||||
|
((x & 0x0000000000FF0000ULL) << 24) |
|
||||||
|
((x & 0x00000000FF000000ULL) << 8) |
|
||||||
|
((x & 0x000000FF00000000ULL) >> 8) |
|
||||||
|
((x & 0x0000FF0000000000ULL) >> 24) |
|
||||||
|
((x & 0x00FF000000000000ULL) >> 40) |
|
||||||
|
((x & 0xFF00000000000000ULL) >> 56);
|
||||||
|
}
|
||||||
|
|
||||||
|
// host -> big-endian (for little-endian hosts) - 64 bit
|
||||||
|
inline constexpr uint64_t chtobe64(uint64_t x) {
|
||||||
|
return cbswap64(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// big-endian -> host (for little-endian hosts) - 64 bit
|
||||||
|
inline constexpr uint64_t cbe64toh(uint64_t x) {
|
||||||
|
return cbswap64(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the config file in an unordered_map format. This purely reads the config file, you still need to parse it manually.
|
||||||
|
std::unordered_map<std::string, std::string> getConfigMap(std::string path);
|
||||||
};
|
};
|
||||||
@@ -33,12 +33,18 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start a TCP Connection (Handler for an incoming connection)
|
||||||
void start();
|
void start();
|
||||||
|
// Send a message to the TCP client
|
||||||
void sendMessage(ServerMessageType type, const std::string& data = "");
|
void sendMessage(ServerMessageType type, const std::string& data = "");
|
||||||
|
// Set callback for disconnects
|
||||||
void setDisconnectCallback(std::function<void(std::shared_ptr<TCPConnection>)> cb);
|
void setDisconnectCallback(std::function<void(std::shared_ptr<TCPConnection>)> cb);
|
||||||
|
// Disconnect the client
|
||||||
void disconnect();
|
void disconnect();
|
||||||
|
|
||||||
|
// Get the assigned session ID
|
||||||
uint64_t getSessionID() const;
|
uint64_t getSessionID() const;
|
||||||
|
// Get the assigned AES key; You should probably access this via the Session Registry instead
|
||||||
std::array<uint8_t, 32> getAESKey() const;
|
std::array<uint8_t, 32> getAESKey() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -51,7 +57,9 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
mLastHeartbeatSent(std::chrono::steady_clock::now())
|
mLastHeartbeatSent(std::chrono::steady_clock::now())
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
// Start the heartbeat routine
|
||||||
void mStartHeartbeat();
|
void mStartHeartbeat();
|
||||||
|
// Handle an incoming TCP message
|
||||||
void mHandleMessage(ClientMessageType type, const std::string& data);
|
void mHandleMessage(ClientMessageType type, const std::string& data);
|
||||||
|
|
||||||
std::shared_ptr<MessageHandler> mHandler;
|
std::shared_ptr<MessageHandler> mHandler;
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
mSodiumWrapper(sodiumWrapper),
|
mSodiumWrapper(sodiumWrapper),
|
||||||
mHostRunning(hostRunning)
|
mHostRunning(hostRunning)
|
||||||
{
|
{
|
||||||
|
// Preload the config map
|
||||||
|
mRawServerConfig = Utils::getConfigMap("server_config");
|
||||||
|
|
||||||
asio::error_code ec;
|
asio::error_code ec;
|
||||||
|
|
||||||
if (!ipv4Only) {
|
if (!ipv4Only) {
|
||||||
@@ -59,15 +62,19 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
mStartAccept();
|
mStartAccept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop the TCP Server
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Start accepting clients via TCP
|
||||||
void mStartAccept();
|
void mStartAccept();
|
||||||
|
|
||||||
asio::io_context &mIoContext;
|
asio::io_context &mIoContext;
|
||||||
asio::ip::tcp::acceptor mAcceptor;
|
asio::ip::tcp::acceptor mAcceptor;
|
||||||
std::unordered_set<TCPConnection::pointer> mClients;
|
std::unordered_set<TCPConnection::pointer> mClients;
|
||||||
Utils::LibSodiumWrapper *mSodiumWrapper;
|
Utils::LibSodiumWrapper *mSodiumWrapper;
|
||||||
bool* mHostRunning;
|
bool* mHostRunning;
|
||||||
|
std::unordered_map<std::string, std::string> mRawServerConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -42,13 +42,18 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
mStartReceive();
|
mStartReceive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop the UDP server
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
|
// Send UDP data to an endpoint; Fetched via the Session Registry
|
||||||
void sendData(const uint64_t sessionID, const std::string& data);
|
void sendData(const uint64_t sessionID, const std::string& data);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Start receiving UDP data
|
||||||
void mStartReceive();
|
void mStartReceive();
|
||||||
|
// Handle an incoming UDP packet
|
||||||
void mHandlePacket(std::size_t bytes);
|
void mHandlePacket(std::size_t bytes);
|
||||||
|
|
||||||
asio::ip::udp::socket mSocket;
|
asio::ip::udp::socket mSocket;
|
||||||
asio::ip::udp::endpoint mRemoteEndpoint;
|
asio::ip::udp::endpoint mRemoteEndpoint;
|
||||||
std::array<uint8_t, 2048> mRecvBuffer; // Adjust size as needed
|
std::array<uint8_t, 2048> mRecvBuffer; // Adjust size as needed
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ int main(int argc, char** argv) {
|
|||||||
auto result = options.parse(argc, argv);
|
auto result = options.parse(argc, argv);
|
||||||
if (result.count("help")) {
|
if (result.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 << "Copyright (C) 2025, The ColumnLynx Contributors.\n";
|
||||||
|
std::cout << "This software is provided under ABSOLUTELY NO WARRANTY, to the extent permitted by law.\n";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,10 +66,12 @@ int main(int argc, char** argv) {
|
|||||||
WintunInitialize();
|
WintunInitialize();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::shared_ptr<VirtualInterface> tun = std::make_shared<VirtualInterface>("utun0");
|
std::shared_ptr<VirtualInterface> tun = std::make_shared<VirtualInterface>("utun1");
|
||||||
log("Using virtual interface: " + tun->getName());
|
log("Using virtual interface: " + tun->getName());
|
||||||
|
|
||||||
LibSodiumWrapper sodiumWrapper = LibSodiumWrapper();
|
LibSodiumWrapper sodiumWrapper = LibSodiumWrapper();
|
||||||
|
debug("Public Key: " + Utils::bytesToHexString(sodiumWrapper.getPublicKey(), 32));
|
||||||
|
debug("Private Key: " + Utils::bytesToHexString(sodiumWrapper.getPrivateKey(), 64));
|
||||||
|
|
||||||
std::array<uint8_t, 32> aesKey = {0}; // Defualt zeroed state until modified by handshake
|
std::array<uint8_t, 32> aesKey = {0}; // Defualt zeroed state until modified by handshake
|
||||||
uint64_t sessionID = 0;
|
uint64_t sessionID = 0;
|
||||||
|
|||||||
@@ -28,14 +28,18 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
// Check if hostname or IPv4/IPv6
|
// Check if hostname or IPv4/IPv6
|
||||||
sockaddr_in addr4{};
|
sockaddr_in addr4{};
|
||||||
sockaddr_in6 addr6{};
|
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;
|
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;
|
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());
|
||||||
payload.insert(payload.end(),
|
/*payload.insert(payload.end(),
|
||||||
mLibSodiumWrapper->getXPublicKey(),
|
mLibSodiumWrapper->getXPublicKey(),
|
||||||
mLibSodiumWrapper->getXPublicKey() + crypto_box_PUBLICKEYBYTES
|
mLibSodiumWrapper->getXPublicKey() + crypto_box_PUBLICKEYBYTES
|
||||||
|
);*/
|
||||||
|
payload.insert(payload.end(),
|
||||||
|
mLibSodiumWrapper->getPublicKey(),
|
||||||
|
mLibSodiumWrapper->getPublicKey() + crypto_sign_PUBLICKEYBYTES
|
||||||
);
|
);
|
||||||
|
|
||||||
mHandler->sendMessage(ClientMessageType::HANDSHAKE_INIT, Utils::uint8ArrayToString(payload.data(), payload.size()));
|
mHandler->sendMessage(ClientMessageType::HANDSHAKE_INIT, Utils::uint8ArrayToString(payload.data(), payload.size()));
|
||||||
@@ -247,6 +251,9 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
|
|
||||||
std::memcpy(&mConnectionSessionID, decrypted.data(), sizeof(mConnectionSessionID));
|
std::memcpy(&mConnectionSessionID, decrypted.data(), sizeof(mConnectionSessionID));
|
||||||
std::memcpy(&mTunConfig, decrypted.data() + sizeof(mConnectionSessionID), sizeof(Protocol::TunConfig));
|
std::memcpy(&mTunConfig, decrypted.data() + sizeof(mConnectionSessionID), sizeof(Protocol::TunConfig));
|
||||||
|
|
||||||
|
mConnectionSessionID = Utils::cbe64toh(mConnectionSessionID);
|
||||||
|
|
||||||
Utils::log("Connection established with Session ID: " + std::to_string(mConnectionSessionID));
|
Utils::log("Connection established with Session ID: " + std::to_string(mConnectionSessionID));
|
||||||
|
|
||||||
if (mSessionIDRef) { // Copy to the global reference
|
if (mSessionIDRef) { // Copy to the global reference
|
||||||
|
|||||||
@@ -22,9 +22,12 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Utils::debug("Using AES key: " + Utils::bytesToHexString(mAesKeyRef->data(), 32));
|
||||||
|
|
||||||
auto encryptedPayload = Utils::LibSodiumWrapper::encryptMessage(
|
auto encryptedPayload = Utils::LibSodiumWrapper::encryptMessage(
|
||||||
reinterpret_cast<const uint8_t*>(data.data()), data.size(),
|
reinterpret_cast<const uint8_t*>(data.data()), data.size(),
|
||||||
*mAesKeyRef, hdr.nonce, "udp-data"
|
*mAesKeyRef, hdr.nonce, "udp-data"
|
||||||
|
//std::string(reinterpret_cast<const char*>(&mSessionIDRef), sizeof(uint64_t))
|
||||||
);
|
);
|
||||||
|
|
||||||
std::vector<uint8_t> packet;
|
std::vector<uint8_t> packet;
|
||||||
@@ -41,7 +44,7 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
packet.insert(packet.end(), encryptedPayload.begin(), encryptedPayload.end());
|
packet.insert(packet.end(), encryptedPayload.begin(), encryptedPayload.end());
|
||||||
|
|
||||||
mSocket.send_to(asio::buffer(packet), mRemoteEndpoint);
|
mSocket.send_to(asio::buffer(packet), mRemoteEndpoint);
|
||||||
Utils::log("Sent UDP packet of size " + std::to_string(packet.size()));
|
Utils::debug("Sent UDP packet of size " + std::to_string(packet.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void UDPClient::stop() {
|
void UDPClient::stop() {
|
||||||
@@ -100,6 +103,7 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
|
|
||||||
std::vector<uint8_t> plaintext = Utils::LibSodiumWrapper::decryptMessage(
|
std::vector<uint8_t> plaintext = Utils::LibSodiumWrapper::decryptMessage(
|
||||||
ciphertext.data(), ciphertext.size(), *mAesKeyRef, hdr.nonce, "udp-data"
|
ciphertext.data(), ciphertext.size(), *mAesKeyRef, hdr.nonce, "udp-data"
|
||||||
|
//std::string(reinterpret_cast<const char*>(&mSessionIDRef), sizeof(uint64_t))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (plaintext.empty()) {
|
if (plaintext.empty()) {
|
||||||
@@ -107,7 +111,7 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::log("UDP Client received packet from " + mRemoteEndpoint.address().to_string() + " - Packet size: " + std::to_string(bytes));
|
Utils::debug("UDP Client received packet from " + mRemoteEndpoint.address().to_string() + " - Packet size: " + std::to_string(bytes));
|
||||||
|
|
||||||
// Write to TUN
|
// Write to TUN
|
||||||
if (mTunRef) {
|
if (mTunRef) {
|
||||||
|
|||||||
@@ -6,15 +6,27 @@
|
|||||||
|
|
||||||
namespace ColumnLynx::Utils {
|
namespace ColumnLynx::Utils {
|
||||||
void log(const std::string &msg) {
|
void log(const std::string &msg) {
|
||||||
std::cout << "[LOG] " << msg << std::endl;
|
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||||
|
std::cout << "\033[0m[" << std::to_string(now) << " LOG] " << msg << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void warn(const std::string &msg) {
|
void warn(const std::string &msg) {
|
||||||
std::cerr << "[WARN] " << msg << std::endl;
|
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||||
|
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) {
|
||||||
std::cerr << "[ERROR] " << msg << std::endl;
|
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||||
|
std::cerr << "\033[31m[" << std::to_string(now) << " ERROR] " << msg << "\033[0m" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug(const std::string &msg) {
|
||||||
|
#if DEBUG || _DEBUG
|
||||||
|
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||||
|
std::cerr << "\033[95m[" << std::to_string(now) << " DEBUG] " << msg << "\033[0m" << std::endl;
|
||||||
|
#else
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getHostname() {
|
std::string getHostname() {
|
||||||
@@ -37,7 +49,7 @@ namespace ColumnLynx::Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string getVersion() {
|
std::string getVersion() {
|
||||||
return "a0.4";
|
return "a0.5";
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned short serverPort() {
|
unsigned short serverPort() {
|
||||||
@@ -61,4 +73,78 @@ namespace ColumnLynx::Utils {
|
|||||||
|
|
||||||
return hexString;
|
return hexString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> hexStringToBytes(const std::string& hex) {
|
||||||
|
// TODO: recover from errors
|
||||||
|
|
||||||
|
if (hex.length() % 2 != 0) {
|
||||||
|
throw std::invalid_argument("Hex string must have even length");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto hexValue = [](char c) -> uint8_t {
|
||||||
|
if ('0' <= c && c <= '9') return c - '0';
|
||||||
|
if ('A' <= c && c <= 'F') return c - 'A' + 10;
|
||||||
|
if ('a' <= c && c <= 'f') return c - 'a' + 10;
|
||||||
|
throw std::invalid_argument("Invalid hex character");
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t len = hex.length();
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
bytes.reserve(len / 2);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i += 2) {
|
||||||
|
uint8_t high = hexValue(hex[i]);
|
||||||
|
uint8_t low = hexValue(hex[i + 1]);
|
||||||
|
bytes.push_back((high << 4) | low);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> getWhitelistedKeys() {
|
||||||
|
// 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.
|
||||||
|
// I might redo this part.
|
||||||
|
|
||||||
|
std::vector<std::string> out;
|
||||||
|
|
||||||
|
std::ifstream file("whitelisted_keys"); // TODO: This is hardcoded for now, make dynamic
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
while (std::getline(file, line)) {
|
||||||
|
out.push_back(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::string> getConfigMap(std::string path) {
|
||||||
|
// TODO: Currently re-reads every time.
|
||||||
|
std::vector<std::string> readLines;
|
||||||
|
|
||||||
|
std::ifstream file(path);
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
while (std::getline(file, line)) {
|
||||||
|
readLines.push_back(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse them into the struct
|
||||||
|
std::unordered_map<std::string, std::string> config;
|
||||||
|
char delimiter = '=';
|
||||||
|
|
||||||
|
for (std::string str : readLines) {
|
||||||
|
std::stringstream ss(str);
|
||||||
|
|
||||||
|
std::string key;
|
||||||
|
std::string val;
|
||||||
|
|
||||||
|
std::getline(ss, key, delimiter);
|
||||||
|
std::getline(ss, val, delimiter);
|
||||||
|
|
||||||
|
config.insert({ key, val });
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
#include <columnlynx/common/net/virtual_interface.hpp>
|
#include <columnlynx/common/net/virtual_interface.hpp>
|
||||||
|
|
||||||
|
// This is all fucking voodoo dark magic.
|
||||||
|
|
||||||
namespace ColumnLynx::Net {
|
namespace ColumnLynx::Net {
|
||||||
// ------------------------------ Constructor ------------------------------
|
// ------------------------------ Constructor ------------------------------
|
||||||
VirtualInterface::VirtualInterface(const std::string& ifName)
|
VirtualInterface::VirtualInterface(const std::string& ifName)
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
options.add_options()
|
options.add_options()
|
||||||
("h,help", "Print help")
|
("h,help", "Print help")
|
||||||
("4,ipv4-only", "Force IPv4 only operation", cxxopts::value<bool>()->default_value("false"));
|
("4,ipv4-only", "Force IPv4 only operation", cxxopts::value<bool>()->default_value("false"))
|
||||||
|
("c,config", "Specify config file location", cxxopts::value<std::string>()->default_value("config.json"));
|
||||||
|
|
||||||
PanicHandler::init();
|
PanicHandler::init();
|
||||||
|
|
||||||
@@ -49,10 +50,14 @@ int main(int argc, char** argv) {
|
|||||||
auto result = options.parse(argc, argv);
|
auto result = options.parse(argc, argv);
|
||||||
if (result.count("help")) {
|
if (result.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 << "Copyright (C) 2025, The ColumnLynx Contributors.\n";
|
||||||
|
std::cout << "This software is provided under ABSOLUTELY NO WARRANTY, to the extent permitted by law.\n";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ipv4Only = result["ipv4-only"].as<bool>();
|
bool ipv4Only = result["ipv4-only"].as<bool>();
|
||||||
|
std::string configPath = result["config"].as<std::string>();
|
||||||
|
|
||||||
log("ColumnLynx Server, Version " + getVersion());
|
log("ColumnLynx Server, Version " + getVersion());
|
||||||
log("This software is licensed under the GPLv2 only OR the GPLv3. See LICENSES/ for details.");
|
log("This software is licensed under the GPLv2 only OR the GPLv3. See LICENSES/ for details.");
|
||||||
|
|||||||
@@ -114,7 +114,26 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
|
|
||||||
Utils::log("Client protocol version " + std::to_string(clientProtoVer) + " accepted from " + reqAddr + ".");
|
Utils::log("Client protocol version " + std::to_string(clientProtoVer) + " accepted from " + reqAddr + ".");
|
||||||
|
|
||||||
std::memcpy(mConnectionPublicKey.data(), data.data() + 1, std::min(data.size() - 1, sizeof(mConnectionPublicKey))); // Store the client's public key (for identification)
|
PublicKey signPk;
|
||||||
|
std::memcpy(signPk.data(), data.data() + 1, std::min(data.size() - 1, sizeof(signPk)));
|
||||||
|
|
||||||
|
// We can safely store this without further checking, the client will need to send the encrypted AES key in a way where they must possess the corresponding private key anyways.
|
||||||
|
crypto_sign_ed25519_pk_to_curve25519(mConnectionPublicKey.data(), signPk.data()); // Store the client's public encryption key key (for identification)
|
||||||
|
Utils::debug("Client " + reqAddr + " converted public encryption key: " + Utils::bytesToHexString(mConnectionPublicKey.data(), 32));
|
||||||
|
|
||||||
|
Utils::debug("Key attempted connect: " + Utils::bytesToHexString(signPk.data(), signPk.size()));
|
||||||
|
|
||||||
|
std::vector<std::string> whitelistedKeys = Utils::getWhitelistedKeys();
|
||||||
|
|
||||||
|
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);
|
||||||
|
disconnect();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::debug("Client " + reqAddr + " passed authorized_keys");
|
||||||
|
|
||||||
mHandler->sendMessage(ServerMessageType::HANDSHAKE_IDENTIFY, Utils::uint8ArrayToString(mLibSodiumWrapper->getPublicKey(), crypto_sign_PUBLICKEYBYTES)); // This public key should always exist
|
mHandler->sendMessage(ServerMessageType::HANDSHAKE_IDENTIFY, Utils::uint8ArrayToString(mLibSodiumWrapper->getPublicKey(), crypto_sign_PUBLICKEYBYTES)); // This public key should always exist
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -173,12 +192,17 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
// Make a Session ID
|
// Make a Session ID
|
||||||
randombytes_buf(&mConnectionSessionID, sizeof(mConnectionSessionID));
|
randombytes_buf(&mConnectionSessionID, sizeof(mConnectionSessionID));
|
||||||
|
|
||||||
// TODO: Make the session ID little-endian for network transmission
|
|
||||||
|
|
||||||
// Encrypt the Session ID with the established AES key (using symmetric encryption, nonce can be all zeros for this purpose)
|
// Encrypt the Session ID with the established AES key (using symmetric encryption, nonce can be all zeros for this purpose)
|
||||||
Nonce symNonce{}; // All zeros
|
Nonce symNonce{}; // All zeros
|
||||||
|
|
||||||
uint32_t clientIP = SessionRegistry::getInstance().getFirstAvailableIP();
|
uint32_t clientIP = SessionRegistry::getInstance().getFirstAvailableIP();
|
||||||
|
|
||||||
|
if (clientIP == 0) {
|
||||||
|
Utils::warn("Out of available IPs! Disconnecting client " + reqAddr);
|
||||||
|
disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Protocol::TunConfig tunConfig{};
|
Protocol::TunConfig tunConfig{};
|
||||||
tunConfig.version = Utils::protocolVersion();
|
tunConfig.version = Utils::protocolVersion();
|
||||||
tunConfig.prefixLength = 24;
|
tunConfig.prefixLength = 24;
|
||||||
@@ -190,8 +214,10 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
|
|
||||||
SessionRegistry::getInstance().lockIP(mConnectionSessionID, clientIP);
|
SessionRegistry::getInstance().lockIP(mConnectionSessionID, clientIP);
|
||||||
|
|
||||||
|
uint64_t sessionIDNet = Utils::chtobe64(mConnectionSessionID);
|
||||||
|
|
||||||
std::vector<uint8_t> payload(sizeof(uint64_t) + sizeof(tunConfig));
|
std::vector<uint8_t> payload(sizeof(uint64_t) + sizeof(tunConfig));
|
||||||
std::memcpy(payload.data(), &mConnectionSessionID, sizeof(uint64_t));
|
std::memcpy(payload.data(), &sessionIDNet, sizeof(uint64_t));
|
||||||
std::memcpy(payload.data() + sizeof(uint64_t), &tunConfig, sizeof(tunConfig));
|
std::memcpy(payload.data() + sizeof(uint64_t), &tunConfig, sizeof(tunConfig));
|
||||||
|
|
||||||
std::vector<uint8_t> encryptedPayload = Utils::LibSodiumWrapper::encryptMessage(
|
std::vector<uint8_t> encryptedPayload = Utils::LibSodiumWrapper::encryptMessage(
|
||||||
@@ -202,7 +228,7 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
mHandler->sendMessage(ServerMessageType::HANDSHAKE_EXCHANGE_KEY_CONFIRM, Utils::uint8ArrayToString(encryptedPayload.data(), encryptedPayload.size()));
|
mHandler->sendMessage(ServerMessageType::HANDSHAKE_EXCHANGE_KEY_CONFIRM, Utils::uint8ArrayToString(encryptedPayload.data(), encryptedPayload.size()));
|
||||||
|
|
||||||
// Add to session registry
|
// Add to session registry
|
||||||
Utils::log("Handshake with " + reqAddr + " completed successfully. Session ID assigned.");
|
Utils::log("Handshake with " + reqAddr + " completed successfully. Session ID assigned (" + std::to_string(mConnectionSessionID) + ").");
|
||||||
auto session = std::make_shared<SessionState>(mConnectionAESKey, std::chrono::hours(12), clientIP, htonl(0x0A0A0001), mConnectionSessionID);
|
auto session = std::make_shared<SessionState>(mConnectionAESKey, std::chrono::hours(12), clientIP, htonl(0x0A0A0001), mConnectionSessionID);
|
||||||
SessionRegistry::getInstance().put(mConnectionSessionID, std::move(session));
|
SessionRegistry::getInstance().put(mConnectionSessionID, std::move(session));
|
||||||
|
|
||||||
|
|||||||
@@ -48,26 +48,30 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
|
|
||||||
// Decrypt the actual payload
|
// Decrypt the actual payload
|
||||||
try {
|
try {
|
||||||
|
//Utils::debug("Using AES key " + Utils::bytesToHexString(session->aesKey.data(), 32));
|
||||||
|
|
||||||
auto plaintext = Utils::LibSodiumWrapper::decryptMessage(
|
auto plaintext = Utils::LibSodiumWrapper::decryptMessage(
|
||||||
encryptedPayload.data(), encryptedPayload.size(),
|
encryptedPayload.data(), encryptedPayload.size(),
|
||||||
session->aesKey,
|
session->aesKey,
|
||||||
hdr->nonce,
|
hdr->nonce, "udp-data"
|
||||||
"udp-data"
|
//std::string(reinterpret_cast<const char*>(&sessionID), sizeof(uint64_t))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Utils::debug("Passed decryption");
|
||||||
|
|
||||||
const_cast<SessionState*>(session.get())->setUDPEndpoint(mRemoteEndpoint); // Update endpoint after confirming decryption
|
const_cast<SessionState*>(session.get())->setUDPEndpoint(mRemoteEndpoint); // Update endpoint after confirming decryption
|
||||||
// Update recv counter
|
// Update recv counter
|
||||||
const_cast<SessionState*>(session.get())->recv_ctr.fetch_add(1, std::memory_order_relaxed);
|
const_cast<SessionState*>(session.get())->recv_ctr.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
|
||||||
// For now, just log the decrypted payload
|
// For now, just log the decrypted payload
|
||||||
std::string payloadStr(plaintext.begin(), plaintext.end());
|
std::string payloadStr(plaintext.begin(), plaintext.end());
|
||||||
Utils::log("UDP: Received packet from " + mRemoteEndpoint.address().to_string() + " - Payload: " + payloadStr);
|
Utils::debug("UDP: Received packet from " + mRemoteEndpoint.address().to_string() + " - Payload: " + payloadStr);
|
||||||
|
|
||||||
if (mTun) {
|
if (mTun) {
|
||||||
mTun->writePacket(plaintext); // Send to virtual interface
|
mTun->writePacket(plaintext); // Send to virtual interface
|
||||||
}
|
}
|
||||||
} catch (...) {
|
} catch (const std::exception &ex) {
|
||||||
Utils::warn("UDP: Failed to decrypt payload from " + mRemoteEndpoint.address().to_string());
|
Utils::warn("UDP: Failed to process payload from " + mRemoteEndpoint.address().to_string() + " Raw Error: '" + ex.what() + "'");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,6 +97,7 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
auto encryptedPayload = Utils::LibSodiumWrapper::encryptMessage(
|
auto encryptedPayload = Utils::LibSodiumWrapper::encryptMessage(
|
||||||
reinterpret_cast<const uint8_t*>(data.data()), data.size(),
|
reinterpret_cast<const uint8_t*>(data.data()), data.size(),
|
||||||
session->aesKey, hdr.nonce, "udp-data"
|
session->aesKey, hdr.nonce, "udp-data"
|
||||||
|
//std::string(reinterpret_cast<const char*>(&sessionID), sizeof(uint64_t))
|
||||||
);
|
);
|
||||||
|
|
||||||
std::vector<uint8_t> packet;
|
std::vector<uint8_t> packet;
|
||||||
@@ -109,7 +114,7 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
|
|
||||||
// Send packet
|
// Send packet
|
||||||
mSocket.send_to(asio::buffer(packet), endpoint);
|
mSocket.send_to(asio::buffer(packet), endpoint);
|
||||||
Utils::log("UDP: Sent packet of size " + std::to_string(packet.size()) + " to " + std::to_string(sessionID) + " (" + endpoint.address().to_string() + ":" + std::to_string(endpoint.port()) + ")");
|
Utils::debug("UDP: Sent packet of size " + std::to_string(packet.size()) + " to " + std::to_string(sessionID) + " (" + endpoint.address().to_string() + ":" + std::to_string(endpoint.port()) + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
void UDPServer::stop() {
|
void UDPServer::stop() {
|
||||||
|
|||||||
Reference in New Issue
Block a user