1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
package dhcpv4
import (
"bytes"
"errors"
"fmt"
)
// ErrShortByteStream is an error that is thrown any time a short byte stream is
// detected during option parsing.
var ErrShortByteStream = errors.New("short byte stream")
// ErrZeroLengthByteStream is an error that is thrown any time a zero-length
// byte stream is encountered.
var ErrZeroLengthByteStream = errors.New("zero-length byte stream")
// MagicCookie is the magic 4-byte value at the beginning of the list of options
// in a DHCPv4 packet.
var MagicCookie = []byte{99, 130, 83, 99}
// OptionCode is a single byte representing the code for a given Option.
type OptionCode byte
// Option is an interface that all DHCP v4 options adhere to.
type Option interface {
Code() OptionCode
ToBytes() []byte
Length() int
String() string
}
// ParseOption parses a sequence of bytes as a single DHCPv4 option, returning
// the specific option structure or error, if any.
func ParseOption(data []byte) (Option, error) {
if len(data) == 0 {
return nil, errors.New("invalid zero-length DHCPv4 option")
}
var (
opt Option
err error
)
switch OptionCode(data[0]) {
case OptionSubnetMask:
opt, err = ParseOptSubnetMask(data)
case OptionRouter:
opt, err = ParseOptRouter(data)
case OptionDomainNameServer:
opt, err = ParseOptDomainNameServer(data)
case OptionHostName:
opt, err = ParseOptHostName(data)
case OptionDomainName:
opt, err = ParseOptDomainName(data)
case OptionBroadcastAddress:
opt, err = ParseOptBroadcastAddress(data)
case OptionNTPServers:
opt, err = ParseOptNTPServers(data)
case OptionRequestedIPAddress:
opt, err = ParseOptRequestedIPAddress(data)
case OptionIPAddressLeaseTime:
opt, err = ParseOptIPAddressLeaseTime(data)
case OptionDHCPMessageType:
opt, err = ParseOptMessageType(data)
case OptionServerIdentifier:
opt, err = ParseOptServerIdentifier(data)
case OptionParameterRequestList:
opt, err = ParseOptParameterRequestList(data)
case OptionMaximumDHCPMessageSize:
opt, err = ParseOptMaximumDHCPMessageSize(data)
case OptionClassIdentifier:
opt, err = ParseOptClassIdentifier(data)
case OptionTFTPServerName:
opt, err = ParseOptTFTPServerName(data)
case OptionBootfileName:
opt, err = ParseOptBootfileName(data)
case OptionUserClassInformation:
opt, err = ParseOptUserClass(data)
case OptionClientSystemArchitectureType:
opt, err = ParseOptClientArchType(data)
case OptionVendorIdentifyingVendorClass:
opt, err = ParseOptVIVC(data)
case OptionDNSDomainSearchList:
opt, err = ParseOptDomainSearch(data)
default:
opt, err = ParseOptionGeneric(data)
}
if err != nil {
return nil, err
}
return opt, nil
}
// OptionsFromBytes parses a sequence of bytes until the end and builds a list
// of options from it. The sequence must contain the Magic Cookie. Returns an
// error if any invalid option or length is found.
func OptionsFromBytes(data []byte) ([]Option, error) {
if len(data) < len(MagicCookie) {
return nil, errors.New("invalid options: shorter than 4 bytes")
}
if !bytes.Equal(data[:len(MagicCookie)], MagicCookie) {
return nil, fmt.Errorf("invalid magic cookie: %v", data[:len(MagicCookie)])
}
opts, err := OptionsFromBytesWithoutMagicCookie(data[len(MagicCookie):])
if err != nil {
return nil, err
}
return opts, nil
}
// OptionsFromBytesWithoutMagicCookie parses a sequence of bytes until the end
// and builds a list of options from it. The sequence should not contain the
// DHCP magic cookie. Returns an error if any invalid option or length is found.
func OptionsFromBytesWithoutMagicCookie(data []byte) ([]Option, error) {
options := make([]Option, 0, 10)
idx := 0
for {
if idx == len(data) {
break
}
// This should never happen.
if idx > len(data) {
return nil, errors.New("read past the end of options")
}
opt, err := ParseOption(data[idx:])
idx++
if err != nil {
return nil, err
}
options = append(options, opt)
// Options with zero length have no length byte, so here we handle the
// ones with nonzero length
if opt.Length() > 0 {
idx++
}
idx += opt.Length()
}
return options, nil
}
|