Skip to content

Commit 0e10c55

Browse files
committed
uefi-test-runner: SNP: improve clarity
Originally, the idea was to make this test idempotent from the PXE test, but it turned out that test has surprisingly many sharp edges. A huge amount of time went into debugging certain problems. While this commit doesn't change the fundamentals of the test, it helps to ease future debugging and to form the mental model of what is going on in this test. PS: Interestingly, the interrupt status never shows that we can receive a packet. Probably not implemented by OVMF?
1 parent 1190df7 commit 0e10c55

File tree

3 files changed

+175
-102
lines changed

3 files changed

+175
-102
lines changed

uefi-test-runner/src/proto/network/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ pub fn test() {
55

66
http::test();
77
pxe::test();
8+
// Currently, we are in the unfortunate situation that the SNP test
9+
// depends on the PXE test, as it assigns an IPv4 address to the
10+
// interface via DHCP.
811
snp::test();
912
}
1013

Lines changed: 171 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,143 @@
11
// SPDX-License-Identifier: MIT OR Apache-2.0
22

3+
use core::ops::DerefMut;
34
use core::time::Duration;
4-
55
use uefi::proto::network::MacAddress;
6-
use uefi::proto::network::snp::{InterruptStatus, ReceiveFlags, SimpleNetwork};
6+
use uefi::proto::network::snp::{InterruptStatus, NetworkState, ReceiveFlags, SimpleNetwork};
77
use uefi::{Status, boot};
88

