之前遇到一个问题,虽然最后的原因跟grub没关系,不过在分析case的过程中看了下grub的相关实现,也借此记录一下。

grub启动流程

grub启动的总体流程入口:grub-core/kern/main.c:grub_main,跟本case相关的启动过程主要有:

  1. 设置root和prefix变量。
    • 对EFI环境,增加了fw_path变量[*],用于替代prefix变量。fw_path被设置为grub二进制所在的路径。
  2. 进入normal模式后,依次尝试使用fw_path和prefix搜索配置文件,直至找到一个配置文件[*]。搜索的逻辑为:
    • 对应实现在:grub-core/net/net.c:grub_net_search_configfile。会依次尝试以下文件: 假设MAC地址为aa:bb:cc:dd:ee:ff,IP地址为10.20.0.6,IP对应的十六进制为0A140006
      $path/grub.cfg-01-aa-bb-cc-dd-ee-ff
      $path/grub.cfg-01-aa-bb-cc-dd-ee-ff
      $path/grub.cfg-0A140006
      $path/grub.cfg-0A14000
      $path/grub.cfg-0A1400
      $path/grub.cfg-0A140
      $path/grub.cfg-0A14
      $path/grub.cfg-0A1
      $path/grub.cfg-0A
      $path/grub.cfg-0
      $path/grub.cfg
      
    • 通过网络访问文件时,有重试,默认重试次数为#define GRUB_NET_TRIES 40
  3. 如果没找到配置文件,失败进入grub命令行,否则
  4. 读取配置文件,并根据配置文件启动。

有一些逻辑不是在grub的上游代码中,而是在redhat系srpm的patch中提供的,包括:

  1. fw_path相关的逻辑,参考https://fedoraproject.org/wiki/Changes/UnifyGrubConfig
  2. 根据MAC和IP地址搜索grub.cfg的逻辑。

case study

问题描述

现象:通过PXE启动的节点偶尔出现下载配置文件失败,进入grub命令行。

从tftp侧的日志看,正常的情况下,固件下载grubaa64.efi后,grubaa64.efi启动,下载配置文件grub.cfg-01-<mac>

Jan 30 05:02:13 node-1 in.tftpd[11461]: RRQ from 10.20.0.5 filename grub/grubaa64.efi
Jan 30 05:02:13 node-1 in.tftpd[11461]: Client 10.20.0.5 finished grub/grubaa64.efi
Jan 30 05:02:13 node-1 in.tftpd[11463]: RRQ from 10.20.0.5 filename grub/grub.cfg-01-fa-16-3e-e5-d0-3d
Jan 30 05:02:13 node-1 in.tftpd[11463]: Client 10.20.0.5 finished grub/grub.cfg-01-fa-16-3e-e5-d0-3d

出问题的情况下,尝试下载了很多文件。

