Compare commits
1 Commits
auth_test
...
d20bee9e60
| Author | SHA1 | Date | |
|---|---|---|---|
| d20bee9e60 |
@@ -15,11 +15,3 @@ This project uses the standalone version of the ASIO C++ library for asynchronou
|
|||||||
- **Copyright:** (c) 2014-2025 Christopher M. Kohlhoff
|
- **Copyright:** (c) 2014-2025 Christopher M. Kohlhoff
|
||||||
- **License:** MIT License
|
- **License:** MIT License
|
||||||
- **License Text:** See `third_party/cxxopts/LICENSE_1_0.txt`
|
- **License Text:** See `third_party/cxxopts/LICENSE_1_0.txt`
|
||||||
|
|
||||||
## Wintun C++ Library
|
|
||||||
- **Name:** wintun
|
|
||||||
- **Website:** https://www.wintun.net/
|
|
||||||
- **Copyright:** (c) 2018-2025 WireGuard LLC
|
|
||||||
- **License:** MIT License OR GPL-2.0 License
|
|
||||||
- **License Text:** See `third_party/wintun/`
|
|
||||||
- **Utilized Under:** MIT License
|
|
||||||
|
|||||||
@@ -6,20 +6,16 @@ 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.0.1
|
||||||
LANGUAGES CXX
|
LANGUAGES CXX
|
||||||
)
|
)
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# General C++ setup
|
# General C++ setup
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
set(CMAKE_CXX_STANDARD 23)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
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)
|
||||||
|
|
||||||
@@ -29,7 +25,7 @@ include(FetchContent)
|
|||||||
if(APPLE)
|
if(APPLE)
|
||||||
# Build universal (arm64 + x86_64), or limit to one arch if you prefer
|
# Build universal (arm64 + x86_64), or limit to one arch if you prefer
|
||||||
# e.g., set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "" FORCE)
|
# e.g., set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "" FORCE)
|
||||||
set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "Build architectures" FORCE)
|
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "Build architectures" FORCE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
@@ -62,15 +58,6 @@ set(SODIUM_CMAKE_ARGS
|
|||||||
|
|
||||||
FetchContent_MakeAvailable(Sodium)
|
FetchContent_MakeAvailable(Sodium)
|
||||||
|
|
||||||
# OpenSSL
|
|
||||||
find_package(OpenSSL REQUIRED)
|
|
||||||
if(OPENSSL_FOUND)
|
|
||||||
message(STATUS "Found OpenSSL version ${OPENSSL_VERSION}")
|
|
||||||
include_directories(${OPENSSL_INCLUDE_DIR})
|
|
||||||
else()
|
|
||||||
message(FATAL_ERROR "OpenSSL not found")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# Output directories
|
# Output directories
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
@@ -90,7 +77,7 @@ endforeach()
|
|||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
file(GLOB_RECURSE COMMON_SRC CONFIGURE_DEPENDS src/common/*.cpp)
|
file(GLOB_RECURSE COMMON_SRC CONFIGURE_DEPENDS src/common/*.cpp)
|
||||||
add_library(common STATIC ${COMMON_SRC})
|
add_library(common STATIC ${COMMON_SRC})
|
||||||
target_link_libraries(common PUBLIC sodium OpenSSL::SSL OpenSSL::Crypto)
|
target_link_libraries(common PUBLIC sodium)
|
||||||
target_include_directories(common PUBLIC
|
target_include_directories(common PUBLIC
|
||||||
${PROJECT_SOURCE_DIR}/include
|
${PROJECT_SOURCE_DIR}/include
|
||||||
${sodium_SOURCE_DIR}/src/libsodium/include
|
${sodium_SOURCE_DIR}/src/libsodium/include
|
||||||
@@ -103,7 +90,7 @@ target_compile_definitions(common PUBLIC ASIO_STANDALONE)
|
|||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
file(GLOB_RECURSE CLIENT_SRC CONFIGURE_DEPENDS src/client/*.cpp)
|
file(GLOB_RECURSE CLIENT_SRC CONFIGURE_DEPENDS src/client/*.cpp)
|
||||||
add_executable(client ${CLIENT_SRC})
|
add_executable(client ${CLIENT_SRC})
|
||||||
target_link_libraries(client PRIVATE common sodium OpenSSL::SSL OpenSSL::Crypto)
|
target_link_libraries(client PRIVATE common sodium)
|
||||||
target_include_directories(client PRIVATE
|
target_include_directories(client PRIVATE
|
||||||
${PROJECT_SOURCE_DIR}/include
|
${PROJECT_SOURCE_DIR}/include
|
||||||
${sodium_SOURCE_DIR}/src/libsodium/include
|
${sodium_SOURCE_DIR}/src/libsodium/include
|
||||||
@@ -117,7 +104,7 @@ set_target_properties(client PROPERTIES OUTPUT_NAME "columnlynx_client")
|
|||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
file(GLOB_RECURSE SERVER_SRC CONFIGURE_DEPENDS src/server/*.cpp)
|
file(GLOB_RECURSE SERVER_SRC CONFIGURE_DEPENDS src/server/*.cpp)
|
||||||
add_executable(server ${SERVER_SRC})
|
add_executable(server ${SERVER_SRC})
|
||||||
target_link_libraries(server PRIVATE common sodium OpenSSL::SSL OpenSSL::Crypto)
|
target_link_libraries(server PRIVATE common sodium)
|
||||||
target_include_directories(server PRIVATE
|
target_include_directories(server PRIVATE
|
||||||
${PROJECT_SOURCE_DIR}/include
|
${PROJECT_SOURCE_DIR}/include
|
||||||
${sodium_SOURCE_DIR}/src/libsodium/include
|
${sodium_SOURCE_DIR}/src/libsodium/include
|
||||||
|
|||||||
47
README.md
47
README.md
@@ -1,20 +1,6 @@
|
|||||||
# ColumnLynx
|
# ColumnLynx
|
||||||
|
|
||||||
## What is it?
|
ColumnLynx is a VPN protocol designed to be as lightweight as possible.
|
||||||
|
|
||||||
ColumnLynx is a VPN protocol designed to be as lightweight and simple to understand as possible.
|
|
||||||
|
|
||||||
### Origin
|
|
||||||
|
|
||||||
The original goal of this project was for me to learn about the inner-workings of VPN protocols, but overtime, it has transformed into the goal seen above.
|
|
||||||
|
|
||||||
### Design Philosophy
|
|
||||||
|
|
||||||
A VPN (Virtual Private Network), in the most basic terms, is a protocol that tunnels network traffic from a client to a server over an encrypted tunnel and having the server send that traffic on its behalf. It can be catagorized into sitting somewhere in-between the 3rd and 4th layers of the ISO/OSI model.
|
|
||||||
|
|
||||||
This project aims to be just that, an encrypted tunneling protocol that works on the 3rd and 4th layers of the ISO/OSI model, nothing more, nothing less. We leave complex functions like compression, to the higher layers (though it could be argued that making an encrypted tunnel already pushes us up to Layer 6).
|
|
||||||
|
|
||||||
This simplicity-focused design approach allows us to make an efficient, low-overhead VPN protocol and minimize any potential attack surface.
|
|
||||||
|
|
||||||
## How does it work
|
## How does it work
|
||||||
|
|
||||||
@@ -108,27 +94,9 @@ The **Data** is generally just the **raw underlying packet** forwarded to the se
|
|||||||
| uint64_t | 8 bytes | **Header** - Session ID | The unique and random session identifier for the client |
|
| uint64_t | 8 bytes | **Header** - Session ID | The unique and random session identifier for the client |
|
||||||
| uint8_t | variable | Data | General data / payload |
|
| uint8_t | variable | Data | General data / payload |
|
||||||
|
|
||||||
## Misc.
|
|
||||||
Building the binary for Windows requires the Wintun DLL. The include header is pre-packaged.
|
|
||||||
|
|
||||||
## Legal
|
## Legal
|
||||||
|
|
||||||
### Copyright ownership:
|
Copyright (C) 2025 Jonas Korene Novak
|
||||||
Unless explicitly stated otherwise, all source code and material contained in this project
|
|
||||||
is the copyright of their respective authors, as identified in (but not limited to)
|
|
||||||
the project's version control history (e.g., Git commit authorship).
|
|
||||||
|
|
||||||
Each contribution is provided under the terms of the GNU General Public License,
|
|
||||||
version 2 or (at your option) any later version, as published by the Free Software Foundation,
|
|
||||||
unless an individual file or component specifies a different license.
|
|
||||||
|
|
||||||
No contributor or maintainer claims exclusive ownership of the entire project.
|
|
||||||
All rights are retained by their respective authors.
|
|
||||||
|
|
||||||
By submitting a contribution, you agree that it will be licensed under the
|
|
||||||
same dual GPL terms as the project as a whole.
|
|
||||||
|
|
||||||
### Licensing:
|
|
||||||
|
|
||||||
This project is **dual-licensed** under the GNU General Public License (GPL):
|
This project is **dual-licensed** under the GNU General Public License (GPL):
|
||||||
|
|
||||||
@@ -152,12 +120,11 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
This project includes the [ASIO C++ Library](https://think-async.com/Asio/),
|
DcruBro is the online pseudonym of Jonas Korene Novak. Both refer to the same individual and may be used interchangeably for copyright attribution purposes.
|
||||||
distributed under the [Boost Software License, Version 1.0](https://www.boost.org/LICENSE_1_0.txt).
|
|
||||||
|
|
||||||
This project includes the CXXOPTS Library
|
### Licensing
|
||||||
distributed under the MIT License
|
|
||||||
|
|
||||||
This project includes the [Wintun Library](https://www.wintun.net/), distributed under the MIT License or the GPL-2.0 License. This project utilizes it under the MIT license.
|
|
||||||
|
|
||||||
*See **ATTRIBUTIONS.md** for details.*
|
*See **ATTRIBUTIONS.md** for details.*
|
||||||
|
|
||||||
|
This project includes the [ASIO C++ Library](https://think-async.com/Asio/),
|
||||||
|
distributed under the [Boost Software License, Version 1.0](https://www.boost.org/LICENSE_1_0.txt).
|
||||||
@@ -13,9 +13,6 @@
|
|||||||
#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/virtual_interface.hpp>
|
|
||||||
|
|
||||||
using asio::ip::tcp;
|
using asio::ip::tcp;
|
||||||
|
|
||||||
@@ -27,9 +24,7 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
const std::string& port,
|
const std::string& port,
|
||||||
Utils::LibSodiumWrapper* sodiumWrapper,
|
Utils::LibSodiumWrapper* sodiumWrapper,
|
||||||
std::array<uint8_t, 32>* aesKey,
|
std::array<uint8_t, 32>* aesKey,
|
||||||
uint64_t* sessionIDRef,
|
uint64_t* sessionIDRef)
|
||||||
bool* insecureMode,
|
|
||||||
std::shared_ptr<VirtualInterface> tun = nullptr)
|
|
||||||
:
|
:
|
||||||
mResolver(ioContext),
|
mResolver(ioContext),
|
||||||
mSocket(ioContext),
|
mSocket(ioContext),
|
||||||
@@ -38,50 +33,242 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
mLibSodiumWrapper(sodiumWrapper),
|
mLibSodiumWrapper(sodiumWrapper),
|
||||||
mGlobalKeyRef(aesKey),
|
mGlobalKeyRef(aesKey),
|
||||||
mSessionIDRef(sessionIDRef),
|
mSessionIDRef(sessionIDRef),
|
||||||
mInsecureMode(insecureMode),
|
|
||||||
mHeartbeatTimer(mSocket.get_executor()),
|
mHeartbeatTimer(mSocket.get_executor()),
|
||||||
mLastHeartbeatReceived(std::chrono::steady_clock::now()),
|
mLastHeartbeatReceived(std::chrono::steady_clock::now()),
|
||||||
mLastHeartbeatSent(std::chrono::steady_clock::now()),
|
mLastHeartbeatSent(std::chrono::steady_clock::now())
|
||||||
mTun(tun)
|
{}
|
||||||
{
|
|
||||||
// Preload the config map
|
|
||||||
mRawClientConfig = Utils::getConfigMap("client_config");
|
|
||||||
|
|
||||||
if (!mRawClientConfig.empty()) {
|
void start() {
|
||||||
Utils::debug("Loading the keys");
|
auto self = shared_from_this();
|
||||||
|
mResolver.async_resolve(mHost, mPort,
|
||||||
|
[this, self](asio::error_code ec, tcp::resolver::results_type endpoints) {
|
||||||
|
if (!ec) {
|
||||||
|
asio::async_connect(mSocket, endpoints,
|
||||||
|
[this, self](asio::error_code ec, const tcp::endpoint&) {
|
||||||
|
if (!NetHelper::isExpectedDisconnect(ec)) {
|
||||||
|
mConnected = true;
|
||||||
|
Utils::log("Client connected.");
|
||||||
|
mHandler = std::make_shared<MessageHandler>(std::move(mSocket));
|
||||||
|
mHandler->onMessage([this](AnyMessageType type, const std::string& data) {
|
||||||
|
mHandleMessage(static_cast<ServerMessageType>(MessageHandler::toUint8(type)), data);
|
||||||
|
});
|
||||||
|
mHandler->start();
|
||||||
|
|
||||||
PrivateKey sk;
|
// Init connection handshake
|
||||||
PublicKey pk;
|
Utils::log("Sending handshake init to server.");
|
||||||
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);
|
std::vector<uint8_t> payload;
|
||||||
|
payload.reserve(1 + crypto_box_PUBLICKEYBYTES);
|
||||||
|
payload.push_back(Utils::protocolVersion());
|
||||||
|
payload.insert(payload.end(),
|
||||||
|
mLibSodiumWrapper->getXPublicKey(),
|
||||||
|
mLibSodiumWrapper->getXPublicKey() + crypto_box_PUBLICKEYBYTES
|
||||||
|
);
|
||||||
|
|
||||||
Utils::debug("Newly-Loaded Public Key: " + Utils::bytesToHexString(mLibSodiumWrapper->getPublicKey(), 32));
|
mHandler->sendMessage(ClientMessageType::HANDSHAKE_INIT, Utils::uint8ArrayToString(payload.data(), payload.size()));
|
||||||
Utils::debug("Newly-Loaded Private Key: " + Utils::bytesToHexString(mLibSodiumWrapper->getPrivateKey(), 64));
|
|
||||||
Utils::debug("Public Encryption Key: " + Utils::bytesToHexString(mLibSodiumWrapper->getXPublicKey(), 32));
|
mStartHeartbeat();
|
||||||
|
} else {
|
||||||
|
Utils::error("Client connect failed: " + ec.message());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Utils::error("Client resolve failed: " + ec.message());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendMessage(ClientMessageType type, const std::string& data = "") {
|
||||||
|
if (!mConnected) {
|
||||||
|
Utils::error("Cannot send message, client not connected.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mHandler) {
|
||||||
|
asio::post(mHandler->socket().get_executor(), [self = shared_from_this(), type, data]() {
|
||||||
|
self->mHandler->sendMessage(type, data);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts the TCP Client and initiaties the handshake
|
void disconnect(bool echo = true) {
|
||||||
void start();
|
if (mConnected && mHandler) {
|
||||||
// Sends a TCP message to the server
|
if (echo) {
|
||||||
void sendMessage(ClientMessageType type, const std::string& data = "");
|
mHandler->sendMessage(ClientMessageType::GRACEFUL_DISCONNECT, "Goodbye");
|
||||||
// Attempt to gracefully disconnect from the server
|
}
|
||||||
void disconnect(bool echo = true);
|
|
||||||
|
|
||||||
// Get the handshake status
|
asio::error_code ec;
|
||||||
bool isHandshakeComplete() const;
|
mHeartbeatTimer.cancel();
|
||||||
// Get the connection status
|
|
||||||
bool isConnected() const;
|
mHandler->socket().shutdown(tcp::socket::shutdown_both, ec);
|
||||||
|
if (ec) {
|
||||||
|
Utils::error("Error during socket shutdown: " + ec.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
mHandler->socket().close(ec);
|
||||||
|
if (ec) {
|
||||||
|
Utils::error("Error during socket close: " + ec.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
mConnected = false;
|
||||||
|
Utils::log("Client disconnected.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isHandshakeComplete() const {
|
||||||
|
return mHandshakeComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isConnected() const {
|
||||||
|
return mConnected;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Start the heartbeat routine
|
void mStartHeartbeat() {
|
||||||
void mStartHeartbeat();
|
auto self = shared_from_this();
|
||||||
// Handle an incoming TCP message
|
mHeartbeatTimer.expires_after(std::chrono::seconds(5));
|
||||||
void mHandleMessage(ServerMessageType type, const std::string& data);
|
mHeartbeatTimer.async_wait([this, self](const asio::error_code& ec) {
|
||||||
|
if (ec == asio::error::operation_aborted) {
|
||||||
|
return; // Timer was cancelled
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Move ptrs to smart ptrs
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - self->mLastHeartbeatReceived).count();
|
||||||
|
|
||||||
|
if (elapsed >= 15) { // 3 missed heartbeats
|
||||||
|
Utils::error("Missed 3 heartbeats. I think the other party might have died! Disconnecting.");
|
||||||
|
|
||||||
|
// Close sockets forcefully, server is dead
|
||||||
|
asio::error_code ec;
|
||||||
|
mHandler->socket().shutdown(tcp::socket::shutdown_both, ec);
|
||||||
|
mHandler->socket().close(ec);
|
||||||
|
mConnected = false;
|
||||||
|
|
||||||
|
mGlobalKeyRef = nullptr;
|
||||||
|
if (mSessionIDRef) {
|
||||||
|
*mSessionIDRef = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->sendMessage(ClientMessageType::HEARTBEAT);
|
||||||
|
Utils::log("Sent HEARTBEAT to server.");
|
||||||
|
self->mLastHeartbeatSent = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
self->mStartHeartbeat(); // Recursive
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void mHandleMessage(ServerMessageType type, const std::string& data) {
|
||||||
|
switch (type) {
|
||||||
|
case ServerMessageType::HANDSHAKE_IDENTIFY:
|
||||||
|
Utils::log("Received server identity: " + data);
|
||||||
|
std::memcpy(mServerPublicKey, data.data(), std::min(data.size(), sizeof(mServerPublicKey)));
|
||||||
|
|
||||||
|
// Generate and send challenge
|
||||||
|
{
|
||||||
|
Utils::log("Sending challenge to server.");
|
||||||
|
mSubmittedChallenge = Utils::LibSodiumWrapper::generateRandom256Bit(); // Temporarily store the challenge to verify later
|
||||||
|
mHandler->sendMessage(ClientMessageType::HANDSHAKE_CHALLENGE, Utils::uint8ArrayToString(mSubmittedChallenge));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ServerMessageType::HANDSHAKE_CHALLENGE_RESPONSE:
|
||||||
|
Utils::log("Received challenge response from server.");
|
||||||
|
{
|
||||||
|
// Verify the signature
|
||||||
|
Signature sig{};
|
||||||
|
std::memcpy(sig.data(), data.data(), std::min(data.size(), sig.size()));
|
||||||
|
if (Utils::LibSodiumWrapper::verifyMessage(mSubmittedChallenge.data(), mSubmittedChallenge.size(), sig, mServerPublicKey)) {
|
||||||
|
Utils::log("Challenge response verified successfully.");
|
||||||
|
|
||||||
|
// Convert the server's public key to Curve25519 for encryption
|
||||||
|
AsymPublicKey serverXPubKey{};
|
||||||
|
crypto_sign_ed25519_pk_to_curve25519(serverXPubKey.data(), mServerPublicKey);
|
||||||
|
|
||||||
|
// Generate AES key and send confirmation
|
||||||
|
mConnectionAESKey = Utils::LibSodiumWrapper::generateRandom256Bit();
|
||||||
|
if (mGlobalKeyRef) { // Copy to the global reference
|
||||||
|
std::copy(mConnectionAESKey.begin(), mConnectionAESKey.end(), mGlobalKeyRef->begin());
|
||||||
|
}
|
||||||
|
AsymNonce nonce{};
|
||||||
|
randombytes_buf(nonce.data(), nonce.size());
|
||||||
|
|
||||||
|
// TODO: This is pretty redundant, it should return the required type directly
|
||||||
|
std::array<uint8_t, 32> arrayPrivateKey;
|
||||||
|
std::copy(mLibSodiumWrapper->getXPrivateKey(),
|
||||||
|
mLibSodiumWrapper->getXPrivateKey() + 32,
|
||||||
|
arrayPrivateKey.begin());
|
||||||
|
|
||||||
|
std::vector<uint8_t> encr = Utils::LibSodiumWrapper::encryptAsymmetric(
|
||||||
|
mConnectionAESKey.data(), mConnectionAESKey.size(),
|
||||||
|
nonce,
|
||||||
|
serverXPubKey,
|
||||||
|
arrayPrivateKey
|
||||||
|
);
|
||||||
|
|
||||||
|
std::vector<uint8_t> payload;
|
||||||
|
payload.reserve(nonce.size() + encr.size());
|
||||||
|
payload.insert(payload.end(), nonce.begin(), nonce.end());
|
||||||
|
payload.insert(payload.end(), encr.begin(), encr.end());
|
||||||
|
|
||||||
|
mHandler->sendMessage(ClientMessageType::HANDSHAKE_EXCHANGE_KEY, Utils::uint8ArrayToString(payload.data(), payload.size()));
|
||||||
|
} else {
|
||||||
|
Utils::error("Challenge response verification failed. Terminating connection.");
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ServerMessageType::HANDSHAKE_EXCHANGE_KEY_CONFIRM:
|
||||||
|
Utils::log("Received handshake exchange key confirmation from server.");
|
||||||
|
// Decrypt the session ID using the established AES key
|
||||||
|
{
|
||||||
|
Nonce symNonce{}; // All zeros
|
||||||
|
std::vector<uint8_t> ciphertext(data.begin(), data.end());
|
||||||
|
std::vector<uint8_t> decrypted = Utils::LibSodiumWrapper::decryptMessage(
|
||||||
|
ciphertext.data(), ciphertext.size(),
|
||||||
|
mConnectionAESKey, symNonce
|
||||||
|
);
|
||||||
|
|
||||||
|
if (decrypted.size() != sizeof(mConnectionSessionID)) {
|
||||||
|
Utils::error("Decrypted session ID has invalid size. Terminating connection.");
|
||||||
|
disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memcpy(&mConnectionSessionID, decrypted.data(), sizeof(mConnectionSessionID));
|
||||||
|
Utils::log("Connection established with Session ID: " + std::to_string(mConnectionSessionID));
|
||||||
|
|
||||||
|
if (mSessionIDRef) { // Copy to the global reference
|
||||||
|
*mSessionIDRef = mConnectionSessionID;
|
||||||
|
}
|
||||||
|
|
||||||
|
mHandshakeComplete = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ServerMessageType::HEARTBEAT:
|
||||||
|
Utils::log("Received HEARTBEAT from server.");
|
||||||
|
mHandler->sendMessage(ClientMessageType::HEARTBEAT_ACK, ""); // Send ACK
|
||||||
|
break;
|
||||||
|
case ServerMessageType::HEARTBEAT_ACK:
|
||||||
|
Utils::log("Received HEARTBEAT_ACK from server.");
|
||||||
|
mLastHeartbeatReceived = std::chrono::steady_clock::now();
|
||||||
|
mMissedHeartbeats = 0; // Reset missed heartbeat count
|
||||||
|
break;
|
||||||
|
case ServerMessageType::GRACEFUL_DISCONNECT:
|
||||||
|
Utils::log("Server is disconnecting: " + data);
|
||||||
|
if (mConnected) { // Prevent Recursion
|
||||||
|
disconnect(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Utils::log("Received unknown message type from server.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool mConnected = false;
|
bool mConnected = false;
|
||||||
bool mHandshakeComplete = false;
|
bool mHandshakeComplete = false;
|
||||||
@@ -96,14 +283,9 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
SymmetricKey mConnectionAESKey;
|
SymmetricKey mConnectionAESKey;
|
||||||
std::array<uint8_t, 32>* mGlobalKeyRef; // Reference to global AES key
|
std::array<uint8_t, 32>* mGlobalKeyRef; // Reference to global AES key
|
||||||
uint64_t* mSessionIDRef; // Reference to global Session ID
|
uint64_t* mSessionIDRef; // Reference to global Session ID
|
||||||
bool* mInsecureMode; // Reference to 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;
|
||||||
int mMissedHeartbeats = 0;
|
int mMissedHeartbeats = 0;
|
||||||
bool mIsHostDomain;
|
|
||||||
Protocol::TunConfig mTunConfig;
|
|
||||||
std::shared_ptr<VirtualInterface> mTun = nullptr;
|
|
||||||
std::unordered_map<std::string, std::string> mRawClientConfig;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
#include <columnlynx/common/utils.hpp>
|
#include <columnlynx/common/utils.hpp>
|
||||||
#include <columnlynx/common/libsodium_wrapper.hpp>
|
#include <columnlynx/common/libsodium_wrapper.hpp>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <columnlynx/common/net/virtual_interface.hpp>
|
|
||||||
|
|
||||||
namespace ColumnLynx::Net::UDP {
|
namespace ColumnLynx::Net::UDP {
|
||||||
class UDPClient {
|
class UDPClient {
|
||||||
@@ -18,25 +17,113 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
const std::string& host,
|
const std::string& host,
|
||||||
const std::string& port,
|
const std::string& port,
|
||||||
std::array<uint8_t, 32>* aesKeyRef,
|
std::array<uint8_t, 32>* aesKeyRef,
|
||||||
uint64_t* sessionIDRef,
|
uint64_t* sessionIDRef)
|
||||||
std::shared_ptr<VirtualInterface> tunRef = nullptr)
|
: mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port), mAesKeyRef(aesKeyRef), mSessionIDRef(sessionIDRef) { mStartReceive(); }
|
||||||
: mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port), mAesKeyRef(aesKeyRef), mSessionIDRef(sessionIDRef), mTunRef(tunRef)
|
|
||||||
{
|
void start() {
|
||||||
mStartReceive();
|
auto endpoints = mResolver.resolve(asio::ip::udp::v4(), mHost, mPort);
|
||||||
|
mRemoteEndpoint = *endpoints.begin();
|
||||||
|
mSocket.open(asio::ip::udp::v4());
|
||||||
|
Utils::log("UDP Client ready to send to " + mRemoteEndpoint.address().to_string() + ":" + std::to_string(mRemoteEndpoint.port()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the UDP client
|
void sendMessage(const std::string& data = "") {
|
||||||
void start();
|
UDPPacketHeader hdr{};
|
||||||
// Send a UDP message
|
randombytes_buf(hdr.nonce.data(), hdr.nonce.size());
|
||||||
void sendMessage(const std::string& data = "");
|
|
||||||
// Stop the UDP client
|
if (mAesKeyRef == nullptr || mSessionIDRef == nullptr) {
|
||||||
void stop();
|
Utils::error("UDP Client AES key or Session ID reference is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto encryptedPayload = Utils::LibSodiumWrapper::encryptMessage(
|
||||||
|
reinterpret_cast<const uint8_t*>(data.data()), data.size(),
|
||||||
|
*mAesKeyRef, hdr.nonce, "udp-data"
|
||||||
|
);
|
||||||
|
|
||||||
|
std::vector<uint8_t> packet;
|
||||||
|
packet.reserve(sizeof(UDPPacketHeader) + sizeof(uint64_t) + encryptedPayload.size());
|
||||||
|
packet.insert(packet.end(),
|
||||||
|
reinterpret_cast<uint8_t*>(&hdr),
|
||||||
|
reinterpret_cast<uint8_t*>(&hdr) + sizeof(UDPPacketHeader)
|
||||||
|
);
|
||||||
|
uint64_t sid = *mSessionIDRef;
|
||||||
|
packet.insert(packet.end(),
|
||||||
|
reinterpret_cast<uint8_t*>(&sid),
|
||||||
|
reinterpret_cast<uint8_t*>(&sid) + sizeof(sid)
|
||||||
|
);
|
||||||
|
packet.insert(packet.end(), encryptedPayload.begin(), encryptedPayload.end());
|
||||||
|
|
||||||
|
mSocket.send_to(asio::buffer(packet), mRemoteEndpoint);
|
||||||
|
Utils::log("Sent UDP packet of size " + std::to_string(packet.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
if (mSocket.is_open()) {
|
||||||
|
asio::error_code ec;
|
||||||
|
mSocket.cancel(ec);
|
||||||
|
mSocket.close(ec);
|
||||||
|
Utils::log("UDP Client socket closed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Start the UDP listener routine
|
void mStartReceive() {
|
||||||
void mStartReceive();
|
mSocket.async_receive_from(
|
||||||
// Handle an incoming UDP message
|
asio::buffer(mRecvBuffer), mRemoteEndpoint,
|
||||||
void mHandlePacket(std::size_t bytes);
|
[this](asio::error_code ec, std::size_t bytes) {
|
||||||
|
if (ec) {
|
||||||
|
if (ec == asio::error::operation_aborted) return; // Socket closed
|
||||||
|
// Other recv error
|
||||||
|
mStartReceive();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes > 0) {
|
||||||
|
mHandlePacket(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
mStartReceive();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mHandlePacket(std::size_t bytes) {
|
||||||
|
if (bytes < sizeof(UDPPacketHeader) + sizeof(uint64_t)) {
|
||||||
|
Utils::warn("UDP Client received packet too small to process.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse header
|
||||||
|
UDPPacketHeader hdr;
|
||||||
|
std::memcpy(&hdr, mRecvBuffer.data(), sizeof(UDPPacketHeader));
|
||||||
|
|
||||||
|
// Parse session ID
|
||||||
|
uint64_t sessionID;
|
||||||
|
std::memcpy(&sessionID, mRecvBuffer.data() + sizeof(UDPPacketHeader), sizeof(uint64_t));
|
||||||
|
|
||||||
|
// Decrypt payload
|
||||||
|
std::vector<uint8_t> ciphertext(
|
||||||
|
mRecvBuffer.begin() + sizeof(UDPPacketHeader) + sizeof(uint64_t),
|
||||||
|
mRecvBuffer.begin() + bytes
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mAesKeyRef == nullptr) {
|
||||||
|
Utils::error("UDP Client AES key reference is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> plaintext = Utils::LibSodiumWrapper::decryptMessage(
|
||||||
|
ciphertext.data(), ciphertext.size(), *mAesKeyRef, hdr.nonce, "udp-data"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (plaintext.empty()) {
|
||||||
|
Utils::warn("UDP Client failed to decrypt received packet.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::log("UDP Client received packet from " + mRemoteEndpoint.address().to_string() + " - Packet size: " + std::to_string(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
asio::ip::udp::socket mSocket;
|
asio::ip::udp::socket mSocket;
|
||||||
asio::ip::udp::resolver mResolver;
|
asio::ip::udp::resolver mResolver;
|
||||||
@@ -45,7 +132,6 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
std::string mPort;
|
std::string mPort;
|
||||||
std::array<uint8_t, 32>* mAesKeyRef;
|
std::array<uint8_t, 32>* mAesKeyRef;
|
||||||
uint64_t* mSessionIDRef;
|
uint64_t* mSessionIDRef;
|
||||||
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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -11,12 +11,6 @@
|
|||||||
#include <columnlynx/common/utils.hpp>
|
#include <columnlynx/common/utils.hpp>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <openssl/x509.h>
|
|
||||||
#include <openssl/x509_vfy.h>
|
|
||||||
#include <openssl/pem.h>
|
|
||||||
#include <openssl/x509v3.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace ColumnLynx {
|
namespace ColumnLynx {
|
||||||
using PublicKey = std::array<uint8_t, crypto_sign_PUBLICKEYBYTES>; // Ed25519
|
using PublicKey = std::array<uint8_t, crypto_sign_PUBLICKEYBYTES>; // Ed25519
|
||||||
@@ -35,35 +29,27 @@ 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);
|
||||||
@@ -80,11 +66,6 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -99,7 +80,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 symmetrically with ChaCha20-Poly1305; returns ciphertext as bytes
|
// Encrypt 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,
|
||||||
@@ -122,7 +103,7 @@ namespace ColumnLynx::Utils {
|
|||||||
return ciphertext;
|
return ciphertext;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt symmetrically with ChaCha20-Poly1305; Returns plaintext as bytes
|
// Decrypt 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,
|
||||||
@@ -148,14 +129,12 @@ 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,
|
||||||
@@ -176,7 +155,6 @@ 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,
|
||||||
@@ -200,100 +178,6 @@ 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) {
|
|
||||||
// Parse DER-encoded certificate
|
|
||||||
const unsigned char* p = cert_der.data();
|
|
||||||
std::unique_ptr<X509, decltype(&X509_free)> cert(
|
|
||||||
d2i_X509(nullptr, &p, cert_der.size()), X509_free
|
|
||||||
);
|
|
||||||
if (!cert) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a certificate store
|
|
||||||
std::unique_ptr<X509_STORE, decltype(&X509_STORE_free)> store(
|
|
||||||
X509_STORE_new(), X509_STORE_free
|
|
||||||
);
|
|
||||||
if (!store) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load system default CA paths (/etc/ssl/certs, etc.)
|
|
||||||
if (X509_STORE_set_default_paths(store.get()) != 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a verification context
|
|
||||||
std::unique_ptr<X509_STORE_CTX, decltype(&X509_STORE_CTX_free)> ctx(
|
|
||||||
X509_STORE_CTX_new(), X509_STORE_CTX_free
|
|
||||||
);
|
|
||||||
if (!ctx) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize verification context
|
|
||||||
if (X509_STORE_CTX_init(ctx.get(), store.get(), cert.get(), nullptr) != 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform the actual certificate verification
|
|
||||||
int result = X509_verify_cert(ctx.get());
|
|
||||||
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) {
|
|
||||||
std::vector<std::string> names;
|
|
||||||
|
|
||||||
if (cert_der.empty())
|
|
||||||
return names;
|
|
||||||
|
|
||||||
// Parse DER certificate
|
|
||||||
const unsigned char* p = cert_der.data();
|
|
||||||
X509* cert = d2i_X509(nullptr, &p, cert_der.size());
|
|
||||||
if (!cert)
|
|
||||||
return names;
|
|
||||||
|
|
||||||
// --- Subject Alternative Names (SAN) ---
|
|
||||||
GENERAL_NAMES* san_names =
|
|
||||||
(GENERAL_NAMES*)X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr);
|
|
||||||
|
|
||||||
if (san_names) {
|
|
||||||
int san_count = sk_GENERAL_NAME_num(san_names);
|
|
||||||
for (int i = 0; i < san_count; i++) {
|
|
||||||
const GENERAL_NAME* current = sk_GENERAL_NAME_value(san_names, i);
|
|
||||||
if (current->type == GEN_DNS) {
|
|
||||||
const char* dns_name = (const char*)ASN1_STRING_get0_data(current->d.dNSName);
|
|
||||||
// Safety: ensure no embedded nulls
|
|
||||||
if (ASN1_STRING_length(current->d.dNSName) == (int)std::strlen(dns_name)) {
|
|
||||||
names.emplace_back(dns_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GENERAL_NAMES_free(san_names);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Fallback: Common Name (CN) ---
|
|
||||||
if (names.empty()) {
|
|
||||||
X509_NAME* subject = X509_get_subject_name(cert);
|
|
||||||
if (subject) {
|
|
||||||
int idx = X509_NAME_get_index_by_NID(subject, NID_commonName, -1);
|
|
||||||
if (idx >= 0) {
|
|
||||||
X509_NAME_ENTRY* entry = X509_NAME_get_entry(subject, idx);
|
|
||||||
ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(entry);
|
|
||||||
const char* cn_str = (const char*)ASN1_STRING_get0_data(cn_asn1);
|
|
||||||
if (ASN1_STRING_length(cn_asn1) == (int)std::strlen(cn_str)) {
|
|
||||||
names.emplace_back(cn_str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
X509_free(cert);
|
|
||||||
return names;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::array<uint8_t, crypto_sign_PUBLICKEYBYTES> mPublicKey;
|
std::array<uint8_t, crypto_sign_PUBLICKEYBYTES> mPublicKey;
|
||||||
std::array<uint8_t, crypto_sign_SECRETKEYBYTES> mPrivateKey;
|
std::array<uint8_t, crypto_sign_SECRETKEYBYTES> mPrivateKey;
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
// protocol_structs.hpp - Network Protocol Structures
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace ColumnLynx::Protocol {
|
|
||||||
#pragma pack(push, 1)
|
|
||||||
struct TunConfig {
|
|
||||||
uint8_t version;
|
|
||||||
uint8_t prefixLength;
|
|
||||||
uint16_t mtu;
|
|
||||||
uint32_t serverIP;
|
|
||||||
uint32_t clientIP;
|
|
||||||
uint32_t dns1;
|
|
||||||
uint32_t dns2;
|
|
||||||
};
|
|
||||||
#pragma pack(pop)
|
|
||||||
}
|
|
||||||
@@ -14,16 +14,13 @@
|
|||||||
|
|
||||||
namespace ColumnLynx::Net {
|
namespace ColumnLynx::Net {
|
||||||
struct SessionState {
|
struct SessionState {
|
||||||
SymmetricKey aesKey; // Agreed-upon AES-256 kes for that session; Immutable after creation
|
SymmetricKey aesKey; // 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}; // Per-direction counters
|
std::atomic<uint64_t> recv_ctr{0};
|
||||||
asio::ip::udp::endpoint udpEndpoint; // Deducted IP + Port of that session client
|
asio::ip::udp::endpoint udpEndpoint;
|
||||||
std::atomic<uint64_t> sendCounter{0}; // Counter of sent messages
|
std::atomic<uint64_t> sendCounter{0};
|
||||||
std::chrono::steady_clock::time_point created = std::chrono::steady_clock::now(); // Time created
|
std::chrono::steady_clock::time_point created = std::chrono::steady_clock::now();
|
||||||
std::chrono::steady_clock::time_point expires{}; // Time of expiry
|
std::chrono::steady_clock::time_point expires{};
|
||||||
uint32_t clientTunIP; // Assigned IP
|
|
||||||
uint32_t serverTunIP; // Server IP
|
|
||||||
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()); }
|
||||||
@@ -32,11 +29,10 @@ namespace ColumnLynx::Net {
|
|||||||
SessionState(SessionState&&) = default;
|
SessionState(SessionState&&) = default;
|
||||||
SessionState& operator=(SessionState&&) = default;
|
SessionState& operator=(SessionState&&) = default;
|
||||||
|
|
||||||
explicit SessionState(const SymmetricKey& k, std::chrono::seconds ttl = std::chrono::hours(24), uint32_t clientIP = 0, uint32_t serverIP = 0, uint64_t id = 0) : aesKey(k), clientTunIP(clientIP), serverTunIP(serverIP), sessionID(id) {
|
explicit SessionState(const SymmetricKey& k, std::chrono::seconds ttl = std::chrono::hours(24)) : aesKey(k) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -44,31 +40,21 @@ 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 a session entry
|
// Insert or replace
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup a session entry by session ID
|
// Lookup
|
||||||
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_lock lock(mMutex);
|
|
||||||
auto it = mIPSessions.find(ip);
|
|
||||||
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);
|
||||||
@@ -76,7 +62,7 @@ namespace ColumnLynx::Net {
|
|||||||
return snap;
|
return snap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove a session by ID
|
// Remove
|
||||||
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);
|
||||||
@@ -93,56 +79,10 @@ namespace ColumnLynx::Net {
|
|||||||
++it;
|
++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
|
|
||||||
int size() const {
|
|
||||||
std::shared_lock lock(mMutex);
|
|
||||||
return static_cast<int>(mSessions.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
// IP management (simple for /24 subnet)
|
|
||||||
|
|
||||||
// Get the lowest available IPv4 address; Returns 0 if none available
|
|
||||||
uint32_t getFirstAvailableIP() const {
|
|
||||||
std::shared_lock lock(mMutex);
|
|
||||||
uint32_t baseIP = 0x0A0A0002; // 10.10.0.2
|
|
||||||
|
|
||||||
// TODO: Expand to support larger subnets
|
|
||||||
for (uint32_t offset = 0; offset < 254; offset++) {
|
|
||||||
uint32_t candidateIP = baseIP + offset;
|
|
||||||
if (mSessionIPs.find(candidateIP) == mSessionIPs.end()) {
|
|
||||||
return candidateIP;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0; // Unavailable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
||||||
std::unordered_map<uint64_t, std::shared_ptr<SessionState>> mSessions;
|
std::unordered_map<uint64_t, std::shared_ptr<SessionState>> mSessions;
|
||||||
std::unordered_map<uint64_t, uint32_t> mSessionIPs;
|
|
||||||
std::unordered_map<uint32_t, std::shared_ptr<SessionState>> mIPSessions;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
#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,
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
// virtual_interface.hpp - Virtual Interface for Network Communication
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <cstring>
|
|
||||||
#include <cerrno>
|
|
||||||
#include <vector>
|
|
||||||
#include <iostream>
|
|
||||||
#include <columnlynx/common/utils.hpp>
|
|
||||||
|
|
||||||
#if defined(__linux__)
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <linux/if.h>
|
|
||||||
#include <linux/if_tun.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/kern_control.h>
|
|
||||||
#include <sys/sys_domain.h>
|
|
||||||
#include <net/if_utun.h>
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
#include <windows.h>
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#include <winsock2.h>
|
|
||||||
#include <wintun/wintun.h>
|
|
||||||
#pragma comment(lib, "advapi32.lib")
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ColumnLynx::Net {
|
|
||||||
class VirtualInterface {
|
|
||||||
public:
|
|
||||||
explicit VirtualInterface(const std::string& ifName);
|
|
||||||
~VirtualInterface();
|
|
||||||
|
|
||||||
bool configureIP(uint32_t clientIP, uint32_t serverIP,
|
|
||||||
uint8_t prefixLen, uint16_t mtu);
|
|
||||||
|
|
||||||
std::vector<uint8_t> readPacket();
|
|
||||||
void writePacket(const std::vector<uint8_t>& packet);
|
|
||||||
|
|
||||||
const std::string& getName() const;
|
|
||||||
int getFd() const; // For ASIO integration (on POSIX)
|
|
||||||
|
|
||||||
static inline std::string ipv4ToString(uint32_t ip) {
|
|
||||||
struct in_addr addr;
|
|
||||||
addr.s_addr = htonl(ip);
|
|
||||||
|
|
||||||
char buf[INET_ADDRSTRLEN];
|
|
||||||
if (!inet_ntop(AF_INET, &addr, buf, sizeof(buf)))
|
|
||||||
return "0.0.0.0";
|
|
||||||
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t prefixLengthToNetmask(uint8_t prefixLen) {
|
|
||||||
if (prefixLen == 0) return 0;
|
|
||||||
uint32_t mask = (0xFFFFFFFF << (32 - prefixLen)) & 0xFFFFFFFF;
|
|
||||||
return htonl(mask); // convert to network byte order
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool mApplyLinuxIP(uint32_t clientIP, uint32_t serverIP, uint8_t prefixLen, uint16_t mtu);
|
|
||||||
bool mApplyMacOSIP(uint32_t clientIP, uint32_t serverIP, uint8_t prefixLen, uint16_t mtu);
|
|
||||||
bool mApplyWindowsIP(uint32_t clientIP, uint32_t serverIP, uint8_t prefixLen, uint16_t mtu);
|
|
||||||
|
|
||||||
std::string mIfName;
|
|
||||||
int mFd; // POSIX
|
|
||||||
#if defined(_WIN32)
|
|
||||||
HANDLE mHandle; // Windows
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -184,7 +184,7 @@ namespace ColumnLynx::Utils {
|
|||||||
out << "----------------------\n";
|
out << "----------------------\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Panic the main thread and instantly halt execution. This produces a stack trace dump. Do not use by itself, throw an error instead.
|
//Panic the main thread and instantly halt execution. This produces a stack trace dump. Do not use by itself, throw an error instead.
|
||||||
static void panic(const std::string& reason) {
|
static void panic(const std::string& reason) {
|
||||||
std::cerr << "\n***\033[31m MAIN THREAD PANIC! \033[0m***\n";
|
std::cerr << "\n***\033[31m MAIN THREAD PANIC! \033[0m***\n";
|
||||||
std::cerr << "Reason: " << reason << "\n";
|
std::cerr << "Reason: " << reason << "\n";
|
||||||
@@ -204,7 +204,6 @@ 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,12 +7,6 @@
|
|||||||
#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>
|
||||||
@@ -23,27 +17,17 @@
|
|||||||
#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>
|
||||||
@@ -54,28 +38,4 @@ 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);
|
|
||||||
};
|
};
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
#include <columnlynx/common/utils.hpp>
|
#include <columnlynx/common/utils.hpp>
|
||||||
#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>
|
|
||||||
|
|
||||||
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> {
|
||||||
@@ -33,19 +32,56 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start a TCP Connection (Handler for an incoming connection)
|
void start() {
|
||||||
void start();
|
mHandler->onMessage([this](AnyMessageType type, const std::string& data) {
|
||||||
// Send a message to the TCP client
|
mHandleMessage(static_cast<ClientMessageType>(MessageHandler::toUint8(type)), data);
|
||||||
void sendMessage(ServerMessageType type, const std::string& data = "");
|
});
|
||||||
// Set callback for disconnects
|
|
||||||
void setDisconnectCallback(std::function<void(std::shared_ptr<TCPConnection>)> cb);
|
|
||||||
// Disconnect the client
|
|
||||||
void disconnect();
|
|
||||||
|
|
||||||
// Get the assigned session ID
|
mHandler->onDisconnect([this](const asio::error_code& ec) {
|
||||||
uint64_t getSessionID() const;
|
Utils::log("Client disconnected: " + mHandler->socket().remote_endpoint().address().to_string() + " - " + ec.message());
|
||||||
// Get the assigned AES key; You should probably access this via the Session Registry instead
|
disconnect();
|
||||||
std::array<uint8_t, 32> getAESKey() const;
|
});
|
||||||
|
|
||||||
|
mHandler->start();
|
||||||
|
mStartHeartbeat();
|
||||||
|
|
||||||
|
// Placeholder for message handling setup
|
||||||
|
Utils::log("Client connected: " + mHandler->socket().remote_endpoint().address().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendMessage(ServerMessageType type, const std::string& data = "") {
|
||||||
|
if (mHandler) {
|
||||||
|
mHandler->sendMessage(type, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDisconnectCallback(std::function<void(std::shared_ptr<TCPConnection>)> cb) {
|
||||||
|
mOnDisconnect = std::move(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void disconnect() {
|
||||||
|
std::string ip = mHandler->socket().remote_endpoint().address().to_string();
|
||||||
|
|
||||||
|
mHandler->sendMessage(ServerMessageType::GRACEFUL_DISCONNECT, "Server initiated disconnect.");
|
||||||
|
mHeartbeatTimer.cancel();
|
||||||
|
asio::error_code ec;
|
||||||
|
mHandler->socket().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
|
||||||
|
mHandler->socket().close(ec);
|
||||||
|
|
||||||
|
Utils::log("Closed connection to " + ip);
|
||||||
|
|
||||||
|
if (mOnDisconnect) {
|
||||||
|
mOnDisconnect(shared_from_this());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t getSessionID() const {
|
||||||
|
return mConnectionSessionID;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<uint8_t, 32> getAESKey() const {
|
||||||
|
return mConnectionAESKey;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TCPConnection(asio::ip::tcp::socket socket, Utils::LibSodiumWrapper* sodiumWrapper)
|
TCPConnection(asio::ip::tcp::socket socket, Utils::LibSodiumWrapper* sodiumWrapper)
|
||||||
@@ -57,10 +93,164 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
mLastHeartbeatSent(std::chrono::steady_clock::now())
|
mLastHeartbeatSent(std::chrono::steady_clock::now())
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// Start the heartbeat routine
|
void mStartHeartbeat() {
|
||||||
void mStartHeartbeat();
|
auto self = shared_from_this();
|
||||||
// Handle an incoming TCP message
|
mHeartbeatTimer.expires_after(std::chrono::seconds(5));
|
||||||
void mHandleMessage(ClientMessageType type, const std::string& data);
|
mHeartbeatTimer.async_wait([this, self](const asio::error_code& ec) {
|
||||||
|
if (ec == asio::error::operation_aborted) {
|
||||||
|
return; // Timer was cancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - self->mLastHeartbeatReceived).count();
|
||||||
|
|
||||||
|
if (elapsed >= 15) { // 3 missed heartbeats
|
||||||
|
Utils::error("Missed 3 heartbeats. I think the other party (client " + std::to_string(self->mConnectionSessionID) + ") might have died! Disconnecting.");
|
||||||
|
|
||||||
|
// Remove socket forcefully, client is dead
|
||||||
|
asio::error_code ec;
|
||||||
|
mHandler->socket().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
|
||||||
|
mHandler->socket().close(ec);
|
||||||
|
|
||||||
|
SessionRegistry::getInstance().erase(self->mConnectionSessionID);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->sendMessage(ServerMessageType::HEARTBEAT);
|
||||||
|
Utils::log("Sent HEARTBEAT to client " + std::to_string(self->mConnectionSessionID));
|
||||||
|
self->mLastHeartbeatSent = now;
|
||||||
|
|
||||||
|
self->mStartHeartbeat(); // Recursive
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void mHandleMessage(ClientMessageType type, const std::string& data) {
|
||||||
|
std::string reqAddr = mHandler->socket().remote_endpoint().address().to_string();
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case ClientMessageType::HANDSHAKE_INIT: {
|
||||||
|
Utils::log("Received HANDSHAKE_INIT from " + reqAddr);
|
||||||
|
|
||||||
|
if (data.size() < 1 + crypto_box_PUBLICKEYBYTES) {
|
||||||
|
Utils::warn("HANDSHAKE_INIT from " + reqAddr + " is too short.");
|
||||||
|
disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t clientProtoVer = static_cast<uint8_t>(data[0]);
|
||||||
|
if (clientProtoVer != Utils::protocolVersion()) {
|
||||||
|
Utils::warn("Client protocol version mismatch from " + reqAddr + ". Expected " +
|
||||||
|
std::to_string(Utils::protocolVersion()) + ", got " + std::to_string(clientProtoVer) + ".");
|
||||||
|
disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
mHandler->sendMessage(ServerMessageType::HANDSHAKE_IDENTIFY, Utils::uint8ArrayToString(mLibSodiumWrapper->getPublicKey(), crypto_sign_PUBLICKEYBYTES)); // This public key should always exist
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ClientMessageType::HANDSHAKE_CHALLENGE: {
|
||||||
|
Utils::log("Received HANDSHAKE_CHALLENGE from " + reqAddr);
|
||||||
|
|
||||||
|
// Convert to byte array
|
||||||
|
uint8_t challengeData[32];
|
||||||
|
std::memcpy(challengeData, data.data(), std::min(data.size(), sizeof(challengeData)));
|
||||||
|
|
||||||
|
// Sign the challenge
|
||||||
|
Signature sig = Utils::LibSodiumWrapper::signMessage(
|
||||||
|
challengeData, sizeof(challengeData),
|
||||||
|
mLibSodiumWrapper->getPrivateKey()
|
||||||
|
);
|
||||||
|
|
||||||
|
mHandler->sendMessage(ServerMessageType::HANDSHAKE_CHALLENGE_RESPONSE, Utils::uint8ArrayToString(sig.data(), sig.size())); // Placeholder response
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ClientMessageType::HANDSHAKE_EXCHANGE_KEY: {
|
||||||
|
Utils::log("Received HANDSHAKE_EXCHANGE_KEY from " + reqAddr);
|
||||||
|
|
||||||
|
// Extract encrypted AES key and nonce (nonce is the first 24 bytes, rest is the ciphertext)
|
||||||
|
if (data.size() < 24) { // Minimum size check (nonce)
|
||||||
|
Utils::warn("HANDSHAKE_EXCHANGE_KEY from " + reqAddr + " is too short.");
|
||||||
|
disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsymNonce nonce{};
|
||||||
|
std::memcpy(nonce.data(), data.data(), nonce.size());
|
||||||
|
std::vector<uint8_t> ciphertext(data.size() - nonce.size());
|
||||||
|
std::memcpy(ciphertext.data(), data.data() + nonce.size(), ciphertext.size());
|
||||||
|
try {
|
||||||
|
std::array<uint8_t, 32> arrayPrivateKey;
|
||||||
|
std::copy(mLibSodiumWrapper->getXPrivateKey(),
|
||||||
|
mLibSodiumWrapper->getXPrivateKey() + 32,
|
||||||
|
arrayPrivateKey.begin());
|
||||||
|
|
||||||
|
// Decrypt the AES key using the client's public key and server's private key
|
||||||
|
std::vector<uint8_t> decrypted = Utils::LibSodiumWrapper::decryptAsymmetric(
|
||||||
|
ciphertext.data(), ciphertext.size(),
|
||||||
|
nonce,
|
||||||
|
mConnectionPublicKey,
|
||||||
|
arrayPrivateKey
|
||||||
|
);
|
||||||
|
|
||||||
|
if (decrypted.size() != 32) {
|
||||||
|
Utils::warn("Decrypted HANDSHAKE_EXCHANGE_KEY from " + reqAddr + " has invalid size.");
|
||||||
|
disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memcpy(mConnectionAESKey.data(), decrypted.data(), decrypted.size());
|
||||||
|
|
||||||
|
// Make a Session ID
|
||||||
|
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)
|
||||||
|
Nonce symNonce{}; // All zeros
|
||||||
|
std::vector<uint8_t> encryptedSessionID = Utils::LibSodiumWrapper::encryptMessage(
|
||||||
|
reinterpret_cast<uint8_t*>(&mConnectionSessionID), sizeof(mConnectionSessionID),
|
||||||
|
mConnectionAESKey, symNonce
|
||||||
|
);
|
||||||
|
|
||||||
|
mHandler->sendMessage(ServerMessageType::HANDSHAKE_EXCHANGE_KEY_CONFIRM, Utils::uint8ArrayToString(encryptedSessionID.data(), encryptedSessionID.size()));
|
||||||
|
|
||||||
|
// Add to session registry
|
||||||
|
Utils::log("Handshake with " + reqAddr + " completed successfully. Session ID assigned.");
|
||||||
|
auto session = std::make_shared<SessionState>(mConnectionAESKey, std::chrono::hours(12));
|
||||||
|
SessionRegistry::getInstance().put(mConnectionSessionID, std::move(session));
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
Utils::error("Failed to decrypt HANDSHAKE_EXCHANGE_KEY from " + reqAddr + ": " + e.what());
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ClientMessageType::HEARTBEAT: {
|
||||||
|
Utils::log("Received HEARTBEAT from " + reqAddr);
|
||||||
|
mHandler->sendMessage(ServerMessageType::HEARTBEAT_ACK, ""); // Send ACK
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ClientMessageType::HEARTBEAT_ACK: {
|
||||||
|
Utils::log("Received HEARTBEAT_ACK from " + reqAddr);
|
||||||
|
mLastHeartbeatReceived = std::chrono::steady_clock::now();
|
||||||
|
mMissedHeartbeats = 0; // Reset missed heartbeat count
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ClientMessageType::GRACEFUL_DISCONNECT: {
|
||||||
|
Utils::log("Received GRACEFUL_DISCONNECT from " + reqAddr + ": " + data);
|
||||||
|
disconnect();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
Utils::warn("Unhandled message type from " + reqAddr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|||||||
@@ -16,65 +16,27 @@
|
|||||||
#include <columnlynx/common/utils.hpp>
|
#include <columnlynx/common/utils.hpp>
|
||||||
#include <columnlynx/server/net/tcp/tcp_connection.hpp>
|
#include <columnlynx/server/net/tcp/tcp_connection.hpp>
|
||||||
#include <columnlynx/common/libsodium_wrapper.hpp>
|
#include <columnlynx/common/libsodium_wrapper.hpp>
|
||||||
#include <columnlynx/common/net/protocol_structs.hpp>
|
|
||||||
|
|
||||||
namespace ColumnLynx::Net::TCP {
|
namespace ColumnLynx::Net::TCP {
|
||||||
|
|
||||||
class TCPServer {
|
class TCPServer {
|
||||||
public:
|
public:
|
||||||
TCPServer(asio::io_context& ioContext,
|
TCPServer(asio::io_context& ioContext, uint16_t port, Utils::LibSodiumWrapper* sodiumWrapper, bool* hostRunning)
|
||||||
uint16_t port,
|
: mIoContext(ioContext), mAcceptor(ioContext, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)), mSodiumWrapper(sodiumWrapper), mHostRunning(hostRunning)
|
||||||
Utils::LibSodiumWrapper* sodiumWrapper,
|
|
||||||
bool* hostRunning, bool ipv4Only = false)
|
|
||||||
: mIoContext(ioContext),
|
|
||||||
mAcceptor(ioContext),
|
|
||||||
mSodiumWrapper(sodiumWrapper),
|
|
||||||
mHostRunning(hostRunning)
|
|
||||||
{
|
{
|
||||||
// Preload the config map
|
|
||||||
mRawServerConfig = Utils::getConfigMap("server_config");
|
|
||||||
|
|
||||||
asio::error_code ec;
|
|
||||||
|
|
||||||
if (!ipv4Only) {
|
|
||||||
// Try IPv6 first (dual-stack check)
|
|
||||||
asio::ip::tcp::endpoint endpoint_v6(asio::ip::tcp::v6(), port);
|
|
||||||
mAcceptor.open(endpoint_v6.protocol(), ec);
|
|
||||||
if (!ec) {
|
|
||||||
mAcceptor.set_option(asio::ip::v6_only(false), ec); // Allow dual-stack if possible
|
|
||||||
mAcceptor.bind(endpoint_v6, ec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to IPv4 if anything failed
|
|
||||||
if (ec || ipv4Only) {
|
|
||||||
Utils::warn("TCP: IPv6 unavailable (" + ec.message() + "), falling back to IPv4 only");
|
|
||||||
|
|
||||||
asio::ip::tcp::endpoint endpoint_v4(asio::ip::tcp::v4(), port);
|
|
||||||
mAcceptor.close(); // ensure clean state
|
|
||||||
mAcceptor.open(endpoint_v4.protocol());
|
|
||||||
mAcceptor.bind(endpoint_v4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start listening
|
|
||||||
mAcceptor.listen();
|
|
||||||
Utils::log("Started TCP server on port " + std::to_string(port));
|
Utils::log("Started TCP server on port " + std::to_string(port));
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -8,56 +8,26 @@
|
|||||||
#include <columnlynx/common/net/udp/udp_message_type.hpp>
|
#include <columnlynx/common/net/udp/udp_message_type.hpp>
|
||||||
#include <columnlynx/common/utils.hpp>
|
#include <columnlynx/common/utils.hpp>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <columnlynx/common/net/virtual_interface.hpp>
|
|
||||||
|
|
||||||
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, bool* hostRunning)
|
||||||
: mSocket(ioContext), mHostRunning(hostRunning), mTun(tun)
|
: mSocket(ioContext, asio::ip::udp::endpoint(asio::ip::udp::v4(), port)), mHostRunning(hostRunning)
|
||||||
{
|
{
|
||||||
asio::error_code ec;
|
|
||||||
|
|
||||||
if (!ipv4Only) {
|
|
||||||
// Try IPv6 first (dual-stack check)
|
|
||||||
asio::ip::udp::endpoint endpoint_v6(asio::ip::udp::v6(), port);
|
|
||||||
mSocket.open(endpoint_v6.protocol(), ec);
|
|
||||||
if (!ec) {
|
|
||||||
mSocket.set_option(asio::ip::v6_only(false), ec); // Allow dual-stack if possible
|
|
||||||
mSocket.bind(endpoint_v6, ec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to IPv4 if anything failed
|
|
||||||
if (ec || ipv4Only) {
|
|
||||||
Utils::warn("UDP: IPv6 unavailable (" + ec.message() + "), falling back to IPv4 only");
|
|
||||||
|
|
||||||
asio::ip::udp::endpoint endpoint_v4(asio::ip::udp::v4(), port);
|
|
||||||
mSocket.close(); // ensure clean state
|
|
||||||
mSocket.open(endpoint_v4.protocol());
|
|
||||||
mSocket.bind(endpoint_v4);
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::log("Started UDP server on port " + std::to_string(port));
|
Utils::log("Started UDP server on port " + std::to_string(port));
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
||||||
|
void mSendData(const uint64_t sessionID, const std::string& data);
|
||||||
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
|
||||||
bool* mHostRunning;
|
bool* mHostRunning;
|
||||||
std::shared_ptr<VirtualInterface> mTun;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: GPL-2.0 OR MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <winsock2.h>
|
|
||||||
#include <windows.h>
|
|
||||||
#include <ipexport.h>
|
|
||||||
#include <ifdef.h>
|
|
||||||
#include <ws2ipdef.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef ALIGNED
|
|
||||||
# if defined(_MSC_VER)
|
|
||||||
# define ALIGNED(n) __declspec(align(n))
|
|
||||||
# elif defined(__GNUC__)
|
|
||||||
# define ALIGNED(n) __attribute__((aligned(n)))
|
|
||||||
# else
|
|
||||||
# error "Unable to define ALIGNED"
|
|
||||||
# endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* MinGW is missing this one, unfortunately. */
|
|
||||||
#ifndef _Post_maybenull_
|
|
||||||
# define _Post_maybenull_
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#pragma warning(push)
|
|
||||||
#pragma warning(disable : 4324) /* structure was padded due to alignment specifier */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A handle representing Wintun adapter
|
|
||||||
*/
|
|
||||||
typedef struct _WINTUN_ADAPTER *WINTUN_ADAPTER_HANDLE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Wintun adapter.
|
|
||||||
*
|
|
||||||
* @param Name The requested name of the adapter. Zero-terminated string of up to MAX_ADAPTER_NAME-1
|
|
||||||
* characters.
|
|
||||||
*
|
|
||||||
* @param TunnelType Name of the adapter tunnel type. Zero-terminated string of up to MAX_ADAPTER_NAME-1
|
|
||||||
* characters.
|
|
||||||
*
|
|
||||||
* @param RequestedGUID The GUID of the created network adapter, which then influences NLA generation deterministically.
|
|
||||||
* If it is set to NULL, the GUID is chosen by the system at random, and hence a new NLA entry is
|
|
||||||
* created for each new adapter. It is called "requested" GUID because the API it uses is
|
|
||||||
* completely undocumented, and so there could be minor interesting complications with its usage.
|
|
||||||
*
|
|
||||||
* @return If the function succeeds, the return value is the adapter handle. Must be released with
|
|
||||||
* WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call
|
|
||||||
* GetLastError.
|
|
||||||
*/
|
|
||||||
typedef _Must_inspect_result_
|
|
||||||
_Return_type_success_(return != NULL)
|
|
||||||
_Post_maybenull_
|
|
||||||
WINTUN_ADAPTER_HANDLE(WINAPI WINTUN_CREATE_ADAPTER_FUNC)
|
|
||||||
(_In_z_ LPCWSTR Name, _In_z_ LPCWSTR TunnelType, _In_opt_ const GUID *RequestedGUID);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens an existing Wintun adapter.
|
|
||||||
*
|
|
||||||
* @param Name The requested name of the adapter. Zero-terminated string of up to MAX_ADAPTER_NAME-1
|
|
||||||
* characters.
|
|
||||||
*
|
|
||||||
* @return If the function succeeds, the return value is the adapter handle. Must be released with
|
|
||||||
* WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call
|
|
||||||
* GetLastError.
|
|
||||||
*/
|
|
||||||
typedef _Must_inspect_result_
|
|
||||||
_Return_type_success_(return != NULL)
|
|
||||||
_Post_maybenull_
|
|
||||||
WINTUN_ADAPTER_HANDLE(WINAPI WINTUN_OPEN_ADAPTER_FUNC)(_In_z_ LPCWSTR Name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Releases Wintun adapter resources and, if adapter was created with WintunCreateAdapter, removes adapter.
|
|
||||||
*
|
|
||||||
* @param Adapter Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter.
|
|
||||||
*/
|
|
||||||
typedef VOID(WINAPI WINTUN_CLOSE_ADAPTER_FUNC)(_In_opt_ WINTUN_ADAPTER_HANDLE Adapter);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the Wintun driver if there are no more adapters in use.
|
|
||||||
*
|
|
||||||
* @return If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To
|
|
||||||
* get extended error information, call GetLastError.
|
|
||||||
*/
|
|
||||||
typedef _Return_type_success_(return != FALSE)
|
|
||||||
BOOL(WINAPI WINTUN_DELETE_DRIVER_FUNC)(VOID);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the LUID of the adapter.
|
|
||||||
*
|
|
||||||
* @param Adapter Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter
|
|
||||||
*
|
|
||||||
* @param Luid Pointer to LUID to receive adapter LUID.
|
|
||||||
*/
|
|
||||||
typedef VOID(WINAPI WINTUN_GET_ADAPTER_LUID_FUNC)(_In_ WINTUN_ADAPTER_HANDLE Adapter, _Out_ NET_LUID *Luid);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines the version of the Wintun driver currently loaded.
|
|
||||||
*
|
|
||||||
* @return If the function succeeds, the return value is the version number. If the function fails, the return value is
|
|
||||||
* zero. To get extended error information, call GetLastError. Possible errors include the following:
|
|
||||||
* ERROR_FILE_NOT_FOUND Wintun not loaded
|
|
||||||
*/
|
|
||||||
typedef _Return_type_success_(return != 0)
|
|
||||||
DWORD(WINAPI WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC)(VOID);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines the level of logging, passed to WINTUN_LOGGER_CALLBACK.
|
|
||||||
*/
|
|
||||||
typedef enum
|
|
||||||
{
|
|
||||||
WINTUN_LOG_INFO, /**< Informational */
|
|
||||||
WINTUN_LOG_WARN, /**< Warning */
|
|
||||||
WINTUN_LOG_ERR /**< Error */
|
|
||||||
} WINTUN_LOGGER_LEVEL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by internal logger to report diagnostic messages
|
|
||||||
*
|
|
||||||
* @param Level Message level.
|
|
||||||
*
|
|
||||||
* @param Timestamp Message timestamp in in 100ns intervals since 1601-01-01 UTC.
|
|
||||||
*
|
|
||||||
* @param Message Message text.
|
|
||||||
*/
|
|
||||||
typedef VOID(CALLBACK *WINTUN_LOGGER_CALLBACK)(
|
|
||||||
_In_ WINTUN_LOGGER_LEVEL Level,
|
|
||||||
_In_ DWORD64 Timestamp,
|
|
||||||
_In_z_ LPCWSTR Message);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets logger callback function.
|
|
||||||
*
|
|
||||||
* @param NewLogger Pointer to callback function to use as a new global logger. NewLogger may be called from various
|
|
||||||
* threads concurrently. Should the logging require serialization, you must handle serialization in
|
|
||||||
* NewLogger. Set to NULL to disable.
|
|
||||||
*/
|
|
||||||
typedef VOID(WINAPI WINTUN_SET_LOGGER_FUNC)(_In_ WINTUN_LOGGER_CALLBACK NewLogger);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum ring capacity.
|
|
||||||
*/
|
|
||||||
#define WINTUN_MIN_RING_CAPACITY 0x20000 /* 128kiB */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum ring capacity.
|
|
||||||
*/
|
|
||||||
#define WINTUN_MAX_RING_CAPACITY 0x4000000 /* 64MiB */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A handle representing Wintun session
|
|
||||||
*/
|
|
||||||
typedef struct _TUN_SESSION *WINTUN_SESSION_HANDLE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts Wintun session.
|
|
||||||
*
|
|
||||||
* @param Adapter Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter
|
|
||||||
*
|
|
||||||
* @param Capacity Rings capacity. Must be between WINTUN_MIN_RING_CAPACITY and WINTUN_MAX_RING_CAPACITY (incl.)
|
|
||||||
* Must be a power of two.
|
|
||||||
*
|
|
||||||
* @return Wintun session handle. Must be released with WintunEndSession. If the function fails, the return value is
|
|
||||||
* NULL. To get extended error information, call GetLastError.
|
|
||||||
*/
|
|
||||||
typedef _Must_inspect_result_
|
|
||||||
_Return_type_success_(return != NULL)
|
|
||||||
_Post_maybenull_
|
|
||||||
WINTUN_SESSION_HANDLE(WINAPI WINTUN_START_SESSION_FUNC)(_In_ WINTUN_ADAPTER_HANDLE Adapter, _In_ DWORD Capacity);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ends Wintun session.
|
|
||||||
*
|
|
||||||
* @param Session Wintun session handle obtained with WintunStartSession
|
|
||||||
*/
|
|
||||||
typedef VOID(WINAPI WINTUN_END_SESSION_FUNC)(_In_ WINTUN_SESSION_HANDLE Session);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets Wintun session's read-wait event handle.
|
|
||||||
*
|
|
||||||
* @param Session Wintun session handle obtained with WintunStartSession
|
|
||||||
*
|
|
||||||
* @return Pointer to receive event handle to wait for available data when reading. Should
|
|
||||||
* WintunReceivePackets return ERROR_NO_MORE_ITEMS (after spinning on it for a while under heavy
|
|
||||||
* load), wait for this event to become signaled before retrying WintunReceivePackets. Do not call
|
|
||||||
* CloseHandle on this event - it is managed by the session.
|
|
||||||
*/
|
|
||||||
typedef HANDLE(WINAPI WINTUN_GET_READ_WAIT_EVENT_FUNC)(_In_ WINTUN_SESSION_HANDLE Session);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum IP packet size
|
|
||||||
*/
|
|
||||||
#define WINTUN_MAX_IP_PACKET_SIZE 0xFFFF
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves one or packet. After the packet content is consumed, call WintunReleaseReceivePacket with Packet returned
|
|
||||||
* from this function to release internal buffer. This function is thread-safe.
|
|
||||||
*
|
|
||||||
* @param Session Wintun session handle obtained with WintunStartSession
|
|
||||||
*
|
|
||||||
* @param PacketSize Pointer to receive packet size.
|
|
||||||
*
|
|
||||||
* @return Pointer to layer 3 IPv4 or IPv6 packet. Client may modify its content at will. If the function fails, the
|
|
||||||
* return value is NULL. To get extended error information, call GetLastError. Possible errors include the
|
|
||||||
* following:
|
|
||||||
* ERROR_HANDLE_EOF Wintun adapter is terminating;
|
|
||||||
* ERROR_NO_MORE_ITEMS Wintun buffer is exhausted;
|
|
||||||
* ERROR_INVALID_DATA Wintun buffer is corrupt
|
|
||||||
*/
|
|
||||||
typedef _Must_inspect_result_
|
|
||||||
_Return_type_success_(return != NULL)
|
|
||||||
_Post_maybenull_
|
|
||||||
_Post_writable_byte_size_(*PacketSize)
|
|
||||||
BYTE *(WINAPI WINTUN_RECEIVE_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _Out_ DWORD *PacketSize);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Releases internal buffer after the received packet has been processed by the client. This function is thread-safe.
|
|
||||||
*
|
|
||||||
* @param Session Wintun session handle obtained with WintunStartSession
|
|
||||||
*
|
|
||||||
* @param Packet Packet obtained with WintunReceivePacket
|
|
||||||
*/
|
|
||||||
typedef VOID(
|
|
||||||
WINAPI WINTUN_RELEASE_RECEIVE_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ const BYTE *Packet);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocates memory for a packet to send. After the memory is filled with packet data, call WintunSendPacket to send
|
|
||||||
* and release internal buffer. WintunAllocateSendPacket is thread-safe and the WintunAllocateSendPacket order of
|
|
||||||
* calls define the packet sending order.
|
|
||||||
*
|
|
||||||
* @param Session Wintun session handle obtained with WintunStartSession
|
|
||||||
*
|
|
||||||
* @param PacketSize Exact packet size. Must be less or equal to WINTUN_MAX_IP_PACKET_SIZE.
|
|
||||||
*
|
|
||||||
* @return Returns pointer to memory where to prepare layer 3 IPv4 or IPv6 packet for sending. If the function fails,
|
|
||||||
* the return value is NULL. To get extended error information, call GetLastError. Possible errors include the
|
|
||||||
* following:
|
|
||||||
* ERROR_HANDLE_EOF Wintun adapter is terminating;
|
|
||||||
* ERROR_BUFFER_OVERFLOW Wintun buffer is full;
|
|
||||||
*/
|
|
||||||
typedef _Must_inspect_result_
|
|
||||||
_Return_type_success_(return != NULL)
|
|
||||||
_Post_maybenull_
|
|
||||||
_Post_writable_byte_size_(PacketSize)
|
|
||||||
BYTE *(WINAPI WINTUN_ALLOCATE_SEND_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ DWORD PacketSize);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends the packet and releases internal buffer. WintunSendPacket is thread-safe, but the WintunAllocateSendPacket
|
|
||||||
* order of calls define the packet sending order. This means the packet is not guaranteed to be sent in the
|
|
||||||
* WintunSendPacket yet.
|
|
||||||
*
|
|
||||||
* @param Session Wintun session handle obtained with WintunStartSession
|
|
||||||
*
|
|
||||||
* @param Packet Packet obtained with WintunAllocateSendPacket
|
|
||||||
*/
|
|
||||||
typedef VOID(WINAPI WINTUN_SEND_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ const BYTE *Packet);
|
|
||||||
|
|
||||||
#pragma warning(pop)
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -10,18 +10,15 @@
|
|||||||
#include <columnlynx/client/net/tcp/tcp_client.hpp>
|
#include <columnlynx/client/net/tcp/tcp_client.hpp>
|
||||||
#include <columnlynx/client/net/udp/udp_client.hpp>
|
#include <columnlynx/client/net/udp/udp_client.hpp>
|
||||||
#include <cxxopts/cxxopts.hpp>
|
#include <cxxopts/cxxopts.hpp>
|
||||||
#include <columnlynx/common/net/virtual_interface.hpp>
|
|
||||||
|
|
||||||
using asio::ip::tcp;
|
using asio::ip::tcp;
|
||||||
using namespace ColumnLynx::Utils;
|
using namespace ColumnLynx::Utils;
|
||||||
using namespace ColumnLynx::Net;
|
|
||||||
using namespace ColumnLynx;
|
|
||||||
|
|
||||||
volatile sig_atomic_t done = 0;
|
volatile sig_atomic_t done = 0;
|
||||||
|
|
||||||
void signalHandler(int signum) {
|
void signalHandler(int signum) {
|
||||||
if (signum == SIGINT || signum == SIGTERM) {
|
if (signum == SIGINT || signum == SIGTERM) {
|
||||||
//log("Received termination signal. Shutting down client.");
|
log("Received termination signal. Shutting down client.");
|
||||||
done = 1;
|
done = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,17 +38,11 @@ int main(int argc, char** argv) {
|
|||||||
options.add_options()
|
options.add_options()
|
||||||
("h,help", "Print help")
|
("h,help", "Print help")
|
||||||
("s,server", "Server address", cxxopts::value<std::string>()->default_value("127.0.0.1"))
|
("s,server", "Server address", cxxopts::value<std::string>()->default_value("127.0.0.1"))
|
||||||
("p,port", "Server port", cxxopts::value<uint16_t>()->default_value(std::to_string(serverPort())))
|
("p,port", "Server port", cxxopts::value<uint16_t>()->default_value(std::to_string(serverPort())));
|
||||||
("allow-selfsigned", "Allow self-signed certificates", cxxopts::value<bool>()->default_value("false"));
|
|
||||||
|
|
||||||
bool insecureMode = options.parse(argc, argv).count("allow-selfsigned") > 0;
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,25 +51,16 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
log("ColumnLynx Client, Version " + getVersion());
|
log("ColumnLynx Client, 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 GPLv3. See LICENSE for details.");
|
||||||
|
|
||||||
#if defined(__WIN32__)
|
|
||||||
WintunInitialize();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::shared_ptr<VirtualInterface> tun = std::make_shared<VirtualInterface>("utun1");
|
|
||||||
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;
|
||||||
|
|
||||||
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);
|
||||||
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);
|
||||||
|
|
||||||
client->start();
|
client->start();
|
||||||
udpClient->start();
|
udpClient->start();
|
||||||
@@ -87,31 +69,29 @@ int main(int argc, char** argv) {
|
|||||||
std::thread ioThread([&io]() {
|
std::thread ioThread([&io]() {
|
||||||
io.run();
|
io.run();
|
||||||
});
|
});
|
||||||
//ioThread.join();
|
ioThread.detach();
|
||||||
|
|
||||||
log("Client connected to " + host + ":" + port);
|
log("Client connected to " + host + ":" + port);
|
||||||
|
|
||||||
// Client is running
|
// Client is running
|
||||||
while ((client->isConnected() || !client->isHandshakeComplete()) && !done) {
|
while ((!done && client->isConnected()) || !client->isHandshakeComplete()) {
|
||||||
auto packet = tun->readPacket();
|
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Temp wait
|
||||||
if (!client->isConnected() || done) {
|
|
||||||
break; // Bail out if connection died or signal set while blocked
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet.empty()) {
|
if (client->isHandshakeComplete()) {
|
||||||
continue;
|
// Send a test UDP message every 5 seconds after handshake is complete
|
||||||
|
static auto lastSendTime = std::chrono::steady_clock::now();
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
if (std::chrono::duration_cast<std::chrono::seconds>(now - lastSendTime).count() >= 5) {
|
||||||
|
udpClient->sendMessage("Hello from UDP client!");
|
||||||
|
lastSendTime = now;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
udpClient->sendMessage(std::string(packet.begin(), packet.end()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log("Client shutting down.");
|
log("Client shutting down.");
|
||||||
udpClient->stop();
|
udpClient->stop();
|
||||||
client->disconnect();
|
client->disconnect();
|
||||||
io.stop();
|
io.stop();
|
||||||
|
ioThread.join();
|
||||||
if (ioThread.joinable())
|
|
||||||
ioThread.join();
|
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
error("Client error: " + std::string(e.what()));
|
error("Client error: " + std::string(e.what()));
|
||||||
|
|||||||
@@ -1,296 +0,0 @@
|
|||||||
// tcp_client.cpp - TCP Client 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/client/net/tcp/tcp_client.hpp>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
|
|
||||||
namespace ColumnLynx::Net::TCP {
|
|
||||||
void TCPClient::start() {
|
|
||||||
auto self = shared_from_this();
|
|
||||||
mResolver.async_resolve(mHost, mPort,
|
|
||||||
[this, self](asio::error_code ec, tcp::resolver::results_type endpoints) {
|
|
||||||
if (!ec) {
|
|
||||||
asio::async_connect(mSocket, endpoints,
|
|
||||||
[this, self](asio::error_code ec, const tcp::endpoint&) {
|
|
||||||
if (!NetHelper::isExpectedDisconnect(ec)) {
|
|
||||||
mConnected = true;
|
|
||||||
Utils::log("Client connected.");
|
|
||||||
mHandler = std::make_shared<MessageHandler>(std::move(mSocket));
|
|
||||||
mHandler->onMessage([this](AnyMessageType type, const std::string& data) {
|
|
||||||
mHandleMessage(static_cast<ServerMessageType>(MessageHandler::toUint8(type)), data);
|
|
||||||
});
|
|
||||||
mHandler->start();
|
|
||||||
|
|
||||||
// Init connection handshake
|
|
||||||
Utils::log("Sending handshake init to server.");
|
|
||||||
|
|
||||||
// Check if hostname or IPv4/IPv6
|
|
||||||
sockaddr_in addr4{};
|
|
||||||
sockaddr_in6 addr6{};
|
|
||||||
self->mIsHostDomain = inet_pton(AF_INET, mHost.c_str(), (void*)(&addr4)) != 1 && inet_pton(AF_INET6, mHost.c_str(), (void*)(&addr6)) != 1; // Voodoo black magic
|
|
||||||
|
|
||||||
std::vector<uint8_t> payload;
|
|
||||||
payload.reserve(1 + crypto_box_PUBLICKEYBYTES);
|
|
||||||
payload.push_back(Utils::protocolVersion());
|
|
||||||
/*payload.insert(payload.end(),
|
|
||||||
mLibSodiumWrapper->getXPublicKey(),
|
|
||||||
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()));
|
|
||||||
|
|
||||||
mStartHeartbeat();
|
|
||||||
} else {
|
|
||||||
Utils::error("Client connect failed: " + ec.message());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Utils::error("Client resolve failed: " + ec.message());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPClient::sendMessage(ClientMessageType type, const std::string& data) {
|
|
||||||
if (!mConnected) {
|
|
||||||
Utils::error("Cannot send message, client not connected.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mHandler) {
|
|
||||||
asio::post(mHandler->socket().get_executor(), [self = shared_from_this(), type, data]() {
|
|
||||||
self->mHandler->sendMessage(type, data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPClient::disconnect(bool echo) {
|
|
||||||
if (mConnected && mHandler) {
|
|
||||||
if (echo) {
|
|
||||||
mHandler->sendMessage(ClientMessageType::GRACEFUL_DISCONNECT, "Goodbye");
|
|
||||||
}
|
|
||||||
|
|
||||||
asio::error_code ec;
|
|
||||||
mHeartbeatTimer.cancel();
|
|
||||||
|
|
||||||
mHandler->socket().shutdown(tcp::socket::shutdown_both, ec);
|
|
||||||
if (ec) {
|
|
||||||
Utils::error("Error during socket shutdown: " + ec.message());
|
|
||||||
}
|
|
||||||
|
|
||||||
mHandler->socket().close(ec);
|
|
||||||
if (ec) {
|
|
||||||
Utils::error("Error during socket close: " + ec.message());
|
|
||||||
}
|
|
||||||
|
|
||||||
mConnected = false;
|
|
||||||
Utils::log("Client disconnected.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TCPClient::isHandshakeComplete() const {
|
|
||||||
return mHandshakeComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TCPClient::isConnected() const {
|
|
||||||
return mConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPClient::mStartHeartbeat() {
|
|
||||||
auto self = shared_from_this();
|
|
||||||
mHeartbeatTimer.expires_after(std::chrono::seconds(5));
|
|
||||||
mHeartbeatTimer.async_wait([this, self](const asio::error_code& ec) {
|
|
||||||
if (ec == asio::error::operation_aborted) {
|
|
||||||
return; // Timer was cancelled
|
|
||||||
}
|
|
||||||
|
|
||||||
auto now = std::chrono::steady_clock::now();
|
|
||||||
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - self->mLastHeartbeatReceived).count();
|
|
||||||
|
|
||||||
if (elapsed >= 15) { // 3 missed heartbeats
|
|
||||||
Utils::error("Missed 3 heartbeats. I think the other party might have died! Disconnecting.");
|
|
||||||
|
|
||||||
// Close sockets forcefully, server is dead
|
|
||||||
asio::error_code ec;
|
|
||||||
mHandler->socket().shutdown(tcp::socket::shutdown_both, ec);
|
|
||||||
mHandler->socket().close(ec);
|
|
||||||
mConnected = false;
|
|
||||||
|
|
||||||
mGlobalKeyRef = nullptr;
|
|
||||||
if (mSessionIDRef) {
|
|
||||||
*mSessionIDRef = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self->sendMessage(ClientMessageType::HEARTBEAT);
|
|
||||||
Utils::log("Sent HEARTBEAT to server.");
|
|
||||||
self->mLastHeartbeatSent = std::chrono::steady_clock::now();
|
|
||||||
|
|
||||||
self->mStartHeartbeat(); // Recursive
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPClient::mHandleMessage(ServerMessageType type, const std::string& data) {
|
|
||||||
switch (type) {
|
|
||||||
case ServerMessageType::HANDSHAKE_IDENTIFY: {
|
|
||||||
Utils::log("Received server identity: " + data);
|
|
||||||
std::memcpy(mServerPublicKey, data.data(), std::min(data.size(), sizeof(mServerPublicKey)));
|
|
||||||
|
|
||||||
// Convert key (uint8_t raw array) to vector
|
|
||||||
std::vector<uint8_t> serverPublicKeyVec(std::begin(mServerPublicKey), std::end(mServerPublicKey));
|
|
||||||
|
|
||||||
// Verify server public key
|
|
||||||
if (!Utils::LibSodiumWrapper::verifyCertificateWithSystemCAs(serverPublicKeyVec)) {
|
|
||||||
if (!(*mInsecureMode)) {
|
|
||||||
Utils::error("Server public key verification failed. Terminating connection.");
|
|
||||||
disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::warn("Warning: Server public key verification failed, but continuing due to insecure mode.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract and verify hostname from certificate if not IP
|
|
||||||
if (mIsHostDomain) {
|
|
||||||
std::vector<std::string> certHostnames = Utils::LibSodiumWrapper::getCertificateHostname(serverPublicKeyVec);
|
|
||||||
|
|
||||||
// Temp: print extracted hostnames if any
|
|
||||||
for (const auto& hostname : certHostnames) {
|
|
||||||
Utils::log("Extracted hostname from certificate: " + hostname);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (certHostnames.empty() || std::find(certHostnames.begin(), certHostnames.end(), mHost) == certHostnames.end()) {
|
|
||||||
if (!(*mInsecureMode)) {
|
|
||||||
Utils::error("Server hostname verification failed. Terminating connection.");
|
|
||||||
disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::warn("Warning: Server hostname verification failed, but continuing due to insecure mode.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Utils::warn("Connecting via IP address, I can't verify the server's identity! You might be getting MITM'd!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate and send challenge
|
|
||||||
Utils::log("Sending challenge to server.");
|
|
||||||
mSubmittedChallenge = Utils::LibSodiumWrapper::generateRandom256Bit(); // Temporarily store the challenge to verify later
|
|
||||||
mHandler->sendMessage(ClientMessageType::HANDSHAKE_CHALLENGE, Utils::uint8ArrayToString(mSubmittedChallenge));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case ServerMessageType::HANDSHAKE_CHALLENGE_RESPONSE:
|
|
||||||
Utils::log("Received challenge response from server.");
|
|
||||||
{
|
|
||||||
// Verify the signature
|
|
||||||
Signature sig{};
|
|
||||||
std::memcpy(sig.data(), data.data(), std::min(data.size(), sig.size()));
|
|
||||||
if (Utils::LibSodiumWrapper::verifyMessage(mSubmittedChallenge.data(), mSubmittedChallenge.size(), sig, mServerPublicKey)) {
|
|
||||||
Utils::log("Challenge response verified successfully.");
|
|
||||||
|
|
||||||
// Convert the server's public key to Curve25519 for encryption
|
|
||||||
AsymPublicKey serverXPubKey{};
|
|
||||||
crypto_sign_ed25519_pk_to_curve25519(serverXPubKey.data(), mServerPublicKey);
|
|
||||||
|
|
||||||
// Generate AES key and send confirmation
|
|
||||||
mConnectionAESKey = Utils::LibSodiumWrapper::generateRandom256Bit();
|
|
||||||
if (mGlobalKeyRef) { // Copy to the global reference
|
|
||||||
std::copy(mConnectionAESKey.begin(), mConnectionAESKey.end(), mGlobalKeyRef->begin());
|
|
||||||
}
|
|
||||||
AsymNonce nonce{};
|
|
||||||
randombytes_buf(nonce.data(), nonce.size());
|
|
||||||
|
|
||||||
// TODO: This is pretty redundant, it should return the required type directly
|
|
||||||
std::array<uint8_t, 32> arrayPrivateKey;
|
|
||||||
std::copy(mLibSodiumWrapper->getXPrivateKey(),
|
|
||||||
mLibSodiumWrapper->getXPrivateKey() + 32,
|
|
||||||
arrayPrivateKey.begin());
|
|
||||||
|
|
||||||
std::vector<uint8_t> encr = Utils::LibSodiumWrapper::encryptAsymmetric(
|
|
||||||
mConnectionAESKey.data(), mConnectionAESKey.size(),
|
|
||||||
nonce,
|
|
||||||
serverXPubKey,
|
|
||||||
arrayPrivateKey
|
|
||||||
);
|
|
||||||
|
|
||||||
std::vector<uint8_t> payload;
|
|
||||||
payload.reserve(nonce.size() + encr.size());
|
|
||||||
payload.insert(payload.end(), nonce.begin(), nonce.end());
|
|
||||||
payload.insert(payload.end(), encr.begin(), encr.end());
|
|
||||||
|
|
||||||
mHandler->sendMessage(ClientMessageType::HANDSHAKE_EXCHANGE_KEY, Utils::uint8ArrayToString(payload.data(), payload.size()));
|
|
||||||
} else {
|
|
||||||
Utils::error("Challenge response verification failed. Terminating connection.");
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case ServerMessageType::HANDSHAKE_EXCHANGE_KEY_CONFIRM:
|
|
||||||
Utils::log("Received handshake exchange key confirmation from server.");
|
|
||||||
// Decrypt the session ID using the established AES key
|
|
||||||
{
|
|
||||||
Nonce symNonce{}; // All zeros
|
|
||||||
std::vector<uint8_t> ciphertext(data.begin(), data.end());
|
|
||||||
std::vector<uint8_t> decrypted = Utils::LibSodiumWrapper::decryptMessage(
|
|
||||||
ciphertext.data(), ciphertext.size(),
|
|
||||||
mConnectionAESKey, symNonce
|
|
||||||
);
|
|
||||||
|
|
||||||
if (decrypted.size() != sizeof(mConnectionSessionID) + sizeof(Protocol::TunConfig)) {
|
|
||||||
Utils::error("Decrypted config has invalid size. Terminating connection.");
|
|
||||||
disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::memcpy(&mConnectionSessionID, decrypted.data(), sizeof(mConnectionSessionID));
|
|
||||||
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));
|
|
||||||
|
|
||||||
if (mSessionIDRef) { // Copy to the global reference
|
|
||||||
*mSessionIDRef = mConnectionSessionID;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t clientIP = ntohl(mTunConfig.clientIP);
|
|
||||||
uint32_t serverIP = ntohl(mTunConfig.serverIP);
|
|
||||||
uint8_t prefixLen = mTunConfig.prefixLength;
|
|
||||||
uint16_t mtu = mTunConfig.mtu;
|
|
||||||
|
|
||||||
if (mTun) {
|
|
||||||
mTun->configureIP(clientIP, serverIP, prefixLen, mtu);
|
|
||||||
}
|
|
||||||
|
|
||||||
mHandshakeComplete = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case ServerMessageType::HEARTBEAT:
|
|
||||||
Utils::log("Received HEARTBEAT from server.");
|
|
||||||
mHandler->sendMessage(ClientMessageType::HEARTBEAT_ACK, ""); // Send ACK
|
|
||||||
break;
|
|
||||||
case ServerMessageType::HEARTBEAT_ACK:
|
|
||||||
Utils::log("Received HEARTBEAT_ACK from server.");
|
|
||||||
mLastHeartbeatReceived = std::chrono::steady_clock::now();
|
|
||||||
mMissedHeartbeats = 0; // Reset missed heartbeat count
|
|
||||||
break;
|
|
||||||
case ServerMessageType::GRACEFUL_DISCONNECT:
|
|
||||||
Utils::log("Server is disconnecting: " + data);
|
|
||||||
if (mConnected) { // Prevent Recursion
|
|
||||||
disconnect(false);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Utils::log("Received unknown message type from server.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
// udp_client.cpp - UDP Client 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/client/net/udp/udp_client.hpp>
|
|
||||||
|
|
||||||
namespace ColumnLynx::Net::UDP {
|
|
||||||
void UDPClient::start() {
|
|
||||||
// TODO: Add IPv6
|
|
||||||
auto endpoints = mResolver.resolve(asio::ip::udp::v4(), mHost, mPort);
|
|
||||||
mRemoteEndpoint = *endpoints.begin();
|
|
||||||
mSocket.open(asio::ip::udp::v4());
|
|
||||||
Utils::log("UDP Client ready to send to " + mRemoteEndpoint.address().to_string() + ":" + std::to_string(mRemoteEndpoint.port()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void UDPClient::sendMessage(const std::string& data) {
|
|
||||||
UDPPacketHeader hdr{};
|
|
||||||
randombytes_buf(hdr.nonce.data(), hdr.nonce.size());
|
|
||||||
|
|
||||||
if (mAesKeyRef == nullptr || mSessionIDRef == nullptr) {
|
|
||||||
Utils::error("UDP Client AES key or Session ID reference is null!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Utils::debug("Using AES key: " + Utils::bytesToHexString(mAesKeyRef->data(), 32));
|
|
||||||
|
|
||||||
auto encryptedPayload = Utils::LibSodiumWrapper::encryptMessage(
|
|
||||||
reinterpret_cast<const uint8_t*>(data.data()), data.size(),
|
|
||||||
*mAesKeyRef, hdr.nonce, "udp-data"
|
|
||||||
//std::string(reinterpret_cast<const char*>(&mSessionIDRef), sizeof(uint64_t))
|
|
||||||
);
|
|
||||||
|
|
||||||
std::vector<uint8_t> packet;
|
|
||||||
packet.reserve(sizeof(UDPPacketHeader) + sizeof(uint64_t) + encryptedPayload.size());
|
|
||||||
packet.insert(packet.end(),
|
|
||||||
reinterpret_cast<uint8_t*>(&hdr),
|
|
||||||
reinterpret_cast<uint8_t*>(&hdr) + sizeof(UDPPacketHeader)
|
|
||||||
);
|
|
||||||
uint64_t sid = *mSessionIDRef;
|
|
||||||
packet.insert(packet.end(),
|
|
||||||
reinterpret_cast<uint8_t*>(&sid),
|
|
||||||
reinterpret_cast<uint8_t*>(&sid) + sizeof(sid)
|
|
||||||
);
|
|
||||||
packet.insert(packet.end(), encryptedPayload.begin(), encryptedPayload.end());
|
|
||||||
|
|
||||||
mSocket.send_to(asio::buffer(packet), mRemoteEndpoint);
|
|
||||||
Utils::debug("Sent UDP packet of size " + std::to_string(packet.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void UDPClient::stop() {
|
|
||||||
if (mSocket.is_open()) {
|
|
||||||
asio::error_code ec;
|
|
||||||
mSocket.cancel(ec);
|
|
||||||
mSocket.close(ec);
|
|
||||||
Utils::log("UDP Client socket closed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void UDPClient::mStartReceive() {
|
|
||||||
mSocket.async_receive_from(
|
|
||||||
asio::buffer(mRecvBuffer), mRemoteEndpoint,
|
|
||||||
[this](asio::error_code ec, std::size_t bytes) {
|
|
||||||
if (ec) {
|
|
||||||
if (ec == asio::error::operation_aborted) return; // Socket closed
|
|
||||||
// Other recv error
|
|
||||||
mStartReceive();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bytes > 0) {
|
|
||||||
mHandlePacket(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
mStartReceive();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UDPClient::mHandlePacket(std::size_t bytes) {
|
|
||||||
if (bytes < sizeof(UDPPacketHeader) + sizeof(uint64_t)) {
|
|
||||||
Utils::warn("UDP Client received packet too small to process.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse header
|
|
||||||
UDPPacketHeader hdr;
|
|
||||||
std::memcpy(&hdr, mRecvBuffer.data(), sizeof(UDPPacketHeader));
|
|
||||||
|
|
||||||
// Parse session ID
|
|
||||||
uint64_t sessionID;
|
|
||||||
std::memcpy(&sessionID, mRecvBuffer.data() + sizeof(UDPPacketHeader), sizeof(uint64_t));
|
|
||||||
|
|
||||||
// Decrypt payload
|
|
||||||
std::vector<uint8_t> ciphertext(
|
|
||||||
mRecvBuffer.begin() + sizeof(UDPPacketHeader) + sizeof(uint64_t),
|
|
||||||
mRecvBuffer.begin() + bytes
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mAesKeyRef == nullptr) {
|
|
||||||
Utils::error("UDP Client AES key reference is null!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> plaintext = Utils::LibSodiumWrapper::decryptMessage(
|
|
||||||
ciphertext.data(), ciphertext.size(), *mAesKeyRef, hdr.nonce, "udp-data"
|
|
||||||
//std::string(reinterpret_cast<const char*>(&mSessionIDRef), sizeof(uint64_t))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (plaintext.empty()) {
|
|
||||||
Utils::warn("UDP Client failed to decrypt received packet.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::debug("UDP Client received packet from " + mRemoteEndpoint.address().to_string() + " - Packet size: " + std::to_string(bytes));
|
|
||||||
|
|
||||||
// Write to TUN
|
|
||||||
if (mTunRef) {
|
|
||||||
mTunRef->writePacket(plaintext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,27 +6,15 @@
|
|||||||
|
|
||||||
namespace ColumnLynx::Utils {
|
namespace ColumnLynx::Utils {
|
||||||
void log(const std::string &msg) {
|
void log(const std::string &msg) {
|
||||||
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
std::cout << "[LOG] " << msg << std::endl;
|
||||||
std::cout << "\033[0m[" << std::to_string(now) << " LOG] " << msg << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void warn(const std::string &msg) {
|
void warn(const std::string &msg) {
|
||||||
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
std::cerr << "[WARN] " << msg << std::endl;
|
||||||
std::cerr << "\033[33m[" << std::to_string(now) << " WARN] " << msg << "\033[0m" << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void error(const std::string &msg) {
|
void error(const std::string &msg) {
|
||||||
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
std::cerr << "[ERROR] " << msg << std::endl;
|
||||||
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() {
|
||||||
@@ -49,7 +37,7 @@ namespace ColumnLynx::Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string getVersion() {
|
std::string getVersion() {
|
||||||
return "a0.5";
|
return "a0.2";
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned short serverPort() {
|
unsigned short serverPort() {
|
||||||
@@ -73,78 +61,4 @@ 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
// virtual_interface.cpp - Virtual Interface for Network Communication
|
|
||||||
// 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/virtual_interface.hpp>
|
|
||||||
|
|
||||||
// This is all fucking voodoo dark magic.
|
|
||||||
|
|
||||||
namespace ColumnLynx::Net {
|
|
||||||
// ------------------------------ Constructor ------------------------------
|
|
||||||
VirtualInterface::VirtualInterface(const std::string& ifName)
|
|
||||||
: mIfName(ifName), mFd(-1)
|
|
||||||
{
|
|
||||||
#if defined(__linux__)
|
|
||||||
// ---- Linux: /dev/net/tun ----
|
|
||||||
mFd = open("/dev/net/tun", O_RDWR);
|
|
||||||
if (mFd < 0)
|
|
||||||
throw std::runtime_error("Failed to open /dev/net/tun: " + std::string(strerror(errno)));
|
|
||||||
|
|
||||||
struct ifreq ifr {};
|
|
||||||
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
|
|
||||||
std::strncpy(ifr.ifr_name, ifName.c_str(), IFNAMSIZ);
|
|
||||||
|
|
||||||
if (ioctl(mFd, TUNSETIFF, &ifr) < 0) {
|
|
||||||
close(mFd);
|
|
||||||
throw std::runtime_error("TUNSETIFF failed: " + std::string(strerror(errno)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
// ---- macOS: UTUN (system control socket) ----
|
|
||||||
mFd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
|
|
||||||
if (mFd < 0)
|
|
||||||
throw std::runtime_error("socket(PF_SYSTEM) failed: " + std::string(strerror(errno)));
|
|
||||||
|
|
||||||
struct ctl_info ctlInfo {};
|
|
||||||
std::strncpy(ctlInfo.ctl_name, UTUN_CONTROL_NAME, sizeof(ctlInfo.ctl_name));
|
|
||||||
if (ioctl(mFd, CTLIOCGINFO, &ctlInfo) == -1)
|
|
||||||
throw std::runtime_error("ioctl(CTLIOCGINFO) failed: " + std::string(strerror(errno)));
|
|
||||||
|
|
||||||
struct sockaddr_ctl sc {};
|
|
||||||
sc.sc_len = sizeof(sc);
|
|
||||||
sc.sc_family = AF_SYSTEM;
|
|
||||||
sc.ss_sysaddr = AF_SYS_CONTROL;
|
|
||||||
sc.sc_id = ctlInfo.ctl_id;
|
|
||||||
sc.sc_unit = 0; // utun0 (0 = auto-assign)
|
|
||||||
|
|
||||||
if (connect(mFd, (struct sockaddr*)&sc, sizeof(sc)) < 0) {
|
|
||||||
if (errno == EPERM)
|
|
||||||
throw std::runtime_error("connect(AF_SYS_CONTROL) failed: Insufficient permissions (try running as root)");
|
|
||||||
throw std::runtime_error("connect(AF_SYS_CONTROL) failed: " + std::string(strerror(errno)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve actual utun device name
|
|
||||||
struct sockaddr_storage addr;
|
|
||||||
socklen_t addrlen = sizeof(addr);
|
|
||||||
if (getsockname(mFd, (struct sockaddr*)&addr, &addrlen) == 0) {
|
|
||||||
const struct sockaddr_ctl* addr_ctl = (const struct sockaddr_ctl*)&addr;
|
|
||||||
mIfName = "utun" + std::to_string(addr_ctl->sc_unit - 1);
|
|
||||||
} else {
|
|
||||||
mIfName = "utunX";
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
// ---- Windows: Wintun (WireGuard virtual adapter) ----
|
|
||||||
WINTUN_ADAPTER_HANDLE adapter =
|
|
||||||
WintunOpenAdapter(L"ColumnLynx", std::wstring(ifName.begin(), ifName.end()).c_str());
|
|
||||||
if (!adapter)
|
|
||||||
throw std::runtime_error("Wintun adapter not found or not installed");
|
|
||||||
|
|
||||||
WINTUN_SESSION_HANDLE session =
|
|
||||||
WintunStartSession(adapter, 0x200000); // ring buffer size
|
|
||||||
if (!session)
|
|
||||||
throw std::runtime_error("Failed to start Wintun session");
|
|
||||||
|
|
||||||
mHandle = WintunGetReadWaitEvent(session);
|
|
||||||
mFd = -1; // not used on Windows
|
|
||||||
mIfName = ifName;
|
|
||||||
|
|
||||||
#else
|
|
||||||
throw std::runtime_error("Unsupported platform");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------ Destructor ------------------------------
|
|
||||||
VirtualInterface::~VirtualInterface() {
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
|
||||||
if (mFd >= 0)
|
|
||||||
close(mFd);
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
// Wintun sessions need explicit stop
|
|
||||||
// (assuming you stored the session handle as member)
|
|
||||||
// WintunEndSession(mSession);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------ Read ------------------------------
|
|
||||||
std::vector<uint8_t> VirtualInterface::readPacket() {
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
|
||||||
std::vector<uint8_t> buf(4096);
|
|
||||||
ssize_t n = read(mFd, buf.data(), buf.size());
|
|
||||||
if (n < 0) {
|
|
||||||
if (errno == EINTR) {
|
|
||||||
return {}; // Interrupted, return empty
|
|
||||||
}
|
|
||||||
throw std::runtime_error("read() failed: " + std::string(strerror(errno)));
|
|
||||||
}
|
|
||||||
buf.resize(n);
|
|
||||||
return buf;
|
|
||||||
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
WINTUN_PACKET* packet = WintunReceivePacket(mSession, nullptr);
|
|
||||||
if (!packet) return {};
|
|
||||||
std::vector<uint8_t> buf(packet->Data, packet->Data + packet->Length);
|
|
||||||
WintunReleaseReceivePacket(mSession, packet);
|
|
||||||
return buf;
|
|
||||||
#else
|
|
||||||
return {};
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------ Write ------------------------------
|
|
||||||
void VirtualInterface::writePacket(const std::vector<uint8_t>& packet) {
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
|
||||||
ssize_t n = write(mFd, packet.data(), packet.size());
|
|
||||||
if (n < 0)
|
|
||||||
throw std::runtime_error("write() failed: " + std::string(strerror(errno)));
|
|
||||||
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
WINTUN_PACKET* tx = WintunAllocateSendPacket(mSession, (DWORD)packet.size());
|
|
||||||
if (!tx) throw std::runtime_error("WintunAllocateSendPacket failed");
|
|
||||||
memcpy(tx->Data, packet.data(), packet.size());
|
|
||||||
WintunSendPacket(mSession, tx);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------ Accessors ------------------------------
|
|
||||||
const std::string& VirtualInterface::getName() const { return mIfName; }
|
|
||||||
|
|
||||||
int VirtualInterface::getFd() const { return mFd; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// IP CONFIGURATION
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
bool VirtualInterface::configureIP(uint32_t clientIP, uint32_t serverIP,
|
|
||||||
uint8_t prefixLen, uint16_t mtu)
|
|
||||||
{
|
|
||||||
#if defined(__linux__)
|
|
||||||
return mApplyLinuxIP(clientIP, serverIP, prefixLen, mtu);
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
return mApplyMacOSIP(clientIP, serverIP, prefixLen, mtu);
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
return mApplyWindowsIP(clientIP, serverIP, prefixLen, mtu);
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// Linux
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
bool VirtualInterface::mApplyLinuxIP(uint32_t clientIP, uint32_t serverIP,
|
|
||||||
uint8_t prefixLen, uint16_t mtu)
|
|
||||||
{
|
|
||||||
char cmd[512];
|
|
||||||
|
|
||||||
std::string ipStr = ipv4ToString(clientIP);
|
|
||||||
std::string peerStr = ipv4ToString(serverIP);
|
|
||||||
|
|
||||||
snprintf(cmd, sizeof(cmd),
|
|
||||||
"ip addr add %s/%d peer %s dev %s",
|
|
||||||
ipStr.c_str(), prefixLen, peerStr.c_str(), mIfName.c_str());
|
|
||||||
system(cmd);
|
|
||||||
|
|
||||||
snprintf(cmd, sizeof(cmd),
|
|
||||||
"ip link set dev %s up mtu %d", mIfName.c_str(), mtu);
|
|
||||||
system(cmd);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// macOS (utun)
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
bool VirtualInterface::mApplyMacOSIP(uint32_t clientIP, uint32_t serverIP,
|
|
||||||
uint8_t prefixLen, uint16_t mtu)
|
|
||||||
{
|
|
||||||
char cmd[512];
|
|
||||||
|
|
||||||
std::string ipStr = ipv4ToString(clientIP);
|
|
||||||
std::string peerStr = ipv4ToString(serverIP);
|
|
||||||
|
|
||||||
// Set netmask (/24 CIDR temporarily with raw command, improve later)
|
|
||||||
snprintf(cmd, sizeof(cmd),
|
|
||||||
"ifconfig utun0 %s %s mtu %d netmask 255.255.255.0 up",
|
|
||||||
ipStr.c_str(), peerStr.c_str(), mtu);
|
|
||||||
system(cmd);
|
|
||||||
|
|
||||||
Utils::log("Executed command: " + std::string(cmd));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// Windows (Wintun)
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
bool VirtualInterface::mApplyWindowsIP(uint32_t clientIP, uint32_t serverIP,
|
|
||||||
uint8_t prefixLen, uint16_t mtu)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
char ip[32], gw[32];
|
|
||||||
strcpy(ip, ipv4ToString(clientIP).c_str());
|
|
||||||
strcpy(gw, ipv4ToString(serverIP).c_str());
|
|
||||||
|
|
||||||
char cmd[256];
|
|
||||||
snprintf(cmd, sizeof(cmd),
|
|
||||||
"netsh interface ip set address name=\"%s\" static %s %d.%d.%d.%d",
|
|
||||||
mIfName.c_str(), ip,
|
|
||||||
(prefixLen <= 8) ? ((prefixLen << 3) & 255) : 255,
|
|
||||||
255, 255, 255);
|
|
||||||
system(cmd);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
} // namespace ColumnLynx::Net
|
|
||||||
@@ -10,76 +10,46 @@
|
|||||||
#include <columnlynx/server/net/udp/udp_server.hpp>
|
#include <columnlynx/server/net/udp/udp_server.hpp>
|
||||||
#include <columnlynx/common/libsodium_wrapper.hpp>
|
#include <columnlynx/common/libsodium_wrapper.hpp>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <cxxopts/cxxopts.hpp>
|
|
||||||
#include <columnlynx/common/net/virtual_interface.hpp>
|
|
||||||
|
|
||||||
using asio::ip::tcp;
|
using asio::ip::tcp;
|
||||||
using namespace ColumnLynx::Utils;
|
using namespace ColumnLynx::Utils;
|
||||||
using namespace ColumnLynx::Net::TCP;
|
using namespace ColumnLynx::Net::TCP;
|
||||||
using namespace ColumnLynx::Net::UDP;
|
using namespace ColumnLynx::Net::UDP;
|
||||||
using namespace ColumnLynx::Net;
|
|
||||||
using namespace ColumnLynx;
|
|
||||||
|
|
||||||
volatile sig_atomic_t done = 0;
|
volatile sig_atomic_t done = 0;
|
||||||
|
|
||||||
void signalHandler(int signum) {
|
/*void signalHandler(int signum) {
|
||||||
if (signum == SIGINT || signum == SIGTERM) {
|
if (signum == SIGINT || signum == SIGTERM) {
|
||||||
log("Received termination signal. Shutting down server gracefully.");
|
log("Received termination signal. Shutting down server gracefully.");
|
||||||
done = 1;
|
done = 1;
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
// Capture SIGINT and SIGTERM for graceful shutdown
|
|
||||||
struct sigaction action;
|
|
||||||
memset(&action, 0, sizeof(struct sigaction));
|
|
||||||
action.sa_handler = signalHandler;
|
|
||||||
sigaction(SIGINT, &action, nullptr);
|
|
||||||
sigaction(SIGTERM, &action, nullptr);
|
|
||||||
|
|
||||||
cxxopts::Options options("columnlynx_server", "ColumnLynx Server Application");
|
|
||||||
|
|
||||||
options.add_options()
|
|
||||||
("h,help", "Print help")
|
|
||||||
("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();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto result = options.parse(argc, argv);
|
// Catch SIGINT and SIGTERM for graceful shutdown
|
||||||
if (result.count("help")) {
|
/*struct sigaction action;
|
||||||
std::cout << options.help() << std::endl;
|
memset(&action, 0, sizeof(struct sigaction));
|
||||||
std::cout << "This software is licensed under the GPLv2-only license OR the GPLv3 license.\n";
|
action.sa_handler = signalHandler;
|
||||||
std::cout << "Copyright (C) 2025, The ColumnLynx Contributors.\n";
|
sigaction(SIGINT, &action, nullptr);
|
||||||
std::cout << "This software is provided under ABSOLUTELY NO WARRANTY, to the extent permitted by law.\n";
|
sigaction(SIGTERM, &action, nullptr);*/
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 GPLv3. See LICENSE for details.");
|
||||||
|
|
||||||
#if defined(__WIN32__)
|
|
||||||
WintunInitialize();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::shared_ptr<VirtualInterface> tun = std::make_shared<VirtualInterface>("utun0");
|
|
||||||
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();
|
LibSodiumWrapper sodiumWrapper = LibSodiumWrapper();
|
||||||
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
|
log("Server private key: " + bytesToHexString(sodiumWrapper.getPrivateKey(), crypto_sign_SECRETKEYBYTES)); // TEMP, remove later
|
||||||
|
|
||||||
bool hostRunning = true;
|
bool hostRunning = 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);
|
||||||
auto udpServer = std::make_shared<UDPServer>(io, serverPort(), &hostRunning, ipv4Only, tun);
|
auto udpServer = std::make_shared<UDPServer>(io, serverPort(), &hostRunning);
|
||||||
|
|
||||||
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) {
|
||||||
@@ -102,21 +72,7 @@ int main(int argc, char** argv) {
|
|||||||
log("Server started on port " + std::to_string(serverPort()));
|
log("Server started on port " + std::to_string(serverPort()));
|
||||||
|
|
||||||
while (!done) {
|
while (!done) {
|
||||||
auto packet = tun->readPacket();
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
if (packet.empty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint8_t* ip = packet.data();
|
|
||||||
uint32_t dstIP = ntohl(*(uint32_t*)(ip + 16)); // IPv4 destination address offset in IPv6-mapped header
|
|
||||||
|
|
||||||
auto session = SessionRegistry::getInstance().getByIP(dstIP);
|
|
||||||
if (!session) {
|
|
||||||
Utils::warn("TUN: No session found for destination IP " + VirtualInterface::ipv4ToString(dstIP));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
udpServer->sendData(session->sessionID, std::string(packet.begin(), packet.end()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log("Shutting down server...");
|
log("Shutting down server...");
|
||||||
|
|||||||
@@ -1,263 +0,0 @@
|
|||||||
// tcp_connection.cpp - TCP Connection 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/server/net/tcp/tcp_connection.hpp>
|
|
||||||
|
|
||||||
namespace ColumnLynx::Net::TCP {
|
|
||||||
void TCPConnection::start() {
|
|
||||||
mHandler->onMessage([this](AnyMessageType type, const std::string& data) {
|
|
||||||
mHandleMessage(static_cast<ClientMessageType>(MessageHandler::toUint8(type)), data);
|
|
||||||
});
|
|
||||||
|
|
||||||
mHandler->onDisconnect([this](const asio::error_code& ec) {
|
|
||||||
Utils::log("Client disconnected: " + mHandler->socket().remote_endpoint().address().to_string() + " - " + ec.message());
|
|
||||||
disconnect();
|
|
||||||
});
|
|
||||||
|
|
||||||
mHandler->start();
|
|
||||||
mStartHeartbeat();
|
|
||||||
|
|
||||||
// Placeholder for message handling setup
|
|
||||||
Utils::log("Client connected: " + mHandler->socket().remote_endpoint().address().to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPConnection::sendMessage(ServerMessageType type, const std::string& data) {
|
|
||||||
if (mHandler) {
|
|
||||||
mHandler->sendMessage(type, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPConnection::setDisconnectCallback(std::function<void(std::shared_ptr<TCPConnection>)> cb) {
|
|
||||||
mOnDisconnect = std::move(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPConnection::disconnect() {
|
|
||||||
std::string ip = mHandler->socket().remote_endpoint().address().to_string();
|
|
||||||
|
|
||||||
mHandler->sendMessage(ServerMessageType::GRACEFUL_DISCONNECT, "Server initiated disconnect.");
|
|
||||||
mHeartbeatTimer.cancel();
|
|
||||||
asio::error_code ec;
|
|
||||||
mHandler->socket().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
|
|
||||||
mHandler->socket().close(ec);
|
|
||||||
|
|
||||||
SessionRegistry::getInstance().erase(mConnectionSessionID);
|
|
||||||
SessionRegistry::getInstance().deallocIP(mConnectionSessionID);
|
|
||||||
|
|
||||||
Utils::log("Closed connection to " + ip);
|
|
||||||
|
|
||||||
if (mOnDisconnect) {
|
|
||||||
mOnDisconnect(shared_from_this());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t TCPConnection::getSessionID() const {
|
|
||||||
return mConnectionSessionID;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::array<uint8_t, 32> TCPConnection::getAESKey() const {
|
|
||||||
return mConnectionAESKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPConnection::mStartHeartbeat() {
|
|
||||||
auto self = shared_from_this();
|
|
||||||
mHeartbeatTimer.expires_after(std::chrono::seconds(5));
|
|
||||||
mHeartbeatTimer.async_wait([this, self](const asio::error_code& ec) {
|
|
||||||
if (ec == asio::error::operation_aborted) {
|
|
||||||
return; // Timer was cancelled
|
|
||||||
}
|
|
||||||
|
|
||||||
auto now = std::chrono::steady_clock::now();
|
|
||||||
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - self->mLastHeartbeatReceived).count();
|
|
||||||
|
|
||||||
if (elapsed >= 15) { // 3 missed heartbeats
|
|
||||||
Utils::error("Missed 3 heartbeats. I think the other party (client " + std::to_string(self->mConnectionSessionID) + ") might have died! Disconnecting.");
|
|
||||||
|
|
||||||
// Remove socket forcefully, client is dead
|
|
||||||
asio::error_code ec;
|
|
||||||
mHandler->socket().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
|
|
||||||
mHandler->socket().close(ec);
|
|
||||||
|
|
||||||
SessionRegistry::getInstance().erase(self->mConnectionSessionID);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self->sendMessage(ServerMessageType::HEARTBEAT);
|
|
||||||
Utils::log("Sent HEARTBEAT to client " + std::to_string(self->mConnectionSessionID));
|
|
||||||
self->mLastHeartbeatSent = now;
|
|
||||||
|
|
||||||
self->mStartHeartbeat(); // Recursive
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPConnection::mHandleMessage(ClientMessageType type, const std::string& data) {
|
|
||||||
std::string reqAddr = mHandler->socket().remote_endpoint().address().to_string();
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case ClientMessageType::HANDSHAKE_INIT: {
|
|
||||||
Utils::log("Received HANDSHAKE_INIT from " + reqAddr);
|
|
||||||
|
|
||||||
if (data.size() < 1 + crypto_box_PUBLICKEYBYTES) {
|
|
||||||
Utils::warn("HANDSHAKE_INIT from " + reqAddr + " is too short.");
|
|
||||||
disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t clientProtoVer = static_cast<uint8_t>(data[0]);
|
|
||||||
if (clientProtoVer != Utils::protocolVersion()) {
|
|
||||||
Utils::warn("Client protocol version mismatch from " + reqAddr + ". Expected " +
|
|
||||||
std::to_string(Utils::protocolVersion()) + ", got " + std::to_string(clientProtoVer) + ".");
|
|
||||||
disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::log("Client protocol version " + std::to_string(clientProtoVer) + " accepted from " + reqAddr + ".");
|
|
||||||
|
|
||||||
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
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ClientMessageType::HANDSHAKE_CHALLENGE: {
|
|
||||||
Utils::log("Received HANDSHAKE_CHALLENGE from " + reqAddr);
|
|
||||||
|
|
||||||
// Convert to byte array
|
|
||||||
uint8_t challengeData[32];
|
|
||||||
std::memcpy(challengeData, data.data(), std::min(data.size(), sizeof(challengeData)));
|
|
||||||
|
|
||||||
// Sign the challenge
|
|
||||||
Signature sig = Utils::LibSodiumWrapper::signMessage(
|
|
||||||
challengeData, sizeof(challengeData),
|
|
||||||
mLibSodiumWrapper->getPrivateKey()
|
|
||||||
);
|
|
||||||
|
|
||||||
mHandler->sendMessage(ServerMessageType::HANDSHAKE_CHALLENGE_RESPONSE, Utils::uint8ArrayToString(sig.data(), sig.size())); // Placeholder response
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ClientMessageType::HANDSHAKE_EXCHANGE_KEY: {
|
|
||||||
Utils::log("Received HANDSHAKE_EXCHANGE_KEY from " + reqAddr);
|
|
||||||
|
|
||||||
// Extract encrypted AES key and nonce (nonce is the first 24 bytes, rest is the ciphertext)
|
|
||||||
if (data.size() < 24) { // Minimum size check (nonce)
|
|
||||||
Utils::warn("HANDSHAKE_EXCHANGE_KEY from " + reqAddr + " is too short.");
|
|
||||||
disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsymNonce nonce{};
|
|
||||||
std::memcpy(nonce.data(), data.data(), nonce.size());
|
|
||||||
std::vector<uint8_t> ciphertext(data.size() - nonce.size());
|
|
||||||
std::memcpy(ciphertext.data(), data.data() + nonce.size(), ciphertext.size());
|
|
||||||
try {
|
|
||||||
std::array<uint8_t, 32> arrayPrivateKey;
|
|
||||||
std::copy(mLibSodiumWrapper->getXPrivateKey(),
|
|
||||||
mLibSodiumWrapper->getXPrivateKey() + 32,
|
|
||||||
arrayPrivateKey.begin());
|
|
||||||
|
|
||||||
// Decrypt the AES key using the client's public key and server's private key
|
|
||||||
std::vector<uint8_t> decrypted = Utils::LibSodiumWrapper::decryptAsymmetric(
|
|
||||||
ciphertext.data(), ciphertext.size(),
|
|
||||||
nonce,
|
|
||||||
mConnectionPublicKey,
|
|
||||||
arrayPrivateKey
|
|
||||||
);
|
|
||||||
|
|
||||||
if (decrypted.size() != 32) {
|
|
||||||
Utils::warn("Decrypted HANDSHAKE_EXCHANGE_KEY from " + reqAddr + " has invalid size.");
|
|
||||||
disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::memcpy(mConnectionAESKey.data(), decrypted.data(), decrypted.size());
|
|
||||||
|
|
||||||
// Make a Session ID
|
|
||||||
randombytes_buf(&mConnectionSessionID, sizeof(mConnectionSessionID));
|
|
||||||
|
|
||||||
// Encrypt the Session ID with the established AES key (using symmetric encryption, nonce can be all zeros for this purpose)
|
|
||||||
Nonce symNonce{}; // All zeros
|
|
||||||
|
|
||||||
uint32_t clientIP = SessionRegistry::getInstance().getFirstAvailableIP();
|
|
||||||
|
|
||||||
if (clientIP == 0) {
|
|
||||||
Utils::warn("Out of available IPs! Disconnecting client " + reqAddr);
|
|
||||||
disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Protocol::TunConfig tunConfig{};
|
|
||||||
tunConfig.version = Utils::protocolVersion();
|
|
||||||
tunConfig.prefixLength = 24;
|
|
||||||
tunConfig.mtu = 1420;
|
|
||||||
tunConfig.serverIP = htonl(0x0A0A0001); // 10.10.0.1
|
|
||||||
tunConfig.clientIP = htonl(clientIP); // 10.10.0.X
|
|
||||||
tunConfig.dns1 = htonl(0x08080808); // 8.8.8.8
|
|
||||||
tunConfig.dns2 = 0;
|
|
||||||
|
|
||||||
SessionRegistry::getInstance().lockIP(mConnectionSessionID, clientIP);
|
|
||||||
|
|
||||||
uint64_t sessionIDNet = Utils::chtobe64(mConnectionSessionID);
|
|
||||||
|
|
||||||
std::vector<uint8_t> payload(sizeof(uint64_t) + sizeof(tunConfig));
|
|
||||||
std::memcpy(payload.data(), &sessionIDNet, sizeof(uint64_t));
|
|
||||||
std::memcpy(payload.data() + sizeof(uint64_t), &tunConfig, sizeof(tunConfig));
|
|
||||||
|
|
||||||
std::vector<uint8_t> encryptedPayload = Utils::LibSodiumWrapper::encryptMessage(
|
|
||||||
payload.data(), payload.size(),
|
|
||||||
mConnectionAESKey, symNonce
|
|
||||||
);
|
|
||||||
|
|
||||||
mHandler->sendMessage(ServerMessageType::HANDSHAKE_EXCHANGE_KEY_CONFIRM, Utils::uint8ArrayToString(encryptedPayload.data(), encryptedPayload.size()));
|
|
||||||
|
|
||||||
// Add to session registry
|
|
||||||
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);
|
|
||||||
SessionRegistry::getInstance().put(mConnectionSessionID, std::move(session));
|
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
Utils::error("Failed to decrypt HANDSHAKE_EXCHANGE_KEY from " + reqAddr + ": " + e.what());
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ClientMessageType::HEARTBEAT: {
|
|
||||||
Utils::log("Received HEARTBEAT from " + reqAddr);
|
|
||||||
mHandler->sendMessage(ServerMessageType::HEARTBEAT_ACK, ""); // Send ACK
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ClientMessageType::HEARTBEAT_ACK: {
|
|
||||||
Utils::log("Received HEARTBEAT_ACK from " + reqAddr);
|
|
||||||
mLastHeartbeatReceived = std::chrono::steady_clock::now();
|
|
||||||
mMissedHeartbeats = 0; // Reset missed heartbeat count
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ClientMessageType::GRACEFUL_DISCONNECT: {
|
|
||||||
Utils::log("Received GRACEFUL_DISCONNECT from " + reqAddr + ": " + data);
|
|
||||||
disconnect();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
Utils::warn("Unhandled message type from " + reqAddr);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,14 @@
|
|||||||
namespace ColumnLynx::Net::TCP {
|
namespace ColumnLynx::Net::TCP {
|
||||||
|
|
||||||
void TCPServer::mStartAccept() {
|
void TCPServer::mStartAccept() {
|
||||||
|
// A bit of a shotty implementation, might improve later
|
||||||
|
/*std::cout << "Host running pointer: " << *mHostRunning << std::endl;
|
||||||
|
|
||||||
|
if (mHostRunning != nullptr && !(*mHostRunning)) {
|
||||||
|
Utils::log("Server is stopping, not accepting new connections.");
|
||||||
|
return;
|
||||||
|
}*/
|
||||||
|
|
||||||
mAcceptor.async_accept(
|
mAcceptor.async_accept(
|
||||||
[this](asio::error_code ec, asio::ip::tcp::socket socket) {
|
[this](asio::error_code ec, asio::ip::tcp::socket socket) {
|
||||||
if (ec) {
|
if (ec) {
|
||||||
|
|||||||
@@ -10,6 +10,12 @@
|
|||||||
|
|
||||||
namespace ColumnLynx::Net::UDP {
|
namespace ColumnLynx::Net::UDP {
|
||||||
void UDPServer::mStartReceive() {
|
void UDPServer::mStartReceive() {
|
||||||
|
// A bit of a shotty implementation, might improve later
|
||||||
|
/*if (mHostRunning != nullptr && !(*mHostRunning)) {
|
||||||
|
Utils::log("Server is stopping, not receiving new packets.");
|
||||||
|
return;
|
||||||
|
}*/
|
||||||
|
|
||||||
mSocket.async_receive_from(
|
mSocket.async_receive_from(
|
||||||
asio::buffer(mRecvBuffer), mRemoteEndpoint,
|
asio::buffer(mRecvBuffer), mRemoteEndpoint,
|
||||||
[this](asio::error_code ec, std::size_t bytes) {
|
[this](asio::error_code ec, std::size_t bytes) {
|
||||||
@@ -48,35 +54,32 @@ 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, "udp-data"
|
hdr->nonce,
|
||||||
//std::string(reinterpret_cast<const char*>(&sessionID), sizeof(uint64_t))
|
"udp-data"
|
||||||
);
|
);
|
||||||
|
|
||||||
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::debug("UDP: Received packet from " + mRemoteEndpoint.address().to_string() + " - Payload: " + payloadStr);
|
Utils::log("UDP: Received packet from " + mRemoteEndpoint.address().to_string() + " - Payload: " + payloadStr);
|
||||||
|
|
||||||
if (mTun) {
|
// TODO: Process the packet payload, for now just echo back
|
||||||
mTun->writePacket(plaintext); // Send to virtual interface
|
mSendData(sessionID, std::string(plaintext.begin(), plaintext.end()));
|
||||||
}
|
} 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UDPServer::sendData(const uint64_t sessionID, const std::string& data) {
|
void UDPServer::mSendData(const uint64_t sessionID, const std::string& data) {
|
||||||
|
// TODO: Implement
|
||||||
|
|
||||||
// Find the IPv4/IPv6 endpoint for the session
|
// Find the IPv4/IPv6 endpoint for the session
|
||||||
std::shared_ptr<const SessionState> session = SessionRegistry::getInstance().get(sessionID);
|
std::shared_ptr<const SessionState> session = SessionRegistry::getInstance().get(sessionID);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
@@ -97,7 +100,6 @@ 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;
|
||||||
@@ -114,7 +116,7 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
|
|
||||||
// Send packet
|
// Send packet
|
||||||
mSocket.send_to(asio::buffer(packet), endpoint);
|
mSocket.send_to(asio::buffer(packet), endpoint);
|
||||||
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()) + ")");
|
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()) + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
void UDPServer::stop() {
|
void UDPServer::stop() {
|
||||||
|
|||||||
338
third_party/wintun/LICENSE_1_0_GPL-2.0-only.txt
vendored
338
third_party/wintun/LICENSE_1_0_GPL-2.0-only.txt
vendored
@@ -1,338 +0,0 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
Version 2, June 1991
|
|
||||||
|
|
||||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
|
||||||
<https://fsf.org/>
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The licenses for most software are designed to take away your
|
|
||||||
freedom to share and change it. By contrast, the GNU General Public
|
|
||||||
License is intended to guarantee your freedom to share and change free
|
|
||||||
software--to make sure the software is free for all its users. This
|
|
||||||
General Public License applies to most of the Free Software
|
|
||||||
Foundation's software and to any other program whose authors commit to
|
|
||||||
using it. (Some other Free Software Foundation software is covered by
|
|
||||||
the GNU Lesser General Public License instead.) You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
|
||||||
have the freedom to distribute copies of free software (and charge for
|
|
||||||
this service if you wish), that you receive source code or can get it
|
|
||||||
if you want it, that you can change the software or use pieces of it
|
|
||||||
in new free programs; and that you know you can do these things.
|
|
||||||
|
|
||||||
To protect your rights, we need to make restrictions that forbid
|
|
||||||
anyone to deny you these rights or to ask you to surrender the rights.
|
|
||||||
These restrictions translate to certain responsibilities for you if you
|
|
||||||
distribute copies of the software, or if you modify it.
|
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
|
||||||
gratis or for a fee, you must give the recipients all the rights that
|
|
||||||
you have. You must make sure that they, too, receive or can get the
|
|
||||||
source code. And you must show them these terms so they know their
|
|
||||||
rights.
|
|
||||||
|
|
||||||
We protect your rights with two steps: (1) copyright the software, and
|
|
||||||
(2) offer you this license which gives you legal permission to copy,
|
|
||||||
distribute and/or modify the software.
|
|
||||||
|
|
||||||
Also, for each author's protection and ours, we want to make certain
|
|
||||||
that everyone understands that there is no warranty for this free
|
|
||||||
software. If the software is modified by someone else and passed on, we
|
|
||||||
want its recipients to know that what they have is not the original, so
|
|
||||||
that any problems introduced by others will not reflect on the original
|
|
||||||
authors' reputations.
|
|
||||||
|
|
||||||
Finally, any free program is threatened constantly by software
|
|
||||||
patents. We wish to avoid the danger that redistributors of a free
|
|
||||||
program will individually obtain patent licenses, in effect making the
|
|
||||||
program proprietary. To prevent this, we have made it clear that any
|
|
||||||
patent must be licensed for everyone's free use or not licensed at all.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow.
|
|
||||||
|
|
||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. This License applies to any program or other work which contains
|
|
||||||
a notice placed by the copyright holder saying it may be distributed
|
|
||||||
under the terms of this General Public License. The "Program", below,
|
|
||||||
refers to any such program or work, and a "work based on the Program"
|
|
||||||
means either the Program or any derivative work under copyright law:
|
|
||||||
that is to say, a work containing the Program or a portion of it,
|
|
||||||
either verbatim or with modifications and/or translated into another
|
|
||||||
language. (Hereinafter, translation is included without limitation in
|
|
||||||
the term "modification".) Each licensee is addressed as "you".
|
|
||||||
|
|
||||||
Activities other than copying, distribution and modification are not
|
|
||||||
covered by this License; they are outside its scope. The act of
|
|
||||||
running the Program is not restricted, and the output from the Program
|
|
||||||
is covered only if its contents constitute a work based on the
|
|
||||||
Program (independent of having been made by running the Program).
|
|
||||||
Whether that is true depends on what the Program does.
|
|
||||||
|
|
||||||
1. You may copy and distribute verbatim copies of the Program's
|
|
||||||
source code as you receive it, in any medium, provided that you
|
|
||||||
conspicuously and appropriately publish on each copy an appropriate
|
|
||||||
copyright notice and disclaimer of warranty; keep intact all the
|
|
||||||
notices that refer to this License and to the absence of any warranty;
|
|
||||||
and give any other recipients of the Program a copy of this License
|
|
||||||
along with the Program.
|
|
||||||
|
|
||||||
You may charge a fee for the physical act of transferring a copy, and
|
|
||||||
you may at your option offer warranty protection in exchange for a fee.
|
|
||||||
|
|
||||||
2. You may modify your copy or copies of the Program or any portion
|
|
||||||
of it, thus forming a work based on the Program, and copy and
|
|
||||||
distribute such modifications or work under the terms of Section 1
|
|
||||||
above, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) You must cause the modified files to carry prominent notices
|
|
||||||
stating that you changed the files and the date of any change.
|
|
||||||
|
|
||||||
b) You must cause any work that you distribute or publish, that in
|
|
||||||
whole or in part contains or is derived from the Program or any
|
|
||||||
part thereof, to be licensed as a whole at no charge to all third
|
|
||||||
parties under the terms of this License.
|
|
||||||
|
|
||||||
c) If the modified program normally reads commands interactively
|
|
||||||
when run, you must cause it, when started running for such
|
|
||||||
interactive use in the most ordinary way, to print or display an
|
|
||||||
announcement including an appropriate copyright notice and a
|
|
||||||
notice that there is no warranty (or else, saying that you provide
|
|
||||||
a warranty) and that users may redistribute the program under
|
|
||||||
these conditions, and telling the user how to view a copy of this
|
|
||||||
License. (Exception: if the Program itself is interactive but
|
|
||||||
does not normally print such an announcement, your work based on
|
|
||||||
the Program is not required to print an announcement.)
|
|
||||||
|
|
||||||
These requirements apply to the modified work as a whole. If
|
|
||||||
identifiable sections of that work are not derived from the Program,
|
|
||||||
and can be reasonably considered independent and separate works in
|
|
||||||
themselves, then this License, and its terms, do not apply to those
|
|
||||||
sections when you distribute them as separate works. But when you
|
|
||||||
distribute the same sections as part of a whole which is a work based
|
|
||||||
on the Program, the distribution of the whole must be on the terms of
|
|
||||||
this License, whose permissions for other licensees extend to the
|
|
||||||
entire whole, and thus to each and every part regardless of who wrote it.
|
|
||||||
|
|
||||||
Thus, it is not the intent of this section to claim rights or contest
|
|
||||||
your rights to work written entirely by you; rather, the intent is to
|
|
||||||
exercise the right to control the distribution of derivative or
|
|
||||||
collective works based on the Program.
|
|
||||||
|
|
||||||
In addition, mere aggregation of another work not based on the Program
|
|
||||||
with the Program (or with a work based on the Program) on a volume of
|
|
||||||
a storage or distribution medium does not bring the other work under
|
|
||||||
the scope of this License.
|
|
||||||
|
|
||||||
3. You may copy and distribute the Program (or a work based on it,
|
|
||||||
under Section 2) in object code or executable form under the terms of
|
|
||||||
Sections 1 and 2 above provided that you also do one of the following:
|
|
||||||
|
|
||||||
a) Accompany it with the complete corresponding machine-readable
|
|
||||||
source code, which must be distributed under the terms of Sections
|
|
||||||
1 and 2 above on a medium customarily used for software interchange; or,
|
|
||||||
|
|
||||||
b) Accompany it with a written offer, valid for at least three
|
|
||||||
years, to give any third party, for a charge no more than your
|
|
||||||
cost of physically performing source distribution, a complete
|
|
||||||
machine-readable copy of the corresponding source code, to be
|
|
||||||
distributed under the terms of Sections 1 and 2 above on a medium
|
|
||||||
customarily used for software interchange; or,
|
|
||||||
|
|
||||||
c) Accompany it with the information you received as to the offer
|
|
||||||
to distribute corresponding source code. (This alternative is
|
|
||||||
allowed only for noncommercial distribution and only if you
|
|
||||||
received the program in object code or executable form with such
|
|
||||||
an offer, in accord with Subsection b above.)
|
|
||||||
|
|
||||||
The source code for a work means the preferred form of the work for
|
|
||||||
making modifications to it. For an executable work, complete source
|
|
||||||
code means all the source code for all modules it contains, plus any
|
|
||||||
associated interface definition files, plus the scripts used to
|
|
||||||
control compilation and installation of the executable. However, as a
|
|
||||||
special exception, the source code distributed need not include
|
|
||||||
anything that is normally distributed (in either source or binary
|
|
||||||
form) with the major components (compiler, kernel, and so on) of the
|
|
||||||
operating system on which the executable runs, unless that component
|
|
||||||
itself accompanies the executable.
|
|
||||||
|
|
||||||
If distribution of executable or object code is made by offering
|
|
||||||
access to copy from a designated place, then offering equivalent
|
|
||||||
access to copy the source code from the same place counts as
|
|
||||||
distribution of the source code, even though third parties are not
|
|
||||||
compelled to copy the source along with the object code.
|
|
||||||
|
|
||||||
4. You may not copy, modify, sublicense, or distribute the Program
|
|
||||||
except as expressly provided under this License. Any attempt
|
|
||||||
otherwise to copy, modify, sublicense or distribute the Program is
|
|
||||||
void, and will automatically terminate your rights under this License.
|
|
||||||
However, parties who have received copies, or rights, from you under
|
|
||||||
this License will not have their licenses terminated so long as such
|
|
||||||
parties remain in full compliance.
|
|
||||||
|
|
||||||
5. You are not required to accept this License, since you have not
|
|
||||||
signed it. However, nothing else grants you permission to modify or
|
|
||||||
distribute the Program or its derivative works. These actions are
|
|
||||||
prohibited by law if you do not accept this License. Therefore, by
|
|
||||||
modifying or distributing the Program (or any work based on the
|
|
||||||
Program), you indicate your acceptance of this License to do so, and
|
|
||||||
all its terms and conditions for copying, distributing or modifying
|
|
||||||
the Program or works based on it.
|
|
||||||
|
|
||||||
6. Each time you redistribute the Program (or any work based on the
|
|
||||||
Program), the recipient automatically receives a license from the
|
|
||||||
original licensor to copy, distribute or modify the Program subject to
|
|
||||||
these terms and conditions. You may not impose any further
|
|
||||||
restrictions on the recipients' exercise of the rights granted herein.
|
|
||||||
You are not responsible for enforcing compliance by third parties to
|
|
||||||
this License.
|
|
||||||
|
|
||||||
7. If, as a consequence of a court judgment or allegation of patent
|
|
||||||
infringement or for any other reason (not limited to patent issues),
|
|
||||||
conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot
|
|
||||||
distribute so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you
|
|
||||||
may not distribute the Program at all. For example, if a patent
|
|
||||||
license would not permit royalty-free redistribution of the Program by
|
|
||||||
all those who receive copies directly or indirectly through you, then
|
|
||||||
the only way you could satisfy both it and this License would be to
|
|
||||||
refrain entirely from distribution of the Program.
|
|
||||||
|
|
||||||
If any portion of this section is held invalid or unenforceable under
|
|
||||||
any particular circumstance, the balance of the section is intended to
|
|
||||||
apply and the section as a whole is intended to apply in other
|
|
||||||
circumstances.
|
|
||||||
|
|
||||||
It is not the purpose of this section to induce you to infringe any
|
|
||||||
patents or other property right claims or to contest validity of any
|
|
||||||
such claims; this section has the sole purpose of protecting the
|
|
||||||
integrity of the free software distribution system, which is
|
|
||||||
implemented by public license practices. Many people have made
|
|
||||||
generous contributions to the wide range of software distributed
|
|
||||||
through that system in reliance on consistent application of that
|
|
||||||
system; it is up to the author/donor to decide if he or she is willing
|
|
||||||
to distribute software through any other system and a licensee cannot
|
|
||||||
impose that choice.
|
|
||||||
|
|
||||||
This section is intended to make thoroughly clear what is believed to
|
|
||||||
be a consequence of the rest of this License.
|
|
||||||
|
|
||||||
8. If the distribution and/or use of the Program is restricted in
|
|
||||||
certain countries either by patents or by copyrighted interfaces, the
|
|
||||||
original copyright holder who places the Program under this License
|
|
||||||
may add an explicit geographical distribution limitation excluding
|
|
||||||
those countries, so that distribution is permitted only in or among
|
|
||||||
countries not thus excluded. In such case, this License incorporates
|
|
||||||
the limitation as if written in the body of this License.
|
|
||||||
|
|
||||||
9. The Free Software Foundation may publish revised and/or new versions
|
|
||||||
of the General Public License from time to time. Such new versions will
|
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
|
||||||
address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the Program
|
|
||||||
specifies a version number of this License which applies to it and "any
|
|
||||||
later version", you have the option of following the terms and conditions
|
|
||||||
either of that version or of any later version published by the Free
|
|
||||||
Software Foundation. If the Program does not specify a version number of
|
|
||||||
this License, you may choose any version ever published by the Free Software
|
|
||||||
Foundation.
|
|
||||||
|
|
||||||
10. If you wish to incorporate parts of the Program into other free
|
|
||||||
programs whose distribution conditions are different, write to the author
|
|
||||||
to ask for permission. For software which is copyrighted by the Free
|
|
||||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
|
||||||
make exceptions for this. Our decision will be guided by the two goals
|
|
||||||
of preserving the free status of all derivatives of our free software and
|
|
||||||
of promoting the sharing and reuse of software generally.
|
|
||||||
|
|
||||||
NO WARRANTY
|
|
||||||
|
|
||||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
|
||||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
|
||||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
|
||||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
|
||||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
|
||||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
|
||||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
|
||||||
REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
|
||||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
|
||||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
|
||||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
|
||||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
|
||||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
|
||||||
POSSIBILITY OF SUCH DAMAGES.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Programs
|
|
||||||
|
|
||||||
If you develop a new program, and you want it to be of the greatest
|
|
||||||
possible use to the public, the best way to achieve this is to make it
|
|
||||||
free software which everyone can redistribute and change under these terms.
|
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest
|
|
||||||
to attach them to the start of each source file to most effectively
|
|
||||||
convey the exclusion of warranty; and each file should have at least
|
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
|
||||||
Copyright (C) <year> <name of author>
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along
|
|
||||||
with this program; if not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
If the program is interactive, make it output a short notice like this
|
|
||||||
when it starts in an interactive mode:
|
|
||||||
|
|
||||||
Gnomovision version 69, Copyright (C) year name of author
|
|
||||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
|
||||||
This is free software, and you are welcome to redistribute it
|
|
||||||
under certain conditions; type `show c' for details.
|
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, the commands you use may
|
|
||||||
be called something other than `show w' and `show c'; they could even be
|
|
||||||
mouse-clicks or menu items--whatever suits your program.
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or your
|
|
||||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
|
||||||
necessary. Here is a sample; alter the names:
|
|
||||||
|
|
||||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
|
||||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
|
||||||
|
|
||||||
<signature of Moe Ghoul>, 1 April 1989
|
|
||||||
Moe Ghoul, President of Vice
|
|
||||||
|
|
||||||
This General Public License does not permit incorporating your program into
|
|
||||||
proprietary programs. If your program is a subroutine library, you may
|
|
||||||
consider it more useful to permit linking proprietary applications with the
|
|
||||||
library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License.
|
|
||||||
19
third_party/wintun/LICENSE_1_0_MIT.txt
vendored
19
third_party/wintun/LICENSE_1_0_MIT.txt
vendored
@@ -1,19 +0,0 @@
|
|||||||
Copyright (c) 2018 WireGuard LLC
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
Reference in New Issue
Block a user