blob: 5f97372eb52815f5f8d59e000fbb63af26a3900c [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package netboot
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"net"
"strings"
"time"
)
const (
Magic = 0xAA774217
ServerPort = 33330
AdvertPort = 33331
CmdPortStart = 33332
CmdPortEnd = 33339
)
var (
Cookie = uint32(0x12345678)
)
const (
NetbootBoot = 4
NetbootQuery = 5
)
type Netboot struct {
Port int
Timeout time.Duration
Wait bool
}
type NetbootHeader struct {
Magic uint32
Cookie uint32
Cmd uint32
Arg uint32
}
type msg struct {
hdr NetbootHeader
data [1024]byte
}
func (n *Netboot) bind() (conn *net.UDPConn, err error) {
for p := CmdPortStart; p <= CmdPortEnd; p++ {
conn, err = net.ListenUDP("udp6", &net.UDPAddr{IP: net.IPv6zero, Port: p})
if err == nil {
break
}
}
return
}
func (n *Netboot) Discover(_ context.Context, host string, fuchsia bool) (*net.UDPAddr, error) {
conn, err := n.bind()
if err != nil {
return nil, fmt.Errorf("failed to bind to udp6 port: %v\n", err)
}
Cookie++
req := msg{
hdr: NetbootHeader{
Magic: Magic,
Cookie: Cookie,
Cmd: NetbootQuery,
Arg: 0,
},
}
copy(req.data[:], host)
buf := new(bytes.Buffer)
if err := binary.Write(buf, binary.LittleEndian, req); err != nil {
return nil, fmt.Errorf("binary write failed: %v\n", err)
}
_, err = conn.WriteToUDP(buf.Bytes(), &net.UDPAddr{IP: net.IPv6linklocalallnodes, Port: ServerPort, Zone: "eno1"})
if err != nil {
return nil, err
}
conn.SetReadDeadline(time.Now().Add(n.Timeout))
b := make([]byte, 4096)
_, addr, err := conn.ReadFromUDP(b)
if err != nil {
return nil, err
}
buf2 := bytes.NewReader(b)
var res NetbootHeader
if err := binary.Read(buf2, binary.LittleEndian, &res); err != nil {
return nil, fmt.Errorf("binary read failed: %v\n", err)
}
data := make([]byte, 4096)
buf2.Read(data)
fields := strings.Split(string(data[:]), ";")
for _, f := range fields {
vars := strings.SplitN(f, "=", 2)
if vars[0] == "nodename" && vars[1] != host {
return nil, fmt.Errorf("invalid hostname `%s`", vars[1])
}
}
if fuchsia {
addr.IP[11] = 0xFF;
}
return addr, nil
}
func (n *Netboot) Beacon(_ context.Context) (*net.UDPAddr, error) {
conn, err := net.ListenUDP("udp6", &net.UDPAddr{IP: net.IPv6zero, Port: AdvertPort})
conn.SetReadDeadline(time.Now().Add(n.Timeout))
b := make([]byte, 4096)
_, addr, err := conn.ReadFromUDP(b)
if err != nil {
return nil, err
}
buf := bytes.NewReader(b)
var msg NetbootHeader
if err := binary.Read(buf, binary.LittleEndian, &msg); err != nil {
return nil, fmt.Errorf("binary read failed: %v\n", err)
}
data := make([]byte, 4096)
if _, err := buf.Read(data); err != nil {
return nil, fmt.Errorf("read failed: %v\n", err)
}
fields := strings.Split(string(data[:]), ";")
for _, f := range fields {
vars := strings.SplitN(f, "=", 2)
if vars[0] == "nodename" {
return addr, nil
}
}
return nil, fmt.Errorf("no valid beacon")
}
func (n *Netboot) Boot(_ context.Context, addr *net.UDPAddr) error {
Cookie++
msg := NetbootHeader{
Magic: Magic,
Cookie: Cookie,
Cmd: NetbootBoot,
Arg: 0,
}
buf := new(bytes.Buffer)
if err := binary.Write(buf, binary.LittleEndian, msg); err != nil {
return fmt.Errorf("binary write failed: %v\n", err)
}
conn, err := net.ListenUDP("udp6", &net.UDPAddr{IP: net.IPv6zero, Port: AdvertPort})
if err != nil {
return fmt.Errorf("failed to create a socket: %v\n", err)
}
_, err = conn.WriteToUDP(buf.Bytes(), &net.UDPAddr{IP: addr.IP, Port: ServerPort, Zone: addr.Zone})
if err != nil {
return fmt.Errorf("failed to send boot command: %v\n", err)
}
return nil
}