Added protocol version to TCP header, also some launch args

This commit is contained in:
2025-11-09 02:26:44 +01:00
parent 098afeb4d8
commit 2cbcf70c56
11 changed files with 3124 additions and 10 deletions

View File

@@ -3,8 +3,15 @@
## ASIO C++ Library
- **Name:** ASIO (standalone)
- **Website:** https://think-async.com/Asio/
- **Copyright:** (c) 2003-2024 Christopher M. Kohlhoff
- **Copyright:** (c) 2003-2025 Christopher M. Kohlhoff
- **License:** Boost Software License, Version 1.0
- **License Text:** See `third_party/asio/LICENSE_1_0.txt`
This project uses the standalone version of the ASIO C++ library for asynchronous networking.
## CXXOPTS C++ Library
- **Name:** cxxopts
- **Website:** https://github.com/jarro2783/cxxopts/
- **Copyright:** (c) 2014-2025 Christopher M. Kohlhoff
- **License:** MIT License
- **License Text:** See `third_party/cxxopts/LICENSE_1_0.txt`

View File

@@ -43,6 +43,7 @@ The data section is unspecified. It may change depending on the **Packet ID**. I
| Type | Length | Name | Description |
|:-----|:-------|:-----|:------------|
| uint8_t | 1 byte | **Header** - Protocol Version | Supported protocol version |
| uint8_t | 1 byte | **Header** - Packet Type | General type of packet |
| uint8_t/byte array | variable | Data | General packet data - changes for packet to packet |

View File

@@ -45,7 +45,16 @@ namespace ColumnLynx::Net::TCP {
// Init connection handshake
Utils::log("Sending handshake init to server.");
mHandler->sendMessage(ClientMessageType::HANDSHAKE_INIT, Utils::uint8ArrayToString(mLibSodiumWrapper->getXPublicKey(), crypto_box_PUBLICKEYBYTES));
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
);
mHandler->sendMessage(ClientMessageType::HANDSHAKE_INIT, Utils::uint8ArrayToString(payload.data(), payload.size()));
} else {
Utils::error("Client connect failed: " + ec.message());
}

View File

