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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
|
package dhcpv4
import (
"errors"
"fmt"
"io"
"math"
"github.com/u-root/u-root/pkg/uio"
)
var (
// ErrShortByteStream is an error that is thrown any time a short byte stream is
// detected during option parsing.
ErrShortByteStream = errors.New("short byte stream")
// ErrZeroLengthByteStream is an error that is thrown any time a zero-length
// byte stream is encountered.
ErrZeroLengthByteStream = errors.New("zero-length byte stream")
// ErrInvalidOptions is returned when invalid options data is
// encountered during parsing. The data could report an incorrect
// length or have trailing bytes which are not part of the option.
ErrInvalidOptions = errors.New("invalid options data")
)
// Option is an interface that all DHCP v4 options adhere to.
type Option interface {
Code() OptionCode
ToBytes() []byte
String() string
}
// ParseOption parses a sequence of bytes as a single DHCPv4 option, returning
// the specific option structure or error, if any.
func ParseOption(code OptionCode, data []byte) (Option, error) {
var (
opt Option
err error
)
switch code {
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 OptionRootPath:
opt, err = ParseOptRootPath(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 OptionRelayAgentInformation:
opt, err = ParseOptRelayAgentInformation(data)
case OptionClientSystemArchitectureType:
opt, err = ParseOptClientArchType(data)
case OptionDNSDomainSearchList:
opt, err = ParseOptDomainSearch(data)
case OptionVendorIdentifyingVendorClass:
opt, err = ParseOptVIVC(data)
default:
opt, err = ParseOptionGeneric(code, data)
}
if err != nil {
return nil, err
}
return opt, nil
}
// Options is a collection of options.
type Options []Option
// Get will attempt to get all options that match a DHCPv4 option from its
// OptionCode. If the option was not found it will return an empty list.
//
// According to RFC 3396, options that are specified more than once are
// concatenated, and hence this should always just return one option.
func (o Options) Get(code OptionCode) []Option {
opts := []Option{}
for _, opt := range o {
if opt.Code() == code {
opts = append(opts, opt)
}
}
return opts
}
// GetOne will attempt to get an option that match a Option code. If there
// are multiple options with the same OptionCode it will only return the first
// one found. If no matching option is found nil will be returned.
func (o Options) GetOne(code OptionCode) Option {
for _, opt := range o {
if opt.Code() == code {
return opt
}
}
return nil
}
// Has checks whether o has the given `opcode` Option.
func (o Options) Has(code OptionCode) bool {
return o.GetOne(code) != nil
}
// OptionsFromBytes 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 OptionsFromBytes(data []byte) (Options, error) {
return OptionsFromBytesWithParser(data, codeGetter, ParseOption, true)
}
// OptionParser is a function signature for option parsing.
type OptionParser func(code OptionCode, data []byte) (Option, error)
// OptionCodeGetter parses a code into an OptionCode.
type OptionCodeGetter func(code uint8) OptionCode
// codeGetter is an OptionCodeGetter for DHCP optionCodes.
func codeGetter(c uint8) OptionCode {
return optionCode(c)
}
// OptionsFromBytesWithParser parses Options from byte sequences using the
// parsing function that is passed in as a paremeter
func OptionsFromBytesWithParser(data []byte, coder OptionCodeGetter, parser OptionParser, checkEndOption bool) (Options, error) {
if len(data) == 0 {
return nil, nil
}
buf := uio.NewBigEndianBuffer(data)
options := make(map[OptionCode][]byte, 10)
var order []OptionCode
// Due to RFC 3396 allowing an option to be specified multiple times,
// we have to collect all option data first, and then parse it.
var end bool
for buf.Len() >= 1 {
// 1 byte: option code
// 1 byte: option length n
// n bytes: data
code := buf.Read8()
if code == OptionPad.Code() {
continue
} else if code == OptionEnd.Code() {
end = true
break
}
length := int(buf.Read8())
// N bytes: option data
data := buf.Consume(length)
if data == nil {
return nil, fmt.Errorf("error collecting options: %v", buf.Error())
}
data = data[:length:length]
// Get the OptionCode for this guy.
c := coder(code)
if _, ok := options[c]; !ok {
order = append(order, c)
}
// RFC 3396: Just concatenate the data if the option code was
// specified multiple times.
options[c] = append(options[c], data...)
}
// If we never read the End option, the sender of this packet screwed
// up.
if !end && checkEndOption {
return nil, io.ErrUnexpectedEOF
}
// Any bytes left must be padding.
for buf.Len() >= 1 {
if buf.Read8() != OptionPad.Code() {
return nil, ErrInvalidOptions
}
}
opts := make(Options, 0, 10)
for _, code := range order {
parsedOpt, err := parser(code, options[code])
if err != nil {
return nil, fmt.Errorf("error parsing option code %s: %v", code, err)
}
opts = append(opts, parsedOpt)
}
return opts, nil
}
// Marshal writes options binary representations to b.
func (o Options) Marshal(b *uio.Lexer) {
for _, opt := range o {
code := opt.Code().Code()
// Even if the End option is in there, don't marshal it until
// the end.
if code == OptionEnd.Code() {
continue
} else if code == OptionPad.Code() {
// Some DHCPv4 options have fixed length and do not put
// length on the wire.
b.Write8(code)
continue
}
data := opt.ToBytes()
// RFC 3396: If more than 256 bytes of data are given, the
// option is simply listed multiple times.
for len(data) > 0 {
// 1 byte: option code
b.Write8(code)
n := len(data)
if n > math.MaxUint8 {
n = math.MaxUint8
}
// 1 byte: option length
b.Write8(uint8(n))
// N bytes: option data
b.WriteBytes(data[:n])
data = data[n:]
}
}
}
|