1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * efi_selftest_snp 4 * 5 * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de> 6 * 7 * 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> 18 19 /* 20 * MAC address for broadcasts 21 */ 22 static const u8 BROADCAST_MAC[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; 23 24 struct dhcp_hdr { 25 u8 op; 26 #define BOOTREQUEST 1 27 #define BOOTREPLY 2 28 u8 htype; 29 # define HWT_ETHER 1 30 u8 hlen; 31 # define HWL_ETHER 6 32 u8 hops; 33 u32 xid; 34 u16 secs; 35 u16 flags; 36 #define DHCP_FLAGS_UNICAST 0x0000 37 #define DHCP_FLAGS_BROADCAST 0x0080 38 u32 ciaddr; 39 u32 yiaddr; 40 u32 siaddr; 41 u32 giaddr; 42 u8 chaddr[16]; 43 u8 sname[64]; 44 u8 file[128]; 45 }; 46 47 /* 48 * Message type option. 49 */ 50 #define DHCP_MESSAGE_TYPE 0x35 51 #define DHCPDISCOVER 1 52 #define DHCPOFFER 2 53 #define DHCPREQUEST 3 54 #define DHCPDECLINE 4 55 #define DHCPACK 5 56 #define DHCPNAK 6 57 #define DHCPRELEASE 7 58 59 struct dhcp { 60 struct ethernet_hdr eth_hdr; 61 struct ip_udp_hdr ip_udp; 62 struct dhcp_hdr dhcp_hdr; 63 u8 opt[128]; 64 } __packed; 65 66 static struct efi_boot_services *boottime; 67 static struct efi_simple_network *net; 68 static struct efi_event *timer; 69 static const efi_guid_t efi_net_guid = EFI_SIMPLE_NETWORK_GUID; 70 /* IP packet ID */ 71 static 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 79 * @return: checksum 80 */ 81 static 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 */ 100 static efi_status_t send_dhcp_discover(void) 101 { 102 efi_status_t ret; 103 struct dhcp p = {}; 104 105 /* 106 * Fill Ethernet header 107 */ 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 177 * @return: EFI_ST_SUCCESS for success 178 */ 179 static 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) { 200 efi_st_error("Failed to set timer\n"); 201 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) { 208 net = NULL; 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 * Start network adapter. 233 */ 234 ret = net->start(net); 235 if (ret != EFI_SUCCESS && ret != EFI_ALREADY_STARTED) { 236 efi_st_error("Failed to start network adapter\n"); 237 return EFI_ST_FAILURE; 238 } 239 /* 240 * Initialize network adapter. 241 */ 242 ret = net->initialize(net, 0, 0); 243 if (ret != EFI_SUCCESS) { 244 efi_st_error("Failed to initialize 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 efi_uintn_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 /* Setup may have failed */ 277 if (!net || !timer) { 278 efi_st_error("Cannot execute test after setup failure\n"); 279 return EFI_ST_FAILURE; 280 } 281 282 /* 283 * Send DHCP discover message 284 */ 285 ret = send_dhcp_discover(); 286 if (ret != EFI_SUCCESS) 287 return EFI_ST_FAILURE; 288 289 /* 290 * If we would call WaitForEvent only with the WaitForPacket event, 291 * our code would block until a packet is received which might never 292 * occur. By calling WaitFor event with both a timer event and the 293 * WaitForPacket event we can escape this blocking situation. 294 * 295 * If the timer event occurs before we have received a DHCP reply 296 * a further DHCP discover message is sent. 297 */ 298 events[0] = timer; 299 events[1] = net->wait_for_packet; 300 for (;;) { 301 /* 302 * Wait for packet to be received or timer event. 303 */ 304 boottime->wait_for_event(2, events, &index); 305 if (index == 0) { 306 /* 307 * The timer event occurred. Check for timeout. 308 */ 309 --timeout; 310 if (!timeout) { 311 efi_st_error("Timeout occurred\n"); 312 return EFI_ST_FAILURE; 313 } 314 /* 315 * Send further DHCP discover message 316 */ 317 ret = send_dhcp_discover(); 318 if (ret != EFI_SUCCESS) 319 return EFI_ST_FAILURE; 320 continue; 321 } 322 /* 323 * Receive packet 324 */ 325 buffer_size = sizeof(buffer); 326 net->receive(net, NULL, &buffer_size, &buffer, 327 &srcaddr, &destaddr, NULL); 328 if (ret != EFI_SUCCESS) { 329 efi_st_error("Failed to receive packet"); 330 return EFI_ST_FAILURE; 331 } 332 /* 333 * Check the packet is meant for this system. 334 * Unfortunately QEMU ignores the broadcast flag. 335 * So we have to check for broadcasts too. 336 */ 337 if (efi_st_memcmp(&destaddr, &net->mode->current_address, 338 ARP_HLEN) && 339 efi_st_memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN)) 340 continue; 341 /* 342 * Check this is a DHCP reply 343 */ 344 if (buffer.p.eth_hdr.et_protlen != ntohs(PROT_IP) || 345 buffer.p.ip_udp.ip_hl_v != 0x45 || 346 buffer.p.ip_udp.ip_p != IPPROTO_UDP || 347 buffer.p.ip_udp.udp_src != ntohs(67) || 348 buffer.p.ip_udp.udp_dst != ntohs(68) || 349 buffer.p.dhcp_hdr.op != BOOTREPLY) 350 continue; 351 /* 352 * We successfully received a DHCP reply. 353 */ 354 break; 355 } 356 357 /* 358 * Write a log message. 359 */ 360 addr = (u8 *)&buffer.p.ip_udp.ip_src; 361 efi_st_printf("DHCP reply received from %u.%u.%u.%u (%pm) ", 362 addr[0], addr[1], addr[2], addr[3], &srcaddr); 363 if (!efi_st_memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN)) 364 efi_st_printf("as broadcast message.\n"); 365 else 366 efi_st_printf("as unicast message.\n"); 367 368 return EFI_ST_SUCCESS; 369 } 370 371 /* 372 * Tear down unit test. 373 * 374 * Close the timer event created in setup. 375 * Shut down the network adapter. 376 * 377 * @return: EFI_ST_SUCCESS for success 378 */ 379 static int teardown(void) 380 { 381 efi_status_t ret; 382 int exit_status = EFI_ST_SUCCESS; 383 384 if (timer) { 385 /* 386 * Stop timer. 387 */ 388 ret = boottime->set_timer(timer, EFI_TIMER_STOP, 0); 389 if (ret != EFI_SUCCESS) { 390 efi_st_error("Failed to stop timer"); 391 exit_status = EFI_ST_FAILURE; 392 } 393 /* 394 * Close timer event. 395 */ 396 ret = boottime->close_event(timer); 397 if (ret != EFI_SUCCESS) { 398 efi_st_error("Failed to close event"); 399 exit_status = EFI_ST_FAILURE; 400 } 401 } 402 if (net) { 403 /* 404 * Stop network adapter. 405 */ 406 ret = net->stop(net); 407 if (ret != EFI_SUCCESS) { 408 efi_st_error("Failed to stop network adapter\n"); 409 exit_status = EFI_ST_FAILURE; 410 } 411 /* 412 * Shut down network adapter. 413 */ 414 ret = net->shutdown(net); 415 if (ret != EFI_SUCCESS) { 416 efi_st_error("Failed to shut down network adapter\n"); 417 exit_status = EFI_ST_FAILURE; 418 } 419 } 420 421 return exit_status; 422 } 423 424 EFI_UNIT_TEST(snp) = { 425 .name = "simple network protocol", 426 .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT, 427 .setup = setup, 428 .execute = execute, 429 .teardown = teardown, 430 #ifdef CONFIG_SANDBOX 431 /* 432 * Running this test on the sandbox requires setting environment 433 * variable ethact to a network interface connected to a DHCP server and 434 * ethrotate to 'no'. 435 */ 436 .on_request = true, 437 #endif 438 }; 439