summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--dhcpv4/bsdp.go349
-rw-r--r--dhcpv4/bsdp/bsdp.go336
-rw-r--r--dhcpv4/bsdp/bsdp_test.go362
-rw-r--r--dhcpv4/bsdp/client.go (renamed from dhcpv4/bsdp_client.go)52
-rw-r--r--dhcpv4/bsdp/types.go68
-rw-r--r--dhcpv4/bsdp_test.go297
-rw-r--r--dhcpv4/client.go12
-rw-r--r--dhcpv4/dhcpv4.go44
-rw-r--r--dhcpv4/dhcpv4_test.go23
-rw-r--r--dhcpv4/options.go43
-rw-r--r--dhcpv4/options_test.go42
-rw-r--r--dhcpv4/types.go24
12 files changed, 852 insertions, 800 deletions
diff --git a/dhcpv4/bsdp.go b/dhcpv4/bsdp.go
deleted file mode 100644
index 4096ce6..0000000
--- a/dhcpv4/bsdp.go
+++ /dev/null
@@ -1,349 +0,0 @@
-// +build darwin
-
-package dhcpv4
-
-// Implements Apple's netboot protocol BSDP (Boot Service Discovery Protocol).
-// Canonical implementation is defined here:
-// http://opensource.apple.com/source/bootp/bootp-198.1/Documentation/BSDP.doc
-
-import (
- "encoding/binary"
- "fmt"
- "syscall"
-)
-
-// Options (occur as sub-options of DHCP option 43).
-const (
- BSDPOptionMessageType OptionCode = iota + 1
- BSDPOptionVersion
- BSDPOptionServerIdentifier
- BSDPOptionServerPriority
- BSDPOptionReplyPort
- BSDPOptionBootImageListPath // Not used
- BSDPOptionDefaultBootImageID
- BSDPOptionSelectedBootImageID
- BSDPOptionBootImageList
- BSDPOptionNetboot1_0Firmware
- BSDPOptionBootImageAttributesFilterList
- BSDPOptionShadowMountPath OptionCode = 128
- BSDPOptionShadowFilePath OptionCode = 129
- BSDPOptionMachineName OptionCode = 130
-)
-
-// Versions
-var (
- BSDPVersion1_0 = []byte{1, 0}
- BSDPVersion1_1 = []byte{1, 1}
-)
-
-// BSDP message types
-const (
- BSDPMessageTypeList byte = iota + 1
- BSDPMessageTypeSelect
- BSDPMessageTypeFailed
-)
-
-// Boot image kinds
-const (
- BSDPBootImageMacOS9 byte = iota
- BSDPBootImageMacOSX
- BSDPBootImageMacOSXServer
- BSDPBootImageHardwareDiagnostics
- // 0x4 - 0x7f are reserved for future use.
-)
-
-// BootImageID describes a boot image ID - whether it's an install image and
-// what kind of boot image (e.g. OS 9, macOS, hardware diagnostics)
-type BootImageID struct {
- isInstall bool
- imageKind byte
- index uint16
-}
-
-// toBytes serializes a BootImageID to network-order bytes.
-func (b BootImageID) toBytes() (bytes []byte) {
- bytes = make([]byte, 4)
- // Attributes.
- if b.isInstall {
- bytes[0] |= 0x80
- }
- bytes[0] |= b.imageKind
-
- // Index
- binary.BigEndian.PutUint16(bytes[2:], b.index)
- return
-}
-
-// BootImageIDFromBytes deserializes a collection of 4 bytes to a BootImageID.
-func bootImageIDFromBytes(bytes []byte) BootImageID {
- return BootImageID{
- isInstall: bytes[0]&0x80 != 0,
- imageKind: bytes[0] & 0x7f,
- index: binary.BigEndian.Uint16(bytes[2:]),
- }
-}
-
-// BootImage describes a boot image - contains the boot image ID and the name.
-type BootImage struct {
- ID BootImageID
- // This is a utf-8 string.
- Name string
-}
-
-// toBytes converts a BootImage to a slice of bytes.
-func (b *BootImage) toBytes() (bytes []byte) {
- idBytes := b.ID.toBytes()
- bytes = append(bytes, idBytes[:]...)
- bytes = append(bytes, byte(len(b.Name)))
- bytes = append(bytes, []byte(b.Name)...)
- return
-}
-
-// BootImageFromBytes returns a deserialized BootImage struct from bytes as well
-// as the number of bytes read from the slice.
-func bootImageFromBytes(bytes []byte) (*BootImage, int, error) {
- // If less than length of boot image ID and count, it's probably invalid.
- if len(bytes) < 5 {
- return nil, 0, fmt.Errorf("not enough bytes for BootImage")
- }
- imageID := bootImageIDFromBytes(bytes[:4])
- nameLength := int(bytes[4])
- if 5+nameLength > len(bytes) {
- return nil, 0, fmt.Errorf("not enough bytes for BootImage")
- }
- name := string(bytes[5 : 5+nameLength])
- return &BootImage{ID: imageID, Name: name}, 5 + nameLength, nil
-}
-
-// makeVendorClassIdentifier calls the sysctl syscall on macOS to get the
-// platform model.
-func makeVendorClassIdentifier() (string, error) {
- // Fetch hardware model for class ID.
- hwModel, err := syscall.Sysctl("hw.model")
- if err != nil {
- return "", err
- }
- vendorClassID := fmt.Sprintf("AAPLBSDPC/i386/%s", hwModel)
- return vendorClassID, nil
-}
-
-// parseBootImagesFromBSDPOption parses data from the BSDPOptionBootImageList
-// option and returns a list of BootImages.
-func parseBootImagesFromBSDPOption(data []byte) ([]BootImage, error) {
- // Should at least have the # bytes of boot images.
- if len(data) < 4 {
- return nil, fmt.Errorf("invalid length boot image list")
- }
-
- readByteCount := 0
- start := data
- var bootImages []BootImage
- for {
- bootImage, readBytes, err := bootImageFromBytes(start)
- if err != nil {
- return nil, err
- }
- bootImages = append(bootImages, *bootImage)
- readByteCount += readBytes
- if readByteCount+1 >= len(data) {
- break
- }
- start = start[readByteCount:]
- }
-
- return bootImages, nil
-}
-
-// parseVendorOptionsFromOptions extracts the sub-options list of the vendor-
-// specific options from the larger DHCP options list.
-func parseVendorOptionsFromOptions(options []Option) []Option {
- var vendorOpts []Option
- var err error
- for _, opt := range options {
- if opt.Code == OptionVendorSpecificInformation {
- vendorOpts, err = OptionsFromBytes(opt.Data)
- if err != nil {
- return []Option{}
- }
- break
- }
- }
- return vendorOpts
-}
-
-// 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) ([]BootImage, error) {
- var bootImages []BootImage
- vendorOpts := parseVendorOptionsFromOptions(ack.options)
- for _, opt := range vendorOpts {
- if opt.Code == BSDPOptionBootImageList {
- images, err := parseBootImagesFromBSDPOption(opt.Data)
- if err != nil {
- return nil, err
- }
- bootImages = append(bootImages, images...)
- }
- }
-
- return bootImages, nil
-}
-
-// NewInformListForInterface creates a new INFORM packet for interface ifname
-// with configuration options specified by config.
-func NewInformListForInterface(iface string, replyPort uint16) (*DHCPv4, error) {
- d, err := NewInformForInterface(iface /* needsBroadcast */, false)
- if err != nil {
- return nil, err
- }
-
- // These are vendor-specific options used to pass along BSDP information.
- vendorOpts := []Option{
- Option{
- Code: BSDPOptionMessageType,
- Data: []byte{BSDPMessageTypeList},
- },
- Option{
- Code: BSDPOptionVersion,
- Data: BSDPVersion1_1,
- },
- }
-
- // If specified, replyPort MUST be a priviledged port.
- if replyPort != 0 && replyPort != ClientPort {
- if replyPort >= 1024 {
- return nil, fmt.Errorf("replyPort must be a priviledged port (< 1024)")
- }
- bytes := make([]byte, 3)
- bytes[0] = 2
- binary.BigEndian.PutUint16(bytes[1:], replyPort)
- d.AddOption(Option{
- Code: BSDPOptionReplyPort,
- Data: bytes,
- })
- }
- d.AddOption(Option{
- Code: OptionVendorSpecificInformation,
- Data: OptionsToBytes(vendorOpts),
- })
-
- d.AddOption(Option{
- Code: OptionParameterRequestList,
- Data: []byte{OptionVendorSpecificInformation, OptionClassIdentifier},
- })
-
- u16 := make([]byte, 2)
- binary.BigEndian.PutUint16(u16, 1500)
- d.AddOption(Option{
- Code: OptionMaximumDHCPMessageSize,
- Data: u16,
- })
-
- vendorClassID, err := makeVendorClassIdentifier()
- if err != nil {
- return nil, err
- }
- d.AddOption(Option{
- Code: OptionClassIdentifier,
- Data: []byte(vendorClassID),
- })
-
- d.AddOption(Option{Code: OptionEnd})
- return d, nil
-}
-
-// InformSelectForAck constructs an INFORM[SELECT] packet given an ACK to the
-// previously-sent INFORM[LIST] with BSDPConfig config.
-func InformSelectForAck(ack DHCPv4, replyPort uint16, selectedImage BootImage) (*DHCPv4, error) {
- d, err := New()
- if err != nil {
- return nil, err
- }
- d.SetOpcode(OpcodeBootRequest)
- d.SetHwType(ack.HwType())
- d.SetHwAddrLen(ack.HwAddrLen())
- clientHwAddr := ack.ClientHwAddr()
- d.SetClientHwAddr(clientHwAddr[:])
- d.SetTransactionID(ack.TransactionID())
- if ack.IsBroadcast() {
- d.SetBroadcast()
- } else {
- d.SetUnicast()
- }
-
- // Data for BSDPOptionSelectedBootImageID
- vendorOpts := []Option{
- Option{
- Code: BSDPOptionMessageType,
- Data: []byte{BSDPMessageTypeSelect},
- },
- Option{
- Code: BSDPOptionVersion,
- Data: BSDPVersion1_1,
- },
- Option{
- Code: BSDPOptionSelectedBootImageID,
- Data: selectedImage.ID.toBytes(),
- },
- }
-
- // Find server IP address
- var serverIP []byte
- for _, opt := range ack.options {
- if opt.Code == OptionServerIdentifier {
- serverIP = make([]byte, 4)
- copy(serverIP, opt.Data)
- }
- }
- if len(serverIP) == 0 {
- return nil, fmt.Errorf("could not parse server identifier from ACK")
- }
- vendorOpts = append(vendorOpts, Option{
- Code: BSDPOptionServerIdentifier,
- Data: serverIP,
- })
-
- // Validate replyPort if requested.
- if replyPort != 0 && replyPort != ClientPort {
- // replyPort MUST be a priviledged port.
- if replyPort >= 1024 {
- return nil, fmt.Errorf("replyPort must be a priviledged port")
- }
- bytes := make([]byte, 3)
- bytes[0] = 2
- binary.BigEndian.PutUint16(bytes[1:], replyPort)
- vendorOpts = append(vendorOpts, Option{
- Code: BSDPOptionReplyPort,
- Data: bytes,
- })
- }
-
- vendorClassID, err := makeVendorClassIdentifier()
- if err != nil {
- return nil, err
- }
- d.AddOption(Option{
- Code: OptionClassIdentifier,
- Data: []byte(vendorClassID),
- })
- d.AddOption(Option{
- Code: OptionParameterRequestList,
- Data: []byte{
- OptionSubnetMask,
- OptionRouter,
- OptionBootfileName,
- OptionVendorSpecificInformation,
- OptionClassIdentifier,
- },
- })
- d.AddOption(Option{
- Code: OptionDHCPMessageType,
- Data: []byte{MessageTypeInform},
- })
- d.AddOption(Option{
- Code: OptionVendorSpecificInformation,
- Data: OptionsToBytes(vendorOpts),
- })
- d.AddOption(Option{Code: OptionEnd})
- return d, nil
-}
diff --git a/dhcpv4/bsdp/bsdp.go b/dhcpv4/bsdp/bsdp.go
new file mode 100644
index 0000000..6c8d00f
--- /dev/null
+++ b/dhcpv4/bsdp/bsdp.go
@@ -0,0 +1,336 @@
+// +build darwin
+
+package bsdp
+
+// Implements Apple's netboot protocol BSDP (Boot Service Discovery Protocol).
+// Canonical implementation is defined here:
+// http://opensource.apple.com/source/bootp/bootp-198.1/Documentation/BSDP.doc
+
+import (
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "log"
+ "net"
+ "syscall"
+
+ "github.com/insomniacslk/dhcp/dhcpv4"
+)
+
+// MaxDHCPMessageSize is the size set in DHCP option 57 (DHCP Maximum Message Size).
+// BSDP includes its own sub-option (12) to indicate to NetBoot servers that the
+// client can support larger message sizes, and modern NetBoot servers will
+// prefer this BSDP-specific option over the DHCP standard option.
+const MaxDHCPMessageSize = 1500
+
+// BootImageID describes a boot image ID - whether it's an install image and
+// what kind of boot image (e.g. OS 9, macOS, hardware diagnostics)
+type BootImageID struct {
+ IsInstall bool
+ ImageType BootImageType
+ Index uint16
+}
+
+// ToBytes serializes a BootImageID to network-order bytes.
+func (b BootImageID) ToBytes() []byte {
+ bytes := make([]byte, 4)
+ if b.IsInstall {
+ bytes[0] |= 0x80
+ }
+ bytes[0] |= byte(b.ImageType)
+ binary.BigEndian.PutUint16(bytes[2:], b.Index)
+ return bytes
+}
+
+// BootImageIDFromBytes deserializes a collection of 4 bytes to a BootImageID.
+func BootImageIDFromBytes(bytes []byte) (*BootImageID, error) {
+ if len(bytes) < 4 {
+ return nil, fmt.Errorf("not enough bytes to serialize BootImageID")
+ }
+ return &BootImageID{
+ IsInstall: bytes[0]&0x80 != 0,
+ ImageType: BootImageType(bytes[0] & 0x7f),
+ Index: binary.BigEndian.Uint16(bytes[2:]),
+ }, nil
+}
+
+// BootImage describes a boot image - contains the boot image ID and the name.
+type BootImage struct {
+ ID BootImageID
+ Name string
+}
+
+// ToBytes converts a BootImage to a slice of bytes.
+func (b *BootImage) ToBytes() []byte {
+ bytes := b.ID.ToBytes()
+ bytes = append(bytes, byte(len(b.Name)))
+ bytes = append(bytes, []byte(b.Name)...)
+ return bytes
+}
+
+// BootImageFromBytes returns a deserialized BootImage struct from bytes.
+func BootImageFromBytes(bytes []byte) (*BootImage, error) {
+ // Should at least contain 4 bytes of BootImageID + byte for length of
+ // boot image name.
+ if len(bytes) < 5 {
+ return nil, fmt.Errorf("not enough bytes to serialize BootImage")
+ }
+ imageID, err := BootImageIDFromBytes(bytes[:4])
+ if err != nil {
+ return nil, err
+ }
+ nameLength := int(bytes[4])
+ if 5+nameLength > len(bytes) {
+ return nil, fmt.Errorf("not enough bytes for BootImage")
+ }
+ name := string(bytes[5 : 5+nameLength])
+ return &BootImage{ID: *imageID, Name: name}, nil
+}
+
+// makeVendorClassIdentifier calls the sysctl syscall on macOS to get the
+// platform model.
+func makeVendorClassIdentifier() (string, error) {
+ // Fetch hardware model for class ID.
+ hwModel, err := syscall.Sysctl("hw.model")
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("AAPLBSDPC/i386/%s", hwModel), nil
+}
+
+// ParseBootImagesFromOption parses data from the BSDPOptionBootImageList
+// option and returns a list of BootImages.
+func ParseBootImagesFromOption(data []byte) ([]BootImage, error) {
+ // Should at least have the # bytes of boot images.
+ if len(data) < 4 {
+ return nil, fmt.Errorf("invalid length boot image list")
+ }
+
+ var (
+ readByteCount = 0
+ start = data
+ bootImages []BootImage
+ )
+ for {
+ bootImage, err := BootImageFromBytes(start)
+ if err != nil {
+ return nil, err
+ }
+ bootImages = append(bootImages, *bootImage)
+ // Read BootImageID + name length + name
+ readByteCount += 4 + 1 + len(bootImage.Name)
+ if readByteCount+1 >= len(data) {
+ break
+ }
+ start = start[readByteCount:]
+ }
+
+ return bootImages, nil
+}
+
+// ParseVendorOptionsFromOptions extracts the sub-options list of the vendor-
+// specific options from the larger DHCP options list.
+// TODO: Implement options.GetOneOption for dhcpv4.
+func ParseVendorOptionsFromOptions(options []dhcpv4.Option) []dhcpv4.Option {
+ var (
+ vendorOpts []dhcpv4.Option
+ err error
+ )
+ for _, opt := range options {
+ if opt.Code == dhcpv4.OptionVendorSpecificInformation {
+ vendorOpts, err = dhcpv4.OptionsFromBytesWithoutMagicCookie(opt.Data)
+ if err != nil {
+ log.Println("Warning: could not parse vendor options in DHCP options")
+ return []dhcpv4.Option{}
+ }
+ break
+ }
+ }
+ return vendorOpts
+}
+
+// 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
+ for _, opt := range ParseVendorOptionsFromOptions(ack.Options()) {
+ if opt.Code == OptionBootImageList {
+ images, err := ParseBootImagesFromOption(opt.Data)
+ if err != nil {
+ return nil, err
+ }
+ bootImages = append(bootImages, images...)
+ }
+ }
+
+ return bootImages, nil
+}
+
+func needsReplyPort(replyPort uint16) bool {
+ return replyPort != 0 && replyPort != dhcpv4.ClientPort
+}
+
+func serializeReplyPort(replyPort uint16) []byte {
+ bytes := make([]byte, 2)
+ binary.BigEndian.PutUint16(bytes, replyPort)
+ return bytes
+}
+
+// NewInformListForInterface creates a new INFORM packet for interface ifname
+// with configuration options specified by config.
+func NewInformListForInterface(iface string, replyPort uint16) (*dhcpv4.DHCPv4, error) {
+ d, err := dhcpv4.NewInformForInterface(iface /* needsBroadcast = */, false)
+ if err != nil {
+ return nil, err
+ }
+
+ // Validate replyPort first
+ if needsReplyPort(replyPort) && replyPort >= 1024 {
+ return nil, errors.New("replyPort must be a privileged port")
+ }
+
+ // These are vendor-specific options used to pass along BSDP information.
+ vendorOpts := []dhcpv4.Option{
+ dhcpv4.Option{
+ Code: OptionMessageType,
+ Data: []byte{byte(MessageTypeList)},
+ },
+ dhcpv4.Option{
+ Code: OptionVersion,
+ Data: Version1_1,
+ },
+ }
+
+ if needsReplyPort(replyPort) {
+ vendorOpts = append(vendorOpts,
+ dhcpv4.Option{
+ Code: OptionReplyPort,
+ Data: serializeReplyPort(replyPort),
+ },
+ )
+ }
+ d.AddOption(dhcpv4.Option{
+ Code: dhcpv4.OptionVendorSpecificInformation,
+ Data: dhcpv4.OptionsToBytesWithoutMagicCookie(vendorOpts),
+ })
+
+ d.AddOption(dhcpv4.Option{
+ Code: dhcpv4.OptionParameterRequestList,
+ Data: []byte{
+ dhcpv4.OptionVendorSpecificInformation,
+ dhcpv4.OptionClassIdentifier,
+ },
+ })
+
+ u16 := make([]byte, 2)
+ binary.BigEndian.PutUint16(u16, MaxDHCPMessageSize)
+ d.AddOption(dhcpv4.Option{
+ Code: dhcpv4.OptionMaximumDHCPMessageSize,
+ Data: u16,
+ })
+
+ vendorClassID, err := makeVendorClassIdentifier()
+ if err != nil {
+ return nil, err
+ }
+ d.AddOption(dhcpv4.Option{
+ Code: dhcpv4.OptionClassIdentifier,
+ Data: []byte(vendorClassID),
+ })
+
+ d.AddOption(dhcpv4.Option{Code: dhcpv4.OptionEnd})
+ return d, nil
+}
+
+// InformSelectForAck constructs an INFORM[SELECT] packet given an ACK to the
+// previously-sent INFORM[LIST] with Config config.
+func InformSelectForAck(ack dhcpv4.DHCPv4, replyPort uint16, selectedImage BootImage) (*dhcpv4.DHCPv4, error) {
+ d, err := dhcpv4.New()
+ if err != nil {
+ return nil, err
+ }
+
+ if needsReplyPort(replyPort) && replyPort >= 1024 {
+ return nil, errors.New("replyPort must be a privilegded port")
+ }
+ d.SetOpcode(dhcpv4.OpcodeBootRequest)
+ d.SetHwType(ack.HwType())
+ d.SetHwAddrLen(ack.HwAddrLen())
+ clientHwAddr := ack.ClientHwAddr()
+ d.SetClientHwAddr(clientHwAddr[:])
+ d.SetTransactionID(ack.TransactionID())
+ if ack.IsBroadcast() {
+ d.SetBroadcast()
+ } else {
+ d.SetUnicast()
+ }
+
+ // Data for OptionSelectedBootImageID
+ vendorOpts := []dhcpv4.Option{
+ dhcpv4.Option{
+ Code: OptionMessageType,
+ Data: []byte{byte(MessageTypeSelect)},
+ },
+ dhcpv4.Option{
+ Code: OptionVersion,
+ Data: Version1_1,
+ },
+ dhcpv4.Option{
+ Code: OptionSelectedBootImageID,
+ Data: selectedImage.ID.ToBytes(),
+ },
+ }
+
+ // Find server IP address
+ var serverIP net.IP
+ // TODO replace this loop with `ack.GetOneOption(OptionBootImageList)`
+ for _, opt := range ack.Options() {
+ if opt.Code == dhcpv4.OptionServerIdentifier {
+ serverIP = net.IP(opt.Data)
+ }
+ }
+ if serverIP.To4() == nil {
+ return nil, fmt.Errorf("could not parse server identifier from ACK")
+ }
+ vendorOpts = append(vendorOpts, dhcpv4.Option{
+ Code: OptionServerIdentifier,
+ Data: serverIP,
+ })
+
+ // Validate replyPort if requested.
+ if needsReplyPort(replyPort) {
+ vendorOpts = append(vendorOpts, dhcpv4.Option{
+ Code: OptionReplyPort,
+ Data: serializeReplyPort(replyPort),
+ })
+ }
+
+ vendorClassID, err := makeVendorClassIdentifier()
+ if err != nil {
+ return nil, err
+ }
+ d.AddOption(dhcpv4.Option{
+ Code: dhcpv4.OptionClassIdentifier,
+ Data: []byte(vendorClassID),
+ })
+ d.AddOption(dhcpv4.Option{
+ Code: dhcpv4.OptionParameterRequestList,
+ Data: []byte{
+ dhcpv4.OptionSubnetMask,
+ dhcpv4.OptionRouter,
+ dhcpv4.OptionBootfileName,
+ dhcpv4.OptionVendorSpecificInformation,
+ dhcpv4.OptionClassIdentifier,
+ },
+ })
+ d.AddOption(dhcpv4.Option{
+ Code: dhcpv4.OptionDHCPMessageType,
+ Data: []byte{byte(dhcpv4.MessageTypeInform)},
+ })
+ d.AddOption(dhcpv4.Option{
+ Code: dhcpv4.OptionVendorSpecificInformation,
+ Data: dhcpv4.OptionsToBytesWithoutMagicCookie(vendorOpts),
+ })
+ d.AddOption(dhcpv4.Option{Code: dhcpv4.OptionEnd})
+ return d, nil
+}
diff --git a/dhcpv4/bsdp/bsdp_test.go b/dhcpv4/bsdp/bsdp_test.go
new file mode 100644
index 0000000..b66efbc
--- /dev/null
+++ b/dhcpv4/bsdp/bsdp_test.go
@@ -0,0 +1,362 @@
+package bsdp
+
+import (
+ "testing"
+
+ "github.com/insomniacslk/dhcp/dhcpv4"
+ "github.com/stretchr/testify/assert"
+)
+
+/*
+ * BootImageID
+ */
+func TestBootImageIDToBytes(t *testing.T) {
+ b := BootImageID{
+ IsInstall: true,
+ ImageType: BootImageTypeMacOSX,
+ Index: 0x1000,
+ }
+ actual := b.ToBytes()
+ expected := []byte{0x81, 0, 0x10, 0}
+ assert.Equal(t, actual, expected, "serialized BootImageID should be equal")
+
+ b.IsInstall = false
+ actual = b.ToBytes()
+ expected = []byte{0x01, 0, 0x10, 0}
+ assert.Equal(t, actual, expected, "serialized BootImageID should be equal")
+}
+
+func TestBootImageIDFromBytes(t *testing.T) {
+ b := BootImageID{
+ IsInstall: false,
+ ImageType: BootImageTypeMacOSX,
+ Index: 0x1000,
+ }
+ newBootImage, err := BootImageIDFromBytes(b.ToBytes())
+ assert.Nil(t, err, "error from BootImageIDFromBytes")
+ assert.Equal(t, b, *newBootImage, "deserialized BootImage should be equal")
+
+ b = BootImageID{
+ IsInstall: true,
+ ImageType: BootImageTypeMacOSX,
+ Index: 0x1011,
+ }
+ newBootImage, err = BootImageIDFromBytes(b.ToBytes())
+ assert.Nil(t, err, "error from BootImageIDFromBytes")
+ assert.Equal(t, b, *newBootImage, "deserialized BootImage should be equal")
+}
+
+func TestBootImageIDFromBytesFail(t *testing.T) {
+ serialized := []byte{0x81, 0, 0x10} // intentionally left short
+ deserialized, err := BootImageIDFromBytes(serialized)
+ assert.Nil(t, deserialized, "BootImageIDFromBytes should return nil on failed deserialization")
+ assert.NotNil(t, err, "BootImageIDFromBytes should return err on failed deserialization")
+}
+
+/*
+ * BootImage
+ */
+func TestBootImageToBytes(t *testing.T) {
+ b := BootImage{
+ ID: BootImageID{
+ IsInstall: true,
+ ImageType: BootImageTypeMacOSX,
+ Index: 0x1000,
+ },
+ Name: "bsdp-1",
+ }
+ expected := []byte{
+ 0x81, 0, 0x10, 0, // boot image ID
+ 6, // len(Name)
+ 98, 115, 100, 112, 45, 49, // byte-encoding of Name
+ }
+ actual := b.ToBytes()
+ assert.Equal(t, actual, expected, "serialized BootImage should be equal")
+
+ b = BootImage{
+ ID: BootImageID{
+ IsInstall: false,
+ ImageType: BootImageTypeMacOSX,
+ Index: 0x1010,
+ },
+ Name: "bsdp-21",
+ }
+ expected = []byte{
+ 0x1, 0, 0x10, 0x10, // boot image ID
+ 7, // len(Name)
+ 98, 115, 100, 112, 45, 50, 49, // byte-encoding of Name
+ }
+ actual = b.ToBytes()
+ assert.Equal(t, actual, expected, "serialized BootImage should be equal")
+}
+
+func TestBootImageFromBytes(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
+ }
+ b, err := BootImageFromBytes(input)
+ assert.Nil(t, err, "error while marshalling BootImage")
+ expectedBootImage := BootImage{
+ ID: BootImageID{
+ IsInstall: false,
+ ImageType: BootImageTypeMacOSX,
+ Index: 0x1010,
+ },
+ Name: "bsdp-21",
+ }
+ assert.Equal(t, *b, expectedBootImage, "invalid marshalling of BootImage")
+}
+
+func TestBootImageFromBytesOnlyBootImageID(t *testing.T) {
+ // Only a BootImageID, nothing else.
+ input := []byte{0x1, 0, 0x10, 0x10}
+ b, err := BootImageFromBytes(input)
+ assert.Nil(t, b, "short bytestream should return nil BootImageID")
+ assert.NotNil(t, err, "short bytestream should return error")
+}
+
+func TestBootImageFromBytesShortBootImage(t *testing.T) {
+ input := []byte{
+ 0x1, 0, 0x10, 0x10, // boot image ID
+ 7, // len(Name)
+ 98, 115, 100, 112, 45, 50, // Name bytes (intentionally off-by-one)
+ }
+ b, err := BootImageFromBytes(input)
+ assert.Nil(t, b, "short bytestream should return nil BootImageID")
+ assert.NotNil(t, err, "short bytestream should return error")
+}
+
+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)
+ assert.Nil(t, err, "parsing single boot image should not return error")
+ assert.Equal(t, len(bs), 1, "parsing single boot image should return 1")
+ b := bs[0]
+ expectedBootImage := BootImageID{
+ IsInstall: false,
+ ImageType: BootImageTypeMacOSX,
+ Index: 0x1010,
+ }
+ assert.Equal(t, b.ID, expectedBootImage, "parsed BootImageIDs should be equal")
+ assert.Equal(t, b.Name, "bsdp-21", "BootImage name should be equal")
+}
+
+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)
+ assert.Nil(t, err, "parsing multiple BootImages should not return error")
+ assert.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,
+ }
+ assert.Equal(t, b1.ID, expectedID1, "first BootImageID should be equal")
+ assert.Equal(t, b2.ID, expectedID2, "second BootImageID should be equal")
+ assert.Equal(t, b1.Name, "bsdp-21", "first BootImage name should be equal")
+ assert.Equal(t, b2.Name, "bsdp-222", "second BootImage name should be equal")
+}
+
+func TestParseBootImageFail(t *testing.T) {
+ _, err := ParseBootImagesFromOption([]byte{})
+ assert.NotNil(t, err, "parseBootImages with empty arg")
+
+ _, err = ParseBootImagesFromOption([]byte{1, 2, 3})
+ assert.NotNil(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
+ })
+ assert.NotNil(t, err, "parseBootImages with short arg")
+}
+
+/*
+ * ParseVendorOptionsFromOptions
+ */
+func TestParseVendorOptions(t *testing.T) {
+ vendorOpts := []dhcpv4.Option{
+ dhcpv4.Option{
+ Code: OptionMessageType,
+ Data: []byte{byte(MessageTypeList)},
+ },
+ dhcpv4.Option{
+ Code: OptionVersion,
+ Data: Version1_0,
+ },
+ }
+ recvOpts := []dhcpv4.Option{
+ dhcpv4.Option{
+ Code: dhcpv4.OptionDHCPMessageType,
+ Data: []byte{byte(dhcpv4.MessageTypeAck)},
+ },
+ dhcpv4.Option{
+ Code: dhcpv4.OptionBroadcastAddress,
+ Data: []byte{0xff, 0xff, 0xff, 0xff},
+ },
+ dhcpv4.Option{
+ Code: dhcpv4.OptionVendorSpecificInformation,
+ Data: dhcpv4.OptionsToBytesWithoutMagicCookie(vendorOpts),
+ },
+ }
+ opts := ParseVendorOptionsFromOptions(recvOpts)
+ assert.Equal(t, opts, vendorOpts, "Parsed vendorOpts should be the same")
+}
+
+func TestParseVendorOptionsFromOptionsNotPresent(t *testing.T) {
+ recvOpts := []dhcpv4.Option{
+ dhcpv4.Option{
+ Code: dhcpv4.OptionDHCPMessageType,
+ Data: []byte{byte(dhcpv4.MessageTypeAck)},
+ },
+ dhcpv4.Option{
+ Code: dhcpv4.OptionBroadcastAddress,
+ Data: []byte{0xff, 0xff, 0xff, 0xff},
+ },
+ }
+ opts := ParseVendorOptionsFromOptions(recvOpts)
+ assert.Empty(t, opts, "vendor opts should be empty if not present in input")
+}
+
+func TestParseVendorOptionsFromOptionsEmpty(t *testing.T) {
+ options := ParseVendorOptionsFromOptions([]dhcpv4.Option{})
+ assert.Empty(t, options, "vendor opts should be empty if given an empty input")
+}
+
+func TestParseVendorOptionsFromOptionsFail(t *testing.T) {
+ opts := []dhcpv4.Option{
+ dhcpv4.Option{
+ Code: dhcpv4.OptionVendorSpecificInformation,
+ Data: []byte{
+ 0x1, 0x1, 0x1, // Option 1: LIST
+ 0x2, 0x2, 0x01, // Option 2: Version (intentionally left short)
+ },
+ },
+ }
+ vendorOpts := ParseVendorOptionsFromOptions(opts)
+ assert.Empty(t, vendorOpts, "vendor opts should be empty on parse error")
+}
+
+/*
+ * ParseBootImageListFromAck
+ */
+func TestParseBootImageListFromAck(t *testing.T) {
+ bootImages := []BootImage{
+ BootImage{
+ ID: BootImageID{
+ IsInstall: true,
+ ImageType: BootImageTypeMacOSX,
+ Index: 0x1010,
+ },
+ Name: "bsdp-1",
+ },
+ BootImage{
+ ID: BootImageID{
+ IsInstall: false,
+ ImageType: BootImageTypeMacOS9,
+ Index: 0x1111,
+ },
+ Name: "bsdp-2",
+ },
+ }
+ var bootImageBytes []byte
+ for _, image := range bootImages {
+ bootImageBytes = append(bootImageBytes, image.ToBytes()...)
+ }
+ ack, _ := dhcpv4.New()
+ ack.AddOption(dhcpv4.Option{
+ Code: dhcpv4.OptionVendorSpecificInformation,
+ Data: dhcpv4.OptionsToBytesWithoutMagicCookie([]dhcpv4.Option{
+ dhcpv4.Option{
+ Code: OptionBootImageList,
+ Data: bootImageBytes,
+ },
+ }),
+ })
+
+ images, err := ParseBootImageListFromAck(*ack)
+ assert.Nil(t, err, "error from ParseBootImageListFromAck")
+ assert.NotNil(t, images, "parsed boot images from ack")
+ assert.Equal(t, images, bootImages, "should get same BootImages")
+}
+
+func TestParseBootImageListFromAckNoVendorOption(t *testing.T) {
+ ack, _ := dhcpv4.New()
+ ack.AddOption(dhcpv4.Option{
+ Code: OptionMessageType,
+ Data: []byte{byte(dhcpv4.MessageTypeAck)},
+ })
+ images, err := ParseBootImageListFromAck(*ack)
+ assert.Nil(t, err, "no vendor extensions should not return error")
+ assert.Empty(t, images, "should not get images from ACK without Vendor extensions")
+}
+
+func TestParseBootImageListFromAckFail(t *testing.T) {
+ ack, _ := dhcpv4.New()
+ ack.AddOption(dhcpv4.Option{
+ Code: OptionMessageType,
+ Data: []byte{byte(dhcpv4.MessageTypeAck)},
+ })
+ ack.AddOption(dhcpv4.Option{
+ Code: dhcpv4.OptionVendorSpecificInformation,
+ Data: dhcpv4.OptionsToBytesWithoutMagicCookie([]dhcpv4.Option{
+ dhcpv4.Option{
+ Code: OptionBootImageList,
+ Data: []byte{
+ // 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)
+ assert.Nil(t, images, "should get nil on parse error")
+ assert.NotNil(t, err, "should get error on parse error")
+}
+
+/*
+ * Private funcs
+ */
+func TestNeedsReplyPort(t *testing.T) {
+ assert.True(t, needsReplyPort(123), "")
+ assert.False(t, needsReplyPort(0), "")
+ assert.False(t, needsReplyPort(dhcpv4.ClientPort), "")
+}
diff --git a/dhcpv4/bsdp_client.go b/dhcpv4/bsdp/client.go
index 5c4dc00..c2e8ae0 100644
--- a/dhcpv4/bsdp_client.go
+++ b/dhcpv4/bsdp/client.go
@@ -1,25 +1,32 @@
-// +build darwin
-
-package dhcpv4
+package bsdp
import (
"fmt"
"net"
"syscall"
+
+ "github.com/insomniacslk/dhcp/dhcpv4"
)
-// BSDPExchange runs a full BSDP exchange (Inform[list], Ack, Inform[select],
+// Client is a BSDP-specific client suitable for performing BSDP exchanges.
+type Client dhcpv4.Client
+
+// Exchange runs a full BSDP exchange (Inform[list], Ack, Inform[select],
// Ack). Returns a list of DHCPv4 structures representing the exchange.
-func (c *Client) BSDPExchange(ifname string, d *DHCPv4) ([]DHCPv4, error) {
- conversation := make([]DHCPv4, 1)
+func (c *Client) Exchange(ifname string, informList *dhcpv4.DHCPv4) ([]dhcpv4.DHCPv4, error) {
+ conversation := make([]dhcpv4.DHCPv4, 1)
var err error
// INFORM[LIST]
- if d == nil {
- d, err = NewInformListForInterface(ifname, ClientPort)
+ if informList == nil {
+ informList, err = NewInformListForInterface(ifname, dhcpv4.ClientPort)
+ if err != nil {
+ return conversation, err
+ }
}
- conversation[0] = *d
+ conversation[0] = *informList
+ // TODO: deduplicate with code in dhcpv4/client.go
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
if err != nil {
return conversation, err
@@ -36,13 +43,15 @@ func (c *Client) BSDPExchange(ifname string, d *DHCPv4) ([]DHCPv4, error) {
if err != nil {
return conversation, err
}
- err = BindToInterface(fd, ifname)
+ err = dhcpv4.BindToInterface(fd, ifname)
if err != nil {
return conversation, err
}
- daddr := syscall.SockaddrInet4{Port: ClientPort, Addr: [4]byte{255, 255, 255, 255}}
- packet, err := makeRawBroadcastPacket(d.ToBytes())
+ bcast := [4]byte{}
+ copy(bcast[:], net.IPv4bcast)
+ daddr := syscall.SockaddrInet4{Port: dhcpv4.ClientPort, Addr: bcast}
+ packet, err := dhcpv4.MakeRawBroadcastPacket(informList.ToBytes())
if err != nil {
return conversation, err
}
@@ -52,16 +61,16 @@ func (c *Client) BSDPExchange(ifname string, d *DHCPv4) ([]DHCPv4, error) {
}
// ACK 1
- conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: ClientPort})
+ conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: dhcpv4.ClientPort})
if err != nil {
return conversation, err
}
defer conn.Close()
- buf := make([]byte, maxUDPReceivedPacketSize)
+ buf := make([]byte, dhcpv4.MaxUDPReceivedPacketSize)
oobdata := []byte{} // ignoring oob data
n, _, _, _, err := conn.ReadMsgUDP(buf, oobdata)
- ack1, err := FromBytes(buf[:n])
+ ack1, err := dhcpv4.FromBytes(buf[:n])
if err != nil {
return conversation, err
}
@@ -71,7 +80,6 @@ func (c *Client) BSDPExchange(ifname string, d *DHCPv4) ([]DHCPv4, error) {
// Parse boot images sent back by server
bootImages, err := ParseBootImageListFromAck(*ack1)
- fmt.Println(bootImages)
if err != nil {
return conversation, err
}
@@ -80,12 +88,12 @@ func (c *Client) BSDPExchange(ifname string, d *DHCPv4) ([]DHCPv4, error) {
}
// INFORM[SELECT]
- request, err := InformSelectForAck(*ack1, ClientPort, bootImages[0])
+ informSelect, err := InformSelectForAck(*ack1, dhcpv4.ClientPort, bootImages[0])
if err != nil {
return conversation, err
}
- conversation = append(conversation, *request)
- packet, err = makeRawBroadcastPacket(request.ToBytes())
+ conversation = append(conversation, *informSelect)
+ packet, err = dhcpv4.MakeRawBroadcastPacket(informSelect.ToBytes())
if err != nil {
return conversation, err
}
@@ -95,15 +103,15 @@ func (c *Client) BSDPExchange(ifname string, d *DHCPv4) ([]DHCPv4, error) {
}
// ACK 2
- buf = make([]byte, maxUDPReceivedPacketSize)
+ buf = make([]byte, dhcpv4.MaxUDPReceivedPacketSize)
n, _, _, _, err = conn.ReadMsgUDP(buf, oobdata)
- acknowledge, err := FromBytes(buf[:n])
+ ack2, err := dhcpv4.FromBytes(buf[:n])
if err != nil {
return conversation, err
}
// TODO match the packet content
// TODO check that the peer address matches the declared server IP and port
- conversation = append(conversation, *acknowledge)
+ conversation = append(conversation, *ack2)
return conversation, nil
}
diff --git a/dhcpv4/bsdp/types.go b/dhcpv4/bsdp/types.go
new file mode 100644
index 0000000..54f38e2
--- /dev/null
+++ b/dhcpv4/bsdp/types.go
@@ -0,0 +1,68 @@
+package bsdp
+
+import "github.com/insomniacslk/dhcp/dhcpv4"
+
+// Options (occur as sub-options of DHCP option 43).
+const (
+ OptionMessageType dhcpv4.OptionCode = iota + 1
+ OptionVersion
+ OptionServerIdentifier
+ OptionServerPriority
+ OptionReplyPort
+ OptionBootImageListPath // Not used
+ OptionDefaultBootImageID
+ OptionSelectedBootImageID
+ OptionBootImageList
+ OptionNetboot1_0Firmware
+ OptionBootImageAttributesFilterList
+ OptionShadowMountPath dhcpv4.OptionCode = 128
+ OptionShadowFilePath dhcpv4.OptionCode = 129
+ OptionMachineName dhcpv4.OptionCode = 130
+)
+
+// Versions
+var (
+ Version1_0 = []byte{1, 0}
+ Version1_1 = []byte{1, 1}
+)
+
+// MessageType represents the different BSDP message types.
+type MessageType byte
+
+// BSDP Message types - e.g. LIST, SELECT, FAILED
+const (
+ MessageTypeList MessageType = iota + 1
+ MessageTypeSelect
+ MessageTypeFailed
+)
+
+// BootImageType represents the different BSDP boot image types.
+type BootImageType byte
+
+// Different types of BootImages - e.g. for different flavors of macOS.
+const (
+ BootImageTypeMacOS9 BootImageType = iota
+ BootImageTypeMacOSX
+ BootImageTypeMacOSXServer
+ BootImageTypeHardwareDiagnostics
+ // 0x4 - 0x7f are reserved for future use.
+)
+
+// 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",
+ 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",
+}
diff --git a/dhcpv4/bsdp_test.go b/dhcpv4/bsdp_test.go
deleted file mode 100644
index 80da518..0000000
--- a/dhcpv4/bsdp_test.go
+++ /dev/null
@@ -1,297 +0,0 @@
-package dhcpv4
-
-import (
- "bytes"
- "testing"
-)
-
-/*
- * BootImageID
- */
-func TestBootImageIDToBytes(t *testing.T) {
- b := BootImageID{
- isInstall: true,
- imageKind: BSDPBootImageMacOSX,
- index: 0x1000,
- }
- actual := b.toBytes()
- expected := []byte{0x81, 0, 0x10, 0}
- if !bytes.Equal(actual, expected) {
- t.Fatalf("Invalid bytes conversion: expected %v, got %v", expected, actual)
- }
-
- b.isInstall = false
- actual = b.toBytes()
- expected = []byte{0x01, 0, 0x10, 0}
- if !bytes.Equal(actual, expected) {
- t.Fatalf("Invalid bytes conversion: expected %v, got %v", expected, actual)
- }
-}
-
-func TestBootImageIDFromBytes(t *testing.T) {
- b := BootImageID{
- isInstall: false,
- imageKind: BSDPBootImageMacOSX,
- index: 0x1000,
- }
- newBootImage := bootImageIDFromBytes(b.toBytes())
- if b != newBootImage {
- t.Fatalf("Difference in BootImageIDs: expected %v, got %v", b, newBootImage)
- }
-
- b = BootImageID{
- isInstall: true,
- imageKind: BSDPBootImageMacOSX,
- index: 0x1011,
- }
- newBootImage = bootImageIDFromBytes(b.toBytes())
- if b != newBootImage {
- t.Fatalf("Difference in BootImageIDs: expected %v, got %v", b, newBootImage)
- }
-}
-
-/*
- * BootImage
- */
-func TestBootImageToBytes(t *testing.T) {
- b := BootImage{
- ID: BootImageID{
- isInstall: true,
- imageKind: BSDPBootImageMacOSX,
- index: 0x1000,
- },
- Name: "bsdp-1",
- }
- expected := []byte{
- 0x81, 0, 0x10, 0, // boot image ID
- 6, // len(Name)
- 98, 115, 100, 112, 45, 49, // byte-encoding of Name
- }
- actual := b.toBytes()
- if !bytes.Equal(expected, actual) {
- t.Fatalf("Invalid bytes conversion: expected %v, got %v", expected, actual)
- }
-
- b = BootImage{
- ID: BootImageID{
- isInstall: false,
- imageKind: BSDPBootImageMacOSX,
- index: 0x1010,
- },
- Name: "bsdp-21",
- }
- expected = []byte{
- 0x1, 0, 0x10, 0x10, // boot image ID
- 7, // len(Name)
- 98, 115, 100, 112, 45, 50, 49, // byte-encoding of Name
- }
- actual = b.toBytes()
- if !bytes.Equal(expected, actual) {
- t.Fatalf("Invalid bytes conversion: expected %v, got %v", expected, actual)
- }
-}
-
-func TestBootImageFromBytes(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
- }
- b, read, err := bootImageFromBytes(input)
- AssertNil(t, err, "error while marshalling BootImage")
- AssertEqual(t, read, len(input), "number of bytes from input")
- expectedBootImage := BootImage{
- ID: BootImageID{
- isInstall: false,
- imageKind: BSDPBootImageMacOSX,
- index: 0x1010,
- },
- Name: "bsdp-21",
- }
- AssertEqual(t, *b, expectedBootImage, "invalid marshalling of BootImage")
-}
-
-func TestBootImageFromBytesOnlyBootImageID(t *testing.T) {
- // Only a BootImageID, nothing else.
- input := []byte{0x1, 0, 0x10, 0x10}
- _, _, err := bootImageFromBytes(input)
- AssertNotNil(t, err, "short bytestream")
-}
-
-func TestBootImageFromBytesShortBootImage(t *testing.T) {
- input := []byte{
- 0x1, 0, 0x10, 0x10, // boot image ID
- 7, // len(Name)
- 98, 115, 100, 112, 45, 50, // Name bytes (intentially off-by-one)
- }
- _, _, err := bootImageFromBytes(input)
- AssertNotNil(t, err, "short bytestream")
-}
-
-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 := parseBootImagesFromBSDPOption(input)
- AssertNil(t, err, "parsing boot image")
- AssertEqual(t, len(bs), 1, "length of boot images")
- b := bs[0]
- expectedBootImage := BootImageID{
- isInstall: false,
- imageKind: BSDPBootImageMacOSX,
- index: 0x1010,
- }
- AssertEqual(t, b.ID, expectedBootImage, "boot image ID")
- AssertEqual(t, b.Name, "bsdp-21", "boot image name")
-}
-
-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 := parseBootImagesFromBSDPOption(input)
- AssertNil(t, err, "parsing boot image")
- AssertEqual(t, len(bs), 2, "length of boot images")
- b1 := bs[0]
- b2 := bs[1]
- expectedID1 := BootImageID{
- isInstall: false,
- imageKind: BSDPBootImageMacOSX,
- index: 0x1010,
- }
- expectedID2 := BootImageID{
- isInstall: true,
- imageKind: BSDPBootImageMacOSXServer,
- index: 0x1122,
- }
- AssertEqual(t, b1.ID, expectedID1, "boot image ID 1")
- AssertEqual(t, b2.ID, expectedID2, "boot image ID 2")
- AssertEqual(t, b1.Name, "bsdp-21", "boot image 1 name")
- AssertEqual(t, b2.Name, "bsdp-222", "boot image 1 name")
-}
-
-func TestParseBootImageFail(t *testing.T) {
- _, err := parseBootImagesFromBSDPOption([]byte{})
- AssertNotNil(t, err, "parseBootImages with empty arg")
-
- _, err = parseBootImagesFromBSDPOption([]byte{1, 2, 3})
- AssertNotNil(t, err, "parseBootImages with short arg")
-}
-
-/*
- * parseVendorOptionsFromOptions
- */
-func TestParseVendorOptions(t *testing.T) {
- recvOpts := []Option{
- Option{
- Code: OptionDHCPMessageType,
- Data: []byte{MessageTypeAck},
- },
- Option{
- Code: OptionBroadcastAddress,
- Data: []byte{0xff, 0xff, 0xff, 0xff},
- },
- Option{
- Code: OptionVendorSpecificInformation,
- Data: OptionsToBytes([]Option{
- Option{
- Code: BSDPOptionMessageType,
- Data: []byte{BSDPMessageTypeList},
- },
- Option{
- Code: BSDPOptionVersion,
- Data: BSDPVersion1_0,
- },
- }),
- },
- }
- opts := parseVendorOptionsFromOptions(recvOpts)
- AssertEqual(t, len(opts), 2, "len of vendor opts")
-}
-
-func TestParseVendorOptionsFromOptionsNotPresent(t *testing.T) {
- recvOpts := []Option{
- Option{
- Code: OptionDHCPMessageType,
- Data: []byte{MessageTypeAck},
- },
- Option{
- Code: OptionBroadcastAddress,
- Data: []byte{0xff, 0xff, 0xff, 0xff},
- },
- }
- opts := parseVendorOptionsFromOptions(recvOpts)
- AssertEqual(t, len(opts), 0, "len of vendor opts")
-}
-
-func TestParseVendorOptionsFromOptionsEmpty(t *testing.T) {
- options := parseVendorOptionsFromOptions([]Option{})
- AssertEqual(t, len(options), 0, "size of options")
-}
-
-/*
- * ParseBootImageListFromAck
- */
-func TestParseBootImageListFromAck(t *testing.T) {
- bootImages := []BootImage{
- BootImage{
- ID: BootImageID{
- isInstall: true,
- imageKind: BSDPBootImageMacOSX,
- index: 0x1010,
- },
- Name: "bsdp-1",
- },
- BootImage{
- ID: BootImageID{
- isInstall: false,
- imageKind: BSDPBootImageMacOS9,
- index: 0x1111,
- },
- Name: "bsdp-2",
- },
- }
- var bootImageBytes []byte
- for _, image := range bootImages {
- bootImageBytes = append(bootImageBytes, image.toBytes()...)
- }
- ack := DHCPv4{
- options: []Option{
- Option{
- Code: OptionVendorSpecificInformation,
- Data: OptionsToBytes([]Option{
- Option{
- Code: BSDPOptionBootImageList,
- Data: bootImageBytes,
- },
- }),
- },
- },
- }
-
- images, err := ParseBootImageListFromAck(ack)
- AssertNil(t, err, "error from ParseBootImageListFromAck")
- AssertNotNil(t, images, "parsed boot images from ack")
- if len(images) != len(bootImages) {
- t.Fatalf("Expected same number of BootImages, got %d instead", len(images))
- }
- for i := range images {
- if images[i] != bootImages[i] {
- t.Fatalf("Expected boot images to be same. %v != %v", images[i], bootImages[i])
- }
- }
-}
-
-/*
- * NewInformListForInterface
- */
diff --git a/dhcpv4/client.go b/dhcpv4/client.go
index ef2af4e..3d91b84 100644
--- a/dhcpv4/client.go
+++ b/dhcpv4/client.go
@@ -10,7 +10,7 @@ import (
)
const (
- maxUDPReceivedPacketSize = 8192 // arbitrary size. Theoretically could be up to 65kb
+ MaxUDPReceivedPacketSize = 8192 // arbitrary size. Theoretically could be up to 65kb
)
type Client struct {
@@ -19,7 +19,7 @@ type Client struct {
Timeout time.Duration
}
-func makeRawBroadcastPacket(payload []byte) ([]byte, error) {
+func MakeRawBroadcastPacket(payload []byte) ([]byte, error) {
udp := make([]byte, 8)
binary.BigEndian.PutUint16(udp[:2], ClientPort)
binary.BigEndian.PutUint16(udp[2:4], ServerPort)
@@ -82,7 +82,7 @@ func (c *Client) Exchange(ifname string, d *DHCPv4) ([]DHCPv4, error) {
}
daddr := syscall.SockaddrInet4{Port: ClientPort, Addr: [4]byte{255, 255, 255, 255}}
- packet, err := makeRawBroadcastPacket(d.ToBytes())
+ packet, err := MakeRawBroadcastPacket(d.ToBytes())
if err != nil {
return conversation, err
}
@@ -98,7 +98,7 @@ func (c *Client) Exchange(ifname string, d *DHCPv4) ([]DHCPv4, error) {
}
defer conn.Close()
- buf := make([]byte, maxUDPReceivedPacketSize)
+ buf := make([]byte, MaxUDPReceivedPacketSize)
oobdata := []byte{} // ignoring oob data
n, _, _, _, err := conn.ReadMsgUDP(buf, oobdata)
offer, err := FromBytes(buf[:n])
@@ -115,7 +115,7 @@ func (c *Client) Exchange(ifname string, d *DHCPv4) ([]DHCPv4, error) {
return conversation, err
}
conversation = append(conversation, *request)
- packet, err = makeRawBroadcastPacket(request.ToBytes())
+ packet, err = MakeRawBroadcastPacket(request.ToBytes())
if err != nil {
return conversation, err
}
@@ -125,7 +125,7 @@ func (c *Client) Exchange(ifname string, d *DHCPv4) ([]DHCPv4, error) {
}
// Acknowledge
- buf = make([]byte, maxUDPReceivedPacketSize)
+ buf = make([]byte, MaxUDPReceivedPacketSize)
n, _, _, _, err = conn.ReadMsgUDP(buf, oobdata)
acknowledge, err := FromBytes(buf[:n])
if err != nil {
diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go
index f0690b2..182d4a4 100644
--- a/dhcpv4/dhcpv4.go
+++ b/dhcpv4/dhcpv4.go
@@ -38,11 +38,13 @@ type DHCPv4 struct {
options []Option
}
-// interfaceV4Addr obtains the currently-configured IPv4 address for iface.
-func interfaceV4Addr(iface *net.Interface) (net.IP, error) {
+// IPv4AddrsForInterface obtains the currently-configured, non-loopback IPv4
+// addresses for iface.
+func IPv4AddrsForInterface(iface *net.Interface) ([]net.IP, error) {
addrs, err := iface.Addrs()
+ var v4addrs []net.IP
if err != nil {
- return nil, err
+ return v4addrs, err
}
for _, addr := range addrs {
var ip net.IP
@@ -60,9 +62,9 @@ func interfaceV4Addr(iface *net.Interface) (net.IP, error) {
if ip == nil {
continue
}
- return ip, nil
+ v4addrs = append(v4addrs, ip)
}
- return nil, errors.New("Could not get local IPv4 address")
+ return v4addrs, nil
}
// GenerateTransactionID generates a random 32-bits number suitable for use as
@@ -105,7 +107,7 @@ func New() (*DHCPv4, error) {
copy(d.clientHwAddr[:], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
copy(d.serverHostName[:], []byte{})
copy(d.bootFileName[:], []byte{})
- options, err := OptionsFromBytesWithMagicCookie(MagicCookie)
+ options, err := OptionsFromBytes(MagicCookie)
if err != nil {
return nil, err
}
@@ -133,7 +135,7 @@ func NewDiscoveryForInterface(ifname string) (*DHCPv4, error) {
d.SetBroadcast()
d.AddOption(Option{
Code: OptionDHCPMessageType,
- Data: []byte{MessageTypeDiscover},
+ Data: []byte{byte(MessageTypeDiscover)},
})
d.AddOption(Option{
Code: OptionParameterRequestList,
@@ -170,15 +172,15 @@ func NewInformForInterface(ifname string, needsBroadcast bool) (*DHCPv4, error)
}
// Set Client IP as iface's currently-configured IP.
- localIP, err := interfaceV4Addr(iface)
- if err != nil {
- return nil, err
+ localIPs, err := IPv4AddrsForInterface(iface)
+ if err != nil || len(localIPs) == 0 {
+ return nil, fmt.Errorf("could not get local IPs for iface %s", ifname)
}
- d.SetClientIPAddr(localIP)
+ d.SetClientIPAddr(localIPs[0])
d.AddOption(Option{
Code: OptionDHCPMessageType,
- Data: []byte{MessageTypeInform},
+ Data: []byte{byte(MessageTypeInform)},
})
return d, nil
@@ -215,7 +217,7 @@ func RequestFromOffer(offer DHCPv4) (*DHCPv4, error) {
d.SetServerIPAddr(serverIP)
d.AddOption(Option{
Code: OptionDHCPMessageType,
- Data: []byte{MessageTypeRequest},
+ Data: []byte{byte(MessageTypeRequest)},
})
d.AddOption(Option{
Code: OptionRequestedIPAddress,
@@ -252,7 +254,7 @@ func FromBytes(data []byte) (*DHCPv4, error) {
copy(d.clientHwAddr[:], data[28:44])
copy(d.serverHostName[:], data[44:108])
copy(d.bootFileName[:], data[108:236])
- options, err := OptionsFromBytesWithMagicCookie(data[236:])
+ options, err := OptionsFromBytes(data[236:])
if err != nil {
return nil, err
}
@@ -568,18 +570,6 @@ func (d *DHCPv4) Summary() string {
)
ret += " options=\n"
for _, opt := range d.options {
- // Parse and display sub-options
- if opt.Code == OptionVendorSpecificInformation {
- ret += fmt.Sprintf(" %v ->\n", OptionCodeToString[OptionVendorSpecificInformation])
- subopts, err := OptionsFromBytes(opt.Data)
- if err == nil {
- for _, subopt := range subopts {
- ret += fmt.Sprintf(" %v\n", subopt.BSDPString())
- }
- continue
- }
- // fall-through to normal display if the above fails
- }
ret += fmt.Sprintf(" %v\n", opt.String())
if opt.Code == OptionEnd {
break
@@ -638,6 +628,6 @@ func (d *DHCPv4) ToBytes() []byte {
ret = append(ret, d.serverHostName[:64]...)
ret = append(ret, d.bootFileName[:128]...)
d.ValidateOptions() // print warnings about broken options, if any
- ret = append(ret, OptionsToBytesWithMagicCookie(d.options)...)
+ ret = append(ret, OptionsToBytes(d.options)...)
return ret
}
diff --git a/dhcpv4/dhcpv4_test.go b/dhcpv4/dhcpv4_test.go
index a3a1955..0d569c8 100644
--- a/dhcpv4/dhcpv4_test.go
+++ b/dhcpv4/dhcpv4_test.go
@@ -17,29 +17,6 @@ func AssertEqual(t *testing.T, a, b interface{}, what string) {
}
}
-func AssertNotNil(t *testing.T, a interface{}, what string) {
- if a == nil {
- t.Fatalf("Expected %s to not be nil. %v", what, a)
- }
-}
-
-func AssertNil(t *testing.T, a interface{}, what string) {
- if a != nil {
- t.Fatalf("Expected %s to be nil. %v", what, a)
- }
-}
-
-func AssertEqualSlice(t *testing.T, a, b []interface{}, what string) {
- if len(a) != len(b) {
- t.Fatalf("Invalid %s. %v != %v", what, a, b)
- }
- for i := range a {
- if a[i] != b[i] {
- t.Fatalf("Invalid %s. %v != %v at index %d", what, a, b, i)
- }
- }
-}
-
func AssertEqualBytes(t *testing.T, a, b []byte, what string) {
if !bytes.Equal(a, b) {
t.Fatalf("Invalid %s. %v != %v", what, a, b)
diff --git a/dhcpv4/options.go b/dhcpv4/options.go
index 989a93b..6576c05 100644
--- a/dhcpv4/options.go
+++ b/dhcpv4/options.go
@@ -10,6 +10,7 @@ type OptionCode byte
var MagicCookie = []byte{99, 130, 83, 99}
+// TODO: implement Option as an interface similar to dhcpv6.
type Option struct {
Code OptionCode
Data []byte
@@ -38,27 +39,27 @@ func ParseOption(dataStart []byte) (*Option, error) {
}
}
-// OptionsFromBytesWithMagicCookie parses a sequence of bytes until the end and
-// builds a list of options from it. The sequence must contain the Magic Cookie.
-// Returns an error if any invalid option or length is found.
-func OptionsFromBytesWithMagicCookie(data []byte) ([]Option, error) {
+// OptionsFromBytes parses a sequence of bytes until the end and builds a list
+// of options from it. The sequence must contain the Magic Cookie. Returns an
+// error if any invalid option or length is found.
+func OptionsFromBytes(data []byte) ([]Option, error) {
if len(data) < len(MagicCookie) {
return nil, errors.New("Invalid options: shorter than 4 bytes")
}
if !bytes.Equal(data[:len(MagicCookie)], MagicCookie) {
return nil, fmt.Errorf("Invalid Magic Cookie: %v", data[:len(MagicCookie)])
}
- opts, err := OptionsFromBytes(data[len(MagicCookie):])
+ opts, err := OptionsFromBytesWithoutMagicCookie(data[len(MagicCookie):])
if err != nil {
return nil, err
}
return opts, nil
}
-// OptionsFromBytes parses a sequence of bytes until the end and builds a list
-// of options from it. The sequence should not contain the DHCP magic cookie.
-// Returns an error if any invalid option or length is found.
-func OptionsFromBytes(data []byte) ([]Option, error) {
+// OptionsFromBytesWithoutMagicCookie parses a sequence of bytes until the end
+// and builds a list of options from it. The sequence should not contain the
+// DHCP magic cookie. Returns an error if any invalid option or length is found.
+func OptionsFromBytesWithoutMagicCookie(data []byte) ([]Option, error) {
options := make([]Option, 0, 10)
idx := 0
for {
@@ -86,15 +87,15 @@ func OptionsFromBytes(data []byte) ([]Option, error) {
return options, nil
}
-// OptionsToBytesWithMagicCookie converts a list of options to a wire-format
-// representation with the DHCP magic cookie prepended.
-func OptionsToBytesWithMagicCookie(options []Option) []byte {
- ret := MagicCookie
- return append(ret, OptionsToBytes(options)...)
+// OptionsToBytes converts a list of options to a wire-format representation
+// with the DHCP magic cookie prepended.
+func OptionsToBytes(options []Option) []byte {
+ return append(MagicCookie, OptionsToBytesWithoutMagicCookie(options)...)
}
-// OptionsToBytes converts a list of options to a wire-format representation.
-func OptionsToBytes(options []Option) []byte {
+// OptionsToBytesWithoutMagicCookie converts a list of options to a wire-format
+// representation.
+func OptionsToBytesWithoutMagicCookie(options []Option) []byte {
ret := []byte{}
for _, opt := range options {
ret = append(ret, opt.ToBytes()...)
@@ -110,16 +111,6 @@ func (o *Option) String() string {
return fmt.Sprintf("%v -> %v", code, o.Data)
}
-// BSDPString converts a BSDP-specific option embedded in
-// vendor-specific information to a human-readable string.
-func (o *Option) BSDPString() string {
- code, ok := BSDPOptionCodeToString[o.Code]
- if !ok {
- code = "Unknown"
- }
- return fmt.Sprintf("%v -> %v", code, o.Data)
-}
-
func (o *Option) ToBytes() []byte {
// Convert a single option to its wire-format representation
ret := []byte{byte(o.Code)}
diff --git a/dhcpv4/options_test.go b/dhcpv4/options_test.go
index 47258aa..07be9bf 100644
--- a/dhcpv4/options_test.go
+++ b/dhcpv4/options_test.go
@@ -56,19 +56,19 @@ func TestOptionsFromBytes(t *testing.T) {
255, // end
0, 0, 0, //padding
}
- opts, err := OptionsFromBytesWithMagicCookie(options)
+ opts, err := OptionsFromBytes(options)
if err != nil {
t.Fatal(err)
}
// each padding byte counts as an option. Magic Cookie doesn't add up
if len(opts) != 5 {
- t.Fatalf("Invalid options length. Expected 5, got %v", len(opts))
+ t.Fatal("Invalid options length. Expected 5, got %v", len(opts))
}
if opts[0].Code != OptionNameServer {
- t.Fatalf("Invalid option code. Expected %v, got %v", OptionNameServer, opts[0].Code)
+ t.Fatal("Invalid option code. Expected %v, got %v", OptionNameServer, opts[0].Code)
}
if !bytes.Equal(opts[0].Data, options[6:10]) {
- t.Fatalf("Invalid option data. Expected %v, got %v", options[6:10], opts[0].Data)
+ t.Fatal("Invalid option data. Expected %v, got %v", options[6:10], opts[0].Data)
}
if opts[1].Code != OptionEnd {
t.Fatalf("Invalid option code. Expected %v, got %v", OptionEnd, opts[1].Code)
@@ -80,7 +80,7 @@ func TestOptionsFromBytes(t *testing.T) {
func TestOptionsFromBytesZeroLength(t *testing.T) {
options := []byte{}
- _, err := OptionsFromBytesWithMagicCookie(options)
+ _, err := OptionsFromBytes(options)
if err == nil {
t.Fatal("Expected an error, got none")
}
@@ -88,36 +88,36 @@ func TestOptionsFromBytesZeroLength(t *testing.T) {
func TestOptionsFromBytesBadMagicCookie(t *testing.T) {
options := []byte{1, 2, 3, 4}
- _, err := OptionsFromBytesWithMagicCookie(options)
+ _, err := OptionsFromBytes(options)
if err == nil {
t.Fatal("Expected an error, got none")
}
}
-func TestOptionsToBytesWithMagicCookie(t *testing.T) {
+func TestOptionsToBytes(t *testing.T) {
originalOptions := []byte{
99, 130, 83, 99, // Magic Cookie
5, 4, 192, 168, 1, 1, // DNS
255, // end
0, 0, 0, //padding
}
- options, err := OptionsFromBytesWithMagicCookie(originalOptions)
+ options, err := OptionsFromBytes(originalOptions)
if err != nil {
t.Fatal(err)
}
- finalOptions := OptionsToBytesWithMagicCookie(options)
+ finalOptions := OptionsToBytes(options)
if !bytes.Equal(originalOptions, finalOptions) {
t.Fatalf("Invalid options. Expected %v, got %v", originalOptions, finalOptions)
}
}
-func TestOptionsToBytesWithMagicCookieEmpty(t *testing.T) {
+func TestOptionsToBytesEmpty(t *testing.T) {
originalOptions := []byte{99, 130, 83, 99}
- options, err := OptionsFromBytesWithMagicCookie(originalOptions)
+ options, err := OptionsFromBytes(originalOptions)
if err != nil {
t.Fatal(err)
}
- finalOptions := OptionsToBytesWithMagicCookie(options)
+ finalOptions := OptionsToBytes(options)
if !bytes.Equal(originalOptions, finalOptions) {
t.Fatalf("Invalid options. Expected %v, got %v", originalOptions, finalOptions)
}
@@ -146,21 +146,3 @@ func TestOptionsToStringDHCPMessageType(t *testing.T) {
t.Fatalf("Invalid string representation: %v", stropt)
}
}
-
-func TestBSDPOptionToString(t *testing.T) {
- // Parse message type
- option := Option{
- Code: BSDPOptionMessageType,
- Data: []byte{BSDPMessageTypeList},
- }
- stropt := option.BSDPString()
- AssertEqual(t, stropt, "BSDP Message Type -> [1]", "BSDP string representation")
-
- // Parse failure
- option = Option{
- Code: OptionCode(12), // invalid BSDP Opcode
- Data: []byte{1, 2, 3},
- }
- stropt = option.BSDPString()
- AssertEqual(t, stropt, "Unknown -> [1 2 3]", "BSDP string representation")
-}
diff --git a/dhcpv4/types.go b/dhcpv4/types.go
index 4f3ad0f..6ec4d9b 100644
--- a/dhcpv4/types.go
+++ b/dhcpv4/types.go
@@ -3,9 +3,12 @@ package dhcpv4
// values from http://www.networksorcery.com/enp/protocol/dhcp.htm and
// http://www.networksorcery.com/enp/protocol/bootp/options.htm
+// MessageType represents the possible DHCP message types - DISCOVER, OFFER, etc
+type MessageType byte
+
// DHCP message types
const (
- MessageTypeDiscover byte = iota + 1
+ MessageTypeDiscover MessageType = iota + 1
MessageTypeOffer
MessageTypeRequest
MessageTypeDecline
@@ -359,22 +362,3 @@ var OptionCodeToString = map[OptionCode]string{
OptionEnd: "End",
}
-
-// BSDPOptionCodeToString maps BSDP OptionCodes to human-readable strings
-// describing what they are.
-var BSDPOptionCodeToString = map[OptionCode]string{
- BSDPOptionMessageType: "BSDP Message Type",
- BSDPOptionVersion: "BSDP Version",
- BSDPOptionServerIdentifier: "BSDP Server Identifier",
- BSDPOptionServerPriority: "BSDP Server Priority",
- BSDPOptionReplyPort: "BSDP Reply Port",
- BSDPOptionBootImageListPath: "", // Not used
- BSDPOptionDefaultBootImageID: "BSDP Default Boot Image ID",
- BSDPOptionSelectedBootImageID: "BSDP Selected Boot Image ID",
- BSDPOptionBootImageList: "BSDP Boot Image List",
- BSDPOptionNetboot1_0Firmware: "BSDP Netboot 1.0 Firmware",
- BSDPOptionBootImageAttributesFilterList: "BSDP Boot Image Attributes Filter List",
- BSDPOptionShadowMountPath: "BSDP Shadow Mount Path",
- BSDPOptionShadowFilePath: "BSDP Shadow File Path",
- BSDPOptionMachineName: "BSDP Machine Name",
-}