mirror of
https://github.com/danderson/netboot.git
synced 2025-10-17 10:31:28 +02:00
dhcp4: add consts for common options, and option getters.
This commit is contained in:
parent
6ff894645a
commit
c412f00cb8
166
dhcp4/options.go
166
dhcp4/options.go
@ -20,16 +20,61 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Option is a DHCP option.
|
||||||
|
type Option byte
|
||||||
|
|
||||||
|
// Some of the more commonly seen DHCP options. Refer to
|
||||||
|
// http://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml
|
||||||
|
// for the full authoritative list.
|
||||||
|
const (
|
||||||
|
OptSubnetMask Option = 1 // IPMask
|
||||||
|
OptTimeOffset = 2 // int32
|
||||||
|
OptRouters = 3 // IPs
|
||||||
|
OptDNSServers = 6 // IPs
|
||||||
|
OptHostname = 12 // string
|
||||||
|
OptBootFileSize = 13 // uint16
|
||||||
|
OptDomainName = 15 // string
|
||||||
|
OptBroadcastAddr = 28 // IP
|
||||||
|
OptNTPServers = 42 // IP
|
||||||
|
OptVendorSpecific = 43 // []byte
|
||||||
|
OptRequestedIP = 50 // IP
|
||||||
|
OptLeaseTime = 51 // uint32
|
||||||
|
OptOverload = 52 // byte
|
||||||
|
OptServerIdentifier = 54 // IP
|
||||||
|
OptRequestedOptions = 55 // []byte
|
||||||
|
OptMessage = 56 // string
|
||||||
|
OptMaximumMessageSize = 57 // uint16
|
||||||
|
OptRenewalTime = 58 // uint32
|
||||||
|
OptRebindingTime = 59 // uint32
|
||||||
|
OptVendorIdentifier = 60 // string
|
||||||
|
OptClientIdentifier = 61 // string
|
||||||
|
OptFQDN = 81 // string
|
||||||
|
|
||||||
|
// You shouldn't need to use the following directly. Instead,
|
||||||
|
// refer to the fields in the Packet struct, and Marshal/Unmarshal
|
||||||
|
// will handle encoding for you.
|
||||||
|
|
||||||
|
OptTFTPServer = 66 // string
|
||||||
|
OptBootFile = 67 // string
|
||||||
|
OptDHCPMessageType = 53 // byte
|
||||||
|
)
|
||||||
|
|
||||||
// Options stores DHCP options.
|
// Options stores DHCP options.
|
||||||
type Options map[int][]byte
|
type Options map[Option][]byte
|
||||||
|
|
||||||
|
var (
|
||||||
|
errOptionNotPresent = errors.New("option not present in Options")
|
||||||
|
errOptionWrongSize = errors.New("option value is the wrong size")
|
||||||
|
)
|
||||||
|
|
||||||
// Unmarshal parses DHCP options into o.
|
// Unmarshal parses DHCP options into o.
|
||||||
func (o Options) Unmarshal(bs []byte) error {
|
func (o Options) Unmarshal(bs []byte) error {
|
||||||
for len(bs) > 0 {
|
for len(bs) > 0 {
|
||||||
opt := int(bs[0])
|
opt := Option(bs[0])
|
||||||
switch opt {
|
switch opt {
|
||||||
case 0:
|
case 0:
|
||||||
// Padding byte
|
// Padding byte
|
||||||
@ -97,13 +142,13 @@ func (o Options) marshalLimited(w io.Writer, nBytes int, skip52 bool) (Options,
|
|||||||
if n <= 0 || n >= 255 {
|
if n <= 0 || n >= 255 {
|
||||||
return nil, fmt.Errorf("invalid DHCP option number %d", n)
|
return nil, fmt.Errorf("invalid DHCP option number %d", n)
|
||||||
}
|
}
|
||||||
ks = append(ks, n)
|
ks = append(ks, int(n))
|
||||||
}
|
}
|
||||||
sort.Ints(ks)
|
sort.Ints(ks)
|
||||||
|
|
||||||
ret := make(Options)
|
ret := make(Options)
|
||||||
for _, n := range ks {
|
for _, n := range ks {
|
||||||
opt := o[n]
|
opt := o[Option(n)]
|
||||||
if len(opt) > 255 {
|
if len(opt) > 255 {
|
||||||
return nil, fmt.Errorf("DHCP option %d has value >255 bytes", n)
|
return nil, fmt.Errorf("DHCP option %d has value >255 bytes", n)
|
||||||
}
|
}
|
||||||
@ -111,7 +156,7 @@ func (o Options) marshalLimited(w io.Writer, nBytes int, skip52 bool) (Options,
|
|||||||
// If space is limited, verify that we can fit the option plus
|
// If space is limited, verify that we can fit the option plus
|
||||||
// the final end-of-options marker.
|
// the final end-of-options marker.
|
||||||
if nBytes > 0 && ((skip52 && n == 52) || len(opt)+3 > nBytes) {
|
if nBytes > 0 && ((skip52 && n == 52) || len(opt)+3 > nBytes) {
|
||||||
ret[n] = opt
|
ret[Option(n)] = opt
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,21 +174,108 @@ func (o Options) marshalLimited(w io.Writer, nBytes int, skip52 bool) (Options,
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Byte returns the value of single-byte option n, if the option value
|
// String returns the value of option n as a byte slice.
|
||||||
// is indeed a single byte.
|
func (o Options) Bytes(n Option) ([]byte, error) {
|
||||||
func (o Options) Byte(n int) (v byte, ok bool) {
|
|
||||||
bs := o[n]
|
bs := o[n]
|
||||||
if bs == nil || len(bs) != 1 {
|
if bs == nil {
|
||||||
return 0, false
|
return nil, errOptionNotPresent
|
||||||
}
|
}
|
||||||
return bs[0], true
|
return bs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uint16 returns the value of option n interpreted as a uint16.
|
// String returns the value of option n as a string.
|
||||||
func (o Options) Uint16(n int) (v uint16, ok bool) {
|
func (o Options) String(n Option) (string, error) {
|
||||||
bs := o[n]
|
bs, err := o.Bytes(n)
|
||||||
if bs == nil || len(bs) != 2 {
|
if err != nil {
|
||||||
return 0, false
|
return "", err
|
||||||
}
|
}
|
||||||
return binary.BigEndian.Uint16(bs[:2]), true
|
return string(bs), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Byte returns the value of option n as a byte.
|
||||||
|
func (o Options) Byte(n Option) (byte, error) {
|
||||||
|
bs, err := o.Bytes(n)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len(bs) != 1 {
|
||||||
|
return 0, errOptionWrongSize
|
||||||
|
}
|
||||||
|
return bs[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16 returns the value of option n as a uint16.
|
||||||
|
func (o Options) Uint16(n Option) (uint16, error) {
|
||||||
|
bs, err := o.Bytes(n)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len(bs) != 2 {
|
||||||
|
return 0, errOptionWrongSize
|
||||||
|
}
|
||||||
|
return binary.BigEndian.Uint16(bs), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32 returns the value of option n as a uint32.
|
||||||
|
func (o Options) Uint32(n Option) (uint32, error) {
|
||||||
|
bs, err := o.Bytes(n)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len(bs) != 4 {
|
||||||
|
return 0, errOptionWrongSize
|
||||||
|
}
|
||||||
|
return binary.BigEndian.Uint32(bs), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32 returns the value of option n as an int32.
|
||||||
|
func (o Options) Int32(n Option) (int32, error) {
|
||||||
|
bs, err := o.Bytes(n)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len(bs) != 4 {
|
||||||
|
return 0, errOptionWrongSize
|
||||||
|
}
|
||||||
|
return int32(binary.BigEndian.Uint32(bs)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPs returns the value of option n as a list of IPv4 addresses.
|
||||||
|
func (o Options) IPs(n Option) ([]net.IP, error) {
|
||||||
|
bs, err := o.Bytes(n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(bs) < 4 || len(bs)%4 != 0 {
|
||||||
|
return nil, errOptionWrongSize
|
||||||
|
}
|
||||||
|
ret := make([]net.IP, len(bs)/4)
|
||||||
|
for i := 0; i < len(bs); i += 4 {
|
||||||
|
ret = append(ret, net.IP(bs[i:i+4]))
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP returns the value of option n as an IPv4 address.
|
||||||
|
func (o Options) IP(n Option) (net.IP, error) {
|
||||||
|
ips, err := o.IPs(n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(ips) != 1 {
|
||||||
|
return nil, errOptionWrongSize
|
||||||
|
}
|
||||||
|
return ips[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPMask returns the value of option n as a net.IPMask.
|
||||||
|
func (o Options) IPMask(n Option) (net.IPMask, error) {
|
||||||
|
bs := o[n]
|
||||||
|
if bs == nil {
|
||||||
|
return nil, fmt.Errorf("option %d not found", n)
|
||||||
|
}
|
||||||
|
if len(bs) != 4 {
|
||||||
|
return nil, fmt.Errorf("option %d is the wrong size for an IPMask", n)
|
||||||
|
}
|
||||||
|
return net.IPMask(bs), nil
|
||||||
}
|
}
|
||||||
|
@ -23,22 +23,22 @@ func TestOptionReading(t *testing.T) {
|
|||||||
3: []byte{0, 1},
|
3: []byte{0, 1},
|
||||||
}
|
}
|
||||||
|
|
||||||
b, ok := o.Byte(2)
|
b, err := o.Byte(2)
|
||||||
if !ok {
|
if err != nil {
|
||||||
t.Fatalf("Option 2 should be a valid byte")
|
t.Fatalf("Option 2 should be a valid byte, but got: %s", err)
|
||||||
}
|
}
|
||||||
if b != 3 {
|
if b != 3 {
|
||||||
t.Fatalf("Wanted value 3 for option 2, got %d", b)
|
t.Fatalf("Wanted value 3 for option 2, got %d", b)
|
||||||
}
|
}
|
||||||
|
|
||||||
b, ok = o.Byte(3)
|
b, err = o.Byte(3)
|
||||||
if ok {
|
if err == nil {
|
||||||
t.Fatalf("Option 3 shouldn't be a valid byte")
|
t.Fatalf("Option 3 shouldn't be a valid byte")
|
||||||
}
|
}
|
||||||
|
|
||||||
u, ok := o.Uint16(3)
|
u, err := o.Uint16(3)
|
||||||
if !ok {
|
if err != nil {
|
||||||
t.Fatalf("Option 3 should be a valid byte")
|
t.Fatalf("Option 3 should be a valid byte, but got: %s", err)
|
||||||
}
|
}
|
||||||
if u != 1 {
|
if u != 1 {
|
||||||
t.Fatalf("Wanted value 1 for option 3, got %d", u)
|
t.Fatalf("Wanted value 1 for option 3, got %d", u)
|
||||||
|
@ -125,11 +125,11 @@ func (p *Packet) DebugString() string {
|
|||||||
|
|
||||||
var opts []int
|
var opts []int
|
||||||
for n := range p.Options {
|
for n := range p.Options {
|
||||||
opts = append(opts, n)
|
opts = append(opts, int(n))
|
||||||
}
|
}
|
||||||
sort.Ints(opts)
|
sort.Ints(opts)
|
||||||
for _, n := range opts {
|
for _, n := range opts {
|
||||||
fmt.Fprintf(&b, " %d: %#v\n", n, p.Options[n])
|
fmt.Fprintf(&b, " %d: %#v\n", n, p.Options[Option(n)])
|
||||||
}
|
}
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
@ -146,8 +146,8 @@ func (p *Packet) Marshal() ([]byte, error) {
|
|||||||
return nil, errors.New("sname must be <= 64 bytes")
|
return nil, errors.New("sname must be <= 64 bytes")
|
||||||
}
|
}
|
||||||
optsInFile, optsInSname := false, false
|
optsInFile, optsInSname := false, false
|
||||||
v, ok := p.Options.Byte(52)
|
v, err := p.Options.Byte(OptOverload)
|
||||||
if ok {
|
if err == nil {
|
||||||
optsInFile, optsInFile = v&1 != 0, v&2 != 0
|
optsInFile, optsInFile = v&1 != 0, v&2 != 0
|
||||||
}
|
}
|
||||||
if optsInFile && p.BootFilename != "" {
|
if optsInFile && p.BootFilename != "" {
|
||||||
@ -199,7 +199,6 @@ func (p *Packet) Marshal() ([]byte, error) {
|
|||||||
opts[k] = v
|
opts[k] = v
|
||||||
}
|
}
|
||||||
opts[53] = []byte{byte(p.Type)}
|
opts[53] = []byte{byte(p.Type)}
|
||||||
var err error
|
|
||||||
if optsInSname {
|
if optsInSname {
|
||||||
opts, err = opts.marshalLimited(ret, 64, true)
|
opts, err = opts.marshalLimited(ret, 64, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -286,8 +285,8 @@ func Unmarshal(bs []byte) (*Packet, error) {
|
|||||||
// meaning from BOOTP, or can store extra DHCP options if the main
|
// meaning from BOOTP, or can store extra DHCP options if the main
|
||||||
// options section specifies the "Option overload" option.
|
// options section specifies the "Option overload" option.
|
||||||
file, sname := false, false
|
file, sname := false, false
|
||||||
v, ok := ret.Options.Byte(52)
|
v, err := ret.Options.Byte(OptOverload)
|
||||||
if ok {
|
if err == nil {
|
||||||
file, sname = v&1 != 0, v&2 != 0
|
file, sname = v&1 != 0, v&2 != 0
|
||||||
}
|
}
|
||||||
if sname {
|
if sname {
|
||||||
@ -310,17 +309,17 @@ func Unmarshal(bs []byte) (*Packet, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("unterminated 'file' string")
|
return nil, fmt.Errorf("unterminated 'file' string")
|
||||||
}
|
}
|
||||||
ret.BootServerName = s
|
ret.BootFilename = s
|
||||||
}
|
}
|
||||||
|
|
||||||
// DHCP packets must all have at least the "DHCP Message Type"
|
// DHCP packets must all have at least the "DHCP Message Type"
|
||||||
// option.
|
// option.
|
||||||
typ, ok := ret.Options.Byte(53)
|
typ, err := ret.Options.Byte(OptDHCPMessageType)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return nil, errors.New("packet has no DHCP Message Type")
|
return nil, fmt.Errorf("getting DHCP Message type: %s", err)
|
||||||
}
|
}
|
||||||
ret.Type = MessageType(typ)
|
ret.Type = MessageType(typ)
|
||||||
delete(ret.Options, 53)
|
delete(ret.Options, OptDHCPMessageType)
|
||||||
switch ret.Type {
|
switch ret.Type {
|
||||||
case MsgDiscover, MsgRequest, MsgDecline, MsgRelease, MsgInform:
|
case MsgDiscover, MsgRequest, MsgDecline, MsgRelease, MsgInform:
|
||||||
if bs[0] != 1 {
|
if bs[0] != 1 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user