]> git.proxmox.com Git - efi-boot-shim.git/blob - netboot.c
Factor out x86-isms and add cross compile support
[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 #include "str.h"
42
43 #define ntohs(x) __builtin_bswap16(x) /* supported both by GCC and clang */
44 #define htons(x) ntohs(x)
45
46 static EFI_PXE_BASE_CODE *pxe;
47 static EFI_IP_ADDRESS tftp_addr;
48 static CHAR8 *full_path;
49
50
51 typedef struct {
52 UINT16 OpCode;
53 UINT16 Length;
54 UINT8 Data[1];
55 } EFI_DHCP6_PACKET_OPTION;
56
57 static CHAR8 *
58 translate_slashes(char *str)
59 {
60 int i;
61 int j;
62 if (str == NULL)
63 return (CHAR8 *)str;
64
65 for (i = 0, j = 0; str[i] != '\0'; i++, j++) {
66 if (str[i] == '\\') {
67 str[j] = '/';
68 if (str[i+1] == '\\')
69 i++;
70 }
71 }
72 return (CHAR8 *)str;
73 }
74
75 /*
76 * usingNetboot
77 * Returns TRUE if we identify a protocol that is enabled and Providing us with
78 * the needed information to fetch a grubx64.efi image
79 */
80 BOOLEAN findNetboot(EFI_HANDLE device)
81 {
82 EFI_STATUS status;
83
84 status = uefi_call_wrapper(BS->HandleProtocol, 3, device,
85 &PxeBaseCodeProtocol, (VOID **)&pxe);
86 if (status != EFI_SUCCESS) {
87 pxe = NULL;
88 return FALSE;
89 }
90
91 if (!pxe || !pxe->Mode) {
92 pxe = NULL;
93 return FALSE;
94 }
95
96 if (!pxe->Mode->Started || !pxe->Mode->DhcpAckReceived) {
97 pxe = NULL;
98 return FALSE;
99 }
100
101 /*
102 * We've located a pxe protocol handle thats been started and has
103 * received an ACK, meaning its something we'll be able to get
104 * tftp server info out of
105 */
106 return TRUE;
107 }
108
109 static CHAR8 *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt)
110 {
111 void *optr;
112 EFI_DHCP6_PACKET_OPTION *option;
113 CHAR8 *url;
114 UINT32 urllen;
115
116 optr = pkt->DhcpOptions;
117
118 for(;;) {
119 option = (EFI_DHCP6_PACKET_OPTION *)optr;
120
121 if (ntohs(option->OpCode) == 0)
122 return NULL;
123
124 if (ntohs(option->OpCode) == 59) {
125 /* This is the bootfile url option */
126 urllen = ntohs(option->Length);
127 url = AllocateZeroPool(urllen+1);
128 if (!url)
129 return NULL;
130 memcpy(url, option->Data, urllen);
131 return url;
132 }
133 optr += 4 + ntohs(option->Length);
134 }
135
136 return NULL;
137 }
138
139 static CHAR16 str2ns(CHAR8 *str)
140 {
141 CHAR16 ret = 0;
142 CHAR8 v;
143 for(;*str;str++) {
144 if ('0' <= *str && *str <= '9')
145 v = *str - '0';
146 else if ('A' <= *str && *str <= 'F')
147 v = *str - 'A' + 10;
148 else if ('a' <= *str && *str <= 'f')
149 v = *str - 'a' + 10;
150 else
151 v = 0;
152 ret = (ret << 4) + v;
153 }
154 return htons(ret);
155 }
156
157 static CHAR8 *str2ip6(CHAR8 *str)
158 {
159 UINT8 i, j, p;
160 size_t len;
161 CHAR8 *a, *b, t;
162 static UINT16 ip[8];
163
164 for(i=0; i < 8; i++) {
165 ip[i] = 0;
166 }
167 len = strlen(str);
168 a = b = str;
169 for(i=p=0; i < len; i++, b++) {
170 if (*b != ':')
171 continue;
172 *b = '\0';
173 ip[p++] = str2ns(a);
174 *b = ':';
175 a = b + 1;
176 if ( *(b+1) == ':' )
177 break;
178 }
179 a = b = (str + len);
180 for(j=len, p=7; j > i; j--, a--) {
181 if (*a != ':')
182 continue;
183 t = *b;
184 *b = '\0';
185 ip[p--] = str2ns(a+1);
186 *b = t;
187 b = a;
188 }
189 return (CHAR8 *)ip;
190 }
191
192 static BOOLEAN extract_tftp_info(CHAR8 *url)
193 {
194 CHAR8 *start, *end;
195 CHAR8 ip6str[40];
196 CHAR8 *template = (CHAR8 *)translate_slashes(DEFAULT_LOADER_CHAR);
197
198 if (strncmp((UINT8 *)url, (UINT8 *)"tftp://", 7)) {
199 Print(L"URLS MUST START WITH tftp://\n");
200 return FALSE;
201 }
202 start = url + 7;
203 if (*start != '[') {
204 Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
205 return FALSE;
206 }
207
208 start++;
209 end = start;
210 while ((*end != '\0') && (*end != ']')) {
211 end++;
212 if (end - start > 39) {
213 Print(L"TFTP URL includes malformed IPv6 address\n");
214 return FALSE;
215 }
216 }
217 if (end == '\0') {
218 Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
219 return FALSE;
220 }
221 memset(ip6str, 0, 40);
222 memcpy(ip6str, start, end - start);
223 end++;
224 memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
225 full_path = AllocateZeroPool(strlen(end)+strlen(template)+1);
226 if (!full_path)
227 return FALSE;
228 memcpy(full_path, end, strlen(end));
229 end = (CHAR8 *)strrchr((char *)full_path, '/');
230 if (!end)
231 end = (CHAR8 *)full_path;
232 memcpy(end, template, strlen(template));
233 end[strlen(template)] = '\0';
234
235 return TRUE;
236 }
237
238 static EFI_STATUS parseDhcp6()
239 {
240 EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
241 CHAR8 *bootfile_url;
242
243 bootfile_url = get_v6_bootfile_url(packet);
244 if (!bootfile_url)
245 return EFI_NOT_FOUND;
246 if (extract_tftp_info(bootfile_url) == FALSE) {
247 FreePool(bootfile_url);
248 return EFI_NOT_FOUND;
249 }
250 FreePool(bootfile_url);
251 return EFI_SUCCESS;
252 }
253
254 static EFI_STATUS parseDhcp4()
255 {
256 CHAR8 *template = (CHAR8 *)translate_slashes(DEFAULT_LOADER_CHAR);
257 INTN template_len = strlen(template) + 1;
258
259 INTN dir_len = strnlena(pxe->Mode->DhcpAck.Dhcpv4.BootpBootFile, 127);
260 INTN i;
261 UINT8 *dir = pxe->Mode->DhcpAck.Dhcpv4.BootpBootFile;
262
263 for (i = dir_len; i >= 0; i--) {
264 if (dir[i] == '/')
265 break;
266 }
267 dir_len = (i >= 0) ? i + 1 : 0;
268
269 full_path = AllocateZeroPool(dir_len + template_len);
270
271 if (!full_path)
272 return EFI_OUT_OF_RESOURCES;
273
274 if (dir_len > 0) {
275 strncpya(full_path, dir, dir_len);
276 if (full_path[dir_len-1] == '/' && template[0] == '/')
277 full_path[dir_len-1] = '\0';
278 }
279 if (dir_len == 0 && dir[0] != '/' && template[0] == '/')
280 template++;
281 strcata(full_path, template);
282 memcpy(&tftp_addr.v4, pxe->Mode->DhcpAck.Dhcpv4.BootpSiAddr, 4);
283
284 return EFI_SUCCESS;
285 }
286
287 EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle)
288 {
289
290 EFI_STATUS rc;
291
292 if (!pxe)
293 return EFI_NOT_READY;
294
295 memset((UINT8 *)&tftp_addr, 0, sizeof(tftp_addr));
296
297 /*
298 * If we've discovered an active pxe protocol figure out
299 * if its ipv4 or ipv6
300 */
301 if (pxe->Mode->UsingIpv6){
302 rc = parseDhcp6();
303 } else
304 rc = parseDhcp4();
305 return rc;
306 }
307
308 EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINT64 *bufsiz)
309 {
310 EFI_STATUS rc;
311 EFI_PXE_BASE_CODE_TFTP_OPCODE read = EFI_PXE_BASE_CODE_TFTP_READ_FILE;
312 BOOLEAN overwrite = FALSE;
313 BOOLEAN nobuffer = FALSE;
314 UINTN blksz = 512;
315
316 Print(L"Fetching Netboot Image\n");
317 if (*buffer == NULL) {
318 *buffer = AllocatePool(4096 * 1024);
319 if (!*buffer)
320 return EFI_OUT_OF_RESOURCES;
321 *bufsiz = 4096 * 1024;
322 }
323
324 try_again:
325 rc = uefi_call_wrapper(pxe->Mtftp, 10, pxe, read, *buffer, overwrite,
326 bufsiz, &blksz, &tftp_addr, full_path, NULL, nobuffer);
327
328 if (rc == EFI_BUFFER_TOO_SMALL) {
329 /* try again, doubling buf size */
330 *bufsiz *= 2;
331 FreePool(*buffer);
332 *buffer = AllocatePool(*bufsiz);
333 if (!*buffer)
334 return EFI_OUT_OF_RESOURCES;
335 goto try_again;
336 }
337
338 if (rc != EFI_SUCCESS && *buffer) {
339 FreePool(*buffer);
340 }
341 return rc;
342 }