From 8dd28fb90ac330c85314e32e97198acba9b91c9f Mon Sep 17 00:00:00 2001 From: evgeny Date: Sun, 25 Oct 2026 17:55:35 +0100 Subject: [PATCH] Distinguish LAN nonce failures from ordinary unknown devices --- src/chat/chat_local.c | 2 +- src/local_transport/local_transport.c | 22 +++++++++++++++------- src/local_transport/local_transport.h | 3 ++- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/chat/chat_local.c b/src/chat/chat_local.c index 7b8e3457..3af22b23 100644 --- a/src/chat/chat_local.c +++ b/src/chat/chat_local.c @@ -121,7 +121,7 @@ static int route_verify(void* arg, uint64_t peer, const uint8_t* data, size_t le struct TOPO_NODE* node = topo_node_registry_find(inst->topo_groups, peer); if (node && node->timestamp) memcpy(key, node->ed25519_public_key, sizeof(key)); else if (!inst->topo_sqlite_db || topo_node_sqlite_get_ed25519_pubkey(inst->topo_sqlite_db, peer, key)) { - DEBUG_DEBUG(DEBUG_CATEGORY_DISCOVERY, "LAN route ignored peer=%016llx reason=unknown signing key", (unsigned long long)peer); return -1; + DEBUG_DEBUG(DEBUG_CATEGORY_DISCOVERY, "LAN route ignored peer=%016llx reason=unknown signing key", (unsigned long long)peer); return 1; } if (sc_ed25519_verify(key, data, len, signature) != SC_OK) { DEBUG_WARN(DEBUG_CATEGORY_DISCOVERY, "LAN route signature rejected peer=%016llx", (unsigned long long)peer); return -1; diff --git a/src/local_transport/local_transport.c b/src/local_transport/local_transport.c index b0eac092..12524ba7 100644 --- a/src/local_transport/local_transport.c +++ b/src/local_transport/local_transport.c @@ -19,7 +19,7 @@ enum packet_drop { DROP_FOREIGN, DROP_NONLOCAL, DROP_HEADER, DROP_ANNOUNCE, DROP_IDENTITY, - DROP_ENDPOINT, DROP_ACK, DROP_MESSAGE, DROP_CAPACITY, DROP_LIMIT, DROP_COUNT + DROP_ENDPOINT, DROP_ACK, DROP_MESSAGE, DROP_CAPACITY, DROP_LIMIT, DROP_ROUTE_NONCE, DROP_ROUTE_ADDRESS, DROP_COUNT }; struct local_socket { @@ -98,7 +98,8 @@ static void drop_packet(struct local_transport* t, enum packet_drop reason) { t- static void reset_receive(struct local_transport* t) { static const char* reasons[DROP_COUNT] = { "foreign", "non-local source", "header/discovery type", "announcement/name", "identity/destination", - "unknown/expired endpoint", "ACK payload", "message/application limit", "table capacity", "receive budget" + "unknown/expired endpoint", "ACK payload", "message/application limit", "table capacity", "receive budget", + "LAN proof nonce/epoch/destination", "LAN signed/source IP mismatch" }; for (int i = 0; i < DROP_COUNT; i++) { if (t->dropped[i]) { @@ -273,13 +274,16 @@ static void receive_route(struct local_socket* s, const struct sockaddr_storage* if (destination || destination_epoch || memcmp(body, zero, 16)) { drop_packet(t, DROP_IDENTITY); return; } } else if (destination != t->node_id || destination_epoch != t->epoch || !e->challenge_until || now >= e->challenge_until || epoch != e->route_epoch || memcmp(body, e->nonce, 16)) { - drop_packet(t, DROP_IDENTITY); return; + DEBUG_DEBUG(DEBUG_CATEGORY_DISCOVERY, "LAN proof rejected peer=%016llx epoch=%016llx expected=%016llx pending=%d expired=%d", + (unsigned long long)sender, (unsigned long long)epoch, (unsigned long long)e->route_epoch, + e->challenge_until != 0, e->challenge_until != 0 && now >= e->challenge_until); + drop_packet(t, DROP_ROUTE_NONCE); return; } const void* ip = source->ss_family == AF_INET ? (const void*)&((const struct sockaddr_in*)source)->sin_addr : (const void*)&((const struct sockaddr_in6*)source)->sin6_addr; size_t ip_size = source->ss_family == AF_INET ? 4 : 16; if (body[16] != (ip_size == 4 ? 4 : 6) || memcmp(body + 17, ip, ip_size) || - (ip_size == 4 && memcmp(body + 21, zero, 12))) { drop_packet(t, DROP_NONLOCAL); return; } + (ip_size == 4 && memcmp(body + 21, zero, 12))) { drop_packet(t, DROP_ROUTE_ADDRESS); return; } struct local_route_socket sockets[LOCAL_ROUTE_SOCKETS_MAX] = {0}; for (unsigned i = 0; i < body[33]; i++) { const uint8_t* p = body + LOCAL_ROUTE_HEADER_SIZE + i * LOCAL_ROUTE_SOCKET_SIZE; @@ -300,8 +304,10 @@ static void receive_route(struct local_socket* s, const struct sockaddr_storage* if (!t->routes_left) { drop_packet(t, DROP_LIMIT); return; } t->routes_left--; size_t signed_size = size - LOCAL_ROUTE_SIGNATURE_SIZE; - if (t->callbacks.route_verify(t->arg, sender, data, signed_size, data + signed_size)) { - drop_packet(t, DROP_IDENTITY); return; + int verified = t->callbacks.route_verify(t->arg, sender, data, signed_size, data + signed_size); + if (verified) { + if (verified < 0) drop_packet(t, DROP_IDENTITY); + return; } if (type == LOCAL_ROUTE_ANNOUNCE) { int equal = e->route_count == body[33] && e->route_epoch == epoch; @@ -363,8 +369,10 @@ static void receive_packet(struct local_socket* s, struct sockaddr_storage* sour p = &t->peers[t->peer_count++]; memset(p, 0, sizeof(*p)); p->peer.node_id = peer_id; } if (p->epoch != epoch) { + int had_routes = 0; + for (size_t i = 0; i < p->count; i++) if (p->endpoints[i].route_until) had_routes = 1; p->epoch = epoch; p->count = 0; changed = 1; - if (t->callbacks.routes_changed) t->callbacks.routes_changed(t->arg, peer_id); + if (had_routes && t->callbacks.routes_changed) t->callbacks.routes_changed(t->arg, peer_id); } if (strlen(p->peer.name) != len || memcmp(p->peer.name, payload, len)) { memcpy(p->peer.name, payload, len); p->peer.name[len] = 0; changed = 1; diff --git a/src/local_transport/local_transport.h b/src/local_transport/local_transport.h index b45d7c26..7f264758 100644 --- a/src/local_transport/local_transport.h +++ b/src/local_transport/local_transport.h @@ -55,7 +55,8 @@ struct local_transport_callbacks { void (*delivery)(void* arg, uint64_t request_id, uint64_t peer_id, int status); // один результат: enum local_delivery_status void (*activity_changed)(void* arg, int active); // сокеты включаются/выключаются, в том числе при ошибке таймера /* Необязательный LAN transport: задать все пять callbacks либо ни одного. - * sockets возвращает 0..capacity или -1; sign/verify — 0/-1, подпись Ed25519 ровно 64 байта. + * sockets возвращает 0..capacity или -1; sign — 0/-1; verify — 0 (верна), 1 (ключ неизвестен), -1 (неверна/ошибка). + * Подпись Ed25519 ровно 64 байта. * Подписываются полный wire-заголовок и тело без подписи; verify использует только известный ключ peer. * offline возвращает 1 при отсутствии прямого UP и живого BGP-пути во всех группах. * routes_changed вызывается после применения/удаления адресов; не останавливать транспорт из callback. */