Skip to content

Commit

Permalink
Add pxe boot.
Browse files Browse the repository at this point in the history
  • Loading branch information
zhaohui-yan committed Dec 24, 2024
1 parent 9ea83ba commit 091d325
Show file tree
Hide file tree
Showing 4 changed files with 313 additions and 3 deletions.
9 changes: 6 additions & 3 deletions scripts/envsetup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1335,9 +1335,12 @@ function build_rv_uroot()
pushd $RV_UROOT_DIR
GOARCH=riscv64 go build
GOOS=linux GOARCH=riscv64 $RV_UROOT_DIR/u-root -uroot-source $RV_UROOT_DIR -build bb \
-uinitcmd="boot" -files ../busybox/busybox:bin/busybox -o $RV_UROOT_DIR/initramfs.cpio \
-files "$RV_UROOT_DIR/firmware/:lib/firmware/" \
core boot
-uinitcmd="/init-boot.sh" \
-files ./cmds/init/init-boot.sh:init-boot.sh \
-files ../busybox/busybox:bin/busybox \
-o $RV_UROOT_DIR/initramfs.cpio \
-files "$RV_UROOT_DIR/firmware/:lib/firmware/" \
core boot ./cmds/boot/multiboot
popd
cp $RV_UROOT_DIR/initramfs.cpio $RV_FIRMWARE_INSTALL_DIR/initrd.img
}
Expand Down
274 changes: 274 additions & 0 deletions u-root/cmds/boot/multiboot/multiboot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
// Copyright 2017-2020 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Command bootpxe implements PXE-based booting followed by local booting.

package main

import (
"context"
"flag"
"fmt"
"log"
"net"
"strings"
"time"

"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/insomniacslk/dhcp/iana"
"github.com/u-root/u-root/pkg/boot"
"github.com/u-root/u-root/pkg/boot/bootcmd"
"github.com/u-root/u-root/pkg/boot/localboot"
"github.com/u-root/u-root/pkg/boot/menu"
"github.com/u-root/u-root/pkg/boot/netboot"
"github.com/u-root/u-root/pkg/cmdline"
"github.com/u-root/u-root/pkg/curl"
"github.com/u-root/u-root/pkg/dhclient"
"github.com/u-root/u-root/pkg/ipmi"
"github.com/u-root/u-root/pkg/ipmi/ocp"
"github.com/u-root/u-root/pkg/mount"
"github.com/u-root/u-root/pkg/mount/block"
"github.com/u-root/u-root/pkg/sh"
"github.com/u-root/u-root/pkg/ulog"
)

var (
ifName = "^e.*"
noLoad = flag.Bool("no-load", false, "get DHCP response, print chosen boot configuration, but do not download + exec it")
noExec = flag.Bool("no-exec", false, "download boot configuration, but do not exec it")
noNetConfig = flag.Bool("no-net-config", false, "get DHCP response, but do not apply the network config it to the kernel interface")
skipBonded = flag.Bool("skip-bonded", false, "Skip NICs that have already been added to a bond")
verbose = flag.Bool("v", false, "Verbose output")
ipv4 = flag.Bool("ipv4", true, "use IPV4")
ipv6 = flag.Bool("ipv6", true, "use IPV6")
cmdAppend = flag.String("cmd", "", "Kernel command to append for each image")
bootfile = flag.String("file", "", "Boot file name (default tftp) or full URI to use instead of DHCP.")
server = flag.String("server", "0.0.0.0", "Server IP (Requires -file for effect)")
)

const (
dhcpTimeout = 5 * time.Second
dhcpTries = 3
)

