Browse Source

auto_socket: Windows-скан + список сокетов в Network Settings (add/remove, чистый rewrite конфига)

topo_upd
Evgeny 2 months ago
parent
commit
f82dca3185
  1. 90
      lib/platform_compat.c
  2. 78
      src/transport_layer/auto_socket.c
  3. 368
      tools/chatgui/src/networksettingspage.cpp
  4. 40
      tools/chatgui/src/networksettingspage.h
  5. 72
      tools/chatgui/transport/config_updater.cpp
  6. 14
      tools/chatgui/transport/config_updater.h
  7. 9
      tools/chatgui/transport/node_config.cpp
  8. 4
      tools/chatgui/transport/node_config.h

90
lib/platform_compat.c

@ -281,26 +281,94 @@ uint32_t get_default_route_netif_index(int family) {
freeifaddrs(ifaddr);
return result;
}
#else
#else /* _WIN32 */
/* ── Windows: реализация через IP Helper (GetAdaptersAddresses) ── */
static PIP_ADAPTER_ADDRESSES plat_win_get_adapters(ULONG family) {
PIP_ADAPTER_ADDRESSES adapters = NULL;
ULONG size = 0;
ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
if (GetAdaptersAddresses(family, flags, NULL, NULL, &size) != ERROR_BUFFER_OVERFLOW) return NULL;
adapters = (PIP_ADAPTER_ADDRESSES)malloc(size);
if (!adapters) return NULL;
if (GetAdaptersAddresses(family, flags, NULL, adapters, &size) != NO_ERROR) { free(adapters); return NULL; }
return adapters;
}
static PIP_ADAPTER_ADDRESSES plat_win_find_adapter(PIP_ADAPTER_ADDRESSES adapters, uint32_t ifindex) {
for (PIP_ADAPTER_ADDRESSES a = adapters; a; a = a->Next) {
if (a->IfIndex == ifindex || a->Ipv4IfIndex == ifindex || a->Ipv6IfIndex == ifindex) return a;
}
return NULL;
}
uint32_t get_interface_ip_by_index(uint32_t netif_index) {
(void)netif_index;
return 0;
if (!netif_index) return 0;
PIP_ADAPTER_ADDRESSES adapters = plat_win_get_adapters(AF_INET);
if (!adapters) return 0;
uint32_t result = 0;
PIP_ADAPTER_ADDRESSES a = plat_win_find_adapter(adapters, netif_index);
if (a) {
for (PIP_ADAPTER_UNICAST_ADDRESS u = a->FirstUnicastAddress; u; u = u->Next) {
if (u->Address.lpSockaddr && u->Address.lpSockaddr->sa_family == AF_INET) {
result = ((const struct sockaddr_in*)u->Address.lpSockaddr)->sin_addr.s_addr;
break;
}
}
}
free(adapters);
return result;
}
int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* addr_out) {
(void)netif_index; (void)temporary;
if (addr_out) memset(addr_out, 0, 16);
return -1;
/* Windows IP Helper не различает temporary/stable без запроса Addresses — отдаём первый не-link-local */
(void)temporary;
return get_interface_ipv6_addr_nl(netif_index, 1, addr_out);
}
int get_interface_ipv6_addr_nl(uint32_t netif_index, int prefer_stable, uint8_t* addr_out) {
(void)netif_index; (void)prefer_stable;
if (addr_out) memset(addr_out, 0, 16);
return -1;
(void)prefer_stable;
if (!addr_out) return -1;
memset(addr_out, 0, 16);
if (!netif_index) return -1;
PIP_ADAPTER_ADDRESSES adapters = plat_win_get_adapters(AF_INET6);
if (!adapters) return -1;
int found = 0;
PIP_ADAPTER_ADDRESSES a = plat_win_find_adapter(adapters, netif_index);
if (a) {
for (PIP_ADAPTER_UNICAST_ADDRESS u = a->FirstUnicastAddress; u; u = u->Next) {
if (!u->Address.lpSockaddr || u->Address.lpSockaddr->sa_family != AF_INET6) continue;
const uint8_t* b = ((const struct sockaddr_in6*)u->Address.lpSockaddr)->sin6_addr.s6_addr;
if ((b[0] == 0xfe && (b[1] & 0xc0) == 0x80)) continue; /* link-local */
{ static const uint8_t lb[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
if (memcmp(b, lb, 16) == 0) continue; } /* loopback */
{ static const uint8_t z[16]; if (memcmp(b, z, 16) == 0) continue; } /* :: */
memcpy(addr_out, b, 16);
found = 1;
break;
}
}
free(adapters);
return found ? 0 : -1;
}
uint32_t get_default_route_netif_index(int family) {
(void)family;
return 0;
struct sockaddr_storage dst;
memset(&dst, 0, sizeof(dst));
if (family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&dst;
sin->sin_family = AF_INET;
inet_pton(AF_INET, "8.8.8.8", &sin->sin_addr);
} else if (family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&dst;
sin6->sin6_family = AF_INET6;
inet_pton(AF_INET6, "2001:4860:4860::8888", &sin6->sin6_addr);
} else {
return 0;
}
DWORD idx = 0;
if (GetBestInterfaceEx((const SOCKADDR*)&dst, &idx) != NO_ERROR) return 0;
return (uint32_t)idx;
}
#endif

78
src/transport_layer/auto_socket.c

@ -698,19 +698,91 @@ static void auto_socket_scan_all(struct AUTO_SOCKET* as) {
#else /* _WIN32 */
/* Собирает IfIndex всех UP не-loopback адаптеров. Возвращает число найденных (до max_count). */
static int win_collect_up_ifaces(uint32_t* out_ifindex, int max_count) {
PIP_ADAPTER_ADDRESSES adapters = NULL;
ULONG size = 0;
ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
DWORD rv = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, NULL, &size);
if (rv != ERROR_BUFFER_OVERFLOW) return 0;
adapters = (PIP_ADAPTER_ADDRESSES)u_malloc(size);
if (!adapters) return 0;
rv = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, adapters, &size);
if (rv != NO_ERROR) { u_free(adapters); return 0; }
int count = 0;
for (PIP_ADAPTER_ADDRESSES a = adapters; a && count < max_count; a = a->Next) {
if (a->OperStatus != IfOperStatusUp) continue;
if (a->IfType == IF_TYPE_SOFTWARE_LOOPBACK) continue;
out_ifindex[count++] = a->IfIndex;
}
u_free(adapters);
return count;
}
static int scan_and_classify_iface(uint32_t ifindex, const char* ifname,
int* out_has_v4, int* out_has_v6,
uint8_t* out_v4_type, uint8_t* out_v6_type) {
(void)ifindex; (void)ifname;
(void)ifname;
*out_has_v4 = 0; *out_has_v6 = 0;
*out_v4_type = CFG_SERVER_TYPE_NAT;
*out_v6_type = CFG_SERVER_TYPE_NAT;
PIP_ADAPTER_ADDRESSES adapters = NULL;
ULONG size = 0;
ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
DWORD rv = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, NULL, &size);
if (rv != ERROR_BUFFER_OVERFLOW) return -1;
adapters = (PIP_ADAPTER_ADDRESSES)u_malloc(size);
if (!adapters) return -1;
rv = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, adapters, &size);
if (rv != NO_ERROR) { u_free(adapters); return -1; }
PIP_ADAPTER_ADDRESSES a = adapters;
while (a && a->IfIndex != ifindex) a = a->Next;
if (!a || a->OperStatus != IfOperStatusUp || a->IfType == IF_TYPE_SOFTWARE_LOOPBACK) {
u_free(adapters);
return 0;
}
for (PIP_ADAPTER_UNICAST_ADDRESS u = a->FirstUnicastAddress; u; u = u->Next) {
if (!u->Address.lpSockaddr) continue;
if (u->Address.lpSockaddr->sa_family == AF_INET) {
const struct sockaddr_in* sin = (const struct sockaddr_in*)u->Address.lpSockaddr;
if (sin->sin_addr.s_addr == INADDR_ANY) continue;
*out_has_v4 = 1;
if (is_ipv4_public(sin->sin_addr.s_addr)) *out_v4_type = CFG_SERVER_TYPE_PUBLIC;
} else if (u->Address.lpSockaddr->sa_family == AF_INET6) {
const struct sockaddr_in6* sin6 = (const struct sockaddr_in6*)u->Address.lpSockaddr;
int cls = v6_classify(sin6->sin6_addr.s6_addr);
if (cls == AS_V6_OTH || cls == AS_V6_LL) continue;
*out_has_v6 = 1;
if (cls == AS_V6_DIR) *out_v6_type = CFG_SERVER_TYPE_PUBLIC;
}
}
u_free(adapters);
return 0;
}
static void auto_socket_scan_all(struct AUTO_SOCKET* as) {
(void)as;
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] Windows: auto_socket_scan_all not yet implemented");
uint32_t up[64];
int up_count = win_collect_up_ifaces(up, 64);
for (int i = 0; i < up_count; i++) {
char ifname[IF_NAMESIZE];
if (!if_indextoname(up[i], ifname)) snprintf(ifname, sizeof(ifname), "if%u", up[i]);
reconcile_iface(as, up[i], ifname);
}
/* prune: удаляем сокеты адаптеров, которых больше нет в UP-списке (или они DOWN) */
struct auto_sock_iface** pp = &as->ifaces;
while (*pp) {
struct auto_sock_iface* ifa = *pp;
int found = 0;
for (int i = 0; i < up_count; i++) if (up[i] == ifa->netif_index) { found = 1; break; }
if (!found) { remove_iface_sockets(as, ifa->netif_index); continue; }
pp = &ifa->next;
}
}
#endif /* _WIN32 */

