]>
Commit | Line | Data |
---|---|---|
5ca23ed5 HS |
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) { | |
fdd04563 | 201 | efi_st_error("Failed to set timer\n"); |
5ca23ed5 HS |
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) { | |
fdd04563 | 209 | net = NULL; |
5ca23ed5 HS |
210 | efi_st_error("Failed to locate simple network protocol\n"); |
211 | return EFI_ST_FAILURE; | |
212 | } | |
213 | /* | |
214 | * Check hardware address size. | |
215 | */ | |
216 | if (!net->mode) { | |
217 | efi_st_error("Mode not provided\n"); | |
218 | return EFI_ST_FAILURE; | |
219 | } | |
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; | |
224 | } | |
225 | /* | |
226 | * Check that WaitForPacket event exists. | |
227 | */ | |
228 | if (!net->wait_for_packet) { | |
229 | efi_st_error("WaitForPacket event missing\n"); | |
230 | return EFI_ST_FAILURE; | |
231 | } | |
232 | /* | |
233 | * Initialize network adapter. | |
234 | */ | |
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; | |
239 | } | |
240 | /* | |
241 | * Start network adapter. | |
242 | */ | |
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; | |
247 | } | |
248 | return EFI_ST_SUCCESS; | |
249 | } | |
250 | ||
251 | /* | |
252 | * Execute unit test. | |
253 | * | |
254 | * A DHCP discover message is sent. The test is successful if a | |
255 | * DHCP reply is received within 10 seconds. | |
256 | * | |
257 | * @return: EFI_ST_SUCCESS for success | |
258 | */ | |
259 | static int execute(void) | |
260 | { | |
261 | efi_status_t ret; | |
262 | struct efi_event *events[2]; | |
f5a2a938 | 263 | efi_uintn_t index; |
5ca23ed5 HS |
264 | union { |
265 | struct dhcp p; | |
266 | u8 b[PKTSIZE]; | |
267 | } buffer; | |
268 | struct efi_mac_address srcaddr; | |
269 | struct efi_mac_address destaddr; | |
270 | size_t buffer_size; | |
271 | u8 *addr; | |
272 | /* | |
273 | * The timeout is to occur after 10 s. | |
274 | */ | |
275 | unsigned int timeout = 10; | |
276 | ||
fdd04563 HS |
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; | |
281 | } | |
282 | ||
5ca23ed5 HS |
283 | /* |
284 | * Send DHCP discover message | |
285 | */ | |
286 | ret = send_dhcp_discover(); | |
287 | if (ret != EFI_SUCCESS) | |
288 | return EFI_ST_FAILURE; | |
289 | ||
290 | /* | |
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. | |
295 | * | |
296 | * If the timer event occurs before we have received a DHCP reply | |
297 | * a further DHCP discover message is sent. | |
298 | */ | |
299 | events[0] = timer; | |
300 | events[1] = net->wait_for_packet; | |
301 | for (;;) { | |
302 | /* | |
303 | * Wait for packet to be received or timer event. | |
304 | */ | |
305 | boottime->wait_for_event(2, events, &index); | |
306 | if (index == 0) { | |
307 | /* | |
308 | * The timer event occurred. Check for timeout. | |
309 | */ | |
310 | --timeout; | |
311 | if (!timeout) { | |
312 | efi_st_error("Timeout occurred\n"); | |
313 | return EFI_ST_FAILURE; | |
314 | } | |
315 | /* | |
316 | * Send further DHCP discover message | |
317 | */ | |
318 | ret = send_dhcp_discover(); | |
319 | if (ret != EFI_SUCCESS) | |
320 | return EFI_ST_FAILURE; | |
321 | continue; | |
322 | } | |
323 | /* | |
324 | * Receive packet | |
325 | */ | |
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; | |
332 | } | |
333 | /* | |
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. | |
337 | */ | |
338 | if (efi_st_memcmp(&destaddr, &net->mode->current_address, | |
339 | ARP_HLEN) && | |
340 | efi_st_memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN)) | |
341 | continue; | |
342 | /* | |
343 | * Check this is a DHCP reply | |
344 | */ | |
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) | |
351 | continue; | |
352 | /* | |
353 | * We successfully received a DHCP reply. | |
354 | */ | |
355 | break; | |
356 | } | |
357 | ||
358 | /* | |
359 | * Write a log message. | |
360 | */ | |
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"); | |
366 | else | |
367 | efi_st_printf("as unicast message.\n"); | |
368 | ||
369 | return EFI_ST_SUCCESS; | |
370 | } | |
371 | ||
372 | /* | |
373 | * Tear down unit test. | |
374 | * | |
375 | * Close the timer event created in setup. | |
376 | * Shut down the network adapter. | |
377 | * | |
378 | * @return: EFI_ST_SUCCESS for success | |
379 | */ | |
380 | static int teardown(void) | |
381 | { | |
382 | efi_status_t ret; | |
383 | int exit_status = EFI_ST_SUCCESS; | |
384 | ||
385 | if (timer) { | |
386 | /* | |
387 | * Stop timer. | |
388 | */ | |
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; | |
393 | } | |
394 | /* | |
395 | * Close timer event. | |
396 | */ | |
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; | |
401 | } | |
402 | } | |
403 | if (net) { | |
404 | /* | |
405 | * Stop network adapter. | |
406 | */ | |
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; | |
411 | } | |
412 | /* | |
413 | * Shut down network adapter. | |
414 | */ | |
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; | |
419 | } | |
420 | } | |
421 | ||
422 | return exit_status; | |
423 | } | |
424 | ||
425 | EFI_UNIT_TEST(snp) = { | |
426 | .name = "simple network protocol", | |
427 | .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT, | |
428 | .setup = setup, | |
429 | .execute = execute, | |
430 | .teardown = teardown, | |
431 | }; |