func ServeProxyDHCP(port int, booter api.Booter) error { conn, err := net.ListenPacket("udp4", fmt.Sprintf(":%d", port)) if err != nil { return err } defer conn.Close() l := ipv4.NewPacketConn(conn) if err = l.SetControlMessage(ipv4.FlagInterface, true); err != nil { return err } log.Log("ProxyDHCP", "Listening on port %d", port) buf := make([]byte, 1024) for { n, msg, addr, err := l.ReadFrom(buf) if err != nil { log.Log("ProxyDHCP", "Error reading from socket: %s", err) continue } udpAddr := addr.(*net.UDPAddr) udpAddr.IP = net.IPv4bcast req, err := ParseDHCP(buf[:n]) if err != nil { log.Debug("ProxyDHCP", "ParseDHCP: %s", err) continue } if err = booter.ShouldBoot(req.MAC); err != nil { log.Debug("ProxyDHCP", "Not offering to boot %s: %s", req.MAC, err) continue } req.ServerIP, err = InterfaceIP(msg.IfIndex) if err != nil { log.Log("ProxyDHCP", "Couldn't find an IP address to use to reply to %s: %s", req.MAC, err) continue } log.Log("ProxyDHCP", "Offering to boot %s (via %s)", req.MAC, req.ServerIP) if _, err := l.WriteTo(OfferDHCP(req), &ipv4.ControlMessage{ IfIndex: msg.IfIndex, }, udpAddr); err != nil { log.Log("ProxyDHCP", "Responding to %s: %s", req.MAC, err) continue } } }
func ServePXE(pxePort, httpPort int) error { conn, err := net.ListenPacket("udp4", fmt.Sprintf(":%d", pxePort)) if err != nil { return err } defer conn.Close() l := ipv4.NewPacketConn(conn) if err = l.SetControlMessage(ipv4.FlagInterface, true); err != nil { return err } log.Log("PXE", "Listening on port %d", pxePort) buf := make([]byte, 1024) for { n, msg, addr, err := l.ReadFrom(buf) if err != nil { log.Log("PXE", "Error reading from socket: %s", err) continue } req, err := ParsePXE(buf[:n]) if err != nil { log.Debug("PXE", "ParsePXE: %s", err) continue } req.ServerIP, err = dhcp.InterfaceIP(msg.IfIndex) if err != nil { log.Log("PXE", "Couldn't find an IP address to use to reply to %s: %s", req.MAC, err) continue } req.HTTPServer = fmt.Sprintf("http://%s:%d/", req.ServerIP, httpPort) log.Log("PXE", "Chainloading %s (%s) to pxelinux (via %s)", req.MAC, req.ClientIP, req.ServerIP) if _, err := l.WriteTo(ReplyPXE(req), &ipv4.ControlMessage{ IfIndex: msg.IfIndex, }, addr); err != nil { log.Log("PXE", "Responding to %s: %s", req.MAC, err) continue } } }
func (s *httpServer) File(w http.ResponseWriter, r *http.Request) { encodedID := filepath.Base(r.URL.Path) id, err := base64.URLEncoding.DecodeString(encodedID) if err != nil { log.Log("http", "Bad base64 encoding for URL %q from %s: %s", r.URL, r.RemoteAddr, err) http.Error(w, "Malformed file ID", http.StatusBadRequest) return } f, pretty, err := s.booter.File(string(id)) if err != nil { log.Log("HTTP", "Couldn't get byte stream for %q from %s: %s", r.URL, r.RemoteAddr, err) http.Error(w, "Couldn't get byte stream", http.StatusInternalServerError) return } defer f.Close() w.Header().Set("Content-Type", "application/octet-stream") written, err := io.Copy(w, f) if err != nil { log.Log("HTTP", "Error serving %s to %s: %s", pretty, r.RemoteAddr, err) return } log.Log("HTTP", "Sent %s to %s (%d bytes)", pretty, r.RemoteAddr, written) }
func ServeHTTP(port int, booter api.Booter, ldlinux []byte) error { s := &httpServer{ booter: booter, ldlinux: ldlinux, } if _, err := io.ReadFull(rand.Reader, s.key[:]); err != nil { return fmt.Errorf("cannot initialize ephemeral signing key: %s", err) } http.HandleFunc("/ldlinux.c32", s.Ldlinux) http.HandleFunc("/pxelinux.cfg/", s.PxelinuxConfig) http.HandleFunc("/f/", s.File) log.Log("HTTP", "Listening on port %d", port) return http.ListenAndServe(fmt.Sprintf(":%d", port), nil) }
func (s *httpServer) PxelinuxConfig(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") macStr := filepath.Base(r.URL.Path) errStr := fmt.Sprintf("%s requested a pxelinux config from URL %q, which does not include a MAC address", r.RemoteAddr, r.URL) if !strings.HasPrefix(macStr, "01-") { log.Debug("HTTP", errStr) http.Error(w, "Missing MAC address in request", http.StatusBadRequest) return } mac, err := net.ParseMAC(macStr[3:]) if err != nil { log.Debug("HTTP", errStr) http.Error(w, "Malformed MAC address in request", http.StatusBadRequest) return } spec, err := s.booter.BootSpec(mac) if err != nil { // We have a machine sitting in pxelinux, but the Booter says // we shouldn't be netbooting. So, give it a config that tells // pxelinux to shut down PXE booting and continue with the // next local boot method. log.Debug("HTTP", "Telling pxelinux on %s (%s) to boot from disk because of API server verdict: %s", mac, r.RemoteAddr, err) w.Write([]byte(bootFromDisk)) return } // The file IDs can be arbitrary blobs that make sense to the // Booter, but pxelinux speaks URL, so we need to encode the // blobs. spec.Kernel = "f/" + base64.URLEncoding.EncodeToString([]byte(spec.Kernel)) for i := range spec.Initrd { spec.Initrd[i] = "f/" + base64.URLEncoding.EncodeToString([]byte(spec.Initrd[i])) } cfg := fmt.Sprintf(` SAY %s DEFAULT linux LABEL linux LINUX %s APPEND initrd=%s %s `, strings.Replace(limerick, "\n", "\nSAY ", -1), spec.Kernel, strings.Join(spec.Initrd, ","), spec.Cmdline) w.Write([]byte(cfg)) log.Log("HTTP", "Sent pxelinux config to %s (%s)", mac, r.RemoteAddr) }
func main() { flag.Parse() booter, err := pickBooter() if err != nil { flag.Usage() fmt.Fprintf(os.Stderr, "\nERROR: %s\n", err) os.Exit(1) } pxelinux, err := assets.Asset("lpxelinux.0") if err != nil { fmt.Println(err) os.Exit(1) } ldlinux, err := assets.Asset("ldlinux.c32") if err != nil { fmt.Println(err) os.Exit(1) } go func() { log.Fatalln(dhcp.ServeProxyDHCP(*portDHCP, booter)) }() go func() { log.Fatalln(pxe.ServePXE(*portPXE, *portHTTP)) }() go func() { tftp.Log = func(msg string, args ...interface{}) { pixiecorelog.Log("TFTP", msg, args...) } tftp.Debug = func(msg string, args ...interface{}) { pixiecorelog.Debug("TFTP", msg, args...) } log.Fatalln(tftp.ListenAndServe("udp4", ":"+strconv.Itoa(*portTFTP), tftp.Blob(pxelinux))) }() go func() { log.Fatalln(http.ServeHTTP(*portHTTP, booter, ldlinux)) }() pixiecorelog.RecordLogs(*debug) }
func (s *httpServer) Ldlinux(w http.ResponseWriter, r *http.Request) { log.Debug("HTTP", "Starting send of ldlinux.c32 to %s (%d bytes)", r.RemoteAddr, len(s.ldlinux)) w.Header().Set("Content-Type", "application/octet-stream") w.Write(s.ldlinux) log.Log("HTTP", "Sent ldlinux.c32 to %s (%d bytes)", r.RemoteAddr, len(s.ldlinux)) }