From 78d75f4d4be95787bd3e8b1bf28172059d09273d Mon Sep 17 00:00:00 2001 From: Sean Karlage Date: Tue, 14 Aug 2018 22:53:45 -0700 Subject: BSDP: Add code to generate ACKs for INFORMs --- dhcpv4/bsdp/bsdp.go | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) (limited to 'dhcpv4/bsdp/bsdp.go') diff --git a/dhcpv4/bsdp/bsdp.go b/dhcpv4/bsdp/bsdp.go index 27de68e..2909ccd 100644 --- a/dhcpv4/bsdp/bsdp.go +++ b/dhcpv4/bsdp/bsdp.go @@ -14,6 +14,20 @@ import ( // 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 int + 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) { @@ -139,3 +153,79 @@ func InformSelectForAck(ack dhcpv4.DHCPv4, replyPort uint16, selectedImage BootI d.AddOption(&OptVendorSpecificInformation{vendorOpts}) return 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) { + if config.DefaultImage == nil { + return nil, errors.New("NewReplyForInformList: no default boot image ID set") + } + if len(config.Images) == 0 { + return nil, errors.New("NewReplyForInformList: no boot images provided") + } + reply, err := dhcpv4.NewReplyFromRequest(inform) + if err != nil { + return nil, err + } + reply.SetClientIPAddr(inform.ClientIPAddr()) + reply.SetYourIPAddr(net.IPv4zero) + reply.SetGatewayIPAddr(inform.GatewayIPAddr()) + reply.SetServerIPAddr(config.ServerIP) + reply.SetServerHostName([]byte(config.ServerHostname)) + + reply.AddOption(&dhcpv4.OptMessageType{MessageType: dhcpv4.MessageTypeAck}) + reply.AddOption(&dhcpv4.OptServerIdentifier{ServerID: config.ServerIP}) + reply.AddOption(&dhcpv4.OptClassIdentifier{Identifier: AppleVendorID}) + + // BSDP opts. + vendorOpts := []dhcpv4.Option{ + &OptMessageType{Type: MessageTypeList}, + &OptServerPriority{Priority: config.ServerPriority}, + &OptDefaultBootImageID{ID: config.DefaultImage.ID}, + &OptBootImageList{Images: config.Images}, + } + if config.SelectedImage != nil { + vendorOpts = append(vendorOpts, &OptSelectedBootImageID{ID: config.SelectedImage.ID}) + } + reply.AddOption(&OptVendorSpecificInformation{Options: vendorOpts}) + + reply.AddOption(&dhcpv4.OptionGeneric{OptionCode: dhcpv4.OptionEnd}) + return 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) { + if config.SelectedImage == nil { + return nil, errors.New("NewReplyForInformSelect: no selected boot image ID set") + } + if len(config.Images) == 0 { + return nil, errors.New("NewReplyForInformSelect: no boot images provided") + } + reply, err := dhcpv4.NewReplyFromRequest(inform) + if err != nil { + return nil, err + } + + reply.SetClientIPAddr(inform.ClientIPAddr()) + reply.SetYourIPAddr(net.IPv4zero) + reply.SetGatewayIPAddr(inform.GatewayIPAddr()) + reply.SetServerIPAddr(config.ServerIP) + reply.SetServerHostName([]byte(config.ServerHostname)) + reply.SetBootFileName([]byte(config.BootFileName)) + + reply.AddOption(&dhcpv4.OptMessageType{MessageType: dhcpv4.MessageTypeAck}) + reply.AddOption(&dhcpv4.OptServerIdentifier{ServerID: config.ServerIP}) + reply.AddOption(&dhcpv4.OptClassIdentifier{Identifier: AppleVendorID}) + + // BSDP opts. + reply.AddOption(&OptVendorSpecificInformation{ + Options: []dhcpv4.Option{ + &OptMessageType{Type: MessageTypeSelect}, + &OptSelectedBootImageID{ID: config.SelectedImage.ID}, + }, + }) + + reply.AddOption(&dhcpv4.OptionGeneric{OptionCode: dhcpv4.OptionEnd}) + return reply, nil +} -- cgit v1.2.3 From d614fa996b8fb38528191fe52c258037ae9c539a Mon Sep 17 00:00:00 2001 From: Sean Karlage Date: Wed, 15 Aug 2018 10:20:05 -0700 Subject: Add nil check for Images slice --- dhcpv4/bsdp/bsdp.go | 4 ++-- dhcpv4/bsdp/bsdp_test.go | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) (limited to 'dhcpv4/bsdp/bsdp.go') diff --git a/dhcpv4/bsdp/bsdp.go b/dhcpv4/bsdp/bsdp.go index 2909ccd..3402b0c 100644 --- a/dhcpv4/bsdp/bsdp.go +++ b/dhcpv4/bsdp/bsdp.go @@ -160,7 +160,7 @@ func NewReplyForInformList(inform *dhcpv4.DHCPv4, config ReplyConfig) (*dhcpv4.D if config.DefaultImage == nil { return nil, errors.New("NewReplyForInformList: no default boot image ID set") } - if len(config.Images) == 0 { + if config.Images == nil || len(config.Images) == 0 { return nil, errors.New("NewReplyForInformList: no boot images provided") } reply, err := dhcpv4.NewReplyFromRequest(inform) @@ -199,7 +199,7 @@ func NewReplyForInformSelect(inform *dhcpv4.DHCPv4, config ReplyConfig) (*dhcpv4 if config.SelectedImage == nil { return nil, errors.New("NewReplyForInformSelect: no selected boot image ID set") } - if len(config.Images) == 0 { + if config.Images == nil || len(config.Images) == 0 { return nil, errors.New("NewReplyForInformSelect: no boot images provided") } reply, err := dhcpv4.NewReplyFromRequest(inform) diff --git a/dhcpv4/bsdp/bsdp_test.go b/dhcpv4/bsdp/bsdp_test.go index f457631..fc1aedf 100644 --- a/dhcpv4/bsdp/bsdp_test.go +++ b/dhcpv4/bsdp/bsdp_test.go @@ -80,6 +80,11 @@ func TestNewReplyForInformList_NoImages(t *testing.T) { DefaultImage: &fakeImage, }) require.Error(t, err) + + _, err = NewReplyForInformList(inform, ReplyConfig{ + Images: nil, + SelectedImage: &fakeImage, + }) } // TODO (get9): clean up when #99 lands. @@ -186,6 +191,12 @@ func TestNewReplyForInformSelect_NoImages(t *testing.T) { SelectedImage: &fakeImage, }) require.Error(t, err) + + _, err = NewReplyForInformSelect(inform, ReplyConfig{ + Images: nil, + SelectedImage: &fakeImage, + }) + require.Error(t, err) } func TestNewReplyForInformSelect(t *testing.T) { -- cgit v1.2.3