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);
|
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]
|
#[tokio::test]
|
||||||
async fn writes_development_certificate_when_configured() {
|
async fn writes_development_certificate_when_configured() {
|
||||||
let cert_path = unique_temp_cert_path();
|
let cert_path = unique_temp_cert_path();
|
||||||
@@ -1548,6 +1613,19 @@ mod tests {
|
|||||||
welcome
|
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(
|
async fn accepted_client_for_forwarding(
|
||||||
rooms: &Arc<Mutex<RoomRegistry>>,
|
rooms: &Arc<Mutex<RoomRegistry>>,
|
||||||
mac: MacAddr,
|
mac: MacAddr,
|
||||||
|
|||||||
Reference in New Issue
Block a user