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:
@@ -14,7 +14,6 @@ use crate::{
|
||||
events,
|
||||
local_games::{get_game_file_descriptions, is_local_dir_name, local_download_available},
|
||||
peer::{send_game_file_chunk, send_game_file_data},
|
||||
remote_peer::ensure_peer_id_for_addr,
|
||||
services::handshake::{
|
||||
accept_inbound_hello,
|
||||
perform_handshake_with_peer,
|
||||
@@ -82,16 +81,16 @@ async fn dispatch_request(
|
||||
send_response(framed_tx, Response::HelloAck(ack), "HelloAck").await
|
||||
}
|
||||
Request::ListGames => handle_list_games(ctx, framed_tx).await,
|
||||
Request::LibrarySummary(summary) => {
|
||||
handle_library_summary(ctx, remote_addr, summary).await;
|
||||
Request::LibrarySummary { peer_id, summary } => {
|
||||
handle_library_summary(ctx, peer_id, summary).await;
|
||||
framed_tx
|
||||
}
|
||||
Request::LibrarySnapshot(snapshot) => {
|
||||
handle_library_snapshot(ctx, remote_addr, snapshot).await;
|
||||
Request::LibrarySnapshot { peer_id, snapshot } => {
|
||||
handle_library_snapshot(ctx, peer_id, snapshot).await;
|
||||
framed_tx
|
||||
}
|
||||
Request::LibraryDelta(delta) => {
|
||||
handle_library_delta(ctx, remote_addr, delta).await;
|
||||
Request::LibraryDelta { peer_id, delta } => {
|
||||
handle_library_delta(ctx, peer_id, delta).await;
|
||||
framed_tx
|
||||
}
|
||||
Request::GetGame { id } => handle_get_game(ctx, id, framed_tx).await,
|
||||
@@ -160,20 +159,16 @@ async fn handle_list_games(ctx: &PeerCtx, framed_tx: ResponseWriter) -> Response
|
||||
send_response(framed_tx, Response::ListGames(games), "ListGames").await
|
||||
}
|
||||
|
||||
async fn handle_library_summary(
|
||||
ctx: &PeerCtx,
|
||||
remote_addr: Option<SocketAddr>,
|
||||
summary: LibrarySummary,
|
||||
) {
|
||||
let Some(addr) = remote_addr else {
|
||||
return;
|
||||
};
|
||||
|
||||
let peer_id = ensure_peer_id_for_addr(&ctx.peer_game_db, addr).await;
|
||||
let (previous_digest, previous_count, features) = {
|
||||
async fn handle_library_summary(ctx: &PeerCtx, peer_id: String, summary: LibrarySummary) {
|
||||
let (addr, previous_digest, previous_count, features) = {
|
||||
let db = ctx.peer_game_db.read().await;
|
||||
let Some(addr) = db.peer_addr(&peer_id) else {
|
||||
log::debug!("Ignoring library summary from unknown peer {peer_id}");
|
||||
return;
|
||||
};
|
||||
let (_, digest) = db.peer_library_state(&peer_id).unwrap_or((0, 0));
|
||||
(
|
||||
addr,
|
||||
digest,
|
||||
db.peer_game_count(&peer_id),
|
||||
db.peer_features(&peer_id),
|
||||
@@ -193,12 +188,14 @@ async fn handle_library_summary(
|
||||
if summary.library_digest != previous_digest || previous_count == 0 {
|
||||
ctx.task_tracker.spawn({
|
||||
let peer_id_arc = 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 = ctx.tx_notify_ui.clone();
|
||||
async move {
|
||||
if let Err(err) = perform_handshake_with_peer(
|
||||
peer_id_arc,
|
||||
local_peer_addr,
|
||||
local_library,
|
||||
peer_game_db,
|
||||
tx_notify_ui,
|
||||
@@ -214,28 +211,32 @@ async fn handle_library_summary(
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_library_snapshot(
|
||||
ctx: &PeerCtx,
|
||||
remote_addr: Option<SocketAddr>,
|
||||
snapshot: LibrarySnapshot,
|
||||
) {
|
||||
if let Some(addr) = remote_addr {
|
||||
let peer_id = ensure_peer_id_for_addr(&ctx.peer_game_db, addr).await;
|
||||
{
|
||||
let mut db = ctx.peer_game_db.write().await;
|
||||
async fn handle_library_snapshot(ctx: &PeerCtx, peer_id: String, snapshot: LibrarySnapshot) {
|
||||
let applied = {
|
||||
let mut db = ctx.peer_game_db.write().await;
|
||||
if db.peer_addr(&peer_id).is_some() {
|
||||
db.apply_library_snapshot(&peer_id, snapshot);
|
||||
true
|
||||
} else {
|
||||
log::debug!("Ignoring library snapshot from unknown peer {peer_id}");
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if applied {
|
||||
events::emit_peer_game_list(&ctx.peer_game_db, &ctx.tx_notify_ui).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_library_delta(ctx: &PeerCtx, remote_addr: Option<SocketAddr>, delta: LibraryDelta) {
|
||||
let Some(addr) = remote_addr else {
|
||||
async fn handle_library_delta(ctx: &PeerCtx, peer_id: String, delta: LibraryDelta) {
|
||||
let Some(addr) = ({
|
||||
let db = ctx.peer_game_db.read().await;
|
||||
db.peer_addr(&peer_id)
|
||||
}) else {
|
||||
log::debug!("Ignoring library delta from unknown peer {peer_id}");
|
||||
return;
|
||||
};
|
||||
|
||||
let peer_id = ensure_peer_id_for_addr(&ctx.peer_game_db, addr).await;
|
||||
let applied = {
|
||||
let mut db = ctx.peer_game_db.write().await;
|
||||
db.apply_library_delta(&peer_id, delta)
|
||||
@@ -246,6 +247,7 @@ async fn handle_library_delta(ctx: &PeerCtx, remote_addr: Option<SocketAddr>, de
|
||||
} else {
|
||||
spawn_library_resync(
|
||||
ctx.peer_id.clone(),
|
||||
ctx.local_peer_addr.clone(),
|
||||
ctx.local_library.clone(),
|
||||
ctx.peer_game_db.clone(),
|
||||
ctx.tx_notify_ui.clone(),
|
||||
|
||||
Reference in New Issue
Block a user