mirror of
https://github.com/danderson/netboot.git
synced 2025-08-07 23:27:16 +02:00
Responding with an error makes iPXE print a misleading error message (misleading because it doesn't break the boot in any way, it just makes humans uncomfortable).
210 lines
6.8 KiB
Go
210 lines
6.8 KiB
Go
// Copyright 2016 Google Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package pixiecore
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"text/template"
|
|
)
|
|
|
|
func serveHTTP(l net.Listener, handlers ...func(*http.ServeMux)) error {
|
|
mux := http.NewServeMux()
|
|
for _, h := range handlers {
|
|
h(mux)
|
|
}
|
|
if err := http.Serve(l, mux); err != nil {
|
|
return fmt.Errorf("HTTP server shut down: %s", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) serveHTTP(mux *http.ServeMux) {
|
|
mux.HandleFunc("/_/ipxe", s.handleIpxe)
|
|
mux.HandleFunc("/_/file", s.handleFile)
|
|
mux.HandleFunc("/_/booting", s.handleBooting)
|
|
}
|
|
|
|
func (s *Server) handleIpxe(w http.ResponseWriter, r *http.Request) {
|
|
macStr := r.URL.Query().Get("mac")
|
|
if macStr == "" {
|
|
s.debug("HTTP", "Bad request %q from %s, missing MAC address", r.URL, r.RemoteAddr)
|
|
http.Error(w, "missing MAC address parameter", http.StatusBadRequest)
|
|
return
|
|
}
|
|
archStr := r.URL.Query().Get("arch")
|
|
if archStr == "" {
|
|
s.debug("HTTP", "Bad request %q from %s, missing architecture", r.URL, r.RemoteAddr)
|
|
http.Error(w, "missing architecture parameter", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
mac, err := net.ParseMAC(macStr)
|
|
if err != nil {
|
|
s.debug("HTTP", "Bad request %q from %s, invalid MAC address %q (%s)", r.URL, r.RemoteAddr, macStr, err)
|
|
http.Error(w, "invalid MAC address", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
i, err := strconv.Atoi(archStr)
|
|
if err != nil {
|
|
s.debug("HTTP", "Bad request %q from %s, invalid architecture %q (%s)", r.URL, r.RemoteAddr, archStr, err)
|
|
http.Error(w, "invalid architecture", http.StatusBadRequest)
|
|
return
|
|
}
|
|
arch := Architecture(i)
|
|
switch arch {
|
|
case ArchIA32, ArchX64:
|
|
default:
|
|
s.debug("HTTP", "Bad request %q from %s, unknown architecture %q", r.URL, r.RemoteAddr, arch)
|
|
http.Error(w, "unknown architecture", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
mach := Machine{
|
|
MAC: mac,
|
|
Arch: arch,
|
|
}
|
|
spec, err := s.Booter.BootSpec(mach)
|
|
if err != nil {
|
|
s.log("HTTP", "Couldn't get a bootspec for %s (query %q from %s): %s", mac, r.URL, r.RemoteAddr, err)
|
|
http.Error(w, "couldn't get a bootspec", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if spec == nil {
|
|
// TODO: make ipxe abort netbooting so it can fall through to
|
|
// other boot options - unsure if that's possible.
|
|
s.debug("HTTP", "No boot spec for %s (query %q from %s), ignoring boot request", mac, r.URL, r.RemoteAddr)
|
|
http.Error(w, "you don't netboot", http.StatusNotFound)
|
|
return
|
|
}
|
|
script, err := ipxeScript(mach, spec, r.Host)
|
|
if err != nil {
|
|
s.log("HTTP", "Failed to assemble ipxe script for %s (query %q from %s): %s", mac, r.URL, r.RemoteAddr, err)
|
|
http.Error(w, "couldn't get a boot script", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
s.log("HTTP", "Sending ipxe boot script to %s", r.RemoteAddr)
|
|
s.machineEvent(mac, machineStateIpxeScript, "Sent iPXE boot script")
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
w.Write(script)
|
|
}
|
|
|
|
func (s *Server) handleFile(w http.ResponseWriter, r *http.Request) {
|
|
name := r.URL.Query().Get("name")
|
|
if name == "" {
|
|
s.debug("HTTP", "Bad request %q from %s, missing filename", r.URL, r.RemoteAddr)
|
|
http.Error(w, "missing filename", http.StatusBadRequest)
|
|
}
|
|
|
|
f, sz, err := s.Booter.ReadBootFile(ID(name))
|
|
if err != nil {
|
|
s.log("HTTP", "Error getting file %q (query %q from %s): %s", name, r.URL, r.RemoteAddr, err)
|
|
http.Error(w, "couldn't get file", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer f.Close()
|
|
if sz >= 0 {
|
|
w.Header().Set("Content-Length", strconv.FormatInt(sz, 10))
|
|
} else {
|
|
s.log("HTTP", "Unknown file size for %q, boot will be VERY slow (can your Booter provide file sizes?)", name)
|
|
}
|
|
if _, err = io.Copy(w, f); err != nil {
|
|
s.log("HTTP", "Copy of %q to %s (query %q) failed: %s", name, r.RemoteAddr, r.URL, err)
|
|
return
|
|
}
|
|
s.log("HTTP", "Sent file %q to %s", name, r.RemoteAddr)
|
|
|
|
switch r.URL.Query().Get("type") {
|
|
case "kernel":
|
|
mac, err := net.ParseMAC(r.URL.Query().Get("mac"))
|
|
if err != nil {
|
|
s.log("HTTP", "File fetch provided invalid MAC address %q", r.URL.Query().Get("mac"))
|
|
return
|
|
}
|
|
s.machineEvent(mac, machineStateKernel, "Sent kernel %q", name)
|
|
case "initrd":
|
|
mac, err := net.ParseMAC(r.URL.Query().Get("mac"))
|
|
if err != nil {
|
|
s.log("HTTP", "File fetch provided invalid MAC address %q", r.URL.Query().Get("mac"))
|
|
return
|
|
}
|
|
s.machineEvent(mac, machineStateInitrd, "Sent initrd %q", name)
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleBooting(w http.ResponseWriter, r *http.Request) {
|
|
// This should not always errors out.
|
|
// if we send http.StatusInternalServerError iPXE will get a image not found error
|
|
// http.Error(w, "", http.StatusInternalServerError)
|
|
// return a empty commented script
|
|
fmt.Fprintf(w, "# Booting")
|
|
|
|
macStr := r.URL.Query().Get("mac")
|
|
if macStr == "" {
|
|
s.debug("HTTP", "Bad request %q from %s, missing MAC address", r.URL, r.RemoteAddr)
|
|
return
|
|
}
|
|
mac, err := net.ParseMAC(macStr)
|
|
if err != nil {
|
|
s.debug("HTTP", "Bad request %q from %s, invalid MAC address %q (%s)", r.URL, r.RemoteAddr, macStr, err)
|
|
return
|
|
}
|
|
s.machineEvent(mac, machineStateBooted, "Booting into OS")
|
|
}
|
|
|
|
func ipxeScript(mach Machine, spec *Spec, serverHost string) ([]byte, error) {
|
|
if spec.Kernel == "" {
|
|
return nil, errors.New("spec is missing Kernel")
|
|
}
|
|
|
|
urlTemplate := fmt.Sprintf("http://%s/_/file?name=%%s&type=%%s&mac=%%s", serverHost)
|
|
var b bytes.Buffer
|
|
b.WriteString("#!ipxe\n")
|
|
u := fmt.Sprintf(urlTemplate, url.QueryEscape(string(spec.Kernel)), "kernel", url.QueryEscape(mach.MAC.String()))
|
|
fmt.Fprintf(&b, "kernel --name kernel %s\n", u)
|
|
for i, initrd := range spec.Initrd {
|
|
u = fmt.Sprintf(urlTemplate, url.QueryEscape(string(initrd)), "initrd", url.QueryEscape(mach.MAC.String()))
|
|
fmt.Fprintf(&b, "initrd --name initrd%d %s\n", i, u)
|
|
}
|
|
|
|
fmt.Fprintf(&b, "imgfetch --name ready http://%s/_/booting?mac=%s ||\n", serverHost, url.QueryEscape(mach.MAC.String()))
|
|
b.WriteString("imgfree ready ||\n")
|
|
|
|
b.WriteString("boot kernel ")
|
|
for i := range spec.Initrd {
|
|
fmt.Fprintf(&b, "initrd=initrd%d ", i)
|
|
}
|
|
|
|
f := func(id string) string {
|
|
return fmt.Sprintf("http://%s/_/file?name=%s", serverHost, url.QueryEscape(id))
|
|
}
|
|
cmdline, err := expandCmdline(spec.Cmdline, template.FuncMap{"ID": f})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("expanding cmdline %q: %s", spec.Cmdline, err)
|
|
}
|
|
b.WriteString(cmdline)
|
|
b.WriteByte('\n')
|
|
|
|
return b.Bytes(), nil
|
|
}
|