From 8c70d406f6d24a17219a5f543174c1f3f3ad9e35 Mon Sep 17 00:00:00 2001 From: Pablo Mazzini Date: Mon, 4 Dec 2023 10:33:34 +0000 Subject: [PATCH 01/10] remove bsdp package --- dhcpv4/bsdp/boot_image.go | 136 ------ dhcpv4/bsdp/boot_image_test.go | 139 ------ dhcpv4/bsdp/bsdp.go | 254 ----------- dhcpv4/bsdp/bsdp_option_boot_image_list.go | 53 --- .../bsdp/bsdp_option_boot_image_list_test.go | 114 ----- dhcpv4/bsdp/bsdp_option_message_type.go | 57 --- dhcpv4/bsdp/bsdp_option_message_type_test.go | 31 -- dhcpv4/bsdp/bsdp_option_misc.go | 66 --- dhcpv4/bsdp/bsdp_option_misc_test.go | 105 ----- dhcpv4/bsdp/bsdp_test.go | 410 ------------------ dhcpv4/bsdp/client.go | 75 ---- dhcpv4/bsdp/doc.go | 10 - .../option_vendor_specific_information.go | 157 ------- ...option_vendor_specific_information_test.go | 79 ---- dhcpv4/bsdp/types.go | 64 --- dhcpv4/bsdp/vendor_class_identifier.go | 9 - dhcpv4/bsdp/vendor_class_identifier_darwin.go | 18 - 17 files changed, 1777 deletions(-) delete mode 100644 dhcpv4/bsdp/boot_image.go delete mode 100644 dhcpv4/bsdp/boot_image_test.go delete mode 100644 dhcpv4/bsdp/bsdp.go delete mode 100644 dhcpv4/bsdp/bsdp_option_boot_image_list.go delete mode 100644 dhcpv4/bsdp/bsdp_option_boot_image_list_test.go delete mode 100644 dhcpv4/bsdp/bsdp_option_message_type.go delete mode 100644 dhcpv4/bsdp/bsdp_option_message_type_test.go delete mode 100644 dhcpv4/bsdp/bsdp_option_misc.go delete mode 100644 dhcpv4/bsdp/bsdp_option_misc_test.go delete mode 100644 dhcpv4/bsdp/bsdp_test.go delete mode 100644 dhcpv4/bsdp/client.go delete mode 100644 dhcpv4/bsdp/doc.go delete mode 100644 dhcpv4/bsdp/option_vendor_specific_information.go delete mode 100644 dhcpv4/bsdp/option_vendor_specific_information_test.go delete mode 100644 dhcpv4/bsdp/types.go delete mode 100644 dhcpv4/bsdp/vendor_class_identifier.go delete mode 100644 dhcpv4/bsdp/vendor_class_identifier_darwin.go diff --git a/dhcpv4/bsdp/boot_image.go b/dhcpv4/bsdp/boot_image.go deleted file mode 100644 index 5e815301..00000000 --- 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 0b51287d..00000000 --- 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 d90e8845..00000000 --- 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 ed702436..00000000 --- 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 6282156e..00000000 --- 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 541cd969..00000000 --- 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 6666137d..00000000 --- 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 3848a6c3..00000000 --- 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 675a5db2..00000000 --- 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 05cd85c0..00000000 --- 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 5d0e667e..00000000 --- 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 b3a21861..00000000 --- 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 4c2365ce..00000000 --- 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 a6727f56..00000000 --- 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 49310815..00000000 --- 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 dfd1ca96..00000000 --- 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 46ef6561..00000000 --- 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 -} From c78f3548092896489cdff6cf2ab113c71f73951e Mon Sep 17 00:00:00 2001 From: Arjun <36335769+0x34d@users.noreply.github.com> Date: Wed, 26 Jul 2023 23:09:53 +0530 Subject: [PATCH 02/10] [fuzzing] add cifuzz workflows Add [CIFuzz](https://google.github.io/oss-fuzz/getting-started/continuous-integration) for PR fuzzing. Signed-off-by: Arjun Singh --- .github/workflows/cifuzz.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/cifuzz.yml diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 00000000..9364eb7a --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,24 @@ +name: CIFuzz +on: [pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'go-dhcp' + language: go + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'go-dhcp' + language: go + fuzz-seconds: 300 + - name: Upload Crash + uses: actions/upload-artifact@v3 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts From 15c9b87919140e8a3f70d66399323ffb193e0ad5 Mon Sep 17 00:00:00 2001 From: Arjun Singh Date: Tue, 8 Aug 2023 09:08:16 +0530 Subject: [PATCH 03/10] [cifuzz] updated Signed-off-by: Arjun Singh --- .github/workflows/cifuzz.yml | 53 ++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 9364eb7a..64e1e132 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -1,24 +1,35 @@ name: CIFuzz on: [pull_request] +permissions: {} jobs: - Fuzzing: - runs-on: ubuntu-latest - steps: - - name: Build Fuzzers - id: build - uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master - with: - oss-fuzz-project-name: 'go-dhcp' - language: go - - name: Run Fuzzers - uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master - with: - oss-fuzz-project-name: 'go-dhcp' - language: go - fuzz-seconds: 300 - - name: Upload Crash - uses: actions/upload-artifact@v3 - if: failure() && steps.build.outcome == 'success' - with: - name: artifacts - path: ./out/artifacts + Fuzzing: + runs-on: ubuntu-latest + permissions: + security-events: write + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'go-dhcp' + language: go + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'go-dhcp' + language: go + fuzz-seconds: 300 + output-sarif: true + - name: Upload Crash + uses: actions/upload-artifact@v3 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts + - name: Upload Sarif + if: always() && steps.build.outcome == 'success' + uses: github/codeql-action/upload-sarif@v2 + with: + # Path to SARIF file relative to the root of the repository + sarif_file: cifuzz-sarif/results.sarif + checkout_path: cifuzz-sarif From ca2dc33955c19d8d1c2a4e33a83e720244e30285 Mon Sep 17 00:00:00 2001 From: Pablo Mazzini Date: Sun, 4 Feb 2024 14:59:17 +0000 Subject: [PATCH 04/10] go mod tidy: remove unused dependencies --- go.mod | 2 -- go.sum | 15 --------------- 2 files changed, 17 deletions(-) diff --git a/go.mod b/go.mod index 547404dc..d94cc3a5 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/insomniacslk/dhcp go 1.20 require ( - github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72 github.com/google/go-cmp v0.5.9 github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 github.com/jsimonetti/rtnetlink v1.3.5 @@ -21,7 +20,6 @@ require ( github.com/mdlayher/socket v0.4.1 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/smartystreets/goconvey v1.6.4 // indirect github.com/stretchr/objx v0.1.0 // indirect golang.org/x/sync v0.3.0 // indirect gopkg.in/yaml.v3 v3.0.0 // indirect diff --git a/go.sum b/go.sum index 117971fd..fa94a8de 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,8 @@ github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72 h1:0eU/faU2oDIB2BkQVM02hgRLJjGzzUuRf19HUhp0394= -github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= @@ -14,8 +10,6 @@ github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtL github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/jsimonetti/rtnetlink v1.3.5 h1:hVlNQNRlLDGZz31gBPicsG7Q53rnlsz1l1Ix/9XlpVA= github.com/jsimonetti/rtnetlink v1.3.5/go.mod h1:0LFedyiTkebnd43tE4YAkWGIq9jQphow4CcwxaT2Y00= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY= @@ -26,29 +20,20 @@ github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From fb14ecd046015c4922c683913eace6be63f2d580 Mon Sep 17 00:00:00 2001 From: Brian Candler Date: Sun, 25 Feb 2024 22:24:51 +0700 Subject: [PATCH 05/10] Add option IPv6OnlyPreferred (RFC 8925) Signed-off-by: Brian Candler --- dhcpv4/dhcpv4.go | 16 +++++ dhcpv4/modifiers.go | 5 ++ dhcpv4/modifiers_test.go | 9 +++ ...dress_lease_time.go => option_duration.go} | 5 ++ dhcpv4/option_duration_test.go | 71 +++++++++++++++++++ dhcpv4/option_ip_address_lease_time_test.go | 35 --------- dhcpv4/options.go | 2 +- dhcpv4/types.go | 4 ++ 8 files changed, 111 insertions(+), 36 deletions(-) rename dhcpv4/{option_ip_address_lease_time.go => option_duration.go} (85%) create mode 100644 dhcpv4/option_duration_test.go delete mode 100644 dhcpv4/option_ip_address_lease_time_test.go diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go index 6043cd95..b922fb92 100644 --- a/dhcpv4/dhcpv4.go +++ b/dhcpv4/dhcpv4.go @@ -730,6 +730,22 @@ func (d *DHCPv4) IPAddressRebindingTime(def time.Duration) time.Duration { return time.Duration(dur) } +// IPv6OnlyPreferred returns the V6ONLY_WAIT duration, and a boolean +// indicating whether this option was present. +// +// The IPv6-Only Preferred option is described by RFC 8925, Section 3.1. +func (d *DHCPv4) IPv6OnlyPreferred() (time.Duration, bool) { + v := d.Options.Get(OptionIPv6OnlyPreferred) + if v == nil { + return 0, false + } + var dur Duration + if err := dur.FromBytes(v); err != nil { + return 0, false + } + return time.Duration(dur), true +} + // MaxMessageSize returns the DHCP Maximum Message Size if present. // // The Maximum DHCP Message Size option is described by RFC 2132, Section 9.10. diff --git a/dhcpv4/modifiers.go b/dhcpv4/modifiers.go index 68da298c..55863fe0 100644 --- a/dhcpv4/modifiers.go +++ b/dhcpv4/modifiers.go @@ -159,6 +159,11 @@ func WithLeaseTime(leaseTime uint32) Modifier { return WithOption(OptIPAddressLeaseTime(time.Duration(leaseTime) * time.Second)) } +// WithIPv6OnlyPreferred adds or updates an OptIPv6OnlyPreferred +func WithIPv6OnlyPreferred(v6OnlyWait uint32) Modifier { + return WithOption(OptIPv6OnlyPreferred(time.Duration(v6OnlyWait) * time.Second)) +} + // WithDomainSearchList adds or updates an OptionDomainSearch func WithDomainSearchList(searchList ...string) Modifier { return WithOption(OptDomainSearch(&rfc1035label.Labels{ diff --git a/dhcpv4/modifiers_test.go b/dhcpv4/modifiers_test.go index 57d8ff04..7659fc2a 100644 --- a/dhcpv4/modifiers_test.go +++ b/dhcpv4/modifiers_test.go @@ -154,6 +154,15 @@ func TestWithDNS(t *testing.T) { require.Equal(t, net.ParseIP("10.0.0.2").To4(), dns[1]) } +func TestWithIPv6OnlyPreferred(t *testing.T) { + d, err := New(WithIPv6OnlyPreferred(300)) + require.NoError(t, err) + + v6pref, ok := d.IPv6OnlyPreferred() + require.True(t, ok) + require.Equal(t, 300*time.Second, v6pref) +} + func TestWithDomainSearchList(t *testing.T) { d, err := New(WithDomainSearchList("slackware.it", "dhcp.slackware.it")) require.NoError(t, err) diff --git a/dhcpv4/option_ip_address_lease_time.go b/dhcpv4/option_duration.go similarity index 85% rename from dhcpv4/option_ip_address_lease_time.go rename to dhcpv4/option_duration.go index 0b5cd30c..bfa01366 100644 --- a/dhcpv4/option_ip_address_lease_time.go +++ b/dhcpv4/option_duration.go @@ -39,3 +39,8 @@ func (d Duration) String() string { func OptIPAddressLeaseTime(d time.Duration) Option { return Option{Code: OptionIPAddressLeaseTime, Value: Duration(d)} } + +// The IPv6-Only Preferred option is described by RFC 8925, Section 3.1 +func OptIPv6OnlyPreferred(d time.Duration) Option { + return Option{Code: OptionIPv6OnlyPreferred, Value: Duration(d)} +} diff --git a/dhcpv4/option_duration_test.go b/dhcpv4/option_duration_test.go new file mode 100644 index 00000000..1b3a0e50 --- /dev/null +++ b/dhcpv4/option_duration_test.go @@ -0,0 +1,71 @@ +package dhcpv4 + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestOptIPAddressLeaseTime(t *testing.T) { + o := OptIPAddressLeaseTime(43200 * time.Second) + require.Equal(t, OptionIPAddressLeaseTime, o.Code, "Code") + require.Equal(t, []byte{0, 0, 168, 192}, o.Value.ToBytes(), "ToBytes") + require.Equal(t, "IP Addresses Lease Time: 12h0m0s", o.String(), "String") +} + +func TestGetIPAddressLeaseTime(t *testing.T) { + m, _ := New(WithGeneric(OptionIPAddressLeaseTime, []byte{0, 0, 168, 192})) + leaseTime := m.IPAddressLeaseTime(0) + require.Equal(t, 43200*time.Second, leaseTime) + + // Too short. + m, _ = New(WithGeneric(OptionIPAddressLeaseTime, []byte{168, 192})) + leaseTime = m.IPAddressLeaseTime(0) + require.Equal(t, time.Duration(0), leaseTime) + + // Too long. + m, _ = New(WithGeneric(OptionIPAddressLeaseTime, []byte{1, 1, 1, 1, 1})) + leaseTime = m.IPAddressLeaseTime(0) + require.Equal(t, time.Duration(0), leaseTime) + + // Empty. + m, _ = New() + require.Equal(t, time.Duration(10), m.IPAddressLeaseTime(10)) +} + +func TestOptIPv6OnlyPreferred(t *testing.T) { + o := OptIPv6OnlyPreferred(43200 * time.Second) + require.Equal(t, OptionIPv6OnlyPreferred, o.Code, "Code") + require.Equal(t, []byte{0, 0, 168, 192}, o.Value.ToBytes(), "ToBytes") + require.Equal(t, "IPv6-Only Preferred: 12h0m0s", o.String(), "String") +} + +func TestOptIPv6OnlyPreferredZero(t *testing.T) { + o := OptIPv6OnlyPreferred(0) + require.Equal(t, OptionIPv6OnlyPreferred, o.Code, "Code") + require.Equal(t, []byte{0, 0, 0, 0}, o.Value.ToBytes(), "ToBytes") + require.Equal(t, "IPv6-Only Preferred: 0s", o.String(), "String") +} + +func TestGetIPv6OnlyPreferred(t *testing.T) { + m, _ := New(WithGeneric(OptionIPv6OnlyPreferred, []byte{0, 0, 168, 192})) + v6onlyWait, ok := m.IPv6OnlyPreferred() + require.True(t, ok) + require.Equal(t, 43200*time.Second, v6onlyWait) + + // Too short. + m, _ = New(WithGeneric(OptionIPv6OnlyPreferred, []byte{168, 192})) + _, ok = m.IPv6OnlyPreferred() + require.False(t, ok) + + // Too long. + m, _ = New(WithGeneric(OptionIPv6OnlyPreferred, []byte{1, 1, 1, 1, 1})) + _, ok = m.IPv6OnlyPreferred() + require.False(t, ok) + + // Missing. + m, _ = New() + _, ok = m.IPv6OnlyPreferred() + require.False(t, ok) +} diff --git a/dhcpv4/option_ip_address_lease_time_test.go b/dhcpv4/option_ip_address_lease_time_test.go deleted file mode 100644 index edd709f3..00000000 --- a/dhcpv4/option_ip_address_lease_time_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package dhcpv4 - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestOptIPAddressLeaseTime(t *testing.T) { - o := OptIPAddressLeaseTime(43200 * time.Second) - require.Equal(t, OptionIPAddressLeaseTime, o.Code, "Code") - require.Equal(t, []byte{0, 0, 168, 192}, o.Value.ToBytes(), "ToBytes") - require.Equal(t, "IP Addresses Lease Time: 12h0m0s", o.String(), "String") -} - -func TestGetIPAddressLeaseTime(t *testing.T) { - m, _ := New(WithGeneric(OptionIPAddressLeaseTime, []byte{0, 0, 168, 192})) - leaseTime := m.IPAddressLeaseTime(0) - require.Equal(t, 43200*time.Second, leaseTime) - - // Too short. - m, _ = New(WithGeneric(OptionIPAddressLeaseTime, []byte{168, 192})) - leaseTime = m.IPAddressLeaseTime(0) - require.Equal(t, time.Duration(0), leaseTime) - - // Too long. - m, _ = New(WithGeneric(OptionIPAddressLeaseTime, []byte{1, 1, 1, 1, 1})) - leaseTime = m.IPAddressLeaseTime(0) - require.Equal(t, time.Duration(0), leaseTime) - - // Empty. - m, _ = New() - require.Equal(t, time.Duration(10), m.IPAddressLeaseTime(10)) -} diff --git a/dhcpv4/options.go b/dhcpv4/options.go index 9d404b43..1b56c28d 100644 --- a/dhcpv4/options.go +++ b/dhcpv4/options.go @@ -336,7 +336,7 @@ func getOption(code OptionCode, data []byte, vendorDecoder OptionDecoder) fmt.St case OptionDNSDomainSearchList: d = &rfc1035label.Labels{} - case OptionIPAddressLeaseTime: + case OptionIPAddressLeaseTime, OptionIPv6OnlyPreferred: var dur Duration d = &dur diff --git a/dhcpv4/types.go b/dhcpv4/types.go index 7dddefd8..80ea49cf 100644 --- a/dhcpv4/types.go +++ b/dhcpv4/types.go @@ -238,6 +238,8 @@ const ( OptionGeoConfCivic optionCode = 99 OptionIEEE10031TZString optionCode = 100 OptionReferenceToTZDatabase optionCode = 101 + // Option 108 returned in RFC 8925 + OptionIPv6OnlyPreferred optionCode = 108 // Options 102-111 returned in RFC 3679 OptionNetInfoParentServerAddress optionCode = 112 OptionNetInfoParentServerTag optionCode = 113 @@ -401,6 +403,8 @@ var optionCodeToString = map[OptionCode]string{ OptionGeoConfCivic: "GEOCONF_CIVIC", OptionIEEE10031TZString: "IEEE 1003.1 TZ String", OptionReferenceToTZDatabase: "Reference to the TZ Database", + // Option 108 returned in RFC 8925 + OptionIPv6OnlyPreferred: "IPv6-Only Preferred", // Options 102-111 returned in RFC 3679 OptionNetInfoParentServerAddress: "NetInfo Parent Server Address", OptionNetInfoParentServerTag: "NetInfo Parent Server Tag", From a0528844abc59ea4e1bceea6bff7a02273b70253 Mon Sep 17 00:00:00 2001 From: Brian Candler Date: Sun, 25 Feb 2024 23:56:18 +0700 Subject: [PATCH 06/10] Add helpers for AutoConfigure from RFC 2563 Signed-off-by: Brian Candler --- dhcpv4/dhcpv4.go | 9 +++++ dhcpv4/option_autoconfigure.go | 61 +++++++++++++++++++++++++++++ dhcpv4/option_autoconfigure_test.go | 46 ++++++++++++++++++++++ dhcpv4/options.go | 4 ++ dhcpv4/options_test.go | 5 +++ 5 files changed, 125 insertions(+) create mode 100644 dhcpv4/option_autoconfigure.go create mode 100644 dhcpv4/option_autoconfigure_test.go diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go index 6043cd95..c758a54e 100644 --- a/dhcpv4/dhcpv4.go +++ b/dhcpv4/dhcpv4.go @@ -737,6 +737,15 @@ func (d *DHCPv4) MaxMessageSize() (uint16, error) { return GetUint16(OptionMaximumDHCPMessageSize, d.Options) } +// AutoConfigure returns the value of the AutoConfigure option, and a +// boolean indicating if it was present. +// +// The AutoConfigure option is described by RFC 2563, Section 2. +func (d *DHCPv4) AutoConfigure() (AutoConfiguration, bool) { + v, err := GetByte(OptionAutoConfigure, d.Options) + return AutoConfiguration(v), err == nil +} + // MessageType returns the DHCPv4 Message Type option. func (d *DHCPv4) MessageType() MessageType { v := d.Options.Get(OptionDHCPMessageType) diff --git a/dhcpv4/option_autoconfigure.go b/dhcpv4/option_autoconfigure.go new file mode 100644 index 00000000..e2237bfc --- /dev/null +++ b/dhcpv4/option_autoconfigure.go @@ -0,0 +1,61 @@ +package dhcpv4 + +import ( + "fmt" +) + +// AutoConfiguration implements encoding and decoding functions for a +// byte enumeration as used in RFC 2563, Section 2. +type AutoConfiguration byte + +const ( + DoNotAutoConfigure AutoConfiguration = 0 + AutoConfigure AutoConfiguration = 1 +) + +var autoConfigureToString = map[AutoConfiguration]string{ + DoNotAutoConfigure: "DoNotAutoConfigure", + AutoConfigure: "AutoConfigure", +} + +// ToBytes returns a serialized stream of bytes for this option. +func (o AutoConfiguration) ToBytes() []byte { + return []byte{byte(o)} +} + +// String returns a human-readable string for this option. +func (o AutoConfiguration) String() string { + s := autoConfigureToString[o] + if s != "" { + return s + } + return fmt.Sprintf("UNKNOWN (%d)", byte(o)) +} + +// FromBytes parses a a single byte into AutoConfiguration +func (o *AutoConfiguration) FromBytes(data []byte) error { + if len(data) == 1 { + *o = AutoConfiguration(data[0]) + return nil + } + return fmt.Errorf("Invalid buffer length (%d)", len(data)) +} + +// GetByte parses any single-byte option +func GetByte(code OptionCode, o Options) (byte, error) { + data := o.Get(code) + if data == nil { + return 0, fmt.Errorf("option not present") + } + if len(data) != 1 { + return 0, fmt.Errorf("Invalid buffer length (%d)", len(data)) + } + return data[0], nil +} + +// OptAutoConfigure returns a new AutoConfigure option. +// +// The AutoConfigure option is described by RFC 2563, Section 2. +func OptAutoConfigure(autoconf AutoConfiguration) Option { + return Option{Code: OptionAutoConfigure, Value: autoconf} +} diff --git a/dhcpv4/option_autoconfigure_test.go b/dhcpv4/option_autoconfigure_test.go new file mode 100644 index 00000000..55bdde41 --- /dev/null +++ b/dhcpv4/option_autoconfigure_test.go @@ -0,0 +1,46 @@ +package dhcpv4 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOptAutoConfigure(t *testing.T) { + o := OptAutoConfigure(0) + require.Equal(t, OptionAutoConfigure, o.Code, "Code") + require.Equal(t, []byte{0}, o.Value.ToBytes(), "ToBytes") + require.Equal(t, "Auto-Configure: DoNotAutoConfigure", o.String()) + + o = OptAutoConfigure(1) + require.Equal(t, OptionAutoConfigure, o.Code, "Code") + require.Equal(t, []byte{1}, o.Value.ToBytes(), "ToBytes") + require.Equal(t, "Auto-Configure: AutoConfigure", o.String()) + + o = OptAutoConfigure(2) + require.Equal(t, OptionAutoConfigure, o.Code, "Code") + require.Equal(t, []byte{2}, o.Value.ToBytes(), "ToBytes") + require.Equal(t, "Auto-Configure: UNKNOWN (2)", o.String()) +} + +func TestGetAutoConfigure(t *testing.T) { + m, _ := New(WithGeneric(OptionAutoConfigure, []byte{1})) + o, ok := m.AutoConfigure() + require.True(t, ok) + require.Equal(t, AutoConfigure, o) + + // Missing + m, _ = New() + _, ok = m.AutoConfigure() + require.False(t, ok, "should get error if option missing") + + // Short byte stream + m, _ = New(WithGeneric(OptionAutoConfigure, []byte{})) + _, ok = m.AutoConfigure() + require.False(t, ok, "should get error from short byte stream") + + // Bad length + m, _ = New(WithGeneric(OptionAutoConfigure, []byte{2, 2})) + _, ok = m.AutoConfigure() + require.False(t, ok, "should get error from bad length") +} diff --git a/dhcpv4/options.go b/dhcpv4/options.go index 9d404b43..7296a5ea 100644 --- a/dhcpv4/options.go +++ b/dhcpv4/options.go @@ -352,6 +352,10 @@ func getOption(code OptionCode, data []byte, vendorDecoder OptionDecoder) fmt.St d = &s } + case OptionAutoConfigure: + var a AutoConfiguration + d = &a + case OptionVendorIdentifyingVendorClass: d = &VIVCIdentifiers{} diff --git a/dhcpv4/options_test.go b/dhcpv4/options_test.go index e918ee3d..331dd1b4 100644 --- a/dhcpv4/options_test.go +++ b/dhcpv4/options_test.go @@ -106,6 +106,11 @@ func TestParseOption(t *testing.T) { value: []byte{1, 2}, want: "258", }, + { + code: OptionAutoConfigure, + value: []byte{0}, + want: "DoNotAutoConfigure", + }, { code: OptionUserClassInformation, value: []byte{4, 't', 'e', 's', 't', 3, 'f', 'o', 'o'}, From a328c9d2e253b152d46976bda8e1875f3e1cdc1e Mon Sep 17 00:00:00 2001 From: Brian Candler Date: Sun, 14 Apr 2024 13:23:11 +0100 Subject: [PATCH 07/10] Make IsOptionRequested() work with GenericOptionCode Fixes #511 Signed-off-by: Brian Candler --- dhcpv4/dhcpv4.go | 2 +- dhcpv4/dhcpv4_test.go | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go index 71a6ffac..a8754312 100644 --- a/dhcpv4/dhcpv4.go +++ b/dhcpv4/dhcpv4.go @@ -483,7 +483,7 @@ func (d *DHCPv4) IsOptionRequested(requested OptionCode) bool { } for _, o := range rq { - if o == requested { + if o.Code() == requested.Code() { return true } } diff --git a/dhcpv4/dhcpv4_test.go b/dhcpv4/dhcpv4_test.go index 95523aba..8c0f15d1 100644 --- a/dhcpv4/dhcpv4_test.go +++ b/dhcpv4/dhcpv4_test.go @@ -378,6 +378,19 @@ func TestIsOptionRequested(t *testing.T) { require.True(t, pkt.IsOptionRequested(OptionDomainNameServer)) } +func TestIsGenericOptionRequested(t *testing.T) { + pkt, err := New() + require.NoError(t, err) + + var genericOption224 = GenericOptionCode(224) + var genericOption225 = GenericOptionCode(225) + pkt.UpdateOption(OptParameterRequestList(OptionBroadcastAddress, genericOption224)) + require.True(t, pkt.IsOptionRequested(OptionBroadcastAddress)) + require.False(t, pkt.IsOptionRequested(OptionSwapServer)) + require.True(t, pkt.IsOptionRequested(genericOption224), "Did not detect generic option 224 in <%v>", pkt.ParameterRequestList()) + require.False(t, pkt.IsOptionRequested(genericOption225), "Detected generic option 225 in <%v>", pkt.ParameterRequestList()) +} + // TODO // test Summary() and String() func TestSummary(t *testing.T) { From 98ed35fb9458c1e4b9b97b464094b7fd99b5e65b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:15:30 +0000 Subject: [PATCH 08/10] Bump golang.org/x/net from 0.17.0 to 0.23.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.17.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index d94cc3a5..f93fee90 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,8 @@ require ( github.com/mdlayher/packet v1.1.2 github.com/stretchr/testify v1.6.1 github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 - golang.org/x/net v0.17.0 - golang.org/x/sys v0.13.0 + golang.org/x/net v0.23.0 + golang.org/x/sys v0.18.0 ) require ( diff --git a/go.sum b/go.sum index fa94a8de..6b08a535 100644 --- a/go.sum +++ b/go.sum @@ -27,13 +27,13 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From be454c61fecd5c4f0b098b551e343653a5748495 Mon Sep 17 00:00:00 2001 From: Francesco Montorsi Date: Thu, 9 May 2024 12:07:03 +0200 Subject: [PATCH 09/10] Update README.md adding Tinkerbell as user of this library --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 741cb845..ab2b7ac6 100644 --- a/README.md +++ b/README.md @@ -54,3 +54,4 @@ See more example code at https://github.com/insomniacslk/exdhcp * CoreDHCP, a fast, multithreaded, modular and extensible DHCP server, https://github.com/coredhcp/coredhcp * u-root, an embeddable root file system, https://github.com/u-root/u-root * Talos: a modern OS for Kubernetes, https://github.com/talos-systems/talos +* Smee: the network boot service in the Tinkerbell stack, https://github.com/tinkerbell/smee From 6ac7581c809846d3c75feabf5c2fd672667aa80e Mon Sep 17 00:00:00 2001 From: Michael Haro Date: Thu, 23 May 2024 08:24:56 -0700 Subject: [PATCH 10/10] Make Option 82 come last https://datatracker.ietf.org/doc/html/rfc3046#section-2.1 states: ``` A DHCP relay agent adding a Relay Agent Information field SHALL add it as the last option (but before 'End Option' 255, if present) in the DHCP options field of any recognized BOOTP or DHCP packet forwarded from a client to a server. ``` Signed-off-by: Michael Haro --- dhcpv4/options.go | 22 ++++++++++++++++++++-- dhcpv4/options_test.go | 3 +++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/dhcpv4/options.go b/dhcpv4/options.go index 779626fb..3f900e93 100644 --- a/dhcpv4/options.go +++ b/dhcpv4/options.go @@ -110,8 +110,9 @@ func (o Options) FromBytes(data []byte) error { } const ( - optPad = 0 - optEnd = 255 + optPad = 0 + optAgentInfo = 82 + optEnd = 255 ) // FromBytesCheckEnd parses Options from byte sequences using the @@ -176,11 +177,28 @@ func (o Options) fromBytesCheckEnd(data []byte, checkEndOption bool) error { func (o Options) sortedKeys() []int { // Send all values for a given key var codes []int + var hasOptAgentInfo, hasOptEnd bool for k := range o { + // RFC 3046 section 2.1 states that option 82 SHALL come last (ignoring End). + if k == optAgentInfo { + hasOptAgentInfo = true + continue + } + if k == optEnd { + hasOptEnd = true + continue + } codes = append(codes, int(k)) } sort.Ints(codes) + + if hasOptAgentInfo { + codes = append(codes, optAgentInfo) + } + if hasOptEnd { + codes = append(codes, optEnd) + } return codes } diff --git a/dhcpv4/options_test.go b/dhcpv4/options_test.go index 331dd1b4..ff11f888 100644 --- a/dhcpv4/options_test.go +++ b/dhcpv4/options_test.go @@ -179,14 +179,17 @@ func TestOptionsMarshal(t *testing.T) { }, { // Test sorted key order. + // RFC 3046 section 2.1 states that option 82 SHALL come last. opts: Options{ 5: []byte{1, 2, 3}, + 82: []byte{201, 202, 203}, 100: []byte{101, 102, 103}, 255: []byte{}, }, want: []byte{ 5, 3, 1, 2, 3, 100, 3, 101, 102, 103, + 82, 3, 201, 202, 203, }, }, {