summaryrefslogtreecommitdiffhomepage
path: root/dhcpv4
diff options
context:
space:
mode:
authorSean Karlage <skarlage@get9.io>2018-03-26 15:40:59 -0700
committerinsomniac <insomniacslk@users.noreply.github.com>2018-03-26 15:40:59 -0700
commitbb95a0335e44a76a1e061b5dea2b1592c55177ac (patch)
treefa3dc814cbfe44182c151b9bd2646212be2b2e91 /dhcpv4
parente920c892c48b14dcb1e50e247836b2b9a69fa929 (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.go25
-rw-r--r--dhcpv4/bsdp/bsdp_option_boot_image_list.go86
-rw-r--r--dhcpv4/bsdp/bsdp_option_boot_image_list_test.go137
-rw-r--r--dhcpv4/bsdp/bsdp_option_default_boot_image_id.go59
-rw-r--r--dhcpv4/bsdp/bsdp_option_default_boot_image_id_test.go56
-rw-r--r--dhcpv4/bsdp/bsdp_option_generic.go60
-rw-r--r--dhcpv4/bsdp/bsdp_option_generic_test.go66
-rw-r--r--dhcpv4/bsdp/bsdp_option_machine_name.go54
-rw-r--r--dhcpv4/bsdp/bsdp_option_machine_name_test.go44
-rw-r--r--dhcpv4/bsdp/bsdp_option_server_identifier.go57
-rw-r--r--dhcpv4/bsdp/bsdp_option_server_identifier_test.go42
-rw-r--r--dhcpv4/bsdp/bsdp_option_server_priority.go58
-rw-r--r--dhcpv4/bsdp/bsdp_option_server_priority_test.go39
-rw-r--r--dhcpv4/bsdp/bsdp_test.go200
-rw-r--r--dhcpv4/bsdp/client.go18
-rw-r--r--dhcpv4/bsdp/option_vendor_specific_information.go30
-rw-r--r--dhcpv4/bsdp/option_vendor_specific_information_test.go189
-rw-r--r--dhcpv4/bsdp/types.go26
-rw-r--r--dhcpv4/dhcpv4.go10
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
}