From b20f497f22a76785f4c615af78a5b70f6ba49046 Mon Sep 17 00:00:00 2001 From: Adriano Cordova Date: Wed, 4 Dec 2024 00:05:21 -0300 Subject: efi_loader: efi_net: add efi_net_set_addr, efi_net_get_addr Add the functions efi_net_set_addr and efi_net_get_addr to set and get the ip address from efi code in a network agnostic way. This could also go in net_common, or be compiled conditionally for each network stack. Signed-off-by: Adriano Cordova Reviewed-by: Heinrich Schuchardt --- lib/efi_loader/efi_net.c | 125 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) (limited to 'lib/efi_loader/efi_net.c') diff --git a/lib/efi_loader/efi_net.c b/lib/efi_loader/efi_net.c index 7cd536705f4..3491d4c4818 100644 --- a/lib/efi_loader/efi_net.c +++ b/lib/efi_loader/efi_net.c @@ -17,6 +17,7 @@ #include #include +#include #include static const efi_guid_t efi_net_guid = EFI_SIMPLE_NETWORK_PROTOCOL_GUID; @@ -997,3 +998,127 @@ out_of_resources: printf("ERROR: Out of memory\n"); return EFI_OUT_OF_RESOURCES; } + +/** + * efi_net_get_addr() - get IP address information + * + * Copy the current IP address, mask, and gateway into the + * efi_ipv4_address structs pointed to by ip, mask and gw, + * respectively. + * + * @ip: pointer to an efi_ipv4_address struct to + * be filled with the current IP address + * @mask: pointer to an efi_ipv4_address struct to + * be filled with the current network mask + * @gw: pointer to an efi_ipv4_address struct to be + * filled with the current network gateway + */ +void efi_net_get_addr(struct efi_ipv4_address *ip, + struct efi_ipv4_address *mask, + struct efi_ipv4_address *gw) +{ +#ifdef CONFIG_NET_LWIP + char ipstr[] = "ipaddr\0\0"; + char maskstr[] = "netmask\0\0"; + char gwstr[] = "gatewayip\0\0"; + int idx; + struct in_addr tmp; + char *env; + + idx = dev_seq(eth_get_dev()); + + if (idx < 0 || idx > 99) { + log_err("unexpected idx %d\n", idx); + return; + } + + if (idx) { + sprintf(ipstr, "ipaddr%d", idx); + sprintf(maskstr, "netmask%d", idx); + sprintf(gwstr, "gatewayip%d", idx); + } + + env = env_get(ipstr); + if (env && ip) { + tmp = string_to_ip(env); + memcpy(ip, &tmp, sizeof(tmp)); + } + + env = env_get(maskstr); + if (env && mask) { + tmp = string_to_ip(env); + memcpy(mask, &tmp, sizeof(tmp)); + } + env = env_get(gwstr); + if (env && gw) { + tmp = string_to_ip(env); + memcpy(gw, &tmp, sizeof(tmp)); + } +#else + if (ip) + memcpy(ip, &net_ip, sizeof(net_ip)); + if (mask) + memcpy(mask, &net_netmask, sizeof(net_netmask)); +#endif +} + +/** + * efi_net_set_addr() - set IP address information + * + * Set the current IP address, mask, and gateway to the + * efi_ipv4_address structs pointed to by ip, mask and gw, + * respectively. + * + * @ip: pointer to new IP address + * @mask: pointer to new network mask to set + * @gw: pointer to new network gateway + */ +void efi_net_set_addr(struct efi_ipv4_address *ip, + struct efi_ipv4_address *mask, + struct efi_ipv4_address *gw) +{ +#ifdef CONFIG_NET_LWIP + char ipstr[] = "ipaddr\0\0"; + char maskstr[] = "netmask\0\0"; + char gwstr[] = "gatewayip\0\0"; + int idx; + struct in_addr *addr; + char tmp[46]; + + idx = dev_seq(eth_get_dev()); + + if (idx < 0 || idx > 99) { + log_err("unexpected idx %d\n", idx); + return; + } + + if (idx) { + sprintf(ipstr, "ipaddr%d", idx); + sprintf(maskstr, "netmask%d", idx); + sprintf(gwstr, "gatewayip%d", idx); + } + + if (ip) { + addr = (struct in_addr *)ip; + ip_to_string(*addr, tmp); + env_set(ipstr, tmp); + } + + if (mask) { + addr = (struct in_addr *)mask; + ip_to_string(*addr, tmp); + env_set(maskstr, tmp); + } + + if (gw) { + addr = (struct in_addr *)gw; + ip_to_string(*addr, tmp); + env_set(gwstr, tmp); + } +#else + if (ip) + memcpy(&net_ip, ip, sizeof(*ip)); + if (mask) + memcpy(&net_netmask, mask, sizeof(*mask)); +#endif +} -- cgit v1.2.3 From e55a4acb54e807c6411c4f6ab914fa2b3f55784e Mon Sep 17 00:00:00 2001 From: Adriano Cordova Date: Wed, 4 Dec 2024 00:05:23 -0300 Subject: efi_loader: net: set EFI bootdevice device path to HTTP when loaded from wget Set the device path of the efi boot device to an HTTP device path (as formed by efi_dp_from_http) when the next boot stage is loaded using wget (i.e., when wget is used with wget_info.set_bootdev=1). When loaded from HTTP, the device path should account for it so that the next boot stage is aware (e.g. grub only loads its http stack if it itself was loaded from http, and it checks this from its device path). Signed-off-by: Adriano Cordova Reviewed-by: Heinrich Schuchardt --- lib/efi_loader/efi_net.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) (limited to 'lib/efi_loader/efi_net.c') diff --git a/lib/efi_loader/efi_net.c b/lib/efi_loader/efi_net.c index 3491d4c4818..e8af2e3d95e 100644 --- a/lib/efi_loader/efi_net.c +++ b/lib/efi_loader/efi_net.c @@ -16,6 +16,7 @@ */ #include +#include #include #include #include @@ -32,6 +33,13 @@ static int rx_packet_idx; static int rx_packet_num; static struct efi_net_obj *netobj; +/* + * The current network device path. This device path is updated when a new + * bootfile is downloaded from the network. If then the bootfile is loaded + * as an efi image, net_dp is passed as the device path of the loaded image. + */ +static struct efi_device_path *net_dp; + /* * The notification function of this event is called in every timer cycle * to check if a new network packet has been received. @@ -902,8 +910,10 @@ efi_status_t efi_net_register(void) &netobj->net); if (r != EFI_SUCCESS) goto failure_to_add_protocol; + if (!net_dp) + efi_net_set_dp("Net", NULL); r = efi_add_protocol(&netobj->header, &efi_guid_device_path, - efi_dp_from_eth()); + net_dp); if (r != EFI_SUCCESS) goto failure_to_add_protocol; r = efi_add_protocol(&netobj->header, &efi_pxe_base_code_protocol_guid, @@ -999,6 +1009,49 @@ out_of_resources: return EFI_OUT_OF_RESOURCES; } +/** + * efi_net_set_dp() - set device path of efi net device + * + * This gets called to update the device path when a new boot + * file is downloaded + * + * @dev: dev to set the device path from + * @server: remote server address + * Return: status code + */ +efi_status_t efi_net_set_dp(const char *dev, const char *server) +{ + efi_free_pool(net_dp); + + net_dp = NULL; + if (!strcmp(dev, "Net")) + net_dp = efi_dp_from_eth(); + else if (!strcmp(dev, "Http")) + net_dp = efi_dp_from_http(server); + + if (!net_dp) + return EFI_OUT_OF_RESOURCES; + + return EFI_SUCCESS; +} + +/** + * efi_net_get_dp() - get device path of efi net device + * + * Produce a copy of the current device path + * + * @dp: copy of the current device path, or NULL on error + */ +void efi_net_get_dp(struct efi_device_path **dp) +{ + if (!dp) + return; + if (!net_dp) + efi_net_set_dp("Net", NULL); + if (net_dp) + *dp = efi_dp_dup(net_dp); +} + /** * efi_net_get_addr() - get IP address information * -- cgit v1.2.3 From 5a5c5bf40a0ea479426ad3f5c0cbc5afa675786f Mon Sep 17 00:00:00 2001 From: Adriano Cordova Date: Wed, 4 Dec 2024 00:05:24 -0300 Subject: efi_loader: net: add support to send http requests and parse http headers Add network-stack agnostic way to send an http request and parse http headers from efi drivers. This uses wget as a backend and communicates with it via efi_wget_info. The function efi_net_do_request allocates a buffer on behalf of an efi application using efi_alloc and passes it to wget to receive the data. If the method is GET and the buffer is too small, it re-allocates the buffer based on the last received Content-Length header and tries again. If the method is HEAD it just issues one request. So issuing a HEAD request (to update Content-Length) and then a GET request is preferred but not required. The function efi_net_parse_headers parses a raw buffer containing an http header into an array of EFI specific 'http_header' structs. Signed-off-by: Adriano Cordova Reviewed-by: Heinrich Schuchardt --- lib/efi_loader/efi_net.c | 155 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) (limited to 'lib/efi_loader/efi_net.c') diff --git a/lib/efi_loader/efi_net.c b/lib/efi_loader/efi_net.c index e8af2e3d95e..368f62c5b2d 100644 --- a/lib/efi_loader/efi_net.c +++ b/lib/efi_loader/efi_net.c @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -40,6 +41,12 @@ static struct efi_net_obj *netobj; */ static struct efi_device_path *net_dp; +static struct wget_http_info efi_wget_info = { + .set_bootdev = false, + .check_buffer_size = true, + +}; + /* * The notification function of this event is called in every timer cycle * to check if a new network packet has been received. @@ -1175,3 +1182,151 @@ void efi_net_set_addr(struct efi_ipv4_address *ip, memcpy(&net_netmask, mask, sizeof(*mask)); #endif } + +/** + * efi_net_set_buffer() - allocate a buffer of min 64K + * + * @buffer: allocated buffer + * @size: desired buffer size + * Return: status code + */ +static efi_status_t efi_net_set_buffer(void **buffer, size_t size) +{ + efi_status_t ret = EFI_SUCCESS; + + if (size < SZ_64K) + size = SZ_64K; + + efi_free_pool(*buffer); + + *buffer = efi_alloc(size); + if (!*buffer) + ret = EFI_OUT_OF_RESOURCES; + + efi_wget_info.buffer_size = (ulong)size; + + return ret; +} + +/** + * efi_net_parse_headers() - parse HTTP headers + * + * Parses the raw buffer efi_wget_info.headers into an array headers + * of efi structs http_headers. The array should be at least + * MAX_HTTP_HEADERS long. + * + * @num_headers: number of headers + * @headers: caller provided array of struct http_headers + */ +void efi_net_parse_headers(ulong *num_headers, struct http_header *headers) +{ + if (!num_headers || !headers) + return; + + // Populate info with http headers. + *num_headers = 0; + const uchar *line_start = efi_wget_info.headers; + const uchar *line_end; + ulong count; + struct http_header *current_header; + const uchar *separator; + size_t name_length, value_length; + + // Skip the first line (request or status line) + line_end = strstr(line_start, "\r\n"); + + if (line_end) + line_start = line_end + 2; + + while ((line_end = strstr(line_start, "\r\n")) != NULL) { + count = *num_headers; + if (line_start == line_end || count >= MAX_HTTP_HEADERS) + break; + current_header = headers + count; + separator = strchr(line_start, ':'); + if (separator) { + name_length = separator - line_start; + ++separator; + while (*separator == ' ') + ++separator; + value_length = line_end - separator; + if (name_length < MAX_HTTP_HEADER_NAME && + value_length < MAX_HTTP_HEADER_VALUE) { + strncpy(current_header->name, line_start, name_length); + current_header->name[name_length] = '\0'; + strncpy(current_header->value, separator, value_length); + current_header->value[value_length] = '\0'; + (*num_headers)++; + } + } + line_start = line_end + 2; + } +} + +/** + * efi_net_do_request() - issue an HTTP request using wget + * + * @url: url + * @method: HTTP method + * @buffer: data buffer + * @status_code: HTTP status code + * @file_size: file size in bytes + * @headers_buffer: headers buffer + * Return: status code + */ +efi_status_t efi_net_do_request(u8 *url, enum efi_http_method method, void **buffer, + u32 *status_code, ulong *file_size, char *headers_buffer) +{ + efi_status_t ret = EFI_SUCCESS; + int wget_ret; + static bool last_head; + + if (!buffer || !file_size) + return EFI_ABORTED; + + efi_wget_info.method = (enum wget_http_method)method; + efi_wget_info.headers = headers_buffer; + + switch (method) { + case HTTP_METHOD_GET: + ret = efi_net_set_buffer(buffer, last_head ? (size_t)efi_wget_info.hdr_cont_len : 0); + if (ret != EFI_SUCCESS) + goto out; + wget_ret = wget_request((ulong)*buffer, url, &efi_wget_info); + if ((ulong)efi_wget_info.hdr_cont_len > efi_wget_info.buffer_size) { + // Try again with updated buffer size + ret = efi_net_set_buffer(buffer, (size_t)efi_wget_info.hdr_cont_len); + if (ret != EFI_SUCCESS) + goto out; + if (wget_request((ulong)*buffer, url, &efi_wget_info)) { + efi_free_pool(*buffer); + ret = EFI_DEVICE_ERROR; + goto out; + } + } else if (wget_ret) { + efi_free_pool(*buffer); + ret = EFI_DEVICE_ERROR; + goto out; + } + // Pass the actual number of received bytes to the application + *file_size = efi_wget_info.file_size; + *status_code = efi_wget_info.status_code; + last_head = false; + break; + case HTTP_METHOD_HEAD: + ret = efi_net_set_buffer(buffer, 0); + if (ret != EFI_SUCCESS) + goto out; + wget_request((ulong)*buffer, url, &efi_wget_info); + *file_size = 0; + *status_code = efi_wget_info.status_code; + last_head = true; + break; + default: + ret = EFI_UNSUPPORTED; + break; + } + +out: + return ret; +} -- cgit v1.2.3 From 929363cbb35ef9943f9c02938dc23d2c78582c5f Mon Sep 17 00:00:00 2001 From: Adriano Cordova Date: Wed, 4 Dec 2024 00:05:25 -0300 Subject: efi_loader: efi_net: add EFI_IP4_CONFIG2_PROTOCOL Add an implementation of the EFI_IP4_CONFIG2_PROTOCOL. The protocol is attached to the handle of the efi network device. This is the same handle where snp and pxe are attached to. Signed-off-by: Adriano Cordova --- lib/efi_loader/efi_net.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'lib/efi_loader/efi_net.c') diff --git a/lib/efi_loader/efi_net.c b/lib/efi_loader/efi_net.c index 368f62c5b2d..916c15999e2 100644 --- a/lib/efi_loader/efi_net.c +++ b/lib/efi_loader/efi_net.c @@ -60,11 +60,12 @@ static struct efi_event *wait_for_packet; /** * struct efi_net_obj - EFI object representing a network interface * - * @header: EFI object header - * @net: simple network protocol interface - * @net_mode: status of the network interface - * @pxe: PXE base code protocol interface - * @pxe_mode: status of the PXE base code protocol + * @header: EFI object header + * @net: simple network protocol interface + * @net_mode: status of the network interface + * @pxe: PXE base code protocol interface + * @pxe_mode: status of the PXE base code protocol + * @ip4_config2: IP4 Config2 protocol interface */ struct efi_net_obj { struct efi_object header; @@ -72,6 +73,9 @@ struct efi_net_obj { struct efi_simple_network_mode net_mode; struct efi_pxe_base_code_protocol pxe; struct efi_pxe_mode pxe_mode; +#if IS_ENABLED(CONFIG_EFI_IP4_CONFIG2_PROTOCOL) + struct efi_ip4_config2_protocol ip4_config2; +#endif }; /* @@ -999,6 +1003,12 @@ efi_status_t efi_net_register(void) return r; } +#if IS_ENABLED(CONFIG_EFI_IP4_CONFIG2_PROTOCOL) + r = efi_ipconfig_register(&netobj->header, &netobj->ip4_config2); + if (r != EFI_SUCCESS) + goto failure_to_add_protocol; +#endif + return EFI_SUCCESS; failure_to_add_protocol: printf("ERROR: Failure to add protocol\n"); -- cgit v1.2.3 From 238e0269d82f9662b27cd040e32c3543ccf3380f Mon Sep 17 00:00:00 2001 From: Adriano Cordova Date: Wed, 4 Dec 2024 00:05:26 -0300 Subject: efi_loader: efi_net: add EFI_HTTP_PROTOCOL Add an EFI HTTP driver. This commit implements the EFI_HTTP_PROTOCOL and the EFI_HTTP_SERVICE_BINDING_PROTOCOL. The latter is attached to the handle of th efi network device. This is the same handle where snp, pxe, and ipconfig are attached to. Signed-off-by: Adriano Cordova --- lib/efi_loader/efi_net.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'lib/efi_loader/efi_net.c') diff --git a/lib/efi_loader/efi_net.c b/lib/efi_loader/efi_net.c index 916c15999e2..67593ef50c0 100644 --- a/lib/efi_loader/efi_net.c +++ b/lib/efi_loader/efi_net.c @@ -66,6 +66,7 @@ static struct efi_event *wait_for_packet; * @pxe: PXE base code protocol interface * @pxe_mode: status of the PXE base code protocol * @ip4_config2: IP4 Config2 protocol interface + * @http_service_binding: Http service binding protocol interface */ struct efi_net_obj { struct efi_object header; @@ -76,6 +77,9 @@ struct efi_net_obj { #if IS_ENABLED(CONFIG_EFI_IP4_CONFIG2_PROTOCOL) struct efi_ip4_config2_protocol ip4_config2; #endif +#if IS_ENABLED(CONFIG_EFI_HTTP_PROTOCOL) + struct efi_service_binding_protocol http_service_binding; +#endif }; /* @@ -1009,6 +1013,19 @@ efi_status_t efi_net_register(void) goto failure_to_add_protocol; #endif +#ifdef CONFIG_EFI_HTTP_PROTOCOL + r = efi_http_register(&netobj->header, &netobj->http_service_binding); + if (r != EFI_SUCCESS) + goto failure_to_add_protocol; + /* + * No harm on doing the following. If the PXE handle is present, the client could + * find it and try to get its IP address from it. In here the PXE handle is present + * but the PXE protocol is not yet implmenented, so we add this in the meantime. + */ + efi_net_get_addr((struct efi_ipv4_address *)&netobj->pxe_mode.station_ip, + (struct efi_ipv4_address *)&netobj->pxe_mode.subnet_mask, NULL); +#endif + return EFI_SUCCESS; failure_to_add_protocol: printf("ERROR: Failure to add protocol\n"); @@ -1207,8 +1224,6 @@ static efi_status_t efi_net_set_buffer(void **buffer, size_t size) if (size < SZ_64K) size = SZ_64K; - efi_free_pool(*buffer); - *buffer = efi_alloc(size); if (!*buffer) ret = EFI_OUT_OF_RESOURCES; @@ -1305,6 +1320,7 @@ efi_status_t efi_net_do_request(u8 *url, enum efi_http_method method, void **buf wget_ret = wget_request((ulong)*buffer, url, &efi_wget_info); if ((ulong)efi_wget_info.hdr_cont_len > efi_wget_info.buffer_size) { // Try again with updated buffer size + efi_free_pool(*buffer); ret = efi_net_set_buffer(buffer, (size_t)efi_wget_info.hdr_cont_len); if (ret != EFI_SUCCESS) goto out; -- cgit v1.2.3