blob: 15af8d3e18c92fcbde143c618f8582a5c6d89fb8 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001// SPDX-License-Identifier: GPL-2.0+
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +02002/*
3 * efi_selftest_snp
4 *
5 * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
6 *
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +02007 * This unit test covers the Simple Network Protocol as well as
8 * the CopyMem and SetMem boottime services.
9 *
10 * A DHCP discover message is sent. The test is successful if a
11 * DHCP reply is received.
12 *
13 * TODO: Once ConnectController and DisconnectController are implemented
14 * we should connect our code as controller.
15 */
16
17#include <efi_selftest.h>
Simon Glass90526e92020-05-10 11:39:56 -060018#include <net.h>
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +020019
20/*
21 * MAC address for broadcasts
22 */
23static const u8 BROADCAST_MAC[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
24
25struct dhcp_hdr {
26 u8 op;
27#define BOOTREQUEST 1
28#define BOOTREPLY 2
29 u8 htype;
30# define HWT_ETHER 1
31 u8 hlen;
32# define HWL_ETHER 6
33 u8 hops;
34 u32 xid;
35 u16 secs;
36 u16 flags;
37#define DHCP_FLAGS_UNICAST 0x0000
38#define DHCP_FLAGS_BROADCAST 0x0080
39 u32 ciaddr;
40 u32 yiaddr;
41 u32 siaddr;
42 u32 giaddr;
43 u8 chaddr[16];
44 u8 sname[64];
45 u8 file[128];
46};
47
48/*
49 * Message type option.
50 */
51#define DHCP_MESSAGE_TYPE 0x35
52#define DHCPDISCOVER 1
53#define DHCPOFFER 2
54#define DHCPREQUEST 3
55#define DHCPDECLINE 4
56#define DHCPACK 5
57#define DHCPNAK 6
58#define DHCPRELEASE 7
59
60struct dhcp {
61 struct ethernet_hdr eth_hdr;
62 struct ip_udp_hdr ip_udp;
63 struct dhcp_hdr dhcp_hdr;
64 u8 opt[128];
65} __packed;
66
67static struct efi_boot_services *boottime;
68static struct efi_simple_network *net;
69static struct efi_event *timer;
Heinrich Schuchardtdec88e42019-04-20 07:39:11 +020070static const efi_guid_t efi_net_guid = EFI_SIMPLE_NETWORK_PROTOCOL_GUID;
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +020071/* IP packet ID */
72static unsigned int net_ip_id;
73
74/*
75 * Compute the checksum of the IP header. We cover even values of length only.
76 * We cannot use net/checksum.c due to different CFLAGS values.
77 *
78 * @buf: IP header
79 * @len: length of header in bytes
Heinrich Schuchardt3dd719d2022-01-20 19:48:20 +010080 * Return: checksum
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +020081 */
82static unsigned int efi_ip_checksum(const void *buf, size_t len)
83{
84 size_t i;
85 u32 sum = 0;
86 const u16 *pos = buf;
87
88 for (i = 0; i < len; i += 2)
89 sum += *pos++;
90
91 sum = (sum >> 16) + (sum & 0xffff);
92 sum += sum >> 16;
93 sum = ~sum & 0xffff;
94
95 return sum;
96}
97
98/*
99 * Transmit a DHCPDISCOVER message.
100 */
101static efi_status_t send_dhcp_discover(void)
102{
103 efi_status_t ret;
104 struct dhcp p = {};
105
106 /*
Heinrich Schuchardt0fdb9e32018-10-20 22:01:08 +0200107 * Fill Ethernet header
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200108 */
109 boottime->copy_mem(p.eth_hdr.et_dest, (void *)BROADCAST_MAC, ARP_HLEN);
110 boottime->copy_mem(p.eth_hdr.et_src, &net->mode->current_address,
111 ARP_HLEN);
112 p.eth_hdr.et_protlen = htons(PROT_IP);
113 /*
114 * Fill IP header
115 */
116 p.ip_udp.ip_hl_v = 0x45;
117 p.ip_udp.ip_len = htons(sizeof(struct dhcp) -
118 sizeof(struct ethernet_hdr));
119 p.ip_udp.ip_id = htons(++net_ip_id);
120 p.ip_udp.ip_off = htons(IP_FLAGS_DFRAG);
121 p.ip_udp.ip_ttl = 0xff; /* time to live */
122 p.ip_udp.ip_p = IPPROTO_UDP;
123 boottime->set_mem(&p.ip_udp.ip_dst, 4, 0xff);
124 p.ip_udp.ip_sum = efi_ip_checksum(&p.ip_udp, IP_HDR_SIZE);
125
126 /*
127 * Fill UDP header
128 */
129 p.ip_udp.udp_src = htons(68);
130 p.ip_udp.udp_dst = htons(67);
131 p.ip_udp.udp_len = htons(sizeof(struct dhcp) -
132 sizeof(struct ethernet_hdr) -
133 sizeof(struct ip_hdr));
134 /*
135 * Fill DHCP header
136 */
137 p.dhcp_hdr.op = BOOTREQUEST;
138 p.dhcp_hdr.htype = HWT_ETHER;
139 p.dhcp_hdr.hlen = HWL_ETHER;
140 p.dhcp_hdr.flags = htons(DHCP_FLAGS_UNICAST);
141 boottime->copy_mem(&p.dhcp_hdr.chaddr,
142 &net->mode->current_address, ARP_HLEN);
143 /*
144 * Fill options
145 */
146 p.opt[0] = 0x63; /* DHCP magic cookie */
147 p.opt[1] = 0x82;
148 p.opt[2] = 0x53;
149 p.opt[3] = 0x63;
150 p.opt[4] = DHCP_MESSAGE_TYPE;
151 p.opt[5] = 0x01; /* length */
152 p.opt[6] = DHCPDISCOVER;
153 p.opt[7] = 0x39; /* maximum message size */
154 p.opt[8] = 0x02; /* length */
155 p.opt[9] = 0x02; /* 576 bytes */
156 p.opt[10] = 0x40;
157 p.opt[11] = 0xff; /* end of options */
158
159 /*
160 * Transmit DHCPDISCOVER message.
161 */
162 ret = net->transmit(net, 0, sizeof(struct dhcp), &p, NULL, NULL, 0);
163 if (ret != EFI_SUCCESS)
164 efi_st_error("Sending a DHCP request failed\n");
165 else
166 efi_st_printf("DHCP Discover\n");
167 return ret;
168}
169
170/*
171 * Setup unit test.
172 *
173 * Create a 1 s periodic timer.
174 * Start the network driver.
175 *
176 * @handle: handle of the loaded image
177 * @systable: system table
Heinrich Schuchardt3dd719d2022-01-20 19:48:20 +0100178 * Return: EFI_ST_SUCCESS for success
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200179 */
180static int setup(const efi_handle_t handle,
181 const struct efi_system_table *systable)
182{
183 efi_status_t ret;
184
185 boottime = systable->boottime;
186
187 /*
188 * Create a timer event.
189 */
190 ret = boottime->create_event(EVT_TIMER, TPL_CALLBACK, NULL, NULL,
191 &timer);
192 if (ret != EFI_SUCCESS) {
193 efi_st_error("Failed to create event\n");
194 return EFI_ST_FAILURE;
195 }
196 /*
197 * Set timer period to 1s.
198 */
199 ret = boottime->set_timer(timer, EFI_TIMER_PERIODIC, 10000000);
200 if (ret != EFI_SUCCESS) {
Heinrich Schuchardtfdd04562017-10-08 06:57:28 +0200201 efi_st_error("Failed to set timer\n");
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200202 return EFI_ST_FAILURE;
203 }
204 /*
205 * Find an interface implementing the SNP protocol.
206 */
207 ret = boottime->locate_protocol(&efi_net_guid, NULL, (void **)&net);
208 if (ret != EFI_SUCCESS) {
Heinrich Schuchardtfdd04562017-10-08 06:57:28 +0200209 net = NULL;
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200210 efi_st_error("Failed to locate simple network protocol\n");
211 return EFI_ST_FAILURE;
212 }
213 /*
214 * Check hardware address size.
215 */
216 if (!net->mode) {
217 efi_st_error("Mode not provided\n");
218 return EFI_ST_FAILURE;
219 }
220 if (net->mode->hwaddr_size != ARP_HLEN) {
221 efi_st_error("HwAddressSize = %u, expected %u\n",
222 net->mode->hwaddr_size, ARP_HLEN);
223 return EFI_ST_FAILURE;
224 }
225 /*
226 * Check that WaitForPacket event exists.
227 */
228 if (!net->wait_for_packet) {
229 efi_st_error("WaitForPacket event missing\n");
230 return EFI_ST_FAILURE;
231 }
Heinrich Schuchardt72a8f162019-09-01 15:24:47 +0200232 if (net->mode->state == EFI_NETWORK_INITIALIZED) {
233 /*
234 * Shut down network adapter.
235 */
236 ret = net->shutdown(net);
237 if (ret != EFI_SUCCESS) {
238 efi_st_error("Failed to shut down network adapter\n");
239 return EFI_ST_FAILURE;
240 }
241 }
242 if (net->mode->state == EFI_NETWORK_STARTED) {
243 /*
244 * Stop network adapter.
245 */
246 ret = net->stop(net);
247 if (ret != EFI_SUCCESS) {
248 efi_st_error("Failed to stop network adapter\n");
249 return EFI_ST_FAILURE;
250 }
251 }
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200252 /*
Heinrich Schuchardt0fdb9e32018-10-20 22:01:08 +0200253 * Start network adapter.
254 */
255 ret = net->start(net);
256 if (ret != EFI_SUCCESS && ret != EFI_ALREADY_STARTED) {
257 efi_st_error("Failed to start network adapter\n");
258 return EFI_ST_FAILURE;
259 }
Heinrich Schuchardt72a8f162019-09-01 15:24:47 +0200260 if (net->mode->state != EFI_NETWORK_STARTED) {
261 efi_st_error("Failed to start network adapter\n");
262 return EFI_ST_FAILURE;
263 }
Heinrich Schuchardt0fdb9e32018-10-20 22:01:08 +0200264 /*
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200265 * Initialize network adapter.
266 */
267 ret = net->initialize(net, 0, 0);
268 if (ret != EFI_SUCCESS) {
269 efi_st_error("Failed to initialize network adapter\n");
270 return EFI_ST_FAILURE;
271 }
Heinrich Schuchardt72a8f162019-09-01 15:24:47 +0200272 if (net->mode->state != EFI_NETWORK_INITIALIZED) {
273 efi_st_error("Failed to initialize network adapter\n");
274 return EFI_ST_FAILURE;
275 }
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200276 return EFI_ST_SUCCESS;
277}
278
279/*
280 * Execute unit test.
281 *
282 * A DHCP discover message is sent. The test is successful if a
283 * DHCP reply is received within 10 seconds.
284 *
Heinrich Schuchardt3dd719d2022-01-20 19:48:20 +0100285 * Return: EFI_ST_SUCCESS for success
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200286 */
287static int execute(void)
288{
289 efi_status_t ret;
290 struct efi_event *events[2];
Heinrich Schuchardtf5a2a932017-11-06 21:17:48 +0100291 efi_uintn_t index;
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200292 union {
293 struct dhcp p;
294 u8 b[PKTSIZE];
295 } buffer;
296 struct efi_mac_address srcaddr;
297 struct efi_mac_address destaddr;
298 size_t buffer_size;
299 u8 *addr;
Heinrich Schuchardt0c7adf42019-08-31 10:00:58 +0200300
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200301 /*
302 * The timeout is to occur after 10 s.
303 */
304 unsigned int timeout = 10;
305
Heinrich Schuchardtfdd04562017-10-08 06:57:28 +0200306 /* Setup may have failed */
307 if (!net || !timer) {
308 efi_st_error("Cannot execute test after setup failure\n");
309 return EFI_ST_FAILURE;
310 }
311
Masami Hiramatsu39a37ad2021-09-16 17:53:27 +0900312 /* Check media connected */
313 ret = net->get_status(net, NULL, NULL);
314 if (ret != EFI_SUCCESS) {
315 efi_st_error("Failed to get status");
316 return EFI_ST_FAILURE;
317 }
318 if (net->mode && net->mode->media_present_supported &&
319 !net->mode->media_present) {
320 efi_st_error("Network media is not connected");
321 return EFI_ST_FAILURE;
322 }
323
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200324 /*
325 * Send DHCP discover message
326 */
327 ret = send_dhcp_discover();
328 if (ret != EFI_SUCCESS)
329 return EFI_ST_FAILURE;
330
331 /*
332 * If we would call WaitForEvent only with the WaitForPacket event,
333 * our code would block until a packet is received which might never
334 * occur. By calling WaitFor event with both a timer event and the
335 * WaitForPacket event we can escape this blocking situation.
336 *
337 * If the timer event occurs before we have received a DHCP reply
338 * a further DHCP discover message is sent.
339 */
340 events[0] = timer;
341 events[1] = net->wait_for_packet;
342 for (;;) {
343 /*
344 * Wait for packet to be received or timer event.
345 */
346 boottime->wait_for_event(2, events, &index);
347 if (index == 0) {
348 /*
349 * The timer event occurred. Check for timeout.
350 */
351 --timeout;
352 if (!timeout) {
353 efi_st_error("Timeout occurred\n");
354 return EFI_ST_FAILURE;
355 }
356 /*
357 * Send further DHCP discover message
358 */
359 ret = send_dhcp_discover();
360 if (ret != EFI_SUCCESS)
361 return EFI_ST_FAILURE;
362 continue;
363 }
364 /*
Masami Hiramatsu28fc87e2021-09-16 17:53:44 +0900365 * Receive packets until buffer is empty
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200366 */
Masami Hiramatsu28fc87e2021-09-16 17:53:44 +0900367 for (;;) {
368 buffer_size = sizeof(buffer);
369 ret = net->receive(net, NULL, &buffer_size, &buffer,
370 &srcaddr, &destaddr, NULL);
371 if (ret == EFI_NOT_READY) {
372 /* The received buffer is empty. */
373 break;
374 }
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200375
Masami Hiramatsu28fc87e2021-09-16 17:53:44 +0900376 if (ret != EFI_SUCCESS) {
377 efi_st_error("Failed to receive packet");
378 return EFI_ST_FAILURE;
379 }
380 /*
381 * Check the packet is meant for this system.
382 * Unfortunately QEMU ignores the broadcast flag.
383 * So we have to check for broadcasts too.
384 */
385 if (memcmp(&destaddr, &net->mode->current_address, ARP_HLEN) &&
386 memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
387 continue;
388 /*
389 * Check this is a DHCP reply
390 */
391 if (buffer.p.eth_hdr.et_protlen != ntohs(PROT_IP) ||
392 buffer.p.ip_udp.ip_hl_v != 0x45 ||
393 buffer.p.ip_udp.ip_p != IPPROTO_UDP ||
394 buffer.p.ip_udp.udp_src != ntohs(67) ||
395 buffer.p.ip_udp.udp_dst != ntohs(68) ||
396 buffer.p.dhcp_hdr.op != BOOTREPLY)
397 continue;
398 /*
399 * We successfully received a DHCP reply.
400 */
401 goto received;
402 }
403 }
404received:
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200405 /*
406 * Write a log message.
407 */
408 addr = (u8 *)&buffer.p.ip_udp.ip_src;
409 efi_st_printf("DHCP reply received from %u.%u.%u.%u (%pm) ",
410 addr[0], addr[1], addr[2], addr[3], &srcaddr);
Heinrich Schuchardt8101dd32019-05-04 19:48:38 +0200411 if (!memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200412 efi_st_printf("as broadcast message.\n");
413 else
414 efi_st_printf("as unicast message.\n");
415
416 return EFI_ST_SUCCESS;
417}
418
419/*
420 * Tear down unit test.
421 *
422 * Close the timer event created in setup.
423 * Shut down the network adapter.
424 *
Heinrich Schuchardt3dd719d2022-01-20 19:48:20 +0100425 * Return: EFI_ST_SUCCESS for success
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200426 */
427static int teardown(void)
428{
429 efi_status_t ret;
430 int exit_status = EFI_ST_SUCCESS;
431
432 if (timer) {
433 /*
434 * Stop timer.
435 */
436 ret = boottime->set_timer(timer, EFI_TIMER_STOP, 0);
437 if (ret != EFI_SUCCESS) {
438 efi_st_error("Failed to stop timer");
439 exit_status = EFI_ST_FAILURE;
440 }
441 /*
442 * Close timer event.
443 */
444 ret = boottime->close_event(timer);
445 if (ret != EFI_SUCCESS) {
446 efi_st_error("Failed to close event");
447 exit_status = EFI_ST_FAILURE;
448 }
449 }
450 if (net) {
451 /*
Heinrich Schuchardt72a8f162019-09-01 15:24:47 +0200452 * Shut down network adapter.
453 */
454 ret = net->shutdown(net);
455 if (ret != EFI_SUCCESS) {
456 efi_st_error("Failed to shut down network adapter\n");
457 exit_status = EFI_ST_FAILURE;
458 }
459 if (net->mode->state != EFI_NETWORK_STARTED) {
460 efi_st_error("Failed to shutdown network adapter\n");
461 return EFI_ST_FAILURE;
462 }
463 /*
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200464 * Stop network adapter.
465 */
466 ret = net->stop(net);
467 if (ret != EFI_SUCCESS) {
468 efi_st_error("Failed to stop network adapter\n");
469 exit_status = EFI_ST_FAILURE;
470 }
Heinrich Schuchardt72a8f162019-09-01 15:24:47 +0200471 if (net->mode->state != EFI_NETWORK_STOPPED) {
472 efi_st_error("Failed to stop network adapter\n");
473 return EFI_ST_FAILURE;
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200474 }
475 }
476
477 return exit_status;
478}
479
480EFI_UNIT_TEST(snp) = {
481 .name = "simple network protocol",
482 .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT,
483 .setup = setup,
484 .execute = execute,
485 .teardown = teardown,
Heinrich Schuchardt8a426412019-01-05 23:50:41 +0100486#ifdef CONFIG_SANDBOX
487 /*
488 * Running this test on the sandbox requires setting environment
489 * variable ethact to a network interface connected to a DHCP server and
490 * ethrotate to 'no'.
491 */
492 .on_request = true,
493#endif
Heinrich Schuchardt5ca23ed2017-10-05 16:36:07 +0200494};