netboot/pixiecore/pixiecore.go
Travis Short c08175e180 Update pixiecore.go
fix interface metadata errors for MacOS
2020-01-26 13:12:51 -08:00

275 lines
7.9 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 "go.universe.tf/netboot/pixiecore"
import (
"bytes"
"fmt"
"io"
"net"
"strings"
"sync"
"text/template"
"go.universe.tf/netboot/dhcp4"
)
const (
portDHCP = 67
portTFTP = 69
portHTTP = 80
portPXE = 4011
)
// An ID is an identifier used by Booters to reference files.
type ID string
// Architecture describes a kind of CPU architecture.
type Architecture int
// Architecture types that Pixiecore knows how to boot.
//
// These architectures are self-reported by the booting machine. The
// machine may support additional execution modes. For example, legacy
// PC BIOS reports itself as an ArchIA32, but may also support ArchX64
// execution.
const (
// ArchIA32 is a 32-bit x86 machine. It _may_ also support X64
// execution, but Pixiecore has no way of knowing.
ArchIA32 Architecture = iota
// ArchX64 is a 64-bit x86 machine (aka amd64 aka X64).
ArchX64
)
func (a Architecture) String() string {
switch a {
case ArchIA32:
return "IA32"
case ArchX64:
return "X64"
default:
return "Unknown architecture"
}
}
// A Machine describes a machine that is attempting to boot.
type Machine struct {
MAC net.HardwareAddr
Arch Architecture
}
// A Spec describes a kernel and associated configuration.
type Spec struct {
// The kernel to boot
Kernel ID
// Optional init ramdisks for linux kernels
Initrd []ID
// Optional kernel commandline. This string is evaluated as a
// text/template template, in which "ID(x)" function is
// available. Invoking ID(x) returns a URL that will call
// Booter.ReadBootFile(x) when fetched.
Cmdline string
// Message to print on the client machine before booting.
Message string
// A raw iPXE script to run. Overrides all of the above.
//
// THIS IS NOT A STABLE INTERFACE. This will only work for
// machines that get booted via iPXE. Currently, that is all of
// them, but there is no guarantee that this will remain
// true. When passing a custom iPXE script, it is your
// responsibility to make the boot succeed, Pixiecore's
// involvement ends when it serves your script.
IpxeScript string
}
func expandCmdline(tpl string, funcs template.FuncMap) (string, error) {
tmpl, err := template.New("cmdline").Option("missingkey=error").Funcs(funcs).Parse(tpl)
if err != nil {
return "", fmt.Errorf("parsing cmdline %q: %s", tpl, err)
}
var out bytes.Buffer
if err = tmpl.Execute(&out, nil); err != nil {
return "", fmt.Errorf("expanding cmdline template %q: %s", tpl, err)
}
cmdline := strings.TrimSpace(out.String())
if strings.Contains(cmdline, "\n") {
return "", fmt.Errorf("cmdline %q contains a newline", cmdline)
}
return cmdline, nil
}
// A Booter provides boot instructions and files for machines.
//
// Due to the stateless nature of various boot protocols, BootSpec()
// will be called multiple times in the course of a single boot
// attempt.
type Booter interface {
// The given MAC address wants to know what it should
// boot. What should Pixiecore make it boot?
//
// Returning an error or a nil BootSpec will make Pixiecore ignore
// the client machine's request.
BootSpec(m Machine) (*Spec, error)
// Get the bytes corresponding to an ID given in Spec.
//
// Additionally returns the total number of bytes in the
// ReadCloser, or -1 if the size is unknown. Be warned, returning
// -1 will make the boot process orders of magnitude slower due to
// poor ipxe behavior.
ReadBootFile(id ID) (io.ReadCloser, int64, error)
// Write the given Reader to an ID given in Spec.
WriteBootFile(id ID, body io.Reader) error
}
// Firmware describes a kind of firmware attempting to boot.
//
// This should only be used for selecting the right bootloader within
// Pixiecore, kernel selection should key off the more generic
// Architecture.
type Firmware int
// The bootloaders that Pixiecore knows how to handle.
const (
FirmwareX86PC Firmware = iota // "Classic" x86 BIOS with PXE/UNDI support
FirmwareEFI32 // 32-bit x86 processor running EFI
FirmwareEFI64 // 64-bit x86 processor running EFI
FirmwareEFIBC // 64-bit x86 processor running EFI
FirmwareX86Ipxe // "Classic" x86 BIOS running iPXE (no UNDI support)
FirmwarePixiecoreIpxe // Pixiecore's iPXE, which has replaced the underlying firmware
)
// A Server boots machines using a Booter.
type Server struct {
Booter Booter
// Address to listen on, or empty for all interfaces.
Address string
// HTTP port for boot services.
HTTPPort int
// HTTP port for human-readable information. Can be the same as
// HTTPPort.
HTTPStatusPort int
// Ipxe lists the supported bootable Firmwares, and their
// associated ipxe binary.
Ipxe map[Firmware][]byte
// Log receives logs on Pixiecore's operation. If nil, logging
// is suppressed.
Log func(subsystem, msg string)
// Debug receives extensive logging on Pixiecore's internals. Very
// useful for debugging, but very verbose.
Debug func(subsystem, msg string)
// These ports can technically be set for testing, but the
// protocols burned in firmware on the client side hardcode these,
// so if you change them in production, nothing will work.
DHCPPort int
TFTPPort int
PXEPort int
// Listen for DHCP traffic without binding to the DHCP port. This
// enables coexistence of Pixiecore with another DHCP server.
//
// Currently only supported on Linux.
DHCPNoBind bool
// Read UI assets from this path, rather than use the builtin UI
// assets. Used for development of Pixiecore.
UIAssetsDir string
errs chan error
eventsMu sync.Mutex
events map[string][]machineEvent
}
// Serve listens for machines attempting to boot, and uses Booter to
// help them.
func (s *Server) Serve() error {
if s.DHCPPort == 0 {
s.DHCPPort = portDHCP
}
if s.TFTPPort == 0 {
s.TFTPPort = portTFTP
}
if s.PXEPort == 0 {
s.PXEPort = portPXE
}
if s.HTTPPort == 0 {
s.HTTPPort = portHTTP
}
newDHCP := dhcp4.NewConn
if s.DHCPNoBind {
newDHCP = dhcp4.NewSnooperConn
}
dhcp, err := newDHCP(fmt.Sprintf("%s:%d", s.Address, s.DHCPPort))
if err != nil {
return err
}
tftp, err := net.ListenPacket("udp", fmt.Sprintf("%s:%d", s.Address, s.TFTPPort))
if err != nil {
dhcp.Close()
return err
}
pxe, err := net.ListenPacket("udp4", fmt.Sprintf("%s:%d", s.Address, s.PXEPort))
if err != nil {
dhcp.Close()
tftp.Close()
return err
}
http, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Address, s.HTTPPort))
if err != nil {
dhcp.Close()
tftp.Close()
pxe.Close()
return err
}
s.events = make(map[string][]machineEvent)
// 5 buffer slots, one for each goroutine, plus one for
// Shutdown(). We only ever pull the first error out, but shutdown
// will likely generate some spurious errors from the other
// goroutines, and we want them to be able to dump them without
// blocking.
s.errs = make(chan error, 6)
s.debug("Init", "Starting Pixiecore goroutines")
go func() { s.errs <- s.serveDHCP(dhcp) }()
go func() { s.errs <- s.servePXE(pxe) }()
go func() { s.errs <- s.serveTFTP(tftp) }()
go func() { s.errs <- serveHTTP(http, s.serveHTTP) }()
// Wait for either a fatal error, or Shutdown().
err = <-s.errs
dhcp.Close()
tftp.Close()
pxe.Close()
http.Close()
return err
}
// Shutdown causes Serve() to exit, cleaning up behind itself.
func (s *Server) Shutdown() {
select {
case s.errs <- nil:
default:
}
}