123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- // SPDX-License-Identifier: GPL-2.0+
- /*
- * efi_selftest_snp
- *
- * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
- *
- * This unit test covers the Simple Network Protocol as well as
- * the CopyMem and SetMem boottime services.
- *
- * A DHCP discover message is sent. The test is successful if a
- * DHCP reply is received.
- *
- * TODO: Once ConnectController and DisconnectController are implemented
- * we should connect our code as controller.
- */
- #include <efi_selftest.h>
- #include <net.h>
- /*
- * MAC address for broadcasts
- */
- static const u8 BROADCAST_MAC[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
- struct dhcp_hdr {
- u8 op;
- #define BOOTREQUEST 1
- #define BOOTREPLY 2
- u8 htype;
- # define HWT_ETHER 1
- u8 hlen;
- # define HWL_ETHER 6
- u8 hops;
- u32 xid;
- u16 secs;
- u16 flags;
- #define DHCP_FLAGS_UNICAST 0x0000
- #define DHCP_FLAGS_BROADCAST 0x0080
- u32 ciaddr;
- u32 yiaddr;
- u32 siaddr;
- u32 giaddr;
- u8 chaddr[16];
- u8 sname[64];
- u8 file[128];
- };
- /*
- * Message type option.
- */
- #define DHCP_MESSAGE_TYPE 0x35
- #define DHCPDISCOVER 1
- #define DHCPOFFER 2
- #define DHCPREQUEST 3
- #define DHCPDECLINE 4
- #define DHCPACK 5
- #define DHCPNAK 6
- #define DHCPRELEASE 7
- struct dhcp {
- struct ethernet_hdr eth_hdr;
- struct ip_udp_hdr ip_udp;
- struct dhcp_hdr dhcp_hdr;
- u8 opt[128];
- } __packed;
- static struct efi_boot_services *boottime;
- static struct efi_simple_network *net;
- static struct efi_event *timer;
- static const efi_guid_t efi_net_guid = EFI_SIMPLE_NETWORK_PROTOCOL_GUID;
- /* IP packet ID */
- static unsigned int net_ip_id;
- /*
- * Compute the checksum of the IP header. We cover even values of length only.
- * We cannot use net/checksum.c due to different CFLAGS values.
- *
- * @buf: IP header
- * @len: length of header in bytes
- * @return: checksum
- */
- static unsigned int efi_ip_checksum(const void *buf, size_t len)
- {
- size_t i;
- u32 sum = 0;
- const u16 *pos = buf;
- for (i = 0; i < len; i += 2)
- sum += *pos++;
- sum = (sum >> 16) + (sum & 0xffff);
- sum += sum >> 16;
- sum = ~sum & 0xffff;
- return sum;
- }
- /*
- * Transmit a DHCPDISCOVER message.
- */
- static efi_status_t send_dhcp_discover(void)
- {
- efi_status_t ret;
- struct dhcp p = {};
- /*
- * Fill Ethernet header
- */
- boottime->copy_mem(p.eth_hdr.et_dest, (void *)BROADCAST_MAC, ARP_HLEN);
- boottime->copy_mem(p.eth_hdr.et_src, &net->mode->current_address,
- ARP_HLEN);
- p.eth_hdr.et_protlen = htons(PROT_IP);
- /*
- * Fill IP header
- */
- p.ip_udp.ip_hl_v = 0x45;
- p.ip_udp.ip_len = htons(sizeof(struct dhcp) -
- sizeof(struct ethernet_hdr));
- p.ip_udp.ip_id = htons(++net_ip_id);
- p.ip_udp.ip_off = htons(IP_FLAGS_DFRAG);
- p.ip_udp.ip_ttl = 0xff; /* time to live */
- p.ip_udp.ip_p = IPPROTO_UDP;
- boottime->set_mem(&p.ip_udp.ip_dst, 4, 0xff);
- p.ip_udp.ip_sum = efi_ip_checksum(&p.ip_udp, IP_HDR_SIZE);
- /*
- * Fill UDP header
- */
- p.ip_udp.udp_src = htons(68);
- p.ip_udp.udp_dst = htons(67);
- p.ip_udp.udp_len = htons(sizeof(struct dhcp) -
- sizeof(struct ethernet_hdr) -
- sizeof(struct ip_hdr));
- /*
- * Fill DHCP header
- */
- p.dhcp_hdr.op = BOOTREQUEST;
- p.dhcp_hdr.htype = HWT_ETHER;
- p.dhcp_hdr.hlen = HWL_ETHER;
- p.dhcp_hdr.flags = htons(DHCP_FLAGS_UNICAST);
- boottime->copy_mem(&p.dhcp_hdr.chaddr,
- &net->mode->current_address, ARP_HLEN);
- /*
- * Fill options
- */
- p.opt[0] = 0x63; /* DHCP magic cookie */
- p.opt[1] = 0x82;
- p.opt[2] = 0x53;
- p.opt[3] = 0x63;
- p.opt[4] = DHCP_MESSAGE_TYPE;
- p.opt[5] = 0x01; /* length */
- p.opt[6] = DHCPDISCOVER;
- p.opt[7] = 0x39; /* maximum message size */
- p.opt[8] = 0x02; /* length */
- p.opt[9] = 0x02; /* 576 bytes */
- p.opt[10] = 0x40;
- p.opt[11] = 0xff; /* end of options */
- /*
- * Transmit DHCPDISCOVER message.
- */
- ret = net->transmit(net, 0, sizeof(struct dhcp), &p, NULL, NULL, 0);
- if (ret != EFI_SUCCESS)
- efi_st_error("Sending a DHCP request failed\n");
- else
- efi_st_printf("DHCP Discover\n");
- return ret;
- }
- /*
- * Setup unit test.
- *
- * Create a 1 s periodic timer.
- * Start the network driver.
- *
- * @handle: handle of the loaded image
- * @systable: system table
- * @return: EFI_ST_SUCCESS for success
- */
- static int setup(const efi_handle_t handle,
- const struct efi_system_table *systable)
- {
- efi_status_t ret;
- boottime = systable->boottime;
- /*
- * Create a timer event.
- */
- ret = boottime->create_event(EVT_TIMER, TPL_CALLBACK, NULL, NULL,
- &timer);
- if (ret != EFI_SUCCESS) {
- efi_st_error("Failed to create event\n");
- return EFI_ST_FAILURE;
- }
- /*
- * Set timer period to 1s.
- */
- ret = boottime->set_timer(timer, EFI_TIMER_PERIODIC, 10000000);
- if (ret != EFI_SUCCESS) {
- efi_st_error("Failed to set timer\n");
- return EFI_ST_FAILURE;
- }
- /*
- * Find an interface implementing the SNP protocol.
- */
- ret = boottime->locate_protocol(&efi_net_guid, NULL, (void **)&net);
- if (ret != EFI_SUCCESS) {
- net = NULL;
- efi_st_error("Failed to locate simple network protocol\n");
- return EFI_ST_FAILURE;
- }
- /*
- * Check hardware address size.
- */
- if (!net->mode) {
- efi_st_error("Mode not provided\n");
- return EFI_ST_FAILURE;
- }
- if (net->mode->hwaddr_size != ARP_HLEN) {
- efi_st_error("HwAddressSize = %u, expected %u\n",
- net->mode->hwaddr_size, ARP_HLEN);
- return EFI_ST_FAILURE;
- }
- /*
- * Check that WaitForPacket event exists.
- */
- if (!net->wait_for_packet) {
- efi_st_error("WaitForPacket event missing\n");
- return EFI_ST_FAILURE;
- }
- if (net->mode->state == EFI_NETWORK_INITIALIZED) {
- /*
- * Shut down network adapter.
- */
- ret = net->shutdown(net);
- if (ret != EFI_SUCCESS) {
- efi_st_error("Failed to shut down network adapter\n");
- return EFI_ST_FAILURE;
- }
- }
- if (net->mode->state == EFI_NETWORK_STARTED) {
- /*
- * Stop network adapter.
- */
- ret = net->stop(net);
- if (ret != EFI_SUCCESS) {
- efi_st_error("Failed to stop network adapter\n");
- return EFI_ST_FAILURE;
- }
- }
- /*
- * Start network adapter.
- */
- ret = net->start(net);
- if (ret != EFI_SUCCESS && ret != EFI_ALREADY_STARTED) {
- efi_st_error("Failed to start network adapter\n");
- return EFI_ST_FAILURE;
- }
- if (net->mode->state != EFI_NETWORK_STARTED) {
- efi_st_error("Failed to start network adapter\n");
- return EFI_ST_FAILURE;
- }
- /*
- * Initialize network adapter.
- */
- ret = net->initialize(net, 0, 0);
- if (ret != EFI_SUCCESS) {
- efi_st_error("Failed to initialize network adapter\n");
- return EFI_ST_FAILURE;
- }
- if (net->mode->state != EFI_NETWORK_INITIALIZED) {
- efi_st_error("Failed to initialize network adapter\n");
- return EFI_ST_FAILURE;
- }
- return EFI_ST_SUCCESS;
- }
- /*
- * Execute unit test.
- *
- * A DHCP discover message is sent. The test is successful if a
- * DHCP reply is received within 10 seconds.
- *
- * @return: EFI_ST_SUCCESS for success
- */
- static int execute(void)
- {
- efi_status_t ret;
- struct efi_event *events[2];
- efi_uintn_t index;
- union {
- struct dhcp p;
- u8 b[PKTSIZE];
- } buffer;
- struct efi_mac_address srcaddr;
- struct efi_mac_address destaddr;
- size_t buffer_size;
- u8 *addr;
- /*
- * The timeout is to occur after 10 s.
- */
- unsigned int timeout = 10;
- /* Setup may have failed */
- if (!net || !timer) {
- efi_st_error("Cannot execute test after setup failure\n");
- return EFI_ST_FAILURE;
- }
- /*
- * Send DHCP discover message
- */
- ret = send_dhcp_discover();
- if (ret != EFI_SUCCESS)
- return EFI_ST_FAILURE;
- /*
- * If we would call WaitForEvent only with the WaitForPacket event,
- * our code would block until a packet is received which might never
- * occur. By calling WaitFor event with both a timer event and the
- * WaitForPacket event we can escape this blocking situation.
- *
- * If the timer event occurs before we have received a DHCP reply
- * a further DHCP discover message is sent.
- */
- events[0] = timer;
- events[1] = net->wait_for_packet;
- for (;;) {
- u32 int_status;
- /*
- * Wait for packet to be received or timer event.
- */
- boottime->wait_for_event(2, events, &index);
- if (index == 0) {
- /*
- * The timer event occurred. Check for timeout.
- */
- --timeout;
- if (!timeout) {
- efi_st_error("Timeout occurred\n");
- return EFI_ST_FAILURE;
- }
- /*
- * Send further DHCP discover message
- */
- ret = send_dhcp_discover();
- if (ret != EFI_SUCCESS)
- return EFI_ST_FAILURE;
- continue;
- }
- /*
- * Receive packet
- */
- buffer_size = sizeof(buffer);
- ret = net->get_status(net, &int_status, NULL);
- if (ret != EFI_SUCCESS) {
- efi_st_error("Failed to get status");
- return EFI_ST_FAILURE;
- }
- if (!(int_status & EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT)) {
- efi_st_error("RX interrupt not set");
- return EFI_ST_FAILURE;
- }
- ret = net->receive(net, NULL, &buffer_size, &buffer,
- &srcaddr, &destaddr, NULL);
- if (ret != EFI_SUCCESS) {
- efi_st_error("Failed to receive packet");
- return EFI_ST_FAILURE;
- }
- /*
- * Check the packet is meant for this system.
- * Unfortunately QEMU ignores the broadcast flag.
- * So we have to check for broadcasts too.
- */
- if (memcmp(&destaddr, &net->mode->current_address, ARP_HLEN) &&
- memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
- continue;
- /*
- * Check this is a DHCP reply
- */
- if (buffer.p.eth_hdr.et_protlen != ntohs(PROT_IP) ||
- buffer.p.ip_udp.ip_hl_v != 0x45 ||
- buffer.p.ip_udp.ip_p != IPPROTO_UDP ||
- buffer.p.ip_udp.udp_src != ntohs(67) ||
- buffer.p.ip_udp.udp_dst != ntohs(68) ||
- buffer.p.dhcp_hdr.op != BOOTREPLY)
- continue;
- /*
- * We successfully received a DHCP reply.
- */
- break;
- }
- /*
- * Write a log message.
- */
- addr = (u8 *)&buffer.p.ip_udp.ip_src;
- efi_st_printf("DHCP reply received from %u.%u.%u.%u (%pm) ",
- addr[0], addr[1], addr[2], addr[3], &srcaddr);
- if (!memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
- efi_st_printf("as broadcast message.\n");
- else
- efi_st_printf("as unicast message.\n");
- return EFI_ST_SUCCESS;
- }
- /*
- * Tear down unit test.
- *
- * Close the timer event created in setup.
- * Shut down the network adapter.
- *
- * @return: EFI_ST_SUCCESS for success
- */
- static int teardown(void)
- {
- efi_status_t ret;
- int exit_status = EFI_ST_SUCCESS;
- if (timer) {
- /*
- * Stop timer.
- */
- ret = boottime->set_timer(timer, EFI_TIMER_STOP, 0);
- if (ret != EFI_SUCCESS) {
- efi_st_error("Failed to stop timer");
- exit_status = EFI_ST_FAILURE;
- }
- /*
- * Close timer event.
- */
- ret = boottime->close_event(timer);
- if (ret != EFI_SUCCESS) {
- efi_st_error("Failed to close event");
- exit_status = EFI_ST_FAILURE;
- }
- }
- if (net) {
- /*
- * Shut down network adapter.
- */
- ret = net->shutdown(net);
- if (ret != EFI_SUCCESS) {
- efi_st_error("Failed to shut down network adapter\n");
- exit_status = EFI_ST_FAILURE;
- }
- if (net->mode->state != EFI_NETWORK_STARTED) {
- efi_st_error("Failed to shutdown network adapter\n");
- return EFI_ST_FAILURE;
- }
- /*
- * Stop network adapter.
- */
- ret = net->stop(net);
- if (ret != EFI_SUCCESS) {
- efi_st_error("Failed to stop network adapter\n");
- exit_status = EFI_ST_FAILURE;
- }
- if (net->mode->state != EFI_NETWORK_STOPPED) {
- efi_st_error("Failed to stop network adapter\n");
- return EFI_ST_FAILURE;
- }
- }
- return exit_status;
- }
- EFI_UNIT_TEST(snp) = {
- .name = "simple network protocol",
- .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT,
- .setup = setup,
- .execute = execute,
- .teardown = teardown,
- #ifdef CONFIG_SANDBOX
- /*
- * Running this test on the sandbox requires setting environment
- * variable ethact to a network interface connected to a DHCP server and
- * ethrotate to 'no'.
- */
- .on_request = true,
- #endif
- };
|