fix(peer): record listener addresses during handshakes

Peers discovered over mDNS could still attribute later library sync traffic to
temporary QUIC source ports. In a real GUI LAN run this made Host B try to
push its library to Host A's outbound port instead of Host A's advertised
listener, so Host A discovered the peer but never saw its games.

Carry the stable listener address in Hello and HelloAck, and key library sync
messages by peer_id instead of inferring identity from the transport source
address. The handshake path now explicitly refreshes an empty peer library from
the known listener address, matching the reliability of the direct-connect CLI
path without overwriting richer snapshot state when it already arrived.

This changes the current wire protocol, so PROTOCOL_VERSION is bumped to 3 and
all peers must be rebuilt together. The architecture note now documents that
listener addresses come from mDNS or Hello/HelloAck, never from ephemeral QUIC
source ports.

Test Plan:
- just fmt
- just test
- just clippy
- just build
- git diff --check

Refs: Local Linux/Win11 GUI LAN test logs from 2026-05-18.
This commit is contained in:
2026-05-18 17:27:15 +02:00
parent 642463d7eb
commit 348a02c35f
8 changed files with 224 additions and 100 deletions
+8 -41
View File
@@ -8,8 +8,7 @@ use std::{
sync::Arc,
};
use lanspread_db::db::{Game, GameDB, GameFileDescription};
use lanspread_proto::GameSummary;
use lanspread_db::db::{GameDB, GameFileDescription};
use tokio::sync::{RwLock, mpsc::UnboundedSender};
use crate::{
@@ -31,7 +30,7 @@ use crate::{
scan_local_library,
version_ini_is_regular_file,
},
network::{request_game_details_from_peer, request_game_list_from_peer, send_library_delta},
network::{request_game_details_from_peer, send_library_delta},
peer_db::PeerGameDB,
remote_peer::ensure_peer_id_for_addr,
services::perform_handshake_with_peer,
@@ -706,6 +705,7 @@ pub async fn handle_connect_peer_command(
) {
log::info!("Direct connect command received for {addr}");
let peer_id = ctx.peer_id.clone();
let local_peer_addr = ctx.local_peer_addr.clone();
let local_library = ctx.local_library.clone();
let peer_game_db = ctx.peer_game_db.clone();
let tx_notify_ui = tx_notify_ui.clone();
@@ -713,54 +713,20 @@ pub async fn handle_connect_peer_command(
ctx.task_tracker.spawn(async move {
if let Err(err) = perform_handshake_with_peer(
peer_id,
local_peer_addr,
local_library,
peer_game_db.clone(),
tx_notify_ui.clone(),
peer_game_db,
tx_notify_ui,
addr,
None,
)
.await
{
log::warn!("Failed direct connect to {addr}: {err}");
return;
}
if let Err(err) = refresh_direct_peer_games(&peer_game_db, &tx_notify_ui, addr).await {
log::warn!("Failed to refresh direct peer games from {addr}: {err}");
}
});
}
async fn refresh_direct_peer_games(
peer_game_db: &Arc<RwLock<PeerGameDB>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
addr: SocketAddr,
) -> eyre::Result<()> {
let games = request_game_list_from_peer(addr).await?;
let summaries = games.into_iter().map(game_to_summary).collect::<Vec<_>>();
let peer_id = ensure_peer_id_for_addr(peer_game_db, addr).await;
{
let mut db = peer_game_db.write().await;
db.update_peer_games(&peer_id, summaries);
}
events::emit_peer_game_list(peer_game_db, tx_notify_ui).await;
Ok(())
}
fn game_to_summary(game: Game) -> GameSummary {
let availability = game.normalized_availability();
GameSummary {
id: game.id,
name: game.name,
size: game.size,
downloaded: game.downloaded,
installed: game.installed,
eti_version: game.eti_game_version,
manifest_hash: 0,
availability,
}
}
// =============================================================================
// Game announcement helpers
// =============================================================================
@@ -823,8 +789,9 @@ pub async fn update_and_announce_games(
for peer_addr in peer_targets {
let delta = delta.clone();
let peer_id = ctx.peer_id.as_ref().clone();
ctx.task_tracker.spawn(async move {
if let Err(e) = send_library_delta(peer_addr, delta).await {
if let Err(e) = send_library_delta(peer_addr, &peer_id, delta).await {
log::warn!("Failed to send library delta to {peer_addr}: {e}");
}
});