Browse Source
- Add NodeConfig: simplified INI config manager for embedded uTun node - X25519 key generation via OpenSSL EVP - Random node_id generation - addServer/removeServer, addClient/removeClient API - Auto-creates config with default server on first run - Integrate UtunNode into MainWindow: - Creates NodeConfig in ~/.config/chatgui/node.conf - Starts embedded uTun node (no TUN, no msg_transport) - In separate thread with uasync_poll event loop - Replace MsgClient (TCP IPC) with UtunNode (direct calls): - ChatPropagator now uses UtunNode for send/recv - send(uint64_t, QByteArray) replaces sendTo - messageReceived signal replaces received signal - MainWindow cleanup: remove MsgClient, add UtunNode+NodeConfigchatgui
7 changed files with 340 additions and 26 deletions
@ -0,0 +1,206 @@ |
|||||||
|
// node_config.cpp — Simplified INI config manager for embedded uTun node
|
||||||
|
#include "node_config.h" |
||||||
|
|
||||||
|
extern "C" { |
||||||
|
#include <openssl/evp.h> |
||||||
|
#include <openssl/rand.h> |
||||||
|
} |
||||||
|
#include <QFile> |
||||||
|
#include <QTextStream> |
||||||
|
#include <QFileInfo> |
||||||
|
|
||||||
|
static QString formatKeyHex(const uint8_t* bin, size_t len) { |
||||||
|
return QByteArray(reinterpret_cast<const char*>(bin), len).toHex(); |
||||||
|
} |
||||||
|
|
||||||
|
static bool parseKeyHex(const QString& hex, uint8_t* bin, size_t len) { |
||||||
|
QByteArray ba = QByteArray::fromHex(hex.toLatin1()); |
||||||
|
if (ba.size() != (int)len) return false; |
||||||
|
memcpy(bin, ba.constData(), len); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// NodeConfig
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
NodeConfig::NodeConfig(const QString& path) : m_path(path) {} |
||||||
|
|
||||||
|
bool NodeConfig::exists() const { |
||||||
|
return QFileInfo::exists(m_path); |
||||||
|
} |
||||||
|
|
||||||
|
void NodeConfig::generateIdentity() { |
||||||
|
// X25519 keypair
|
||||||
|
EVP_PKEY* pkey = nullptr; |
||||||
|
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, nullptr); |
||||||
|
if (ctx) { |
||||||
|
EVP_PKEY_keygen_init(ctx); |
||||||
|
EVP_PKEY_generate(ctx, &pkey); |
||||||
|
EVP_PKEY_CTX_free(ctx); |
||||||
|
} |
||||||
|
if (pkey) { |
||||||
|
size_t pkLen = 32, skLen = 32; |
||||||
|
uint8_t pk[32], sk[32]; |
||||||
|
EVP_PKEY_get_raw_public_key(pkey, pk, &pkLen); |
||||||
|
EVP_PKEY_get_raw_private_key(pkey, sk, &skLen); |
||||||
|
m_pubKey = formatKeyHex(pk, 32); |
||||||
|
m_privKey = formatKeyHex(sk, 32); |
||||||
|
EVP_PKEY_free(pkey); |
||||||
|
} |
||||||
|
|
||||||
|
// Random 63-bit node_id
|
||||||
|
uint64_t id = 0; |
||||||
|
RAND_bytes(reinterpret_cast<uint8_t*>(&id), sizeof(id)); |
||||||
|
id &= 0x7FFFFFFFFFFFFFFFULL; |
||||||
|
m_nodeId = QString("%1").arg(id, 16, 16, QChar('0')).right(16); |
||||||
|
} |
||||||
|
|
||||||
|
bool NodeConfig::create(const QString& nodeName, quint16 listenPort) { |
||||||
|
m_nodeName = nodeName.isEmpty() ? "chatgui" : nodeName; |
||||||
|
if (m_pubKey.isEmpty()) generateIdentity(); |
||||||
|
if (!m_controlAddr.isEmpty() && m_controlAddr.indexOf(':') < 0) |
||||||
|
m_controlAddr = "127.0.0.1:9999"; |
||||||
|
m_servers.clear(); |
||||||
|
m_clients.clear(); |
||||||
|
if (listenPort > 0) |
||||||
|
m_servers.append({"lan", QString("0.0.0.0:%1").arg(listenPort)}); |
||||||
|
return save(); |
||||||
|
} |
||||||
|
|
||||||
|
bool NodeConfig::load() { |
||||||
|
QFile f(m_path); |
||||||
|
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return false; |
||||||
|
|
||||||
|
m_servers.clear(); |
||||||
|
m_clients.clear(); |
||||||
|
|
||||||
|
QString section; |
||||||
|
QTextStream in(&f); |
||||||
|
while (!in.atEnd()) { |
||||||
|
QString line = in.readLine().trimmed(); |
||||||
|
if (line.isEmpty() || line.startsWith('#')) continue; |
||||||
|
|
||||||
|
if (line.startsWith('[') && line.endsWith(']')) { |
||||||
|
section = line.mid(1, line.length() - 2); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
int eq = line.indexOf('='); |
||||||
|
if (eq < 0) continue; |
||||||
|
QString key = line.left(eq).trimmed(); |
||||||
|
QString val = line.mid(eq + 1).trimmed(); |
||||||
|
|
||||||
|
if (section == "global") { |
||||||
|
if (key == "my_node_name") m_nodeName = val; |
||||||
|
else if (key == "my_node_id") m_nodeId = val; |
||||||
|
else if (key == "my_public_key") m_pubKey = val; |
||||||
|
else if (key == "my_private_key") m_privKey = val; |
||||||
|
} else if (section.startsWith("server:")) { |
||||||
|
QString srvName = section.mid(7); |
||||||
|
if (key == "addr") m_servers.append({srvName, val}); |
||||||
|
} else if (section.startsWith("client:")) { |
||||||
|
QString cliName = section.mid(7); |
||||||
|
if (key == "peer_public_key") { |
||||||
|
NodeClient nc; |
||||||
|
nc.name = cliName; |
||||||
|
nc.pubKeyHex = val; |
||||||
|
m_clients.append(nc); |
||||||
|
} else if (key == "link") { |
||||||
|
if (!m_clients.isEmpty() && m_clients.last().name == cliName) { |
||||||
|
// link=server:addr:port
|
||||||
|
int sep1 = val.indexOf(':'); |
||||||
|
int sep2 = val.lastIndexOf(':'); |
||||||
|
if (sep1 > 0 && sep2 > sep1) { |
||||||
|
m_clients.last().serverName = val.left(sep1); |
||||||
|
m_clients.last().remoteAddr = val.mid(sep1 + 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (section == "control") { |
||||||
|
if (key == "addr") m_controlAddr = val; |
||||||
|
} |
||||||
|
} |
||||||
|
f.close(); |
||||||
|
return !m_nodeId.isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
bool NodeConfig::save() { |
||||||
|
QFile f(m_path); |
||||||
|
if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) return false; |
||||||
|
|
||||||
|
QTextStream out(&f); |
||||||
|
out << "[global]\n"; |
||||||
|
if (!m_nodeName.isEmpty()) out << "my_node_name=" << m_nodeName << "\n"; |
||||||
|
if (!m_nodeId.isEmpty()) out << "my_node_id=" << m_nodeId << "\n"; |
||||||
|
if (!m_privKey.isEmpty()) out << "my_private_key=" << m_privKey << "\n"; |
||||||
|
if (!m_pubKey.isEmpty()) out << "my_public_key=" << m_pubKey << "\n"; |
||||||
|
out << "tun_enabled=no\n"; |
||||||
|
out << "\n"; |
||||||
|
|
||||||
|
for (auto& srv : m_servers) { |
||||||
|
out << "[server:" << srv.first << "]\n"; |
||||||
|
out << "addr=" << srv.second << "\n\n"; |
||||||
|
} |
||||||
|
|
||||||
|
for (auto& cli : m_clients) { |
||||||
|
out << "[client:" << cli.name << "]\n"; |
||||||
|
out << "peer_public_key=" << cli.pubKeyHex << "\n"; |
||||||
|
out << "link=" << cli.serverName << ":" << cli.remoteAddr << "\n\n"; |
||||||
|
} |
||||||
|
|
||||||
|
if (!m_controlAddr.isEmpty()) { |
||||||
|
out << "[control]\n"; |
||||||
|
out << "addr=" << m_controlAddr << "\n\n"; |
||||||
|
} |
||||||
|
|
||||||
|
f.close(); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
uint64_t NodeConfig::nodeIdU64() const { |
||||||
|
if (m_nodeId.length() != 16) return 0; |
||||||
|
return m_nodeId.toULongLong(nullptr, 16); |
||||||
|
} |
||||||
|
|
||||||
|
quint16 NodeConfig::controlPort() const { |
||||||
|
if (m_controlAddr.isEmpty()) return 0; |
||||||
|
int sep = m_controlAddr.lastIndexOf(':'); |
||||||
|
if (sep < 0) return 0; |
||||||
|
return static_cast<quint16>(m_controlAddr.midRef(sep + 1).toUInt()); |
||||||
|
} |
||||||
|
|
||||||
|
void NodeConfig::setControlPort(quint16 p) { |
||||||
|
int sep = m_controlAddr.lastIndexOf(':'); |
||||||
|
if (sep >= 0) |
||||||
|
m_controlAddr = m_controlAddr.left(sep) + ":" + QString::number(p); |
||||||
|
else |
||||||
|
m_controlAddr = "127.0.0.1:" + QString::number(p); |
||||||
|
} |
||||||
|
|
||||||
|
bool NodeConfig::addServer(const QString& name, const QString& addr) { |
||||||
|
for (auto& s : m_servers) { if (s.first == name) return false; } |
||||||
|
m_servers.append({name, addr}); |
||||||
|
return save(); |
||||||
|
} |
||||||
|
|
||||||
|
bool NodeConfig::removeServer(const QString& name) { |
||||||
|
for (int i = 0; i < m_servers.size(); ++i) { |
||||||
|
if (m_servers[i].first == name) { m_servers.removeAt(i); return save(); } |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
bool NodeConfig::addClient(const QString& name, const QString& pubKeyHex, |
||||||
|
const QString& serverName, const QString& remoteAddr) { |
||||||
|
for (auto& c : m_clients) { if (c.name == name) return false; } |
||||||
|
m_clients.append({name, pubKeyHex, serverName, remoteAddr}); |
||||||
|
return save(); |
||||||
|
} |
||||||
|
|
||||||
|
bool NodeConfig::removeClient(const QString& name) { |
||||||
|
for (int i = 0; i < m_clients.size(); ++i) { |
||||||
|
if (m_clients[i].name == name) { m_clients.removeAt(i); return save(); } |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
@ -0,0 +1,60 @@ |
|||||||
|
// node_config.h — Simplified INI config manager for embedded uTun node
|
||||||
|
#ifndef NODE_CONFIG_H |
||||||
|
#define NODE_CONFIG_H |
||||||
|
|
||||||
|
#include <QString> |
||||||
|
#include <QList> |
||||||
|
#include <QPair> |
||||||
|
|
||||||
|
struct NodeClient { |
||||||
|
QString name; |
||||||
|
QString pubKeyHex; |
||||||
|
QString serverName; |
||||||
|
QString remoteAddr; |
||||||
|
}; |
||||||
|
|
||||||
|
class NodeConfig { |
||||||
|
public: |
||||||
|
explicit NodeConfig(const QString& path); |
||||||
|
|
||||||
|
bool exists() const; |
||||||
|
bool create(const QString& nodeName, quint16 listenPort); |
||||||
|
bool load(); |
||||||
|
bool save(); |
||||||
|
|
||||||
|
QString nodeName() const { return m_nodeName; } |
||||||
|
void setNodeName(const QString& n) { m_nodeName = n; } |
||||||
|
QString nodeId() const { return m_nodeId; } |
||||||
|
QString pubKey() const { return m_pubKey; } |
||||||
|
QString privKey() const { return m_privKey; } |
||||||
|
uint64_t nodeIdU64() const; |
||||||
|
|
||||||
|
QList<QPair<QString,QString>> servers() const { return m_servers; } |
||||||
|
bool addServer(const QString& name, const QString& addr); |
||||||
|
bool removeServer(const QString& name); |
||||||
|
|
||||||
|
QList<NodeClient> clients() const { return m_clients; } |
||||||
|
bool addClient(const QString& name, const QString& pubKeyHex, |
||||||
|
const QString& serverName, const QString& remoteAddr); |
||||||
|
bool removeClient(const QString& name); |
||||||
|
|
||||||
|
QString controlAddr() const { return m_controlAddr; } |
||||||
|
void setControlAddr(const QString& a) { m_controlAddr = a; } |
||||||
|
quint16 controlPort() const; |
||||||
|
void setControlPort(quint16 p); |
||||||
|
|
||||||
|
QString filePath() const { return m_path; } |
||||||
|
|
||||||
|
private: |
||||||
|
void generateIdentity(); |
||||||
|
|
||||||
|
QString m_path; |
||||||
|
QString m_nodeName; |
||||||
|
QString m_nodeId; |
||||||
|
QString m_pubKey; |
||||||
|
QString m_privKey; |
||||||
|
QList<QPair<QString,QString>> m_servers; |
||||||
|
QList<NodeClient> m_clients; |
||||||
|
QString m_controlAddr; |
||||||
|
}; |
||||||
|
#endif // NODE_CONFIG_H
|
||||||
Loading…
Reference in new issue