blob: b00c76c2f172e427a1afa8d486a35e22d09b5428 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001// SPDX-License-Identifier: GPL-2.0+
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +02002/*
3 * efi_selftest_snp
4 *
5 * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
6 *
Heinrich Schuchardt2a6daf72017-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 Glass274e0b02020-05-10 11:39:56 -060018#include <net.h>
Heinrich Schuchardt2a6daf72017-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 Schuchardt2a6daf72017-10-05 16:36:07 +020070/* IP packet ID */
71static unsigned int net_ip_id;
72
73/*
74 * Compute the checksum of the IP header. We cover even values of length only.
75 * We cannot use net/checksum.c due to different CFLAGS values.
76 *
77 * @buf: IP header
78 * @len: length of header in bytes
Heinrich Schuchardtfbe90212022-01-20 19:48:20 +010079 * Return: checksum
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +020080 */
81static unsigned int efi_ip_checksum(const void *buf, size_t len)
82{
83 size_t i;
84 u32 sum = 0;
85 const u16 *pos = buf;
86
87 for (i = 0; i < len; i += 2)
88 sum += *pos++;
89
90 sum = (sum >> 16) + (sum & 0xffff);
91 sum += sum >> 16;
92 sum = ~sum & 0xffff;
93
94 return sum;
95}
96
97/*
98 * Transmit a DHCPDISCOVER message.
99 */
100static efi_status_t send_dhcp_discover(void)
101{
102 efi_status_t ret;
103 struct dhcp p = {};
104
105 /*
Heinrich Schuchardt39f757d2018-10-20 22:01:08 +0200106 * Fill Ethernet header
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200107 */
108 boottime->copy_mem(p.eth_hdr.et_dest, (void *)BROADCAST_MAC, ARP_HLEN);
109 boottime->copy_mem(p.eth_hdr.et_src, &net->mode->current_address,
110 ARP_HLEN);
111 p.eth_hdr.et_protlen = htons(PROT_IP);
112 /*
113 * Fill IP header
114 */
115 p.ip_udp.ip_hl_v = 0x45;
116 p.ip_udp.ip_len = htons(sizeof(struct dhcp) -
117 sizeof(struct ethernet_hdr));
118 p.ip_udp.ip_id = htons(++net_ip_id);
119 p.ip_udp.ip_off = htons(IP_FLAGS_DFRAG);
120 p.ip_udp.ip_ttl = 0xff; /* time to live */
121 p.ip_udp.ip_p = IPPROTO_UDP;
122 boottime->set_mem(&p.ip_udp.ip_dst, 4, 0xff);
123 p.ip_udp.ip_sum = efi_ip_checksum(&p.ip_udp, IP_HDR_SIZE);
124
125 /*
126 * Fill UDP header
127 */
128 p.ip_udp.udp_src = htons(68);
129 p.ip_udp.udp_dst = htons(67);
130 p.ip_udp.udp_len = htons(sizeof(struct dhcp) -
131 sizeof(struct ethernet_hdr) -
132 sizeof(struct ip_hdr));
133 /*
134 * Fill DHCP header
135 */
136 p.dhcp_hdr.op = BOOTREQUEST;
137 p.dhcp_hdr.htype = HWT_ETHER;
138 p.dhcp_hdr.hlen = HWL_ETHER;
139 p.dhcp_hdr.flags = htons(DHCP_FLAGS_UNICAST);
140 boottime->copy_mem(&p.dhcp_hdr.chaddr,
141 &net->mode->current_address, ARP_HLEN);
142 /*
143 * Fill options
144 */
145 p.opt[0] = 0x63; /* DHCP magic cookie */
146 p.opt[1] = 0x82;
147 p.opt[2] = 0x53;
148 p.opt[3] = 0x63;
149 p.opt[4] = DHCP_MESSAGE_TYPE;
150 p.opt[5] = 0x01; /* length */
151 p.opt[6] = DHCPDISCOVER;
152 p.opt[7] = 0x39; /* maximum message size */
153 p.opt[8] = 0x02; /* length */
154 p.opt[9] = 0x02; /* 576 bytes */
155 p.opt[10] = 0x40;
156 p.opt[11] = 0xff; /* end of options */
157
158 /*
159 * Transmit DHCPDISCOVER message.
160 */
161 ret = net->transmit(net, 0, sizeof(struct dhcp), &p, NULL, NULL, 0);
162 if (ret != EFI_SUCCESS)
163 efi_st_error("Sending a DHCP request failed\n");
164 else
165 efi_st_printf("DHCP Discover\n");
166 return ret;
167}
168
169/*
170 * Setup unit test.
171 *
172 * Create a 1 s periodic timer.
173 * Start the network driver.
174 *
175 * @handle: handle of the loaded image
176 * @systable: system table
Heinrich Schuchardtfbe90212022-01-20 19:48:20 +0100177 * Return: EFI_ST_SUCCESS for success
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200178 */
179static int setup(const efi_handle_t handle,
180 const struct efi_system_table *systable)
181{
182 efi_status_t ret;
183
184 boottime = systable->boottime;
185
186 /*
187 * Create a timer event.
188 */
189 ret = boottime->create_event(EVT_TIMER, TPL_CALLBACK, NULL, NULL,
190 &timer);
191 if (ret != EFI_SUCCESS) {
192 efi_st_error("Failed to create event\n");
193 return EFI_ST_FAILURE;
194 }
195 /*
196 * Set timer period to 1s.
197 */
198 ret = boottime->set_timer(timer, EFI_TIMER_PERIODIC, 10000000);
199 if (ret != EFI_SUCCESS) {
Heinrich Schuchardt45054712017-10-08 06:57:28 +0200200 efi_st_error("Failed to set timer\n");
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200201 return EFI_ST_FAILURE;
202 }
203 /*
204 * Find an interface implementing the SNP protocol.
205 */
206 ret = boottime->locate_protocol(&efi_net_guid, NULL, (void **)&net);
207 if (ret != EFI_SUCCESS) {
Heinrich Schuchardt45054712017-10-08 06:57:28 +0200208 net = NULL;
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200209 efi_st_error("Failed to locate simple network protocol\n");
210 return EFI_ST_FAILURE;
211 }
212 /*
213 * Check hardware address size.
214 */
215 if (!net->mode) {
216 efi_st_error("Mode not provided\n");
217 return EFI_ST_FAILURE;
218 }
219 if (net->mode->hwaddr_size != ARP_HLEN) {
220 efi_st_error("HwAddressSize = %u, expected %u\n",
221 net->mode->hwaddr_size, ARP_HLEN);
222 return EFI_ST_FAILURE;
223 }
224 /*
225 * Check that WaitForPacket event exists.
226 */
227 if (!net->wait_for_packet) {
228 efi_st_error("WaitForPacket event missing\n");
229 return EFI_ST_FAILURE;
230 }
Heinrich Schuchardt6a6e7d82019-09-01 15:24:47 +0200231 if (net->mode->state == EFI_NETWORK_INITIALIZED) {
232 /*
233 * Shut down network adapter.
234 */
235 ret = net->shutdown(net);
236 if (ret != EFI_SUCCESS) {
237 efi_st_error("Failed to shut down network adapter\n");
238 return EFI_ST_FAILURE;
239 }
240 }
241 if (net->mode->state == EFI_NETWORK_STARTED) {
242 /*
243 * Stop network adapter.
244 */
245 ret = net->stop(net);
246 if (ret != EFI_SUCCESS) {
247 efi_st_error("Failed to stop network adapter\n");
248 return EFI_ST_FAILURE;
249 }
250 }
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200251 /*
Heinrich Schuchardt39f757d2018-10-20 22:01:08 +0200252 * Start network adapter.
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200253 */
Heinrich Schuchardt39f757d2018-10-20 22:01:08 +0200254 ret = net->start(net);
255 if (ret != EFI_SUCCESS && ret != EFI_ALREADY_STARTED) {
256 efi_st_error("Failed to start network adapter\n");
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200257 return EFI_ST_FAILURE;
258 }
Heinrich Schuchardt6a6e7d82019-09-01 15:24:47 +0200259 if (net->mode->state != EFI_NETWORK_STARTED) {
260 efi_st_error("Failed to start network adapter\n");
261 return EFI_ST_FAILURE;
262 }
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200263 /*
Heinrich Schuchardt39f757d2018-10-20 22:01:08 +0200264 * Initialize network adapter.
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200265 */
Heinrich Schuchardt39f757d2018-10-20 22:01:08 +0200266 ret = net->initialize(net, 0, 0);
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200267 if (ret != EFI_SUCCESS) {
Heinrich Schuchardt39f757d2018-10-20 22:01:08 +0200268 efi_st_error("Failed to initialize network adapter\n");
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200269 return EFI_ST_FAILURE;
270 }
Heinrich Schuchardt6a6e7d82019-09-01 15:24:47 +0200271 if (net->mode->state != EFI_NETWORK_INITIALIZED) {
272 efi_st_error("Failed to initialize network adapter\n");
273 return EFI_ST_FAILURE;
274 }
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200275 return EFI_ST_SUCCESS;
276}
277
278/*
279 * Execute unit test.
280 *
281 * A DHCP discover message is sent. The test is successful if a
282 * DHCP reply is received within 10 seconds.
283 *
Heinrich Schuchardtfbe90212022-01-20 19:48:20 +0100284 * Return: EFI_ST_SUCCESS for success
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200285 */
286static int execute(void)
287{
288 efi_status_t ret;
289 struct efi_event *events[2];
Heinrich Schuchardt798a4412017-11-06 21:17:48 +0100290 efi_uintn_t index;
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200291 union {
292 struct dhcp p;
293 u8 b[PKTSIZE];
294 } buffer;
295 struct efi_mac_address srcaddr;
296 struct efi_mac_address destaddr;
297 size_t buffer_size;
298 u8 *addr;
Heinrich Schuchardt89407452019-08-31 10:00:58 +0200299
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200300 /*
301 * The timeout is to occur after 10 s.
302 */
303 unsigned int timeout = 10;
304
Heinrich Schuchardt45054712017-10-08 06:57:28 +0200305 /* Setup may have failed */
306 if (!net || !timer) {
307 efi_st_error("Cannot execute test after setup failure\n");
308 return EFI_ST_FAILURE;
309 }
310
Masami Hiramatsu19441032021-09-16 17:53:27 +0900311 /* Check media connected */
312 ret = net->get_status(net, NULL, NULL);
313 if (ret != EFI_SUCCESS) {
314 efi_st_error("Failed to get status");
315 return EFI_ST_FAILURE;
316 }
317 if (net->mode && net->mode->media_present_supported &&
318 !net->mode->media_present) {
319 efi_st_error("Network media is not connected");
320 return EFI_ST_FAILURE;
321 }
322
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200323 /*
324 * Send DHCP discover message
325 */
326 ret = send_dhcp_discover();
327 if (ret != EFI_SUCCESS)
328 return EFI_ST_FAILURE;
329
330 /*
331 * If we would call WaitForEvent only with the WaitForPacket event,
332 * our code would block until a packet is received which might never
333 * occur. By calling WaitFor event with both a timer event and the
334 * WaitForPacket event we can escape this blocking situation.
335 *
336 * If the timer event occurs before we have received a DHCP reply
337 * a further DHCP discover message is sent.
338 */
339 events[0] = timer;
340 events[1] = net->wait_for_packet;
341 for (;;) {
342 /*
343 * Wait for packet to be received or timer event.
344 */
345 boottime->wait_for_event(2, events, &index);
346 if (index == 0) {
347 /*
348 * The timer event occurred. Check for timeout.
349 */
350 --timeout;
351 if (!timeout) {
352 efi_st_error("Timeout occurred\n");
353 return EFI_ST_FAILURE;
354 }
355 /*
356 * Send further DHCP discover message
357 */
358 ret = send_dhcp_discover();
359 if (ret != EFI_SUCCESS)
360 return EFI_ST_FAILURE;
361 continue;
362 }
363 /*
Masami Hiramatsu784a10f2021-09-16 17:53:44 +0900364 * Receive packets until buffer is empty
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200365 */
Masami Hiramatsu784a10f2021-09-16 17:53:44 +0900366 for (;;) {
367 buffer_size = sizeof(buffer);
368 ret = net->receive(net, NULL, &buffer_size, &buffer,
369 &srcaddr, &destaddr, NULL);
370 if (ret == EFI_NOT_READY) {
371 /* The received buffer is empty. */
372 break;
373 }
374
375 if (ret != EFI_SUCCESS) {
376 efi_st_error("Failed to receive packet");
377 return EFI_ST_FAILURE;
378 }
379 /*
380 * Check the packet is meant for this system.
381 * Unfortunately QEMU ignores the broadcast flag.
382 * So we have to check for broadcasts too.
383 */
384 if (memcmp(&destaddr, &net->mode->current_address, ARP_HLEN) &&
385 memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
386 continue;
387 /*
388 * Check this is a DHCP reply
389 */
390 if (buffer.p.eth_hdr.et_protlen != ntohs(PROT_IP) ||
391 buffer.p.ip_udp.ip_hl_v != 0x45 ||
392 buffer.p.ip_udp.ip_p != IPPROTO_UDP ||
393 buffer.p.ip_udp.udp_src != ntohs(67) ||
394 buffer.p.ip_udp.udp_dst != ntohs(68) ||
395 buffer.p.dhcp_hdr.op != BOOTREPLY)
396 continue;
397 /*
398 * We successfully received a DHCP reply.
399 */
400 goto received;
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200401 }
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200402 }
Masami Hiramatsu784a10f2021-09-16 17:53:44 +0900403received:
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200404 /*
405 * Write a log message.
406 */
407 addr = (u8 *)&buffer.p.ip_udp.ip_src;
408 efi_st_printf("DHCP reply received from %u.%u.%u.%u (%pm) ",
409 addr[0], addr[1], addr[2], addr[3], &srcaddr);
Heinrich Schuchardt5f946662019-05-04 19:48:38 +0200410 if (!memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200411 efi_st_printf("as broadcast message.\n");
412 else
413 efi_st_printf("as unicast message.\n");
414
415 return EFI_ST_SUCCESS;
416}
417
418/*
419 * Tear down unit test.
420 *
421 * Close the timer event created in setup.
422 * Shut down the network adapter.
423 *
Heinrich Schuchardtfbe90212022-01-20 19:48:20 +0100424 * Return: EFI_ST_SUCCESS for success
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200425 */
426static int teardown(void)
427{
428 efi_status_t ret;
429 int exit_status = EFI_ST_SUCCESS;
430
431 if (timer) {
432 /*
433 * Stop timer.
434 */
435 ret = boottime->set_timer(timer, EFI_TIMER_STOP, 0);
436 if (ret != EFI_SUCCESS) {
437 efi_st_error("Failed to stop timer");
438 exit_status = EFI_ST_FAILURE;
439 }
440 /*
441 * Close timer event.
442 */
443 ret = boottime->close_event(timer);
444 if (ret != EFI_SUCCESS) {
445 efi_st_error("Failed to close event");
446 exit_status = EFI_ST_FAILURE;
447 }
448 }
449 if (net) {
450 /*
Heinrich Schuchardt6a6e7d82019-09-01 15:24:47 +0200451 * Shut down network adapter.
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200452 */
Heinrich Schuchardt6a6e7d82019-09-01 15:24:47 +0200453 ret = net->shutdown(net);
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200454 if (ret != EFI_SUCCESS) {
Heinrich Schuchardt6a6e7d82019-09-01 15:24:47 +0200455 efi_st_error("Failed to shut down network adapter\n");
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200456 exit_status = EFI_ST_FAILURE;
457 }
Heinrich Schuchardt6a6e7d82019-09-01 15:24:47 +0200458 if (net->mode->state != EFI_NETWORK_STARTED) {
459 efi_st_error("Failed to shutdown network adapter\n");
460 return EFI_ST_FAILURE;
461 }
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200462 /*
Heinrich Schuchardt6a6e7d82019-09-01 15:24:47 +0200463 * Stop network adapter.
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200464 */
Heinrich Schuchardt6a6e7d82019-09-01 15:24:47 +0200465 ret = net->stop(net);
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200466 if (ret != EFI_SUCCESS) {
Heinrich Schuchardt6a6e7d82019-09-01 15:24:47 +0200467 efi_st_error("Failed to stop network adapter\n");
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200468 exit_status = EFI_ST_FAILURE;
469 }
Heinrich Schuchardt6a6e7d82019-09-01 15:24:47 +0200470 if (net->mode->state != EFI_NETWORK_STOPPED) {
471 efi_st_error("Failed to stop network adapter\n");
472 return EFI_ST_FAILURE;
473 }
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200474 }
475
476 return exit_status;
477}
478
479EFI_UNIT_TEST(snp) = {
480 .name = "simple network protocol",
481 .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT,
482 .setup = setup,
483 .execute = execute,
484 .teardown = teardown,
Heinrich Schuchardt2129bb12019-01-05 23:50:41 +0100485#ifdef CONFIG_SANDBOX
486 /*
487 * Running this test on the sandbox requires setting environment
488 * variable ethact to a network interface connected to a DHCP server and
489 * ethrotate to 'no'.
490 */
491 .on_request = true,
492#endif
Heinrich Schuchardt2a6daf72017-10-05 16:36:07 +0200493};