test(client): cover DHCP reply policy at relay boundary
The MVP depends on the Windows TAP adapter receiving DHCP from the real LAN, while still preventing a remote client from pretending to be a DHCP server. The lower-level safety classifiers already encode that direction split, but the client relay boundary did not test both sides together. Extend the client-core QUIC session test with a LAN-side DHCPv4 server reply that must be accepted toward TAP, and a remote-client DHCPv4 server reply that must be dropped before relay send. This keeps the critical DHCP path covered at the same layer that records client stats and feeds the Windows TAP frame pump. Test Plan: - cargo fmt - cargo test -p lanparty-client-core - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings Refs: MVP DHCP tunnel acceptance
This commit is contained in:
@@ -914,6 +914,18 @@ mod tests {
|
|||||||
.send_datagram(Bytes::from(misdirected_response))
|
.send_datagram(Bytes::from(misdirected_response))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let dhcp_response = encode_datagram(
|
||||||
|
FrameType::Ethernet,
|
||||||
|
7,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
&lan_dhcpv4_server_reply_to_client(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
connection
|
||||||
|
.send_datagram(Bytes::from(dhcp_response))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let response = encode_datagram(
|
let response = encode_datagram(
|
||||||
FrameType::Ethernet,
|
FrameType::Ethernet,
|
||||||
7,
|
7,
|
||||||
@@ -938,7 +950,7 @@ mod tests {
|
|||||||
let ControlMessage::Stats(stats) = stats_message else {
|
let ControlMessage::Stats(stats) = stats_message else {
|
||||||
panic!("expected client stats event");
|
panic!("expected client stats event");
|
||||||
};
|
};
|
||||||
assert_eq!(stats, TunnelStats::new(1, 3, 1, 3, 10, 1));
|
assert_eq!(stats, TunnelStats::new(1, 4, 1, 4, 11, 1));
|
||||||
stats_received_tx.send(()).unwrap();
|
stats_received_tx.send(()).unwrap();
|
||||||
|
|
||||||
let mut disconnect_recv = connection.accept_uni().await.unwrap();
|
let mut disconnect_recv = connection.accept_uni().await.unwrap();
|
||||||
@@ -1028,6 +1040,20 @@ mod tests {
|
|||||||
misdirected_unicast_ethernet_frame().as_slice()
|
misdirected_unicast_ethernet_frame().as_slice()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let received =
|
||||||
|
tokio::time::timeout(Duration::from_secs(5), relay_io.recv_ethernet_outcome())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
let ClientReceiveOutcome::Accepted(received) = received else {
|
||||||
|
panic!("expected accepted LAN DHCP server reply");
|
||||||
|
};
|
||||||
|
assert_eq!(received.source_peer_id(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
received.payload(),
|
||||||
|
lan_dhcpv4_server_reply_to_client().as_slice()
|
||||||
|
);
|
||||||
|
|
||||||
let received =
|
let received =
|
||||||
tokio::time::timeout(Duration::from_secs(5), relay_io.recv_ethernet_outcome())
|
tokio::time::timeout(Duration::from_secs(5), relay_io.recv_ethernet_outcome())
|
||||||
.await
|
.await
|
||||||
@@ -1107,14 +1133,20 @@ mod tests {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
ClientSendOutcome::Dropped(DropReason::VlanTaggedFrame)
|
ClientSendOutcome::Dropped(DropReason::VlanTaggedFrame)
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
relay_io
|
||||||
|
.send_ethernet_with_outcome(&remote_dhcpv4_server_reply())
|
||||||
|
.unwrap(),
|
||||||
|
ClientSendOutcome::Dropped(DropReason::DhcpServerReply)
|
||||||
|
);
|
||||||
let stats = relay_io.stats_snapshot();
|
let stats = relay_io.stats_snapshot();
|
||||||
assert_eq!(stats.ethernet_frames_tx(), 1);
|
assert_eq!(stats.ethernet_frames_tx(), 1);
|
||||||
assert_eq!(stats.ethernet_frames_rx(), 3);
|
assert_eq!(stats.ethernet_frames_rx(), 4);
|
||||||
assert_eq!(stats.broadcast_frames_tx(), 0);
|
assert_eq!(stats.broadcast_frames_tx(), 0);
|
||||||
assert_eq!(stats.broadcast_frames_rx(), 0);
|
assert_eq!(stats.broadcast_frames_rx(), 0);
|
||||||
assert_eq!(stats.datagrams_tx(), 1);
|
assert_eq!(stats.datagrams_tx(), 1);
|
||||||
assert_eq!(stats.datagrams_rx(), 3);
|
assert_eq!(stats.datagrams_rx(), 4);
|
||||||
assert_eq!(stats.dropped_frames(), 10);
|
assert_eq!(stats.dropped_frames(), 11);
|
||||||
assert_eq!(stats.malformed_frames(), 1);
|
assert_eq!(stats.malformed_frames(), 1);
|
||||||
assert_eq!(client.stats_snapshot(), stats);
|
assert_eq!(client.stats_snapshot(), stats);
|
||||||
|
|
||||||
@@ -1265,6 +1297,33 @@ mod tests {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn lan_dhcpv4_server_reply_to_client() -> Vec<u8> {
|
||||||
|
ethernet_frame_with_headers(
|
||||||
|
MacAddr::new([0x02, 0, 0, 0, 0, 1]),
|
||||||
|
MacAddr::new([0x0a, 0, 0, 0, 0, 2]),
|
||||||
|
lanparty_proto::ETHERTYPE_IPV4,
|
||||||
|
&ipv4_udp_payload(67, 68),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remote_dhcpv4_server_reply() -> Vec<u8> {
|
||||||
|
ethernet_frame_with_headers(
|
||||||
|
MacAddr::BROADCAST,
|
||||||
|
MacAddr::new([0x02, 0, 0, 0, 0, 1]),
|
||||||
|
lanparty_proto::ETHERTYPE_IPV4,
|
||||||
|
&ipv4_udp_payload(67, 68),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ipv4_udp_payload(source_port: u16, destination_port: u16) -> Vec<u8> {
|
||||||
|
let mut packet = vec![0; 28];
|
||||||
|
packet[0] = 0x45;
|
||||||
|
packet[9] = 17;
|
||||||
|
packet[20..22].copy_from_slice(&source_port.to_be_bytes());
|
||||||
|
packet[22..24].copy_from_slice(&destination_port.to_be_bytes());
|
||||||
|
packet
|
||||||
|
}
|
||||||
|
|
||||||
fn ethernet_frame_with_headers(
|
fn ethernet_frame_with_headers(
|
||||||
destination: MacAddr,
|
destination: MacAddr,
|
||||||
source: MacAddr,
|
source: MacAddr,
|
||||||
|
|||||||
Reference in New Issue
Block a user