Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dhcpv4/dhcpv4_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ func TestUpdateOption(t *testing.T) {
d.UpdateOption(bootFileOpt2)

options := d.Options
require.Equal(t, len(options), 2)
require.Equal(t, len(options.opts), 2)
require.Equal(t, d.GetOneOption(OptionHostName), []byte("darkstar"))
require.Equal(t, d.GetOneOption(OptionBootfileName), []byte("boot2.img"))
}
Expand Down
11 changes: 3 additions & 8 deletions dhcpv4/dhcpv4packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func newDHCPv4(xid TransactionID, modifiers ...Modifier) *DHCPv4 {
YourIPAddr: net.IPv4zero,
ServerIPAddr: net.IPv4zero,
GatewayIPAddr: net.IPv4zero,
Options: make(Options),
Options: Options{},
}
for _, mod := range modifiers {
mod(&d)
Expand Down Expand Up @@ -322,7 +322,7 @@ func FromBytes(q []byte) (*DHCPv4, error) {
return nil, fmt.Errorf("malformed DHCP packet: got magic cookie %v, want %v", cookie[:], magicCookie[:])
}

p.Options = make(Options)
p.Options = Options{}
if err := p.Options.fromBytesCheckEnd(buf.Data(), true); err != nil {
return nil, err
}
Expand Down Expand Up @@ -373,17 +373,12 @@ func (d *DHCPv4) GetOneOption(code OptionCode) []byte {

// DeleteOption deletes an existing option with the given option code.
func (d *DHCPv4) DeleteOption(code OptionCode) {
if d.Options != nil {
d.Options.Del(code)
}
d.Options.Del(code)
}

// UpdateOption replaces an existing option with the same option code with the
// given one, adding it if not already present.
func (d *DHCPv4) UpdateOption(opt Option) {
if d.Options == nil {
d.Options = make(Options)
}
d.Options.Update(opt)
}

Expand Down
2 changes: 1 addition & 1 deletion dhcpv4/modifiers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func TestWithRelayAgentInfo(t *testing.T) {

opt := resp.RelayAgentInfo()
require.NotNil(t, opt)
require.Equal(t, len(opt.Options), 2)
require.Equal(t, len(opt.Options.opts), 2)

circuit := opt.Get(GenericOptionCode(1))
remote := opt.Get(GenericOptionCode(2))
Expand Down
2 changes: 1 addition & 1 deletion dhcpv4/nclient4/lease_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func (sll *testServerLeaseList) testLeaseReleaseHandle(conn net.PacketConn, peer
return fmt.Errorf("gateway IP is %v, expect 0", m.GatewayIPAddr)
}
mustlist, maylist := sll.getCheckList()
for o := range m.Options {
for o := range m.Options.All() {
foundInMust := false
foundInMay := false
if _, ok := mustlist[o]; ok {
Expand Down
8 changes: 4 additions & 4 deletions dhcpv4/option_ip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ func TestOptBroadcastAddress(t *testing.T) {
}

func TestGetIPs(t *testing.T) {
o := Options{102: []byte{}}
o := Options{[]optionCodeValue{{102, []byte{}}}}
i := GetIPs(optionCode(102), o)
require.Nil(t, i)

o = Options{102: []byte{192, 168, 0}}
o = Options{[]optionCodeValue{{102, []byte{192, 168, 0}}}}
i = GetIPs(optionCode(102), o)
require.Nil(t, i)

o = Options{102: []byte{192, 168, 0, 1}}
o = Options{[]optionCodeValue{{102, []byte{192, 168, 0, 1}}}}
i = GetIPs(optionCode(102), o)
require.Equal(t, i, []net.IP{{192, 168, 0, 1}})

o = Options{102: []byte{192, 168, 0, 1, 192, 168, 0, 2}}
o = Options{[]optionCodeValue{{102, []byte{192, 168, 0, 1, 192, 168, 0, 2}}}}
i = GetIPs(optionCode(102), o)
require.Equal(t, i, []net.IP{{192, 168, 0, 1}, {192, 168, 0, 2}})
}
Expand Down
5 changes: 5 additions & 0 deletions dhcpv4/option_misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ func OptClientArch(archs ...iana.Arch) Option {
func OptClientIdentifier(ident []byte) Option {
return OptGeneric(OptionClientIdentifier, ident)
}

// OptPadding returns a new padding option.
func OptPadding() Option {
return OptGeneric(OptionPad, nil)
}
2 changes: 1 addition & 1 deletion dhcpv4/option_relay_agent_information.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func (r RelayOptions) String() string {

// FromBytes parses relay agent options from data.
func (r *RelayOptions) FromBytes(data []byte) error {
r.Options = make(Options)
r.Options = Options{}
return r.Options.FromBytes(data)
}

Expand Down
2 changes: 1 addition & 1 deletion dhcpv4/option_relay_agent_information_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func TestGetRelayAgentInformation(t *testing.T) {

opt := m.RelayAgentInfo()
require.NotNil(t, opt)
require.Equal(t, len(opt.Options), 2)
require.Equal(t, len(opt.Options.opts), 2)

circuit := opt.Get(GenericOptionCode(1))
remote := opt.Get(GenericOptionCode(2))
Expand Down
124 changes: 89 additions & 35 deletions dhcpv4/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"errors"
"fmt"
"io"
"iter"
"math"
"sort"
"strings"

"github.com/insomniacslk/dhcp/iana"
Expand Down Expand Up @@ -47,18 +47,35 @@ func (o Option) String() string {
return fmt.Sprintf("%s: %s", o.Code, v)
}

type optionCodeValue struct {
code uint8
value []byte
}

// Options is a collection of options.
type Options map[uint8][]byte
type Options struct {
opts []optionCodeValue
}

// OptionsFromList adds all given options to an options map.
func OptionsFromList(o ...Option) Options {
opts := make(Options)
opts := Options{}
for _, opt := range o {
opts.Update(opt)
}
return opts
}

func (o Options) All() iter.Seq[uint8] {
return func(yield func(uint8) bool) {
for _, opt := range o.opts {
if !yield(opt.code) {
return
}
}
}
}

// 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.
Expand All @@ -67,24 +84,47 @@ func OptionsFromList(o ...Option) Options {
// concatenated, and hence this should always just return one option. This
// currently returns a list to be API compatible.
func (o Options) Get(code OptionCode) []byte {
return o[code.Code()]
for _, opt := range o.opts {
if opt.code == code.Code() {
return opt.value
}
}
return nil
}

// Has checks whether o has the given opcode.
func (o Options) Has(opcode OptionCode) bool {
_, ok := o[opcode.Code()]
return ok
for _, opt := range o.opts {
if opt.code == opcode.Code() {
return true
}
}
return false
}

// Del deletes the option matching the option code.
func (o Options) Del(opcode OptionCode) {
delete(o, opcode.Code())
func (o *Options) Del(opcode OptionCode) {
for i, opt := range o.opts {
if opt.code == opcode.Code() {
o.opts = append(o.opts[:i], o.opts[i+1:]...)
return
}
}
}

// Update updates the existing options with the passed option, adding it
// at the end if not present already
func (o Options) Update(option Option) {
o[option.Code.Code()] = option.Value.ToBytes()
func (o *Options) Update(option Option) {
// Avoid merging padding options
if option.Code.Code() != optPad {
for i, opt := range o.opts {
if opt.code == option.Code.Code() {
o.opts[i].value = option.Value.ToBytes()
return
}
}
}
o.opts = append(o.opts, optionCodeValue{option.Code.Code(), option.Value.ToBytes()})
}

// ToBytes makes Options usable as an OptionValue as well.
Expand All @@ -100,7 +140,7 @@ func (o Options) ToBytes() []byte {
// The sequence should not contain the DHCP magic cookie.
//
// Returns an error if any invalid option or length is found.
func (o Options) FromBytes(data []byte) error {
func (o *Options) FromBytes(data []byte) error {
return o.fromBytesCheckEnd(data, false)
}

Expand All @@ -112,7 +152,7 @@ const (

// FromBytesCheckEnd parses Options from byte sequences using the
// parsing function that is passed in as a paremeter
func (o Options) fromBytesCheckEnd(data []byte, checkEndOption bool) error {
func (o *Options) fromBytesCheckEnd(data []byte, checkEndOption bool) error {
if len(data) == 0 {
return nil
}
Expand Down Expand Up @@ -147,7 +187,18 @@ func (o Options) fromBytesCheckEnd(data []byte, checkEndOption bool) error {
//
// See also RFC 3396 for concatenation order and options longer
// than 255 bytes.
o[code] = append(o[code], data...)
var foundOpt int = -1
for i, opt := range o.opts {
if opt.code == code {
foundOpt = i
break
}
}
if foundOpt != -1 {
o.opts[foundOpt].value = append(o.opts[foundOpt].value, data...)
} else {
o.opts = append(o.opts, optionCodeValue{code, data})
}
}

// If we never read the End option, the sender of this packet screwed
Expand All @@ -163,45 +214,48 @@ func (o Options) fromBytesCheckEnd(data []byte, checkEndOption bool) error {
// use in serializing options to binary.
func (o Options) sortedKeys() []int {
// Send all values for a given key
var codes []int
var hasOptAgentInfo, hasOptEnd bool
for k := range o {
var indices []int
var optAgentInfoIndex int = -1
var optEndIndex int = -1
for i, opt := range o.opts {
// RFC 3046 section 2.1 states that option 82 SHALL come last (ignoring End).
if k == optAgentInfo {
hasOptAgentInfo = true
if opt.code == optAgentInfo {
optAgentInfoIndex = i
continue
}
if k == optEnd {
hasOptEnd = true
if opt.code == optEnd {
optEndIndex = i
continue
}
codes = append(codes, int(k))
indices = append(indices, int(i))
}

sort.Ints(codes)

if hasOptAgentInfo {
codes = append(codes, optAgentInfo)
if optAgentInfoIndex != -1 {
indices = append(indices, optAgentInfoIndex)
}
if hasOptEnd {
codes = append(codes, optEnd)
if optEndIndex != -1 {
indices = append(indices, optEndIndex)
}
return codes
return indices
}

// Marshal writes options binary representations to b.
func (o Options) Marshal(b *uio.Lexer) {
for _, c := range o.sortedKeys() {
code := uint8(c)
code := uint8(o.opts[c].code)
// Even if the End option is in there, don't marshal it until
// the end.
// Don't write padding either, since the options are sorted
// it would always be written first which isn't useful
if code == optEnd || code == optPad {
if code == optEnd {
continue
}

// Padding does not have length
if code == optPad {
b.Write8(code)
continue
}

data := o[code]
data := o.opts[c].value

// Ensure even 0-length options are written out
if len(data) == 0 {
Expand Down Expand Up @@ -280,8 +334,8 @@ var dhcpHumanizer = OptionHumanizer{
func (o Options) ToString(humanizer OptionHumanizer) string {
var ret string
for _, c := range o.sortedKeys() {
code := uint8(c)
v := o[code]
code := uint8(o.opts[c].code)
v := o.opts[c].value
optString := humanizer.Stringify(code, v)
// If this option has sub structures, offset them accordingly.
if strings.Contains(optString, "\n") {
Expand Down
Loading