]> git.proxmox.com Git - efi-boot-shim.git/blob - netboot.c
Import new upstream snapshot
[efi-boot-shim.git] / netboot.c
1 /*
2 * netboot - trivial UEFI first-stage bootloader netboot support
3 *
4 * Copyright 2012 Red Hat, Inc <mjg@redhat.com>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 *
13 * Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the
16 * distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29 * OF THE POSSIBILITY OF SUCH DAMAGE.
30 *
31 * Significant portions of this code are derived from Tianocore
32 * (http://tianocore.sf.net) and are Copyright 2009-2012 Intel
33 * Corporation.
34 */
35
36 #include <efi.h>
37 #include <efilib.h>
38 #include <string.h>
39 #include "shim.h"
40 #include "netboot.h"
41
42
43 static inline unsigned short int __swap16(unsigned short int x)
44 {
45 __asm__("xchgb %b0,%h0"
46 : "=q" (x)
47 : "0" (x));
48 return x;
49 }
50
51 #define ntohs(x) __swap16(x)
52 #define htons(x) ntohs(x)
53
54 static EFI_PXE_BASE_CODE *pxe;
55 static EFI_IP_ADDRESS tftp_addr;
56 static char *full_path;
57
58
59 /*
60 * Not in the EFI header set yet, so I have to declare it here
61 */
62 typedef struct {
63 UINT32 MessageType:8;
64 UINT32 TransactionId:24;
65 UINT8 DhcpOptions[1024];
66 } EFI_PXE_BASE_CODE_DHCPV6_PACKET;
67
68 typedef struct {
69 UINT16 OpCode;
70 UINT16 Length;
71 UINT8 Data[1];
72 } EFI_DHCP6_PACKET_OPTION;
73
74 /*
75 * usingNetboot
76 * Returns TRUE if we identify a protocol that is enabled and Providing us with
77 * the needed information to fetch a grubx64.efi image
78 */
79 BOOLEAN findNetboot(EFI_HANDLE image_handle)
80 {
81 UINTN bs = sizeof(EFI_HANDLE);
82 EFI_GUID pxe_base_code_protocol = EFI_PXE_BASE_CODE_PROTOCOL;
83 EFI_HANDLE *hbuf;
84 BOOLEAN rc = FALSE;
85 void *buffer = AllocatePool(bs);
86 UINTN errcnt = 0;
87 UINTN i;
88 EFI_STATUS status;
89
90 if (!buffer)
91 return FALSE;
92
93 try_again:
94 status = uefi_call_wrapper(BS->LocateHandle,5, ByProtocol,
95 &pxe_base_code_protocol, NULL, &bs,
96 buffer);
97
98 if (status == EFI_BUFFER_TOO_SMALL) {
99 errcnt++;
100 FreePool(buffer);
101 if (errcnt > 1)
102 return FALSE;
103 buffer = AllocatePool(bs);
104 if (!buffer)
105 return FALSE;
106 goto try_again;
107 }
108
109 if (status == EFI_NOT_FOUND) {
110 FreePool(buffer);
111 return FALSE;
112 }
113
114 /*
115 * We have a list of pxe supporting protocols, lets see if any are
116 * active
117 */
118 hbuf = buffer;
119 pxe = NULL;
120 for (i=0; i < (bs / sizeof(EFI_HANDLE)); i++) {
121 status = uefi_call_wrapper(BS->OpenProtocol, 6, hbuf[i],
122 &pxe_base_code_protocol,
123 &pxe, image_handle, NULL,
124 EFI_OPEN_PROTOCOL_GET_PROTOCOL);
125
126 if (status != EFI_SUCCESS) {
127 pxe = NULL;
128 continue;
129 }
130
131 if (!pxe || !pxe->Mode) {
132 pxe = NULL;
133 continue;
134 }
135
136 if (pxe->Mode->Started && pxe->Mode->DhcpAckReceived) {
137 /*
138 * We've located a pxe protocol handle thats been
139 * started and has received an ACK, meaning its
140 * something we'll be able to get tftp server info
141 * out of
142 */
143 rc = TRUE;
144 break;
145 }
146
147 }
148
149 FreePool(buffer);
150 return rc;
151 }
152
153 static char *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt)
154 {
155 void *optr;
156 EFI_DHCP6_PACKET_OPTION *option;
157 char *url;
158 UINT32 urllen;
159
160 optr = pkt->DhcpOptions;
161
162 for(;;) {
163 option = (EFI_DHCP6_PACKET_OPTION *)optr;
164
165 if (ntohs(option->OpCode) == 0)
166 return NULL;
167
168 if (ntohs(option->OpCode) == 59) {
169 /* This is the bootfile url option */
170 urllen = ntohs(option->Length);
171 url = AllocatePool(urllen+2);
172 if (!url)
173 return NULL;
174 memset(url, 0, urllen+2);
175 memcpy(url, option->Data, urllen);
176 return url;
177 }
178 optr += 4 + ntohs(option->Length);
179 }
180
181 return NULL;
182 }
183
184 static UINT16 str2ns(UINT8 *str)
185 {
186 UINT16 ret = 0;
187 UINT8 v;
188 for(;*str;str++) {
189 if ('0' <= *str && *str <= '9')
190 v = *str - '0';
191 else if ('A' <= *str && *str <= 'F')
192 v = *str - 'A' + 10;
193 else if ('a' <= *str && *str <= 'f')
194 v = *str - 'a' + 10;
195 else
196 v = 0;
197 ret = (ret << 4) + v;
198 }
199 return htons(ret);
200 }
201
202 static UINT8 *str2ip6(char *str)
203 {
204 UINT8 i, j, p;
205 size_t len;
206 UINT8 *a, *b, t;
207 static UINT16 ip[8];
208
209 for(i=0; i < 8; i++) {
210 ip[i] = 0;
211 }
212 len = strlen((UINT8 *)str);
213 a = b = (UINT8 *)str;
214 for(i=p=0; i < len; i++, b++) {
215 if (*b != ':')
216 continue;
217 *b = '\0';
218 ip[p++] = str2ns(a);
219 *b = ':';
220 a = b + 1;
221 if ( *(b+1) == ':' )
222 break;
223 }
224 a = b = (UINT8 *)(str + len);
225 for(j=len, p=7; j > i; j--, a--) {
226 if (*a != ':')
227 continue;
228 t = *b;
229 *b = '\0';
230 ip[p--] = str2ns(a+1);
231 *b = t;
232 b = a;
233 }
234 return (UINT8 *)ip;
235 }
236
237 static BOOLEAN extract_tftp_info(char *url)
238 {
239 char *start, *end;
240 char ip6str[128];
241 char *template = "/grubx64.efi";
242
243 if (strncmp((UINT8 *)url, (UINT8 *)"tftp://", 7)) {
244 Print(L"URLS MUST START WITH tftp://\n");
245 return FALSE;
246 }
247 start = url + 7;
248 if (*start != '[') {
249 Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
250 return FALSE;
251 }
252
253 start++;
254 end = start;
255 while ((*end != '\0') && (*end != ']')) {
256 end++;
257 }
258 if (end == '\0') {
259 Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
260 return FALSE;
261 }
262 *end = '\0';
263 memset(ip6str, 0, 128);
264 memcpy(ip6str, start, strlen((UINT8 *)start));
265 *end = ']';
266 end++;
267 memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
268 full_path = AllocatePool(strlen((UINT8 *)end)+strlen((UINT8 *)template)+1);
269 if (!full_path)
270 return FALSE;
271 memset(full_path, 0, strlen((UINT8 *)end)+strlen((UINT8 *)template));
272 memcpy(full_path, end, strlen((UINT8 *)end));
273 end = strrchr(full_path, '/');
274 if (!end)
275 end = full_path;
276 memcpy(end, template, strlen((UINT8 *)template));
277
278 return TRUE;
279 }
280
281 static EFI_STATUS parseDhcp6()
282 {
283 EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
284 char *bootfile_url;
285
286
287 bootfile_url = get_v6_bootfile_url(packet);
288 if (extract_tftp_info(bootfile_url) == FALSE)
289 return EFI_NOT_FOUND;
290 if (!bootfile_url)
291 return EFI_NOT_FOUND;
292 return EFI_SUCCESS;
293 }
294
295 static EFI_STATUS parseDhcp4()
296 {
297 char *template = "/grubx64.efi";
298 char *tmp = AllocatePool(16);
299
300
301 if (!tmp)
302 return EFI_OUT_OF_RESOURCES;
303
304
305 memcpy(&tftp_addr.v4, pxe->Mode->DhcpAck.Dhcpv4.BootpSiAddr, 4);
306
307 memcpy(tmp, template, 12);
308 tmp[13] = '\0';
309 full_path = tmp;
310
311 /* Note we don't capture the filename option here because we know its shim.efi
312 * We instead assume the filename at the end of the path is going to be grubx64.efi
313 */
314 return EFI_SUCCESS;
315 }
316
317 EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle)
318 {
319
320 EFI_STATUS rc;
321
322 if (!pxe)
323 return EFI_NOT_READY;
324
325 memset((UINT8 *)&tftp_addr, 0, sizeof(tftp_addr));
326
327 /*
328 * If we've discovered an active pxe protocol figure out
329 * if its ipv4 or ipv6
330 */
331 if (pxe->Mode->UsingIpv6){
332 rc = parseDhcp6();
333 } else
334 rc = parseDhcp4();
335 return rc;
336 }
337
338 EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINTN *bufsiz)
339 {
340 EFI_STATUS rc;
341 EFI_PXE_BASE_CODE_TFTP_OPCODE read = EFI_PXE_BASE_CODE_TFTP_READ_FILE;
342 BOOLEAN overwrite = FALSE;
343 BOOLEAN nobuffer = FALSE;
344 UINTN blksz = 512;
345
346 Print(L"Fetching Netboot Image\n");
347 if (*buffer == NULL) {
348 *buffer = AllocatePool(4096 * 1024);
349 if (!*buffer)
350 return EFI_OUT_OF_RESOURCES;
351 *bufsiz = 4096 * 1024;
352 }
353
354 try_again:
355 rc = uefi_call_wrapper(pxe->Mtftp, 10, pxe, read, *buffer, overwrite,
356 &bufsiz, &blksz, &tftp_addr, full_path, NULL, nobuffer);
357
358 if (rc == EFI_BUFFER_TOO_SMALL) {
359 /* try again, doubling buf size */
360 *bufsiz *= 2;
361 FreePool(*buffer);
362 *buffer = AllocatePool(*bufsiz);
363 if (!*buffer)
364 return EFI_OUT_OF_RESOURCES;
365 goto try_again;
366 }
367
368 return rc;
369
370 }