@@ -18,7 +18,7 @@ namespace ColumnLynx::Net::UDP {
const std::string& port,
std::array<uint8_t, 32>* aesKeyRef,
uint64_t* sessionIDRef)
: mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port), mAesKeyRef(aesKeyRef), mSessionIDRef(sessionIDRef) {}
: mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port), mAesKeyRef(aesKeyRef), mSessionIDRef(sessionIDRef) { mStartReceive(); }
void start() {
auto endpoints = mResolver.resolve(asio::ip::udp::v4(), mHost, mPort);
@@ -58,7 +58,73 @@ namespace ColumnLynx::Net::UDP {
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:
void 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 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::resolver mResolver;
asio::ip::udp::endpoint mRemoteEndpoint;
@@ -66,5 +132,6 @@ namespace ColumnLynx::Net::UDP {
std::string mPort;
std::array<uint8_t, 32>* mAesKeyRef;
uint64_t* mSessionIDRef;
std::array<uint8_t, 2048> mRecvBuffer; // Adjust size as needed
};
}

View File

@@ -5,6 +5,8 @@
#pragma once
#include <iostream>
#include <string>
#include <cstdint>
#include <array>
#ifdef _WIN32
#include <winsock2.h>
@@ -22,6 +24,7 @@ namespace ColumnLynx::Utils {
std::string getHostname();
std::string getVersion();
unsigned short serverPort();
unsigned char protocolVersion();
// Raw byte to hex string conversion helper
std::string bytesToHexString(const uint8_t* bytes, size_t length);

View File

@@ -92,7 +92,24 @@ namespace ColumnLynx::Net::TCP {
switch (type) {
case ClientMessageType::HANDSHAKE_INIT: {
Utils::log("Received HANDSHAKE_INIT from " + reqAddr);
std::memcpy(mConnectionPublicKey.data(), data.data(), std::min(data.size(), sizeof(mConnectionPublicKey))); // Store the client's public key (for identification)
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;
}

2930
include/cxxopts/cxxopts.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@
#include <columnlynx/common/panic_handler.hpp>
#include <columnlynx/client/net/tcp/tcp_client.hpp>
#include <columnlynx/client/net/udp/udp_client.hpp>
#include <cxxopts/cxxopts.hpp>
using asio::ip::tcp;
using namespace ColumnLynx::Utils;
@@ -32,6 +33,22 @@ int main(int argc, char** argv) {
PanicHandler::init();
cxxopts::Options options("columnlynx_client", "ColumnLynx Client Application");
options.add_options()
("h,help", "Print help")
("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())));
auto result = options.parse(argc, argv);
if (result.count("help")) {
std::cout << options.help() << std::endl;
return 0;
}
auto host = result["server"].as<std::string>();
auto port = std::to_string(result["port"].as<uint16_t>());
try {
log("ColumnLynx Client, Version " + getVersion());
log("This software is licensed under the GPLv3. See LICENSE for details.");
@@ -42,8 +59,8 @@ int main(int argc, char** argv) {
uint64_t sessionID = 0;
asio::io_context io;
auto client = std::make_shared<ColumnLynx::Net::TCP::TCPClient>(io, "127.0.0.1", std::to_string(serverPort()), &sodiumWrapper, &aesKey, &sessionID);
auto udpClient = std::make_shared<ColumnLynx::Net::UDP::UDPClient>(io, "127.0.0.1", std::to_string(serverPort()), &aesKey, &sessionID);
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);
client->start();
udpClient->start();
@@ -54,7 +71,7 @@ int main(int argc, char** argv) {
});
ioThread.detach();
log("Client connected to 127.0.0.1:" + std::to_string(serverPort()));
log("Client connected to " + host + ":" + port);
// Client is running
while (!done) {
@@ -71,6 +88,7 @@ int main(int argc, char** argv) {
}
}
log("Client shutting down.");
udpClient->stop();
client->disconnect();
io.stop();
ioThread.join();

View File

@@ -44,6 +44,10 @@ namespace ColumnLynx::Utils {
return 48042;
}
unsigned char protocolVersion() {
return 1;
}
std::string bytesToHexString(const uint8_t* bytes, size_t length) {
const char hexChars[] = "0123456789ABCDEF";
std::string hexString;

View File

@@ -65,11 +65,12 @@ namespace ColumnLynx::Net::UDP {
// Update recv counter
const_cast<SessionState*>(session.get())->recv_ctr.fetch_add(1, std::memory_order_relaxed);
// TODO: Process the packet payload
// For now, just log the decrypted payload
std::string payloadStr(plaintext.begin(), plaintext.end());
Utils::log("UDP: Received packet from " + mRemoteEndpoint.address().to_string() + " - Payload: " + payloadStr);
// TODO: Process the packet payload, for now just echo back
mSendData(sessionID, std::string(plaintext.begin(), plaintext.end()));
} catch (...) {
Utils::warn("UDP: Failed to decrypt payload from " + mRemoteEndpoint.address().to_string());
return;
@@ -78,6 +79,44 @@ namespace ColumnLynx::Net::UDP {
void UDPServer::mSendData(const uint64_t sessionID, const std::string& data) {
// TODO: Implement
// Find the IPv4/IPv6 endpoint for the session
std::shared_ptr<const SessionState> session = SessionRegistry::getInstance().get(sessionID);
if (!session) {
Utils::warn("UDP: Cannot send data, unknown session ID " + std::to_string(sessionID));
return;
}
asio::ip::udp::endpoint endpoint = session->udpEndpoint;
if (endpoint.address().is_unspecified()) {
Utils::warn("UDP: Cannot send data, session ID " + std::to_string(sessionID) + " has no known UDP endpoint.");
return;
}
// Prepare packet
UDPPacketHeader hdr{};
randombytes_buf(hdr.nonce.data(), hdr.nonce.size());
auto encryptedPayload = Utils::LibSodiumWrapper::encryptMessage(
reinterpret_cast<const uint8_t*>(data.data()), data.size(),
session->aesKey, 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)
);
packet.insert(packet.end(),
reinterpret_cast<const uint8_t*>(&sessionID),
reinterpret_cast<const uint8_t*>(&sessionID) + sizeof(sessionID)
);
packet.insert(packet.end(), encryptedPayload.begin(), encryptedPayload.end());
// Send packet
mSocket.send_to(asio::buffer(packet), endpoint);
Utils::log("UDP: Sent packet of size " + std::to_string(packet.size()) + " to " + std::to_string(sessionID) + " (" + endpoint.address().to_string() + ":" + std::to_string(endpoint.port()) + ")");
}
void UDPServer::stop() {

19
third_party/cxxopts/LICENSE_1_0.txt vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2014 Jarryd Beck
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.