From c90ab10024ada840e24bb028a3405961e8e4c26a Mon Sep 17 00:00:00 2001 From: Christopher Koch Date: Sat, 29 Dec 2018 14:48:10 -0800 Subject: dhcpv4: nicer API for option parsing. From: r := d.GetOneOption(OptionRouter).(*OptRouter).Routers d.UpdateOption(&OptRouter{Routers: []net.IP{net.IP{192, 168, 0, 1}}}) To: r := GetRouter(d.Options) d.UpdateOption(OptRouter(net.IP{192, 168, 0, 1}, ...)) --- dhcpv4/bsdp/boot_image.go | 51 ++++- dhcpv4/bsdp/bsdp.go | 158 ++++++++------- dhcpv4/bsdp/bsdp_option_boot_image_list.go | 66 ++++--- dhcpv4/bsdp/bsdp_option_boot_image_list_test.go | 19 +- dhcpv4/bsdp/bsdp_option_default_boot_image_id.go | 42 ---- .../bsdp/bsdp_option_default_boot_image_id_test.go | 46 ----- dhcpv4/bsdp/bsdp_option_generic.go | 36 ---- dhcpv4/bsdp/bsdp_option_generic_test.go | 57 ------ dhcpv4/bsdp/bsdp_option_machine_name.go | 34 ---- dhcpv4/bsdp/bsdp_option_machine_name_test.go | 26 --- dhcpv4/bsdp/bsdp_option_message_type.go | 50 +++-- dhcpv4/bsdp/bsdp_option_message_type_test.go | 19 +- dhcpv4/bsdp/bsdp_option_misc.go | 99 ++++++++++ dhcpv4/bsdp/bsdp_option_misc_test.go | 95 +++++++++ dhcpv4/bsdp/bsdp_option_reply_port.go | 42 ---- dhcpv4/bsdp/bsdp_option_reply_port_test.go | 36 ---- dhcpv4/bsdp/bsdp_option_selected_boot_image_id.go | 42 ---- .../bsdp_option_selected_boot_image_id_test.go | 46 ----- dhcpv4/bsdp/bsdp_option_server_identifier.go | 36 ---- dhcpv4/bsdp/bsdp_option_server_identifier_test.go | 33 ---- dhcpv4/bsdp/bsdp_option_server_priority.go | 37 ---- dhcpv4/bsdp/bsdp_option_server_priority_test.go | 30 --- dhcpv4/bsdp/bsdp_option_version.go | 41 ---- dhcpv4/bsdp/bsdp_option_version_test.go | 29 --- dhcpv4/bsdp/bsdp_test.go | 152 +++++++------- dhcpv4/bsdp/client.go | 32 +-- dhcpv4/bsdp/option_vendor_specific_information.go | 136 ++++++------- .../option_vendor_specific_information_test.go | 218 +++++---------------- dhcpv4/bsdp/types.go | 6 +- 29 files changed, 613 insertions(+), 1101 deletions(-) delete mode 100644 dhcpv4/bsdp/bsdp_option_default_boot_image_id.go delete mode 100644 dhcpv4/bsdp/bsdp_option_default_boot_image_id_test.go delete mode 100644 dhcpv4/bsdp/bsdp_option_generic.go delete mode 100644 dhcpv4/bsdp/bsdp_option_generic_test.go delete mode 100644 dhcpv4/bsdp/bsdp_option_machine_name.go delete mode 100644 dhcpv4/bsdp/bsdp_option_machine_name_test.go create mode 100644 dhcpv4/bsdp/bsdp_option_misc.go create mode 100644 dhcpv4/bsdp/bsdp_option_misc_test.go delete mode 100644 dhcpv4/bsdp/bsdp_option_reply_port.go delete mode 100644 dhcpv4/bsdp/bsdp_option_reply_port_test.go delete mode 100644 dhcpv4/bsdp/bsdp_option_selected_boot_image_id.go delete mode 100644 dhcpv4/bsdp/bsdp_option_selected_boot_image_id_test.go delete mode 100644 dhcpv4/bsdp/bsdp_option_server_identifier.go delete mode 100644 dhcpv4/bsdp/bsdp_option_server_identifier_test.go delete mode 100644 dhcpv4/bsdp/bsdp_option_server_priority.go delete mode 100644 dhcpv4/bsdp/bsdp_option_server_priority_test.go delete mode 100644 dhcpv4/bsdp/bsdp_option_version.go delete mode 100644 dhcpv4/bsdp/bsdp_option_version_test.go (limited to 'dhcpv4/bsdp') diff --git a/dhcpv4/bsdp/boot_image.go b/dhcpv4/bsdp/boot_image.go index 954dcb6..58b5167 100644 --- a/dhcpv4/bsdp/boot_image.go +++ b/dhcpv4/bsdp/boot_image.go @@ -3,6 +3,7 @@ package bsdp import ( "fmt" + "github.com/insomniacslk/dhcp/dhcpv4" "github.com/u-root/u-root/pkg/uio" ) @@ -18,9 +19,9 @@ const ( // 4 - 127 are reserved for future use. ) -// BootImageTypeToString maps the different BootImageTypes to human-readable +// bootImageTypeToString maps the different BootImageTypes to human-readable // representations. -var BootImageTypeToString = map[BootImageType]string{ +var bootImageTypeToString = map[BootImageType]string{ BootImageTypeMacOS9: "macOS 9", BootImageTypeMacOSX: "macOS", BootImageTypeMacOSXServer: "macOS Server", @@ -35,6 +36,16 @@ type BootImageID struct { 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 @@ -55,7 +66,7 @@ func (b BootImageID) String() string { } else { s += " uninstallable" } - t, ok := BootImageTypeToString[b.ImageType] + t, ok := bootImageTypeToString[b.ImageType] if !ok { t = "unknown" } @@ -99,3 +110,37 @@ func (b *BootImage) Unmarshal(buf *uio.Lexer) error { 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} +} + +// GetDefaultBootImageID returns the default boot image ID contained in o. +func GetDefaultBootImageID(o dhcpv4.Options) *BootImageID { + return getBootImageID(OptionDefaultBootImageID, o) +} + +// 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} +} + +// GetSelectedBootImageID returns the selected boot image ID contained in o. +func GetSelectedBootImageID(o dhcpv4.Options) *BootImageID { + return getBootImageID(OptionSelectedBootImageID, o) +} diff --git a/dhcpv4/bsdp/bsdp.go b/dhcpv4/bsdp/bsdp.go index 3cc87d2..9bcc15d 100644 --- a/dhcpv4/bsdp/bsdp.go +++ b/dhcpv4/bsdp/bsdp.go @@ -30,20 +30,12 @@ type ReplyConfig struct { // 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) { - opt := ack.GetOneOption(dhcpv4.OptionVendorSpecificInformation) - if opt == nil { +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") } - vendorOpt, err := ParseOptVendorSpecificInformation(opt.ToBytes()) - if err != nil { - return nil, err - } - bootImageOpts := vendorOpt.GetOneOption(OptionBootImageList) - if bootImageOpts == nil { - return nil, fmt.Errorf("boot image option not found") - } - return bootImageOpts.(*OptBootImageList).Images, nil + return GetBootImageList(vendorOpts.Options), nil } func needsReplyPort(replyPort uint16) bool { @@ -53,28 +45,41 @@ func needsReplyPort(replyPort uint16) bool { // 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 { - var ( - vendorOpts *OptVendorSpecificInformation - err error - ) - opt := packet.GetOneOption(dhcpv4.OptionVendorSpecificInformation) - if opt == nil { - return nil - } - if vendorOpts, err = ParseOptVendorSpecificInformation(opt.ToBytes()); err == nil { - if o := vendorOpts.GetOneOption(OptionMessageType); o != nil { - if optMessageType, ok := o.(*OptMessageType); ok { - return &optMessageType.Type - } - } - } - return nil +func MessageTypeFromPacket(packet *dhcpv4.DHCPv4) MessageType { + vendorOpts := GetVendorOptions(packet.Options) + if vendorOpts == nil { + return MessageTypeNone + } + return GetMessageType(vendorOpts.Options) +} + +// 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) (*dhcpv4.DHCPv4, error) { +func NewInformListForInterface(ifname string, replyPort uint16) (*Packet, error) { iface, err := net.InterfaceByName(ifname) if err != nil { return nil, err @@ -96,7 +101,7 @@ func NewInformListForInterface(ifname string, replyPort uint16) (*dhcpv4.DHCPv4, // 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) (*dhcpv4.DHCPv4, error) { +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") @@ -109,60 +114,61 @@ func NewInformList(hwaddr net.HardwareAddr, localIP net.IP, replyPort uint16, mo // These are vendor-specific options used to pass along BSDP information. vendorOpts := []dhcpv4.Option{ - &OptMessageType{MessageTypeList}, - Version1_1, + OptMessageType(MessageTypeList), + OptVersion(Version1_1), } if needsReplyPort(replyPort) { - vendorOpts = append(vendorOpts, &OptReplyPort{replyPort}) + vendorOpts = append(vendorOpts, OptReplyPort(replyPort)) } - return dhcpv4.NewInform(hwaddr, localIP, + d, err := dhcpv4.NewInform(hwaddr, localIP, dhcpv4.PrependModifiers(modifiers, dhcpv4.WithRequestedOptions( dhcpv4.OptionVendorSpecificInformation, dhcpv4.OptionClassIdentifier, ), - dhcpv4.WithOption(&dhcpv4.OptMaximumDHCPMessageSize{Size: MaxDHCPMessageSize}), - dhcpv4.WithOption(&dhcpv4.OptClassIdentifier{Identifier: vendorClassID}), - dhcpv4.WithOption(&OptVendorSpecificInformation{vendorOpts}), + 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 dhcpv4.DHCPv4, replyPort uint16, selectedImage BootImage) (*dhcpv4.DHCPv4, error) { +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}, - Version1_1, - &OptSelectedBootImageID{selectedImage.ID}, + OptMessageType(MessageTypeSelect), + OptVersion(Version1_1), + OptSelectedBootImageID(selectedImage.ID), } // Validate replyPort if requested. if needsReplyPort(replyPort) { - vendorOpts = append(vendorOpts, &OptReplyPort{replyPort}) + vendorOpts = append(vendorOpts, OptReplyPort(replyPort)) } // Find server IP address - var serverIP net.IP - if opt := ack.GetOneOption(dhcpv4.OptionServerIdentifier); opt != nil { - serverIP = opt.(*dhcpv4.OptServerIdentifier).ServerID - } + serverIP := dhcpv4.GetServerIdentifier(ack.Options) if serverIP.To4() == nil { return nil, fmt.Errorf("could not parse server identifier from ACK") } - vendorOpts = append(vendorOpts, &OptServerIdentifier{serverIP}) + vendorOpts = append(vendorOpts, OptServerIdentifier(serverIP)) vendorClassID, err := MakeVendorClassIdentifier() if err != nil { return nil, err } - return dhcpv4.New(dhcpv4.WithReply(&ack), - dhcpv4.WithOption(&dhcpv4.OptClassIdentifier{Identifier: vendorClassID}), + d, err := dhcpv4.New(dhcpv4.WithReply(ack.v4()), + dhcpv4.WithOption(dhcpv4.OptClassIdentifier(vendorClassID)), dhcpv4.WithRequestedOptions( dhcpv4.OptionSubnetMask, dhcpv4.OptionRouter, @@ -171,20 +177,24 @@ func InformSelectForAck(ack dhcpv4.DHCPv4, replyPort uint16, selectedImage BootI dhcpv4.OptionClassIdentifier, ), dhcpv4.WithMessageType(dhcpv4.MessageTypeInform), - dhcpv4.WithOption(&OptVendorSpecificInformation{vendorOpts}), + 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 *dhcpv4.DHCPv4, config ReplyConfig) (*dhcpv4.DHCPv4, error) { +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) + reply, err := dhcpv4.NewReplyFromRequest(&inform.DHCPv4) if err != nil { return nil, err } @@ -193,34 +203,34 @@ func NewReplyForInformList(inform *dhcpv4.DHCPv4, config ReplyConfig) (*dhcpv4.D reply.ServerIPAddr = config.ServerIP reply.ServerHostName = config.ServerHostname - reply.UpdateOption(&dhcpv4.OptMessageType{MessageType: dhcpv4.MessageTypeAck}) - reply.UpdateOption(&dhcpv4.OptServerIdentifier{ServerID: config.ServerIP}) - reply.UpdateOption(&dhcpv4.OptClassIdentifier{Identifier: AppleVendorID}) + reply.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck)) + reply.UpdateOption(dhcpv4.OptServerIdentifier(config.ServerIP)) + reply.UpdateOption(dhcpv4.OptClassIdentifier(AppleVendorID)) // BSDP opts. vendorOpts := []dhcpv4.Option{ - &OptMessageType{Type: MessageTypeList}, - &OptServerPriority{Priority: config.ServerPriority}, - &OptDefaultBootImageID{ID: config.DefaultImage.ID}, - &OptBootImageList{Images: config.Images}, + OptMessageType(MessageTypeList), + OptServerPriority(config.ServerPriority), + OptDefaultBootImageID(config.DefaultImage.ID), + OptBootImageList(config.Images...), } if config.SelectedImage != nil { - vendorOpts = append(vendorOpts, &OptSelectedBootImageID{ID: config.SelectedImage.ID}) + vendorOpts = append(vendorOpts, OptSelectedBootImageID(config.SelectedImage.ID)) } - reply.UpdateOption(&OptVendorSpecificInformation{Options: vendorOpts}) - return reply, nil + 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 *dhcpv4.DHCPv4, config ReplyConfig) (*dhcpv4.DHCPv4, error) { +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) + reply, err := dhcpv4.NewReplyFromRequest(&inform.DHCPv4) if err != nil { return nil, err } @@ -231,16 +241,14 @@ func NewReplyForInformSelect(inform *dhcpv4.DHCPv4, config ReplyConfig) (*dhcpv4 reply.ServerHostName = config.ServerHostname reply.BootFileName = config.BootFileName - reply.UpdateOption(&dhcpv4.OptMessageType{MessageType: dhcpv4.MessageTypeAck}) - reply.UpdateOption(&dhcpv4.OptServerIdentifier{ServerID: config.ServerIP}) - reply.UpdateOption(&dhcpv4.OptClassIdentifier{Identifier: AppleVendorID}) + reply.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck)) + reply.UpdateOption(dhcpv4.OptServerIdentifier(config.ServerIP)) + reply.UpdateOption(dhcpv4.OptClassIdentifier(AppleVendorID)) // BSDP opts. - reply.UpdateOption(&OptVendorSpecificInformation{ - Options: []dhcpv4.Option{ - &OptMessageType{Type: MessageTypeSelect}, - &OptSelectedBootImageID{ID: config.SelectedImage.ID}, - }, - }) - return reply, nil + 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 index 3282fa3..ebbbd2d 100644 --- a/dhcpv4/bsdp/bsdp_option_boot_image_list.go +++ b/dhcpv4/bsdp/bsdp_option_boot_image_list.go @@ -1,52 +1,66 @@ package bsdp import ( + "strings" + "github.com/insomniacslk/dhcp/dhcpv4" "github.com/u-root/u-root/pkg/uio" ) -// OptBootImageList contains the list of boot images presented by a netboot -// server. -type OptBootImageList struct { - Images []BootImage -} +// BootImageList contains a list of boot images presented by a netboot server. +// +// Implements the BSDP option listing the boot images. +type BootImageList []BootImage -// ParseOptBootImageList constructs an OptBootImageList struct from a sequence -// of bytes and returns it, or an error. -func ParseOptBootImageList(data []byte) (*OptBootImageList, error) { +// FromBytes deserializes data into bil. +func (bil *BootImageList) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - var bootImages []BootImage for buf.Has(5) { var image BootImage - if err := (&image).Unmarshal(buf); err != nil { - return nil, err + if err := image.Unmarshal(buf); err != nil { + return err } - bootImages = append(bootImages, image) + *bil = append(*bil, image) } - - return &OptBootImageList{bootImages}, nil -} - -// Code returns the option code. -func (o *OptBootImageList) Code() dhcpv4.OptionCode { - return OptionBootImageList + return nil } // ToBytes returns a serialized stream of bytes for this option. -func (o *OptBootImageList) ToBytes() []byte { +func (bil BootImageList) ToBytes() []byte { buf := uio.NewBigEndianBuffer(nil) - for _, image := range o.Images { + for _, image := range bil { image.Marshal(buf) } return buf.Data() } // String returns a human-readable string for this option. -func (o *OptBootImageList) String() string { - s := "BSDP Boot Image List ->" - for _, image := range o.Images { - s += "\n " + image.String() +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), + } +} + +// GetBootImageList returns the BSDP boot image list. +func GetBootImageList(o dhcpv4.Options) BootImageList { + v := o.Get(OptionBootImageList) + if v == nil { + return nil + } + var bil BootImageList + if err := bil.FromBytes(v); err != nil { + return nil } - return s + return bil } diff --git a/dhcpv4/bsdp/bsdp_option_boot_image_list_test.go b/dhcpv4/bsdp/bsdp_option_boot_image_list_test.go index 5d1b77c..6282156 100644 --- a/dhcpv4/bsdp/bsdp_option_boot_image_list_test.go +++ b/dhcpv4/bsdp/bsdp_option_boot_image_list_test.go @@ -25,8 +25,8 @@ func TestOptBootImageListInterfaceMethods(t *testing.T) { Name: "bsdp-2", }, } - o := OptBootImageList{bs} - require.Equal(t, OptionBootImageList, o.Code(), "Code") + o := OptBootImageList(bs...) + require.Equal(t, OptionBootImageList, o.Code, "Code") expectedBytes := []byte{ // boot image 1 0x1, 0x0, 0x03, 0xe9, // ID @@ -37,7 +37,7 @@ func TestOptBootImageListInterfaceMethods(t *testing.T) { 6, // name length 'b', 's', 'd', 'p', '-', '2', } - require.Equal(t, expectedBytes, o.ToBytes(), "ToBytes") + require.Equal(t, expectedBytes, o.Value.ToBytes(), "ToBytes") } func TestParseOptBootImageList(t *testing.T) { @@ -51,9 +51,10 @@ func TestParseOptBootImageList(t *testing.T) { 6, // name length 'b', 's', 'd', 'p', '-', '2', } - o, err := ParseOptBootImageList(data) + var o BootImageList + err := o.FromBytes(data) require.NoError(t, err) - expectedBootImages := []BootImage{ + expectedBootImages := BootImageList{ BootImage{ ID: BootImageID{ IsInstall: false, @@ -71,7 +72,7 @@ func TestParseOptBootImageList(t *testing.T) { Name: "bsdp-2", }, } - require.Equal(t, &OptBootImageList{expectedBootImages}, o) + require.Equal(t, expectedBootImages, o) // Error parsing boot image (malformed) data = []byte{ @@ -84,7 +85,7 @@ func TestParseOptBootImageList(t *testing.T) { 6, // name length 'b', 's', 'd', 'p', '-', '2', } - _, err = ParseOptBootImageList(data) + err = o.FromBytes(data) require.Error(t, err, "should get error from bad boot image") } @@ -107,7 +108,7 @@ func TestOptBootImageListString(t *testing.T) { Name: "bsdp-2", }, } - o := OptBootImageList{bs} - expectedString := "BSDP Boot Image List ->\n bsdp-1 [1001] uninstallable macOS image\n bsdp-2 [9009] installable macOS 9 image" + 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_default_boot_image_id.go b/dhcpv4/bsdp/bsdp_option_default_boot_image_id.go deleted file mode 100644 index 40ab0be..0000000 --- a/dhcpv4/bsdp/bsdp_option_default_boot_image_id.go +++ /dev/null @@ -1,42 +0,0 @@ -package bsdp - -import ( - "fmt" - - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/u-root/u-root/pkg/uio" -) - -// OptDefaultBootImageID contains the selected boot image ID. -// -// Implements the BSDP option default boot image ID, which tells the client -// which image is the default boot image if one is not selected. -type OptDefaultBootImageID struct { - ID BootImageID -} - -// ParseOptDefaultBootImageID constructs an OptDefaultBootImageID struct from a sequence of -// bytes and returns it, or an error. -func ParseOptDefaultBootImageID(data []byte) (*OptDefaultBootImageID, error) { - var o OptDefaultBootImageID - buf := uio.NewBigEndianBuffer(data) - if err := o.ID.Unmarshal(buf); err != nil { - return nil, err - } - return &o, buf.FinError() -} - -// Code returns the option code. -func (o *OptDefaultBootImageID) Code() dhcpv4.OptionCode { - return OptionDefaultBootImageID -} - -// ToBytes returns a serialized stream of bytes for this option. -func (o *OptDefaultBootImageID) ToBytes() []byte { - return uio.ToBigEndian(o.ID) -} - -// String returns a human-readable string for this option. -func (o *OptDefaultBootImageID) String() string { - return fmt.Sprintf("BSDP Default Boot Image ID -> %s", o.ID.String()) -} diff --git a/dhcpv4/bsdp/bsdp_option_default_boot_image_id_test.go b/dhcpv4/bsdp/bsdp_option_default_boot_image_id_test.go deleted file mode 100644 index a5abdaf..0000000 --- a/dhcpv4/bsdp/bsdp_option_default_boot_image_id_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package bsdp - -import ( - "testing" - - "github.com/stretchr/testify/require" - "github.com/u-root/u-root/pkg/uio" -) - -func TestOptDefaultBootImageIDInterfaceMethods(t *testing.T) { - b := BootImageID{IsInstall: true, ImageType: BootImageTypeMacOSX, Index: 1001} - o := OptDefaultBootImageID{b} - require.Equal(t, OptionDefaultBootImageID, o.Code(), "Code") - require.Equal(t, uio.ToBigEndian(b), o.ToBytes(), "ToBytes") -} - -func TestParseOptDefaultBootImageID(t *testing.T) { - b := BootImageID{IsInstall: true, ImageType: BootImageTypeMacOSX, Index: 1001} - o, err := ParseOptDefaultBootImageID(uio.ToBigEndian(b)) - require.NoError(t, err) - require.Equal(t, &OptDefaultBootImageID{b}, o) - - // Short byte stream - data := []byte{} - _, err = ParseOptDefaultBootImageID(data) - require.Error(t, err, "should get error from short byte stream") - - // Bad length - data = []byte{1, 0, 0, 0, 0} - _, err = ParseOptDefaultBootImageID(data) - require.Error(t, err, "should get error from bad length") -} - -func TestOptDefaultBootImageIDString(t *testing.T) { - b := BootImageID{IsInstall: true, ImageType: BootImageTypeMacOSX, Index: 1001} - o := OptDefaultBootImageID{b} - require.Equal(t, "BSDP Default Boot Image ID -> [1001] installable macOS image", o.String()) - - b = BootImageID{IsInstall: false, ImageType: BootImageTypeMacOS9, Index: 1001} - o = OptDefaultBootImageID{b} - require.Equal(t, "BSDP Default Boot Image ID -> [1001] uninstallable macOS 9 image", o.String()) - - b = BootImageID{IsInstall: false, ImageType: BootImageType(99), Index: 1001} - o = OptDefaultBootImageID{b} - require.Equal(t, "BSDP Default Boot Image ID -> [1001] uninstallable unknown image", o.String()) -} diff --git a/dhcpv4/bsdp/bsdp_option_generic.go b/dhcpv4/bsdp/bsdp_option_generic.go deleted file mode 100644 index e9e163f..0000000 --- a/dhcpv4/bsdp/bsdp_option_generic.go +++ /dev/null @@ -1,36 +0,0 @@ -package bsdp - -import ( - "fmt" - - "github.com/insomniacslk/dhcp/dhcpv4" -) - -// OptGeneric is an option that only contains the option code and associated -// data. Every option that does not have a specific implementation will fall -// back to this option. -type OptGeneric struct { - OptionCode dhcpv4.OptionCode - Data []byte -} - -// ParseOptGeneric parses a bytestream and creates a new OptGeneric from it, -// or an error. -func ParseOptGeneric(code dhcpv4.OptionCode, data []byte) (*OptGeneric, error) { - return &OptGeneric{OptionCode: code, Data: data}, nil -} - -// Code returns the generic option code. -func (o OptGeneric) Code() dhcpv4.OptionCode { - return o.OptionCode -} - -// ToBytes returns a serialized generic option as a slice of bytes. -func (o OptGeneric) ToBytes() []byte { - return o.Data -} - -// String returns a human-readable representation of a generic option. -func (o OptGeneric) String() string { - return fmt.Sprintf("%s -> %v", o.OptionCode, o.Data) -} diff --git a/dhcpv4/bsdp/bsdp_option_generic_test.go b/dhcpv4/bsdp/bsdp_option_generic_test.go deleted file mode 100644 index a813f95..0000000 --- a/dhcpv4/bsdp/bsdp_option_generic_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package bsdp - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestParseOptGeneric(t *testing.T) { - // Good parse - o, err := ParseOptGeneric(OptionMessageType, []byte{1}) - require.NoError(t, err) - require.Equal(t, OptionMessageType, o.Code()) - require.Equal(t, MessageTypeList, MessageType(o.Data[0])) -} - -func TestOptGenericCode(t *testing.T) { - o := OptGeneric{ - OptionCode: OptionMessageType, - Data: []byte{byte(MessageTypeList)}, - } - require.Equal(t, OptionMessageType, o.Code()) -} - -func TestOptGenericData(t *testing.T) { - o := OptGeneric{ - OptionCode: OptionServerIdentifier, - Data: []byte{192, 168, 0, 1}, - } - require.Equal(t, []byte{192, 168, 0, 1}, o.Data) -} - -func TestOptGenericToBytes(t *testing.T) { - o := OptGeneric{ - OptionCode: OptionServerIdentifier, - Data: []byte{192, 168, 0, 1}, - } - serialized := o.ToBytes() - expected := []byte{192, 168, 0, 1} - require.Equal(t, expected, serialized) -} - -func TestOptGenericString(t *testing.T) { - o := OptGeneric{ - OptionCode: OptionServerIdentifier, - Data: []byte{192, 168, 0, 1}, - } - require.Equal(t, "BSDP Server Identifier -> [192 168 0 1]", o.String()) -} - -func TestOptGenericStringUnknown(t *testing.T) { - o := OptGeneric{ - OptionCode: optionCode(102), // Returned option code. - Data: []byte{5}, - } - require.Equal(t, "unknown -> [5]", o.String()) -} diff --git a/dhcpv4/bsdp/bsdp_option_machine_name.go b/dhcpv4/bsdp/bsdp_option_machine_name.go deleted file mode 100644 index ced88b0..0000000 --- a/dhcpv4/bsdp/bsdp_option_machine_name.go +++ /dev/null @@ -1,34 +0,0 @@ -package bsdp - -import ( - "github.com/insomniacslk/dhcp/dhcpv4" -) - -// OptMachineName represents a BSDP message type. -// -// Implements the BSDP option machine name, which gives the Netboot server's -// machine name. -type OptMachineName struct { - Name string -} - -// ParseOptMachineName constructs an OptMachineName struct from a sequence of -// bytes and returns it, or an error. -func ParseOptMachineName(data []byte) (*OptMachineName, error) { - return &OptMachineName{Name: string(data)}, nil -} - -// Code returns the option code. -func (o *OptMachineName) Code() dhcpv4.OptionCode { - return OptionMachineName -} - -// ToBytes returns a serialized stream of bytes for this option. -func (o *OptMachineName) ToBytes() []byte { - return []byte(o.Name) -} - -// String returns a human-readable string for this option. -func (o *OptMachineName) String() string { - return "BSDP Machine Name -> " + o.Name -} diff --git a/dhcpv4/bsdp/bsdp_option_machine_name_test.go b/dhcpv4/bsdp/bsdp_option_machine_name_test.go deleted file mode 100644 index abc0d54..0000000 --- a/dhcpv4/bsdp/bsdp_option_machine_name_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package bsdp - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestOptMachineNameInterfaceMethods(t *testing.T) { - o := OptMachineName{"somebox"} - require.Equal(t, OptionMachineName, o.Code(), "Code") - expectedBytes := []byte{'s', 'o', 'm', 'e', 'b', 'o', 'x'} - require.Equal(t, expectedBytes, o.ToBytes(), "ToBytes") -} - -func TestParseOptMachineName(t *testing.T) { - data := []byte{'s', 'o', 'm', 'e', 'b', 'o', 'x'} - o, err := ParseOptMachineName(data) - require.NoError(t, err) - require.Equal(t, &OptMachineName{"somebox"}, o) -} - -func TestOptMachineNameString(t *testing.T) { - o := OptMachineName{"somebox"} - require.Equal(t, "BSDP Machine Name -> somebox", o.String()) -} diff --git a/dhcpv4/bsdp/bsdp_option_message_type.go b/dhcpv4/bsdp/bsdp_option_message_type.go index 5f96f12..cb0c5cf 100644 --- a/dhcpv4/bsdp/bsdp_option_message_type.go +++ b/dhcpv4/bsdp/bsdp_option_message_type.go @@ -15,16 +15,23 @@ 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 "Unknown" + return fmt.Sprintf("unknown (%d)", m) } // messageTypeToString maps each BSDP message type to a human-readable string. @@ -34,29 +41,30 @@ var messageTypeToString = map[MessageType]string{ MessageTypeFailed: "FAILED", } -// OptMessageType represents a BSDP message type. -type OptMessageType struct { - Type MessageType -} - -// ParseOptMessageType constructs an OptMessageType struct from a sequence of -// bytes and returns it, or an error. -func ParseOptMessageType(data []byte) (*OptMessageType, error) { +// FromBytes reads data into m. +func (m *MessageType) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - return &OptMessageType{Type: MessageType(buf.Read8())}, buf.FinError() + *m = MessageType(buf.Read8()) + return buf.FinError() } -// Code returns the option code. -func (o *OptMessageType) Code() dhcpv4.OptionCode { - return OptionMessageType -} - -// ToBytes returns a serialized stream of bytes for this option. -func (o *OptMessageType) ToBytes() []byte { - return []byte{byte(o.Type)} +// OptMessageType returns a new BSDP Message Type option. +func OptMessageType(mt MessageType) dhcpv4.Option { + return dhcpv4.Option{ + Code: OptionMessageType, + Value: mt, + } } -// String returns a human-readable string for this option. -func (o *OptMessageType) String() string { - return fmt.Sprintf("BSDP Message Type -> %s", o.Type.String()) +// GetMessageType returns the BSDP Message Type in o. +func GetMessageType(o dhcpv4.Options) MessageType { + v := o.Get(OptionMessageType) + if v == nil { + return MessageTypeNone + } + var m MessageType + if err := m.FromBytes(v); err != nil { + return MessageTypeNone + } + return m } diff --git a/dhcpv4/bsdp/bsdp_option_message_type_test.go b/dhcpv4/bsdp/bsdp_option_message_type_test.go index a6695cc..6666137 100644 --- a/dhcpv4/bsdp/bsdp_option_message_type_test.go +++ b/dhcpv4/bsdp/bsdp_option_message_type_test.go @@ -7,24 +7,25 @@ import ( ) func TestOptMessageTypeInterfaceMethods(t *testing.T) { - o := OptMessageType{MessageTypeList} - require.Equal(t, OptionMessageType, o.Code(), "Code") - require.Equal(t, []byte{1}, o.ToBytes(), "ToBytes") + 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 - o, err := ParseOptMessageType(data) + err := o.FromBytes(data) require.NoError(t, err) - require.Equal(t, &OptMessageType{MessageTypeList}, o) + require.Equal(t, MessageTypeList, o) } func TestOptMessageTypeString(t *testing.T) { // known - o := OptMessageType{MessageTypeList} - require.Equal(t, "BSDP Message Type -> LIST", o.String()) + o := OptMessageType(MessageTypeList) + require.Equal(t, "BSDP Message Type: LIST", o.String()) // unknown - o = OptMessageType{99} - require.Equal(t, "BSDP Message Type -> Unknown", o.String()) + 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 new file mode 100644 index 0000000..2d3a7bf --- /dev/null +++ b/dhcpv4/bsdp/bsdp_option_misc.go @@ -0,0 +1,99 @@ +package bsdp + +import ( + "fmt" + "net" + + "github.com/insomniacslk/dhcp/dhcpv4" + "github.com/u-root/u-root/pkg/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)} +} + +// GetReplyPort returns the BSDP reply port in o, if present. +func GetReplyPort(o dhcpv4.Options) (uint16, error) { + return dhcpv4.GetUint16(OptionReplyPort, o) +} + +// OptServerPriority returns a new BSDP server priority option. +func OptServerPriority(prio uint16) dhcpv4.Option { + return dhcpv4.Option{Code: OptionServerPriority, Value: dhcpv4.Uint16(prio)} +} + +// GetServerPriority returns the BSDP server priority in o if present. +func GetServerPriority(o dhcpv4.Options) (uint16, error) { + return dhcpv4.GetUint16(OptionServerPriority, o) +} + +// OptMachineName returns a BSDP Machine Name option. +func OptMachineName(name string) dhcpv4.Option { + return dhcpv4.Option{Code: OptionMachineName, Value: dhcpv4.String(name)} +} + +// GetMachineName finds and parses the BSDP Machine Name option from o. +func GetMachineName(o dhcpv4.Options) string { + return dhcpv4.GetString(OptionMachineName, o) +} + +// 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} +} + +// GetVersion returns the BSDP version in o if present. +func GetVersion(o dhcpv4.Options) (Version, error) { + v := o.Get(OptionVersion) + if v == nil { + return Version{0, 0}, fmt.Errorf("version not found") + } + var ver Version + if err := ver.FromBytes(v); err != nil { + return Version{0, 0}, err + } + return ver, nil +} + +// GetServerIdentifier returns the BSDP Server Identifier value in o. +func GetServerIdentifier(o dhcpv4.Options) net.IP { + return dhcpv4.GetIP(OptionServerIdentifier, o) +} + +// 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 new file mode 100644 index 0000000..dfa81b5 --- /dev/null +++ b/dhcpv4/bsdp/bsdp_option_misc_test.go @@ -0,0 +1,95 @@ +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 := GetReplyPort(o.Options) + require.NoError(t, err) + require.Equal(t, uint16(1234), port) + + port, err = GetReplyPort(dhcpv4.Options{}) + 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 := GetServerPriority(o.Options) + require.NoError(t, err) + require.Equal(t, uint16(1234), prio) + + prio, err = GetServerPriority(dhcpv4.Options{}) + 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", GetMachineName(o.Options)) + require.Equal(t, "", GetMachineName(dhcpv4.Options{})) +} + +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 := GetVersion(o.Options) + require.NoError(t, err) + require.Equal(t, ver, Version1_1) + + ver, err = GetVersion(dhcpv4.Options{}) + require.Error(t, err, "no version present") + + ver, err = GetVersion(dhcpv4.Options{OptionVersion.Code(): []byte{}}) + require.Error(t, err, "empty version field") + + ver, err = GetVersion(dhcpv4.Options{OptionVersion.Code(): []byte{1}}) + require.Error(t, err, "version option too short") + + ver, err = GetVersion(dhcpv4.Options{OptionVersion.Code(): []byte{1, 2, 3}}) + 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}, GetServerIdentifier(o.Options)) + require.Equal(t, net.IP(nil), GetServerIdentifier(dhcpv4.Options{})) +} diff --git a/dhcpv4/bsdp/bsdp_option_reply_port.go b/dhcpv4/bsdp/bsdp_option_reply_port.go deleted file mode 100644 index 5eea5ee..0000000 --- a/dhcpv4/bsdp/bsdp_option_reply_port.go +++ /dev/null @@ -1,42 +0,0 @@ -package bsdp - -import ( - "fmt" - - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/u-root/u-root/pkg/uio" -) - -// OptReplyPort represents a BSDP protocol version. -// -// 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. -type OptReplyPort struct { - Port uint16 -} - -// ParseOptReplyPort constructs an OptReplyPort struct from a sequence of -// bytes and returns it, or an error. -func ParseOptReplyPort(data []byte) (*OptReplyPort, error) { - buf := uio.NewBigEndianBuffer(data) - return &OptReplyPort{buf.Read16()}, buf.FinError() -} - -// Code returns the option code. -func (o *OptReplyPort) Code() dhcpv4.OptionCode { - return OptionReplyPort -} - -// ToBytes returns a serialized stream of bytes for this option. -func (o *OptReplyPort) ToBytes() []byte { - buf := uio.NewBigEndianBuffer(nil) - buf.Write16(o.Port) - return buf.Data() -} - -// String returns a human-readable string for this option. -func (o *OptReplyPort) String() string { - return fmt.Sprintf("BSDP Reply Port -> %v", o.Port) -} diff --git a/dhcpv4/bsdp/bsdp_option_reply_port_test.go b/dhcpv4/bsdp/bsdp_option_reply_port_test.go deleted file mode 100644 index de94ffb..0000000 --- a/dhcpv4/bsdp/bsdp_option_reply_port_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package bsdp - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestOptReplyPortInterfaceMethods(t *testing.T) { - o := OptReplyPort{1234} - require.Equal(t, OptionReplyPort, o.Code(), "Code") - require.Equal(t, []byte{4, 210}, o.ToBytes(), "ToBytes") -} - -func TestParseOptReplyPort(t *testing.T) { - data := []byte{0, 1} - o, err := ParseOptReplyPort(data) - require.NoError(t, err) - require.Equal(t, &OptReplyPort{1}, o) - - // Short byte stream - data = []byte{} - _, err = ParseOptReplyPort(data) - require.Error(t, err, "should get error from short byte stream") - - // Bad length - data = []byte{1} - _, err = ParseOptReplyPort(data) - require.Error(t, err, "should get error from bad length") -} - -func TestOptReplyPortString(t *testing.T) { - // known - o := OptReplyPort{1234} - require.Equal(t, "BSDP Reply Port -> 1234", o.String()) -} diff --git a/dhcpv4/bsdp/bsdp_option_selected_boot_image_id.go b/dhcpv4/bsdp/bsdp_option_selected_boot_image_id.go deleted file mode 100644 index 67f99a8..0000000 --- a/dhcpv4/bsdp/bsdp_option_selected_boot_image_id.go +++ /dev/null @@ -1,42 +0,0 @@ -package bsdp - -import ( - "fmt" - - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/u-root/u-root/pkg/uio" -) - -// OptSelectedBootImageID contains the selected boot image ID. -// -// Implements the BSDP option selected boot image ID, which tells the server -// which boot image has been selected by the client. -type OptSelectedBootImageID struct { - ID BootImageID -} - -// ParseOptSelectedBootImageID constructs an OptSelectedBootImageID struct from a sequence of -// bytes and returns it, or an error. -func ParseOptSelectedBootImageID(data []byte) (*OptSelectedBootImageID, error) { - var o OptSelectedBootImageID - buf := uio.NewBigEndianBuffer(data) - if err := o.ID.Unmarshal(buf); err != nil { - return nil, err - } - return &o, buf.FinError() -} - -// Code returns the option code. -func (o *OptSelectedBootImageID) Code() dhcpv4.OptionCode { - return OptionSelectedBootImageID -} - -// ToBytes returns a serialized stream of bytes for this option. -func (o *OptSelectedBootImageID) ToBytes() []byte { - return uio.ToBigEndian(o.ID) -} - -// String returns a human-readable string for this option. -func (o *OptSelectedBootImageID) String() string { - return fmt.Sprintf("BSDP Selected Boot Image ID -> %s", o.ID.String()) -} diff --git a/dhcpv4/bsdp/bsdp_option_selected_boot_image_id_test.go b/dhcpv4/bsdp/bsdp_option_selected_boot_image_id_test.go deleted file mode 100644 index e187fc7..0000000 --- a/dhcpv4/bsdp/bsdp_option_selected_boot_image_id_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package bsdp - -import ( - "testing" - - "github.com/stretchr/testify/require" - "github.com/u-root/u-root/pkg/uio" -) - -func TestOptSelectedBootImageIDInterfaceMethods(t *testing.T) { - b := BootImageID{IsInstall: true, ImageType: BootImageTypeMacOSX, Index: 1001} - o := OptSelectedBootImageID{b} - require.Equal(t, OptionSelectedBootImageID, o.Code(), "Code") - require.Equal(t, uio.ToBigEndian(b), o.ToBytes(), "ToBytes") -} - -func TestParseOptSelectedBootImageID(t *testing.T) { - b := BootImageID{IsInstall: true, ImageType: BootImageTypeMacOSX, Index: 1001} - o, err := ParseOptSelectedBootImageID(uio.ToBigEndian(b)) - require.NoError(t, err) - require.Equal(t, &OptSelectedBootImageID{b}, o) - - // Short byte stream - data := []byte{} - _, err = ParseOptSelectedBootImageID(data) - require.Error(t, err, "should get error from short byte stream") - - // Bad length - data = []byte{1, 0, 0, 0, 0} - _, err = ParseOptSelectedBootImageID(data) - require.Error(t, err, "should get error from bad length") -} - -func TestOptSelectedBootImageIDString(t *testing.T) { - b := BootImageID{IsInstall: true, ImageType: BootImageTypeMacOSX, Index: 1001} - o := OptSelectedBootImageID{b} - require.Equal(t, "BSDP Selected Boot Image ID -> [1001] installable macOS image", o.String()) - - b = BootImageID{IsInstall: false, ImageType: BootImageTypeMacOS9, Index: 1001} - o = OptSelectedBootImageID{b} - require.Equal(t, "BSDP Selected Boot Image ID -> [1001] uninstallable macOS 9 image", o.String()) - - b = BootImageID{IsInstall: false, ImageType: BootImageType(99), Index: 1001} - o = OptSelectedBootImageID{b} - require.Equal(t, "BSDP Selected Boot Image ID -> [1001] uninstallable unknown image", o.String()) -} diff --git a/dhcpv4/bsdp/bsdp_option_server_identifier.go b/dhcpv4/bsdp/bsdp_option_server_identifier.go deleted file mode 100644 index d1f5b6c..0000000 --- a/dhcpv4/bsdp/bsdp_option_server_identifier.go +++ /dev/null @@ -1,36 +0,0 @@ -package bsdp - -import ( - "fmt" - "net" - - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/u-root/u-root/pkg/uio" -) - -// OptServerIdentifier implements the BSDP server identifier option. -type OptServerIdentifier struct { - ServerID net.IP -} - -// ParseOptServerIdentifier returns a new OptServerIdentifier from a byte -// stream, or error if any. -func ParseOptServerIdentifier(data []byte) (*OptServerIdentifier, error) { - buf := uio.NewBigEndianBuffer(data) - return &OptServerIdentifier{ServerID: net.IP(buf.CopyN(net.IPv4len))}, buf.FinError() -} - -// Code returns the option code. -func (o *OptServerIdentifier) Code() dhcpv4.OptionCode { - return OptionServerIdentifier -} - -// ToBytes returns a serialized stream of bytes for this option. -func (o *OptServerIdentifier) ToBytes() []byte { - return o.ServerID.To4() -} - -// String returns a human-readable string. -func (o *OptServerIdentifier) String() string { - return fmt.Sprintf("BSDP Server Identifier -> %v", o.ServerID.String()) -} diff --git a/dhcpv4/bsdp/bsdp_option_server_identifier_test.go b/dhcpv4/bsdp/bsdp_option_server_identifier_test.go deleted file mode 100644 index 5a77644..0000000 --- a/dhcpv4/bsdp/bsdp_option_server_identifier_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package bsdp - -import ( - "net" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestOptServerIdentifierInterfaceMethods(t *testing.T) { - ip := net.IP{192, 168, 0, 1} - o := OptServerIdentifier{ServerID: ip} - require.Equal(t, OptionServerIdentifier, o.Code(), "Code") - expectedBytes := []byte{192, 168, 0, 1} - require.Equal(t, expectedBytes, o.ToBytes(), "ToBytes") - require.Equal(t, "BSDP Server Identifier -> 192.168.0.1", o.String(), "String") -} - -func TestParseOptServerIdentifier(t *testing.T) { - var ( - o *OptServerIdentifier - err error - ) - o, err = ParseOptServerIdentifier([]byte{}) - require.Error(t, err, "empty byte stream") - - o, err = ParseOptServerIdentifier([]byte{3, 4, 192}) - require.Error(t, err, "wrong IP length") - - o, err = ParseOptServerIdentifier([]byte{192, 168, 0, 1}) - require.NoError(t, err) - require.Equal(t, net.IP{192, 168, 0, 1}, o.ServerID) -} diff --git a/dhcpv4/bsdp/bsdp_option_server_priority.go b/dhcpv4/bsdp/bsdp_option_server_priority.go deleted file mode 100644 index f6fcf57..0000000 --- a/dhcpv4/bsdp/bsdp_option_server_priority.go +++ /dev/null @@ -1,37 +0,0 @@ -package bsdp - -import ( - "fmt" - - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/u-root/u-root/pkg/uio" -) - -// OptServerPriority represents an option encapsulating the server priority. -type OptServerPriority struct { - Priority uint16 -} - -// ParseOptServerPriority returns a new OptServerPriority from a byte stream, or -// error if any. -func ParseOptServerPriority(data []byte) (*OptServerPriority, error) { - buf := uio.NewBigEndianBuffer(data) - return &OptServerPriority{Priority: buf.Read16()}, buf.FinError() -} - -// Code returns the option code. -func (o *OptServerPriority) Code() dhcpv4.OptionCode { - return OptionServerPriority -} - -// ToBytes returns a serialized stream of bytes for this option. -func (o *OptServerPriority) ToBytes() []byte { - buf := uio.NewBigEndianBuffer(nil) - buf.Write16(o.Priority) - return buf.Data() -} - -// String returns a human-readable string. -func (o *OptServerPriority) String() string { - return fmt.Sprintf("BSDP Server Priority -> %v", o.Priority) -} diff --git a/dhcpv4/bsdp/bsdp_option_server_priority_test.go b/dhcpv4/bsdp/bsdp_option_server_priority_test.go deleted file mode 100644 index c4c96de..0000000 --- a/dhcpv4/bsdp/bsdp_option_server_priority_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package bsdp - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestOptServerPriorityInterfaceMethods(t *testing.T) { - o := OptServerPriority{Priority: 100} - require.Equal(t, OptionServerPriority, o.Code(), "Code") - require.Equal(t, []byte{0, 100}, o.ToBytes(), "ToBytes") - require.Equal(t, "BSDP Server Priority -> 100", o.String(), "String") -} - -func TestParseOptServerPriority(t *testing.T) { - var ( - o *OptServerPriority - err error - ) - o, err = ParseOptServerPriority([]byte{}) - require.Error(t, err, "empty byte stream") - - o, err = ParseOptServerPriority([]byte{1}) - require.Error(t, err, "short byte stream") - - o, err = ParseOptServerPriority([]byte{0, 100}) - require.NoError(t, err) - require.Equal(t, uint16(100), o.Priority) -} diff --git a/dhcpv4/bsdp/bsdp_option_version.go b/dhcpv4/bsdp/bsdp_option_version.go deleted file mode 100644 index d6b78c8..0000000 --- a/dhcpv4/bsdp/bsdp_option_version.go +++ /dev/null @@ -1,41 +0,0 @@ -package bsdp - -import ( - "fmt" - - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/u-root/u-root/pkg/uio" -) - -// 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} -) - -// ParseOptVersion constructs an OptVersion struct from a sequence of -// bytes and returns it, or an error. -func ParseOptVersion(data []byte) (Version, error) { - buf := uio.NewBigEndianBuffer(data) - var v Version - buf.ReadBytes(v[:]) - return v, buf.FinError() -} - -// Code returns the option code. -func (o Version) Code() dhcpv4.OptionCode { - return OptionVersion -} - -// 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("BSDP Version -> %d.%d", o[0], o[1]) -} diff --git a/dhcpv4/bsdp/bsdp_option_version_test.go b/dhcpv4/bsdp/bsdp_option_version_test.go deleted file mode 100644 index 69d4c86..0000000 --- a/dhcpv4/bsdp/bsdp_option_version_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package bsdp - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestOptVersionInterfaceMethods(t *testing.T) { - o := Version1_1 - require.Equal(t, OptionVersion, o.Code(), "Code") - require.Equal(t, []byte{1, 1}, o.ToBytes(), "ToBytes") -} - -func TestParseOptVersion(t *testing.T) { - data := []byte{1, 1} - o, err := ParseOptVersion(data) - require.NoError(t, err) - require.Equal(t, Version1_1, o) - - // Short byte stream - data = []byte{2} - _, err = ParseOptVersion(data) - require.Error(t, err, "should get error from short byte stream") -} - -func TestOptVersionString(t *testing.T) { - require.Equal(t, "BSDP Version -> 1.1", Version1_1.String()) -} diff --git a/dhcpv4/bsdp/bsdp_test.go b/dhcpv4/bsdp/bsdp_test.go index 638a408..e0378c2 100644 --- a/dhcpv4/bsdp/bsdp_test.go +++ b/dhcpv4/bsdp/bsdp_test.go @@ -12,9 +12,9 @@ import ( 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.GetOne(opt.Code()) - require.Equal(t, opt, actual) + require.True(t, opts.Has(opt.Code)) + actual := opts.Get(opt.Code) + require.Equal(t, opt.Value.ToBytes(), actual) } func TestParseBootImageListFromAck(t *testing.T) { @@ -37,11 +37,11 @@ func TestParseBootImageListFromAck(t *testing.T) { }, } ack, _ := dhcpv4.New() - ack.UpdateOption(&OptVendorSpecificInformation{ - []dhcpv4.Option{&OptBootImageList{expectedBootImages}}, - }) + ack.UpdateOption(OptVendorOptions( + OptBootImageList(expectedBootImages...), + )) - images, err := ParseBootImageListFromAck(*ack) + images, err := ParseBootImageListFromAck(ack) require.NoError(t, err) require.NotEmpty(t, images, "should get BootImages") require.Equal(t, expectedBootImages, images, "should get same BootImages") @@ -49,7 +49,7 @@ func TestParseBootImageListFromAck(t *testing.T) { func TestParseBootImageListFromAckNoVendorOption(t *testing.T) { ack, _ := dhcpv4.New() - images, err := ParseBootImageListFromAck(*ack) + images, err := ParseBootImageListFromAck(ack) require.Error(t, err) require.Empty(t, images, "no BootImages") } @@ -70,14 +70,13 @@ func TestNewInformList_NoReplyPort(t *testing.T) { require.True(t, m.Options.Has(dhcpv4.OptionParameterRequestList)) require.True(t, m.Options.Has(dhcpv4.OptionMaximumDHCPMessageSize)) - opt := m.GetOneOption(dhcpv4.OptionVendorSpecificInformation) - require.NotNil(t, opt, "vendor opts not present") - vendorInfo := opt.(*OptVendorSpecificInformation) - require.True(t, vendorInfo.Options.Has(OptionMessageType)) - require.True(t, vendorInfo.Options.Has(OptionVersion)) + vendorOpts := GetVendorOptions(m.Options) + require.NotNil(t, vendorOpts, "vendor opts not present") + require.True(t, vendorOpts.Has(OptionMessageType)) + require.True(t, vendorOpts.Has(OptionVersion)) - opt = vendorInfo.GetOneOption(OptionMessageType) - require.Equal(t, MessageTypeList, opt.(*OptMessageType).Type) + mt := GetMessageType(vendorOpts.Options) + require.Equal(t, MessageTypeList, mt) } func TestNewInformList_ReplyPort(t *testing.T) { @@ -94,12 +93,12 @@ func TestNewInformList_ReplyPort(t *testing.T) { m, err := NewInformList(hwAddr, localIP, replyPort) require.NoError(t, err) - opt := m.GetOneOption(dhcpv4.OptionVendorSpecificInformation) - vendorInfo := opt.(*OptVendorSpecificInformation) - require.True(t, vendorInfo.Options.Has(OptionReplyPort)) + vendorOpts := GetVendorOptions(m.Options) + require.True(t, vendorOpts.Options.Has(OptionReplyPort)) - opt = vendorInfo.GetOneOption(OptionReplyPort) - require.Equal(t, replyPort, opt.(*OptReplyPort).Port) + port, err := GetReplyPort(vendorOpts.Options) + require.NoError(t, err) + require.Equal(t, replyPort, port) } func newAck(hwAddr net.HardwareAddr, transactionID [4]byte) *dhcpv4.DHCPv4 { @@ -108,7 +107,7 @@ func newAck(hwAddr net.HardwareAddr, transactionID [4]byte) *dhcpv4.DHCPv4 { ack.TransactionID = transactionID ack.HWType = iana.HWTypeEthernet ack.ClientHWAddr = hwAddr - ack.UpdateOption(&dhcpv4.OptMessageType{MessageType: dhcpv4.MessageTypeAck}) + ack.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck)) return ack } @@ -126,9 +125,9 @@ func TestInformSelectForAck_Broadcast(t *testing.T) { } ack := newAck(hwAddr, tid) ack.SetBroadcast() - ack.UpdateOption(&dhcpv4.OptServerIdentifier{ServerID: serverID}) + ack.UpdateOption(dhcpv4.OptServerIdentifier(serverID)) - m, err := InformSelectForAck(*ack, 0, bootImage) + 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) @@ -140,17 +139,16 @@ func TestInformSelectForAck_Broadcast(t *testing.T) { require.True(t, m.Options.Has(dhcpv4.OptionClassIdentifier)) require.True(t, m.Options.Has(dhcpv4.OptionParameterRequestList)) require.True(t, m.Options.Has(dhcpv4.OptionDHCPMessageType)) - opt := m.GetOneOption(dhcpv4.OptionDHCPMessageType) - require.Equal(t, dhcpv4.MessageTypeInform, opt.(*dhcpv4.OptMessageType).MessageType) + mt := dhcpv4.GetMessageType(m.Options) + require.Equal(t, dhcpv4.MessageTypeInform, mt) // Validate vendor opts. require.True(t, m.Options.Has(dhcpv4.OptionVendorSpecificInformation)) - opt = m.GetOneOption(dhcpv4.OptionVendorSpecificInformation) - vendorInfo := opt.(*OptVendorSpecificInformation) - RequireHasOption(t, vendorInfo.Options, &OptMessageType{Type: MessageTypeSelect}) - require.True(t, vendorInfo.Options.Has(OptionVersion)) - RequireHasOption(t, vendorInfo.Options, &OptSelectedBootImageID{ID: bootImage.ID}) - RequireHasOption(t, vendorInfo.Options, &OptServerIdentifier{ServerID: serverID}) + 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) { @@ -166,7 +164,7 @@ func TestInformSelectForAck_NoServerID(t *testing.T) { } ack := newAck(hwAddr, tid) - _, err := InformSelectForAck(*ack, 0, bootImage) + _, err := InformSelectForAck(PacketFor(ack), 0, bootImage) require.Error(t, err, "expect error for no server identifier option") } @@ -184,9 +182,9 @@ func TestInformSelectForAck_BadReplyPort(t *testing.T) { } ack := newAck(hwAddr, tid) ack.SetBroadcast() - ack.UpdateOption(&dhcpv4.OptServerIdentifier{ServerID: serverID}) + ack.UpdateOption(dhcpv4.OptServerIdentifier(serverID)) - _, err := InformSelectForAck(*ack, 11223, bootImage) + _, err := InformSelectForAck(PacketFor(ack), 11223, bootImage) require.Error(t, err, "expect error for > 1024 replyPort") } @@ -204,16 +202,15 @@ func TestInformSelectForAck_ReplyPort(t *testing.T) { } ack := newAck(hwAddr, tid) ack.SetBroadcast() - ack.UpdateOption(&dhcpv4.OptServerIdentifier{ServerID: serverID}) + ack.UpdateOption(dhcpv4.OptServerIdentifier(serverID)) replyPort := uint16(999) - m, err := InformSelectForAck(*ack, replyPort, bootImage) + m, err := InformSelectForAck(PacketFor(ack), replyPort, bootImage) require.NoError(t, err) require.True(t, m.Options.Has(dhcpv4.OptionVendorSpecificInformation)) - opt := m.GetOneOption(dhcpv4.OptionVendorSpecificInformation) - vendorInfo := opt.(*OptVendorSpecificInformation) - RequireHasOption(t, vendorInfo.Options, &OptReplyPort{Port: replyPort}) + vendorOpts := GetVendorOptions(m.Options).Options + RequireHasOption(t, vendorOpts, OptReplyPort(replyPort)) } func TestNewReplyForInformList_NoDefaultImage(t *testing.T) { @@ -274,24 +271,24 @@ func TestNewReplyForInformList(t *testing.T) { require.Equal(t, "bsdp.foo.com", ack.ServerHostName) // Validate options. - RequireHasOption(t, ack.Options, &dhcpv4.OptMessageType{MessageType: dhcpv4.MessageTypeAck}) - RequireHasOption(t, ack.Options, &dhcpv4.OptServerIdentifier{ServerID: net.IP{9, 9, 9, 9}}) - RequireHasOption(t, ack.Options, &dhcpv4.OptClassIdentifier{Identifier: AppleVendorID}) + 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 := ack.GetOneOption(dhcpv4.OptionVendorSpecificInformation).(*OptVendorSpecificInformation) - RequireHasOption(t, vendorOpts.Options, &OptMessageType{Type: MessageTypeList}) - RequireHasOption(t, vendorOpts.Options, &OptDefaultBootImageID{ID: images[0].ID}) - RequireHasOption(t, vendorOpts.Options, &OptServerPriority{Priority: 0x7070}) - RequireHasOption(t, vendorOpts.Options, &OptBootImageList{Images: images}) + 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 = ack.GetOneOption(dhcpv4.OptionVendorSpecificInformation).(*OptVendorSpecificInformation) - RequireHasOption(t, vendorOpts.Options, &OptSelectedBootImageID{ID: images[0].ID}) + vendorOpts = GetVendorOptions(ack.Options).Options + RequireHasOption(t, vendorOpts, OptSelectedBootImageID(images[0].ID)) } func TestNewReplyForInformSelect_NoSelectedImage(t *testing.T) { @@ -352,30 +349,22 @@ func TestNewReplyForInformSelect(t *testing.T) { require.Equal(t, "bsdp.foo.com", ack.ServerHostName) // Validate options. - RequireHasOption(t, ack.Options, &dhcpv4.OptMessageType{MessageType: dhcpv4.MessageTypeAck}) - RequireHasOption(t, ack.Options, &dhcpv4.OptServerIdentifier{ServerID: net.IP{9, 9, 9, 9}}) - RequireHasOption(t, ack.Options, &dhcpv4.OptServerIdentifier{ServerID: net.IP{9, 9, 9, 9}}) - RequireHasOption(t, ack.Options, &dhcpv4.OptClassIdentifier{Identifier: AppleVendorID}) + 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 := ack.GetOneOption(dhcpv4.OptionVendorSpecificInformation).(*OptVendorSpecificInformation) - RequireHasOption(t, vendorOpts.Options, &OptMessageType{Type: MessageTypeSelect}) - RequireHasOption(t, vendorOpts.Options, &OptSelectedBootImageID{ID: images[0].ID}) + vendorOpts := GetVendorOptions(ack.Options) + RequireHasOption(t, vendorOpts.Options, OptMessageType(MessageTypeSelect)) + RequireHasOption(t, vendorOpts.Options, OptSelectedBootImageID(images[0].ID)) } func TestMessageTypeForPacket(t *testing.T) { - var ( - pkt *dhcpv4.DHCPv4 - gotMessageType *MessageType - ) - - list := new(MessageType) - *list = MessageTypeList - testcases := []struct { tcName string opts []dhcpv4.Option - wantMessageType *MessageType + wantMessageType MessageType }{ { tcName: "No options", @@ -384,45 +373,38 @@ func TestMessageTypeForPacket(t *testing.T) { { tcName: "Some options, no vendor opts", opts: []dhcpv4.Option{ - &dhcpv4.OptHostName{HostName: "foobar1234"}, + dhcpv4.OptHostName("foobar1234"), }, }, { tcName: "Vendor opts, no message type", opts: []dhcpv4.Option{ - &dhcpv4.OptHostName{HostName: "foobar1234"}, - &OptVendorSpecificInformation{ - Options: []dhcpv4.Option{ - Version1_1, - }, - }, + dhcpv4.OptHostName("foobar1234"), + OptVendorOptions( + OptVersion(Version1_1), + ), }, }, { tcName: "Vendor opts, with message type", opts: []dhcpv4.Option{ - &dhcpv4.OptHostName{HostName: "foobar1234"}, - &OptVendorSpecificInformation{ - Options: []dhcpv4.Option{ - Version1_1, - &OptMessageType{Type: MessageTypeList}, - }, - }, + dhcpv4.OptHostName("foobar1234"), + OptVendorOptions( + OptVersion(Version1_1), + OptMessageType(MessageTypeList), + ), }, - wantMessageType: list, + wantMessageType: MessageTypeList, }, } for _, tt := range testcases { t.Run(tt.tcName, func(t *testing.T) { - pkt, _ = dhcpv4.New() + pkt, _ := dhcpv4.New() for _, opt := range tt.opts { pkt.UpdateOption(opt) } - gotMessageType = MessageTypeFromPacket(pkt) + gotMessageType := MessageTypeFromPacket(pkt) require.Equal(t, tt.wantMessageType, gotMessageType) - if tt.wantMessageType != nil { - require.Equal(t, *tt.wantMessageType, *gotMessageType) - } }) } } diff --git a/dhcpv4/bsdp/client.go b/dhcpv4/bsdp/client.go index dd4a0a0..e8ca2ca 100644 --- a/dhcpv4/bsdp/client.go +++ b/dhcpv4/bsdp/client.go @@ -18,24 +18,10 @@ func NewClient() *Client { return &Client{Client: dhcpv4.Client{}} } -func castVendorOpt(ack *dhcpv4.DHCPv4) { - opts := ack.Options - for i := 0; i < len(opts); i++ { - if opts[i].Code() == dhcpv4.OptionVendorSpecificInformation { - vendorOpt, err := ParseOptVendorSpecificInformation(opts[i].ToBytes()) - // Oh well, we tried - if err != nil { - return - } - opts[i] = vendorOpt - } - } -} - // Exchange runs a full BSDP exchange (Inform[list], Ack, Inform[select], // Ack). Returns a list of DHCPv4 structures representing the exchange. -func (c *Client) Exchange(ifname string) ([]*dhcpv4.DHCPv4, error) { - conversation := make([]*dhcpv4.DHCPv4, 0) +func (c *Client) Exchange(ifname string) ([]*Packet, error) { + conversation := make([]*Packet, 0) // Get our file descriptor for the broadcast socket. sendFd, err := dhcpv4.MakeBroadcastSocket(ifname) @@ -55,17 +41,16 @@ func (c *Client) Exchange(ifname string) ([]*dhcpv4.DHCPv4, error) { conversation = append(conversation, informList) // ACK[LIST] - ackForList, err := c.Client.SendReceive(sendFd, recvFd, informList, dhcpv4.MessageTypeAck) + ackForList, err := c.Client.SendReceive(sendFd, recvFd, informList.v4(), dhcpv4.MessageTypeAck) if err != nil { return conversation, err } // Rewrite vendor-specific option for pretty printing. - castVendorOpt(ackForList) - conversation = append(conversation, ackForList) + conversation = append(conversation, PacketFor(ackForList)) // Parse boot images sent back by server - bootImages, err := ParseBootImageListFromAck(*ackForList) + bootImages, err := ParseBootImageListFromAck(ackForList) if err != nil { return conversation, err } @@ -74,17 +59,16 @@ func (c *Client) Exchange(ifname string) ([]*dhcpv4.DHCPv4, error) { } // INFORM[SELECT] - informSelect, err := InformSelectForAck(*ackForList, dhcpv4.ClientPort, bootImages[0]) + 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, dhcpv4.MessageTypeAck) - castVendorOpt(ackForSelect) + ackForSelect, err := c.Client.SendReceive(sendFd, recvFd, informSelect.v4(), dhcpv4.MessageTypeAck) if err != nil { return conversation, err } - return append(conversation, ackForSelect), nil + return append(conversation, PacketFor(ackForSelect)), nil } diff --git a/dhcpv4/bsdp/option_vendor_specific_information.go b/dhcpv4/bsdp/option_vendor_specific_information.go index a87135f..4e107e1 100644 --- a/dhcpv4/bsdp/option_vendor_specific_information.go +++ b/dhcpv4/bsdp/option_vendor_specific_information.go @@ -1,93 +1,87 @@ package bsdp import ( - "strings" + "fmt" "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/u-root/u-root/pkg/uio" ) -// OptVendorSpecificInformation encapsulates the BSDP-specific options used for -// the protocol. -type OptVendorSpecificInformation struct { - Options dhcpv4.Options +// VendorOptions is like dhcpv4.Options, but stringifies using BSDP-specific +// option codes. +type VendorOptions struct { + dhcpv4.Options } -// parseOption is similar to dhcpv4.ParseOption, except that it switches based -// on the BSDP specific options. -func parseOption(code dhcpv4.OptionCode, data []byte) (dhcpv4.Option, error) { - var ( - opt dhcpv4.Option - err error - ) - switch code { - case OptionBootImageList: - opt, err = ParseOptBootImageList(data) - case OptionDefaultBootImageID: - opt, err = ParseOptDefaultBootImageID(data) - case OptionMachineName: - opt, err = ParseOptMachineName(data) - case OptionMessageType: - opt, err = ParseOptMessageType(data) - case OptionReplyPort: - opt, err = ParseOptReplyPort(data) - case OptionSelectedBootImageID: - opt, err = ParseOptSelectedBootImageID(data) - case OptionServerIdentifier: - opt, err = ParseOptServerIdentifier(data) - case OptionServerPriority: - opt, err = ParseOptServerPriority(data) - case OptionVersion: - opt, err = ParseOptVersion(data) - default: - opt, err = ParseOptGeneric(code, data) - } - if err != nil { - return nil, err - } - return opt, nil +// String prints the contained options using BSDP-specific option code parsing. +func (v VendorOptions) String() string { + return v.Options.ToString(bsdpHumanizer) } -// codeGetter is a dhcpv4.OptionCodeGetter for BSDP optionCodes. -func codeGetter(c uint8) dhcpv4.OptionCode { - return optionCode(c) +// FromBytes parses vendor options from +func (v *VendorOptions) FromBytes(data []byte) error { + v.Options = make(dhcpv4.Options) + return v.Options.FromBytes(data) } -// ParseOptVendorSpecificInformation constructs an OptVendorSpecificInformation struct from a sequence of -// bytes and returns it, or an error. -func ParseOptVendorSpecificInformation(data []byte) (*OptVendorSpecificInformation, error) { - options, err := dhcpv4.OptionsFromBytesWithParser(data, codeGetter, parseOption, false /* don't check for OptionEnd tag */) - if err != nil { - return nil, err +// 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...)}, } - return &OptVendorSpecificInformation{options}, nil } -// Code returns the option code. -func (o *OptVendorSpecificInformation) Code() dhcpv4.OptionCode { - return dhcpv4.OptionVendorSpecificInformation +// 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 } -// ToBytes returns a serialized stream of bytes for this option. -func (o *OptVendorSpecificInformation) ToBytes() []byte { - return uio.ToBigEndian(o.Options) +var bsdpHumanizer = dhcpv4.OptionHumanizer{ + ValueHumanizer: parseOption, + CodeHumanizer: func(c uint8) dhcpv4.OptionCode { + return optionCode(c) + }, } -// String returns a human-readable string for this option. -func (o *OptVendorSpecificInformation) String() string { - s := "Vendor Specific Information ->" - for _, opt := range o.Options { - optString := opt.String() - // If this option has sub-structures, offset them accordingly. - if strings.Contains(optString, "\n") { - optString = strings.Replace(optString, "\n ", "\n ", -1) - } - s += "\n " + optString - } - return s -} +// 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{} -// GetOneOption returns the first suboption that matches the OptionCode code. -func (o *OptVendorSpecificInformation) GetOneOption(code dhcpv4.OptionCode) dhcpv4.Option { - return o.Options.GetOne(code) + 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} } diff --git a/dhcpv4/bsdp/option_vendor_specific_information_test.go b/dhcpv4/bsdp/option_vendor_specific_information_test.go index ede8a0b..a6727f5 100644 --- a/dhcpv4/bsdp/option_vendor_specific_information_test.go +++ b/dhcpv4/bsdp/option_vendor_specific_information_test.go @@ -1,6 +1,7 @@ package bsdp import ( + "net" "testing" "github.com/insomniacslk/dhcp/dhcpv4" @@ -8,182 +9,71 @@ import ( ) func TestOptVendorSpecificInformationInterfaceMethods(t *testing.T) { - messageTypeOpt := &OptMessageType{MessageTypeList} - versionOpt := Version1_1 - o := &OptVendorSpecificInformation{[]dhcpv4.Option{messageTypeOpt, versionOpt}} - require.Equal(t, dhcpv4.OptionVendorSpecificInformation, o.Code(), "Code") - - expectedBytes := []byte{ - 1, 1, 1, // List option - 2, 2, 1, 1, // Version option - } - o = &OptVendorSpecificInformation{ - []dhcpv4.Option{ - &OptMessageType{MessageTypeList}, - Version1_1, - }, - } - require.Equal(t, expectedBytes, o.ToBytes(), "ToBytes") -} - -func TestParseOptVendorSpecificInformation(t *testing.T) { - var ( - o *OptVendorSpecificInformation - err error + o := OptVendorOptions( + OptVersion(Version1_1), + OptMessageType(MessageTypeList), ) - o, err = ParseOptVendorSpecificInformation([]byte{1, 2}) - require.Error(t, err, "short byte stream") - - // Good byte stream - data := []byte{ - 1, 1, 1, // List option - 2, 2, 1, 1, // Version option - } - o, err = ParseOptVendorSpecificInformation(data) - require.NoError(t, err) - expected := &OptVendorSpecificInformation{ - []dhcpv4.Option{ - &OptMessageType{MessageTypeList}, - Version1_1, - }, - } - require.Equal(t, 2, len(o.Options), "number of parsed suboptions") - typ := o.GetOneOption(OptionMessageType) - version := o.GetOneOption(OptionVersion) - require.Equal(t, expected.Options[0].Code(), typ.Code()) - require.Equal(t, expected.Options[1].Code(), version.Code()) - - // Short byte stream (length and data mismatch) - data = []byte{ - 1, 1, 1, // List option - 2, 2, 1, // Version option - } - o, err = ParseOptVendorSpecificInformation(data) - require.Error(t, err) - - // Bad option - data = []byte{ - 1, 1, 1, // List option - 2, 2, 1, // Version option - 5, 3, 1, 1, 1, // Reply port option - } - o, err = ParseOptVendorSpecificInformation(data) - require.Error(t, err) + require.Equal(t, dhcpv4.OptionVendorSpecificInformation, o.Code, "Code") - // Boot images + default. - data = []byte{ + expectedBytes := []byte{ 1, 1, 1, // List option 2, 2, 1, 1, // Version option - 5, 2, 1, 1, // Reply port option - - // Boot image list - 9, 22, - 0x1, 0x0, 0x03, 0xe9, // ID - 6, // name length - 'b', 's', 'd', 'p', '-', '1', - 0x80, 0x0, 0x23, 0x31, // ID - 6, // name length - 'b', 's', 'd', 'p', '-', '2', - - // Default Boot Image ID - 7, 4, 0x1, 0x0, 0x03, 0xe9, } - o, err = ParseOptVendorSpecificInformation(data) - require.NoError(t, err) - require.Equal(t, 5, len(o.Options)) - for _, opt := range []dhcpv4.OptionCode{ - OptionMessageType, - OptionVersion, - OptionReplyPort, - OptionBootImageList, - OptionDefaultBootImageID, - } { - require.True(t, o.Options.Has(opt)) - } - optBootImage := o.GetOneOption(OptionBootImageList).(*OptBootImageList) - expectedBootImages := []BootImage{ - BootImage{ - ID: BootImageID{ - IsInstall: false, - ImageType: BootImageTypeMacOSX, - Index: 1001, - }, - Name: "bsdp-1", - }, - BootImage{ - ID: BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOS9, - Index: 9009, - }, - Name: "bsdp-2", - }, - } - require.Equal(t, expectedBootImages, optBootImage.Images) + require.Equal(t, expectedBytes, o.Value.ToBytes(), "ToBytes") } func TestOptVendorSpecificInformationString(t *testing.T) { - o := &OptVendorSpecificInformation{ - []dhcpv4.Option{ - &OptMessageType{MessageTypeList}, - Version1_1, - }, - } - expectedString := "Vendor Specific Information ->\n BSDP Message Type -> LIST\n BSDP Version -> 1.1" + 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 = &OptVendorSpecificInformation{ - []dhcpv4.Option{ - &OptMessageType{MessageTypeList}, - &OptBootImageList{ - []BootImage{ - BootImage{ - ID: BootImageID{ - IsInstall: false, - ImageType: BootImageTypeMacOSX, - Index: 1001, - }, - Name: "bsdp-1", - }, - BootImage{ - ID: BootImageID{ - IsInstall: true, - ImageType: BootImageTypeMacOS9, - Index: 9009, - }, - Name: "bsdp-2", - }, + o = OptVendorOptions( + OptMessageType(MessageTypeList), + OptBootImageList( + BootImage{ + ID: BootImageID{ + IsInstall: false, + ImageType: BootImageTypeMacOSX, + Index: 1001, }, + Name: "bsdp-1", }, - }, - } - expectedString = "Vendor Specific Information ->\n" + - " BSDP Message Type -> LIST\n" + - " BSDP Boot Image List ->\n" + - " bsdp-1 [1001] uninstallable macOS image\n" + - " bsdp-2 [9009] installable macOS 9 image" + 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()) } - -func TestOptVendorSpecificInformationGetOneOption(t *testing.T) { - // No option - o := &OptVendorSpecificInformation{ - []dhcpv4.Option{ - &OptMessageType{MessageTypeList}, - Version1_1, - }, - } - foundOpt := o.GetOneOption(OptionBootImageList) - require.Nil(t, foundOpt, "should not get options") - - // One option - o = &OptVendorSpecificInformation{ - []dhcpv4.Option{ - &OptMessageType{MessageTypeList}, - Version1_1, - }, - } - foundOpt = o.GetOneOption(OptionMessageType) - require.Equal(t, MessageTypeList, foundOpt.(*OptMessageType).Type) -} diff --git a/dhcpv4/bsdp/types.go b/dhcpv4/bsdp/types.go index 4ce840f..4931081 100644 --- a/dhcpv4/bsdp/types.go +++ b/dhcpv4/bsdp/types.go @@ -1,5 +1,9 @@ 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 @@ -19,7 +23,7 @@ func (o optionCode) String() string { if s, ok := optionCodeToString[o]; ok { return s } - return "unknown" + return fmt.Sprintf("unknown (%d)", o) } // Options (occur as sub-options of DHCP option 43). -- cgit v1.2.3