4 * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
6 * SPDX-License-Identifier: GPL-2.0+
8 * This unit test covers the Simple Network Protocol as well as
9 * the CopyMem and SetMem boottime services.
11 * A DHCP discover message is sent. The test is successful if a
12 * DHCP reply is received.
14 * TODO: Once ConnectController and DisconnectController are implemented
15 * we should connect our code as controller.
18 #include <efi_selftest.h>
21 * MAC address for broadcasts
23 static const u8 BROADCAST_MAC[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
37 #define DHCP_FLAGS_UNICAST 0x0000
38 #define DHCP_FLAGS_BROADCAST 0x0080
49 * Message type option.
51 #define DHCP_MESSAGE_TYPE 0x35
52 #define DHCPDISCOVER 1
61 struct ethernet_hdr eth_hdr;
62 struct ip_udp_hdr ip_udp;
63 struct dhcp_hdr dhcp_hdr;
67 static struct efi_boot_services *boottime;
68 static struct efi_simple_network *net;
69 static struct efi_event *timer;
70 static const efi_guid_t efi_net_guid = EFI_SIMPLE_NETWORK_GUID;
72 static unsigned int net_ip_id;
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.
79 * @len: length of header in bytes
82 static unsigned int efi_ip_checksum(const void *buf, size_t len)
88 for (i = 0; i < len; i += 2)
91 sum = (sum >> 16) + (sum & 0xffff);
99 * Transmit a DHCPDISCOVER message.
101 static efi_status_t send_dhcp_discover(void)
107 * Fill ethernet header
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,
112 p.eth_hdr.et_protlen = htons(PROT_IP);
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);
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));
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);
146 p.opt[0] = 0x63; /* DHCP magic cookie */
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 */
157 p.opt[11] = 0xff; /* end of options */
160 * Transmit DHCPDISCOVER message.
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");
166 efi_st_printf("DHCP Discover\n");
173 * Create a 1 s periodic timer.
174 * Start the network driver.
176 * @handle: handle of the loaded image
177 * @systable: system table
178 * @return: EFI_ST_SUCCESS for success
180 static int setup(const efi_handle_t handle,
181 const struct efi_system_table *systable)
185 boottime = systable->boottime;
188 * Create a timer event.
190 ret = boottime->create_event(EVT_TIMER, TPL_CALLBACK, NULL, NULL,
192 if (ret != EFI_SUCCESS) {
193 efi_st_error("Failed to create event\n");
194 return EFI_ST_FAILURE;
197 * Set timer period to 1s.
199 ret = boottime->set_timer(timer, EFI_TIMER_PERIODIC, 10000000);
200 if (ret != EFI_SUCCESS) {
201 efi_st_error("Failed to set timer\n");
202 return EFI_ST_FAILURE;
205 * Find an interface implementing the SNP protocol.
207 ret = boottime->locate_protocol(&efi_net_guid, NULL, (void **)&net);
208 if (ret != EFI_SUCCESS) {
210 efi_st_error("Failed to locate simple network protocol\n");
211 return EFI_ST_FAILURE;
214 * Check hardware address size.
217 efi_st_error("Mode not provided\n");
218 return EFI_ST_FAILURE;
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;
226 * Check that WaitForPacket event exists.
228 if (!net->wait_for_packet) {
229 efi_st_error("WaitForPacket event missing\n");
230 return EFI_ST_FAILURE;
233 * Initialize network adapter.
235 ret = net->initialize(net, 0, 0);
236 if (ret != EFI_SUCCESS) {
237 efi_st_error("Failed to initialize network adapter\n");
238 return EFI_ST_FAILURE;
241 * Start network adapter.
243 ret = net->start(net);
244 if (ret != EFI_SUCCESS) {
245 efi_st_error("Failed to start network adapter\n");
246 return EFI_ST_FAILURE;
248 return EFI_ST_SUCCESS;
254 * A DHCP discover message is sent. The test is successful if a
255 * DHCP reply is received within 10 seconds.
257 * @return: EFI_ST_SUCCESS for success
259 static int execute(void)
262 struct efi_event *events[2];
268 struct efi_mac_address srcaddr;
269 struct efi_mac_address destaddr;
273 * The timeout is to occur after 10 s.
275 unsigned int timeout = 10;
277 /* Setup may have failed */
278 if (!net || !timer) {
279 efi_st_error("Cannot execute test after setup failure\n");
280 return EFI_ST_FAILURE;
284 * Send DHCP discover message
286 ret = send_dhcp_discover();
287 if (ret != EFI_SUCCESS)
288 return EFI_ST_FAILURE;
291 * If we would call WaitForEvent only with the WaitForPacket event,
292 * our code would block until a packet is received which might never
293 * occur. By calling WaitFor event with both a timer event and the
294 * WaitForPacket event we can escape this blocking situation.
296 * If the timer event occurs before we have received a DHCP reply
297 * a further DHCP discover message is sent.
300 events[1] = net->wait_for_packet;
303 * Wait for packet to be received or timer event.
305 boottime->wait_for_event(2, events, &index);
308 * The timer event occurred. Check for timeout.
312 efi_st_error("Timeout occurred\n");
313 return EFI_ST_FAILURE;
316 * Send further DHCP discover message
318 ret = send_dhcp_discover();
319 if (ret != EFI_SUCCESS)
320 return EFI_ST_FAILURE;
326 buffer_size = sizeof(buffer);
327 net->receive(net, NULL, &buffer_size, &buffer,
328 &srcaddr, &destaddr, NULL);
329 if (ret != EFI_SUCCESS) {
330 efi_st_error("Failed to receive packet");
331 return EFI_ST_FAILURE;
334 * Check the packet is meant for this system.
335 * Unfortunately QEMU ignores the broadcast flag.
336 * So we have to check for broadcasts too.
338 if (efi_st_memcmp(&destaddr, &net->mode->current_address,
340 efi_st_memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
343 * Check this is a DHCP reply
345 if (buffer.p.eth_hdr.et_protlen != ntohs(PROT_IP) ||
346 buffer.p.ip_udp.ip_hl_v != 0x45 ||
347 buffer.p.ip_udp.ip_p != IPPROTO_UDP ||
348 buffer.p.ip_udp.udp_src != ntohs(67) ||
349 buffer.p.ip_udp.udp_dst != ntohs(68) ||
350 buffer.p.dhcp_hdr.op != BOOTREPLY)
353 * We successfully received a DHCP reply.
359 * Write a log message.
361 addr = (u8 *)&buffer.p.ip_udp.ip_src;
362 efi_st_printf("DHCP reply received from %u.%u.%u.%u (%pm) ",
363 addr[0], addr[1], addr[2], addr[3], &srcaddr);
364 if (!efi_st_memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
365 efi_st_printf("as broadcast message.\n");
367 efi_st_printf("as unicast message.\n");
369 return EFI_ST_SUCCESS;
373 * Tear down unit test.
375 * Close the timer event created in setup.
376 * Shut down the network adapter.
378 * @return: EFI_ST_SUCCESS for success
380 static int teardown(void)
383 int exit_status = EFI_ST_SUCCESS;
389 ret = boottime->set_timer(timer, EFI_TIMER_STOP, 0);
390 if (ret != EFI_SUCCESS) {
391 efi_st_error("Failed to stop timer");
392 exit_status = EFI_ST_FAILURE;
397 ret = boottime->close_event(timer);
398 if (ret != EFI_SUCCESS) {
399 efi_st_error("Failed to close event");
400 exit_status = EFI_ST_FAILURE;
405 * Stop network adapter.
407 ret = net->stop(net);
408 if (ret != EFI_SUCCESS) {
409 efi_st_error("Failed to stop network adapter\n");
410 exit_status = EFI_ST_FAILURE;
413 * Shut down network adapter.
415 ret = net->shutdown(net);
416 if (ret != EFI_SUCCESS) {
417 efi_st_error("Failed to shut down network adapter\n");
418 exit_status = EFI_ST_FAILURE;
425 EFI_UNIT_TEST(snp) = {
426 .name = "simple network protocol",
427 .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT,
430 .teardown = teardown,