Jan 30 05:17:40 node-1 in.tftpd[11936]: RRQ from 10.20.0.6 filename grub/grubaa64.efi
Jan 30 05:17:40 node-1 in.tftpd[11936]: Client 10.20.0.6 finished grub/grubaa64.efi
Jan 30 05:18:12 node-1 in.tftpd[11985]: RRQ from 10.20.0.6 filename /EFI/BOOT/grub.cfg-01-fa-16-3e-d3-86-f6
Jan 30 05:18:12 node-1 in.tftpd[11985]: Client 10.20.0.6 File not found /EFI/BOOT/grub.cfg-01-fa-16-3e-d3-86-f6
Jan 30 05:18:12 node-1 in.tftpd[11986]: RRQ from 10.20.0.6 filename /EFI/BOOT/grub.cfg-0A140006
Jan 30 05:18:12 node-1 in.tftpd[11986]: Client 10.20.0.6 File not found /EFI/BOOT/grub.cfg-0A140006
Jan 30 05:18:12 node-1 in.tftpd[11987]: RRQ from 10.20.0.6 filename /EFI/BOOT/grub.cfg-0A14000
Jan 30 05:18:12 node-1 in.tftpd[11987]: Client 10.20.0.6 File not found /EFI/BOOT/grub.cfg-0A14000
Jan 30 05:18:12 node-1 in.tftpd[11988]: RRQ from 10.20.0.6 filename /EFI/BOOT/grub.cfg-0A1400
Jan 30 05:18:12 node-1 in.tftpd[11988]: Client 10.20.0.6 File not found /EFI/BOOT/grub.cfg-0A1400
Jan 30 05:18:12 node-1 in.tftpd[11989]: RRQ from 10.20.0.6 filename /EFI/BOOT/grub.cfg-0A140
Jan 30 05:18:12 node-1 in.tftpd[11989]: Client 10.20.0.6 File not found /EFI/BOOT/grub.cfg-0A140
Jan 30 05:18:12 node-1 in.tftpd[11990]: RRQ from 10.20.0.6 filename /EFI/BOOT/grub.cfg-0A14
Jan 30 05:18:12 node-1 in.tftpd[11990]: Client 10.20.0.6 File not found /EFI/BOOT/grub.cfg-0A14
Jan 30 05:18:12 node-1 in.tftpd[11991]: RRQ from 10.20.0.6 filename /EFI/BOOT/grub.cfg-0A1
Jan 30 05:18:12 node-1 in.tftpd[11991]: Client 10.20.0.6 File not found /EFI/BOOT/grub.cfg-0A1
Jan 30 05:18:12 node-1 in.tftpd[11992]: RRQ from 10.20.0.6 filename /EFI/BOOT/grub.cfg-0A
Jan 30 05:18:12 node-1 in.tftpd[11992]: Client 10.20.0.6 File not found /EFI/BOOT/grub.cfg-0A
Jan 30 05:18:12 node-1 in.tftpd[11993]: RRQ from 10.20.0.6 filename /EFI/BOOT/grub.cfg-0
Jan 30 05:18:12 node-1 in.tftpd[11993]: Client 10.20.0.6 File not found /EFI/BOOT/grub.cfg-0
Jan 30 05:18:12 node-1 in.tftpd[11994]: RRQ from 10.20.0.6 filename /EFI/BOOT/grub.cfg
Jan 30 05:18:12 node-1 in.tftpd[11994]: Client 10.20.0.6 File not found /EFI/BOOT/grub.cfg

问题分析

正常的情况下,下载grub/grub.cfg-01-fa-16-3e-e5-d0-3d就是上面流程中搜索grub文件时,尝试$fw_path/grub.cfg-01-<mac>,成功之后流程就往下走了。

异常的情况下,日志记录第一个下载的文件是/EFI/BOOT/grub.cfg-01-fa-16-3e-d3-86-f6EFI/BOOT其实是$prefix的值,也就是尝试$fw_path失败之后开始尝试prefix下的文件。

其中有几个点值得注意:

  1. 下载完grubaa64.efi之后,每次异常情况都是经过大概32秒的时间下载/EFI/BOOT/grub.cfg-01-fa-16-3e-d3-86-f6。从这点可以基本排除随机的网络问题,因为不应该每次都这么精准地卡点。
  2. 猜测上条中间隔的32秒,grub在尝试$fw_path下的文件,根据上面的分析,grub会在$fw_path下根据MAC和IP尝试多个文件,但是从日志来看,每次出问题都是完全没有$fw_path相关的日志。
    • 后来看代码发现,这块grub实现上确实有小bug,在尝试$fw_path/grub.cfg-01-<mac>失败之后,在特定的代码路径下,会跳过$fw_path的后续尝试。

问题的原因后来定位到是跟tftp server所在节点的网络配置相关。grub在tftp下载前需要通过arp协议获取MAC地址。而tftp server的rp_filter被设置为1,导致所有网卡都会以自己的mac地址reply。 然而节点上的iptables规则禁止了某个网卡的请求,导致如果grub发送tftp请求使用的是该网卡的mac地址,就会失败。

Reference

  • grub2 source
  • grub2 srpm (grub2-2.02-99)