diff --git a/README.md b/README.md index b9f46e6..bdd8d3f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/bootloop/main.go b/bootloop/main.go index 3a6259e..1ecdfa7 100644 --- a/bootloop/main.go +++ b/bootloop/main.go @@ -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{ @@ -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 @@ -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]) } @@ -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 { @@ -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) diff --git a/coresmd/tftp.go b/coresmd/tftp.go index dd0157a..c077ba1 100644 --- a/coresmd/tftp.go +++ b/coresmd/tftp.go @@ -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 @@ -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 { diff --git a/resources/config.example.yaml b/resources/config.example.yaml index fcccb3f..6d3d456 100644 --- a/resources/config.example.yaml +++ b/resources/config.example.yaml @@ -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 diff --git a/resources/reboot.ipxe b/resources/reboot.ipxe deleted file mode 100644 index 3adf24b..0000000 --- a/resources/reboot.ipxe +++ /dev/null @@ -1,2 +0,0 @@ -#!ipxe -reboot