func NetbootImages(ifaceNames string) ([]boot.OSImage, error) {
filteredIfs, err := dhclient.Interfaces(ifaceNames)
if err != nil {
return nil, err
}

if *skipBonded {
filteredIfs = dhclient.FilterBondedInterfaces(filteredIfs, *verbose)
}

ctx, cancel := context.WithTimeout(context.Background(), (1<<dhcpTries)*dhcpTimeout)
defer cancel()
var modifiers []dhcpv4.Modifier
option93 := dhcpv4.OptClientArch(iana.EFI_RISCV64_HTTP)
modifiers = append(modifiers, dhcpv4.WithOption(option93))
c := dhclient.Config{
Timeout: dhcpTimeout,
Retries: dhcpTries,
Modifiers4: modifiers,
}
if *verbose {
c.LogLevel = dhclient.LogSummary
}
r := dhclient.SendRequests(ctx, filteredIfs, *ipv4, *ipv6, c, 30*time.Second)

for {
select {
case <-ctx.Done():
return nil, ctx.Err()

case result, ok := <-r:
if !ok {
return nil, fmt.Errorf("nothing bootable found, all interfaces are configured or timed out")
}
iname := result.Interface.Attrs().Name
if result.Err != nil {
log.Printf("Could not configure %s for %s: %v", iname, result.Protocol, result.Err)
continue
}

if *noNetConfig {
log.Printf("Skipping configuring %s with lease %s", iname, result.Lease)
} else if err := result.Lease.Configure(); err != nil {
log.Printf("Failed to configure lease %s: %v", result.Lease, err)
}

imgs, err := netboot.BootImages(context.Background(), ulog.Log, curl.DefaultSchemes, result.Lease)
if err != nil {
log.Printf("Failed to boot lease %v: %v", result.Lease, err)
continue
}

return imgs, nil
}
}
}

func newManualLease() (dhclient.Lease, error) {
filteredIfs, err := dhclient.Interfaces(ifName)
if err != nil {
return nil, err
}

d, err := dhcpv4.New()
if err != nil {
return nil, err
}

d.BootFileName = *bootfile
d.ServerIPAddr = net.ParseIP(*server)

return dhclient.NewPacket4(filteredIfs[0], d), nil
}

func dumpNetDebugInfo() {
log.Println("Dump debug info of network status")
commands := []string{"ip link", "ip addr", "ip route show table all", "ip -6 route show table all", "ip neigh"}
for _, cmd := range commands {
cmds := strings.Split(cmd, " ")
name := cmds[0]
args := cmds[1:]
sh.RunWithLogs(name, args...)
}
}

func updateBootCmdline(cl string) string {
f := cmdline.NewUpdateFilter(*cmdAppend, []string{}, []string{})
return f.Update(cl)
}

func localBoot() {
log.Printf("local_boot....")
blockDevs, err := block.GetBlockDevices()
if err != nil {
log.Fatal("No available block devices to boot from")
}

blockDevs = blockDevs.FilterZeroSize()

log.Printf("Booting from the following block devices: %v", blockDevs)

var l ulog.Logger = ulog.Null
if *verbose {
l = ulog.Log
}
mountPool := &mount.Pool{}
images, err := localboot.Localboot(l, blockDevs, mountPool)
if err != nil {
log.Fatal(err)
}
for _, img := range images {
if li, ok := img.(*boot.LinuxImage); ok {
li.Cmdline = updateBootCmdline(li.Cmdline)
}
}

menuEntries := menu.OSImages(*verbose, images...)
menuEntries = append(menuEntries, menu.Reboot{})
menuEntries = append(menuEntries, menu.StartShell{})

bootcmd.ShowMenuAndBoot(menuEntries, mountPool, *noLoad, *noExec)
}

func getBootOrderViaIPMI() (byte, error) {
var bootType byte
i, err := ipmi.Open(0)
if err != nil {
log.Printf("Failed to open ipmi device %v, watchdog may still be running", err)
return ocp.LOCAL_BOOT, err
}
defer i.Close()

bootType, err = ocp.GetBootOrder(i)

if err != nil {
log.Printf("Failed to get bootOrder!")
return ocp.LOCAL_BOOT, err
} else {
log.Printf("bootType %d.", bootType)
return bootType, nil
}
}

