diff options
author | Sean Karlage <skarlage@get9.io> | 2018-03-26 15:40:59 -0700 |
---|---|---|
committer | insomniac <insomniacslk@users.noreply.github.com> | 2018-03-26 15:40:59 -0700 |
commit | bb95a0335e44a76a1e061b5dea2b1592c55177ac (patch) | |
tree | fa3dc814cbfe44182c151b9bd2646212be2b2e91 /dhcpv4 | |
parent | e920c892c48b14dcb1e50e247836b2b9a69fa929 (diff) |
Add some more specific bsdp options + vendor specific implementation (#21)
Add some more specific options + vendor specific implementation
Diffstat (limited to 'dhcpv4')
-rw-r--r-- | dhcpv4/bsdp/bsdp.go | 25 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_boot_image_list.go | 86 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_boot_image_list_test.go | 137 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_default_boot_image_id.go | 59 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_default_boot_image_id_test.go | 56 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_generic.go | 60 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_generic_test.go | 66 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_machine_name.go | 54 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_machine_name_test.go | 44 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_server_identifier.go | 57 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_server_identifier_test.go | 42 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_server_priority.go | 58 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_server_priority_test.go | 39 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_test.go | 200 | ||||
-rw-r--r-- | dhcpv4/bsdp/client.go | 18 | ||||
-rw-r--r-- | dhcpv4/bsdp/option_vendor_specific_information.go | 30 | ||||
-rw-r--r-- | dhcpv4/bsdp/option_vendor_specific_information_test.go | 189 | ||||
-rw-r--r-- | dhcpv4/bsdp/types.go | 26 | ||||
-rw-r--r-- | dhcpv4/dhcpv4.go | 10 |
19 files changed, 1029 insertions, 227 deletions
diff --git a/dhcpv4/bsdp/bsdp.go b/dhcpv4/bsdp/bsdp.go index f04e255..dac45a6 100644 --- a/dhcpv4/bsdp/bsdp.go +++ b/dhcpv4/bsdp/bsdp.go @@ -35,13 +35,20 @@ func makeVendorClassIdentifier() (string, error) { // ParseBootImageListFromAck parses the list of boot images presented in the // ACK[LIST] packet and returns them as a list of BootImages. func ParseBootImageListFromAck(ack dhcpv4.DHCPv4) ([]BootImage, error) { - var bootImages []BootImage - o := ack.GetOption(dhcpv4.OptionVendorSpecificInformation) - vendorOpt := o.(*OptVendorSpecificInformation) - for _, o := range vendorOpt.GetOptions(OptionBootImageList) { - bootImages = append(bootImages, o.(*OptBootImageList).BootImages...) + var images []BootImage + for _, opt := range ack.Options() { + if opt.Code() == dhcpv4.OptionVendorSpecificInformation { + vendorOpt, err := ParseOptVendorSpecificInformation(opt.ToBytes()) + if err != nil { + return nil, err + } + bootImageOpts := vendorOpt.GetOptions(OptionBootImageList) + for _, opt := range bootImageOpts { + images = append(images, opt.(*OptBootImageList).Images...) + } + } } - return bootImages, nil + return images, nil } func needsReplyPort(replyPort uint16) bool { @@ -97,7 +104,7 @@ func InformSelectForAck(ack dhcpv4.DHCPv4, replyPort uint16, selectedImage BootI } if needsReplyPort(replyPort) && replyPort >= 1024 { - return nil, errors.New("replyPort must be a privilegded port") + return nil, errors.New("replyPort must be a privileged port") } d.SetOpcode(dhcpv4.OpcodeBootRequest) d.SetHwType(ack.HwType()) @@ -123,13 +130,13 @@ func InformSelectForAck(ack dhcpv4.DHCPv4, replyPort uint16, selectedImage BootI // TODO replace this loop with `ack.GetOneOption(OptionBootImageList)` for _, opt := range ack.Options() { if opt.Code() == dhcpv4.OptionServerIdentifier { - serverIP = net.IP(opt.(*dhcpv4.OptionGeneric).Data) + serverIP = opt.(*dhcpv4.OptServerIdentifier).ServerID } } if serverIP.To4() == nil { return nil, fmt.Errorf("could not parse server identifier from ACK") } - vendorOpts = append(vendorOpts, &dhcpv4.OptServerIdentifier{serverIP}) + vendorOpts = append(vendorOpts, &OptServerIdentifier{serverIP}) // Validate replyPort if requested. if needsReplyPort(replyPort) { diff --git a/dhcpv4/bsdp/bsdp_option_boot_image_list.go b/dhcpv4/bsdp/bsdp_option_boot_image_list.go new file mode 100644 index 0000000..37e61fa --- /dev/null +++ b/dhcpv4/bsdp/bsdp_option_boot_image_list.go @@ -0,0 +1,86 @@ +// +build darwin + +package bsdp + +import ( + "fmt" + + "github.com/insomniacslk/dhcp/dhcpv4" +) + +// Implements the BSDP option listing the boot images. + +// OptBootImageList contains the list of boot images presented by a netboot +// server. +type OptBootImageList struct { + Images []BootImage +} + +// ParseOptBootImageList constructs an OptBootImageList struct from a sequence +// of bytes and returns it, or an error. +func ParseOptBootImageList(data []byte) (*OptBootImageList, error) { + // Should have at least code + length + if len(data) < 2 { + return nil, dhcpv4.ErrShortByteStream + } + code := dhcpv4.OptionCode(data[0]) + if code != OptionBootImageList { + return nil, fmt.Errorf("expected option %v, got %v instead", OptionBootImageList, code) + } + length := int(data[1]) + if len(data) < length+2 { + return nil, fmt.Errorf("expected length %d, got %d instead", length, len(data)) + } + + // Offset from code + length byte + var bootImages []BootImage + idx := 2 + for { + if idx >= len(data) { + break + } + image, err := BootImageFromBytes(data[idx:]) + if err != nil { + return nil, fmt.Errorf("parsing bytes stream: %v", err) + } + bootImages = append(bootImages, *image) + + // 4 bytes of BootImageID, 1 byte of name length, name + idx += 4 + 1 + len(image.Name) + } + + return &OptBootImageList{bootImages}, nil +} + +// Code returns the option code. +func (o *OptBootImageList) Code() dhcpv4.OptionCode { + return OptionBootImageList +} + +// ToBytes returns a serialized stream of bytes for this option. +func (o *OptBootImageList) ToBytes() []byte { + bs := make([]byte, 0, 2+o.Length()) + bs = append(bs, []byte{byte(o.Code()), byte(o.Length())}...) + for _, image := range o.Images { + bs = append(bs, image.ToBytes()...) + } + return bs +} + +// String returns a human-readable string for this option. +func (o *OptBootImageList) String() string { + s := "BSDP Boot Image List ->" + for _, image := range o.Images { + s += "\n " + image.String() + } + return s +} + +// Length returns the length of the data portion of this option. +func (o *OptBootImageList) Length() int { + length := 0 + for _, image := range o.Images { + length += 4 + 1 + len(image.Name) + } + return length +} diff --git a/dhcpv4/bsdp/bsdp_option_boot_image_list_test.go b/dhcpv4/bsdp/bsdp_option_boot_image_list_test.go new file mode 100644 index 0000000..29a2ccc --- /dev/null +++ b/dhcpv4/bsdp/bsdp_option_boot_image_list_test.go @@ -0,0 +1,137 @@ +// +build darwin + +package bsdp + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOptBootImageListInterfaceMethods(t *testing.T) { + bs := []BootImage{ + BootImage{ + ID: BootImageID{ + IsInstall: false, + ImageType: BootImageTypeMacOSX, + Index: 1001, + }, + Name: "bsdp-1", + }, + BootImage{ + ID: BootImageID{ + IsInstall: true, + ImageType: BootImageTypeMacOS9, + Index: 9009, + }, + Name: "bsdp-2", + }, + } + o := OptBootImageList{bs} + require.Equal(t, OptionBootImageList, o.Code(), "Code") + require.Equal(t, 22, o.Length(), "Length") + expectedBytes := []byte{ + 9, // code + 22, // length + // boot image 1 + 0x1, 0x0, 0x03, 0xe9, // ID + 6, // name length + 'b', 's', 'd', 'p', '-', '1', + // boot image 1 + 0x80, 0x0, 0x23, 0x31, // ID + 6, // name length + 'b', 's', 'd', 'p', '-', '2', + } + require.Equal(t, expectedBytes, o.ToBytes(), "ToBytes") +} + +func TestParseOptBootImageList(t *testing.T) { + data := []byte{ + 9, // code + 22, // length + // boot image 1 + 0x1, 0x0, 0x03, 0xe9, // ID + 6, // name length + 'b', 's', 'd', 'p', '-', '1', + // boot image 1 + 0x80, 0x0, 0x23, 0x31, // ID + 6, // name length + 'b', 's', 'd', 'p', '-', '2', + } + o, err := ParseOptBootImageList(data) + require.NoError(t, err) + expectedBootImages := []BootImage{ + BootImage{ + ID: BootImageID{ + IsInstall: false, + ImageType: BootImageTypeMacOSX, + Index: 1001, + }, + Name: "bsdp-1", + }, + BootImage{ + ID: BootImageID{ + IsInstall: true, + ImageType: BootImageTypeMacOS9, + Index: 9009, + }, + Name: "bsdp-2", + }, + } + require.Equal(t, &OptBootImageList{expectedBootImages}, o) + + // Short byte stream + data = []byte{9} + _, err = ParseOptBootImageList(data) + require.Error(t, err, "should get error from short byte stream") + + // Wrong code + data = []byte{54, 1, 1} + _, err = ParseOptBootImageList(data) + require.Error(t, err, "should get error from wrong code") + + // Bad length + data = []byte{9, 10, 1, 1, 1} + _, err = ParseOptBootImageList(data) + require.Error(t, err, "should get error from bad length") + + // Error parsing boot image (malformed) + data = []byte{ + 9, // code + 22, // length + // boot image 1 + 0x1, 0x0, 0x03, 0xe9, // ID + 4, // name length + 'b', 's', 'd', 'p', '-', '1', + // boot image 1 + 0x80, 0x0, 0x23, 0x31, // ID + 6, // name length + 'b', 's', 'd', 'p', '-', '2', + } + _, err = ParseOptBootImageList(data) + require.Error(t, err, "should get error from bad boot image") +} + +func TestOptBootImageListString(t *testing.T) { + bs := []BootImage{ + BootImage{ + ID: BootImageID{ + IsInstall: false, + ImageType: BootImageTypeMacOSX, + Index: 1001, + }, + Name: "bsdp-1", + }, + BootImage{ + ID: BootImageID{ + IsInstall: true, + ImageType: BootImageTypeMacOS9, + Index: 9009, + }, + Name: "bsdp-2", + }, + } + o := OptBootImageList{bs} + expectedString := "BSDP Boot Image List ->\n bsdp-1 [1001] uninstallable macOS image\n bsdp-2 [9009] installable macOS 9 image" + require.Equal(t, expectedString, o.String()) +} diff --git a/dhcpv4/bsdp/bsdp_option_default_boot_image_id.go b/dhcpv4/bsdp/bsdp_option_default_boot_image_id.go new file mode 100644 index 0000000..70e340d --- /dev/null +++ b/dhcpv4/bsdp/bsdp_option_default_boot_image_id.go @@ -0,0 +1,59 @@ +// +build darwin + +package bsdp + +import ( + "fmt" + + "github.com/insomniacslk/dhcp/dhcpv4" +) + +// Implements the BSDP option default boot image ID, which tells the client +// which image is the default boot image if one is not selected. + +// OptDefaultBootImageID contains the selected boot image ID. +type OptDefaultBootImageID struct { + ID BootImageID +} + +// ParseOptDefaultBootImageID constructs an OptDefaultBootImageID struct from a sequence of +// bytes and returns it, or an error. +func ParseOptDefaultBootImageID(data []byte) (*OptDefaultBootImageID, error) { + if len(data) < 6 { + return nil, dhcpv4.ErrShortByteStream + } + code := dhcpv4.OptionCode(data[0]) + if code != OptionDefaultBootImageID { + return nil, fmt.Errorf("expected option %v, got %v instead", OptionDefaultBootImageID, code) + } + length := int(data[1]) + if length != 4 { + return nil, fmt.Errorf("expected length 4, got %d instead", length) + } + id, err := BootImageIDFromBytes(data[2:6]) + if err != nil { + return nil, err + } + return &OptDefaultBootImageID{*id}, nil +} + +// Code returns the option code. +func (o *OptDefaultBootImageID) Code() dhcpv4.OptionCode { + return OptionDefaultBootImageID +} + +// ToBytes returns a serialized stream of bytes for this option. +func (o *OptDefaultBootImageID) ToBytes() []byte { + serializedID := o.ID.ToBytes() + return append([]byte{byte(o.Code()), byte(len(serializedID))}, serializedID...) +} + +// String returns a human-readable string for this option. +func (o *OptDefaultBootImageID) String() string { + return fmt.Sprintf("BSDP Default Boot Image ID -> %s", o.ID.String()) +} + +// Length returns the length of the data portion of this option. +func (o *OptDefaultBootImageID) Length() int { + return 4 +} diff --git a/dhcpv4/bsdp/bsdp_option_default_boot_image_id_test.go b/dhcpv4/bsdp/bsdp_option_default_boot_image_id_test.go new file mode 100644 index 0000000..0ca6a32 --- /dev/null +++ b/dhcpv4/bsdp/bsdp_option_default_boot_image_id_test.go @@ -0,0 +1,56 @@ +// +build darwin + +package bsdp + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOptDefaultBootImageIDInterfaceMethods(t *testing.T) { + b := BootImageID{IsInstall: true, ImageType: BootImageTypeMacOSX, Index: 1001} + o := OptDefaultBootImageID{b} + require.Equal(t, OptionDefaultBootImageID, o.Code(), "Code") + require.Equal(t, 4, o.Length(), "Length") + expectedBytes := []byte{byte(OptionDefaultBootImageID), 4} + require.Equal(t, append(expectedBytes, b.ToBytes()...), o.ToBytes(), "ToBytes") +} + +func TestParseOptDefaultBootImageID(t *testing.T) { + b := BootImageID{IsInstall: true, ImageType: BootImageTypeMacOSX, Index: 1001} + bootImageBytes := b.ToBytes() + data := append([]byte{byte(OptionDefaultBootImageID), 4}, bootImageBytes...) + o, err := ParseOptDefaultBootImageID(data) + require.NoError(t, err) + require.Equal(t, &OptDefaultBootImageID{b}, o) + + // Short byte stream + data = []byte{byte(OptionDefaultBootImageID), 4} + _, err = ParseOptDefaultBootImageID(data) + require.Error(t, err, "should get error from short byte stream") + + // Wrong code + data = []byte{54, 2, 1, 0, 0, 0} + _, err = ParseOptDefaultBootImageID(data) + require.Error(t, err, "should get error from wrong code") + + // Bad length + data = []byte{byte(OptionDefaultBootImageID), 5, 1, 0, 0, 0, 0} + _, err = ParseOptDefaultBootImageID(data) + require.Error(t, err, "should get error from bad length") +} + +func TestOptDefaultBootImageIDString(t *testing.T) { + b := BootImageID{IsInstall: true, ImageType: BootImageTypeMacOSX, Index: 1001} + o := OptDefaultBootImageID{b} + require.Equal(t, "BSDP Default Boot Image ID -> [1001] installable macOS image", o.String()) + + b = BootImageID{IsInstall: false, ImageType: BootImageTypeMacOS9, Index: 1001} + o = OptDefaultBootImageID{b} + require.Equal(t, "BSDP Default Boot Image ID -> [1001] uninstallable macOS 9 image", o.String()) + + b = BootImageID{IsInstall: false, ImageType: BootImageType(99), Index: 1001} + o = OptDefaultBootImageID{b} + require.Equal(t, "BSDP Default Boot Image ID -> [1001] uninstallable unknown image", o.String()) +} diff --git a/dhcpv4/bsdp/bsdp_option_generic.go b/dhcpv4/bsdp/bsdp_option_generic.go new file mode 100644 index 0000000..7ac2a73 --- /dev/null +++ b/dhcpv4/bsdp/bsdp_option_generic.go @@ -0,0 +1,60 @@ +// +build darwin + +package bsdp + +import ( + "fmt" + + "github.com/insomniacslk/dhcp/dhcpv4" +) + +// OptGeneric is an option that only contains the option code and associated +// data. Every option that does not have a specific implementation will fall +// back to this option. +type OptGeneric struct { + OptionCode dhcpv4.OptionCode + Data []byte +} + +// ParseOptGeneric parses a bytestream and creates a new OptGeneric from it, +// or an error. +func ParseOptGeneric(data []byte) (*OptGeneric, error) { + if len(data) == 0 { + return nil, dhcpv4.ErrZeroLengthByteStream + } + var ( + length int + optionData []byte + ) + code := dhcpv4.OptionCode(data[0]) + length = int(data[1]) + if len(data) < length+2 { + return nil, fmt.Errorf("invalid data length: declared %v, actual %v", length, len(data)) + } + optionData = data[2 : length+2] + return &OptGeneric{OptionCode: code, Data: optionData}, nil +} + +// Code returns the generic option code. +func (o OptGeneric) Code() dhcpv4.OptionCode { + return o.OptionCode +} + +// ToBytes returns a serialized generic option as a slice of bytes. +func (o OptGeneric) ToBytes() []byte { + return append([]byte{byte(o.Code()), byte(o.Length())}, o.Data...) +} + +// String returns a human-readable representation of a generic option. +func (o OptGeneric) String() string { + code, ok := OptionCodeToString[o.Code()] + if !ok { + code = "Unknown" + } + return fmt.Sprintf("%v -> %v", code, o.Data) +} + +// Length returns the number of bytes comprising the data section of the option. +func (o OptGeneric) Length() int { + return len(o.Data) +} diff --git a/dhcpv4/bsdp/bsdp_option_generic_test.go b/dhcpv4/bsdp/bsdp_option_generic_test.go new file mode 100644 index 0000000..5a5ad25 --- /dev/null +++ b/dhcpv4/bsdp/bsdp_option_generic_test.go @@ -0,0 +1,66 @@ +// +build darwin + +package bsdp + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseOptGeneric(t *testing.T) { + // Empty bytestream produces error + _, err := ParseOptGeneric([]byte{}) + require.Error(t, err, "error from empty bytestream") +} + +func TestOptGenericCode(t *testing.T) { + o := OptGeneric{ + OptionCode: OptionMessageType, + Data: []byte{byte(MessageTypeList)}, + } + require.Equal(t, OptionMessageType, o.Code()) +} + +func TestOptGenericData(t *testing.T) { + o := OptGeneric{ + OptionCode: OptionServerIdentifier, + Data: []byte{192, 168, 0, 1}, + } + require.Equal(t, []byte{192, 168, 0, 1}, o.Data) +} + +func TestOptGenericToBytes(t *testing.T) { + o := OptGeneric{ + OptionCode: OptionServerIdentifier, + Data: []byte{192, 168, 0, 1}, + } + serialized := o.ToBytes() + expected := []byte{3, 4, 192, 168, 0, 1} + require.Equal(t, expected, serialized) +} + +func TestOptGenericString(t *testing.T) { + o := OptGeneric{ + OptionCode: OptionServerIdentifier, + Data: []byte{192, 168, 0, 1}, + } + require.Equal(t, "BSDP Server Identifier -> [192 168 0 1]", o.String()) +} + +func TestOptGenericStringUnknown(t *testing.T) { + o := OptGeneric{ + OptionCode: 102, // Returend option code. + Data: []byte{5}, + } + require.Equal(t, "Unknown -> [5]", o.String()) +} + +func TestOptGenericLength(t *testing.T) { + filename := "some_machine_name" + o := OptGeneric{ + OptionCode: OptionMachineName, + Data: []byte(filename), + } + require.Equal(t, len(filename), o.Length()) +} diff --git a/dhcpv4/bsdp/bsdp_option_machine_name.go b/dhcpv4/bsdp/bsdp_option_machine_name.go new file mode 100644 index 0000000..42e29d8 --- /dev/null +++ b/dhcpv4/bsdp/bsdp_option_machine_name.go @@ -0,0 +1,54 @@ +// +build darwin + +package bsdp + +import ( + "fmt" + + "github.com/insomniacslk/dhcp/dhcpv4" +) + +// Implements the BSDP option machine name, which gives the Netboot server's +// machine name. + +// OptMachineName represents a BSDP message type. +type OptMachineName struct { + Name string +} + +// ParseOptMachineName constructs an OptMachineName struct from a sequence of +// bytes and returns it, or an error. +func ParseOptMachineName(data []byte) (*OptMachineName, error) { + if len(data) < 2 { + return nil, dhcpv4.ErrShortByteStream + } + code := dhcpv4.OptionCode(data[0]) + if code != OptionMachineName { + return nil, fmt.Errorf("expected option %v, got %v instead", OptionMachineName, code) + } + length := int(data[1]) + if len(data) < length+2 { + return nil, fmt.Errorf("expected length %d, got %d instead", length, len(data)) + } + return &OptMachineName{Name: string(data[2 : length+2])}, nil +} + +// Code returns the option code. +func (o *OptMachineName) Code() dhcpv4.OptionCode { + return OptionMachineName +} + +// ToBytes returns a serialized stream of bytes for this option. +func (o *OptMachineName) ToBytes() []byte { + return append([]byte{byte(o.Code()), byte(o.Length())}, []byte(o.Name)...) +} + +// String returns a human-readable string for this option. +func (o *OptMachineName) String() string { + return "BSDP Machine Name -> " + o.Name +} + +// Length returns the length of the data portion of this option. +func (o *OptMachineName) Length() int { + return len(o.Name) +} diff --git a/dhcpv4/bsdp/bsdp_option_machine_name_test.go b/dhcpv4/bsdp/bsdp_option_machine_name_test.go new file mode 100644 index 0000000..7e4e3a1 --- /dev/null +++ b/dhcpv4/bsdp/bsdp_option_machine_name_test.go @@ -0,0 +1,44 @@ +// +build darwin + +package bsdp + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOptMachineNameInterfaceMethods(t *testing.T) { + o := OptMachineName{"somebox"} + require.Equal(t, OptionMachineName, o.Code(), "Code") + require.Equal(t, 7, o.Length(), "Length") + expectedBytes := []byte{130, 7, 's', 'o', 'm', 'e', 'b', 'o', 'x'} + require.Equal(t, expectedBytes, o.ToBytes(), "ToBytes") +} + +func TestParseOptMachineName(t *testing.T) { + data := []byte{130, 7, 's', 'o', 'm', 'e', 'b', 'o', 'x'} + o, err := ParseOptMachineName(data) + require.NoError(t, err) + require.Equal(t, &OptMachineName{"somebox"}, o) + + // Short byte stream + data = []byte{130} + _, err = ParseOptMachineName(data) + require.Error(t, err, "should get error from short byte stream") + + // Wrong code + data = []byte{54, 1, 1} + _, err = ParseOptMachineName(data) + require.Error(t, err, "should get error from wrong code") + + // Bad length + data = []byte{130, 5, 1} + _, err = ParseOptMachineName(data) + require.Error(t, err, "should get error from bad length") +} + +func TestOptMachineNameString(t *testing.T) { + o := OptMachineName{"somebox"} + require.Equal(t, "BSDP Machine Name -> somebox", o.String()) +} diff --git a/dhcpv4/bsdp/bsdp_option_server_identifier.go b/dhcpv4/bsdp/bsdp_option_server_identifier.go new file mode 100644 index 0000000..2f7b1bd --- /dev/null +++ b/dhcpv4/bsdp/bsdp_option_server_identifier.go @@ -0,0 +1,57 @@ +// +build darwin + +package bsdp + +import ( + "fmt" + "net" + + "github.com/insomniacslk/dhcp/dhcpv4" +) + +// OptServerIdentifier represents an option encapsulating the server identifier. +type OptServerIdentifier struct { + ServerID net.IP +} + +// ParseOptServerIdentifier returns a new OptServerIdentifier from a byte +// stream, or error if any. +func ParseOptServerIdentifier(data []byte) (*OptServerIdentifier, error) { + if len(data) < 2 { + return nil, dhcpv4.ErrShortByteStream + } + code := dhcpv4.OptionCode(data[0]) + if code != OptionServerIdentifier { + return nil, fmt.Errorf("expected code %v, got %v", OptionServerIdentifier, code) + } + length := int(data[1]) + if length != 4 { + return nil, fmt.Errorf("unexpected length: expected 4, got %v", length) + } + if len(data) < 6 { + return nil, dhcpv4.ErrShortByteStream + } + return &OptServerIdentifier{ServerID: net.IP(data[2 : 2+length])}, nil +} + +// Code returns the option code. +func (o *OptServerIdentifier) Code() dhcpv4.OptionCode { + return OptionServerIdentifier +} + +// ToBytes returns a serialized stream of bytes for this option. +func (o *OptServerIdentifier) ToBytes() []byte { + ret := []byte{byte(o.Code()), byte(o.Length())} + return append(ret, o.ServerID.To4()...) +} + +// String returns a human-readable string. +func (o *OptServerIdentifier) String() string { + return fmt.Sprintf("BSDP Server Identifier -> %v", o.ServerID.String()) +} + +// Length returns the length of the data portion (excluding option code an byte +// length). +func (o *OptServerIdentifier) Length() int { + return len(o.ServerID.To4()) +} diff --git a/dhcpv4/bsdp/bsdp_option_server_identifier_test.go b/dhcpv4/bsdp/bsdp_option_server_identifier_test.go new file mode 100644 index 0000000..8e712d9 --- /dev/null +++ b/dhcpv4/bsdp/bsdp_option_server_identifier_test.go @@ -0,0 +1,42 @@ +// +build darwin + +package bsdp + +import ( + "net" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOptServerIdentifierInterfaceMethods(t *testing.T) { + ip := net.IP{192, 168, 0, 1} + o := OptServerIdentifier{ServerID: ip} + require.Equal(t, OptionServerIdentifier, o.Code(), "Code") + expectedBytes := []byte{3, 4, 192, 168, 0, 1} + require.Equal(t, expectedBytes, o.ToBytes(), "ToBytes") + require.Equal(t, 4, o.Length(), "Length") + require.Equal(t, "BSDP Server Identifier -> 192.168.0.1", o.String(), "String") +} + +func TestParseOptServerIdentifier(t *testing.T) { + var ( + o *OptServerIdentifier + err error + ) + o, err = ParseOptServerIdentifier([]byte{}) + require.Error(t, err, "empty byte stream") + + o, err = ParseOptServerIdentifier([]byte{3, 4, 192}) + require.Error(t, err, "short byte stream") + + o, err = ParseOptServerIdentifier([]byte{3, 3, 192, 168, 0, 1}) + require.Error(t, err, "wrong IP length") + + o, err = ParseOptServerIdentifier([]byte{53, 4, 192, 168, 1}) + require.Error(t, err, "wrong option code") + + o, err = ParseOptServerIdentifier([]byte{3, 4, 192, 168, 0, 1}) + require.NoError(t, err) + require.Equal(t, net.IP{192, 168, 0, 1}, o.ServerID) +} diff --git a/dhcpv4/bsdp/bsdp_option_server_priority.go b/dhcpv4/bsdp/bsdp_option_server_priority.go new file mode 100644 index 0000000..dc03c28 --- /dev/null +++ b/dhcpv4/bsdp/bsdp_option_server_priority.go @@ -0,0 +1,58 @@ +// +build darwin + +package bsdp + +import ( + "encoding/binary" + "fmt" + + "github.com/insomniacslk/dhcp/dhcpv4" +) + +// This option implements the server identifier option +// https://tools.ietf.org/html/rfc2132 + +// OptServerPriority represents an option encapsulating the server priority. +type OptServerPriority struct { + Priority int +} + +// ParseOptServerPriority returns a new OptServerPriority from a byte stream, or +// error if any. +func ParseOptServerPriority(data []byte) (*OptServerPriority, error) { + if len(data) < 4 { + return nil, dhcpv4.ErrShortByteStream + } + code := dhcpv4.OptionCode(data[0]) + if code != OptionServerPriority { + return nil, fmt.Errorf("expected code %v, got %v", OptionServerPriority, code) + } + length := int(data[1]) + if length != 2 { + return nil, fmt.Errorf("unexpected length: expected 2, got %v", length) + } + return &OptServerPriority{Priority: int(binary.BigEndian.Uint16(data[2:4]))}, nil +} + +// Code returns the option code. +func (o *OptServerPriority) Code() dhcpv4.OptionCode { + return OptionServerPriority +} + +// ToBytes returns a serialized stream of bytes for this option. +func (o *OptServerPriority) ToBytes() []byte { + serialized := make([]byte, 2) + binary.BigEndian.PutUint16(serialized, uint16(o.Priority)) + return append([]byte{byte(o.Code()), byte(o.Length())}, serialized...) +} + +// String returns a human-readable string. +func (o *OptServerPriority) String() string { + return fmt.Sprintf("BSDP Server Priority -> %v", o.Priority) +} + +// Length returns the length of the data portion (excluding option code an byte +// length). +func (o *OptServerPriority) Length() int { + return 2 +} diff --git a/dhcpv4/bsdp/bsdp_option_server_priority_test.go b/dhcpv4/bsdp/bsdp_option_server_priority_test.go new file mode 100644 index 0000000..e029ffc --- /dev/null +++ b/dhcpv4/bsdp/bsdp_option_server_priority_test.go @@ -0,0 +1,39 @@ +// +build darwin + +package bsdp + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOptServerPriorityInterfaceMethods(t *testing.T) { + o := OptServerPriority{Priority: 100} + require.Equal(t, OptionServerPriority, o.Code(), "Code") + require.Equal(t, []byte{4, 2, 0, 100}, o.ToBytes(), "ToBytes") + require.Equal(t, 2, o.Length(), "Length") + require.Equal(t, "BSDP Server Priority -> 100", o.String(), "String") +} + +func TestParseOptServerPriority(t *testing.T) { + var ( + o *OptServerPriority + err error + ) + o, err = ParseOptServerPriority([]byte{}) + require.Error(t, err, "empty byte stream") + + o, err = ParseOptServerPriority([]byte{4, 2, 1}) + require.Error(t, err, "short byte stream") + + o, err = ParseOptServerPriority([]byte{4, 3, 1, 1}) + require.Error(t, err, "wrong priority length") + + o, err = ParseOptServerPriority([]byte{53, 2, 168, 1}) + require.Error(t, err, "wrong option code") + + o, err = ParseOptServerPriority([]byte{4, 2, 0, 100}) + require.NoError(t, err) + require.Equal(t, 100, o.Priority) +} diff --git a/dhcpv4/bsdp/bsdp_test.go b/dhcpv4/bsdp/bsdp_test.go index ae9060d..4fed612 100644 --- a/dhcpv4/bsdp/bsdp_test.go +++ b/dhcpv4/bsdp/bsdp_test.go @@ -9,152 +9,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestParseBootImageSingleBootImage(t *testing.T) { - input := []byte{ - 0x1, 0, 0x10, 0x10, // boot image ID - 7, // len(Name) - 98, 115, 100, 112, 45, 50, 49, // byte-encoding of Name - } - bs, err := ParseBootImagesFromOption(input) - require.NoError(t, err) - require.Equal(t, len(bs), 1, "parsing single boot image should return 1") - b := bs[0] - expectedBootImageID := BootImageID{ - IsInstall: false, - ImageType: BootImageTypeMacOSX, - Index: 0x1010, - } - require.Equal(t, expectedBootImageID, b.ID) - require.Equal(t, b.Name, "bsdp-21") -} - -func TestParseBootImageMultipleBootImage(t *testing.T) { - input := []byte{ - // boot image 1 - 0x1, 0, 0x10, 0x10, // boot image ID - 7, // len(Name) - 98, 115, 100, 112, 45, 50, 49, // byte-encoding of Name - - // boot image 2 - 0x82, 0, 0x11, 0x22, // boot image ID - 8, // len(Name) - 98, 115, 100, 112, 45, 50, 50, 50, // byte-encoding of Name - } - bs, err := ParseBootImagesFromOption(input) - require.NoError(t, err) - require.Equal(t, len(bs), 2, "parsing 2 BootImages should return 2") - b1 := bs[0] - b2 := bs[1] - expectedID1 := BootImageID{ - IsInstall: false, - ImageType: BootImageTypeMacOSX, - Index: 0x1010, - } - expectedID2 := BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOSXServer, - Index: 0x1122, - } - require.Equal(t, expectedID1, b1.ID, "first BootImageID should be equal") - require.Equal(t, expectedID2, b2.ID, "second BootImageID should be equal") - require.Equal(t, "bsdp-21", b1.Name, "first BootImage name should be equal") - require.Equal(t, "bsdp-222", b2.Name, "second BootImage name should be equal") -} - -func TestParseBootImageFail(t *testing.T) { - _, err := ParseBootImagesFromOption([]byte{}) - require.Error(t, err, "parseBootImages with empty arg") - - _, err = ParseBootImagesFromOption([]byte{1, 2, 3}) - require.Error(t, err, "parseBootImages with short arg") - - _, err = ParseBootImagesFromOption([]byte{ - // boot image 1 - 0x1, 0, 0x10, 0x10, // boot image ID - 7, // len(Name) - 98, 115, 100, 112, 45, 50, // byte-encoding of Name (intentionally shorter) - - // boot image 2 - 0x82, 0, 0x11, 0x22, // boot image ID - 8, // len(Name) - 98, 115, 100, 112, 45, 50, 50, 50, // byte-encoding of Name - }) - require.Error(t, err, "parseBootImages with short arg") -} - -/* - * ParseVendorOptionsFromOptions - */ -func TestParseVendorOptions(t *testing.T) { - expectedOpts := []dhcpv4.Option{ - &dhcpv4.OptionGeneric{ - OptionCode: OptionMessageType, - Data: []byte{byte(MessageTypeList)}, - }, - &dhcpv4.OptionGeneric{ - OptionCode: OptionVersion, - Data: Version1_0, - }, - } - var expectedOptsBytes []byte - for _, opt := range expectedOpts { - expectedOptsBytes = append(expectedOptsBytes, opt.ToBytes()...) - } - recvOpts := []dhcpv4.Option{ - &dhcpv4.OptionGeneric{ - OptionCode: dhcpv4.OptionDHCPMessageType, - Data: []byte{byte(dhcpv4.MessageTypeAck)}, - }, - &dhcpv4.OptionGeneric{ - OptionCode: dhcpv4.OptionBroadcastAddress, - Data: []byte{0xff, 0xff, 0xff, 0xff}, - }, - &dhcpv4.OptionGeneric{ - OptionCode: dhcpv4.OptionVendorSpecificInformation, - Data: expectedOptsBytes, - }, - } - opts := ParseVendorOptionsFromOptions(recvOpts) - require.Equal(t, expectedOpts, opts, "Parsed vendorOpts should be the same") -} - -func TestParseVendorOptionsFromOptionsNotPresent(t *testing.T) { - expectedOpts := []dhcpv4.Option{ - dhcpv4.OptionGeneric{ - OptionCode: dhcpv4.OptionDHCPMessageType, - Data: []byte{byte(dhcpv4.MessageTypeAck)}, - }, - dhcpv4.OptionGeneric{ - OptionCode: dhcpv4.OptionBroadcastAddress, - Data: []byte{0xff, 0xff, 0xff, 0xff}, - }, - } - opts := ParseVendorOptionsFromOptions(expectedOpts) - require.Empty(t, opts, "empty vendor opts if not present in DHCP opts") -} - -func TestParseVendorOptionsFromOptionsEmpty(t *testing.T) { - opts := ParseVendorOptionsFromOptions([]dhcpv4.Option{}) - require.Empty(t, opts, "vendor opts should be empty if given an empty input") -} - -func TestParseVendorOptionsFromOptionsFail(t *testing.T) { - opts := []dhcpv4.Option{ - &dhcpv4.OptionGeneric{ - OptionCode: dhcpv4.OptionVendorSpecificInformation, - Data: []byte{ - 0x1, 0x1, 0x1, // Option 1: LIST - 0x2, 0x2, 0x01, // Option 2: Version (intentionally left short) - }, - }, - } - vendorOpts := ParseVendorOptionsFromOptions(opts) - require.Empty(t, vendorOpts, "vendor opts should be empty on parse error") -} - -/* - * ParseBootImageListFromAck - */ func TestParseBootImageListFromAck(t *testing.T) { expectedBootImages := []BootImage{ BootImage{ @@ -174,68 +28,24 @@ func TestParseBootImageListFromAck(t *testing.T) { Name: "bsdp-2", }, } - var bootImageBytes []byte - for _, image := range expectedBootImages { - bootImageBytes = append(bootImageBytes, image.ToBytes()...) - } ack, _ := dhcpv4.New() - bootImageListOpt := dhcpv4.OptionGeneric{ - OptionCode: OptionBootImageList, - Data: bootImageBytes, - } - ack.AddOption(&dhcpv4.OptionGeneric{ - OptionCode: dhcpv4.OptionVendorSpecificInformation, - Data: bootImageListOpt.ToBytes(), + ack.AddOption(&OptVendorSpecificInformation{ + []dhcpv4.Option{&OptBootImageList{expectedBootImages}}, }) images, err := ParseBootImageListFromAck(*ack) require.NoError(t, err) + require.NotEmpty(t, images, "should get BootImages") require.Equal(t, expectedBootImages, images, "should get same BootImages") } func TestParseBootImageListFromAckNoVendorOption(t *testing.T) { ack, _ := dhcpv4.New() - ack.AddOption(dhcpv4.OptionGeneric{ - OptionCode: OptionMessageType, - Data: []byte{byte(dhcpv4.MessageTypeAck)}, - }) images, err := ParseBootImageListFromAck(*ack) - require.NoError(t, err, "no vendor extensions should not return error") - require.Empty(t, images, "should not get images from ACK without Vendor extensions") -} - -func TestParseBootImageListFromAckFail(t *testing.T) { - ack, _ := dhcpv4.New() - ack.AddOption(dhcpv4.OptionGeneric{ - OptionCode: OptionMessageType, - Data: []byte{byte(dhcpv4.MessageTypeAck)}, - }) - ack.AddOption(&dhcpv4.OptionGeneric{ - OptionCode: dhcpv4.OptionVendorSpecificInformation, - Data: []byte{ - 9, // OptionBootImageList - 24, // length - // boot image 1 - 0x1, 0, 0x10, 0x10, // boot image ID - 7, // len(Name) - 98, 115, 100, 112, 45, 49, // byte-encoding of Name (intentionally short) - - // boot image 2 - 0x82, 0, 0x11, 0x22, // boot image ID - 8, // len(Name) - 98, 115, 100, 112, 45, 50, 50, 50, // byte-encoding of Name - }, - }, - ) - - images, err := ParseBootImageListFromAck(*ack) - require.Nil(t, images, "should get nil on parse error") - require.Error(t, err, "should get error on parse error") + require.NoError(t, err) + require.Empty(t, images, "no BootImages") } -/* - * Private funcs - */ func TestNeedsReplyPort(t *testing.T) { require.True(t, needsReplyPort(123)) require.False(t, needsReplyPort(0)) diff --git a/dhcpv4/bsdp/client.go b/dhcpv4/bsdp/client.go index f64f035..68d5288 100644 --- a/dhcpv4/bsdp/client.go +++ b/dhcpv4/bsdp/client.go @@ -22,6 +22,20 @@ func NewClient() *Client { } } +func castVendorOpt(ack *dhcpv4.DHCPv4) { + opts := ack.Options() + for i := 0; i < len(opts); i++ { + if opts[i].Code() == dhcpv4.OptionVendorSpecificInformation { + vendorOpt, err := ParseOptVendorSpecificInformation(opts[i].ToBytes()) + // Oh well, we tried + if err != nil { + return + } + opts[i] = vendorOpt + } + } +} + // Exchange runs a full BSDP exchange (Inform[list], Ack, Inform[select], // Ack). Returns a list of DHCPv4 structures representing the exchange. func (c *Client) Exchange(ifname string, informList *dhcpv4.DHCPv4) ([]dhcpv4.DHCPv4, error) { @@ -48,6 +62,9 @@ func (c *Client) Exchange(ifname string, informList *dhcpv4.DHCPv4) ([]dhcpv4.DH if err != nil { return conversation, err } + + // Rewrite vendor-specific option for pretty printing. + castVendorOpt(ackForList) conversation = append(conversation, *ackForList) // Parse boot images sent back by server @@ -68,6 +85,7 @@ func (c *Client) Exchange(ifname string, informList *dhcpv4.DHCPv4) ([]dhcpv4.DH // ACK[SELECT] ackForSelect, err := dhcpv4.BroadcastSendReceive(fd, informSelect, c.ReadTimeout, c.WriteTimeout) + castVendorOpt(ackForSelect) if err != nil { return conversation, err } diff --git a/dhcpv4/bsdp/option_vendor_specific_information.go b/dhcpv4/bsdp/option_vendor_specific_information.go index b6e27ac..51f68b5 100644 --- a/dhcpv4/bsdp/option_vendor_specific_information.go +++ b/dhcpv4/bsdp/option_vendor_specific_information.go @@ -5,6 +5,7 @@ package bsdp import ( "errors" "fmt" + "strings" "github.com/insomniacslk/dhcp/dhcpv4" ) @@ -26,16 +27,26 @@ func parseOption(data []byte) (dhcpv4.Option, error) { err error ) switch dhcpv4.OptionCode(data[0]) { + case OptionBootImageList: + opt, err = ParseOptBootImageList(data) + case OptionDefaultBootImageID: + opt, err = ParseOptDefaultBootImageID(data) + case OptionMachineName: + opt, err = ParseOptMachineName(data) case OptionMessageType: opt, err = ParseOptMessageType(data) - case OptionVersion: - opt, err = ParseOptVersion(data) case OptionReplyPort: opt, err = ParseOptReplyPort(data) case OptionSelectedBootImageID: opt, err = ParseOptSelectedBootImageID(data) + case OptionServerIdentifier: + opt, err = ParseOptServerIdentifier(data) + case OptionServerPriority: + opt, err = ParseOptServerPriority(data) + case OptionVersion: + opt, err = ParseOptVersion(data) default: - opt, err = dhcpv4.ParseOptionGeneric(data) + opt, err = ParseOptGeneric(data) } if err != nil { return nil, err @@ -60,7 +71,7 @@ func ParseOptVendorSpecificInformation(data []byte) (*OptVendorSpecificInformati } options := make([]dhcpv4.Option, 0, 10) - idx := 0 + idx := 2 for { if idx == len(data) { break @@ -100,9 +111,14 @@ func (o *OptVendorSpecificInformation) ToBytes() []byte { // String returns a human-readable string for this option. func (o *OptVendorSpecificInformation) String() string { - s := "Vendor Specific Information ->\n" + s := "Vendor Specific Information ->" for _, opt := range o.Options { - s += " " + opt.String() + "\n" + optString := opt.String() + // If this option has sub-structures, offset them accordingly. + if strings.Contains(optString, "\n") { + optString = strings.Replace(optString, "\n ", "\n ", -1) + } + s += "\n " + optString } return s } @@ -121,7 +137,7 @@ func (o *OptVendorSpecificInformation) Length() int { func (o *OptVendorSpecificInformation) GetOptions(code dhcpv4.OptionCode) []dhcpv4.Option { var opts []dhcpv4.Option for _, opt := range o.Options { - if o.Code() == code { + if opt.Code() == code { opts = append(opts, opt) } } diff --git a/dhcpv4/bsdp/option_vendor_specific_information_test.go b/dhcpv4/bsdp/option_vendor_specific_information_test.go new file mode 100644 index 0000000..5fbba0c --- /dev/null +++ b/dhcpv4/bsdp/option_vendor_specific_information_test.go @@ -0,0 +1,189 @@ +// +build darwin + +package bsdp + +import ( + "testing" + + "github.com/insomniacslk/dhcp/dhcpv4" + "github.com/stretchr/testify/require" +) + +func TestOptVendorSpecificInformationInterfaceMethods(t *testing.T) { + messageTypeOpt := &OptMessageType{MessageTypeList} + versionOpt := &OptVersion{Version1_1} + o := &OptVendorSpecificInformation{[]dhcpv4.Option{messageTypeOpt, versionOpt}} + require.Equal(t, dhcpv4.OptionVendorSpecificInformation, o.Code(), "Code") + require.Equal(t, 2+messageTypeOpt.Length()+2+versionOpt.Length(), o.Length(), "Length") + + expectedBytes := []byte{ + 43, // code + 7, // length + 1, 1, 1, // List option + 2, 2, 1, 1, // Version option + } + o = &OptVendorSpecificInformation{ + []dhcpv4.Option{ + &OptMessageType{MessageTypeList}, + &OptVersion{Version1_1}, + }, + } + require.Equal(t, expectedBytes, o.ToBytes(), "ToBytes") +} + +func TestParseOptVendorSpecificInformation(t *testing.T) { + var ( + o *OptVendorSpecificInformation + err error + ) + o, err = ParseOptVendorSpecificInformation([]byte{}) + require.Error(t, err, "empty byte stream") + + o, err = ParseOptVendorSpecificInformation([]byte{1, 2}) + require.Error(t, err, "short byte stream") + + o, err = ParseOptVendorSpecificInformation([]byte{53, 2, 1, 1}) + require.Error(t, err, "wrong option code") + + // Good byte stream + data := []byte{ + 43, // code + 7, // length + 1, 1, 1, // List option + 2, 2, 1, 1, // Version option + } + o, err = ParseOptVendorSpecificInformation(data) + require.NoError(t, err) + expected := &OptVendorSpecificInformation{ + []dhcpv4.Option{ + &OptMessageType{MessageTypeList}, + &OptVersion{Version1_1}, + }, + } + require.Equal(t, 2, len(o.Options), "number of parsed suboptions") + require.Equal(t, expected.Options[0].Code(), o.Options[0].Code()) + require.Equal(t, expected.Options[1].Code(), o.Options[1].Code()) + + // Short byte stream (length and data mismatch) + data = []byte{ + 43, // code + 7, // length + 1, 1, 1, // List option + 2, 2, 1, // Version option + } + o, err = ParseOptVendorSpecificInformation(data) + require.Error(t, err) +} + +func TestOptVendorSpecificInformationString(t *testing.T) { + o := &OptVendorSpecificInformation{ + []dhcpv4.Option{ + &OptMessageType{MessageTypeList}, + &OptVersion{Version1_1}, + }, + } + expectedString := "Vendor Specific Information ->\n BSDP Message Type -> LIST\n BSDP Version -> 1.1" + require.Equal(t, expectedString, o.String()) + + // Test more complicated string - sub options of sub options. + o = &OptVendorSpecificInformation{ + []dhcpv4.Option{ + &OptMessageType{MessageTypeList}, + &OptBootImageList{ + []BootImage{ + BootImage{ + ID: BootImageID{ + IsInstall: false, + ImageType: BootImageTypeMacOSX, + Index: 1001, + }, + Name: "bsdp-1", + }, + BootImage{ + ID: BootImageID{ + IsInstall: true, + ImageType: BootImageTypeMacOS9, + Index: 9009, + }, + Name: "bsdp-2", + }, + }, + }, + }, + } + expectedString = "Vendor Specific Information ->\n" + + " BSDP Message Type -> LIST\n" + + " BSDP Boot Image List ->\n" + + " bsdp-1 [1001] uninstallable macOS image\n" + + " bsdp-2 [9009] installable macOS 9 image" + require.Equal(t, expectedString, o.String()) +} + +func TestOptVendorSpecificInformationGetOptions(t *testing.T) { + // No option + o := &OptVendorSpecificInformation{ + []dhcpv4.Option{ + &OptMessageType{MessageTypeList}, + &OptVersion{Version1_1}, + }, + } + foundOpts := o.GetOptions(OptionBootImageList) + require.Empty(t, foundOpts, "should not get any options") + + // One option + o = &OptVendorSpecificInformation{ + []dhcpv4.Option{ + &OptMessageType{MessageTypeList}, + &OptVersion{Version1_1}, + }, + } + foundOpts = o.GetOptions(OptionMessageType) + require.Equal(t, 1, len(foundOpts), "should only get one option") + require.Equal(t, MessageTypeList, foundOpts[0].(*OptMessageType).Type) + + // Multiple options + o = &OptVendorSpecificInformation{ + []dhcpv4.Option{ + &OptMessageType{MessageTypeList}, + &OptVersion{Version1_1}, + &OptVersion{Version1_0}, + }, + } + foundOpts = o.GetOptions(OptionVersion) + require.Equal(t, 2, len(foundOpts), "should get two options") + require.Equal(t, Version1_1, foundOpts[0].(*OptVersion).Version) + require.Equal(t, Version1_0, foundOpts[1].(*OptVersion).Version) +} + +func TestOptVendorSpecificInformationGetOption(t *testing.T) { + // No option + o := &OptVendorSpecificInformation{ + []dhcpv4.Option{ + &OptMessageType{MessageTypeList}, + &OptVersion{Version1_1}, + }, + } + foundOpt := o.GetOption(OptionBootImageList) + require.Nil(t, foundOpt, "should not get options") + + // One option + o = &OptVendorSpecificInformation{ + []dhcpv4.Option{ + &OptMessageType{MessageTypeList}, + &OptVersion{Version1_1}, + }, + } + foundOpt = o.GetOption(OptionMessageType) + require.Equal(t, MessageTypeList, foundOpt.(*OptMessageType).Type) + + // Multiple options + o = &OptVendorSpecificInformation{ + []dhcpv4.Option{ + &OptMessageType{MessageTypeList}, + &OptVersion{Version1_1}, + &OptVersion{Version1_0}, + }, + } + foundOpt = o.GetOption(OptionVersion) + require.Equal(t, Version1_1, foundOpt.(*OptVersion).Version) +} diff --git a/dhcpv4/bsdp/types.go b/dhcpv4/bsdp/types.go index 691c903..efe99a8 100644 --- a/dhcpv4/bsdp/types.go +++ b/dhcpv4/bsdp/types.go @@ -25,18 +25,18 @@ const ( // OptionCodeToString maps BSDP OptionCodes to human-readable strings // describing what they are. var OptionCodeToString = map[dhcpv4.OptionCode]string{ - OptionMessageType: "Message Type", - OptionVersion: "Version", - OptionServerIdentifier: "Server Identifier", - OptionServerPriority: "Server Priority", - OptionReplyPort: "Reply Port", + OptionMessageType: "BSDP Message Type", + OptionVersion: "BSDP Version", + OptionServerIdentifier: "BSDP Server Identifier", + OptionServerPriority: "BSDP Server Priority", + OptionReplyPort: "BSDP Reply Port", OptionBootImageListPath: "", // Not used - OptionDefaultBootImageID: "Default Boot Image ID", - OptionSelectedBootImageID: "Selected Boot Image ID", - OptionBootImageList: "Boot Image List", - OptionNetboot1_0Firmware: "Netboot 1.0 Firmware", - OptionBootImageAttributesFilterList: "Boot Image Attributes Filter List", - OptionShadowMountPath: "Shadow Mount Path", - OptionShadowFilePath: "Shadow File Path", - OptionMachineName: "Machine Name", + OptionDefaultBootImageID: "BSDP Default Boot Image ID", + OptionSelectedBootImageID: "BSDP Selected Boot Image ID", + OptionBootImageList: "BSDP Boot Image List", + OptionNetboot1_0Firmware: "BSDP Netboot 1.0 Firmware", + OptionBootImageAttributesFilterList: "BSDP Boot Image Attributes Filter List", + OptionShadowMountPath: "BSDP Shadow Mount Path", + OptionShadowFilePath: "BSDP Shadow File Path", + OptionMachineName: "BSDP Machine Name", } diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go index 6fd0e36..3deda86 100644 --- a/dhcpv4/dhcpv4.go +++ b/dhcpv4/dhcpv4.go @@ -179,8 +179,7 @@ func NewInformForInterface(ifname string, needsBroadcast bool) (*DHCPv4, error) } d.SetClientIPAddr(localIPs[0]) - d.AddOption(&OptMessageType{MessageType: MessageTypeDiscover}) - + d.AddOption(&OptMessageType{MessageType: MessageTypeInform}) return d, nil } @@ -578,7 +577,12 @@ func (d *DHCPv4) Summary() string { ) ret += " options=\n" for _, opt := range d.options { - ret += fmt.Sprintf(" %v\n", opt.String()) + optString := opt.String() + // If this option has sub structures, offset them accordingly. + if strings.Contains(optString, "\n") { + optString = strings.Replace(optString, "\n ", "\n ", -1) + } + ret += fmt.Sprintf(" %v\n", optString) if opt.Code() == OptionEnd { break } |