From af0139549cfd44024ae70f4e336afd11d7208e13 Mon Sep 17 00:00:00 2001 From: Robert Volkmann Date: Tue, 15 Apr 2025 09:52:34 +0200 Subject: [PATCH 1/6] feat: Add configurable server IP for DHCP responses Add the ability to configure a fixed source IP address for DHCP and PXE responses through a new --server-ip flag. This is particularly useful when network interfaces don't have IPv4 addresses configured but DHCP/PXE services still need to operate with a specific source IP. Changes include: - Add new ServerIP configuration option to Server struct - Implement --server-ip CLI flag with IPv4 validation - Modify DHCP and PXE services to use configured IP when available - Maintain backward compatibility by keeping auto-detection as default - Allow DHCP/PXE operation even when interfaces lack IPv4 addresses Example: pixiecore --server-ip 192.168.1.10 [other options] --- pixiecore/cli/cli.go | 23 ++++++++++++++++++++++- pixiecore/dhcp.go | 15 ++++++++++----- pixiecore/pixiecore.go | 10 +++++++--- pixiecore/pxe.go | 18 ++++++++++++------ 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/pixiecore/cli/cli.go b/pixiecore/cli/cli.go index 0bfbead..681f81c 100644 --- a/pixiecore/cli/cli.go +++ b/pixiecore/cli/cli.go @@ -18,11 +18,13 @@ package cli // import "github.com/metal-stack/pixie/cli" import ( "fmt" "log/slog" + "net" "os" - "github.com/metal-stack/pixie/pixiecore" "github.com/spf13/cobra" "github.com/spf13/viper" + + "github.com/metal-stack/pixie/pixiecore" ) // Ipxe is the set of ipxe binaries for supported firmware. @@ -63,6 +65,7 @@ func fatalf(msg string, args ...any) { func serverConfigFlags(cmd *cobra.Command) { cmd.Flags().BoolP("debug", "d", false, "Log more things that aren't directly related to booting a recognized client") cmd.Flags().StringP("listen-addr", "l", "0.0.0.0", "IPv4 address to listen on") + cmd.Flags().String("server-ip", "", "Source IP of the DHCP responses") cmd.Flags().IntP("port", "p", 80, "Port to listen on for HTTP") cmd.Flags().String("metrics-listen-addr", "0.0.0.0", "IPv4 address of the metrics server to listen on") cmd.Flags().Int("metrics-port", 2113, "Metrics server port") @@ -92,6 +95,10 @@ func serverFromFlags(cmd *cobra.Command) *pixiecore.Server { if err != nil { fatalf("Error reading flag: %s", err) } + serverIP, err := cmd.Flags().GetString("server-ip") + if err != nil { + fatalf("Error reading flag: %s", err) + } httpPort, err := cmd.Flags().GetInt("port") if err != nil { fatalf("Error reading flag: %s", err) @@ -142,6 +149,20 @@ func serverFromFlags(cmd *cobra.Command) *pixiecore.Server { MetricsAddress: metricsAddr, DHCPNoBind: dhcpNoBind, } + if serverIP != "" { + ip := net.ParseIP(serverIP) + if ip == nil { + fatalf("Error: serverIP %q is not a valid IP address", serverIP) + } + + // Check specifically for IPv4 address + ipv4 := ip.To4() + if ipv4 == nil { + fatalf("Error: serverIP %q is not a valid IPv4 address", serverIP) + } + + ret.ServerIP = &ipv4 + } for fwtype, bs := range Ipxe { ret.Ipxe[fwtype] = bs } diff --git a/pixiecore/dhcp.go b/pixiecore/dhcp.go index d58d940..1d963f1 100644 --- a/pixiecore/dhcp.go +++ b/pixiecore/dhcp.go @@ -22,7 +22,7 @@ import ( "github.com/metal-stack/pixie/dhcp4" ) -func (s *Server) serveDHCP(conn *dhcp4.Conn) error { +func (s *Server) serveDHCP(conn *dhcp4.Conn, fixedServerIP *net.IP) error { for { pkt, intf, err := conn.RecvDHCP() if err != nil { @@ -63,10 +63,15 @@ func (s *Server) serveDHCP(conn *dhcp4.Conn) error { } // Machine should be booted. - serverIP, err := interfaceIP(intf) - if err != nil { - s.Log.Info("Want to boot, but couldn't get a source address", "mac", pkt.HardwareAddr, "interface", intf.Name, "error", err) - continue + var serverIP net.IP + if fixedServerIP != nil { + serverIP = *fixedServerIP + } else { + serverIP, err = interfaceIP(intf) + if err != nil { + s.Log.Info("Want to boot, but couldn't get a source address", "mac", pkt.HardwareAddr, "interface", intf.Name, "error", err) + continue + } } resp, err := s.offerDHCP(pkt, mach, serverIP, fwtype) diff --git a/pixiecore/pixiecore.go b/pixiecore/pixiecore.go index 03cf717..711c23a 100644 --- a/pixiecore/pixiecore.go +++ b/pixiecore/pixiecore.go @@ -26,10 +26,11 @@ import ( "sync" "text/template" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/metal-stack/pixie/api" "github.com/metal-stack/pixie/dhcp4" "github.com/metal-stack/v" - "github.com/prometheus/client_golang/prometheus/promhttp" ) const ( @@ -174,6 +175,9 @@ type Server struct { // HTTPPort. HTTPStatusPort int + // Source IP of DHCP responses. + ServerIP *net.IP + // MetricsPort is the port of the metrics server. MetricsPort int // MetricsAddress is the bind address that the metrics server listens on. @@ -264,8 +268,8 @@ func (s *Server) Serve() error { s.Log.Debug("Starting Pixiecore goroutines", "version", v.V.String()) - go func() { s.errs <- s.serveDHCP(dhcp) }() - go func() { s.errs <- s.servePXE(pxe) }() + go func() { s.errs <- s.serveDHCP(dhcp, s.ServerIP) }() + go func() { s.errs <- s.servePXE(pxe, s.ServerIP) }() go func() { s.errs <- s.serveTFTP(tftpAddr) }() go func() { s.errs <- serveHTTP(http, s.serveHTTP) }() go func() { s.errs <- serveHTTP(metrics, s.serveMetrics) }() diff --git a/pixiecore/pxe.go b/pixiecore/pxe.go index 5658602..67f71b4 100644 --- a/pixiecore/pxe.go +++ b/pixiecore/pxe.go @@ -19,8 +19,9 @@ import ( "fmt" "net" - "github.com/metal-stack/pixie/dhcp4" "golang.org/x/net/ipv4" + + "github.com/metal-stack/pixie/dhcp4" ) // TODO: this may actually be the BINL protocol, a @@ -29,7 +30,7 @@ import ( // TianoCore EDK2 source code to figure out if what this is doing is // actually BINL, and if so rename everything. -func (s *Server) servePXE(conn net.PacketConn) error { +func (s *Server) servePXE(conn net.PacketConn, fixedServerIP *net.IP) error { buf := make([]byte, 1024) l := ipv4.NewPacketConn(conn) if err := l.SetControlMessage(ipv4.FlagInterface, true); err != nil { @@ -63,10 +64,15 @@ func (s *Server) servePXE(conn net.PacketConn) error { continue } - serverIP, err := interfaceIP(intf) - if err != nil { - s.Log.Info("Want to boot, but couldn't get a source address", "mac", pkt.HardwareAddr, "addr", addr, "interface", intf.Name, "error", err) - continue + var serverIP net.IP + if fixedServerIP != nil { + serverIP = *fixedServerIP + } else { + serverIP, err = interfaceIP(intf) + if err != nil { + s.Log.Info("Want to boot, but couldn't get a source address", "mac", pkt.HardwareAddr, "addr", addr, "interface", intf.Name, "error", err) + continue + } } s.machineEvent(pkt.HardwareAddr, machineStatePXE, "Sent PXE configuration") From ab87a46695c4f5e4aca4de8523d1594ce98f2ba3 Mon Sep 17 00:00:00 2001 From: Robert Volkmann Date: Tue, 15 Apr 2025 10:06:32 +0200 Subject: [PATCH 2/6] Stop building iPXE in the Github Action. --- .github/workflows/main.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 85e01a9..a70cd2b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,10 +37,6 @@ jobs: go-version: "1.23" cache: false - - name: make ipxe - run: | - make ipxe - - name: Lint uses: golangci/golangci-lint-action@v6 with: From 1cc0850345004c699178fcf3a2a75eda1a869f53 Mon Sep 17 00:00:00 2001 From: Robert Volkmann Date: Tue, 15 Apr 2025 10:13:27 +0200 Subject: [PATCH 3/6] Try to ignore ipxe/bin during lint --- ipxe/ipxe.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipxe/ipxe.go b/ipxe/ipxe.go index 24264b2..1f5232a 100644 --- a/ipxe/ipxe.go +++ b/ipxe/ipxe.go @@ -7,8 +7,8 @@ import ( ) // Files to embed get created during make ipxe -// nolint:typecheck // +//nolint:typecheck //go:embed ipxe/bin var payload embed.FS From 71dfc156a6b0be3209ac60c09a7c39951613c0f8 Mon Sep 17 00:00:00 2001 From: Robert Volkmann Date: Tue, 15 Apr 2025 11:02:17 +0200 Subject: [PATCH 4/6] Fake ipxe/bin --- .dockerignore | 1 + .github/workflows/main.yml | 5 +++++ ipxe/ipxe.go | 1 - 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 4cd265e..85eb42a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,3 +3,4 @@ .git *.md LICENSE +ipxe/ipxe/bin diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a70cd2b..3db1af8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,6 +37,11 @@ jobs: go-version: "1.23" cache: false + - name: fake ipxe/bin for linter + run: | + mkdir -p ipxe/ipxe/bin + touch ipxe/ipxe/bin/empty + - name: Lint uses: golangci/golangci-lint-action@v6 with: diff --git a/ipxe/ipxe.go b/ipxe/ipxe.go index 1f5232a..207cfdc 100644 --- a/ipxe/ipxe.go +++ b/ipxe/ipxe.go @@ -8,7 +8,6 @@ import ( // Files to embed get created during make ipxe // -//nolint:typecheck //go:embed ipxe/bin var payload embed.FS From 2d4d098c85fed9f35ef79e88cc600f94c544741d Mon Sep 17 00:00:00 2001 From: Robert Volkmann Date: Tue, 15 Apr 2025 15:41:59 +0200 Subject: [PATCH 5/6] Log MAC address human readable Before: dhcp":{"mac":"fMJV5Tr9","error":"packet is DHCPREQUEST, not DHCPDISCOVER"} --- pixiecore/dhcp.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pixiecore/dhcp.go b/pixiecore/dhcp.go index 1d963f1..2bd3f26 100644 --- a/pixiecore/dhcp.go +++ b/pixiecore/dhcp.go @@ -33,24 +33,24 @@ func (s *Server) serveDHCP(conn *dhcp4.Conn, fixedServerIP *net.IP) error { } if err = s.isBootDHCP(pkt); err != nil { - s.Log.Debug("Ignoring packet", "mac", pkt.HardwareAddr, "error", err) + s.Log.Debug("Ignoring packet", "mac", pkt.HardwareAddr.String(), "error", err) continue } mach, fwtype, err := s.validateDHCP(pkt) if err != nil { - s.Log.Info("Unusable packet", "mac", pkt.HardwareAddr, "error", err) + s.Log.Info("Unusable packet", "mac", pkt.HardwareAddr.String(), "error", err) continue } - s.Log.Debug("Got valid request to boot", "mac", mach.MAC, "guid", mach.GUID, "arch", mach.Arch) + s.Log.Debug("Got valid request to boot", "mac", mach.MAC.String(), "guid", mach.GUID, "arch", mach.Arch) spec, err := s.Booter.BootSpec(mach) if err != nil { - s.Log.Info("Couldn't get bootspec", "mac", pkt.HardwareAddr, "error", err) + s.Log.Info("Couldn't get bootspec", "mac", pkt.HardwareAddr.String(), "error", err) continue } if spec == nil { - s.Log.Debug("No boot spec, ignoring boot request", "mac", pkt.HardwareAddr) + s.Log.Debug("No boot spec, ignoring boot request", "mac", pkt.HardwareAddr.String()) s.machineEvent(pkt.HardwareAddr, machineStateIgnored, "Machine should not netboot") continue } @@ -69,19 +69,19 @@ func (s *Server) serveDHCP(conn *dhcp4.Conn, fixedServerIP *net.IP) error { } else { serverIP, err = interfaceIP(intf) if err != nil { - s.Log.Info("Want to boot, but couldn't get a source address", "mac", pkt.HardwareAddr, "interface", intf.Name, "error", err) + s.Log.Info("Want to boot, but couldn't get a source address", "mac", pkt.HardwareAddr.String(), "interface", intf.Name, "error", err) continue } } resp, err := s.offerDHCP(pkt, mach, serverIP, fwtype) if err != nil { - s.Log.Info("Failed to construct ProxyDHCP offer", "mac", pkt.HardwareAddr, "error", err) + s.Log.Info("Failed to construct ProxyDHCP offer", "mac", pkt.HardwareAddr.String(), "error", err) continue } if err = conn.SendDHCP(resp, intf); err != nil { - s.Log.Info("Failed to send ProxyDHCP offer", "mac", pkt.HardwareAddr, "error", err) + s.Log.Info("Failed to send ProxyDHCP offer", "mac", pkt.HardwareAddr.String(), "error", err) continue } } From 81dab4bb758aa4a605eb4d85519fa1b798ca186f Mon Sep 17 00:00:00 2001 From: Robert Volkmann Date: Tue, 15 Apr 2025 19:54:13 +0200 Subject: [PATCH 6/6] Human readable logging of mac address --- pixiecore/pxe.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pixiecore/pxe.go b/pixiecore/pxe.go index 67f71b4..04c50f3 100644 --- a/pixiecore/pxe.go +++ b/pixiecore/pxe.go @@ -50,11 +50,11 @@ func (s *Server) servePXE(conn net.PacketConn, fixedServerIP *net.IP) error { } if err = s.isBootDHCP(pkt); err != nil { - s.Log.Debug("Ignoring packet", "mac", pkt.HardwareAddr, "addr", addr, "error", err) + s.Log.Debug("Ignoring packet", "mac", pkt.HardwareAddr.String(), "addr", addr, "error", err) } fwtype, err := s.validatePXE(pkt) if err != nil { - s.Log.Info("Unusable packet", "mac", pkt.HardwareAddr, "addr", addr, "error", err) + s.Log.Info("Unusable packet", "mac", pkt.HardwareAddr.String(), "addr", addr, "error", err) continue } @@ -70,7 +70,7 @@ func (s *Server) servePXE(conn net.PacketConn, fixedServerIP *net.IP) error { } else { serverIP, err = interfaceIP(intf) if err != nil { - s.Log.Info("Want to boot, but couldn't get a source address", "mac", pkt.HardwareAddr, "addr", addr, "interface", intf.Name, "error", err) + s.Log.Info("Want to boot, but couldn't get a source address", "mac", pkt.HardwareAddr.String(), "addr", addr, "interface", intf.Name, "error", err) continue } } @@ -81,7 +81,7 @@ func (s *Server) servePXE(conn net.PacketConn, fixedServerIP *net.IP) error { bs, err := resp.Marshal() if err != nil { - s.Log.Info("Failed to marshal PXE offer", "mac", pkt.HardwareAddr, "addr", addr, "error", err) + s.Log.Info("Failed to marshal PXE offer", "mac", pkt.HardwareAddr.String(), "addr", addr, "error", err) continue }