test(relay): cover join notification ordering
Add a relay regression test for the join ordering used by gateway MAC-state seeding. The test sends a second client's hello but intentionally delays reading that client's welcome until after the existing peer receives PeerJoined. This guards the ordering from the relay admission path: existing peers are notified before the joining peer can proceed from its welcome and begin sending Ethernet datagrams. That matters for first DHCP/ARP frames after a Windows client joins a room with an existing LAN gateway. Test Plan: - cargo test -p lanparty-relay notifies_existing_peer_before_join_welcome - cargo fmt --check - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - git diff --check - git diff --cached --check Refs: PLAN.md MVP relay lifecycle and gateway MAC learning
This commit is contained in:
@@ -1004,6 +1004,71 @@ mod tests {
|
||||
assert_eq!(rooms.lock().await.room_count(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn notifies_existing_peer_before_join_welcome() {
|
||||
let (server, certificate) = bind_test_server(DEFAULT_MAX_CLIENTS_PER_ROOM);
|
||||
let rooms = Arc::clone(&server.rooms);
|
||||
let server_addr = server.local_addr().unwrap();
|
||||
let server_task = tokio::spawn(async move {
|
||||
let handles = server.accept_many_for_test(2).await.unwrap();
|
||||
let mut accepted = Vec::with_capacity(handles.len());
|
||||
|
||||
for handle in handles {
|
||||
accepted.push(handle.await.unwrap().unwrap().unwrap());
|
||||
}
|
||||
|
||||
server.shutdown("test complete").await;
|
||||
accepted
|
||||
});
|
||||
|
||||
let first_endpoint = client_endpoint(certificate.clone()).unwrap();
|
||||
let first_connection = first_endpoint
|
||||
.connect(server_addr, "lanparty-relay.local")
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
let second_endpoint = client_endpoint(certificate).unwrap();
|
||||
let second_connection = second_endpoint
|
||||
.connect(server_addr, "lanparty-relay.local")
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let first_welcome = welcome_for_client(&first_connection, client_mac(1)).await;
|
||||
let mut second_response =
|
||||
send_client_hello_without_reading_response(&second_connection, client_mac(2)).await;
|
||||
|
||||
let ControlMessage::PeerJoined(peer) = read_control_event(&first_connection).await else {
|
||||
panic!("expected existing peer to receive peer joined event");
|
||||
};
|
||||
assert_eq!(peer.peer_id(), 2);
|
||||
assert_eq!(peer.role(), Role::Client);
|
||||
assert_eq!(peer.mac(), Some(client_mac(2)));
|
||||
|
||||
let response = second_response
|
||||
.read_to_end(MAX_CONTROL_FRAME_LEN)
|
||||
.await
|
||||
.unwrap();
|
||||
let ControlMessage::Welcome(second_welcome) = decode_control_frame(&response).unwrap()
|
||||
else {
|
||||
panic!("expected joining peer welcome");
|
||||
};
|
||||
assert_eq!(second_welcome.peer_id(), 2);
|
||||
assert_eq!(second_welcome.room_id(), first_welcome.room_id());
|
||||
|
||||
first_connection.close(0_u32.into(), b"test complete");
|
||||
second_connection.close(0_u32.into(), b"test complete");
|
||||
first_endpoint.wait_idle().await;
|
||||
second_endpoint.wait_idle().await;
|
||||
|
||||
let accepted = tokio::time::timeout(Duration::from_secs(5), server_task)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(accepted.len(), 2);
|
||||
assert_eq!(rooms.lock().await.room_count(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn writes_development_certificate_when_configured() {
|
||||
let cert_path = unique_temp_cert_path();
|
||||
@@ -1548,6 +1613,19 @@ mod tests {
|
||||
welcome
|
||||
}
|
||||
|
||||
async fn send_client_hello_without_reading_response(
|
||||
connection: &quinn::Connection,
|
||||
mac: MacAddr,
|
||||
) -> RecvStream {
|
||||
let hello = EndpointHello::client(RoomCode::new("TESTROOM").unwrap(), mac, 1400).unwrap();
|
||||
let (mut send, recv) = connection.open_bi().await.unwrap();
|
||||
let request = encode_control_message(&ControlMessage::Hello(hello)).unwrap();
|
||||
send.write_all(&request).await.unwrap();
|
||||
send.finish().unwrap();
|
||||
|
||||
recv
|
||||
}
|
||||
|
||||
async fn accepted_client_for_forwarding(
|
||||
rooms: &Arc<Mutex<RoomRegistry>>,
|
||||
mac: MacAddr,
|
||||
|
||||
Reference in New Issue
Block a user