368
tools/chatgui/src/networksettingspage.cpp

@ -1,14 +1,19 @@
// networksettingspage.cpp — Network configuration form
// networksettingspage.cpp — Network configuration form (список listen-сокетов)
#include "networksettingspage.h"
#include "../transport/config_updater.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QLabel>
#include <QCheckBox>
#include <QLineEdit>
#include <QListWidget>
#include <QPushButton>
#include <QNetworkInterface>
#include <QFile>
#include <QTextStream>
static const char* SECTION = "server:lan";
static const QString AUTO_IP_LABEL = "Auto (0.0.0.0)";
NetworkSettingsPage::NetworkSettingsPage(QWidget* parent)
: QWidget(parent)
@ -17,175 +22,298 @@ NetworkSettingsPage::NetworkSettingsPage(QWidget* parent)
layout->setSpacing(12);
layout->setContentsMargins(0, 0, 0, 0);
auto* title = new QLabel("Listen Address", this);
auto* title = new QLabel("Listen Sockets", this);
title->setStyleSheet("font-weight: bold; font-size: 13px;");
layout->addWidget(title);
// Interface
auto* ifRow = new QHBoxLayout();
ifRow->addWidget(new QLabel("Interface:", this));
m_interfaceCombo = new QComboBox(this);
m_interfaceCombo->setMinimumWidth(180);
ifRow->addWidget(m_interfaceCombo, 1);
layout->addLayout(ifRow);
m_autoSocketsCheck = new QCheckBox("Auto sockets (UDP+TCP from interfaces)", this);
layout->addWidget(m_autoSocketsCheck);
m_serverList = new QListWidget(this);
m_serverList->setMinimumHeight(110);
layout->addWidget(m_serverList);
auto* btnRow = new QHBoxLayout();
m_addBtn = new QPushButton("Add", this);
m_removeBtn = new QPushButton("Remove", this);
btnRow->addWidget(m_addBtn);
btnRow->addWidget(m_removeBtn);
btnRow->addStretch();
layout->addLayout(btnRow);
// --- форма правки выбранного сокета ---
auto* formLayout = new QGridLayout();
formLayout->addWidget(new QLabel("Name:", this), 0, 0);
m_nameEdit = new QLineEdit(this);
m_nameEdit->setMinimumWidth(180);
formLayout->addWidget(m_nameEdit, 0, 1);
// IP
auto* ipRow = new QHBoxLayout();
ipRow->addWidget(new QLabel("IP Address:", this));
formLayout->addWidget(new QLabel("IP Address:", this), 1, 0);
m_ipCombo = new QComboBox(this);
m_ipCombo->setEditable(true);
m_ipCombo->setMinimumWidth(180);
ipRow->addWidget(m_ipCombo, 1);
layout->addLayout(ipRow);
formLayout->addWidget(m_ipCombo, 1, 1);
// Port
auto* portRow = new QHBoxLayout();
portRow->addWidget(new QLabel("Port:", this));
formLayout->addWidget(new QLabel("Port:", this), 2, 0);
m_portSpin = new QSpinBox(this);
m_portSpin->setRange(1, 65535);
m_portSpin->setValue(1333);
m_portSpin->setMinimumWidth(100);
portRow->addWidget(m_portSpin);
portRow->addStretch();
layout->addLayout(portRow);
// Protocol
auto* protoRow = new QHBoxLayout();
protoRow->addWidget(new QLabel("Protocol:", this));
formLayout->addWidget(m_portSpin, 2, 1);
formLayout->addWidget(new QLabel("Protocol:", this), 3, 0);
m_protoCombo = new QComboBox(this);
m_protoCombo->addItems({"UDP", "TCP"});
m_protoCombo->setMinimumWidth(100);
protoRow->addWidget(m_protoCombo);
protoRow->addStretch();
layout->addLayout(protoRow);
formLayout->addWidget(m_protoCombo, 3, 1);
formLayout->addWidget(new QLabel("Type:", this), 4, 0);
m_typeCombo = new QComboBox(this);
m_typeCombo->addItems({"public", "nat", "private", "local"});
formLayout->addWidget(m_typeCombo, 4, 1);
formLayout->addWidget(new QLabel("Interface:", this), 5, 0);
m_interfaceCombo = new QComboBox(this);
m_interfaceCombo->setMinimumWidth(180);
formLayout->addWidget(m_interfaceCombo, 5, 1);
layout->addLayout(formLayout);
auto* note = new QLabel("Changes take effect after restart.", this);
note->setStyleSheet("color: gray; font-size: 11px;");
layout->addWidget(note);
layout->addStretch();
populateInterfaces();
connect(m_interfaceCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &NetworkSettingsPage::onInterfaceChanged);
populateAddresses();
connect(m_autoSocketsCheck, &QCheckBox::toggled, this, &NetworkSettingsPage::onAutoSocketsToggled);
connect(m_addBtn, &QPushButton::clicked, this, &NetworkSettingsPage::onAddServer);
connect(m_removeBtn, &QPushButton::clicked, this, &NetworkSettingsPage::onRemoveServer);
connect(m_serverList, &QListWidget::currentRowChanged, this, [this](int) { onServerSelected(); });
connect(m_nameEdit, &QLineEdit::textChanged, this, [this](const QString&) { onFieldChanged(); });
connect(m_ipCombo, &QComboBox::editTextChanged, this, [this](const QString&) { onFieldChanged(); });
connect(m_portSpin, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int) { onFieldChanged(); });
connect(m_protoCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int) { onFieldChanged(); });
connect(m_typeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int) { onFieldChanged(); });
connect(m_interfaceCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int) { onFieldChanged(); });
}
static void splitAddr(const QString& addr, QString& ip, int& port) {
int lastColon = addr.lastIndexOf(':');
if (lastColon > 0) {
ip = addr.left(lastColon);
port = addr.mid(lastColon + 1).toInt();
} else {
ip = addr;
port = 0;
}
}
void NetworkSettingsPage::populateInterfaces() {
m_interfaceCombo->addItem("Auto (any)");
for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
if (iface.flags() & QNetworkInterface::IsLoopBack) continue;
if (!(iface.flags() & QNetworkInterface::IsUp)) continue;
m_interfaceCombo->addItem(iface.name());
}
}
void NetworkSettingsPage::populateAddresses() {
m_ipCombo->addItem(AUTO_IP_LABEL);
for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
if (iface.flags() & QNetworkInterface::IsLoopBack) continue;
if (!(iface.flags() & QNetworkInterface::IsUp)) continue;
for (const QNetworkAddressEntry& entry : iface.addressEntries()) {
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol)
m_ipCombo->addItem(entry.ip().toString());
}
}
}
void NetworkSettingsPage::applyAutoSocketsState() {
bool manual = !m_autoSocketsCheck->isChecked();
m_serverList->setEnabled(manual);
m_addBtn->setEnabled(manual);
m_removeBtn->setEnabled(manual);
m_nameEdit->setEnabled(manual);
m_ipCombo->setEnabled(manual);
m_portSpin->setEnabled(manual);
m_protoCombo->setEnabled(manual);
m_typeCombo->setEnabled(manual);
m_interfaceCombo->setEnabled(manual);
}
void NetworkSettingsPage::refreshServerList(int selectRow) {
m_updating = true;
m_serverList->clear();
for (const ConfigServer& s : m_servers) {
m_serverList->addItem(QString("%1 %2 (%3)").arg(s.name, s.addr, s.transport));
}
if (selectRow >= 0 && selectRow < m_serverList->count())
m_serverList->setCurrentRow(selectRow);
else if (m_serverList->count() > 0)
m_serverList->setCurrentRow(0);
m_updating = false;
}
void NetworkSettingsPage::loadFormFromSelected() {
int row = m_serverList->currentRow();
if (row < 0 || row >= m_servers.size()) return;
const ConfigServer& s = m_servers[row];
m_updating = true;
m_nameEdit->setText(s.name);
QString ip; int port = 0;
splitAddr(s.addr, ip, port);
if (ip == "0.0.0.0") ip = AUTO_IP_LABEL;
m_ipCombo->setCurrentText(ip);
m_portSpin->setValue(port > 0 ? port : 1333);
m_protoCombo->setCurrentIndex(s.transport == "tcp" ? 1 : 0);
int typeIdx = m_typeCombo->findText(s.type);
m_typeCombo->setCurrentIndex(typeIdx >= 0 ? typeIdx : 0);
int ifIdx = m_interfaceCombo->findText(s.netif);
m_interfaceCombo->setCurrentIndex(ifIdx >= 0 ? ifIdx : 0);
m_updating = false;
}
void NetworkSettingsPage::updateSelectedServerFromForm() {
int row = m_serverList->currentRow();
if (row < 0 || row >= m_servers.size()) return;
ConfigServer& s = m_servers[row];
s.name = m_nameEdit->text().trimmed();
QString ip = m_ipCombo->currentText().trimmed();
if (ip == AUTO_IP_LABEL) ip = "0.0.0.0";
s.addr = ip + ":" + QString::number(m_portSpin->value());
s.transport = (m_protoCombo->currentIndex() == 1) ? "tcp" : "udp";
s.type = m_typeCombo->currentText().trimmed();
QString iface = m_interfaceCombo->currentText();
s.netif = iface.startsWith("Auto") ? QString() : iface;
}
void NetworkSettingsPage::updateSelectedListItemText() {
int row = m_serverList->currentRow();
if (row < 0 || row >= m_servers.size()) return;
const ConfigServer& s = m_servers[row];
m_updating = true;
m_serverList->item(row)->setText(QString("%1 %2 (%3)").arg(s.name, s.addr, s.transport));
m_updating = false;
}
void NetworkSettingsPage::loadFromConfig(const QString& configPath) {
// Read [server:lan] section from INI file
QString addr, netif, transport;
m_configPath = configPath;
m_servers.clear();
QFile f(configPath);
if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
bool inSection = false;
QString section;
ConfigServer cur;
bool inServer = false;
QTextStream in(&f);
while (!in.atEnd()) {
QString line = in.readLine();
QString line = in.readLine().trimmed();
if (line.startsWith('[') && line.endsWith(']')) {
inSection = (line.mid(1, line.length() - 2) == SECTION);
if (inServer) m_servers.append(cur);
inServer = false;
section = line.mid(1, line.length() - 2);
if (section.startsWith("server:")) {
cur = ConfigServer();
cur.name = section.mid(7);
cur.transport = "udp";
cur.type = "public";
inServer = true;
}
continue;
}
if (!inSection || !line.contains('=')) continue;
if (!inServer || !line.contains('=')) continue;
QString key = line.section('=', 0, 0).trimmed();
QString val = line.section('=', 1).trimmed();
if (key == "addr") addr = val;
else if (key == "netif") netif = val;
else if (key == "transport") transport = val;
if (key == "addr") cur.addr = val;
else if (key == "transport") cur.transport = val;
else if (key == "type") cur.type = val;
else if (key == "netif") cur.netif = val;
}
if (inServer) m_servers.append(cur);
f.close();
}
// Parse addr: IP:port
QString ip;
if (!addr.isEmpty()) {
int lastColon = addr.lastIndexOf(':');
if (lastColon > 0) {
ip = addr.left(lastColon);
int port = addr.mid(lastColon + 1).toInt();
if (port > 0 && port <= 65535) m_portSpin->setValue(port);
/* auto_sockets из [global] */
bool autoSockets = false;
f.setFileName(configPath);
if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
QString section;
QTextStream in(&f);
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
if (line.startsWith('[') && line.endsWith(']')) {
section = line.mid(1, line.length() - 2);
continue;
}
if ((section == "global" || section.isEmpty()) && line.startsWith("auto_sockets=")) {
QString v = line.section('=', 1).trimmed();
autoSockets = (v.compare("yes", Qt::CaseInsensitive) == 0 || v == "1");
break;
}
}
f.close();
}
m_autoSocketsCheck->setChecked(autoSockets);
// Set protocol
int protoIdx = (transport == "tcp") ? 1 : 0;
m_protoCombo->setCurrentIndex(protoIdx);
// Set interface
int ifIdx = m_interfaceCombo->findText(netif);
if (ifIdx >= 0) m_interfaceCombo->setCurrentIndex(ifIdx);
// Set IP — populate addresses first, then select
populateAddresses(netif.isEmpty() ? QString() : netif);
if (ip.isEmpty() || ip == "0.0.0.0") ip = "Auto (0.0.0.0)";
int ipIdx = m_ipCombo->findText(ip);
if (ipIdx >= 0)
m_ipCombo->setCurrentIndex(ipIdx);
else
m_ipCombo->setCurrentText(ip);
refreshServerList();
applyAutoSocketsState();
}
void NetworkSettingsPage::saveToConfig(const QString& configPath) const {
// addr
QString ip = m_ipCombo->currentText().trimmed();
if (ip.isEmpty() || ip == "Auto (0.0.0.0)") ip = "0.0.0.0";
config_update_option(configPath, SECTION, "addr",
QString("%1:%2").arg(ip).arg(m_portSpin->value()),
CONFIG_UPDATE_REPLACE);
// netif: set or remove (empty value means no netif binding)
QString iface = m_interfaceCombo->currentText();
if (iface.startsWith("Auto"))
iface.clear();
config_update_option(configPath, SECTION, "netif", iface, CONFIG_UPDATE_REPLACE);
// transport
config_update_option(configPath, SECTION, "transport",
m_protoCombo->currentText().toLower(),
CONFIG_UPDATE_REPLACE);
/* 1. Серверные секции — полная перезапись */
config_rewrite_servers(configPath, m_servers);
// IPv6 dual-stack: always add [server:lan6] with same port
config_update_option(configPath, "server:lan6", "addr",
QString("[::]:%1").arg(m_portSpin->value()),
/* 2. auto_sockets */
config_update_option(configPath, "global", "auto_sockets",
m_autoSocketsCheck->isChecked() ? "yes" : "no",
CONFIG_UPDATE_REPLACE);
}
void NetworkSettingsPage::onInterfaceChanged(int index) {
Q_UNUSED(index);
QString iface = m_interfaceCombo->currentText();
populateAddresses(iface.startsWith("Auto") ? QString() : iface);
void NetworkSettingsPage::onAutoSocketsToggled(bool /*on*/) {
applyAutoSocketsState();
}
void NetworkSettingsPage::populateInterfaces() {
m_interfaceCombo->addItem("Auto (any)");
for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
if (iface.flags() & QNetworkInterface::IsLoopBack) continue;
if (!(iface.flags() & QNetworkInterface::IsUp)) continue;
m_interfaceCombo->addItem(iface.name());
void NetworkSettingsPage::onAddServer() {
int n = m_servers.size() + 1;
QString name;
for (;;) {
name = QString("srv%1").arg(n);
bool used = false;
for (const ConfigServer& s : m_servers) if (s.name == name) { used = true; break; }
if (!used) break;
n++;
}
ConfigServer s;
s.name = name;
s.addr = "0.0.0.0:1333";
s.transport = "udp";
s.type = "public";
m_servers.append(s);
refreshServerList(m_servers.size() - 1);
loadFormFromSelected();
}
void NetworkSettingsPage::populateAddresses(const QString& ifaceName) {
QString current = m_ipCombo->currentText();
m_ipCombo->clear();
m_ipCombo->addItem("Auto (0.0.0.0)");
void NetworkSettingsPage::onRemoveServer() {
int row = m_serverList->currentRow();
if (row < 0 || row >= m_servers.size()) return;
m_servers.removeAt(row);
refreshServerList(row);
loadFormFromSelected();
}
if (ifaceName.isEmpty()) {
for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
if (iface.flags() & QNetworkInterface::IsLoopBack) continue;
if (!(iface.flags() & QNetworkInterface::IsUp)) continue;
for (const QNetworkAddressEntry& entry : iface.addressEntries()) {
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol)
m_ipCombo->addItem(entry.ip().toString());
}
}
} else {
QNetworkInterface iface = QNetworkInterface::interfaceFromName(ifaceName);
if (iface.isValid()) {
for (const QNetworkAddressEntry& entry : iface.addressEntries()) {
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol)
m_ipCombo->addItem(entry.ip().toString());
}
}
}
void NetworkSettingsPage::onServerSelected() {
loadFormFromSelected();
}
int idx = m_ipCombo->findText(current);
if (idx >= 0) m_ipCombo->setCurrentIndex(idx);
void NetworkSettingsPage::onFieldChanged() {
if (m_updating) return;
updateSelectedServerFromForm();
updateSelectedListItemText();
}

40
tools/chatgui/src/networksettingspage.h

@ -1,10 +1,17 @@
// networksettingspage.h — Network configuration form
// networksettingspage.h — Network configuration form (список listen-сокетов)
#pragma once
#include <QWidget>
#include <QComboBox>
#include <QSpinBox>
#include <QString>
#include <QList>
#include "../transport/config_updater.h"
class QCheckBox;
class QLineEdit;
class QListWidget;
class QPushButton;
class NetworkSettingsPage : public QWidget {
Q_OBJECT
@ -15,14 +22,33 @@ public:
void saveToConfig(const QString& configPath) const;
private slots:
void onInterfaceChanged(int index);
void onAutoSocketsToggled(bool on);
void onAddServer();
void onRemoveServer();
void onServerSelected();
void onFieldChanged();
private:
void populateInterfaces();
void populateAddresses(const QString& ifaceName);
void populateAddresses();
void applyAutoSocketsState();
void refreshServerList(int selectRow = -1);
void loadFormFromSelected();
void updateSelectedServerFromForm();
void updateSelectedListItemText();
QCheckBox* m_autoSocketsCheck;
QListWidget* m_serverList;
QPushButton* m_addBtn;
QPushButton* m_removeBtn;
QLineEdit* m_nameEdit;
QComboBox* m_ipCombo;
QSpinBox* m_portSpin;
QComboBox* m_protoCombo;
QComboBox* m_typeCombo;
QComboBox* m_interfaceCombo;
QComboBox* m_interfaceCombo;
QComboBox* m_ipCombo;
QSpinBox* m_portSpin;
QComboBox* m_protoCombo;
QList<ConfigServer> m_servers;
QString m_configPath;
bool m_updating = false;
};

72
tools/chatgui/transport/config_updater.cpp

@ -85,3 +85,75 @@ bool config_update_option(const QString& filePath, const QString& section,
f.close();
return true;
}
static bool is_section_header(const QString& line) {
QString t = line.trimmed();
return t.startsWith('[') && t.endsWith(']');
}
bool config_rewrite_servers(const QString& filePath, const QList<ConfigServer>& servers) {
QFile f(filePath);
QStringList lines;
if (f.exists()) {
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return false;
QTextStream in(&f);
while (!in.atEnd()) {
QString line = in.readLine(0);
if (line.endsWith('\r')) line.chop(1);
lines.append(line);
}
f.close();
}
/* 1. Удалить все [server:*] секции (заголовок + ключи + хвостовую пустую строку-разделитель) */
QStringList filtered;
filtered.reserve(lines.size());
for (int i = 0; i < lines.size(); i++) {
QString t = lines[i].trimmed();
if (t.startsWith("[server:") && t.endsWith(']')) {
int j = i + 1;
while (j < lines.size() && !is_section_header(lines[j])) j++;
i = j - 1; /* for-loop сделает i++ → j: заголовок+ключи+пустая строка пропущены */
continue;
}
filtered << lines[i];
}
lines = filtered;
/* 2. Собрать блок секций [server:*] */
QStringList block;
for (const ConfigServer& s : servers) {
block << ("[server:" + s.name + "]");
block << ("addr=" + s.addr);
block << ("transport=" + (s.transport.isEmpty() ? QString("udp") : s.transport));
block << ("type=" + (s.type.isEmpty() ? QString("public") : s.type));
if (!s.netif.isEmpty()) block << ("netif=" + s.netif);
block << ""; /* разделитель: одна пустая строка между секциями */
}
/* 3. Точка вставки: после секции [global] (перед следующей секцией) */
int insertPos = lines.size();
for (int i = 0; i < lines.size(); i++) {
if (lines[i].trimmed() == "[global]") {
int j = i + 1;
while (j < lines.size() && !is_section_header(lines[j])) j++;
insertPos = j;
break;
}
}
/* 4. Вставить блок (если непустой) */
if (!block.isEmpty()) {
for (int i = block.size() - 1; i >= 0; i--)
lines.insert(insertPos, block[i]);
}
/* 5. Записать */
if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) return false;
QTextStream out(&f);
for (const QString& l : lines)
out << l << '\n';
f.close();
return true;
}

