Added protocol version to TCP header, also some launch args
This commit is contained in:
@@ -3,8 +3,15 @@
|
|||||||
## ASIO C++ Library
|
## ASIO C++ Library
|
||||||
- **Name:** ASIO (standalone)
|
- **Name:** ASIO (standalone)
|
||||||
- **Website:** https://think-async.com/Asio/
|
- **Website:** https://think-async.com/Asio/
|
||||||
- **Copyright:** (c) 2003-2024 Christopher M. Kohlhoff
|
- **Copyright:** (c) 2003-2025 Christopher M. Kohlhoff
|
||||||
- **License:** Boost Software License, Version 1.0
|
- **License:** Boost Software License, Version 1.0
|
||||||
- **License Text:** See `third_party/asio/LICENSE_1_0.txt`
|
- **License Text:** See `third_party/asio/LICENSE_1_0.txt`
|
||||||
|
|
||||||
This project uses the standalone version of the ASIO C++ library for asynchronous networking.
|
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`
|
||||||
@@ -43,6 +43,7 @@ The data section is unspecified. It may change depending on the **Packet ID**. I
|
|||||||
|
|
||||||
| Type | Length | Name | Description |
|
| 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 | 1 byte | **Header** - Packet Type | General type of packet |
|
||||||
| uint8_t/byte array | variable | Data | General packet data - changes for packet to packet |
|
| uint8_t/byte array | variable | Data | General packet data - changes for packet to packet |
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,16 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
|
|
||||||
// Init connection handshake
|
// Init connection handshake
|
||||||
Utils::log("Sending handshake init to server.");
|
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 {
|
} else {
|
||||||
Utils::error("Client connect failed: " + ec.message());
|
Utils::error("Client connect failed: " + ec.message());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
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)
|
||||||
: 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() {
|
void start() {
|
||||||
auto endpoints = mResolver.resolve(asio::ip::udp::v4(), mHost, mPort);
|
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()));
|
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:
|
||||||
|
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::socket mSocket;
|
||||||
asio::ip::udp::resolver mResolver;
|
asio::ip::udp::resolver mResolver;
|
||||||
asio::ip::udp::endpoint mRemoteEndpoint;
|
asio::ip::udp::endpoint mRemoteEndpoint;
|
||||||
@@ -66,5 +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::array<uint8_t, 2048> mRecvBuffer; // Adjust size as needed
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
@@ -22,6 +24,7 @@ namespace ColumnLynx::Utils {
|
|||||||
std::string getHostname();
|
std::string getHostname();
|
||||||
std::string getVersion();
|
std::string getVersion();
|
||||||
unsigned short serverPort();
|
unsigned short serverPort();
|
||||||
|
unsigned char protocolVersion();
|
||||||
|
|
||||||
// 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);
|
||||||
|
|||||||
@@ -92,7 +92,24 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case ClientMessageType::HANDSHAKE_INIT: {
|
case ClientMessageType::HANDSHAKE_INIT: {
|
||||||
Utils::log("Received HANDSHAKE_INIT from " + reqAddr);
|
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
|
mHandler->sendMessage(ServerMessageType::HANDSHAKE_IDENTIFY, Utils::uint8ArrayToString(mLibSodiumWrapper->getPublicKey(), crypto_sign_PUBLICKEYBYTES)); // This public key should always exist
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
2930
include/cxxopts/cxxopts.hpp
Normal file
2930
include/cxxopts/cxxopts.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@
|
|||||||
#include <columnlynx/common/panic_handler.hpp>
|
#include <columnlynx/common/panic_handler.hpp>
|
||||||
#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>
|
||||||
|
|
||||||
using asio::ip::tcp;
|
using asio::ip::tcp;
|
||||||
using namespace ColumnLynx::Utils;
|
using namespace ColumnLynx::Utils;
|
||||||
@@ -32,6 +33,22 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
PanicHandler::init();
|
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 {
|
try {
|
||||||
log("ColumnLynx Client, Version " + getVersion());
|
log("ColumnLynx Client, Version " + getVersion());
|
||||||
log("This software is licensed under the GPLv3. See LICENSE for details.");
|
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;
|
uint64_t sessionID = 0;
|
||||||
|
|
||||||
asio::io_context io;
|
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 client = std::make_shared<ColumnLynx::Net::TCP::TCPClient>(io, host, port, &sodiumWrapper, &aesKey, &sessionID);
|
||||||
auto udpClient = std::make_shared<ColumnLynx::Net::UDP::UDPClient>(io, "127.0.0.1", std::to_string(serverPort()), &aesKey, &sessionID);
|
auto udpClient = std::make_shared<ColumnLynx::Net::UDP::UDPClient>(io, host, port, &aesKey, &sessionID);
|
||||||
|
|
||||||
client->start();
|
client->start();
|
||||||
udpClient->start();
|
udpClient->start();
|
||||||
@@ -54,7 +71,7 @@ int main(int argc, char** argv) {
|
|||||||
});
|
});
|
||||||
ioThread.detach();
|
ioThread.detach();
|
||||||
|
|
||||||
log("Client connected to 127.0.0.1:" + std::to_string(serverPort()));
|
log("Client connected to " + host + ":" + port);
|
||||||
|
|
||||||
// Client is running
|
// Client is running
|
||||||
while (!done) {
|
while (!done) {
|
||||||
@@ -71,6 +88,7 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
log("Client shutting down.");
|
log("Client shutting down.");
|
||||||
|
udpClient->stop();
|
||||||
client->disconnect();
|
client->disconnect();
|
||||||
io.stop();
|
io.stop();
|
||||||
ioThread.join();
|
ioThread.join();
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ namespace ColumnLynx::Utils {
|
|||||||
return 48042;
|
return 48042;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned char protocolVersion() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
std::string bytesToHexString(const uint8_t* bytes, size_t length) {
|
std::string bytesToHexString(const uint8_t* bytes, size_t length) {
|
||||||
const char hexChars[] = "0123456789ABCDEF";
|
const char hexChars[] = "0123456789ABCDEF";
|
||||||
std::string hexString;
|
std::string hexString;
|
||||||
|
|||||||
@@ -65,11 +65,12 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
// 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);
|
||||||
|
|
||||||
// TODO: Process the packet payload
|
|
||||||
|
|
||||||
// For now, just log the decrypted payload
|
// For now, just log the decrypted payload
|
||||||
std::string payloadStr(plaintext.begin(), plaintext.end());
|
std::string payloadStr(plaintext.begin(), plaintext.end());
|
||||||
Utils::log("UDP: Received packet from " + mRemoteEndpoint.address().to_string() + " - Payload: " + payloadStr);
|
Utils::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 (...) {
|
} catch (...) {
|
||||||
Utils::warn("UDP: Failed to decrypt payload from " + mRemoteEndpoint.address().to_string());
|
Utils::warn("UDP: Failed to decrypt payload from " + mRemoteEndpoint.address().to_string());
|
||||||
return;
|
return;
|
||||||
@@ -78,6 +79,44 @@ namespace ColumnLynx::Net::UDP {
|
|||||||
|
|
||||||
void UDPServer::mSendData(const uint64_t sessionID, const std::string& data) {
|
void UDPServer::mSendData(const uint64_t sessionID, const std::string& data) {
|
||||||
// TODO: Implement
|
// 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() {
|
void UDPServer::stop() {
|
||||||
|
|||||||
19
third_party/cxxopts/LICENSE_1_0.txt
vendored
Normal file
19
third_party/cxxopts/LICENSE_1_0.txt
vendored
Normal 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.
|
||||||
Reference in New Issue
Block a user