Compare commits
20 Commits
a08dba5b59
...
b0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| cb0f674c52 | |||
| a2ecc589f8 | |||
| b50b594d68 | |||
| 842752cd88 | |||
| 33bbd7cce6 | |||
| 640a751f9b | |||
| 668b96e8e0 | |||
| 8ed90a3bc8 | |||
| 4f4a0fd385 | |||
| 3bda0b2ec4 | |||
| 210f9f2436 | |||
| 265954d700 | |||
| 0ba78d72ed | |||
| eb7f4930e2 | |||
| 4dbde290c2 | |||
| 15d13b6f04 | |||
| f9c5c56a1b | |||
| 17dd504a7a | |||
| 9f52bdd54c | |||
| 29e90938c5 |
@@ -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 0.0.5
|
VERSION 0.1.0
|
||||||
LANGUAGES CXX
|
LANGUAGES CXX
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,7 +19,9 @@ set(CMAKE_CXX_EXTENSIONS OFF)
|
|||||||
#set(CMAKE_CXX_FLAGS_DEBUG "-g")
|
#set(CMAKE_CXX_FLAGS_DEBUG "-g")
|
||||||
#add_compile_options(${CMAKE_CXX_FLAGS_DEBUG})
|
#add_compile_options(${CMAKE_CXX_FLAGS_DEBUG})
|
||||||
|
|
||||||
add_compile_definitions(DEBUG=1) # TODO: Forcing for now, add dymanic based on compile flags later
|
if(DEBUG)
|
||||||
|
add_compile_definitions(DEBUG=1) # TODO: Forcing for now, add dymanic based on compile flags later
|
||||||
|
endif()
|
||||||
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ endif()
|
|||||||
if(WIN32)
|
if(WIN32)
|
||||||
add_compile_definitions(_WIN32_WINNT=0x0A00 NOMINMAX WIN32_LEAN_AND_MEAN)
|
add_compile_definitions(_WIN32_WINNT=0x0A00 NOMINMAX WIN32_LEAN_AND_MEAN)
|
||||||
elseif(UNIX)
|
elseif(UNIX)
|
||||||
add_compile_options(-Wall -Wextra -Wpedantic)
|
add_compile_options(-Wall -Wextra -Wpedantic -O3)
|
||||||
add_link_options(-pthread)
|
add_link_options(-pthread)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,16 @@ Configurating the server and client are are relatively easy. Currently (since th
|
|||||||
|
|
||||||
- **SERVER_PUBLIC_KEY** (Hex String): The public key to be used
|
- **SERVER_PUBLIC_KEY** (Hex String): The public key to be used
|
||||||
- **SERVER_PRIVATE_KEY** (Hex String): The private key 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)
|
||||||
|
- **SUBNET_MASK** (Integer): The subnet mask to be used (ensure proper length, it will not be checked)
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
```
|
```
|
||||||
SERVER_PUBLIC_KEY=787B648046F10DDD0B77A6303BE42D859AA65C52F5708CC3C58EB5691F217C7B
|
SERVER_PUBLIC_KEY=787B648046F10DDD0B77A6303BE42D859AA65C52F5708CC3C58EB5691F217C7B
|
||||||
SERVER_PRIVATE_KEY=778604245F57B847E63BD85DE8208FF1A127FB559895195928C3987E246B77B8787B648046F10DDD0B77A6303BE42D859AA65C52F5708CC3C58EB5691F217C7B
|
SERVER_PRIVATE_KEY=778604245F57B847E63BD85DE8208FF1A127FB559895195928C3987E246B77B8787B648046F10DDD0B77A6303BE42D859AA65C52F5708CC3C58EB5691F217C7B
|
||||||
|
NETWORK=10.10.0.0
|
||||||
|
SUBNET_MASK=24
|
||||||
```
|
```
|
||||||
|
|
||||||
<hr></hr>
|
<hr></hr>
|
||||||
@@ -76,6 +80,8 @@ ColumnLynx makes use of both **TCP** and **UDP**. **TCP** is used for the initia
|
|||||||
|
|
||||||
It operates on port **48042** for both TCP and UDP.
|
It operates on port **48042** for both TCP and UDP.
|
||||||
|
|
||||||
|
Generally, all transmission is done in **little-endian byte order**, since pretty much every single modern architecture uses it by default. The only exemption to this is the **transmission of IP addresses** (for the **Virtual Interface**), which is **big-endian**.
|
||||||
|
|
||||||
### Handshake Procedure
|
### Handshake Procedure
|
||||||
|
|
||||||
The handshake between the client and server is done over **TCP**. This is to ensure delivery without much hassle.
|
The handshake between the client and server is done over **TCP**. This is to ensure delivery without much hassle.
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
TCPClient(asio::io_context& ioContext,
|
TCPClient(asio::io_context& ioContext,
|
||||||
const std::string& host,
|
const std::string& host,
|
||||||
const std::string& port,
|
const std::string& port,
|
||||||
Utils::LibSodiumWrapper* sodiumWrapper,
|
std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper,
|
||||||
std::array<uint8_t, 32>* aesKey,
|
std::shared_ptr<std::array<uint8_t, 32>> aesKey,
|
||||||
uint64_t* sessionIDRef,
|
std::shared_ptr<uint64_t> sessionIDRef,
|
||||||
bool* insecureMode,
|
bool insecureMode,
|
||||||
std::shared_ptr<VirtualInterface> tun = nullptr)
|
std::shared_ptr<VirtualInterface> tun = nullptr)
|
||||||
:
|
:
|
||||||
mResolver(ioContext),
|
mResolver(ioContext),
|
||||||
@@ -95,12 +95,12 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
std::string mHost, mPort;
|
std::string mHost, mPort;
|
||||||
uint8_t mServerPublicKey[32]; // Assuming 256-bit public key
|
uint8_t mServerPublicKey[32]; // Assuming 256-bit public key
|
||||||
std::array<uint8_t, 32> mSubmittedChallenge{};
|
std::array<uint8_t, 32> mSubmittedChallenge{};
|
||||||
Utils::LibSodiumWrapper* mLibSodiumWrapper;
|
std::shared_ptr<Utils::LibSodiumWrapper> mLibSodiumWrapper;
|
||||||
uint64_t mConnectionSessionID;
|
uint64_t mConnectionSessionID;
|
||||||
SymmetricKey mConnectionAESKey;
|
SymmetricKey mConnectionAESKey;
|
||||||
std::array<uint8_t, 32>* mGlobalKeyRef; // Reference to global AES key
|
std::shared_ptr<std::array<uint8_t, 32>> mGlobalKeyRef; // Reference to global AES key
|
||||||
uint64_t* mSessionIDRef; // Reference to global Session ID
|
std::shared_ptr<uint64_t> mSessionIDRef; // Reference to global Session ID
|
||||||
bool* mInsecureMode; // Reference to insecure mode flag
|
bool mInsecureMode; // Insecure mode flag
|
||||||
asio::steady_timer mHeartbeatTimer;
|
asio::steady_timer mHeartbeatTimer;
|
||||||
std::chrono::steady_clock::time_point mLastHeartbeatReceived;
|
std::chrono::steady_clock::time_point mLastHeartbeatReceived;
|
||||||
std::chrono::steady_clock::time_point mLastHeartbeatSent;
|
std::chrono::steady_clock::time_point mLastHeartbeatSent;
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
UDPClient(asio::io_context& ioContext,
|
UDPClient(asio::io_context& ioContext,
|
||||||
const std::string& host,
|
const std::string& host,
|
||||||
const std::string& port,
|
const std::string& port,
|
||||||
std::array<uint8_t, 32>* aesKeyRef,
|
std::shared_ptr<std::array<uint8_t, 32>> aesKeyRef,
|
||||||
uint64_t* sessionIDRef,
|
std::shared_ptr<uint64_t> sessionIDRef,
|
||||||
std::shared_ptr<VirtualInterface> tunRef = nullptr)
|
std::shared_ptr<VirtualInterface> tunRef = nullptr)
|
||||||
: mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port), mAesKeyRef(aesKeyRef), mSessionIDRef(sessionIDRef), mTunRef(tunRef)
|
: mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port), mAesKeyRef(aesKeyRef), mSessionIDRef(sessionIDRef), mTunRef(tunRef)
|
||||||
{
|
{
|
||||||
@@ -43,8 +43,8 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
asio::ip::udp::endpoint mRemoteEndpoint;
|
asio::ip::udp::endpoint mRemoteEndpoint;
|
||||||
std::string mHost;
|
std::string mHost;
|
||||||
std::string mPort;
|
std::string mPort;
|
||||||
std::array<uint8_t, 32>* mAesKeyRef;
|
std::shared_ptr<std::array<uint8_t, 32>> mAesKeyRef;
|
||||||
uint64_t* mSessionIDRef;
|
std::shared_ptr<uint64_t> mSessionIDRef;
|
||||||
std::shared_ptr<VirtualInterface> mTunRef = nullptr;
|
std::shared_ptr<VirtualInterface> mTunRef = nullptr;
|
||||||
std::array<uint8_t, 2048> mRecvBuffer; // Adjust size as needed
|
std::array<uint8_t, 2048> mRecvBuffer; // Adjust size as needed
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,11 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
#include <sodium.h>
|
#include <sodium.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <atomic>
|
||||||
|
#include <asio.hpp>
|
||||||
#include <columnlynx/common/utils.hpp>
|
#include <columnlynx/common/utils.hpp>
|
||||||
#include <columnlynx/common/libsodium_wrapper.hpp>
|
#include <columnlynx/common/libsodium_wrapper.hpp>
|
||||||
|
|
||||||
@@ -48,96 +52,36 @@ namespace ColumnLynx::Net {
|
|||||||
static SessionRegistry& getInstance() { static SessionRegistry instance; return instance; }
|
static SessionRegistry& getInstance() { static SessionRegistry instance; return instance; }
|
||||||
|
|
||||||
// Insert or replace a session entry
|
// 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);
|
|
||||||
mSessions[sessionID] = std::move(state);
|
|
||||||
mIPSessions[mSessions[sessionID]->clientTunIP] = mSessions[sessionID];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup a session entry by session ID
|
// 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);
|
|
||||||
auto it = mSessions.find(sessionID);
|
|
||||||
return (it == mSessions.end()) ? nullptr : it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup a session entry by IPv4
|
// 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);
|
|
||||||
auto it = mIPSessions.find(ip);
|
|
||||||
return (it == mIPSessions.end()) ? nullptr : it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a snapshot of the Session Registry
|
// 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::shared_lock lock(mMutex);
|
|
||||||
snap = mSessions;
|
|
||||||
return snap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a session by ID
|
// Remove a session by ID
|
||||||
void erase(uint64_t sessionID) {
|
void erase(uint64_t sessionID);
|
||||||
std::unique_lock lock(mMutex);
|
|
||||||
mSessions.erase(sessionID);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup expired sessions
|
// Cleanup expired sessions
|
||||||
void cleanupExpired() {
|
void cleanupExpired();
|
||||||
std::unique_lock lock(mMutex);
|
|
||||||
auto now = std::chrono::steady_clock::now();
|
|
||||||
for (auto it = mSessions.begin(); it != mSessions.end(); ) {
|
|
||||||
if (it->second && it->second->expires <= now) {
|
|
||||||
it = mSessions.erase(it);
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto it = mIPSessions.begin(); it != mIPSessions.end(); ) {
|
|
||||||
if (it->second && it->second->expires <= now) {
|
|
||||||
it = mIPSessions.erase(it);
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the number of registered sessions
|
// Get the number of registered sessions
|
||||||
int size() const {
|
int size() const;
|
||||||
std::shared_lock lock(mMutex);
|
|
||||||
return static_cast<int>(mSessions.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
// IP management (simple for /24 subnet)
|
// IP management
|
||||||
|
|
||||||
// Get the lowest available IPv4 address; Returns 0 if none available
|
// Get the lowest available IPv4 address; Returns 0 if none available
|
||||||
uint32_t getFirstAvailableIP() const {
|
uint32_t getFirstAvailableIP(uint32_t baseIP, uint8_t mask) const;
|
||||||
std::shared_lock lock(mMutex);
|
|
||||||
uint32_t baseIP = 0x0A0A0002; // 10.10.0.2
|
|
||||||
|
|
||||||
// TODO: Expand to support larger subnets
|
// Lock IP to session ID; Do NOT call before put() - You will segfault!
|
||||||
for (uint32_t offset = 0; offset < 254; offset++) {
|
void lockIP(uint64_t sessionID, uint32_t ip);
|
||||||
uint32_t candidateIP = baseIP + offset;
|
|
||||||
if (mSessionIPs.find(candidateIP) == mSessionIPs.end()) {
|
|
||||||
return candidateIP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0; // Unavailable
|
// Unlock IP from session ID
|
||||||
}
|
void deallocIP(uint64_t sessionID);
|
||||||
|
|
||||||
// Lock an IP as assigned to a specific session
|
|
||||||
void lockIP(uint64_t sessionID, uint32_t ip) {
|
|
||||||
std::unique_lock lock(mMutex);
|
|
||||||
mSessionIPs[sessionID] = ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlock the IP associated with a given session
|
|
||||||
void deallocIP(uint64_t sessionID) {
|
|
||||||
std::unique_lock lock(mMutex);
|
|
||||||
mSessionIPs.erase(sessionID);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mutable std::shared_mutex mMutex;
|
mutable std::shared_mutex mMutex;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/poll.h>
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
@@ -49,9 +50,13 @@ namespace ColumnLynx::Net {
|
|||||||
const std::string& getName() const;
|
const std::string& getName() const;
|
||||||
int getFd() const; // For ASIO integration (on POSIX)
|
int getFd() const; // For ASIO integration (on POSIX)
|
||||||
|
|
||||||
static inline std::string ipv4ToString(uint32_t ip) {
|
static inline std::string ipv4ToString(uint32_t ip, bool flip = true) {
|
||||||
struct in_addr addr;
|
struct in_addr addr;
|
||||||
addr.s_addr = htonl(ip);
|
|
||||||
|
if (flip)
|
||||||
|
addr.s_addr = htonl(ip);
|
||||||
|
else
|
||||||
|
addr.s_addr = ip;
|
||||||
|
|
||||||
char buf[INET_ADDRSTRLEN];
|
char buf[INET_ADDRSTRLEN];
|
||||||
if (!inet_ntop(AF_INET, &addr, buf, sizeof(buf)))
|
if (!inet_ntop(AF_INET, &addr, buf, sizeof(buf)))
|
||||||
@@ -60,6 +65,16 @@ namespace ColumnLynx::Net {
|
|||||||
return std::string(buf);
|
return std::string(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline uint32_t stringToIpv4(const std::string &ipStr) {
|
||||||
|
struct in_addr addr;
|
||||||
|
|
||||||
|
if (inet_pton(AF_INET, ipStr.c_str(), &addr) != 1) {
|
||||||
|
return 0; // "0.0.0.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ntohl(addr.s_addr);
|
||||||
|
}
|
||||||
|
|
||||||
static inline uint32_t prefixLengthToNetmask(uint8_t prefixLen) {
|
static inline uint32_t prefixLengthToNetmask(uint8_t prefixLen) {
|
||||||
if (prefixLen == 0) return 0;
|
if (prefixLen == 0) return 0;
|
||||||
uint32_t mask = (0xFFFFFFFF << (32 - prefixLen)) & 0xFFFFFFFF;
|
uint32_t mask = (0xFFFFFFFF << (32 - prefixLen)) & 0xFFFFFFFF;
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
@@ -22,6 +24,10 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace ColumnLynx {
|
||||||
|
using IPv6Addr = std::array<uint8_t, 16>;
|
||||||
|
}
|
||||||
|
|
||||||
namespace ColumnLynx::Utils {
|
namespace ColumnLynx::Utils {
|
||||||
// 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);
|
||||||
@@ -76,6 +82,18 @@ namespace ColumnLynx::Utils {
|
|||||||
return cbswap64(x);
|
return cbswap64(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T cbswap128(const T& x) {
|
||||||
|
static_assert(sizeof(T) == 16, "cbswap128 requires a 128-bit type");
|
||||||
|
|
||||||
|
T out{};
|
||||||
|
const uint8_t* src = reinterpret_cast<const uint8_t*>(&x);
|
||||||
|
uint8_t* dst = reinterpret_cast<uint8_t*>(&out);
|
||||||
|
std::reverse_copy(src, src + 16, dst);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the config file in an unordered_map format. This purely reads the config file, you still need to parse it manually.
|
// 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);
|
std::unordered_map<std::string, std::string> getConfigMap(std::string path, std::vector<std::string> requiredKeys = {});
|
||||||
};
|
};
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
#include <columnlynx/common/libsodium_wrapper.hpp>
|
#include <columnlynx/common/libsodium_wrapper.hpp>
|
||||||
#include <columnlynx/common/net/session_registry.hpp>
|
#include <columnlynx/common/net/session_registry.hpp>
|
||||||
#include <columnlynx/common/net/protocol_structs.hpp>
|
#include <columnlynx/common/net/protocol_structs.hpp>
|
||||||
|
#include <columnlynx/common/net/virtual_interface.hpp>
|
||||||
|
|
||||||
namespace ColumnLynx::Net::TCP {
|
namespace ColumnLynx::Net::TCP {
|
||||||
class TCPConnection : public std::enable_shared_from_this<TCPConnection> {
|
class TCPConnection : public std::enable_shared_from_this<TCPConnection> {
|
||||||
@@ -25,10 +26,11 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
|
|
||||||
static pointer create(
|
static pointer create(
|
||||||
asio::ip::tcp::socket socket,
|
asio::ip::tcp::socket socket,
|
||||||
Utils::LibSodiumWrapper* sodiumWrapper,
|
std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper,
|
||||||
|
std::unordered_map<std::string, std::string>* serverConfig,
|
||||||
std::function<void(pointer)> onDisconnect)
|
std::function<void(pointer)> onDisconnect)
|
||||||
{
|
{
|
||||||
auto conn = pointer(new TCPConnection(std::move(socket), sodiumWrapper));
|
auto conn = pointer(new TCPConnection(std::move(socket), sodiumWrapper, serverConfig));
|
||||||
conn->mOnDisconnect = std::move(onDisconnect);
|
conn->mOnDisconnect = std::move(onDisconnect);
|
||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
@@ -48,10 +50,11 @@ 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, Utils::LibSodiumWrapper* sodiumWrapper)
|
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),
|
||||||
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())
|
||||||
@@ -64,7 +67,8 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
|
|
||||||
std::shared_ptr<MessageHandler> mHandler;
|
std::shared_ptr<MessageHandler> mHandler;
|
||||||
std::function<void(std::shared_ptr<TCPConnection>)> mOnDisconnect;
|
std::function<void(std::shared_ptr<TCPConnection>)> mOnDisconnect;
|
||||||
Utils::LibSodiumWrapper *mLibSodiumWrapper;
|
std::shared_ptr<Utils::LibSodiumWrapper> mLibSodiumWrapper;
|
||||||
|
std::unordered_map<std::string, std::string>* mRawServerConfig;
|
||||||
std::array<uint8_t, 32> mConnectionAESKey;
|
std::array<uint8_t, 32> mConnectionAESKey;
|
||||||
uint64_t mConnectionSessionID;
|
uint64_t mConnectionSessionID;
|
||||||
AsymPublicKey mConnectionPublicKey;
|
AsymPublicKey mConnectionPublicKey;
|
||||||
|
|||||||
@@ -24,15 +24,15 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
public:
|
public:
|
||||||
TCPServer(asio::io_context& ioContext,
|
TCPServer(asio::io_context& ioContext,
|
||||||
uint16_t port,
|
uint16_t port,
|
||||||
Utils::LibSodiumWrapper* sodiumWrapper,
|
std::shared_ptr<Utils::LibSodiumWrapper> sodiumWrapper,
|
||||||
bool* hostRunning, bool ipv4Only = false)
|
std::shared_ptr<bool> hostRunning, bool ipv4Only = false)
|
||||||
: mIoContext(ioContext),
|
: mIoContext(ioContext),
|
||||||
mAcceptor(ioContext),
|
mAcceptor(ioContext),
|
||||||
mSodiumWrapper(sodiumWrapper),
|
mSodiumWrapper(sodiumWrapper),
|
||||||
mHostRunning(hostRunning)
|
mHostRunning(hostRunning)
|
||||||
{
|
{
|
||||||
// Preload the config map
|
// Preload the config map
|
||||||
mRawServerConfig = Utils::getConfigMap("server_config");
|
mRawServerConfig = Utils::getConfigMap("server_config", {"NETWORK", "SUBNET_MASK"});
|
||||||
|
|
||||||
asio::error_code ec;
|
asio::error_code ec;
|
||||||
|
|
||||||
@@ -72,8 +72,8 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
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;
|
std::shared_ptr<Utils::LibSodiumWrapper> mSodiumWrapper;
|
||||||
bool* mHostRunning;
|
std::shared_ptr<bool> mHostRunning;
|
||||||
std::unordered_map<std::string, std::string> mRawServerConfig;
|
std::unordered_map<std::string, std::string> mRawServerConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
namespace ColumnLynx::Net::UDP {
|
namespace ColumnLynx::Net::UDP {
|
||||||
class UDPServer {
|
class UDPServer {
|
||||||
public:
|
public:
|
||||||
UDPServer(asio::io_context& ioContext, uint16_t port, bool* hostRunning, bool ipv4Only = false, std::shared_ptr<VirtualInterface> tun = nullptr)
|
UDPServer(asio::io_context& ioContext, uint16_t port, std::shared_ptr<bool> hostRunning, bool ipv4Only = false, std::shared_ptr<VirtualInterface> tun = nullptr)
|
||||||
: mSocket(ioContext), mHostRunning(hostRunning), mTun(tun)
|
: mSocket(ioContext), mHostRunning(hostRunning), mTun(tun)
|
||||||
{
|
{
|
||||||
asio::error_code ec;
|
asio::error_code ec;
|
||||||
@@ -56,8 +56,8 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
|
|
||||||
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; // 2048 seems stable
|
||||||
bool* mHostRunning;
|
std::shared_ptr<bool> mHostRunning;
|
||||||
std::shared_ptr<VirtualInterface> mTun;
|
std::shared_ptr<VirtualInterface> mTun;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -74,16 +74,17 @@ int main(int argc, char** argv) {
|
|||||||
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());
|
||||||
|
|
||||||
LibSodiumWrapper sodiumWrapper = LibSodiumWrapper();
|
std::shared_ptr<LibSodiumWrapper> sodiumWrapper = std::make_shared<LibSodiumWrapper>();
|
||||||
debug("Public Key: " + Utils::bytesToHexString(sodiumWrapper.getPublicKey(), 32));
|
debug("Public Key: " + Utils::bytesToHexString(sodiumWrapper->getPublicKey(), 32));
|
||||||
debug("Private Key: " + Utils::bytesToHexString(sodiumWrapper.getPrivateKey(), 64));
|
debug("Private Key: " + Utils::bytesToHexString(sodiumWrapper->getPrivateKey(), 64));
|
||||||
|
|
||||||
std::array<uint8_t, 32> aesKey = {0}; // Defualt zeroed state until modified by handshake
|
std::shared_ptr<std::array<uint8_t, 32>> aesKey = std::make_shared<std::array<uint8_t, 32>>();
|
||||||
uint64_t sessionID = 0;
|
aesKey->fill(0); // Defualt zeroed state until modified by handshake
|
||||||
|
std::shared_ptr<uint64_t> sessionID = std::make_shared<uint64_t>(0);
|
||||||
|
|
||||||
asio::io_context io;
|
asio::io_context io;
|
||||||
auto client = std::make_shared<ColumnLynx::Net::TCP::TCPClient>(io, host, port, &sodiumWrapper, &aesKey, &sessionID, &insecureMode, 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();
|
||||||
udpClient->start();
|
udpClient->start();
|
||||||
@@ -106,7 +107,7 @@ int main(int argc, char** argv) {
|
|||||||
if (packet.empty()) {
|
if (packet.empty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
udpClient->sendMessage(std::string(packet.begin(), packet.end()));
|
udpClient->sendMessage(std::string(packet.begin(), packet.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
// Verify pubkey against whitelisted_keys
|
// Verify pubkey against whitelisted_keys
|
||||||
std::vector<std::string> whitelistedKeys = Utils::getWhitelistedKeys();
|
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.");
|
||||||
disconnect();
|
disconnect();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -36,10 +36,9 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
reinterpret_cast<uint8_t*>(&hdr),
|
reinterpret_cast<uint8_t*>(&hdr),
|
||||||
reinterpret_cast<uint8_t*>(&hdr) + sizeof(UDPPacketHeader)
|
reinterpret_cast<uint8_t*>(&hdr) + sizeof(UDPPacketHeader)
|
||||||
);
|
);
|
||||||
uint64_t sid = *mSessionIDRef;
|
|
||||||
packet.insert(packet.end(),
|
packet.insert(packet.end(),
|
||||||
reinterpret_cast<uint8_t*>(&sid),
|
reinterpret_cast<uint8_t*>(mSessionIDRef.get()),
|
||||||
reinterpret_cast<uint8_t*>(&sid) + sizeof(sid)
|
reinterpret_cast<uint8_t*>(mSessionIDRef.get()) + sizeof(uint64_t)
|
||||||
);
|
);
|
||||||
packet.insert(packet.end(), encryptedPayload.begin(), encryptedPayload.end());
|
packet.insert(packet.end(), encryptedPayload.begin(), encryptedPayload.end());
|
||||||
|
|
||||||
@@ -90,6 +89,11 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
uint64_t sessionID;
|
uint64_t sessionID;
|
||||||
std::memcpy(&sessionID, mRecvBuffer.data() + sizeof(UDPPacketHeader), sizeof(uint64_t));
|
std::memcpy(&sessionID, mRecvBuffer.data() + sizeof(UDPPacketHeader), sizeof(uint64_t));
|
||||||
|
|
||||||
|
if (sessionID != *mSessionIDRef) {
|
||||||
|
Utils::warn("Got packet that isn't for me! Dropping!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Decrypt payload
|
// Decrypt payload
|
||||||
std::vector<uint8_t> ciphertext(
|
std::vector<uint8_t> ciphertext(
|
||||||
mRecvBuffer.begin() + sizeof(UDPPacketHeader) + sizeof(uint64_t),
|
mRecvBuffer.begin() + sizeof(UDPPacketHeader) + sizeof(uint64_t),
|
||||||
|
|||||||
100
src/common/session_registry.cpp
Normal file
100
src/common/session_registry.cpp
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
// session_registry.cpp - Session Registry for ColumnLynx
|
||||||
|
// Copyright (C) 2025 DcruBro
|
||||||
|
// Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
|
||||||
|
|
||||||
|
#include <columnlynx/common/net/session_registry.hpp>
|
||||||
|
|
||||||
|
namespace ColumnLynx::Net {
|
||||||
|
void SessionRegistry::put(uint64_t sessionID, std::shared_ptr<SessionState> state) {
|
||||||
|
std::unique_lock lock(mMutex);
|
||||||
|
mSessions[sessionID] = std::move(state);
|
||||||
|
mIPSessions[mSessions[sessionID]->clientTunIP] = mSessions[sessionID];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const SessionState> SessionRegistry::get(uint64_t sessionID) const {
|
||||||
|
std::shared_lock lock(mMutex);
|
||||||
|
auto it = mSessions.find(sessionID);
|
||||||
|
return (it == mSessions.end()) ? nullptr : it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const SessionState> SessionRegistry::getByIP(uint32_t ip) const {
|
||||||
|
std::shared_lock lock(mMutex);
|
||||||
|
auto it = mIPSessions.find(ip);
|
||||||
|
return (it == mIPSessions.end()) ? nullptr : it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<uint64_t, std::shared_ptr<SessionState>> SessionRegistry::snapshot() const {
|
||||||
|
std::unordered_map<uint64_t, std::shared_ptr<SessionState>> snap;
|
||||||
|
std::shared_lock lock(mMutex);
|
||||||
|
snap = mSessions;
|
||||||
|
return snap;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionRegistry::erase(uint64_t sessionID) {
|
||||||
|
std::unique_lock lock(mMutex);
|
||||||
|
mSessions.erase(sessionID);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionRegistry::cleanupExpired() {
|
||||||
|
std::unique_lock lock(mMutex);
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
for (auto it = mSessions.begin(); it != mSessions.end(); ) {
|
||||||
|
if (it->second && it->second->expires <= now) {
|
||||||
|
it = mSessions.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = mIPSessions.begin(); it != mIPSessions.end(); ) {
|
||||||
|
if (it->second && it->second->expires <= now) {
|
||||||
|
it = mIPSessions.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int SessionRegistry::size() const {
|
||||||
|
std::shared_lock lock(mMutex);
|
||||||
|
return static_cast<int>(mSessions.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t SessionRegistry::getFirstAvailableIP(uint32_t baseIP, uint8_t mask) const {
|
||||||
|
std::shared_lock lock(mMutex);
|
||||||
|
|
||||||
|
uint32_t hostCount = (1u << (32 - mask));
|
||||||
|
uint32_t firstHost = 2;
|
||||||
|
uint32_t lastHost = hostCount - 2;
|
||||||
|
|
||||||
|
for (uint32_t offset = firstHost; offset <= lastHost; offset++) {
|
||||||
|
uint32_t candidateIP = baseIP + offset;
|
||||||
|
if (mIPSessions.find(candidateIP) == mIPSessions.end()) {
|
||||||
|
return candidateIP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionRegistry::lockIP(uint64_t sessionID, uint32_t ip) {
|
||||||
|
std::unique_lock lock(mMutex);
|
||||||
|
mSessionIPs[sessionID] = ip;
|
||||||
|
|
||||||
|
/*if (mIPSessions.find(sessionID) == mIPSessions.end()) {
|
||||||
|
Utils::debug("yikes");
|
||||||
|
}*/
|
||||||
|
mIPSessions[ip] = mSessions.find(sessionID)->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionRegistry::deallocIP(uint64_t sessionID) {
|
||||||
|
std::unique_lock lock(mMutex);
|
||||||
|
|
||||||
|
auto it = mSessionIPs.find(sessionID);
|
||||||
|
if (it != mSessionIPs.end()) {
|
||||||
|
uint32_t ip = it->second;
|
||||||
|
mIPSessions.erase(ip);
|
||||||
|
mSessionIPs.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,7 +49,7 @@ namespace ColumnLynx::Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string getVersion() {
|
std::string getVersion() {
|
||||||
return "a0.5";
|
return "b0.1";
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned short serverPort() {
|
unsigned short serverPort() {
|
||||||
@@ -118,7 +118,7 @@ namespace ColumnLynx::Utils {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_map<std::string, std::string> getConfigMap(std::string path) {
|
std::unordered_map<std::string, std::string> getConfigMap(std::string path, std::vector<std::string> requiredKeys) {
|
||||||
// TODO: Currently re-reads every time.
|
// TODO: Currently re-reads every time.
|
||||||
std::vector<std::string> readLines;
|
std::vector<std::string> readLines;
|
||||||
|
|
||||||
@@ -145,6 +145,14 @@ namespace ColumnLynx::Utils {
|
|||||||
config.insert({ key, val });
|
config.insert({ key, val });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!requiredKeys.empty()) {
|
||||||
|
for (std::string x : requiredKeys) {
|
||||||
|
if (config.find(x) == config.end()) {
|
||||||
|
throw std::runtime_error("Config doesn't contain all required keys! (Missing: '" + x + "')");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,7 @@ namespace ColumnLynx::Net {
|
|||||||
|
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
// ---- macOS: UTUN (system control socket) ----
|
// ---- macOS: UTUN (system control socket) ----
|
||||||
|
// TL;DR: macOS doesn't really have a "device file" for TUN/TAP like Linux. Instead we have to request a "system control socket" from the kernel.
|
||||||
mFd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
|
mFd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
|
||||||
if (mFd < 0)
|
if (mFd < 0)
|
||||||
throw std::runtime_error("socket(PF_SYSTEM) failed: " + std::string(strerror(errno)));
|
throw std::runtime_error("socket(PF_SYSTEM) failed: " + std::string(strerror(errno)));
|
||||||
@@ -42,7 +43,7 @@ namespace ColumnLynx::Net {
|
|||||||
sc.sc_family = AF_SYSTEM;
|
sc.sc_family = AF_SYSTEM;
|
||||||
sc.ss_sysaddr = AF_SYS_CONTROL;
|
sc.ss_sysaddr = AF_SYS_CONTROL;
|
||||||
sc.sc_id = ctlInfo.ctl_id;
|
sc.sc_id = ctlInfo.ctl_id;
|
||||||
sc.sc_unit = 0; // lynx0 (0 = auto-assign)
|
sc.sc_unit = 0; // 0 = auto-assign next utunX
|
||||||
|
|
||||||
if (connect(mFd, (struct sockaddr*)&sc, sizeof(sc)) < 0) {
|
if (connect(mFd, (struct sockaddr*)&sc, sizeof(sc)) < 0) {
|
||||||
if (errno == EPERM)
|
if (errno == EPERM)
|
||||||
@@ -50,16 +51,17 @@ namespace ColumnLynx::Net {
|
|||||||
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)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve actual utun device name
|
// Retrieve actual utun device name via UTUN_OPT_IFNAME
|
||||||
struct sockaddr_storage addr;
|
char ifname[IFNAMSIZ];
|
||||||
socklen_t addrlen = sizeof(addr);
|
socklen_t ifname_len = sizeof(ifname);
|
||||||
if (getsockname(mFd, (struct sockaddr*)&addr, &addrlen) == 0) {
|
if (getsockopt(mFd, SYSPROTO_CONTROL, UTUN_OPT_IFNAME, ifname, &ifname_len) == 0) {
|
||||||
const struct sockaddr_ctl* addr_ctl = (const struct sockaddr_ctl*)&addr;
|
mIfName = ifname; // Update to actual assigned name
|
||||||
mIfName = "utun" + std::to_string(addr_ctl->sc_unit - 1);
|
|
||||||
} else {
|
} else {
|
||||||
mIfName = "utunX";
|
mIfName = "utun0"; // Fallback (should not happen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Utils::log("VirtualInterface: opened macOS UTUN: " + mIfName);
|
||||||
|
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
// ---- Windows: Wintun (WireGuard virtual adapter) ----
|
// ---- Windows: Wintun (WireGuard virtual adapter) ----
|
||||||
WINTUN_ADAPTER_HANDLE adapter =
|
WINTUN_ADAPTER_HANDLE adapter =
|
||||||
@@ -95,24 +97,73 @@ namespace ColumnLynx::Net {
|
|||||||
|
|
||||||
// ------------------------------ Read ------------------------------
|
// ------------------------------ Read ------------------------------
|
||||||
std::vector<uint8_t> VirtualInterface::readPacket() {
|
std::vector<uint8_t> VirtualInterface::readPacket() {
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
#if defined(__linux__)
|
||||||
|
|
||||||
|
// Linux TUN: blocking read is fine, unblocks on fd close / EINTR
|
||||||
std::vector<uint8_t> buf(4096);
|
std::vector<uint8_t> buf(4096);
|
||||||
ssize_t n = read(mFd, buf.data(), buf.size());
|
ssize_t n = read(mFd, buf.data(), buf.size());
|
||||||
if (n < 0) {
|
if (n < 0) {
|
||||||
if (errno == EINTR) {
|
if (errno == EINTR) {
|
||||||
return {}; // Interrupted, return empty
|
return {}; // Interrupted, just return empty
|
||||||
}
|
}
|
||||||
throw std::runtime_error("read() failed: " + std::string(strerror(errno)));
|
throw std::runtime_error("read() failed: " + std::string(strerror(errno)));
|
||||||
}
|
}
|
||||||
buf.resize(n);
|
buf.resize(n);
|
||||||
return buf;
|
return buf;
|
||||||
|
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
|
||||||
|
// macOS utun: must poll, or read() can block forever
|
||||||
|
std::vector<uint8_t> buf(4096);
|
||||||
|
|
||||||
|
struct pollfd pfd;
|
||||||
|
pfd.fd = mFd;
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
|
||||||
|
// timeout in ms; keep it small so shutdown is responsive
|
||||||
|
int ret = poll(&pfd, 1, 200);
|
||||||
|
|
||||||
|
if (ret == 0) {
|
||||||
|
// No data yet
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
return {}; // Interrupted by signal
|
||||||
|
}
|
||||||
|
throw std::runtime_error("poll() failed: " + std::string(strerror(errno)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(pfd.revents & POLLIN)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t n = read(mFd, buf.data(), buf.size());
|
||||||
|
if (n <= 0) {
|
||||||
|
// 0 or -1: treat as EOF or transient; you can decide how aggressive to be
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n > 4) {
|
||||||
|
// Drop macOS UTUN header (4 bytes)
|
||||||
|
std::memmove(buf.data(), buf.data() + 4, n - 4);
|
||||||
|
buf.resize(n - 4);
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
|
|
||||||
WINTUN_PACKET* packet = WintunReceivePacket(mSession, nullptr);
|
WINTUN_PACKET* packet = WintunReceivePacket(mSession, nullptr);
|
||||||
if (!packet) return {};
|
if (!packet) return {};
|
||||||
|
|
||||||
std::vector<uint8_t> buf(packet->Data, packet->Data + packet->Length);
|
std::vector<uint8_t> buf(packet->Data, packet->Data + packet->Length);
|
||||||
WintunReleaseReceivePacket(mSession, packet);
|
WintunReleaseReceivePacket(mSession, packet);
|
||||||
return buf;
|
return buf;
|
||||||
|
|
||||||
#else
|
#else
|
||||||
return {};
|
return {};
|
||||||
#endif
|
#endif
|
||||||
@@ -120,16 +171,48 @@ namespace ColumnLynx::Net {
|
|||||||
|
|
||||||
// ------------------------------ Write ------------------------------
|
// ------------------------------ Write ------------------------------
|
||||||
void VirtualInterface::writePacket(const std::vector<uint8_t>& packet) {
|
void VirtualInterface::writePacket(const std::vector<uint8_t>& packet) {
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
#if defined(__linux__)
|
||||||
|
|
||||||
|
// Linux TUN expects raw IP packet
|
||||||
ssize_t n = write(mFd, packet.data(), packet.size());
|
ssize_t n = write(mFd, packet.data(), packet.size());
|
||||||
if (n < 0)
|
if (n < 0)
|
||||||
throw std::runtime_error("write() failed: " + std::string(strerror(errno)));
|
throw std::runtime_error("write() failed: " + std::string(strerror(errno)));
|
||||||
|
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
|
||||||
|
if (packet.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Detect IPv4 or IPv6
|
||||||
|
uint8_t version = packet[0] >> 4;
|
||||||
|
uint32_t af;
|
||||||
|
|
||||||
|
if (version == 4) {
|
||||||
|
af = htonl(AF_INET);
|
||||||
|
} else if (version == 6) {
|
||||||
|
af = htonl(AF_INET6);
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("writePacket(): unknown IP version");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepend 4-byte AF header
|
||||||
|
std::vector<uint8_t> out(packet.size() + 4);
|
||||||
|
memcpy(out.data(), &af, 4);
|
||||||
|
memcpy(out.data() + 4, packet.data(), packet.size());
|
||||||
|
|
||||||
|
ssize_t n = write(mFd, out.data(), out.size());
|
||||||
|
if (n < 0)
|
||||||
|
throw std::runtime_error("utun write() failed: " + std::string(strerror(errno)));
|
||||||
|
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
|
|
||||||
WINTUN_PACKET* tx = WintunAllocateSendPacket(mSession, (DWORD)packet.size());
|
WINTUN_PACKET* tx = WintunAllocateSendPacket(mSession, (DWORD)packet.size());
|
||||||
if (!tx) throw std::runtime_error("WintunAllocateSendPacket failed");
|
if (!tx)
|
||||||
|
throw std::runtime_error("WintunAllocateSendPacket failed");
|
||||||
|
|
||||||
memcpy(tx->Data, packet.data(), packet.size());
|
memcpy(tx->Data, packet.data(), packet.size());
|
||||||
WintunSendPacket(mSession, tx);
|
WintunSendPacket(mSession, tx);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +249,13 @@ namespace ColumnLynx::Net {
|
|||||||
std::string ipStr = ipv4ToString(clientIP);
|
std::string ipStr = ipv4ToString(clientIP);
|
||||||
std::string peerStr = ipv4ToString(serverIP);
|
std::string peerStr = ipv4ToString(serverIP);
|
||||||
|
|
||||||
|
// Wipe the current config
|
||||||
|
snprintf(cmd, sizeof(cmd),
|
||||||
|
"ip addr flush dev %s",
|
||||||
|
mIfName.c_str()
|
||||||
|
);
|
||||||
|
system(cmd);
|
||||||
|
|
||||||
snprintf(cmd, sizeof(cmd),
|
snprintf(cmd, sizeof(cmd),
|
||||||
"ip addr add %s/%d peer %s dev %s",
|
"ip addr add %s/%d peer %s dev %s",
|
||||||
ipStr.c_str(), prefixLen, peerStr.c_str(), mIfName.c_str());
|
ipStr.c_str(), prefixLen, peerStr.c_str(), mIfName.c_str());
|
||||||
@@ -188,12 +278,26 @@ namespace ColumnLynx::Net {
|
|||||||
|
|
||||||
std::string ipStr = ipv4ToString(clientIP);
|
std::string ipStr = ipv4ToString(clientIP);
|
||||||
std::string peerStr = ipv4ToString(serverIP);
|
std::string peerStr = ipv4ToString(serverIP);
|
||||||
std::string prefixStr = ipv4ToString(prefixLen);
|
std::string prefixStr = ipv4ToString(prefixLengthToNetmask(prefixLen), false);
|
||||||
|
Utils::debug("Prefix string: " + prefixStr);
|
||||||
|
|
||||||
// Set netmask (/24 CIDR temporarily with raw command, improve later)
|
// Reset
|
||||||
snprintf(cmd, sizeof(cmd),
|
snprintf(cmd, sizeof(cmd),
|
||||||
"ifconfig lynx0 %s %s mtu %d netmask %s up",
|
"ifconfig %s inet 0.0.0.0 delete",
|
||||||
ipStr.c_str(), peerStr.c_str(), mtu, prefixStr.c_str());
|
mIfName.c_str()
|
||||||
|
);
|
||||||
|
system(cmd);
|
||||||
|
|
||||||
|
snprintf(cmd, sizeof(cmd),
|
||||||
|
"ifconfig %s inet6 :: delete",
|
||||||
|
mIfName.c_str()
|
||||||
|
);
|
||||||
|
system(cmd);
|
||||||
|
|
||||||
|
// Set
|
||||||
|
snprintf(cmd, sizeof(cmd),
|
||||||
|
"ifconfig %s inet %s %s mtu %d netmask %s up",
|
||||||
|
mIfName.c_str(), ipStr.c_str(), peerStr.c_str(), mtu, prefixStr.c_str());
|
||||||
system(cmd);
|
system(cmd);
|
||||||
|
|
||||||
Utils::log("Executed command: " + std::string(cmd));
|
Utils::log("Executed command: " + std::string(cmd));
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ int main(int argc, char** argv) {
|
|||||||
log("Using virtual interface: " + tun->getName());
|
log("Using virtual interface: " + tun->getName());
|
||||||
|
|
||||||
// Generate a temporary keypair, replace with actual CA signed keys later (Note, these are stored in memory)
|
// Generate a temporary keypair, replace with actual CA signed keys later (Note, these are stored in memory)
|
||||||
LibSodiumWrapper sodiumWrapper = LibSodiumWrapper();
|
std::shared_ptr<LibSodiumWrapper> sodiumWrapper = std::make_shared<LibSodiumWrapper>();
|
||||||
|
|
||||||
auto itPubkey = config.find("SERVER_PUBLIC_KEY");
|
auto itPubkey = config.find("SERVER_PUBLIC_KEY");
|
||||||
auto itPrivkey = config.find("SERVER_PRIVATE_KEY");
|
auto itPrivkey = config.find("SERVER_PRIVATE_KEY");
|
||||||
@@ -91,27 +91,26 @@ int main(int argc, char** argv) {
|
|||||||
std::copy_n(Utils::hexStringToBytes(itPrivkey->second).begin(), sk.size(), sk.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());
|
||||||
|
|
||||||
sodiumWrapper.setKeys(pk, sk);
|
sodiumWrapper->setKeys(pk, sk);
|
||||||
} else {
|
} else {
|
||||||
warn("No keypair found in config file! Using random key.");
|
warn("No keypair found in config file! Using random key.");
|
||||||
}
|
}
|
||||||
|
|
||||||
log("Server public key: " + bytesToHexString(sodiumWrapper.getPublicKey(), crypto_sign_PUBLICKEYBYTES));
|
log("Server public key: " + bytesToHexString(sodiumWrapper->getPublicKey(), crypto_sign_PUBLICKEYBYTES));
|
||||||
//log("Server private key: " + bytesToHexString(sodiumWrapper.getPrivateKey(), crypto_sign_SECRETKEYBYTES)); // TEMP, remove later
|
|
||||||
|
|
||||||
bool hostRunning = true;
|
std::shared_ptr<bool> hostRunning = std::make_shared<bool>(true);
|
||||||
|
|
||||||
asio::io_context io;
|
asio::io_context io;
|
||||||
|
|
||||||
auto server = std::make_shared<TCPServer>(io, serverPort(), &sodiumWrapper, &hostRunning, 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);
|
||||||
signals.async_wait([&](const std::error_code&, int) {
|
signals.async_wait([&](const std::error_code&, int) {
|
||||||
log("Received termination signal. Shutting down server gracefully.");
|
log("Received termination signal. Shutting down server gracefully.");
|
||||||
done = 1;
|
done = 1;
|
||||||
asio::post(io, [&]() {
|
asio::post(io, [&]() {
|
||||||
hostRunning = false;
|
*hostRunning = false;
|
||||||
server->stop();
|
server->stop();
|
||||||
udpServer->stop();
|
udpServer->stop();
|
||||||
});
|
});
|
||||||
@@ -145,9 +144,6 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log("Shutting down server...");
|
log("Shutting down server...");
|
||||||
/*hostRunning = false;
|
|
||||||
server->stop();
|
|
||||||
udpServer->stop();*/
|
|
||||||
|
|
||||||
io.stop();
|
io.stop();
|
||||||
if (ioThread.joinable()) {
|
if (ioThread.joinable()) {
|
||||||
|
|||||||
@@ -202,7 +202,18 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
// 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();
|
std::string networkString = mRawServerConfig->find("NETWORK")->second; // The load check guarantees that this value exists
|
||||||
|
uint8_t configMask = std::stoi(mRawServerConfig->find("SUBNET_MASK")->second); // Same deal here
|
||||||
|
|
||||||
|
uint32_t baseIP = Net::VirtualInterface::stringToIpv4(networkString);
|
||||||
|
|
||||||
|
if (baseIP == 0) {
|
||||||
|
Utils::warn("Your NETWORK value in the server configuration is malformed! I will not be able to accept connections! (Connection " + reqAddr + " was killed)");
|
||||||
|
disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t clientIP = SessionRegistry::getInstance().getFirstAvailableIP(baseIP, configMask);
|
||||||
|
|
||||||
if (clientIP == 0) {
|
if (clientIP == 0) {
|
||||||
Utils::warn("Out of available IPs! Disconnecting client " + reqAddr);
|
Utils::warn("Out of available IPs! Disconnecting client " + reqAddr);
|
||||||
@@ -214,13 +225,11 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
tunConfig.version = Utils::protocolVersion();
|
tunConfig.version = Utils::protocolVersion();
|
||||||
tunConfig.prefixLength = 24;
|
tunConfig.prefixLength = 24;
|
||||||
tunConfig.mtu = 1420;
|
tunConfig.mtu = 1420;
|
||||||
tunConfig.serverIP = htonl(0x0A0A0001); // 10.10.0.1
|
tunConfig.serverIP = htonl(baseIP + 1); // e.g. 10.10.0.1
|
||||||
tunConfig.clientIP = htonl(clientIP); // 10.10.0.X
|
tunConfig.clientIP = htonl(clientIP); // e.g. 10.10.0.X
|
||||||
tunConfig.dns1 = htonl(0x08080808); // 8.8.8.8
|
tunConfig.dns1 = htonl(0x08080808); // 8.8.8.8
|
||||||
tunConfig.dns2 = 0;
|
tunConfig.dns2 = 0;
|
||||||
|
|
||||||
SessionRegistry::getInstance().lockIP(mConnectionSessionID, clientIP);
|
|
||||||
|
|
||||||
uint64_t sessionIDNet = Utils::chtobe64(mConnectionSessionID);
|
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));
|
||||||
@@ -238,6 +247,7 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
Utils::log("Handshake with " + reqAddr + " completed successfully. Session ID assigned (" + std::to_string(mConnectionSessionID) + ").");
|
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));
|
||||||
|
SessionRegistry::getInstance().lockIP(mConnectionSessionID, clientIP);
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
Utils::error("Failed to decrypt HANDSHAKE_EXCHANGE_KEY from " + reqAddr + ": " + e.what());
|
Utils::error("Failed to decrypt HANDSHAKE_EXCHANGE_KEY from " + reqAddr + ": " + e.what());
|
||||||
@@ -31,10 +31,11 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
mStartAccept();
|
mStartAccept();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto client = TCPConnection::create(
|
auto client = TCPConnection::create(
|
||||||
std::move(socket),
|
std::move(socket),
|
||||||
mSodiumWrapper,
|
mSodiumWrapper,
|
||||||
|
&mRawServerConfig,
|
||||||
[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.");
|
||||||
Reference in New Issue
Block a user