]> git.proxmox.com Git - efi-boot-shim.git/blob - netboot.c
Merge branch 'mok' into netboot
[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 /*
110 * We have a list of pxe supporting protocols, lets see if any are
111 * active
112 */
113 hbuf = buffer;
114 pxe = NULL;
115 for (i=0; i < (bs / sizeof(EFI_HANDLE)); i++) {
116 status = uefi_call_wrapper(BS->OpenProtocol, 6, hbuf[i],
117 &pxe_base_code_protocol,
118 &pxe, image_handle, NULL,
119 EFI_OPEN_PROTOCOL_GET_PROTOCOL);
120
121 if (status != EFI_SUCCESS) {
122 pxe = NULL;
123 continue;
124 }
125
126 if (!pxe || !pxe->Mode) {
127 pxe = NULL;
128 continue;
129 }
130
131 if (pxe->Mode->Started && pxe->Mode->DhcpAckReceived) {
132 /*
133 * We've located a pxe protocol handle thats been
134 * started and has received an ACK, meaning its
135 * something we'll be able to get tftp server info
136 * out of
137 */
138 rc = TRUE;
139 break;
140 }
141
142 }
143
144 FreePool(buffer);
145 return rc;
146 }
147
148 static char *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt)
149 {
150 void *optr;
151 EFI_DHCP6_PACKET_OPTION *option;
152 char *url;
153 UINT32 urllen;
154
155 optr = pkt->DhcpOptions;
156
157 for(;;) {
158 option = (EFI_DHCP6_PACKET_OPTION *)optr;
159
160 if (ntohs(option->OpCode) == 0)
161 return NULL;
162
163 if (ntohs(option->OpCode) == 59) {
164 /* This is the bootfile url option */
165 urllen = ntohs(option->Length);
166 url = AllocatePool(urllen+2);
167 if (!url)
168 return NULL;
169 memset(url, 0, urllen+2);
170 memcpy(url, option->Data, urllen);
171 return url;
172 }
173 optr += 4 + ntohs(option->Length);
174 }
175
176 return NULL;
177 }
178
179 static UINT16 str2ns(UINT8 *str)
180 {
181 UINT16 ret = 0;
182 UINT8 v;
183 for(;*str;str++) {
184 if ('0' <= *str && *str <= '9')
185 v = *str - '0';
186 else if ('A' <= *str && *str <= 'F')
187 v = *str - 'A' + 10;
188 else if ('a' <= *str && *str <= 'f')
189 v = *str - 'a' + 10;
190 else
191 v = 0;
192 ret = (ret << 4) + v;
193 }
194 return htons(ret);
195 }
196
197 static UINT8 *str2ip6(char *str)
198 {
199 UINT8 i, j, p;
200 size_t len;
201 UINT8 *a, *b, t;
202 static UINT16 ip[8];
203
204 for(i=0; i < 8; i++) {
205 ip[i] = 0;
206 }
207 len = strlen((UINT8 *)str);
208 a = b = (UINT8 *)str;
209 for(i=p=0; i < len; i++, b++) {
210 if (*b != ':')
211 continue;
212 *b = '\0';
213 ip[p++] = str2ns(a);
214 *b = ':';
215 a = b + 1;
216 if ( *(b+1) == ':' )
217 break;
218 }
219 a = b = (UINT8 *)(str + len);
220 for(j=len, p=7; j > i; j--, a--) {
221 if (*a != ':')
222 continue;
223 t = *b;
224 *b = '\0';
225 ip[p--] = str2ns(a+1);
226 *b = t;
227 b = a;
228 }
229 return (UINT8 *)ip;
230 }
231
232 static BOOLEAN extract_tftp_info(char *url)
233 {
234 char *start, *end;
235 char ip6str[128];
236 char *template = "/grubx64.efi";
237
238 if (strncmp((UINT8 *)url, (UINT8 *)"tftp://", 7)) {
239 Print(L"URLS MUST START WITH tftp://\n");
240 return FALSE;
241 }
242 start = url + 7;
243 if (*start != '[') {
244 Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
245 return FALSE;
246 }
247
248 start++;
249 end = start;
250 while ((*end != '\0') && (*end != ']')) {
251 end++;
252 }
253 if (end == '\0') {
254 Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
255 return FALSE;
256 }
257 *end = '\0';
258 memset(ip6str, 0, 128);
259 memcpy(ip6str, start, strlen((UINT8 *)start));
260 *end = ']';
261 end++;
262 memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
263 full_path = AllocatePool(strlen((UINT8 *)end)+strlen((UINT8 *)template)+1);
264 if (!full_path)
265 return FALSE;
266 memset(full_path, 0, strlen((UINT8 *)end)+strlen((UINT8 *)template));
267 memcpy(full_path, end, strlen((UINT8 *)end));
268 end = strrchr(full_path, '/');
269 if (!end)
270 end = full_path;
271 memcpy(end, template, strlen((UINT8 *)template));
272
273 return TRUE;
274 }
275
276 static EFI_STATUS parseDhcp6()
277 {
278 EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
279 char *bootfile_url;
280
281
282 bootfile_url = get_v6_bootfile_url(packet);
283 if (extract_tftp_info(bootfile_url) == FALSE)
284 return EFI_NOT_FOUND;
285 if (!bootfile_url)
286 return EFI_NOT_FOUND;
287 return EFI_SUCCESS;
288 }
289
290 static EFI_STATUS parseDhcp4()
291 {
292 char *template = "/grubx64.efi";
293 char *tmp = AllocatePool(16);
294
295
296 if (!tmp)
297 return EFI_OUT_OF_RESOURCES;
298
299
300 memcpy(&tftp_addr.v4, pxe->Mode->DhcpAck.Dhcpv4.BootpSiAddr, 4);
301
302 memcpy(tmp, template, 12);
303 tmp[13] = '\0';
304 full_path = tmp;
305
306 /* Note we don't capture the filename option here because we know its shim.efi
307 * We instead assume the filename at the end of the path is going to be grubx64.efi
308 */
309 return EFI_SUCCESS;
310 }
311
312 EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle)
313 {
314
315 EFI_STATUS rc;
316
317 if (!pxe)
318 return EFI_NOT_READY;
319
320 memset((UINT8 *)&tftp_addr, 0, sizeof(tftp_addr));
321
322 /*
323 * If we've discovered an active pxe protocol figure out
324 * if its ipv4 or ipv6
325 */
326 if (pxe->Mode->UsingIpv6){
327 rc = parseDhcp6();
328 } else
329 rc = parseDhcp4();
330 return rc;
331 }
332
333 EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINTN *bufsiz)
334 {
335 EFI_STATUS rc;
336 EFI_PXE_BASE_CODE_TFTP_OPCODE read = EFI_PXE_BASE_CODE_TFTP_READ_FILE;
337 BOOLEAN overwrite = FALSE;
338 BOOLEAN nobuffer = FALSE;
339 UINTN blksz = 512;
340
341 Print(L"Fetching Netboot Image\n");
342 if (*buffer == NULL) {
343 *buffer = AllocatePool(4096 * 1024);
344 if (!*buffer)
345 return EFI_OUT_OF_RESOURCES;
346 *bufsiz = 4096 * 1024;
347 }
348
349 try_again:
350 rc = uefi_call_wrapper(pxe->Mtftp, 10, pxe, read, *buffer, overwrite,
351 &bufsiz, &blksz, &tftp_addr, full_path, NULL, nobuffer);
352
353 if (rc == EFI_BUFFER_TOO_SMALL) {
354 /* try again, doubling buf size */
355 *bufsiz *= 2;
356 FreePool(*buffer);
357 *buffer = AllocatePool(*bufsiz);
358 if (!*buffer)
359 return EFI_OUT_OF_RESOURCES;
360 goto try_again;
361 }
362
363 return rc;
364
365 }