仅 UEFI HTTP 启动:GRUB2 使用神秘的 HTTP 代理“UefiHttpBoot/1.0”加载 initrd,但无法完全读取大型 initrd

仅 UEFI HTTP 启动:GRUB2 使用神秘的 HTTP 代理“UefiHttpBoot/1.0”加载 initrd,但无法完全读取大型 initrd

我使用 GRUB 的目的是UEFI PXE 启动或者UEFI HTTP 启动Linux Live 操作系统(由 Ubuntu Focal 或 Bionic 或 Jammy 制作)。我grub.cfg对这两种方法都使用了相同的方法。它在 UEFI PXE 引导下运行良好,但在 UEFI HTTP 引导期间失败。

然后我发现了一些有趣的事情,关于谁在哪个阶段进行了http访问。

  • UEFI PXE启动时,HTTP访问日志是这样的:
"GET /liveos/focal/kernel HTTP/1.1" 200 11780639 "-" "GRUB 2.06-2ubuntu14.1"
"GET /liveos/focal/initrd HTTP/1.1" 200 89168508 "-" "GRUB 2.06-2ubuntu14.1"
"GET /liveos/focal/squashfs HTTP/1.1" 200 388669747 "-" "Wget"

因此GRUB加载了内核和initrd,然后initrd使用wget加载了squashfs。好吧,这是有道理的。

  • 然而,在UEFI HTTP启动时,HTTP访问日志是这样的:
"GET /bootx64.efi HTTP/1.1" 200 955941 "-" "UefiHttpBoot/1.0"
"GET /grubx64.efi HTTP/1.1" 200 1493150 "-" "UefiHttpBoot/1.0"

"GET /grub/grub.cfg HTTP/1.1" 200 1823 "-" "UefiHttpBoot/1.0"

"GET /liveos/focal/kernel HTTP/1.1" 200 11780639 "-" "UefiHttpBoot/1.0"
"GET /liveos/focal/initrd HTTP/1.1" 200 449680 "-" "UefiHttpBoot/1.0"

因此GRUB使用http代理“UefiHttpBoot/1.0”来加载内核和initrd。不幸的是,访问日志显示服务器没有向 UEFI HTTP 启动客户端响应完整大小的数据。全尺寸反应应该是这样的89168508,但现在只是449680

问题来了:

  • http代理“UefiHttpBoot/1.0”在哪里实现?在固件中?
  • 为什么 GRUB2 不使用与 UEFI PXE 引导中相同的逻辑,即使用自己的 http 代理来获取 initrd?

编辑:看起来像这样,在UEFI HTTP启动中,从一开始,加载bootx64.efi的是http代理“UefiHttpBoot/1.0”,所以 “UefiHttpBoot/1.0”应该来自固件但是,它应该将执行控制权传递给bootx64.efi(GRUB2),那么GRUB2应该使用自己的http工具来读取initrd,但实际上不是,为什么?看来UEFI HTTP启动在某种程度上与Linux启动协议配合。

编辑:更多信息:

  [    1.947449] Unpacking initramfs...
  [    1.950954] Initramfs unpacking failed: junk in compressed archive

  or 
  [    1.943484] Unpacking initramfs...
  [    1.946991] Initramfs unpacking failed: broken padding

grub.cfg 是这样的:(请记住它在 UEFI PXE 引导中运行良好):

  set default=0
  set timeout=1
  menuentry boot_liveos {
    linux (http,100.0.0.101)/liveos/focal/kernel nomodeset ro root=squash:http://100.0.0.101/liveos/focal/squashfs ip=::::hostname:BOOTIF ip6=off overlayroot=tmpfs overlayroot_cfgdisk=disabled apparmor=0 ds=nocloud console=tty0 console=ttyS1,115200 BOOTIF=01-${net_default_mac}
    initrd (http,100.0.0.101)/liveos/focal/initrd
  }

PS,不仅是focal,而且bionicjammy都是同样的问题。我还尝试了不同的 GRUB2 版本,从 20200320.0 到 20230222.0,都是同样的问题。

问题是“UefiHttpBoot/1.0”出了问题,它无法接受大数据。

编辑:如果这是固件问题,那么是否有办法告诉 grub 自行加载 http 文件?

编辑:继续调查该问题。

$ strings bootx64.efi |grep UefiHttpBoot
UefiHttpBoot/1.0
$ strings grubx64.efi |grep UefiHttpBoot
UefiHttpBoot/1.0

GRUB2 二进制文件嵌入了 http 代理字符串“UefiHttpBoot/1.0”,因此执行控制应该已传递给 GRUB2,只是 GRUB2 继续使用相同的代理字符串进行 http 访问。我已经检查了源代码http://archive.ubuntu.com/ubuntu/pool/main/g/grub2-unsigned/grub2-unsigned_2.06.orig.tar.xz(父页面是https://packages.ubuntu.com/focal-updates/grub-efi-amd64)但没有找到代理字符串,但找到了一个常量 var PACKAGE_STRING,可能是在构建二进制文件时定义的。