9+
const ETHERNET_PROTOCOL_IPV4: u16 = 0x0800;
10+
/// The MAC address configured for the interface.
11+
const EXPECTED_MAC: [u8; 6] = [0x52, 0x54, 0, 0, 0, 0x1];
12+
13+
/// Receives the next IPv4 packet and prints corresponding metadata.
14+
///
15+
/// Returns the length of the response.
16+
fn receive(simple_network: &mut SimpleNetwork, buffer: &mut [u8]) -> uefi::Result<usize> {
17+
// Wait for a bit to ensure that the previous packet has been processed.
18+
boot::stall(Duration::from_millis(500));
19+
20+
let mut recv_src_mac = MacAddress([0; 32]);
21+
let mut recv_dst_mac = MacAddress([0; 32]);
22+
let mut recv_ethernet_protocol = 0;
23+
24+
let res = simple_network.receive(
25+
buffer,
26+
None,
27+
Some(&mut recv_src_mac),
28+
Some(&mut recv_dst_mac),
29+
Some(&mut recv_ethernet_protocol),
30+
);
31+
32+
// To simplify debugging when receive an unexpected packet, we print the
33+
// necessary info. This is especially useful if an unexpected IPv4 or ARP
34+
// packet is received, which can easily happen when fiddling around with
35+
// this test.
36+
res.inspect(|_| {
37+
debug!("Received:");
38+
debug!(" src_mac = {:x?}", recv_src_mac);
39+
debug!(" dst_mac = {:x?}", recv_dst_mac);
40+
debug!(" ethernet_proto=0x{:x?}", recv_ethernet_protocol);
41+
42+
// Assert the ethernet frame was sent to the expected interface.
43+
{
44+
// UEFI reports proper DST MAC
45+
assert_eq!(recv_dst_mac.0[0..6], EXPECTED_MAC);
46+
}
47+
48+
// Ensure that we do not accidentally get an ARP packet, which we
49+
// do not expect in this test.
50+
assert_eq!(recv_ethernet_protocol, ETHERNET_PROTOCOL_IPV4)
51+
})
52+
}
53+
54+
/// This test sends a simple UDP/IP packet to the `EchoService` (created by
55+
/// `cargo xtask run`) and receives its response.
956
pub fn test() {
57+
// Skip the test if the `pxe` feature is not enabled.
58+
if cfg!(not(feature = "pxe")) {
59+
return;
60+
}
61+
1062
info!("Testing the simple network protocol");
1163

1264
let handles = boot::find_handles::<SimpleNetwork>().unwrap_or_default();
1365

66+
// The handle to our specific network device, as the test requires also a
67+
// specific environment. We do not test all possible handles.
68+
let mut simple_network = None;
69+
70+
// We iterate over all handles until we found the right network device.
1471
for handle in handles {
15-
let simple_network = boot::open_protocol_exclusive::<SimpleNetwork>(handle);
16-
if simple_network.is_err() {
72+
let Ok(handle) = boot::open_protocol_exclusive::<SimpleNetwork>(handle) else {
1773
continue;
18-
}
19-
let simple_network = simple_network.unwrap();
20-
21-
// Check shutdown
22-
let res = simple_network.shutdown();
23-
assert!(res == Ok(()) || res == Err(Status::NOT_STARTED.into()));
24-
25-
// Check stop
26-
let res = simple_network.stop();
27-
assert!(res == Ok(()) || res == Err(Status::NOT_STARTED.into()));
28-
29-
// Check start
30-
simple_network
31-
.start()
32-
.expect("Failed to start Simple Network");
33-
34-
// Check initialize
35-
simple_network
36-
.initialize(0, 0)
37-
.expect("Failed to initialize Simple Network");
38-
39-
// edk2 virtio-net driver does not support statistics, so
40-
// allow UNSUPPORTED (same for collect_statistics below).
41-
let res = simple_network.reset_statistics();
42-
assert!(res == Ok(()) || res == Err(Status::UNSUPPORTED.into()));
43-
44-
// Reading the interrupt status clears it
45-
simple_network.get_interrupt_status().unwrap();
46-
47-
// Set receive filters
48-
simple_network
49-
.receive_filters(
50-
ReceiveFlags::UNICAST | ReceiveFlags::BROADCAST,
51-
ReceiveFlags::empty(),
52-
false,
53-
None,
54-
)
55-
.expect("Failed to set receive filters");
56-
57-
// Check media
58-
if !bool::from(simple_network.mode().media_present_supported)
59-
|| !bool::from(simple_network.mode().media_present)
74+
};
75+
76+
// Check media is present
77+
if !bool::from(handle.mode().media_present_supported)
78+
|| !bool::from(handle.mode().media_present)
6079
{
6180
continue;
6281
}
6382

64-
let payload = b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
83+
let has_mac = handle.mode().current_address.0[0..6] == EXPECTED_MAC
84+
&& handle.mode().permanent_address.0[0..6] == EXPECTED_MAC;
85+
if !has_mac {
86+
continue;
87+
}
88+
89+
simple_network.replace(handle);
90+
}
91+
92+
let mut simple_network = simple_network.unwrap_or_else(|| panic!(
93+
"Failed to find SNP handle for network device with MAC address {:x}:{:x}:{:x}:{:x}:{:x}:{:x}",
94+
EXPECTED_MAC[0],
95+
EXPECTED_MAC[1],
96+
EXPECTED_MAC[2],
97+
EXPECTED_MAC[3],
98+
EXPECTED_MAC[4],
99+
EXPECTED_MAC[5]
100+
));
101+
102+
assert_eq!(
103+
simple_network.mode().state,
104+
NetworkState::STOPPED,
105+
"Should be in stopped state"
106+
);
107+
108+
simple_network
109+
.start()
110+
.expect("Failed to start Simple Network");
111+
112+
simple_network
113+
.initialize(0, 0)
114+
.expect("Failed to initialize Simple Network");
115+
116+
// edk2 virtio-net driver does not support statistics, so
117+
// allow UNSUPPORTED (same for collect_statistics below).
118+
let res = simple_network.reset_statistics();
119+
assert!(res == Ok(()) || res == Err(Status::UNSUPPORTED.into()));
120+
121+
// Reading the interrupt status clears it
122+
simple_network.get_interrupt_status().unwrap();
123+
124+
// Set receive filters
125+
simple_network
126+
.receive_filters(
127+
ReceiveFlags::UNICAST | ReceiveFlags::BROADCAST,
128+
ReceiveFlags::empty(),
129+
false,
130+
None,
131+
)
132+
.expect("Failed to set receive filters");
133+
134+
// EthernetFrame(IPv4Packet(UDPPacket(Payload))).
135+
// The ethernet frame header will be filled by `transmit()`.
136+
// The UDP packet contains the byte sequence `4, 4, 3, 2, 1`.
137+
//
138+
// The packet is sent to the `EchoService` created by
139+
// `cargo xtask run`. It runs on UDP port 21572.
140+
let payload = b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
65141
\x45\x00\
66142
\x00\x21\
67143
\x00\x01\
@@ -77,65 +153,59 @@ pub fn test() {
77153
\xa9\xe4\
78154
\x04\x01\x02\x03\x04";
79155

80-
let dest_addr = MacAddress([0xffu8; 32]);
81-
assert!(
82-
!simple_network
83-
.get_interrupt_status()
84-
.unwrap()
85-
.contains(InterruptStatus::TRANSMIT)
86-
);
87-
88-
// Send the frame
89-
simple_network
90-
.transmit(
91-
simple_network.mode().media_header_size as usize,
92-
payload,
93-
None,
94-
Some(dest_addr),
95-
Some(0x0800),
96-
)
97-
.expect("Failed to transmit frame");
98-
99-
info!("Waiting for the transmit");
100-
while !simple_network
156+
assert!(
157+
!simple_network
101158
.get_interrupt_status()
102159
.unwrap()
103160
.contains(InterruptStatus::TRANSMIT)
104-
{}
105-
106-
// Attempt to receive a frame
107-
let mut buffer = [0u8; 1500];
108-
109-
info!("Waiting for the reception");
110-
if simple_network.receive(&mut buffer, None, None, None, None)
111-
== Err(Status::NOT_READY.into())
112-
{
113-
boot::stall(Duration::from_secs(1));
114-
115-
simple_network
116-
.receive(&mut buffer, None, None, None, None)
117-
.unwrap();
161+
);
162+
163+
// Send the frame
164+
simple_network
165+
.transmit(
166+
simple_network.mode().media_header_size as usize,
167+
payload,
168+
None,
169+
Some(simple_network.mode().broadcast_address),
170+
Some(ETHERNET_PROTOCOL_IPV4),
171+
)
172+
.expect("Failed to transmit frame");
173+
174+
info!("Waiting for the transmit");
175+
while !simple_network
176+
.get_interrupt_status()
177+
.unwrap()
178+
.contains(InterruptStatus::TRANSMIT)
179+
{}
180+
181+
// Attempt to receive a frame
182+
let mut buffer = [0u8; 1500];
183+
184+
info!("Waiting for the reception");
185+
let n = receive(simple_network.deref_mut(), &mut buffer).unwrap();
186+
debug!("Reply has {n} bytes");
187+
188+
// Check payload in UDP packet that was reversed by our EchoService.
189+
assert_eq!(buffer[42..47], [4, 4, 3, 2, 1]);
190+
191+
// Get stats
192+
let res = simple_network.collect_statistics();
193+
match res {
194+
Ok(stats) => {
195+
info!("Stats: {:?}", stats);
196+
197+
// One frame should have been transmitted and one received
198+
assert_eq!(stats.tx_total_frames().unwrap(), 1);
199+
assert_eq!(stats.rx_total_frames().unwrap(), 1);
118200
}
119-
120-
assert_eq!(buffer[42..47], [4, 4, 3, 2, 1]);
121-
122-
// Get stats
123-
let res = simple_network.collect_statistics();
124-
match res {
125-
Ok(stats) => {
126-
info!("Stats: {:?}", stats);
127-
128-
// One frame should have been transmitted and one received
129-
assert_eq!(stats.tx_total_frames().unwrap(), 1);
130-
assert_eq!(stats.rx_total_frames().unwrap(), 1);
131-
}
132-
Err(e) => {
133-
if e == Status::UNSUPPORTED.into() {
134-
info!("Stats: unsupported.");
135-
} else {
136-
panic!("{e}");
137-
}
201+
Err(e) => {
202+
if e == Status::UNSUPPORTED.into() {
203+
info!("Stats: unsupported.");
204+
} else {
205+
panic!("{e}");
138206
}
139207
}
140208
}
209+
210+
simple_network.shutdown().unwrap();
141211
}

uefi/src/proto/network/snp.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ impl SimpleNetwork {
269269
unsafe { &*(ptr::from_ref(&self.0.wait_for_packet).cast::<Event>()) }
270270
}
271271

272-
/// Returns a reference to the Simple Network mode.
272+
/// Returns a reference to the [`NetworkMode`].
273273
#[must_use]
274274
pub fn mode(&self) -> &NetworkMode {
275275
unsafe { &*self.0.mode }

0 commit comments

Comments
 (0)