Skip to content

Commit 310a7d9

Browse files
committed
integration-test: 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 11f0877 commit 310a7d9

File tree

3 files changed

+169
-102
lines changed

3 files changed

+169
-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: 165 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,137 @@
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+
// Ensure that we do not accidentally get an ARP packet, which we
43+
// do not expect in this test.
44+
assert_eq!(recv_ethernet_protocol, ETHERNET_PROTOCOL_IPV4)
45+
})
46+
}
47+
48+
/// This test sends a simple UDP/IP packet to the `EchoService` (created by
49+
/// `cargo xtask run`) and receives its response.
950
pub fn test() {
51+
// Skip the test if the `pxe` feature is not enabled.
52+
if cfg!(not(feature = "pxe")) {
53+
return;
54+
}
55+
1056
info!("Testing the simple network protocol");
1157

1258
let handles = boot::find_handles::<SimpleNetwork>().unwrap_or_default();
1359

60+
// The handle to our specific network device, as the test requires also a
61+
// specific environment. We do not test all possible handles.
62+
let mut simple_network = None;
63+
64+
// We iterate over all handles until we found the right network device.
1465
for handle in handles {
15-
let simple_network = boot::open_protocol_exclusive::<SimpleNetwork>(handle);
16-
if simple_network.is_err() {
66+
let Ok(handle) = boot::open_protocol_exclusive::<SimpleNetwork>(handle) else {
1767
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)
68+
};
69+
70+
// Check media is present
71+
if !bool::from(handle.mode().media_present_supported)
72+
|| !bool::from(handle.mode().media_present)
6073
{
6174
continue;
6275
}
6376

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

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
150+
assert!(
151+
!simple_network
101152
.get_interrupt_status()
102153
.unwrap()
103154
.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();
155+
);
156+
157+
// Send the frame
158+
simple_network
159+
.transmit(
160+
simple_network.mode().media_header_size as usize,
161+
payload,
162+
None,
163+
Some(simple_network.mode().broadcast_address),
164+
Some(ETHERNET_PROTOCOL_IPV4),
165+
)
166+
.expect("Failed to transmit frame");
167+
168+
info!("Waiting for the transmit");
169+
while !simple_network
170+
.get_interrupt_status()
171+
.unwrap()
172+
.contains(InterruptStatus::TRANSMIT)
173+
{}
174+
175+
// Attempt to receive a frame
176+
let mut buffer = [0u8; 1500];
177+
178+
info!("Waiting for the reception");
179+
let n = receive(simple_network.deref_mut(), &mut buffer).unwrap();
180+
debug!("Reply has {n} bytes");
181+
182+
// Check payload in UDP packet that was reversed by our EchoService.
183+
assert_eq!(buffer[42..47], [4, 4, 3, 2, 1]);
184+
185+
// Get stats
186+
let res = simple_network.collect_statistics();
187+
match res {
188+
Ok(stats) => {
189+
info!("Stats: {:?}", stats);
190+
191+
// One frame should have been transmitted and one received
192+
assert_eq!(stats.tx_total_frames().unwrap(), 1);
193+
assert_eq!(stats.rx_total_frames().unwrap(), 1);
118194
}
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-
}
195+
Err(e) => {
196+
if e == Status::UNSUPPORTED.into() {
197+
info!("Stats: unsupported.");
198+
} else {
199+
panic!("{e}");
138200
}
139201
}
140202
}
203+
204+
simple_network.shutdown().unwrap();
141205
}

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)