Commit 5ca23ed5bc63832baa24a6107537fdd229c458ae

Authored by Heinrich Schuchardt
Committed by Alexander Graf
1 parent 1b6332597f

efi_loader: supply EFI network test

This patch provides an EFI application to check the correct function
of the Simple Network Protocol implementation.

It sends a DHCP request and analyzes the DHCP offer.

Different error conditions including a 10s timeout are checked.

A successful execution will look like this:

=> bootefi nettest
Scanning disk ide.blk#0...
Found 1 disks
WARNING: Invalid device tree, expect boot to fail
Network test
DHCP Discover
DHCP reply received from 192.168.76.2 (52:55:c0:a8:4c:02)
as broadcast message.
OK. The test was completed successfully.

Signed-off-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Alexander Graf <agraf@suse.de>

Showing 4 changed files with 467 additions and 1 deletions Side-by-side Diff

include/efi_selftest.h
... ... @@ -61,6 +61,17 @@
61 61 __attribute__ ((format (__printf__, 1, 2)));
62 62  
63 63 /*
  64 + * Compare memory.
  65 + * We cannot use lib/string.c due to different CFLAGS values.
  66 + *
  67 + * @buf1: first buffer
  68 + * @buf2: second buffer
  69 + * @length: number of bytes to compare
  70 + * @return: 0 if both buffers contain the same bytes
  71 + */
  72 +int efi_st_memcmp(const void *buf1, const void *buf2, size_t length);
  73 +
  74 +/*
64 75 * Reads an Unicode character from the input device.
65 76 *
66 77 * @return: Unicode character
lib/efi_selftest/Makefile
... ... @@ -15,13 +15,19 @@
15 15 CFLAGS_REMOVE_efi_selftest_events.o := $(CFLAGS_NON_EFI)
16 16 CFLAGS_efi_selftest_exitbootservices.o := $(CFLAGS_EFI)
17 17 CFLAGS_REMOVE_efi_selftest_exitbootservices.o := $(CFLAGS_NON_EFI)
  18 +CFLAGS_efi_selftest_snp.o := $(CFLAGS_EFI)
  19 +CFLAGS_REMOVE_efi_selftest_snp.o := $(CFLAGS_NON_EFI)
18 20 CFLAGS_efi_selftest_tpl.o := $(CFLAGS_EFI)
19 21 CFLAGS_REMOVE_efi_selftest_tpl.o := $(CFLAGS_NON_EFI)
  22 +CFLAGS_efi_selftest_util.o := $(CFLAGS_EFI)
  23 +CFLAGS_REMOVE_efi_selftest_util.o := $(CFLAGS_NON_EFI)
20 24  
21 25 obj-$(CONFIG_CMD_BOOTEFI_SELFTEST) += \
22 26 efi_selftest.o \
23 27 efi_selftest_console.o \
24 28 efi_selftest_events.o \
25 29 efi_selftest_exitbootservices.o \
26   -efi_selftest_tpl.o
  30 +efi_selftest_snp.o \
  31 +efi_selftest_tpl.o \
  32 +efi_selftest_util.o
lib/efi_selftest/efi_selftest_snp.c
  1 +/*
  2 + * efi_selftest_snp
  3 + *
  4 + * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
  5 + *
  6 + * SPDX-License-Identifier: GPL-2.0+
  7 + *
  8 + * This unit test covers the Simple Network Protocol as well as
  9 + * the CopyMem and SetMem boottime services.
  10 + *
  11 + * A DHCP discover message is sent. The test is successful if a
  12 + * DHCP reply is received.
  13 + *
  14 + * TODO: Once ConnectController and DisconnectController are implemented
  15 + * we should connect our code as controller.
  16 + */
  17 +
  18 +#include <efi_selftest.h>
  19 +
  20 +/*
  21 + * MAC address for broadcasts
  22 + */
  23 +static const u8 BROADCAST_MAC[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
  24 +
  25 +struct 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 +
  60 +struct 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 +
  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;
  71 +/* IP packet ID */
  72 +static 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
  80 + * @return: checksum
  81 + */
  82 +static 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 + */
  101 +static efi_status_t send_dhcp_discover(void)
  102 +{
  103 + efi_status_t ret;
  104 + struct dhcp p = {};
  105 +
  106 + /*
  107 + * Fill ethernet header
  108 + */
  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
  178 + * @return: EFI_ST_SUCCESS for success
  179 + */
  180 +static 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) {
  201 + efi_st_error("Failed to locate simple network protocol\n");
  202 + 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) {
  209 + 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 + }
  231 + /*
  232 + * Initialize network adapter.
  233 + */
  234 + ret = net->initialize(net, 0, 0);
  235 + if (ret != EFI_SUCCESS) {
  236 + efi_st_error("Failed to initialize network adapter\n");
  237 + return EFI_ST_FAILURE;
  238 + }
  239 + /*
  240 + * Start network adapter.
  241 + */
  242 + ret = net->start(net);
  243 + if (ret != EFI_SUCCESS) {
  244 + efi_st_error("Failed to start network adapter\n");
  245 + return EFI_ST_FAILURE;
  246 + }
  247 + return EFI_ST_SUCCESS;
  248 +}
  249 +
  250 +/*
  251 + * Execute unit test.
  252 + *
  253 + * A DHCP discover message is sent. The test is successful if a
  254 + * DHCP reply is received within 10 seconds.
  255 + *
  256 + * @return: EFI_ST_SUCCESS for success
  257 + */
  258 +static int execute(void)
  259 +{
  260 + efi_status_t ret;
  261 + struct efi_event *events[2];
  262 + size_t index;
  263 + union {
  264 + struct dhcp p;
  265 + u8 b[PKTSIZE];
  266 + } buffer;
  267 + struct efi_mac_address srcaddr;
  268 + struct efi_mac_address destaddr;
  269 + size_t buffer_size;
  270 + u8 *addr;
  271 + /*
  272 + * The timeout is to occur after 10 s.
  273 + */
  274 + unsigned int timeout = 10;
  275 +
  276 + /*
  277 + * Send DHCP discover message
  278 + */
  279 + ret = send_dhcp_discover();
  280 + if (ret != EFI_SUCCESS)
  281 + return EFI_ST_FAILURE;
  282 +
  283 + /*
  284 + * If we would call WaitForEvent only with the WaitForPacket event,
  285 + * our code would block until a packet is received which might never
  286 + * occur. By calling WaitFor event with both a timer event and the
  287 + * WaitForPacket event we can escape this blocking situation.
  288 + *
  289 + * If the timer event occurs before we have received a DHCP reply
  290 + * a further DHCP discover message is sent.
  291 + */
  292 + events[0] = timer;
  293 + events[1] = net->wait_for_packet;
  294 + for (;;) {
  295 + /*
  296 + * Wait for packet to be received or timer event.
  297 + */
  298 + boottime->wait_for_event(2, events, &index);
  299 + if (index == 0) {
  300 + /*
  301 + * The timer event occurred. Check for timeout.
  302 + */
  303 + --timeout;
  304 + if (!timeout) {
  305 + efi_st_error("Timeout occurred\n");
  306 + return EFI_ST_FAILURE;
  307 + }
  308 + /*
  309 + * Send further DHCP discover message
  310 + */
  311 + ret = send_dhcp_discover();
  312 + if (ret != EFI_SUCCESS)
  313 + return EFI_ST_FAILURE;
  314 + continue;
  315 + }
  316 + /*
  317 + * Receive packet
  318 + */
  319 + buffer_size = sizeof(buffer);
  320 + net->receive(net, NULL, &buffer_size, &buffer,
  321 + &srcaddr, &destaddr, NULL);
  322 + if (ret != EFI_SUCCESS) {
  323 + efi_st_error("Failed to receive packet");
  324 + return EFI_ST_FAILURE;
  325 + }
  326 + /*
  327 + * Check the packet is meant for this system.
  328 + * Unfortunately QEMU ignores the broadcast flag.
  329 + * So we have to check for broadcasts too.
  330 + */
  331 + if (efi_st_memcmp(&destaddr, &net->mode->current_address,
  332 + ARP_HLEN) &&
  333 + efi_st_memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
  334 + continue;
  335 + /*
  336 + * Check this is a DHCP reply
  337 + */
  338 + if (buffer.p.eth_hdr.et_protlen != ntohs(PROT_IP) ||
  339 + buffer.p.ip_udp.ip_hl_v != 0x45 ||
  340 + buffer.p.ip_udp.ip_p != IPPROTO_UDP ||
  341 + buffer.p.ip_udp.udp_src != ntohs(67) ||
  342 + buffer.p.ip_udp.udp_dst != ntohs(68) ||
  343 + buffer.p.dhcp_hdr.op != BOOTREPLY)
  344 + continue;
  345 + /*
  346 + * We successfully received a DHCP reply.
  347 + */
  348 + break;
  349 + }
  350 +
  351 + /*
  352 + * Write a log message.
  353 + */
  354 + addr = (u8 *)&buffer.p.ip_udp.ip_src;
  355 + efi_st_printf("DHCP reply received from %u.%u.%u.%u (%pm) ",
  356 + addr[0], addr[1], addr[2], addr[3], &srcaddr);
  357 + if (!efi_st_memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
  358 + efi_st_printf("as broadcast message.\n");
  359 + else
  360 + efi_st_printf("as unicast message.\n");
  361 +
  362 + return EFI_ST_SUCCESS;
  363 +}
  364 +
  365 +/*
  366 + * Tear down unit test.
  367 + *
  368 + * Close the timer event created in setup.
  369 + * Shut down the network adapter.
  370 + *
  371 + * @return: EFI_ST_SUCCESS for success
  372 + */
  373 +static int teardown(void)
  374 +{
  375 + efi_status_t ret;
  376 + int exit_status = EFI_ST_SUCCESS;
  377 +
  378 + if (timer) {
  379 + /*
  380 + * Stop timer.
  381 + */
  382 + ret = boottime->set_timer(timer, EFI_TIMER_STOP, 0);
  383 + if (ret != EFI_SUCCESS) {
  384 + efi_st_error("Failed to stop timer");
  385 + exit_status = EFI_ST_FAILURE;
  386 + }
  387 + /*
  388 + * Close timer event.
  389 + */
  390 + ret = boottime->close_event(timer);
  391 + if (ret != EFI_SUCCESS) {
  392 + efi_st_error("Failed to close event");
  393 + exit_status = EFI_ST_FAILURE;
  394 + }
  395 + }
  396 + if (net) {
  397 + /*
  398 + * Stop network adapter.
  399 + */
  400 + ret = net->stop(net);
  401 + if (ret != EFI_SUCCESS) {
  402 + efi_st_error("Failed to stop network adapter\n");
  403 + exit_status = EFI_ST_FAILURE;
  404 + }
  405 + /*
  406 + * Shut down network adapter.
  407 + */
  408 + ret = net->shutdown(net);
  409 + if (ret != EFI_SUCCESS) {
  410 + efi_st_error("Failed to shut down network adapter\n");
  411 + exit_status = EFI_ST_FAILURE;
  412 + }
  413 + }
  414 +
  415 + return exit_status;
  416 +}
  417 +
  418 +EFI_UNIT_TEST(snp) = {
  419 + .name = "simple network protocol",
  420 + .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT,
  421 + .setup = setup,
  422 + .execute = execute,
  423 + .teardown = teardown,
  424 +};
lib/efi_selftest/efi_selftest_util.c
  1 +/*
  2 + * efi_selftest_util
  3 + *
  4 + * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
  5 + *
  6 + * SPDX-License-Identifier: GPL-2.0+
  7 + *
  8 + * Utility functions
  9 + */
  10 +
  11 +#include <efi_selftest.h>
  12 +
  13 +int efi_st_memcmp(const void *buf1, const void *buf2, size_t length)
  14 +{
  15 + const u8 *pos1 = buf1;
  16 + const u8 *pos2 = buf2;
  17 +
  18 + for (; length; --length) {
  19 + if (*pos1 != *pos2)
  20 + return pos1 - pos2;
  21 + ++pos1;
  22 + ++pos2;
  23 + }
  24 + return EFI_ST_SUCCESS;
  25 +}