func setLocalBootViaIPMI() error {
i, err := ipmi.Open(0)
if err != nil {
log.Printf("Failed to open ipmi device %v, watchdog may still be running", err)
return err
}
defer i.Close()
err = ocp.SetLocalBootOrder(i)
if err != nil {
log.Printf("Failed to set local_boot!")
return err
}
log.Printf("Set local_boot!")
return nil
}

func main() {
bootType, err := getBootOrderViaIPMI()
if err != nil {
localBoot()
} else {
if bootType == ocp.NETWORK_BOOT {
//pxe_boot
log.Printf("Try to boot from network.")
flag.Parse()
if len(flag.Args()) > 1 {
log.Fatalf("Only one regexp-style argument is allowed, e.g.: " + ifName)
}
if len(flag.Args()) > 0 {
ifName = flag.Args()[0]
}

var images []boot.OSImage
var err error
maxRetries := 2
for attempts := 0; attempts < maxRetries; attempts++ {
if *bootfile == "" {
images, err = NetbootImages(ifName)
if err != nil {
log.Printf("PXE boot failed: %v", err)
dumpNetDebugInfo()
} else {
break
}
} else {
log.Printf("Skipping DHCP for manual target..")
var l dhclient.Lease
l, err = newManualLease()
if err == nil {
images, err = netboot.BootImages(context.Background(), ulog.Log, curl.DefaultSchemes, l)
break
} else {
log.Printf("PXE Manual boot failed: %v", err)
}
}
}
setLocalBootViaIPMI()
if err == nil {
for _, img := range images {
img.Edit(func(cmdline string) string {
return cmdline + " " + *cmdAppend
})
}

menuEntries := menu.OSImages(*verbose, images...)
menuEntries = append(menuEntries, menu.Reboot{})
menuEntries = append(menuEntries, menu.StartShell{})
bootcmd.ShowMenuAndBoot(menuEntries, nil, *noLoad, *noExec)
} else {
localBoot()
}
} else {
localBoot()
}
}

}
5 changes: 5 additions & 0 deletions u-root/cmds/init/init-boot.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh
# Read the random number first to avoid dhcp using random number timeout
dd if=/dev/urandom of=stdout bs=4 count=1
# pxeboot or localboot
multiboot
28 changes: 28 additions & 0 deletions u-root/pkg/ipmi/ocp/bootorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,34 @@ func getBootOrder(i *ipmi.IPMI, BootOrder *BootOrder) error {
return nil
}

func GetBootOrder(i *ipmi.IPMI) (byte, error) {
var bootType byte
recv, err := i.SendRecv(_IPMI_FB_OEM_NET_FUNCTION1, _FB_OEM_GET_BIOS_BOOT_ORDER, nil)
if err != nil {
return LOCAL_BOOT, err
}
bootType = recv[1]
return bootType, nil
}

func SetLocalBootOrder(i *ipmi.IPMI) error {
var BMCBootOrder BootOrder
BMCBootOrder.bootMode = LOCAL_BOOT
BMCBootOrder.bootSeq[0] = NETWORK_BOOT
BMCBootOrder.bootSeq[1] = LOCAL_BOOT
msg := ipmi.Msg{
Netfn: _IPMI_FB_OEM_NET_FUNCTION1,
Cmd: _FB_OEM_SET_BIOS_BOOT_ORDER,
Data: unsafe.Pointer(&BMCBootOrder),
DataLen: 6,
}

if _, err := i.RawSendRecv(msg); err != nil {
return err
}
return nil
}

func setBootOrder(i *ipmi.IPMI, BootOrder *BootOrder) error {
msg := ipmi.Msg{
Netfn: _IPMI_FB_OEM_NET_FUNCTION1,
Expand Down

0 comments on commit 091d325

Please sign in to comment.