1 // SPDX-License-Identifier: BSD-2-Clause-Patent
4 * netboot - trivial UEFI first-stage bootloader netboot support
6 * Copyright Red Hat, Inc
7 * Author: Matthew Garrett
9 * Significant portions of this code are derived from Tianocore
10 * (http://tianocore.sf.net) and are Copyright 2009-2012 Intel
16 #define ntohs(x) __builtin_bswap16(x) /* supported both by GCC and clang */
17 #define htons(x) ntohs(x)
19 static EFI_PXE_BASE_CODE
*pxe
;
20 static EFI_IP_ADDRESS tftp_addr
;
21 static CHAR8
*full_path
;
28 } EFI_DHCP6_PACKET_OPTION
;
32 * Returns TRUE if we identify a protocol that is enabled and Providing us with
33 * the needed information to fetch a grubx64.efi image
35 BOOLEAN
findNetboot(EFI_HANDLE device
)
37 EFI_STATUS efi_status
;
39 efi_status
= BS
->HandleProtocol(device
, &PxeBaseCodeProtocol
,
41 if (EFI_ERROR(efi_status
)) {
46 if (!pxe
|| !pxe
->Mode
) {
51 if (!pxe
->Mode
->Started
|| !pxe
->Mode
->DhcpAckReceived
) {
57 * We've located a pxe protocol handle thats been started and has
58 * received an ACK, meaning its something we'll be able to get
59 * tftp server info out of
64 static CHAR8
*get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET
*pkt
)
66 void *optr
= NULL
, *end
= NULL
;
67 EFI_DHCP6_PACKET_OPTION
*option
= NULL
;
71 optr
= pkt
->DhcpOptions
;
72 end
= optr
+ sizeof(pkt
->DhcpOptions
);
75 option
= (EFI_DHCP6_PACKET_OPTION
*)optr
;
77 if (ntohs(option
->OpCode
) == 0)
80 if (ntohs(option
->OpCode
) == 59) {
81 /* This is the bootfile url option */
82 urllen
= ntohs(option
->Length
);
83 if ((void *)(option
->Data
+ urllen
) > end
)
85 url
= AllocateZeroPool(urllen
+ 1);
88 memcpy(url
, option
->Data
, urllen
);
91 optr
+= 4 + ntohs(option
->Length
);
92 if (optr
+ sizeof(EFI_DHCP6_PACKET_OPTION
) > end
)
99 static CHAR16
str2ns(CHAR8
*str
)
104 if ('0' <= *str
&& *str
<= '9')
106 else if ('A' <= *str
&& *str
<= 'F')
108 else if ('a' <= *str
&& *str
<= 'f')
112 ret
= (ret
<< 4) + v
;
117 static CHAR8
*str2ip6(CHAR8
*str
)
119 UINT8 i
= 0, j
= 0, p
= 0;
120 size_t len
= 0, dotcount
= 0;
121 enum { MAX_IP6_DOTS
= 7 };
122 CHAR8
*a
= NULL
, *b
= NULL
, t
= 0;
125 memset(ip
, 0, sizeof(ip
));
127 /* Count amount of ':' to prevent overflows.
128 * max. count = 7. Returns an invalid ip6 that
129 * can be checked against
131 for (a
= str
; *a
!= 0; ++a
) {
135 if (dotcount
> MAX_IP6_DOTS
)
140 for (i
= p
= 0; i
< len
; i
++, b
++) {
151 for (j
= len
, p
= 7; j
> i
; j
--, a
--) {
156 ip
[p
--] = str2ns(a
+1);
163 static BOOLEAN
extract_tftp_info(CHAR8
*url
, CHAR8
*name
)
168 int template_len
= 0;
171 while (name
[template_len
++] != '\0');
172 template = (CHAR8
*)AllocatePool((template_len
+ 1) * sizeof (CHAR8
));
173 translate_slashes(template, name
);
175 // to check against str2ip6() errors
176 memset(ip6inv
, 0, sizeof(ip6inv
));
178 if (strncmp((const char *)url
, (const char *)"tftp://", 7)) {
179 console_print(L
"URLS MUST START WITH tftp://\n");
185 console_print(L
"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
192 while ((*end
!= '\0') && (*end
!= ']')) {
194 if (end
- start
>= (int)sizeof(ip6str
)) {
195 console_print(L
"TFTP URL includes malformed IPv6 address\n");
201 console_print(L
"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
205 memset(ip6str
, 0, sizeof(ip6str
));
206 memcpy(ip6str
, start
, end
- start
);
208 memcpy(&tftp_addr
.v6
, str2ip6(ip6str
), 16);
209 if (memcmp(&tftp_addr
.v6
, ip6inv
, sizeof(ip6inv
)) == 0) {
213 full_path
= AllocateZeroPool(strlen(end
)+strlen(template)+1);
218 memcpy(full_path
, end
, strlen(end
));
219 end
= (CHAR8
*)strrchr((char *)full_path
, '/');
221 end
= (CHAR8
*)full_path
;
222 memcpy(end
, template, strlen(template));
223 end
[strlen(template)] = '\0';
229 static EFI_STATUS
parseDhcp6(CHAR8
*name
)
231 EFI_PXE_BASE_CODE_DHCPV6_PACKET
*packet
= (EFI_PXE_BASE_CODE_DHCPV6_PACKET
*)&pxe
->Mode
->DhcpAck
.Raw
;
234 bootfile_url
= get_v6_bootfile_url(packet
);
236 return EFI_NOT_FOUND
;
237 if (extract_tftp_info(bootfile_url
, name
) == FALSE
) {
238 FreePool(bootfile_url
);
239 return EFI_NOT_FOUND
;
241 FreePool(bootfile_url
);
245 static EFI_STATUS
parseDhcp4(CHAR8
*name
)
248 INTN template_len
= 0;
249 UINTN template_ofs
= 0;
250 EFI_PXE_BASE_CODE_DHCPV4_PACKET
* pkt_v4
= (EFI_PXE_BASE_CODE_DHCPV4_PACKET
*)&pxe
->Mode
->DhcpAck
.Dhcpv4
;
252 while (name
[template_len
++] != '\0');
253 template = (CHAR8
*)AllocatePool((template_len
+ 1) * sizeof (CHAR8
));
254 translate_slashes(template, name
);
255 template_len
= strlen(template) + 1;
257 if(pxe
->Mode
->ProxyOfferReceived
) {
259 * Proxy should not have precedence. Check if DhcpAck
260 * contained boot info.
262 if(pxe
->Mode
->DhcpAck
.Dhcpv4
.BootpBootFile
[0] == '\0')
263 pkt_v4
= &pxe
->Mode
->ProxyOffer
.Dhcpv4
;
266 if(pxe
->Mode
->PxeReplyReceived
) {
268 * If we have no bootinfo yet search for it in the PxeReply.
269 * Some mainboards run into this when the server uses boot menus
271 if(pkt_v4
->BootpBootFile
[0] == '\0' && pxe
->Mode
->PxeReply
.Dhcpv4
.BootpBootFile
[0] != '\0')
272 pkt_v4
= &pxe
->Mode
->PxeReply
.Dhcpv4
;
275 INTN dir_len
= strnlen((CHAR8
*)pkt_v4
->BootpBootFile
, 127);
277 UINT8
*dir
= pkt_v4
->BootpBootFile
;
279 for (i
= dir_len
; i
>= 0; i
--) {
280 if ((dir
[i
] == '/') || (dir
[i
] == '\\'))
283 dir_len
= (i
>= 0) ? i
+ 1 : 0;
285 full_path
= AllocateZeroPool(dir_len
+ template_len
);
289 return EFI_OUT_OF_RESOURCES
;
293 strncpy(full_path
, (CHAR8
*)dir
, dir_len
);
294 if (full_path
[dir_len
-1] == '/' && template[0] == '/')
295 full_path
[dir_len
-1] = '\0';
297 * If the path from DHCP is using backslash instead of slash,
298 * accept that and use it in the template in the same position
301 if (full_path
[dir_len
-1] == '\\' && template[0] == '/') {
302 full_path
[dir_len
-1] = '\0';
306 if (dir_len
== 0 && dir
[0] != '/' && template[0] == '/')
308 strcat(full_path
, template + template_ofs
);
309 memcpy(&tftp_addr
.v4
, pkt_v4
->BootpSiAddr
, 4);
315 EFI_STATUS
parseNetbootinfo(EFI_HANDLE image_handle UNUSED
, CHAR8
*netbootname
)
318 EFI_STATUS efi_status
;
321 return EFI_NOT_READY
;
323 memset((UINT8
*)&tftp_addr
, 0, sizeof(tftp_addr
));
326 * If we've discovered an active pxe protocol figure out
327 * if its ipv4 or ipv6
329 if (pxe
->Mode
->UsingIpv6
){
330 efi_status
= parseDhcp6(netbootname
);
332 efi_status
= parseDhcp4(netbootname
);
336 EFI_STATUS
FetchNetbootimage(EFI_HANDLE image_handle UNUSED
, VOID
**buffer
, UINT64
*bufsiz
)
338 EFI_STATUS efi_status
;
339 EFI_PXE_BASE_CODE_TFTP_OPCODE read
= EFI_PXE_BASE_CODE_TFTP_READ_FILE
;
340 BOOLEAN overwrite
= FALSE
;
341 BOOLEAN nobuffer
= FALSE
;
344 console_print(L
"Fetching Netboot Image %a\n", full_path
);
345 if (*buffer
== NULL
) {
346 *buffer
= AllocatePool(4096 * 1024);
348 return EFI_OUT_OF_RESOURCES
;
349 *bufsiz
= 4096 * 1024;
353 efi_status
= pxe
->Mtftp(pxe
, read
, *buffer
, overwrite
, bufsiz
, &blksz
,
354 &tftp_addr
, (UINT8
*)full_path
, NULL
, nobuffer
);
355 if (efi_status
== EFI_BUFFER_TOO_SMALL
) {
356 /* try again, doubling buf size */
359 *buffer
= AllocatePool(*bufsiz
);
361 return EFI_OUT_OF_RESOURCES
;
365 if (EFI_ERROR(efi_status
) && *buffer
) {