14
tools/chatgui/transport/config_updater.h

@ -4,12 +4,22 @@
#include <QString>
#include <QStringList>
#include <QList>
enum ConfigUpdateFlag {
CONFIG_UPDATE_REPLACE = 0, // replace existing param (single value)
CONFIG_UPDATE_APPEND = 1 // add new param even if exists (multi-value)
};
/* Один listen-сокет = секция [server:name]. */
struct ConfigServer {
QString name; // суффикс секции: "lan", "lan6"
QString addr; // "0.0.0.0:1333" | "[::]:1333"
QString transport; // "udp" | "tcp"
QString type; // "public" | "nat" | "private" | "local"
QString netif; // "" = не привязывать к интерфейсу
};
// Batch update — for callers that read/write the file once (e.g. NodeConfig::save)
bool config_update_in_lines(QStringList& lines, const QString& section,
const QString& key, const QString& value, bool replace);
@ -19,4 +29,8 @@ bool config_update_option(const QString& filePath, const QString& section,
const QString& param, const QString& value,
int flags = CONFIG_UPDATE_REPLACE);
// Полностью заменяет секции [server:*]: удаляет старые, вставляет новые после [global].
// Внутри секции без пустых строк, между секциями ровно одна пустая строка.
bool config_rewrite_servers(const QString& filePath, const QList<ConfigServer>& servers);
#endif // CONFIG_UPDATER_H

