summaryrefslogtreecommitdiffhomepage
path: root/dhcpv4/options.go
blob: 02fa6e4fc9007e132a1f4cbc771a261183a20235 (plain)
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
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)
		if opt.Code() == OptionEnd {
			break
		}

		// 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
}