diff options
author | Pablo Mazzini <pmazzini@gmail.com> | 2023-12-04 10:33:34 +0000 |
---|---|---|
committer | insomniac <insomniacslk@users.noreply.github.com> | 2023-12-06 07:48:09 +0100 |
commit | 8c70d406f6d24a17219a5f543174c1f3f3ad9e35 (patch) | |
tree | 7f9acc8e465b9b354dea197f1d77698370e648ab | |
parent | b0416c0f187a65a350b32d34fc31ea30f06c12ce (diff) |
-rw-r--r-- | dhcpv4/bsdp/boot_image.go | 136 | ||||
-rw-r--r-- | dhcpv4/bsdp/boot_image_test.go | 139 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp.go | 254 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_boot_image_list.go | 53 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_boot_image_list_test.go | 114 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_message_type.go | 57 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_message_type_test.go | 31 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_misc.go | 66 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_misc_test.go | 105 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_test.go | 410 | ||||
-rw-r--r-- | dhcpv4/bsdp/client.go | 75 | ||||
-rw-r--r-- | dhcpv4/bsdp/doc.go | 10 | ||||
-rw-r--r-- | dhcpv4/bsdp/option_vendor_specific_information.go | 157 | ||||
-rw-r--r-- | dhcpv4/bsdp/option_vendor_specific_information_test.go | 79 | ||||
-rw-r--r-- | dhcpv4/bsdp/types.go | 64 | ||||
-rw-r--r-- | dhcpv4/bsdp/vendor_class_identifier.go | 9 | ||||
-rw-r--r-- | dhcpv4/bsdp/vendor_class_identifier_darwin.go | 18 |
17 files changed, 0 insertions, 1777 deletions
diff --git a/dhcpv4/bsdp/boot_image.go b/dhcpv4/bsdp/boot_image.go deleted file mode 100644 index 5e81530..0000000 --- a/dhcpv4/bsdp/boot_image.go +++ /dev/null @@ -1,136 +0,0 @@ -package bsdp - -import ( - "fmt" - - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/u-root/uio/uio" -) - -// 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 = 0 - BootImageTypeMacOSX BootImageType = 1 - BootImageTypeMacOSXServer BootImageType = 2 - BootImageTypeHardwareDiagnostics BootImageType = 3 - // 4 - 127 are reserved for future use. -) - -// bootImageTypeToString maps the different BootImageTypes to human-readable -// representations. -var bootImageTypeToString = map[BootImageType]string{ - BootImageTypeMacOS9: "macOS 9", - BootImageTypeMacOSX: "macOS", - BootImageTypeMacOSXServer: "macOS Server", - BootImageTypeHardwareDiagnostics: "Hardware Diagnostic", -} - -// 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 implements dhcpv4.OptionValue. -func (b BootImageID) ToBytes() []byte { - return uio.ToBigEndian(b) -} - -// FromBytes reads data into b. -func (b *BootImageID) FromBytes(data []byte) error { - return uio.FromBigEndian(b, data) -} - -// Marshal writes the binary representation to buf. -func (b BootImageID) Marshal(buf *uio.Lexer) { - var byte0 byte - if b.IsInstall { - byte0 |= 0x80 - } - byte0 |= byte(b.ImageType) - buf.Write8(byte0) - buf.Write8(byte(0)) - buf.Write16(b.Index) -} - -// String converts a BootImageID to a human-readable representation. -func (b BootImageID) String() string { - s := fmt.Sprintf("[%d]", b.Index) - if b.IsInstall { - s += " installable" - } else { - s += " uninstallable" - } - t, ok := bootImageTypeToString[b.ImageType] - if !ok { - t = "unknown" - } - return s + " " + t + " image" -} - -// Unmarshal reads b's binary representation from buf. -func (b *BootImageID) Unmarshal(buf *uio.Lexer) error { - byte0 := buf.Read8() - _ = buf.Read8() - b.IsInstall = byte0&0x80 != 0 - b.ImageType = BootImageType(byte0 & 0x7f) - b.Index = buf.Read16() - return buf.Error() -} - -// BootImage describes a boot image - contains the boot image ID and the name. -type BootImage struct { - ID BootImageID - Name string -} - -// Marshal write a BootImage to buf. -func (b BootImage) Marshal(buf *uio.Lexer) { - b.ID.Marshal(buf) - buf.Write8(uint8(len(b.Name))) - buf.WriteBytes([]byte(b.Name)) -} - -// String converts a BootImage to a human-readable representation. -func (b BootImage) String() string { - return fmt.Sprintf("%v %v", b.Name, b.ID.String()) -} - -// Unmarshal reads data from buf into b. -func (b *BootImage) Unmarshal(buf *uio.Lexer) error { - if err := (&b.ID).Unmarshal(buf); err != nil { - return err - } - nameLength := buf.Read8() - b.Name = string(buf.Consume(int(nameLength))) - return buf.Error() -} - -func getBootImageID(code dhcpv4.OptionCode, o dhcpv4.Options) *BootImageID { - v := o.Get(code) - if v == nil { - return nil - } - var b BootImageID - if err := uio.FromBigEndian(&b, v); err != nil { - return nil - } - return &b -} - -// OptDefaultBootImageID returns a new default boot image ID option as per -// BSDP. -func OptDefaultBootImageID(b BootImageID) dhcpv4.Option { - return dhcpv4.Option{Code: OptionDefaultBootImageID, Value: b} -} - -// OptSelectedBootImageID returns a new selected boot image ID option as per -// BSDP. -func OptSelectedBootImageID(b BootImageID) dhcpv4.Option { - return dhcpv4.Option{Code: OptionSelectedBootImageID, Value: b} -} diff --git a/dhcpv4/bsdp/boot_image_test.go b/dhcpv4/bsdp/boot_image_test.go deleted file mode 100644 index 0b51287..0000000 --- a/dhcpv4/bsdp/boot_image_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package bsdp - -import ( - "testing" - - "github.com/stretchr/testify/require" - "github.com/u-root/uio/uio" -) - -func TestBootImageIDToBytes(t *testing.T) { - b := BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOSX, - Index: 0x1000, - } - actual := uio.ToBigEndian(b) - expected := []byte{0x81, 0, 0x10, 0} - require.Equal(t, expected, actual) - - b.IsInstall = false - actual = uio.ToBigEndian(b) - expected = []byte{0x01, 0, 0x10, 0} - require.Equal(t, expected, actual) -} - -func TestBootImageIDFromBytes(t *testing.T) { - b := BootImageID{ - IsInstall: false, - ImageType: BootImageTypeMacOSX, - Index: 0x1000, - } - var newBootImage BootImageID - require.NoError(t, uio.FromBigEndian(&newBootImage, uio.ToBigEndian(b))) - require.Equal(t, b, newBootImage) - - b = BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOSX, - Index: 0x1011, - } - require.NoError(t, uio.FromBigEndian(&newBootImage, uio.ToBigEndian(b))) - require.Equal(t, b, newBootImage) -} - -func TestBootImageIDFromBytesFail(t *testing.T) { - serialized := []byte{0x81, 0, 0x10} // intentionally left short - var deserialized BootImageID - require.Error(t, uio.FromBigEndian(&deserialized, serialized)) -} - -func TestBootImageIDString(t *testing.T) { - b := BootImageID{IsInstall: false, ImageType: BootImageTypeMacOSX, Index: 1001} - require.Equal(t, "[1001] uninstallable macOS image", b.String()) -} - -/* - * 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 := uio.ToBigEndian(b) - require.Equal(t, expected, actual) - - 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 = uio.ToBigEndian(b) - require.Equal(t, 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 - } - var b BootImage - require.NoError(t, uio.FromBigEndian(&b, input)) - expectedBootImage := BootImage{ - ID: BootImageID{ - IsInstall: false, - ImageType: BootImageTypeMacOSX, - Index: 0x1010, - }, - Name: "bsdp-21", - } - require.Equal(t, expectedBootImage, b) -} - -func TestBootImageFromBytesOnlyBootImageID(t *testing.T) { - // Only a BootImageID, nothing else. - input := []byte{0x1, 0, 0x10, 0x10} - var b BootImage - require.Error(t, uio.FromBigEndian(&b, input)) -} - -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) - } - var b BootImage - require.Error(t, uio.FromBigEndian(&b, input)) -} - -func TestBootImageString(t *testing.T) { - b := BootImage{ - ID: BootImageID{ - IsInstall: false, - ImageType: BootImageTypeMacOSX, - Index: 0x1010, - }, - Name: "bsdp-21", - } - require.Equal(t, "bsdp-21 [4112] uninstallable macOS image", b.String()) -} diff --git a/dhcpv4/bsdp/bsdp.go b/dhcpv4/bsdp/bsdp.go deleted file mode 100644 index d90e884..0000000 --- a/dhcpv4/bsdp/bsdp.go +++ /dev/null @@ -1,254 +0,0 @@ -package bsdp - -import ( - "errors" - "fmt" - "net" - - "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 - -// AppleVendorID is the string constant set in the vendor class identifier (DHCP -// option 60) that is sent by the server. -const AppleVendorID = "AAPLBSDPC" - -// ReplyConfig is a struct containing some common configuration values for a -// BSDP reply (ACK). -type ReplyConfig struct { - ServerIP net.IP - ServerHostname, BootFileName string - ServerPriority uint16 - Images []BootImage - DefaultImage, SelectedImage *BootImage -} - -// 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) { - vendorOpts := GetVendorOptions(ack.Options) - if vendorOpts == nil { - return nil, errors.New("ParseBootImageListFromAck: could not find vendor-specific option") - } - return vendorOpts.BootImageList(), nil -} - -func needsReplyPort(replyPort uint16) bool { - return replyPort != 0 && replyPort != dhcpv4.ClientPort -} - -// MessageTypeFromPacket extracts the BSDP message type (LIST, SELECT) from the -// vendor-specific options and returns it. If the message type option cannot be -// found, returns false. -func MessageTypeFromPacket(packet *dhcpv4.DHCPv4) MessageType { - vendorOpts := GetVendorOptions(packet.Options) - if vendorOpts == nil { - return MessageTypeNone - } - return vendorOpts.MessageType() -} - -// Packet is a BSDP packet wrapper around a DHCPv4 packet in order to print the -// correct vendor-specific BSDP information in String(). -type Packet struct { - dhcpv4.DHCPv4 -} - -// PacketFor returns a wrapped BSDP Packet given a DHCPv4 packet. -func PacketFor(d *dhcpv4.DHCPv4) *Packet { - return &Packet{*d} -} - -func (p Packet) v4() *dhcpv4.DHCPv4 { - return &p.DHCPv4 -} - -func (p Packet) String() string { - return p.DHCPv4.String() -} - -// Summary prints the BSDP packet with the correct vendor-specific options. -func (p Packet) Summary() string { - return p.DHCPv4.SummaryWithVendor(&VendorOptions{}) -} - -// NewInformListForInterface creates a new INFORM packet for interface ifname -// with configuration options specified by config. -func NewInformListForInterface(ifname string, replyPort uint16) (*Packet, error) { - iface, err := net.InterfaceByName(ifname) - if err != nil { - return nil, err - } - // Get currently configured IP. - addrs, err := iface.Addrs() - if err != nil { - return nil, err - } - localIPs, err := dhcpv4.GetExternalIPv4Addrs(addrs) - if err != nil { - return nil, fmt.Errorf("could not get local IPv4 addr for %s: %v", iface.Name, err) - } - if len(localIPs) == 0 { - return nil, fmt.Errorf("could not get local IPv4 addr for %s", iface.Name) - } - return NewInformList(iface.HardwareAddr, localIPs[0], replyPort) -} - -// NewInformList creates a new INFORM packet for interface with hardware address -// `hwaddr` and IP `localIP`. Packet will be sent out on port `replyPort`. -func NewInformList(hwaddr net.HardwareAddr, localIP net.IP, replyPort uint16, modifiers ...dhcpv4.Modifier) (*Packet, error) { - // Validate replyPort first - if needsReplyPort(replyPort) && replyPort >= 1024 { - return nil, errors.New("replyPort must be a privileged port") - } - - vendorClassID, err := MakeVendorClassIdentifier() - if err != nil { - return nil, err - } - - // These are vendor-specific options used to pass along BSDP information. - vendorOpts := []dhcpv4.Option{ - OptMessageType(MessageTypeList), - OptVersion(Version1_1), - } - if needsReplyPort(replyPort) { - vendorOpts = append(vendorOpts, OptReplyPort(replyPort)) - } - - d, err := dhcpv4.NewInform(hwaddr, localIP, - dhcpv4.PrependModifiers(modifiers, dhcpv4.WithRequestedOptions( - dhcpv4.OptionVendorSpecificInformation, - dhcpv4.OptionClassIdentifier, - ), - dhcpv4.WithOption(dhcpv4.OptMaxMessageSize(MaxDHCPMessageSize)), - dhcpv4.WithOption(dhcpv4.OptClassIdentifier(vendorClassID)), - dhcpv4.WithOption(OptVendorOptions(vendorOpts...)), - )...) - if err != nil { - return nil, err - } - return PacketFor(d), nil -} - -// InformSelectForAck constructs an INFORM[SELECT] packet given an ACK to the -// previously-sent INFORM[LIST]. -func InformSelectForAck(ack *Packet, replyPort uint16, selectedImage BootImage) (*Packet, error) { - if needsReplyPort(replyPort) && replyPort >= 1024 { - return nil, errors.New("replyPort must be a privileged port") - } - - // Data for OptionSelectedBootImageID - vendorOpts := []dhcpv4.Option{ - OptMessageType(MessageTypeSelect), - OptVersion(Version1_1), - OptSelectedBootImageID(selectedImage.ID), - } - - // Validate replyPort if requested. - if needsReplyPort(replyPort) { - vendorOpts = append(vendorOpts, OptReplyPort(replyPort)) - } - - // Find server IP address - serverIP := ack.ServerIdentifier() - if serverIP.To4() == nil { - return nil, fmt.Errorf("could not parse server identifier from ACK") - } - vendorOpts = append(vendorOpts, OptServerIdentifier(serverIP)) - - vendorClassID, err := MakeVendorClassIdentifier() - if err != nil { - return nil, err - } - - d, err := dhcpv4.New(dhcpv4.WithReply(ack.v4()), - dhcpv4.WithOption(dhcpv4.OptClassIdentifier(vendorClassID)), - dhcpv4.WithRequestedOptions( - dhcpv4.OptionSubnetMask, - dhcpv4.OptionRouter, - dhcpv4.OptionBootfileName, - dhcpv4.OptionVendorSpecificInformation, - dhcpv4.OptionClassIdentifier, - ), - dhcpv4.WithMessageType(dhcpv4.MessageTypeInform), - dhcpv4.WithOption(OptVendorOptions(vendorOpts...)), - ) - if err != nil { - return nil, err - } - return PacketFor(d), nil -} - -// NewReplyForInformList constructs an ACK for the INFORM[LIST] packet `inform` -// with additional options in `config`. -func NewReplyForInformList(inform *Packet, config ReplyConfig) (*Packet, error) { - if config.DefaultImage == nil { - return nil, errors.New("NewReplyForInformList: no default boot image ID set") - } - if config.Images == nil || len(config.Images) == 0 { - return nil, errors.New("NewReplyForInformList: no boot images provided") - } - reply, err := dhcpv4.NewReplyFromRequest(&inform.DHCPv4) - if err != nil { - return nil, err - } - reply.ClientIPAddr = inform.ClientIPAddr - reply.GatewayIPAddr = inform.GatewayIPAddr - reply.ServerIPAddr = config.ServerIP - reply.ServerHostName = config.ServerHostname - - reply.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck)) - reply.UpdateOption(dhcpv4.OptServerIdentifier(config.ServerIP)) - reply.UpdateOption(dhcpv4.OptClassIdentifier(AppleVendorID)) - - // BSDP opts. - vendorOpts := []dhcpv4.Option{ - OptMessageType(MessageTypeList), - OptServerPriority(config.ServerPriority), - OptDefaultBootImageID(config.DefaultImage.ID), - OptBootImageList(config.Images...), - } - if config.SelectedImage != nil { - vendorOpts = append(vendorOpts, OptSelectedBootImageID(config.SelectedImage.ID)) - } - reply.UpdateOption(OptVendorOptions(vendorOpts...)) - return PacketFor(reply), nil -} - -// NewReplyForInformSelect constructs an ACK for the INFORM[Select] packet -// `inform` with additional options in `config`. -func NewReplyForInformSelect(inform *Packet, config ReplyConfig) (*Packet, error) { - if config.SelectedImage == nil { - return nil, errors.New("NewReplyForInformSelect: no selected boot image ID set") - } - if config.Images == nil || len(config.Images) == 0 { - return nil, errors.New("NewReplyForInformSelect: no boot images provided") - } - reply, err := dhcpv4.NewReplyFromRequest(&inform.DHCPv4) - if err != nil { - return nil, err - } - - reply.ClientIPAddr = inform.ClientIPAddr - reply.GatewayIPAddr = inform.GatewayIPAddr - reply.ServerIPAddr = config.ServerIP - reply.ServerHostName = config.ServerHostname - reply.BootFileName = config.BootFileName - - reply.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck)) - reply.UpdateOption(dhcpv4.OptServerIdentifier(config.ServerIP)) - reply.UpdateOption(dhcpv4.OptClassIdentifier(AppleVendorID)) - - // BSDP opts. - reply.UpdateOption(OptVendorOptions( - OptMessageType(MessageTypeSelect), - OptSelectedBootImageID(config.SelectedImage.ID), - )) - return PacketFor(reply), nil -} diff --git a/dhcpv4/bsdp/bsdp_option_boot_image_list.go b/dhcpv4/bsdp/bsdp_option_boot_image_list.go deleted file mode 100644 index ed70243..0000000 --- a/dhcpv4/bsdp/bsdp_option_boot_image_list.go +++ /dev/null @@ -1,53 +0,0 @@ -package bsdp - -import ( - "strings" - - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/u-root/uio/uio" -) - -// BootImageList contains a list of boot images presented by a netboot server. -// -// Implements the BSDP option listing the boot images. -type BootImageList []BootImage - -// FromBytes deserializes data into bil. -func (bil *BootImageList) FromBytes(data []byte) error { - buf := uio.NewBigEndianBuffer(data) - - for buf.Has(5) { - var image BootImage - if err := image.Unmarshal(buf); err != nil { - return err - } - *bil = append(*bil, image) - } - return nil -} - -// ToBytes returns a serialized stream of bytes for this option. -func (bil BootImageList) ToBytes() []byte { - buf := uio.NewBigEndianBuffer(nil) - for _, image := range bil { - image.Marshal(buf) - } - return buf.Data() -} - -// String returns a human-readable string for this option. -func (bil BootImageList) String() string { - s := make([]string, 0, len(bil)) - for _, image := range bil { - s = append(s, image.String()) - } - return strings.Join(s, ", ") -} - -// OptBootImageList returns a new BSDP boot image list. -func OptBootImageList(b ...BootImage) dhcpv4.Option { - return dhcpv4.Option{ - Code: OptionBootImageList, - Value: BootImageList(b), - } -} diff --git a/dhcpv4/bsdp/bsdp_option_boot_image_list_test.go b/dhcpv4/bsdp/bsdp_option_boot_image_list_test.go deleted file mode 100644 index 6282156..0000000 --- a/dhcpv4/bsdp/bsdp_option_boot_image_list_test.go +++ /dev/null @@ -1,114 +0,0 @@ -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") - expectedBytes := []byte{ - // 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.Value.ToBytes(), "ToBytes") -} - -func TestParseOptBootImageList(t *testing.T) { - data := []byte{ - // 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', - } - var o BootImageList - err := o.FromBytes(data) - require.NoError(t, err) - expectedBootImages := BootImageList{ - 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, expectedBootImages, o) - - // Error parsing boot image (malformed) - data = []byte{ - // boot image 1 - 0x1, 0x0, 0x03, 0xe9, // ID - 4, // name length - 'b', 's', 'd', 'p', '-', '1', - // boot image 2 - 0x80, 0x0, 0x23, 0x31, // ID - 6, // name length - 'b', 's', 'd', 'p', '-', '2', - } - err = o.FromBytes(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: bsdp-1 [1001] uninstallable macOS image, bsdp-2 [9009] installable macOS 9 image" - require.Equal(t, expectedString, o.String()) -} diff --git a/dhcpv4/bsdp/bsdp_option_message_type.go b/dhcpv4/bsdp/bsdp_option_message_type.go deleted file mode 100644 index 541cd96..0000000 --- a/dhcpv4/bsdp/bsdp_option_message_type.go +++ /dev/null @@ -1,57 +0,0 @@ -package bsdp - -import ( - "fmt" - - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/u-root/uio/uio" -) - -// MessageType represents the different BSDP message types. -// -// Implements the BSDP option message type. Can be one of LIST, SELECT, or -// FAILED. -type MessageType byte - -// BSDP Message types - e.g. LIST, SELECT, FAILED -const ( - MessageTypeNone MessageType = 0 - MessageTypeList MessageType = 1 - MessageTypeSelect MessageType = 2 - MessageTypeFailed MessageType = 3 -) - -// ToBytes returns a serialized stream of bytes for this option. -func (m MessageType) ToBytes() []byte { - return []byte{byte(m)} -} - -// String returns a human-friendly representation of MessageType. -func (m MessageType) String() string { - if s, ok := messageTypeToString[m]; ok { - return s - } - return fmt.Sprintf("unknown (%d)", m) -} - -// messageTypeToString maps each BSDP message type to a human-readable string. -var messageTypeToString = map[MessageType]string{ - MessageTypeList: "LIST", - MessageTypeSelect: "SELECT", - MessageTypeFailed: "FAILED", -} - -// FromBytes reads data into m. -func (m *MessageType) FromBytes(data []byte) error { - buf := uio.NewBigEndianBuffer(data) - *m = MessageType(buf.Read8()) - return buf.FinError() -} - -// OptMessageType returns a new BSDP Message Type option. -func OptMessageType(mt MessageType) dhcpv4.Option { - return dhcpv4.Option{ - Code: OptionMessageType, - Value: mt, - } -} diff --git a/dhcpv4/bsdp/bsdp_option_message_type_test.go b/dhcpv4/bsdp/bsdp_option_message_type_test.go deleted file mode 100644 index 6666137..0000000 --- a/dhcpv4/bsdp/bsdp_option_message_type_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package bsdp - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestOptMessageTypeInterfaceMethods(t *testing.T) { - o := OptMessageType(MessageTypeList) - require.Equal(t, OptionMessageType, o.Code, "Code") - require.Equal(t, []byte{1}, o.Value.ToBytes(), "ToBytes") -} - -func TestParseOptMessageType(t *testing.T) { - var o MessageType - data := []byte{1} // DISCOVER - err := o.FromBytes(data) - require.NoError(t, err) - require.Equal(t, MessageTypeList, o) -} - -func TestOptMessageTypeString(t *testing.T) { - // known - o := OptMessageType(MessageTypeList) - require.Equal(t, "BSDP Message Type: LIST", o.String()) - - // unknown - o = OptMessageType(99) - require.Equal(t, "BSDP Message Type: unknown (99)", o.String()) -} diff --git a/dhcpv4/bsdp/bsdp_option_misc.go b/dhcpv4/bsdp/bsdp_option_misc.go deleted file mode 100644 index 3848a6c..0000000 --- a/dhcpv4/bsdp/bsdp_option_misc.go +++ /dev/null @@ -1,66 +0,0 @@ -package bsdp - -import ( - "fmt" - "net" - - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/u-root/uio/uio" -) - -// OptReplyPort returns a new BSDP reply port option. -// -// Implements the BSDP option reply port. This is used when BSDP responses -// should be sent to a reply port other than the DHCP default. The macOS GUI -// "Startup Disk Select" sends this option since it's operating in an -// unprivileged context. -func OptReplyPort(port uint16) dhcpv4.Option { - return dhcpv4.Option{Code: OptionReplyPort, Value: dhcpv4.Uint16(port)} -} - -// OptServerPriority returns a new BSDP server priority option. -func OptServerPriority(prio uint16) dhcpv4.Option { - return dhcpv4.Option{Code: OptionServerPriority, Value: dhcpv4.Uint16(prio)} -} - -// OptMachineName returns a BSDP Machine Name option. -func OptMachineName(name string) dhcpv4.Option { - return dhcpv4.Option{Code: OptionMachineName, Value: dhcpv4.String(name)} -} - -// Version is the BSDP protocol version. Can be one of 1.0 or 1.1. -type Version [2]byte - -// Specific versions. -var ( - Version1_0 = Version{1, 0} - Version1_1 = Version{1, 1} -) - -// ToBytes returns a serialized stream of bytes for this option. -func (o Version) ToBytes() []byte { - return o[:] -} - -// String returns a human-readable string for this option. -func (o Version) String() string { - return fmt.Sprintf("%d.%d", o[0], o[1]) -} - -// FromBytes constructs a Version struct from a sequence of -// bytes and returns it, or an error. -func (o *Version) FromBytes(data []byte) error { - buf := uio.NewBigEndianBuffer(data) - buf.ReadBytes(o[:]) - return buf.FinError() -} - -// OptVersion returns a new BSDP version option. -func OptVersion(version Version) dhcpv4.Option { - return dhcpv4.Option{Code: OptionVersion, Value: version} -} - -// OptServerIdentifier returns a new BSDP Server Identifier option. -func OptServerIdentifier(ip net.IP) dhcpv4.Option { - return dhcpv4.Option{Code: OptionServerIdentifier, Value: dhcpv4.IP(ip)} -} diff --git a/dhcpv4/bsdp/bsdp_option_misc_test.go b/dhcpv4/bsdp/bsdp_option_misc_test.go deleted file mode 100644 index 675a5db..0000000 --- a/dhcpv4/bsdp/bsdp_option_misc_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package bsdp - -import ( - "net" - "testing" - - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/stretchr/testify/require" -) - -func TestOptReplyPort(t *testing.T) { - o := OptReplyPort(1234) - require.Equal(t, OptionReplyPort, o.Code, "Code") - require.Equal(t, []byte{4, 210}, o.Value.ToBytes(), "ToBytes") - require.Equal(t, "BSDP Reply Port: 1234", o.String()) -} - -func TestGetReplyPort(t *testing.T) { - o := VendorOptions{dhcpv4.OptionsFromList(OptReplyPort(1234))} - port, err := o.ReplyPort() - require.NoError(t, err) - require.Equal(t, uint16(1234), port) - - o = VendorOptions{dhcpv4.Options{}} - _, err = o.ReplyPort() - require.Error(t, err, "no reply port present") -} - -func TestOptServerPriority(t *testing.T) { - o := OptServerPriority(1234) - require.Equal(t, OptionServerPriority, o.Code, "Code") - require.Equal(t, []byte{4, 210}, o.Value.ToBytes(), "ToBytes") - require.Equal(t, "BSDP Server Priority: 1234", o.String()) -} - -func TestGetServerPriority(t *testing.T) { - o := VendorOptions{dhcpv4.OptionsFromList(OptServerPriority(1234))} - prio, err := o.ServerPriority() - require.NoError(t, err) - require.Equal(t, uint16(1234), prio) - - o = VendorOptions{dhcpv4.Options{}} - _, err = o.ServerPriority() - require.Error(t, err, "no server prio present") -} - -func TestOptMachineName(t *testing.T) { - o := OptMachineName("foo") - require.Equal(t, OptionMachineName, o.Code, "Code") - require.Equal(t, []byte("foo"), o.Value.ToBytes(), "ToBytes") - require.Equal(t, "BSDP Machine Name: foo", o.String()) -} - -func TestGetMachineName(t *testing.T) { - o := VendorOptions{dhcpv4.OptionsFromList(OptMachineName("foo"))} - require.Equal(t, "foo", o.MachineName()) - - o = VendorOptions{dhcpv4.Options{}} - require.Equal(t, "", o.MachineName()) -} - -func TestOptVersion(t *testing.T) { - o := OptVersion(Version1_1) - require.Equal(t, OptionVersion, o.Code, "Code") - require.Equal(t, []byte{1, 1}, o.Value.ToBytes(), "ToBytes") - require.Equal(t, "BSDP Version: 1.1", o.String()) -} - -func TestGetVersion(t *testing.T) { - o := VendorOptions{dhcpv4.OptionsFromList(OptVersion(Version1_1))} - ver, err := o.Version() - require.NoError(t, err) - require.Equal(t, ver, Version1_1) - - o = VendorOptions{dhcpv4.Options{}} - _, err = o.Version() - require.Error(t, err, "no version present") - - o = VendorOptions{dhcpv4.Options{OptionVersion.Code(): []byte{}}} - _, err = o.Version() - require.Error(t, err, "empty version field") - - o = VendorOptions{dhcpv4.Options{OptionVersion.Code(): []byte{1}}} - _, err = o.Version() - require.Error(t, err, "version option too short") - - o = VendorOptions{dhcpv4.Options{OptionVersion.Code(): []byte{1, 2, 3}}} - _, err = o.Version() - require.Error(t, err, "version option too long") -} - -func TestOptServerIdentifier(t *testing.T) { - o := OptServerIdentifier(net.IP{1, 1, 1, 1}) - require.Equal(t, OptionServerIdentifier, o.Code, "Code") - require.Equal(t, []byte{1, 1, 1, 1}, o.Value.ToBytes(), "ToBytes") - require.Equal(t, "BSDP Server Identifier: 1.1.1.1", o.String()) -} - -func TestGetServerIdentifier(t *testing.T) { - o := VendorOptions{dhcpv4.OptionsFromList(OptServerIdentifier(net.IP{1, 1, 1, 1}))} - require.Equal(t, net.IP{1, 1, 1, 1}, o.ServerIdentifier()) - - o = VendorOptions{dhcpv4.Options{}} - require.Nil(t, o.ServerIdentifier()) -} diff --git a/dhcpv4/bsdp/bsdp_test.go b/dhcpv4/bsdp/bsdp_test.go deleted file mode 100644 index 05cd85c..0000000 --- a/dhcpv4/bsdp/bsdp_test.go +++ /dev/null @@ -1,410 +0,0 @@ -package bsdp - -import ( - "net" - "testing" - - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/insomniacslk/dhcp/iana" - "github.com/stretchr/testify/require" -) - -func RequireHasOption(t *testing.T, opts dhcpv4.Options, opt dhcpv4.Option) { - require.NotNil(t, opts, "must pass list of options") - require.NotNil(t, opt, "must pass option") - require.True(t, opts.Has(opt.Code)) - actual := opts.Get(opt.Code) - require.Equal(t, opt.Value.ToBytes(), actual) -} - -func TestParseBootImageListFromAck(t *testing.T) { - expectedBootImages := []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", - }, - } - ack, _ := dhcpv4.New() - ack.UpdateOption(OptVendorOptions( - 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() - images, err := ParseBootImageListFromAck(ack) - require.Error(t, err) - require.Empty(t, images, "no BootImages") -} - -func TestNeedsReplyPort(t *testing.T) { - require.True(t, needsReplyPort(123)) - require.False(t, needsReplyPort(0)) - require.False(t, needsReplyPort(dhcpv4.ClientPort)) -} - -func TestNewInformList_NoReplyPort(t *testing.T) { - hwAddr := net.HardwareAddr{1, 2, 3, 4, 5, 6} - localIP := net.IPv4(10, 10, 11, 11) - m, err := NewInformList(hwAddr, localIP, 0) - - require.NoError(t, err) - require.True(t, m.Options.Has(dhcpv4.OptionVendorSpecificInformation)) - require.True(t, m.Options.Has(dhcpv4.OptionParameterRequestList)) - require.True(t, m.Options.Has(dhcpv4.OptionMaximumDHCPMessageSize)) - - vendorOpts := GetVendorOptions(m.Options) - require.NotNil(t, vendorOpts, "vendor opts not present") - require.True(t, vendorOpts.Has(OptionMessageType)) - require.True(t, vendorOpts.Has(OptionVersion)) - - mt := vendorOpts.MessageType() - require.Equal(t, MessageTypeList, mt) -} - -func TestNewInformList_ReplyPort(t *testing.T) { - hwAddr := net.HardwareAddr{1, 2, 3, 4, 5, 6} - localIP := net.IPv4(10, 10, 11, 11) - replyPort := uint16(11223) - - // Bad reply port - _, err := NewInformList(hwAddr, localIP, replyPort) - require.Error(t, err) - - // Good reply port - replyPort = uint16(999) - m, err := NewInformList(hwAddr, localIP, replyPort) - require.NoError(t, err) - - vendorOpts := GetVendorOptions(m.Options) - require.True(t, vendorOpts.Options.Has(OptionReplyPort)) - - port, err := vendorOpts.ReplyPort() - require.NoError(t, err) - require.Equal(t, replyPort, port) -} - -func newAck(hwAddr net.HardwareAddr, transactionID [4]byte) *dhcpv4.DHCPv4 { - ack, _ := dhcpv4.New() - ack.OpCode = dhcpv4.OpcodeBootReply - ack.TransactionID = transactionID - ack.HWType = iana.HWTypeEthernet - ack.ClientHWAddr = hwAddr - ack.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck)) - return ack -} - -func TestInformSelectForAck_Broadcast(t *testing.T) { - hwAddr := net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66} - tid := [4]byte{0x22, 0, 0, 0} - serverID := net.IPv4(1, 2, 3, 4) - bootImage := BootImage{ - ID: BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOSX, - Index: 0x1000, - }, - Name: "bsdp-1", - } - ack := newAck(hwAddr, tid) - ack.SetBroadcast() - ack.UpdateOption(dhcpv4.OptServerIdentifier(serverID)) - - m, err := InformSelectForAck(PacketFor(ack), 0, bootImage) - require.NoError(t, err) - require.Equal(t, dhcpv4.OpcodeBootRequest, m.OpCode) - require.Equal(t, ack.HWType, m.HWType) - require.Equal(t, ack.ClientHWAddr, m.ClientHWAddr) - require.Equal(t, ack.TransactionID, m.TransactionID) - require.True(t, m.IsBroadcast()) - - // Validate options. - require.True(t, m.Options.Has(dhcpv4.OptionClassIdentifier)) - require.True(t, m.Options.Has(dhcpv4.OptionParameterRequestList)) - require.True(t, m.Options.Has(dhcpv4.OptionDHCPMessageType)) - mt := m.MessageType() - require.Equal(t, dhcpv4.MessageTypeInform, mt) - - // Validate vendor opts. - require.True(t, m.Options.Has(dhcpv4.OptionVendorSpecificInformation)) - vendorOpts := GetVendorOptions(m.Options).Options - RequireHasOption(t, vendorOpts, OptMessageType(MessageTypeSelect)) - require.True(t, vendorOpts.Has(OptionVersion)) - RequireHasOption(t, vendorOpts, OptSelectedBootImageID(bootImage.ID)) - RequireHasOption(t, vendorOpts, OptServerIdentifier(serverID)) -} - -func TestInformSelectForAck_NoServerID(t *testing.T) { - hwAddr := net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66} - tid := [4]byte{0x22, 0, 0, 0} - bootImage := BootImage{ - ID: BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOSX, - Index: 0x1000, - }, - Name: "bsdp-1", - } - ack := newAck(hwAddr, tid) - - _, err := InformSelectForAck(PacketFor(ack), 0, bootImage) - require.Error(t, err, "expect error for no server identifier option") -} - -func TestInformSelectForAck_BadReplyPort(t *testing.T) { - hwAddr := net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66} - tid := [4]byte{0x22, 0, 0, 0} - serverID := net.IPv4(1, 2, 3, 4) - bootImage := BootImage{ - ID: BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOSX, - Index: 0x1000, - }, - Name: "bsdp-1", - } - ack := newAck(hwAddr, tid) - ack.SetBroadcast() - ack.UpdateOption(dhcpv4.OptServerIdentifier(serverID)) - - _, err := InformSelectForAck(PacketFor(ack), 11223, bootImage) - require.Error(t, err, "expect error for > 1024 replyPort") -} - -func TestInformSelectForAck_ReplyPort(t *testing.T) { - hwAddr := net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66} - tid := [4]byte{0x22, 0, 0, 0} - serverID := net.IPv4(1, 2, 3, 4) - bootImage := BootImage{ - ID: BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOSX, - Index: 0x1000, - }, - Name: "bsdp-1", - } - ack := newAck(hwAddr, tid) - ack.SetBroadcast() - ack.UpdateOption(dhcpv4.OptServerIdentifier(serverID)) - - replyPort := uint16(999) - m, err := InformSelectForAck(PacketFor(ack), replyPort, bootImage) - require.NoError(t, err) - - require.True(t, m.Options.Has(dhcpv4.OptionVendorSpecificInformation)) - vendorOpts := GetVendorOptions(m.Options).Options - RequireHasOption(t, vendorOpts, OptReplyPort(replyPort)) -} - -func TestNewReplyForInformList_NoDefaultImage(t *testing.T) { - inform, _ := NewInformList(net.HardwareAddr{1, 2, 3, 4, 5, 6}, net.IP{1, 2, 3, 4}, dhcpv4.ClientPort) - _, err := NewReplyForInformList(inform, ReplyConfig{}) - require.Error(t, err) -} - -func TestNewReplyForInformList_NoImages(t *testing.T) { - inform, _ := NewInformList(net.HardwareAddr{1, 2, 3, 4, 5, 6}, net.IP{1, 2, 3, 4}, dhcpv4.ClientPort) - fakeImage := BootImage{ - ID: BootImageID{ImageType: BootImageTypeMacOSX}, - } - _, err := NewReplyForInformList(inform, ReplyConfig{ - Images: []BootImage{}, - DefaultImage: &fakeImage, - }) - require.Error(t, err) - - _, err = NewReplyForInformList(inform, ReplyConfig{ - Images: nil, - SelectedImage: &fakeImage, - }) - require.Error(t, err) -} - -func TestNewReplyForInformList(t *testing.T) { - inform, _ := NewInformList(net.HardwareAddr{1, 2, 3, 4, 5, 6}, net.IP{1, 2, 3, 4}, dhcpv4.ClientPort) - images := []BootImage{ - BootImage{ - ID: BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOSX, - Index: 0x7070, - }, - Name: "image-1", - }, - BootImage{ - ID: BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOSX, - Index: 0x8080, - }, - Name: "image-2", - }, - } - config := ReplyConfig{ - Images: images, - DefaultImage: &images[0], - ServerIP: net.IP{9, 9, 9, 9}, - ServerHostname: "bsdp.foo.com", - ServerPriority: 0x7070, - } - ack, err := NewReplyForInformList(inform, config) - require.NoError(t, err) - require.Equal(t, net.IP{1, 2, 3, 4}, ack.ClientIPAddr) - require.Equal(t, net.IPv4zero, ack.YourIPAddr) - require.Equal(t, "bsdp.foo.com", ack.ServerHostName) - - // Validate options. - RequireHasOption(t, ack.Options, dhcpv4.OptMessageType(dhcpv4.MessageTypeAck)) - RequireHasOption(t, ack.Options, dhcpv4.OptServerIdentifier(net.IP{9, 9, 9, 9})) - RequireHasOption(t, ack.Options, dhcpv4.OptClassIdentifier(AppleVendorID)) - require.NotNil(t, ack.GetOneOption(dhcpv4.OptionVendorSpecificInformation)) - - // Vendor-specific options. - vendorOpts := GetVendorOptions(ack.Options).Options - RequireHasOption(t, vendorOpts, OptMessageType(MessageTypeList)) - RequireHasOption(t, vendorOpts, OptDefaultBootImageID(images[0].ID)) - RequireHasOption(t, vendorOpts, OptServerPriority(0x7070)) - RequireHasOption(t, vendorOpts, OptBootImageList(images...)) - - // Add in selected boot image, ensure it's in the generated ACK. - config.SelectedImage = &images[0] - ack, err = NewReplyForInformList(inform, config) - require.NoError(t, err) - vendorOpts = GetVendorOptions(ack.Options).Options - RequireHasOption(t, vendorOpts, OptSelectedBootImageID(images[0].ID)) -} - -func TestNewReplyForInformSelect_NoSelectedImage(t *testing.T) { - inform, _ := NewInformList(net.HardwareAddr{1, 2, 3, 4, 5, 6}, net.IP{1, 2, 3, 4}, dhcpv4.ClientPort) - _, err := NewReplyForInformSelect(inform, ReplyConfig{}) - require.Error(t, err) -} - -func TestNewReplyForInformSelect_NoImages(t *testing.T) { - inform, _ := NewInformList(net.HardwareAddr{1, 2, 3, 4, 5, 6}, net.IP{1, 2, 3, 4}, dhcpv4.ClientPort) - fakeImage := BootImage{ - ID: BootImageID{ImageType: BootImageTypeMacOSX}, - } - _, err := NewReplyForInformSelect(inform, ReplyConfig{ - Images: []BootImage{}, - SelectedImage: &fakeImage, - }) - require.Error(t, err) - - _, err = NewReplyForInformSelect(inform, ReplyConfig{ - Images: nil, - SelectedImage: &fakeImage, - }) - require.Error(t, err) -} - -func TestNewReplyForInformSelect(t *testing.T) { - inform, _ := NewInformList(net.HardwareAddr{1, 2, 3, 4, 5, 6}, net.IP{1, 2, 3, 4}, dhcpv4.ClientPort) - images := []BootImage{ - BootImage{ - ID: BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOSX, - Index: 0x7070, - }, - Name: "image-1", - }, - BootImage{ - ID: BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOSX, - Index: 0x8080, - }, - Name: "image-2", - }, - } - config := ReplyConfig{ - Images: images, - SelectedImage: &images[0], - ServerIP: net.IP{9, 9, 9, 9}, - ServerHostname: "bsdp.foo.com", - ServerPriority: 0x7070, - } - ack, err := NewReplyForInformSelect(inform, config) - require.NoError(t, err) - require.Equal(t, net.IP{1, 2, 3, 4}, ack.ClientIPAddr) - require.Equal(t, net.IPv4zero, ack.YourIPAddr) - require.Equal(t, "bsdp.foo.com", ack.ServerHostName) - - // Validate options. - RequireHasOption(t, ack.Options, dhcpv4.OptMessageType(dhcpv4.MessageTypeAck)) - RequireHasOption(t, ack.Options, dhcpv4.OptServerIdentifier(net.IP{9, 9, 9, 9})) - RequireHasOption(t, ack.Options, dhcpv4.OptServerIdentifier(net.IP{9, 9, 9, 9})) - RequireHasOption(t, ack.Options, dhcpv4.OptClassIdentifier(AppleVendorID)) - require.NotNil(t, ack.GetOneOption(dhcpv4.OptionVendorSpecificInformation)) - - vendorOpts := GetVendorOptions(ack.Options) - RequireHasOption(t, vendorOpts.Options, OptMessageType(MessageTypeSelect)) - RequireHasOption(t, vendorOpts.Options, OptSelectedBootImageID(images[0].ID)) -} - -func TestMessageTypeForPacket(t *testing.T) { - testcases := []struct { - tcName string - opts []dhcpv4.Option - wantMessageType MessageType - }{ - { - tcName: "No options", - opts: []dhcpv4.Option{}, - }, - { - tcName: "Some options, no vendor opts", - opts: []dhcpv4.Option{ - dhcpv4.OptHostName("foobar1234"), - }, - }, - { - tcName: "Vendor opts, no message type", - opts: []dhcpv4.Option{ - dhcpv4.OptHostName("foobar1234"), - OptVendorOptions( - OptVersion(Version1_1), - ), - }, - }, - { - tcName: "Vendor opts, with message type", - opts: []dhcpv4.Option{ - dhcpv4.OptHostName("foobar1234"), - OptVendorOptions( - OptVersion(Version1_1), - OptMessageType(MessageTypeList), - ), - }, - wantMessageType: MessageTypeList, - }, - } - for _, tt := range testcases { - t.Run(tt.tcName, func(t *testing.T) { - pkt, _ := dhcpv4.New() - for _, opt := range tt.opts { - pkt.UpdateOption(opt) - } - gotMessageType := MessageTypeFromPacket(pkt) - require.Equal(t, tt.wantMessageType, gotMessageType) - }) - } -} diff --git a/dhcpv4/bsdp/client.go b/dhcpv4/bsdp/client.go deleted file mode 100644 index 5d0e667..0000000 --- a/dhcpv4/bsdp/client.go +++ /dev/null @@ -1,75 +0,0 @@ -package bsdp - -import ( - "errors" - - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/insomniacslk/dhcp/dhcpv4/client4" -) - -// Client represents a BSDP client that can perform BSDP exchanges via the -// broadcast address. -type Client struct { - client4.Client -} - -// NewClient constructs a new client with default read and write timeouts from -// dhcpv4.Client. -func NewClient() *Client { - return &Client{Client: client4.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) Exchange(ifname string) ([]*Packet, error) { - conversation := make([]*Packet, 0) - - // Get our file descriptor for the broadcast socket. - sendFd, err := client4.MakeBroadcastSocket(ifname) - if err != nil { - return conversation, err - } - recvFd, err := client4.MakeListeningSocket(ifname) - if err != nil { - return conversation, err - } - - // INFORM[LIST] - informList, err := NewInformListForInterface(ifname, dhcpv4.ClientPort) - if err != nil { - return conversation, err - } - conversation = append(conversation, informList) - - // ACK[LIST] - ackForList, err := c.Client.SendReceive(sendFd, recvFd, informList.v4(), dhcpv4.MessageTypeAck) - if err != nil { - return conversation, err - } - - // Rewrite vendor-specific option for pretty printing. - conversation = append(conversation, PacketFor(ackForList)) - - // Parse boot images sent back by server - bootImages, err := ParseBootImageListFromAck(ackForList) - if err != nil { - return conversation, err - } - if len(bootImages) == 0 { - return conversation, errors.New("got no BootImages from server") - } - - // INFORM[SELECT] - informSelect, err := InformSelectForAck(PacketFor(ackForList), dhcpv4.ClientPort, bootImages[0]) - if err != nil { - return conversation, err - } - conversation = append(conversation, informSelect) - - // ACK[SELECT] - ackForSelect, err := c.Client.SendReceive(sendFd, recvFd, informSelect.v4(), dhcpv4.MessageTypeAck) - if err != nil { - return conversation, err - } - return append(conversation, PacketFor(ackForSelect)), nil -} diff --git a/dhcpv4/bsdp/doc.go b/dhcpv4/bsdp/doc.go deleted file mode 100644 index b3a2186..0000000 --- a/dhcpv4/bsdp/doc.go +++ /dev/null @@ -1,10 +0,0 @@ -/* -The BSDP package implements Apple's Boot Service Discovery Protocol (a -pxe-boot-like netboot protocol for booting macOS hardware from -network-connected servers). - -The Canonical implementation is defined here: -http://opensource.apple.com/source/bootp/bootp-198.1/Documentation/BSDP.doc -*/ - -package bsdp diff --git a/dhcpv4/bsdp/option_vendor_specific_information.go b/dhcpv4/bsdp/option_vendor_specific_information.go deleted file mode 100644 index 4c2365c..0000000 --- a/dhcpv4/bsdp/option_vendor_specific_information.go +++ /dev/null @@ -1,157 +0,0 @@ -package bsdp - -import ( - "fmt" - "net" - - "github.com/insomniacslk/dhcp/dhcpv4" -) - -// VendorOptions is like dhcpv4.Options, but stringifies using BSDP-specific -// option codes. -type VendorOptions struct { - dhcpv4.Options -} - -// String prints the contained options using BSDP-specific option code parsing. -func (v VendorOptions) String() string { - return v.Options.ToString(bsdpHumanizer) -} - -// FromBytes parses vendor options from -func (v *VendorOptions) FromBytes(data []byte) error { - v.Options = make(dhcpv4.Options) - return v.Options.FromBytes(data) -} - -// DefaultBootImageID returns the default boot image ID in v. -func (v VendorOptions) DefaultBootImageID() *BootImageID { - return getBootImageID(OptionDefaultBootImageID, v.Options) -} - -// SelectedBootImageID returns the selected boot image ID in v. -func (v VendorOptions) SelectedBootImageID() *BootImageID { - return getBootImageID(OptionSelectedBootImageID, v.Options) -} - -// BootImageList returns the BSDP boot image list in v. -func (v VendorOptions) BootImageList() BootImageList { - val := v.Options.Get(OptionBootImageList) - if val == nil { - return nil - } - var bil BootImageList - if err := bil.FromBytes(val); err != nil { - return nil - } - return bil -} - -// MessageType returns the BSDP Message Type in v. -func (v VendorOptions) MessageType() MessageType { - val := v.Options.Get(OptionMessageType) - if val == nil { - return MessageTypeNone - } - var m MessageType - if err := m.FromBytes(val); err != nil { - return MessageTypeNone - } - return m -} - -// GetVersion returns the BSDP version in v if present. -func (v VendorOptions) Version() (Version, error) { - val := v.Options.Get(OptionVersion) - if val == nil { - return Version{0, 0}, fmt.Errorf("version not found") - } - var ver Version - if err := ver.FromBytes(val); err != nil { - return Version{0, 0}, err - } - return ver, nil -} - -// GetServerIdentifier returns the BSDP Server Identifier value in v if present. -func (v VendorOptions) ServerIdentifier() net.IP { - return dhcpv4.GetIP(OptionServerIdentifier, v.Options) -} - -// GetReplyPort returns the BSDP reply port in v if present. -func (v VendorOptions) ReplyPort() (uint16, error) { - return dhcpv4.GetUint16(OptionReplyPort, v.Options) -} - -// GetServerPriority returns the BSDP server priority in v if present. -func (v VendorOptions) ServerPriority() (uint16, error) { - return dhcpv4.GetUint16(OptionServerPriority, v.Options) -} - -// GetMachineName finds and parses the BSDP Machine Name option from v. -func (v VendorOptions) MachineName() string { - return dhcpv4.GetString(OptionMachineName, v.Options) -} - -// OptVendorOptions returns the BSDP Vendor Specific Info in o. -func OptVendorOptions(o ...dhcpv4.Option) dhcpv4.Option { - return dhcpv4.Option{ - Code: dhcpv4.OptionVendorSpecificInformation, - Value: VendorOptions{dhcpv4.OptionsFromList(o...)}, - } -} - -// GetVendorOptions returns a new BSDP Vendor Specific Info option. -func GetVendorOptions(o dhcpv4.Options) *VendorOptions { - v := o.Get(dhcpv4.OptionVendorSpecificInformation) - if v == nil { - return nil - } - var vo VendorOptions - if err := vo.FromBytes(v); err != nil { - return nil - } - return &vo -} - -var bsdpHumanizer = dhcpv4.OptionHumanizer{ - ValueHumanizer: parseOption, - CodeHumanizer: func(c uint8) dhcpv4.OptionCode { - return optionCode(c) - }, -} - -// parseOption is similar to dhcpv4.parseOption, except that it interprets -// option codes based on the BSDP-specific options. -func parseOption(code dhcpv4.OptionCode, data []byte) fmt.Stringer { - var d dhcpv4.OptionDecoder - switch code { - case OptionMachineName: - var s dhcpv4.String - d = &s - - case OptionServerIdentifier: - d = &dhcpv4.IP{} - - case OptionServerPriority, OptionReplyPort: - var u dhcpv4.Uint16 - d = &u - - case OptionBootImageList: - d = &BootImageList{} - - case OptionDefaultBootImageID, OptionSelectedBootImageID: - d = &BootImageID{} - - case OptionMessageType: - var m MessageType - d = &m - - case OptionVersion: - d = &Version{} - } - if d != nil && d.FromBytes(data) == nil { - return d - } - return dhcpv4.OptionGeneric{Data: data} -} diff --git a/dhcpv4/bsdp/option_vendor_specific_information_test.go b/dhcpv4/bsdp/option_vendor_specific_information_test.go deleted file mode 100644 index a6727f5..0000000 --- a/dhcpv4/bsdp/option_vendor_specific_information_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package bsdp - -import ( - "net" - "testing" - - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/stretchr/testify/require" -) - -func TestOptVendorSpecificInformationInterfaceMethods(t *testing.T) { - o := OptVendorOptions( - OptVersion(Version1_1), - OptMessageType(MessageTypeList), - ) - require.Equal(t, dhcpv4.OptionVendorSpecificInformation, o.Code, "Code") - - expectedBytes := []byte{ - 1, 1, 1, // List option - 2, 2, 1, 1, // Version option - } - require.Equal(t, expectedBytes, o.Value.ToBytes(), "ToBytes") -} - -func TestOptVendorSpecificInformationString(t *testing.T) { - o := OptVendorOptions( - OptMessageType(MessageTypeList), - OptVersion(Version1_1), - ) - expectedString := "Vendor Specific Information:\n BSDP Message Type: LIST\n BSDP Version: 1.1\n" - require.Equal(t, expectedString, o.String()) - - // Test more complicated string - sub options of sub options. - o = OptVendorOptions( - OptMessageType(MessageTypeList), - OptBootImageList( - BootImage{ - ID: BootImageID{ - IsInstall: false, - ImageType: BootImageTypeMacOSX, - Index: 1001, - }, - Name: "bsdp-1", - }, - BootImage{ - ID: BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOS9, - Index: 9009, - }, - Name: "bsdp-2", - }, - ), - OptMachineName("foo"), - OptServerIdentifier(net.IP{1, 1, 1, 1}), - OptServerPriority(1234), - OptReplyPort(1235), - OptDefaultBootImageID(BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOS9, - Index: 9009, - }), - OptSelectedBootImageID(BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOS9, - Index: 9009, - }), - ) - expectedString = "Vendor Specific Information:\n" + - " BSDP Message Type: LIST\n" + - " BSDP Server Identifier: 1.1.1.1\n" + - " BSDP Server Priority: 1234\n" + - " BSDP Reply Port: 1235\n" + - " BSDP Default Boot Image ID: [9009] installable macOS 9 image\n" + - " BSDP Selected Boot Image ID: [9009] installable macOS 9 image\n" + - " BSDP Boot Image List: bsdp-1 [1001] uninstallable macOS image, bsdp-2 [9009] installable macOS 9 image\n" + - " BSDP Machine Name: foo\n" - require.Equal(t, expectedString, o.String()) -} diff --git a/dhcpv4/bsdp/types.go b/dhcpv4/bsdp/types.go deleted file mode 100644 index 4931081..0000000 --- a/dhcpv4/bsdp/types.go +++ /dev/null @@ -1,64 +0,0 @@ -package bsdp - -import ( - "fmt" -) - -// DefaultMacOSVendorClassIdentifier is a default vendor class identifier used -// on non-darwin hosts where the vendor class identifier cannot be determined. -// It should mostly be used for debugging if testing BSDP on a non-darwin -// system. -const DefaultMacOSVendorClassIdentifier = AppleVendorID + "/i386/MacMini6,1" - -// optionCode are BSDP option codes. -// -// optionCode implements the dhcpv4.OptionCode interface. -type optionCode uint8 - -func (o optionCode) Code() uint8 { - return uint8(o) -} - -func (o optionCode) String() string { - if s, ok := optionCodeToString[o]; ok { - return s - } - return fmt.Sprintf("unknown (%d)", o) -} - -// Options (occur as sub-options of DHCP option 43). -const ( - OptionMessageType optionCode = 1 - OptionVersion optionCode = 2 - OptionServerIdentifier optionCode = 3 - OptionServerPriority optionCode = 4 - OptionReplyPort optionCode = 5 - OptionBootImageListPath optionCode = 6 // Not used - OptionDefaultBootImageID optionCode = 7 - OptionSelectedBootImageID optionCode = 8 - OptionBootImageList optionCode = 9 - OptionNetboot1_0Firmware optionCode = 10 - OptionBootImageAttributesFilterList optionCode = 11 - OptionShadowMountPath optionCode = 128 - OptionShadowFilePath optionCode = 129 - OptionMachineName optionCode = 130 -) - -// optionCodeToString maps BSDP OptionCodes to human-readable strings -// describing what they are. -var optionCodeToString = map[optionCode]string{ - OptionMessageType: "BSDP Message Type", - OptionVersion: "BSDP Version", - OptionServerIdentifier: "BSDP Server Identifier", - OptionServerPriority: "BSDP Server Priority", - OptionReplyPort: "BSDP Reply Port", - OptionBootImageListPath: "", // Not used - 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/bsdp/vendor_class_identifier.go b/dhcpv4/bsdp/vendor_class_identifier.go deleted file mode 100644 index dfd1ca9..0000000 --- a/dhcpv4/bsdp/vendor_class_identifier.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build !darwin - -package bsdp - -// MakeVendorClassIdentifier returns a static vendor class identifier for BSDP -// use on non-darwin hosts. -func MakeVendorClassIdentifier() (string, error) { - return DefaultMacOSVendorClassIdentifier, nil -} diff --git a/dhcpv4/bsdp/vendor_class_identifier_darwin.go b/dhcpv4/bsdp/vendor_class_identifier_darwin.go deleted file mode 100644 index 46ef656..0000000 --- a/dhcpv4/bsdp/vendor_class_identifier_darwin.go +++ /dev/null @@ -1,18 +0,0 @@ -package bsdp - -import ( - "fmt" - - "golang.org/x/sys/unix" -) - -// MakeVendorClassIdentifier calls the sysctl syscall on macOS to get the -// platform model. -func MakeVendorClassIdentifier() (string, error) { - // Fetch hardware model for class ID. - hwModel, err := unix.Sysctl("hw.model") - if err != nil { - return "", err - } - return fmt.Sprintf("%s/i386/%s", AppleVendorID, hwModel), nil -} |