9
tools/chatgui/transport/node_config.cpp

@ -178,10 +178,8 @@ bool NodeConfig::create(const QString& nodeName, quint16 listenPort) {
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)});
m_servers.append({"lan6", QString("[::]:%1").arg(listenPort)});
}
(void)listenPort; /* автосокеты сами создают UDP+TCP на случайных портах */
m_autoSockets = true;
if (m_dbPath.isEmpty())
m_dbPath = "chat_data";
return save();
@ -277,6 +275,7 @@ bool NodeConfig::load() {
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 (key == "auto_sockets") m_autoSockets = (val.compare("yes", Qt::CaseInsensitive) == 0 || val == "1");
} else if (section.startsWith("server:")) {
QString srvName = section.mid(7);
if (key == "addr") m_servers.append({srvName, val});
@ -328,6 +327,7 @@ bool NodeConfig::saveFull() {
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 << "auto_sockets=" << (m_autoSockets ? "yes" : "no") << "\n";
out << "\n";
for (auto& srv : m_servers) {
@ -403,6 +403,7 @@ bool NodeConfig::save() {
config_update_in_lines(lines, "global", "my_node_id", m_nodeId, true);
config_update_in_lines(lines, "global", "my_private_key", m_privKey, true);
config_update_in_lines(lines, "global", "my_public_key", m_pubKey, true);
config_update_in_lines(lines, "global", "auto_sockets", m_autoSockets ? "yes" : "no", true);
if (!m_controlAddr.isEmpty()) {
int sep = m_controlAddr.lastIndexOf(':');

4
tools/chatgui/transport/node_config.h

@ -40,6 +40,9 @@ public:
quint16 controlPort() const;
void setControlPort(quint16 p);
bool autoSockets() const { return m_autoSockets; }
void setAutoSockets(bool v) { m_autoSockets = v; }
QString filePath() const { return m_path; }
/* GUI settings (from [gui] section) */
@ -65,6 +68,7 @@ private:
QList<QPair<QString,QString>> m_servers;
QList<NodeClient> m_clients;
QString m_controlAddr;
bool m_autoSockets = true;
QString m_dbPath;
QString m_debugFile;
QString m_debugLevel;

Loading…
Cancel
Save