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
|
package dhcpv4
import (
"errors"
"fmt"
"io"
"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")
)
// 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(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
}
// 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) ([]Option, error) {
return OptionsFromBytesWithParser(data, ParseOption, true)
}
// OptionParser is a function signature for option parsing
type OptionParser func(code OptionCode, data []byte) (Option, error)
// OptionsFromBytesWithParser parses Options from byte sequences using the
// parsing function that is passed in as a paremeter
func OptionsFromBytesWithParser(data []byte, 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 := OptionCode(buf.Read8())
if code == OptionPad {
continue
} else if code == OptionEnd {
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]
if _, ok := options[code]; !ok {
order = append(order, code)
}
// RFC 3396: Just concatenate the data if the option code was
// specified multiple times.
options[code] = append(options[code], 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 OptionCode(buf.Read8()) != OptionPad {
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
}
|