Version 1.2.1 - Bug Fixes
Critical (4)
C1 — udp_client.hpp / udp_client.cpp: Moved mStartReceive() out of the constructor into start(), after the socket is opened.
C2 — client/main.cpp:187: || → && in the main loop condition, so it exits when the connection drops instead of spinning forever.
C3 — tcp_connection.hpp: Added mConnectionAESKey{}, mConnectionSessionID(0), and mConnectionPublicKey{} to the constructor initializer list.
C4 — tcp_server.hpp / tcp_server.cpp: Added std::mutex mClientsMutex and locked it at all three mClients access sites (insert, erase callback, stop snapshot).
High (5)
H1 — tcp_connection.cpp:284: Replaced hardcoded htonl(0x0A0A0001) with htonl(baseIP + 1).
H2 — session_registry.hpp / session_registry.cpp / udp_server.cpp: Changed get() and getByIP() to return shared_ptr<SessionState> (non-const); removed all four const_cast usages.
H3 — session_registry.cpp:90: Guarded against mask == 0 || mask >= 32 (shift UB) and hostCount < 3 (unsigned underflow).
H4 — virtual_interface.cpp: Added runCommandWin32() using CreateProcess (no shell); replaced both system() calls in resetIP().
H5 — virtual_interface.cpp: Added close(mFd); mFd = -1; before both throw sites in the macOS constructor (ioctl failure and connect failure).
Medium (4)
M1 — tcp_connection.cpp:241: Added explicit find() + end-iterator check for NETWORK and SUBNET_MASK before dereferencing.
M2 — tcp_message_handler.cpp: Added an 8 KB (MAX_MSG_BODY = 8192) cap on incoming message body length; oversized messages close the connection.
M3 — tcp_connection.cpp:232: Capped the session ID generation loop at 16 attempts before disconnecting.
M4 — session_registry.cpp:8: put() now evicts the old IP→session mapping before replacing an entry, preventing stale mIPSessions entries.
This commit is contained in:
@@ -20,7 +20,6 @@ namespace ColumnLynx::Net::UDP {
|
||||
const std::string& port)
|
||||
: mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port)
|
||||
{
|
||||
mStartReceive();
|
||||
}
|
||||
|
||||
// Start the UDP client
|
||||
|
||||
@@ -61,10 +61,10 @@ namespace ColumnLynx::Net {
|
||||
void put(uint32_t sessionID, std::shared_ptr<SessionState> state);
|
||||
|
||||
// Lookup a session entry by session ID
|
||||
std::shared_ptr<const SessionState> get(uint32_t sessionID) const;
|
||||
std::shared_ptr<SessionState> get(uint32_t sessionID) const;
|
||||
|
||||
// Lookup a session entry by IPv4
|
||||
std::shared_ptr<const SessionState> getByIP(uint32_t ip) const;
|
||||
std::shared_ptr<SessionState> getByIP(uint32_t ip) const;
|
||||
|
||||
// Get a snapshot of the Session Registry
|
||||
std::unordered_map<uint32_t, std::shared_ptr<SessionState>> snapshot() const;
|
||||
|
||||
@@ -53,6 +53,9 @@ namespace ColumnLynx::Net::TCP {
|
||||
:
|
||||
mHandler(std::make_shared<MessageHandler>(std::move(socket))),
|
||||
mHeartbeatTimer(mHandler->socket().get_executor()),
|
||||
mConnectionAESKey{},
|
||||
mConnectionSessionID(0),
|
||||
mConnectionPublicKey{},
|
||||
mLastHeartbeatReceived(std::chrono::steady_clock::now()),
|
||||
mLastHeartbeatSent(std::chrono::steady_clock::now())
|
||||
{}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_set>
|
||||
#include <new>
|
||||
#include <asio.hpp>
|
||||
@@ -77,6 +78,7 @@ namespace ColumnLynx::Net::TCP {
|
||||
|
||||
asio::io_context &mIoContext;
|
||||
asio::ip::tcp::acceptor mAcceptor;
|
||||
std::mutex mClientsMutex;
|
||||
std::unordered_set<TCPConnection::pointer> mClients;
|
||||
};
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ int main(int argc, char** argv) {
|
||||
debug("isDone flag: " + std::to_string(done));
|
||||
|
||||
// Client is running
|
||||
while ((client->isConnected() || !client->isHandshakeComplete()) && !done) {
|
||||
while (client->isConnected() && !done) {
|
||||
//debug("Client connection flag: " + std::to_string(client->isConnected()));
|
||||
auto packet = tun->readPacket();
|
||||
/*if (!client->isConnected() || done) {
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace ColumnLynx::Net::UDP {
|
||||
}
|
||||
|
||||
Utils::log("UDP Client ready to send to " + mRemoteEndpoint.address().to_string() + ":" + std::to_string(mRemoteEndpoint.port()));
|
||||
mStartReceive();
|
||||
}
|
||||
|
||||
void UDPClient::sendMessage(const std::string& data) {
|
||||
|
||||
@@ -7,17 +7,22 @@
|
||||
namespace ColumnLynx::Net {
|
||||
void SessionRegistry::put(uint32_t sessionID, std::shared_ptr<SessionState> state) {
|
||||
std::unique_lock lock(mMutex);
|
||||
// If replacing an existing entry, evict its old IP mapping first.
|
||||
auto existing = mSessions.find(sessionID);
|
||||
if (existing != mSessions.end() && existing->second) {
|
||||
mIPSessions.erase(existing->second->clientTunIP);
|
||||
}
|
||||
mSessions[sessionID] = std::move(state);
|
||||
mIPSessions[mSessions[sessionID]->clientTunIP] = mSessions[sessionID];
|
||||
}
|
||||
|
||||
std::shared_ptr<const SessionState> SessionRegistry::get(uint32_t sessionID) const {
|
||||
std::shared_ptr<SessionState> SessionRegistry::get(uint32_t sessionID) const {
|
||||
std::shared_lock lock(mMutex);
|
||||
auto it = mSessions.find(sessionID);
|
||||
return (it == mSessions.end()) ? nullptr : it->second;
|
||||
}
|
||||
|
||||
std::shared_ptr<const SessionState> SessionRegistry::getByIP(uint32_t ip) const {
|
||||
std::shared_ptr<SessionState> SessionRegistry::getByIP(uint32_t ip) const {
|
||||
std::shared_lock lock(mMutex);
|
||||
auto it = mIPSessions.find(ip);
|
||||
return (it == mIPSessions.end()) ? nullptr : it->second;
|
||||
@@ -87,7 +92,9 @@ namespace ColumnLynx::Net {
|
||||
uint32_t SessionRegistry::getFirstAvailableIP(uint32_t baseIP, uint8_t mask) const {
|
||||
std::shared_lock lock(mMutex);
|
||||
|
||||
if (mask == 0 || mask >= 32) return 0; // degenerate subnet
|
||||
uint32_t hostCount = (1u << (32 - mask));
|
||||
if (hostCount < 3) return 0; // no assignable host addresses
|
||||
uint32_t firstHost = 2;
|
||||
uint32_t lastHost = hostCount - 2;
|
||||
|
||||
|
||||
@@ -54,6 +54,12 @@ namespace ColumnLynx::Net::TCP {
|
||||
mCurrentType = decodeMessageType(mHeader[0]);
|
||||
|
||||
uint16_t len = (mHeader[1] << 8) | mHeader[2];
|
||||
constexpr uint16_t MAX_MSG_BODY = 8192;
|
||||
if (len > MAX_MSG_BODY) {
|
||||
Utils::error("Incoming message body too large (" + std::to_string(len) + " > " + std::to_string(MAX_MSG_BODY) + "), closing connection.");
|
||||
if (mOnDisconnect) mOnDisconnect(asio::error::message_size);
|
||||
return;
|
||||
}
|
||||
mReadBody(len);
|
||||
} else {
|
||||
if (!NetHelper::isExpectedDisconnect(ec)) {
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace ColumnLynx::Utils {
|
||||
}
|
||||
|
||||
std::string getVersion() {
|
||||
return "1.2.0";
|
||||
return "1.2.1";
|
||||
}
|
||||
|
||||
unsigned short serverPort() {
|
||||
|
||||
@@ -58,6 +58,44 @@ static void InitializeWintun()
|
||||
#undef RESOLVE
|
||||
}
|
||||
|
||||
// Run a command on Windows via CreateProcess (no shell — shell metacharacters in
|
||||
// arguments are not interpreted by cmd.exe).
|
||||
static bool runCommandWin32(const std::vector<std::string>& args) {
|
||||
if (args.empty()) return false;
|
||||
|
||||
// Build a properly-quoted command line using Windows quoting rules.
|
||||
std::string cmdLine;
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
if (i > 0) cmdLine += ' ';
|
||||
bool needsQuote = args[i].find_first_of(" \t\"") != std::string::npos || args[i].empty();
|
||||
if (needsQuote) {
|
||||
cmdLine += '"';
|
||||
for (char c : args[i]) {
|
||||
if (c == '"') cmdLine += '\\';
|
||||
cmdLine += c;
|
||||
}
|
||||
cmdLine += '"';
|
||||
} else {
|
||||
cmdLine += args[i];
|
||||
}
|
||||
}
|
||||
|
||||
STARTUPINFOA si{};
|
||||
si.cb = sizeof(si);
|
||||
PROCESS_INFORMATION pi{};
|
||||
|
||||
if (!CreateProcessA(nullptr, cmdLine.data(), nullptr, nullptr,
|
||||
FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi))
|
||||
return false;
|
||||
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
DWORD exitCode = 1;
|
||||
GetExitCodeProcess(pi.hProcess, &exitCode);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return exitCode == 0;
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
namespace ColumnLynx::Net {
|
||||
@@ -116,8 +154,10 @@ static bool runCommand(const std::vector<std::string>& args) {
|
||||
|
||||
struct ctl_info ctlInfo {};
|
||||
std::strncpy(ctlInfo.ctl_name, UTUN_CONTROL_NAME, sizeof(ctlInfo.ctl_name));
|
||||
if (ioctl(mFd, CTLIOCGINFO, &ctlInfo) == -1)
|
||||
if (ioctl(mFd, CTLIOCGINFO, &ctlInfo) == -1) {
|
||||
close(mFd); mFd = -1;
|
||||
throw std::runtime_error("ioctl(CTLIOCGINFO) failed: " + std::string(strerror(errno)));
|
||||
}
|
||||
|
||||
struct sockaddr_ctl sc {};
|
||||
sc.sc_len = sizeof(sc);
|
||||
@@ -127,9 +167,11 @@ static bool runCommand(const std::vector<std::string>& args) {
|
||||
sc.sc_unit = 0; // 0 = auto-assign next utunX
|
||||
|
||||
if (connect(mFd, (struct sockaddr*)&sc, sizeof(sc)) < 0) {
|
||||
if (errno == EPERM)
|
||||
int savedErrno = errno;
|
||||
close(mFd); mFd = -1;
|
||||
if (savedErrno == EPERM)
|
||||
throw std::runtime_error("connect(AF_SYS_CONTROL) failed: Insufficient permissions (try running with sudo)");
|
||||
throw std::runtime_error("connect(AF_SYS_CONTROL) failed: " + std::string(strerror(errno)));
|
||||
throw std::runtime_error("connect(AF_SYS_CONTROL) failed: " + std::string(strerror(savedErrno)));
|
||||
}
|
||||
|
||||
// Retrieve actual utun device name via UTUN_OPT_IFNAME
|
||||
@@ -351,20 +393,10 @@ static bool runCommand(const std::vector<std::string>& args) {
|
||||
//);
|
||||
//system(cmd);
|
||||
#elif defined(_WIN32)
|
||||
char cmd[512];
|
||||
// Remove any persistent routes associated with this interface
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"netsh routing ip delete persistentroute all name=\"%s\"",
|
||||
mIfName.c_str()
|
||||
);
|
||||
system(cmd);
|
||||
|
||||
runCommandWin32({"netsh", "routing", "ip", "delete", "persistentroute", "all", "name=" + mIfName});
|
||||
// Reset to DHCP
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"netsh interface ip set address name=\"%s\" dhcp",
|
||||
mIfName.c_str()
|
||||
);
|
||||
system(cmd);
|
||||
runCommandWin32({"netsh", "interface", "ip", "set", "address", "name=" + mIfName, "dhcp"});
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -229,17 +229,32 @@ namespace ColumnLynx::Net::TCP {
|
||||
std::memcpy(mConnectionAESKey.data(), decrypted.data(), decrypted.size());
|
||||
|
||||
// Make a Session ID - unique and not zero (zero is reserved for invalid sessions)
|
||||
{
|
||||
int idAttempts = 0;
|
||||
do {
|
||||
if (++idAttempts > 16) {
|
||||
Utils::error("Failed to generate unique session ID after 16 attempts. Killing connection from " + reqAddr);
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
randombytes_buf(&mConnectionSessionID, sizeof(mConnectionSessionID));
|
||||
} while (SessionRegistry::getInstance().exists(mConnectionSessionID) || mConnectionSessionID == 0); // Regenerate if it already exists or is zero (zero is reserved for invalid sessions)
|
||||
} while (SessionRegistry::getInstance().exists(mConnectionSessionID) || mConnectionSessionID == 0);
|
||||
}
|
||||
|
||||
// Encrypt the Session ID with the established AES key (using symmetric encryption, nonce can be all zeros for this purpose)
|
||||
Nonce symNonce{}; // All zeros
|
||||
|
||||
const auto& serverConfig = ServerSession::getInstance().getRawServerConfig();
|
||||
|
||||
std::string networkString = serverConfig.find("NETWORK")->second; // The load check guarantees that this value exists
|
||||
uint8_t configMask = std::stoi(serverConfig.find("SUBNET_MASK")->second); // Same deal here
|
||||
auto netIt = serverConfig.find("NETWORK");
|
||||
auto maskIt = serverConfig.find("SUBNET_MASK");
|
||||
if (netIt == serverConfig.end() || maskIt == serverConfig.end()) {
|
||||
Utils::error("Server config is missing NETWORK or SUBNET_MASK. Killing connection from " + reqAddr);
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
std::string networkString = netIt->second;
|
||||
uint8_t configMask = static_cast<uint8_t>(std::stoi(maskIt->second));
|
||||
|
||||
uint32_t baseIP = Net::VirtualInterface::stringToIpv4(networkString);
|
||||
|
||||
@@ -281,7 +296,7 @@ namespace ColumnLynx::Net::TCP {
|
||||
|
||||
// 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);
|
||||
auto session = std::make_shared<SessionState>(mConnectionAESKey, std::chrono::hours(12), clientIP, htonl(baseIP + 1), mConnectionSessionID);
|
||||
session->setBaseNonce(); // Set it
|
||||
SessionRegistry::getInstance().put(mConnectionSessionID, std::move(session));
|
||||
SessionRegistry::getInstance().lockIP(mConnectionSessionID, clientIP);
|
||||
|
||||
@@ -35,11 +35,15 @@ namespace ColumnLynx::Net::TCP {
|
||||
auto client = TCPConnection::create(
|
||||
std::move(socket),
|
||||
[this](std::shared_ptr<TCPConnection> c) {
|
||||
std::lock_guard<std::mutex> lock(mClientsMutex);
|
||||
mClients.erase(c);
|
||||
Utils::log("Client removed.");
|
||||
}
|
||||
);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mClientsMutex);
|
||||
mClients.insert(client);
|
||||
}
|
||||
client->start();
|
||||
Utils::log("Accepted new client connection.");
|
||||
|
||||
@@ -59,7 +63,11 @@ namespace ColumnLynx::Net::TCP {
|
||||
}
|
||||
|
||||
// Snapshot to avoid iterator invalidation while callbacks erase()
|
||||
std::vector<std::shared_ptr<TCPConnection>> snapshot(mClients.begin(), mClients.end());
|
||||
std::vector<std::shared_ptr<TCPConnection>> snapshot;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mClientsMutex);
|
||||
snapshot.assign(mClients.begin(), mClients.end());
|
||||
}
|
||||
for (auto &client : snapshot) {
|
||||
try {
|
||||
client->disconnect(); // should shutdown+close the socket
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace ColumnLynx::Net::UDP {
|
||||
std::vector<uint8_t> encryptedPayload(it, mRecvBuffer.begin() + bytes);
|
||||
|
||||
// Get associated session state
|
||||
std::shared_ptr<const SessionState> session = SessionRegistry::getInstance().get(sessionID);
|
||||
std::shared_ptr<SessionState> session = SessionRegistry::getInstance().get(sessionID);
|
||||
|
||||
if (!session) {
|
||||
Utils::warn("UDP: Unknown or invalid session from " + mRemoteEndpoint.address().to_string());
|
||||
@@ -60,9 +60,9 @@ namespace ColumnLynx::Net::UDP {
|
||||
|
||||
Utils::debug("Passed decryption");
|
||||
|
||||
const_cast<SessionState*>(session.get())->setUDPEndpoint(mRemoteEndpoint); // Update endpoint after confirming decryption
|
||||
session->setUDPEndpoint(mRemoteEndpoint); // Update endpoint after confirming decryption
|
||||
// Update recv counter
|
||||
const_cast<SessionState*>(session.get())->recv_ctr.fetch_add(1, std::memory_order_relaxed);
|
||||
session->recv_ctr.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
// For now, just log the decrypted payload
|
||||
std::string payloadStr(plaintext.begin(), plaintext.end());
|
||||
@@ -79,7 +79,7 @@ namespace ColumnLynx::Net::UDP {
|
||||
|
||||
void UDPServer::sendData(uint32_t sessionID, const std::string& data) {
|
||||
// Find the IPv4/IPv6 endpoint for the session
|
||||
std::shared_ptr<const SessionState> session = SessionRegistry::getInstance().get(sessionID);
|
||||
std::shared_ptr<SessionState> session = SessionRegistry::getInstance().get(sessionID);
|
||||
if (!session) {
|
||||
Utils::warn("UDP: Cannot send data, unknown session ID " + std::to_string(sessionID));
|
||||
return;
|
||||
@@ -98,14 +98,13 @@ namespace ColumnLynx::Net::UDP {
|
||||
// Increment send counter with overflow protection
|
||||
uint64_t sendCount = 0;
|
||||
{
|
||||
auto ptr = const_cast<SessionState*>(session.get());
|
||||
uint64_t old = ptr->send_ctr.load(std::memory_order_relaxed);
|
||||
uint64_t old = session->send_ctr.load(std::memory_order_relaxed);
|
||||
for (;;) {
|
||||
if (old == std::numeric_limits<uint64_t>::max()) {
|
||||
Utils::error("UDP: send counter overflow for session " + std::to_string(sessionID));
|
||||
return;
|
||||
}
|
||||
if (ptr->send_ctr.compare_exchange_weak(old, old + 1, std::memory_order_relaxed)) {
|
||||
if (session->send_ctr.compare_exchange_weak(old, old + 1, std::memory_order_relaxed)) {
|
||||
sendCount = old;
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user