chat: invite-ссылки с выбором достижимого узла + tcp_io фикс half-close FIN

- Общий модуль invite_build: сборка utun://-ссылок, авто-выбор лучшего
  достижимого узла, список кандидатов (вынесено из android jni_bridge)
- chatgui: диалог invite с выбором узла (комбобокс), события INVITE_CANDIDATES /
  INVITE_LINK_READY через gui_bridge
- headless/chatcli: invite <ch_id> [node_id], команда invite_nodes
- tcp_io: error_cb отличает грациозный EPOLLHUP (SO_ERROR==0) от реальной ошибки —
  при приостановленном чтении FIN не роняет недослейные данные (фикс flaky half-close)
- run_test.sh: half-close echo на выделенном порту 19190 + отдельный DNAT (был недостижим)
This commit is contained in:
evgeny committed 2026-09-07 13:59:15 +03:00
1 parent a17a85a383
commit 1345f44e10
23 files changed
+872 -539

No files matched your search

-115
View File
@@ -2,14 +2,9 @@
#include "utun_node.h"
#include <cinttypes>
#include <QDir>
#include <QMap>
#include <QPair>
#include <csignal>
#include "../../lib/socket_compat.h"
#include "../db/db_manager.h"
#include "../src/invite_link.h"
// === C headers ===
extern "C" {
#include "utun_instance.h"
@@ -139,116 +134,6 @@ bool UtunNode::ntpEnabled() const {
return m_instance ? m_instance->ntp.enabled : false;
}
QList<NodeAddr> UtunNode::getInviteAddresses(DbManager* db) {
(void)db;
QList<NodeAddr> addrs;
QMap<QPair<QByteArray, quint16>, uint8_t> addrProto; // key=(addr,port) → proto bits
auto add_addr = [&](const QByteArray& addr, quint16 port, int family,
int sockId, uint8_t proto) {
QPair<QByteArray, quint16> key(addr, port);
uint8_t existing = addrProto.value(key, 0);
addrProto[key] = existing | proto;
// Store sockId/family for serialization (overwrite is fine — same port)
};
auto append_from_addr_proto = [&](const QByteArray& addr, quint16 port,
int family, int sockId, uint8_t proto) {
NodeAddr a;
a.family = family;
a.address = addr;
a.port = port;
a.socketId = sockId;
a.protocol = proto;
addrs.append(a);
};
if (m_instance) {
/* 1) UDP from active ETCP links */
struct ll_entry* entry = m_instance->connections->head;
while (entry) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data;
struct ETCP_CONN* conn = ce->conn;
if (conn && conn->initialized && conn->links_up > 0) {
struct ETCP_LINK* link = conn->links;
while (link) {
if (!link->is_tcp && link->link_state == 3 && link->nat_type == NAT_TYPE_DIRECT) {
struct sockaddr_storage* sa = &link->remote_addr;
if (sa->ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)sa;
QByteArray addr((const char*)&sin->sin_addr, 4);
add_addr(addr, ntohs(sin->sin_port), 4,
link->conn ? (int)link->conn->sock_id : 0, INVITE_PROTO_UDP);
} else {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa;
QByteArray addr((const char*)&sin6->sin6_addr, 16);
add_addr(addr, ntohs(sin6->sin6_port), 6,
link->conn ? (int)link->conn->sock_id : 0, INVITE_PROTO_UDP);
}
}
link = link->next;
}
}
entry = entry->next;
}
/* 2) UDP from local ETCP sockets (fallback) */
{
struct ETCP_SOCKET* sock = m_instance->etcp_sockets;
while (sock) {
if (sock->is_tcp) { sock = sock->next; continue; }
struct sockaddr_storage* sa = NULL;
if (sock->nat_addr.ss_family != 0)
sa = &sock->nat_addr;
else if (sock->interface_addr.ss_family != 0)
sa = &sock->interface_addr;
if (sa) {
if (sa->ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)sa;
QByteArray addr((const char*)&sin->sin_addr, 4);
add_addr(addr, ntohs(sin->sin_port), 4, (int)sock->sock_id, INVITE_PROTO_UDP);
} else {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa;
QByteArray addr((const char*)&sin6->sin6_addr, 16);
add_addr(addr, ntohs(sin6->sin6_port), 6, (int)sock->sock_id, INVITE_PROTO_UDP);
}
}
sock = sock->next;
}
}
/* 3) TCP from local TCP sockets */
{
struct ETCP_SOCKET* s = m_instance->etcp_sockets;
while (s) {
if (!s->is_tcp) { s = s->next; continue; }
struct sockaddr_storage* sa = &s->interface_addr;
if (sa->ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)sa;
QByteArray addr((const char*)&sin->sin_addr, 4);
add_addr(addr, ntohs(sin->sin_port), 4, (int)s->sock_id, INVITE_PROTO_TCP);
} else if (sa->ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa;
QByteArray addr((const char*)&sin6->sin6_addr, 16);
add_addr(addr, ntohs(sin6->sin6_port), 6, (int)s->sock_id, INVITE_PROTO_TCP);
}
s = s->next;
}
}
/* 4) Dedup by (addr,port); emit one entry per proto (UDP и TCP раздельно, без BOTH) */
for (auto it = addrProto.begin(); it != addrProto.end(); ++it) {
QByteArray addr = it.key().first;
quint16 port = it.key().second;
uint8_t proto = it.value();
int family = (addr.size() == 16) ? 6 : 4;
if (proto & INVITE_PROTO_UDP) append_from_addr_proto(addr, port, family, 0, INVITE_PROTO_UDP);
if (proto & INVITE_PROTO_TCP) append_from_addr_proto(addr, port, family, 0, INVITE_PROTO_TCP);
}
}
return addrs;
}
void UtunNode::recvCallback(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!g_currentNode || !entry || !entry->dgram || entry->len < ROUTER_SVC_HDR_SIZE) return;
uint64_t src;