mirror of
https://github.com/rust-osdev/uefi-rs
synced 2026-06-08 17:17:36 +00:00
Merge pull request #1621 from rust-osdev/snp-test
SNP Integration Test: Improve Clarity
This commit is contained in:
Generated
+45
@@ -345,12 +345,31 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "hash32"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
|
||||
|
||||
[[package]]
|
||||
name = "heapless"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
|
||||
dependencies = [
|
||||
"hash32",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
@@ -438,6 +457,12 @@ dependencies = [
|
||||
"crc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "managed"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
|
||||
|
||||
[[package]]
|
||||
name = "mbrman"
|
||||
version = "0.6.0"
|
||||
@@ -750,6 +775,25 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "smoltcp"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"byteorder",
|
||||
"cfg-if",
|
||||
"heapless",
|
||||
"managed",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
@@ -950,6 +994,7 @@ version = "0.2.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"qemu-exit",
|
||||
"smoltcp",
|
||||
"uefi",
|
||||
"uefi-raw",
|
||||
]
|
||||
|
||||
@@ -8,6 +8,7 @@ edition = "2024"
|
||||
[dependencies]
|
||||
uefi-raw = { path = "../uefi-raw" }
|
||||
uefi = { path = "../uefi", features = ["alloc", "global_allocator", "panic_handler", "logger", "qemu", "log-debugcon"] }
|
||||
smoltcp = { version = "0.12.0", default-features = false, features = ["medium-ethernet", "proto-ipv4", "socket-udp"] }
|
||||
|
||||
log.workspace = true
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ pub fn test() {
|
||||
|
||||
http::test();
|
||||
pxe::test();
|
||||
// Currently, we are in the unfortunate situation that the SNP test
|
||||
// depends on the PXE test, as it assigns an IPv4 address to the
|
||||
// interface via DHCP.
|
||||
snp::test();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,8 +26,11 @@ pub fn test() {
|
||||
|
||||
assert!(base_code.mode().dhcp_ack_received());
|
||||
let dhcp_ack: &DhcpV4Packet = base_code.mode().dhcp_ack().as_ref();
|
||||
let server_ip = dhcp_ack.bootp_si_addr;
|
||||
let server_ip = IpAddress::new_v4(server_ip);
|
||||
|
||||
info!("DHCP: Server IP: {:?}", dhcp_ack.bootp_si_addr);
|
||||
info!("DHCP: Client IP: {:?}", dhcp_ack.bootp_yi_addr);
|
||||
|
||||
let server_ip = IpAddress::new_v4(dhcp_ack.bootp_si_addr);
|
||||
|
||||
const EXAMPLE_FILE_NAME: &[u8] = b"example-file.txt\0";
|
||||
const EXAMPLE_FILE_CONTENT: &[u8] = b"Hello world!";
|
||||
|
||||
@@ -1,141 +1,263 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use core::ops::DerefMut;
|
||||
use core::time::Duration;
|
||||
|
||||
use smoltcp::wire::{
|
||||
ETHERNET_HEADER_LEN, EthernetFrame, IPV4_HEADER_LEN, Ipv4Packet, UDP_HEADER_LEN, UdpPacket,
|
||||
};
|
||||
use uefi::boot::ScopedProtocol;
|
||||
use uefi::proto::network::MacAddress;
|
||||
use uefi::proto::network::snp::{InterruptStatus, ReceiveFlags, SimpleNetwork};
|
||||
use uefi::{Status, boot};
|
||||
use uefi_raw::protocol::network::snp::NetworkState;
|
||||
|
||||
pub fn test() {
|
||||
info!("Testing the simple network protocol");
|
||||
/// The MAC address configured for the interface.
|
||||
const EXPECTED_MAC: [u8; 6] = [0x52, 0x54, 0, 0, 0, 0x1];
|
||||
const ETHERNET_PROTOCOL_IPV4: u16 = 0x0800;
|
||||
|
||||
fn find_network_device() -> Option<ScopedProtocol<SimpleNetwork>> {
|
||||
let mut maybe_handle = None;
|
||||
|
||||
let handles = boot::find_handles::<SimpleNetwork>().unwrap_or_default();
|
||||
|
||||
// We iterate over all handles until we found the right network device.
|
||||
for handle in handles {
|
||||
let simple_network = boot::open_protocol_exclusive::<SimpleNetwork>(handle);
|
||||
if simple_network.is_err() {
|
||||
let Ok(handle) = boot::open_protocol_exclusive::<SimpleNetwork>(handle) else {
|
||||
continue;
|
||||
}
|
||||
let simple_network = simple_network.unwrap();
|
||||
};
|
||||
|
||||
// Check shutdown
|
||||
let res = simple_network.shutdown();
|
||||
assert!(res == Ok(()) || res == Err(Status::NOT_STARTED.into()));
|
||||
|
||||
// Check stop
|
||||
let res = simple_network.stop();
|
||||
assert!(res == Ok(()) || res == Err(Status::NOT_STARTED.into()));
|
||||
|
||||
// Check start
|
||||
simple_network
|
||||
.start()
|
||||
.expect("Failed to start Simple Network");
|
||||
|
||||
// Check initialize
|
||||
simple_network
|
||||
.initialize(0, 0)
|
||||
.expect("Failed to initialize Simple Network");
|
||||
|
||||
// edk2 virtio-net driver does not support statistics, so
|
||||
// allow UNSUPPORTED (same for collect_statistics below).
|
||||
let res = simple_network.reset_statistics();
|
||||
assert!(res == Ok(()) || res == Err(Status::UNSUPPORTED.into()));
|
||||
|
||||
// Reading the interrupt status clears it
|
||||
simple_network.get_interrupt_status().unwrap();
|
||||
|
||||
// Set receive filters
|
||||
simple_network
|
||||
.receive_filters(
|
||||
ReceiveFlags::UNICAST | ReceiveFlags::BROADCAST,
|
||||
ReceiveFlags::empty(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.expect("Failed to set receive filters");
|
||||
|
||||
// Check media
|
||||
if !bool::from(simple_network.mode().media_present_supported)
|
||||
|| !bool::from(simple_network.mode().media_present)
|
||||
// Check media is present
|
||||
if !bool::from(handle.mode().media_present_supported)
|
||||
|| !bool::from(handle.mode().media_present)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let payload = b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
|
||||
\x45\x00\
|
||||
\x00\x21\
|
||||
\x00\x01\
|
||||
\x00\x00\
|
||||
\x10\
|
||||
\x11\
|
||||
\x07\x6a\
|
||||
\xc0\xa8\x11\x0f\
|
||||
\xc0\xa8\x11\x02\
|
||||
\x54\x45\
|
||||
\x54\x44\
|
||||
\x00\x0d\
|
||||
\xa9\xe4\
|
||||
\x04\x01\x02\x03\x04";
|
||||
// Check MAC address
|
||||
let has_mac = handle.mode().current_address.0[0..6] == EXPECTED_MAC
|
||||
&& handle.mode().permanent_address.0[0..6] == EXPECTED_MAC;
|
||||
if !has_mac {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dest_addr = MacAddress([0xffu8; 32]);
|
||||
assert!(
|
||||
!simple_network
|
||||
.get_interrupt_status()
|
||||
.unwrap()
|
||||
.contains(InterruptStatus::TRANSMIT)
|
||||
);
|
||||
maybe_handle.replace(handle);
|
||||
}
|
||||
|
||||
// Send the frame
|
||||
simple_network
|
||||
.transmit(
|
||||
simple_network.mode().media_header_size as usize,
|
||||
payload,
|
||||
None,
|
||||
Some(dest_addr),
|
||||
Some(0x0800),
|
||||
)
|
||||
.expect("Failed to transmit frame");
|
||||
maybe_handle
|
||||
}
|
||||
|
||||
info!("Waiting for the transmit");
|
||||
while !simple_network
|
||||
/// Receives the next IPv4 packet and prints corresponding metadata.
|
||||
///
|
||||
/// Returns the length of the response.
|
||||
fn receive(simple_network: &mut SimpleNetwork, buffer: &mut [u8]) -> uefi::Result<usize> {
|
||||
// Wait for a bit to ensure that the previous packet has been processed.
|
||||
boot::stall(Duration::from_millis(500));
|
||||
|
||||
let mut recv_src_mac = MacAddress([0; 32]);
|
||||
let mut recv_dst_mac = MacAddress([0; 32]);
|
||||
let mut recv_ethernet_protocol = 0;
|
||||
|
||||
let res = simple_network.receive(
|
||||
buffer,
|
||||
None,
|
||||
Some(&mut recv_src_mac),
|
||||
Some(&mut recv_dst_mac),
|
||||
Some(&mut recv_ethernet_protocol),
|
||||
);
|
||||
|
||||
// To simplify debugging when receive an unexpected packet, we print the
|
||||
// necessary info. This is especially useful if an unexpected IPv4 or ARP
|
||||
// packet is received, which can easily happen when fiddling around with
|
||||
// this test.
|
||||
res.inspect(|_| {
|
||||
debug!("Received:");
|
||||
debug!(" src_mac = {:x?}", &recv_src_mac.0[0..6]);
|
||||
debug!(" dst_mac = {:x?}", &recv_dst_mac.0[0..6]);
|
||||
debug!(" ethernet_proto=0x{:x?}", recv_ethernet_protocol);
|
||||
|
||||
// Assert the ethernet frame was sent to the expected interface.
|
||||
{
|
||||
// UEFI reports proper DST MAC
|
||||
assert_eq!(recv_dst_mac.0[0..6], EXPECTED_MAC);
|
||||
|
||||
// Ethernet frame header reports proper DST MAC
|
||||
let recv_frame = smoltcp::wire::EthernetFrame::new_checked(&buffer).unwrap();
|
||||
assert_eq!(
|
||||
recv_frame.dst_addr(),
|
||||
smoltcp::wire::EthernetAddress::from_bytes(&EXPECTED_MAC)
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure that we do not accidentally get an ARP packet, which we
|
||||
// do not expect in this test.
|
||||
assert_eq!(recv_ethernet_protocol, ETHERNET_PROTOCOL_IPV4)
|
||||
})
|
||||
}
|
||||
|
||||
/// This test sends a simple UDP/IP packet to the `EchoService` (created by
|
||||
/// `cargo xtask run`) and receives its response.
|
||||
pub fn test() {
|
||||
// This test currently depends on the PXE test running first.
|
||||
if cfg!(not(feature = "pxe")) {
|
||||
return;
|
||||
}
|
||||
|
||||
info!("Testing the simple network protocol");
|
||||
|
||||
// The handle to our specific network device, as the test requires also a
|
||||
// specific environment. We do not test all possible handles.
|
||||
let mut simple_network = find_network_device().unwrap_or_else(|| panic!(
|
||||
"Failed to find SNP handle for network device with MAC address {:x}:{:x}:{:x}:{:x}:{:x}:{:x}",
|
||||
EXPECTED_MAC[0],
|
||||
EXPECTED_MAC[1],
|
||||
EXPECTED_MAC[2],
|
||||
EXPECTED_MAC[3],
|
||||
EXPECTED_MAC[4],
|
||||
EXPECTED_MAC[5]
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
simple_network.mode().state,
|
||||
NetworkState::STOPPED,
|
||||
"Should be in stopped state"
|
||||
);
|
||||
|
||||
simple_network
|
||||
.start()
|
||||
.expect("Failed to start Simple Network");
|
||||
|
||||
simple_network
|
||||
.initialize(0, 0)
|
||||
.expect("Failed to initialize Simple Network");
|
||||
|
||||
// edk2 virtio-net driver does not support statistics, so
|
||||
// allow UNSUPPORTED (same for collect_statistics below).
|
||||
let res = simple_network.reset_statistics();
|
||||
assert!(res == Ok(()) || res == Err(Status::UNSUPPORTED.into()));
|
||||
|
||||
// Reading the interrupt status clears it
|
||||
simple_network.get_interrupt_status().unwrap();
|
||||
|
||||
// Set receive filters
|
||||
simple_network
|
||||
.receive_filters(
|
||||
ReceiveFlags::UNICAST | ReceiveFlags::BROADCAST,
|
||||
ReceiveFlags::empty(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.expect("Failed to set receive filters");
|
||||
|
||||
// High-level payload to send to destination
|
||||
let payload = [
|
||||
4_u8, /* Number of elements for echo service */
|
||||
1, 2, 3, 4,
|
||||
];
|
||||
let frame = {
|
||||
// IP that was obtained by PXE test running earlier
|
||||
// TODO we should make these tests not depend on each other.
|
||||
let src_ip = smoltcp::wire::Ipv4Address::new(192, 168, 17, 15);
|
||||
let dst_ip = smoltcp::wire::Ipv4Address::new(192, 168, 17, 2);
|
||||
|
||||
let udp_packet_len = UDP_HEADER_LEN + payload.len();
|
||||
let ipv4_packet_len = IPV4_HEADER_LEN + udp_packet_len;
|
||||
let frame_len = ETHERNET_HEADER_LEN + ipv4_packet_len;
|
||||
|
||||
let mut buffer = vec![0u8; frame_len];
|
||||
|
||||
let mut frame = EthernetFrame::new_unchecked(buffer.as_mut_slice());
|
||||
// Ethertype, SRC MAC, and DST MAC will be set by SNP's transmit().
|
||||
|
||||
let ipv4_packet_buffer = &mut frame.payload_mut()[0..ipv4_packet_len];
|
||||
let mut ipv4_packet = Ipv4Packet::new_unchecked(ipv4_packet_buffer);
|
||||
ipv4_packet.set_header_len(IPV4_HEADER_LEN as u8 /* no extensions */);
|
||||
ipv4_packet.set_total_len(ipv4_packet_len as u16);
|
||||
ipv4_packet.set_hop_limit(16);
|
||||
ipv4_packet.set_next_header(smoltcp::wire::IpProtocol::Udp);
|
||||
ipv4_packet.set_dont_frag(true);
|
||||
ipv4_packet.set_ident(0x1337);
|
||||
ipv4_packet.set_version(4);
|
||||
ipv4_packet.set_src_addr(src_ip);
|
||||
ipv4_packet.set_dst_addr(dst_ip);
|
||||
|
||||
let mut udp_packet = UdpPacket::new_unchecked(ipv4_packet.payload_mut());
|
||||
udp_packet.set_len(udp_packet_len as u16);
|
||||
udp_packet.set_src_port(21573);
|
||||
udp_packet.set_dst_port(21572);
|
||||
udp_packet.payload_mut().copy_from_slice(&payload);
|
||||
assert!(udp_packet.check_len().is_ok());
|
||||
|
||||
udp_packet.fill_checksum(&src_ip.into(), &dst_ip.into());
|
||||
// Do this last, as it depends on the other checksum.
|
||||
ipv4_packet.fill_checksum();
|
||||
assert!(ipv4_packet.check_len().is_ok());
|
||||
|
||||
buffer
|
||||
};
|
||||
|
||||
assert!(
|
||||
!simple_network
|
||||
.get_interrupt_status()
|
||||
.unwrap()
|
||||
.contains(InterruptStatus::TRANSMIT)
|
||||
{}
|
||||
);
|
||||
|
||||
// Attempt to receive a frame
|
||||
let mut buffer = [0u8; 1500];
|
||||
// Send the frame
|
||||
simple_network
|
||||
.transmit(
|
||||
simple_network.mode().media_header_size as usize,
|
||||
&frame,
|
||||
None,
|
||||
Some(simple_network.mode().broadcast_address),
|
||||
Some(ETHERNET_PROTOCOL_IPV4),
|
||||
)
|
||||
.expect("Failed to transmit frame");
|
||||
|
||||
info!("Waiting for the reception");
|
||||
if simple_network.receive(&mut buffer, None, None, None, None)
|
||||
== Err(Status::NOT_READY.into())
|
||||
{
|
||||
boot::stall(Duration::from_secs(1));
|
||||
info!("Waiting for the transmit");
|
||||
while !simple_network
|
||||
.get_interrupt_status()
|
||||
.unwrap()
|
||||
.contains(InterruptStatus::TRANSMIT)
|
||||
{}
|
||||
|
||||
simple_network
|
||||
.receive(&mut buffer, None, None, None, None)
|
||||
.unwrap();
|
||||
// Attempt to receive a frame
|
||||
let mut buffer = [0u8; 1500];
|
||||
|
||||
info!("Waiting for the reception");
|
||||
let n = receive(simple_network.deref_mut(), &mut buffer).unwrap();
|
||||
debug!("Reply has {n} bytes");
|
||||
|
||||
// Check payload in UDP packet that was reversed by our EchoService.
|
||||
{
|
||||
let recv_frame = EthernetFrame::new_checked(&buffer).unwrap();
|
||||
let recv_ipv4 = Ipv4Packet::new_checked(recv_frame.payload()).unwrap();
|
||||
let udp_packet = UdpPacket::new_checked(recv_ipv4.payload()).unwrap();
|
||||
assert_eq!(udp_packet.payload(), &[4, 4, 3, 2, 1]);
|
||||
}
|
||||
|
||||
// Get stats
|
||||
let res = simple_network.collect_statistics();
|
||||
match res {
|
||||
Ok(stats) => {
|
||||
info!("Stats: {:?}", stats);
|
||||
|
||||
// One frame should have been transmitted and one received
|
||||
assert_eq!(stats.tx_total_frames().unwrap(), 1);
|
||||
assert_eq!(stats.rx_total_frames().unwrap(), 1);
|
||||
}
|
||||
|
||||
assert_eq!(buffer[42..47], [4, 4, 3, 2, 1]);
|
||||
|
||||
// Get stats
|
||||
let res = simple_network.collect_statistics();
|
||||
match res {
|
||||
Ok(stats) => {
|
||||
info!("Stats: {:?}", stats);
|
||||
|
||||
// One frame should have been transmitted and one received
|
||||
assert_eq!(stats.tx_total_frames().unwrap(), 1);
|
||||
assert_eq!(stats.rx_total_frames().unwrap(), 1);
|
||||
}
|
||||
Err(e) => {
|
||||
if e == Status::UNSUPPORTED.into() {
|
||||
info!("Stats: unsupported.");
|
||||
} else {
|
||||
panic!("{e}");
|
||||
}
|
||||
Err(e) => {
|
||||
if e == Status::UNSUPPORTED.into() {
|
||||
info!("Stats: unsupported.");
|
||||
} else {
|
||||
panic!("{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for OVMF firmware. `stop()` works in CI on x86_64, but not
|
||||
// x86 or aarch64.
|
||||
if simple_network.mode().state == NetworkState::STARTED {
|
||||
simple_network.stop().unwrap();
|
||||
}
|
||||
simple_network.shutdown().unwrap();
|
||||
}
|
||||
|
||||
+2
-1
@@ -498,7 +498,8 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> {
|
||||
"-netdev",
|
||||
"user,id=net0,net=192.168.17.0/24,tftp=uefi-test-runner/tftp/,bootfile=fake-boot-file",
|
||||
"-device",
|
||||
"virtio-net-pci,netdev=net0",
|
||||
// Some integration tests depend on this specific MAC.
|
||||
"virtio-net-pci,netdev=net0,mac=52:54:00:00:00:01",
|
||||
]);
|
||||
Some(net::EchoService::start())
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user