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 85e01a9..3db1af8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,9 +37,10 @@ jobs: go-version: "1.23" cache: false - - name: make ipxe + - name: fake ipxe/bin for linter run: | - make ipxe + mkdir -p ipxe/ipxe/bin + touch ipxe/ipxe/bin/empty - name: Lint uses: golangci/golangci-lint-action@v6 diff --git a/ipxe/ipxe.go b/ipxe/ipxe.go index 24264b2..207cfdc 100644 --- a/ipxe/ipxe.go +++ b/ipxe/ipxe.go @@ -7,7 +7,6 @@ import ( ) // Files to embed get created during make ipxe -// nolint:typecheck // //go:embed ipxe/bin var payload embed.FS 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..2bd3f26 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 { @@ -33,24 +33,24 @@ func (s *Server) serveDHCP(conn *dhcp4.Conn) 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 } @@ -63,20 +63,25 @@ 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.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 } } 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..04c50f3 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 { @@ -49,11 +50,11 @@ func (s *Server) servePXE(conn net.PacketConn) 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 } @@ -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.String(), "addr", addr, "interface", intf.Name, "error", err) + continue + } } s.machineEvent(pkt.HardwareAddr, machineStatePXE, "Sent PXE configuration") @@ -75,7 +81,7 @@ func (s *Server) servePXE(conn net.PacketConn) 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 }