http_establish (struct grub_file *file, grub_off_t offset, int initial)
{
  http_data_t data = file->data;
  grub_uint8_t *ptr;
  int i;
  struct grub_net_buff *nb;
  grub_err_t err;

  nb = grub_netbuff_alloc (GRUB_NET_TCP_RESERVE_SIZE
               + sizeof ("GET ") - 1
               + grub_strlen (data->filename)
               + sizeof (" HTTP/1.1\r\nHost: ") - 1
               + grub_strlen (file->device->net->server)
               + sizeof ("\r\nUser-Agent: " PACKAGE_STRING
                     "\r\n") - 1
               + sizeof ("Range: bytes=XXXXXXXXXXXXXXXXXXXX"
                     "-\r\n\r\n"));
  if (!nb)
    return grub_errno;

在GRUB2的debian补丁源中找到http代理字符串:http://archive.ubuntu.com/ubuntu/pool/main/g/grub2-unsigned/grub2-unsigned_2.06-2ubuntu14.1.debian.tar.xz,是一个补丁文件suse-add-support-for-UEFI-network-protocols.patch,替换了一些http相关的功能来使用efi固件功能。

+static grub_err_t
+efihttp_request (grub_efi_http_t *http, char *server, char *name, int use_https, int headeronly, grub_off_t *file_size)
+{
+  grub_efi_http_request_data_t request_data;
+  grub_efi_http_message_t request_message;
+  grub_efi_http_token_t request_token;
+  grub_efi_http_response_data_t response_data;
+  grub_efi_http_message_t response_message;
+  grub_efi_http_token_t response_token;
+  grub_efi_http_header_t request_headers[3];
+
+  grub_efi_status_t status;
+  grub_efi_boot_services_t *b = grub_efi_system_table->boot_services;
+  char *url = NULL;
+
+  request_headers[0].field_name = (grub_efi_char8_t *)"Host";
+  request_headers[0].field_value = (grub_efi_char8_t *)server;
+  request_headers[1].field_name = (grub_efi_char8_t *)"Accept";
+  request_headers[1].field_value = (grub_efi_char8_t *)"*/*";
+  request_headers[2].field_name = (grub_efi_char8_t *)"User-Agent";
+  request_headers[2].field_value = (grub_efi_char8_t *)"UefiHttpBoot/1.0";
+
...

+  /* request token */
+  request_token.event = NULL;
+  request_token.status = GRUB_EFI_NOT_READY;
+  request_token.message = &request_message;
+
+  request_callback_done = 0;
+  status = efi_call_5 (b->create_event,
+                       GRUB_EFI_EVT_NOTIFY_SIGNAL,
+                       GRUB_EFI_TPL_CALLBACK,
+                       grub_efi_http_request_callback,
+                       NULL,
+                       &request_token.event);
+
+ 
...

编辑:快到了。我使用的 GRUB2 经过优化,使用底层 efi 固件功能来访问 http 文件。听起来不错,但它有几个缺点:

  • 看来确实很好地支持 url 查询字符串。
  • 它不能很好地处理大数据。我会将此事报告给 GRUB2 开发人员。事实证明,存在大量的 TCP 重传和窗口大小调整。

答案1

“UefiHttpBoot/1.0”HTTP 客户端确实在 UEFI 固件(UEFI 规范版本 2.5 或更高版本)中实现。

当使用 UEFI PXE 启动时,固件将bootx64.efi使用 TFTP 加载初始文件,因为这可能是固件可以自行执行的唯一网络文件传输协议。所以GRUB必须如果您想在这种情况下使用 HTTP,请携带其自己的 HTTP 客户端实现。

但快速grep浏览 GRUB 源代码树后,我找不到任何对 UEFI HTTP 协议的引用。 GRUB 的 UEFI 网络驱动程序,grub-core/net/drivers/efi/efinet.c仅依赖于早期 UEFI 规范版本的固件提供的简单网络协议。

所以在我看来,当使用 UEFI HTTP 引导时,GRUB 显然根本不知道它正在使用网络。 UEFI规范表示,通过HTTP引导,固件提供了支持UEFI LoadFile协议的UEFI设备句柄,因此GRUB可以使用它来加载文件,就像从磁盘加载文件一样。当然是GRUB可以进一步调查设备句柄以查看它实际上是一个网络设备 -但它不必这样做以完成其主要工作。

这也清楚地表明 UEFI 启动无法加载 initrd 肯定是固件问题。

答案2

让我自己来回答吧。

解决方案是找到一个较旧的 grubx64.efi ,它不包含单词UefiHttpBoot,幸运的是我20200320.0在本地历史中找到了一个版本,它运行良好。 (它似乎不再存在于 Ubuntu 的存储库中或在互联网上实现。)相应的 bootx64.efi 仍然包含 word UefiHttpBoot,但这并不重要,因为这只是一个填充程序,它只是加载 grubx64.efi 然后将控制权传递给它。

http访问是这样的:

"GET /bootx64.efi HTTP/1.1" 200 1335102 "-" "UefiHttpBoot/1.0"
"GET /grubx64.efi HTTP/1.1" 200 1164950 "-" "UefiHttpBoot/1.0"
...
"GET /grub/grub.cfg HTTP/1.1" 200 737 "-" "GRUB 2.02-2ubuntu8.15"
...
"GET /liveos/focal/kernel HTTP/1.1" 200 11780639 "-" "GRUB 2.02-2ubuntu8.15"
"GET /liveos/focal/initrd HTTP/1.1" 200 89168508 "-" "GRUB 2.02-2ubuntu8.15"
"GET /liveos/focal/squashfs HTTP/1.1" 200 388669747 "-" "Wget"
...

相关内容