Skip to content

Commit

Permalink
Merge pull request #9 from OpenCHAMI/synackd/custom-script
Browse files Browse the repository at this point in the history
bootloop: Add built-in reboot script, ability to specify alternate script
  • Loading branch information
synackd authored Nov 26, 2024
2 parents 4752755 + 9c41812 commit 6c2d23d
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 29 deletions.
23 changes: 10 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,19 +209,16 @@ configured in the CoreDHCP config file.

### Preparation: TFTP

Neither CoreDHCP nor this plugin provide TFTP capability, so a separate TFTP
server is required to be running[^tftp]. The IP address that this server listens
on should match the `server_id` directive in the CoreDHCP config file. This
server should serve the following files:

- `reboot.ipxe` --- This file is located `resources/` in this repository.
- `ipxe.efi` --- The iPXE x86\_64 EFI bootloader. This can be found
[here](https://boot.ipxe.org/ipxe.efi).
- `undionly.kpxe` --- The iPXE x86 legacy bootloader. This can be found
[here](https://boot.ipxe.org/undionly.kpxe).

[^tftp]: [Here](https://github.com/aguslr/docker-atftpd) is one that is easy to
get running.
With default configuration, no preparation is needed.

Coresmd comes with a built-in TFTP server that includes iPXE bootloader binaries
for 32-/64-bit x86/ARM (EFI) and legacy x86 CPU architectures.

When using the bootloop plugin, if the boot script path is set to "default" (see
example config file), then the built-in reboot iPXE script is used for unknown
nodes. This can be changed to a path in TFTP to an alternate custom iPXE boot
script if different functionality is desired. Of course, whatever path is
specified must exist on the TFTP server.

### Running CoreDHCP

Expand Down
28 changes: 18 additions & 10 deletions bootloop/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ type PluginState struct {
var log = logger.GetLogger("plugins/bootloop")

var (
ipv4Start net.IP
ipv4End net.IP
ipv4Range int
p PluginState
ipv4Start net.IP
ipv4End net.IP
ipv4Range int
p PluginState
scriptPath string
)

var Plugin = plugins.Plugin{
Expand All @@ -61,8 +62,8 @@ func setup4(args ...string) (handler.Handler4, error) {
log.Infof("initializing coresmd/bootloop %s (%s), built %s", version.Version, version.GitCommit, version.BuildTime)

// Ensure all required args were passed
if len(args) != 4 {
return nil, fmt.Errorf("wanted 4 arguments (file name, lease duration, IPv4 range start, IPv4 range end), got %d", len(args))
if len(args) != 5 {
return nil, fmt.Errorf("wanted 5 arguments (file name, iPXE script path, lease duration, IPv4 range start, IPv4 range end), got %d", len(args))
}
var err error

Expand All @@ -73,20 +74,26 @@ func setup4(args ...string) (handler.Handler4, error) {
return nil, fmt.Errorf("file path cannot be empty")
}

// Parse boot script path
scriptPath = args[1]
if filename == "" {
return nil, fmt.Errorf("script path cannot be empty; use 'default' if unsure")
}

// Parse short lease duration
p.LeaseTime, err = time.ParseDuration(args[1])
p.LeaseTime, err = time.ParseDuration(args[2])
if err != nil {
return nil, fmt.Errorf("failed to parse short lease duration %q: %w", args[0], err)
}

// Parse start IP
ipv4Start := net.ParseIP(args[2])
ipv4Start := net.ParseIP(args[3])
if ipv4Start.To4() == nil {
return nil, fmt.Errorf("invalid IPv4 address for range start: %s", args[1])
}

// Parse end IP
ipv4End := net.ParseIP(args[3])
ipv4End := net.ParseIP(args[4])
if ipv4End.To4() == nil {
return nil, fmt.Errorf("invalid IPv4 address for range end: %s", args[2])
}
Expand Down Expand Up @@ -173,7 +180,7 @@ func (p *PluginState) Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool)
} else {
if string(cinfo) == "iPXE" {
// BOOT STAGE 2: Send URL to BSS boot script
resp.Options.Update(dhcpv4.OptBootFileName("reboot.ipxe"))
resp.Options.Update(dhcpv4.OptBootFileName(scriptPath))
resp.YourIPAddr = record.IP
resp.Options.Update(dhcpv4.OptIPAddressLeaseTime(p.LeaseTime.Round(time.Second)))
} else {
Expand All @@ -186,6 +193,7 @@ func (p *PluginState) Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool)
dhcpv4.WithMessageType(dhcpv4.MessageTypeNak),
dhcpv4.WithTransactionID(req.TransactionID),
dhcpv4.WithHwAddr(req.ClientHWAddr),
dhcpv4.WithServerIP(resp.ServerIPAddr),
)
if err != nil {
log.Errorf("failed to create new %s message: %w", dhcpv4.MessageTypeNak, err)
Expand Down
18 changes: 18 additions & 0 deletions coresmd/tftp.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ import (
"github.com/pin/tftp"
)

const defaultScriptName = "default"

var defaultScript = `#!ipxe
reboot
`

type ScriptReader struct{}

func (sr ScriptReader) Read(b []byte) (int, error) {
nBytes := copy(b, []byte(defaultScript))
return nBytes, io.EOF
}

func startTFTPServer(directory string) {
s := tftp.NewServer(readHandler(directory), nil)
err := s.ListenAndServe(":69") // default TFTP port
Expand All @@ -18,6 +31,11 @@ func startTFTPServer(directory string) {

func readHandler(directory string) func(string, io.ReaderFrom) error {
return func(filename string, rf io.ReaderFrom) error {
if filename == defaultScriptName {
var sr ScriptReader
_, err := rf.ReadFrom(sr)
return err
}
filePath := filepath.Join(directory, filename)
file, err := os.Open(filePath)
if err != nil {
Expand Down
12 changes: 8 additions & 4 deletions resources/config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,12 @@ server4:
# ARGUMENTS:
# 1. Path to database file that keeps track of leased IPs. This will be
# created if it does not already exist.
# 2. Lease duration.
# 3. IP address beginning range.
# 4. IP address ending range.
# 2. Path of iPXE to use. If using the built-in coresmd TFTP server, if
# this argument is 'default', the default reboot.ipxe script is used.
# This should really only be used if it is desired to do something
# custom instead of rebooting.
# 3. Lease duration.
# 4. IP address beginning range.
# 5. IP address ending range.
#
- bootloop: /tmp/coredhcp.db 5m 172.16.0.156 172.16.0.200
- bootloop: /tmp/coredhcp.db default 5m 172.16.0.156 172.16.0.200
2 changes: 0 additions & 2 deletions resources/reboot.ipxe

This file was deleted.

0 comments on commit 6c2d23d

Please sign in to comment.