134 lines
3.7 KiB
Go
134 lines
3.7 KiB
Go
|
package dhcp
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
|
||
|
log "git.wntrmute.dev/kyle/goutils/syslog"
|
||
|
)
|
||
|
|
||
|
var optionRegistry = map[OptionTag]Option{
|
||
|
OptionTagPadding: OptionPad,
|
||
|
OptionTagHostName: OptionHostName,
|
||
|
OptionTagMessageType: OptionMessageType,
|
||
|
OptionTagParameterRequestList: OptionParameterRequestList,
|
||
|
OptionTagEnd: OptionEnd,
|
||
|
}
|
||
|
|
||
|
func OptionPad(req *BootRequest, r io.Reader) error {
|
||
|
// The padding option is a single 0 byte octet.
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// OptionHostName reads a DHCP host name option.
|
||
|
//
|
||
|
// 3.14. Host Name Option
|
||
|
//
|
||
|
// This option specifies the name of the client. The name may or may
|
||
|
// not be qualified with the local domain name (see section 3.17 for the
|
||
|
// preferred way to retrieve the domain name). See RFC 1035 for
|
||
|
// character set restrictions.
|
||
|
|
||
|
// The code for this option is 12, and its minimum length is 1.
|
||
|
func OptionHostName(req *BootRequest, r io.Reader) error {
|
||
|
read := newPacketReaderFunc(r)
|
||
|
|
||
|
var length uint8
|
||
|
if err := read(&length); err != nil {
|
||
|
return fmt.Errorf("dhcp: reading option length for DHCP Message Type")
|
||
|
} else if length == 0 {
|
||
|
return errors.New("dhcp: read option length 0, but expected option length for DHCP Host Name is >= 1")
|
||
|
}
|
||
|
|
||
|
hostName := make([]byte, int(length))
|
||
|
if n, err := r.Read(hostName); err != nil {
|
||
|
return fmt.Errorf("dhcp: while reading hostname: %w", err)
|
||
|
} else if n != int(length) {
|
||
|
return fmt.Errorf("dhcp: only read %d bytes of hostname, expected %d bytes", n, length)
|
||
|
}
|
||
|
|
||
|
req.HostName = string(hostName)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func OptionMessageType(req *BootRequest, r io.Reader) error {
|
||
|
read := newPacketReaderFunc(r)
|
||
|
|
||
|
var length uint8
|
||
|
if err := read(&length); err != nil {
|
||
|
return fmt.Errorf("dhcp: reading option length for DHCP Message Type")
|
||
|
} else if length != 1 {
|
||
|
return fmt.Errorf("dhcp: read option length %d, but expected option length for DHCP Message Type is 1", length)
|
||
|
}
|
||
|
|
||
|
if err := read(&req.DHCPType); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func OptionParameterRequestList(req *BootRequest, r io.Reader) error {
|
||
|
read := newPacketReaderFunc(r)
|
||
|
|
||
|
var length uint8
|
||
|
if err := read(&length); err != nil {
|
||
|
return fmt.Errorf("dhcp: reading option length for DHCP Message Type")
|
||
|
} else if length == 0 {
|
||
|
return fmt.Errorf("dhcp: read option length %d, but expected option length for DHCP Parameter Request is >= 1", length)
|
||
|
}
|
||
|
|
||
|
var parameters = make([]byte, int(length))
|
||
|
if n, err := r.Read(parameters); err != nil {
|
||
|
return fmt.Errorf("dhcp: while reading parameters: %w", err)
|
||
|
} else if n != int(length) {
|
||
|
return fmt.Errorf("dhcp: only read %d octets of requested parameters, expected %d octets", n, length)
|
||
|
}
|
||
|
|
||
|
for _, parameter := range parameters {
|
||
|
opt := OptionTag(parameter)
|
||
|
log.Debugf("client is requesting %s", opt)
|
||
|
req.ParameterRequests = append(req.ParameterRequests, opt)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func OptionEnd(req *BootRequest, r io.Reader) error {
|
||
|
req.endOptions = true
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func ReadOption(req *BootRequest, tag byte, r io.Reader) error {
|
||
|
opt := OptionTag(tag)
|
||
|
if f, ok := optionRegistry[opt]; ok {
|
||
|
log.Debugf("dhcp: reading option=%s", opt)
|
||
|
return f(req, r)
|
||
|
}
|
||
|
|
||
|
return fmt.Errorf("dhcp: unknown/unhandled option %d", opt)
|
||
|
}
|
||
|
|
||
|
const magicCookieLength = 4
|
||
|
|
||
|
var magicCookie = []byte{99, 130, 83, 99}
|
||
|
|
||
|
func ReadMagicCookie(r io.Reader) error {
|
||
|
var cookie = make([]byte, magicCookieLength)
|
||
|
n, err := r.Read(cookie)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("dhcp: failed to read magic cookie: %w", err)
|
||
|
} else if n != magicCookieLength {
|
||
|
return fmt.Errorf("dhcp: read %d bytes, expected to read %d bytes for the magic cookie",
|
||
|
n, magicCookieLength)
|
||
|
}
|
||
|
|
||
|
if !bytes.Equal(cookie, magicCookie) {
|
||
|
return fmt.Errorf("dhcp: read magic cookie %x, expected %x", cookie, magicCookie)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|