diff options
author | Sean Karlage <skarlage@fb.com> | 2018-08-15 14:52:30 -0700 |
---|---|---|
committer | Sean Karlage <skarlage@fb.com> | 2018-08-15 14:52:30 -0700 |
commit | fcd40679d2ed749dccb751167d15b7c6683e9dcb (patch) | |
tree | ea9722c6a73947edb7c21ad71c50e45c8c95b077 /dhcpv4/bsdp | |
parent | d614fa996b8fb38528191fe52c258037ae9c539a (diff) | |
parent | bcc8c0ec8cd12d2777e9085ffafcbef4c16bf73b (diff) |
Merge branch 'master' into bsdp-acks
Diffstat (limited to 'dhcpv4/bsdp')
-rw-r--r-- | dhcpv4/bsdp/bsdp.go | 30 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_generic_test.go | 10 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_test.go | 280 | ||||
-rw-r--r-- | dhcpv4/bsdp/option_vendor_specific_information.go | 8 | ||||
-rw-r--r-- | dhcpv4/bsdp/option_vendor_specific_information_test.go | 17 |
5 files changed, 278 insertions, 67 deletions
diff --git a/dhcpv4/bsdp/bsdp.go b/dhcpv4/bsdp/bsdp.go index 3402b0c..e15c59c 100644 --- a/dhcpv4/bsdp/bsdp.go +++ b/dhcpv4/bsdp/bsdp.go @@ -40,7 +40,7 @@ func ParseBootImageListFromAck(ack dhcpv4.DHCPv4) ([]BootImage, error) { if err != nil { return nil, err } - bootImageOpts := vendorOpt.GetOptions(OptionBootImageList) + bootImageOpts := vendorOpt.GetOption(OptionBootImageList) for _, opt := range bootImageOpts { images = append(images, opt.(*OptBootImageList).Images...) } @@ -53,8 +53,30 @@ func needsReplyPort(replyPort uint16) bool { // NewInformListForInterface creates a new INFORM packet for interface ifname // with configuration options specified by config. -func NewInformListForInterface(iface string, replyPort uint16) (*dhcpv4.DHCPv4, error) { - d, err := dhcpv4.NewInformForInterface(iface /* needsBroadcast = */, false) +func NewInformListForInterface(ifname string, replyPort uint16) (*dhcpv4.DHCPv4, 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 localIPs == nil || 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) (*dhcpv4.DHCPv4, error) { + d, err := dhcpv4.NewInform(hwaddr, localIP) if err != nil { return nil, err } @@ -91,7 +113,7 @@ func NewInformListForInterface(iface string, replyPort uint16) (*dhcpv4.DHCPv4, } // InformSelectForAck constructs an INFORM[SELECT] packet given an ACK to the -// previously-sent INFORM[LIST] with Config config. +// previously-sent INFORM[LIST]. func InformSelectForAck(ack dhcpv4.DHCPv4, replyPort uint16, selectedImage BootImage) (*dhcpv4.DHCPv4, error) { d, err := dhcpv4.New() if err != nil { diff --git a/dhcpv4/bsdp/bsdp_option_generic_test.go b/dhcpv4/bsdp/bsdp_option_generic_test.go index 5abcfbd..27436dd 100644 --- a/dhcpv4/bsdp/bsdp_option_generic_test.go +++ b/dhcpv4/bsdp/bsdp_option_generic_test.go @@ -10,6 +10,16 @@ func TestParseOptGeneric(t *testing.T) { // Empty bytestream produces error _, err := ParseOptGeneric([]byte{}) require.Error(t, err, "error from empty bytestream") + + // Good parse + o, err := ParseOptGeneric([]byte{1, 1, 1}) + require.NoError(t, err) + require.Equal(t, OptionMessageType, o.Code()) + require.Equal(t, MessageTypeList, MessageType(o.Data[0])) + + // Bad parse + o, err = ParseOptGeneric([]byte{1, 2, 1}) + require.Error(t, err, "invalid length") } func TestOptGenericCode(t *testing.T) { diff --git a/dhcpv4/bsdp/bsdp_test.go b/dhcpv4/bsdp/bsdp_test.go index fc1aedf..6029d49 100644 --- a/dhcpv4/bsdp/bsdp_test.go +++ b/dhcpv4/bsdp/bsdp_test.go @@ -52,43 +52,31 @@ func TestNeedsReplyPort(t *testing.T) { require.False(t, needsReplyPort(dhcpv4.ClientPort)) } -// TODO(get9): Remove when #99 lands. -func newInform() *dhcpv4.DHCPv4 { - p, _ := dhcpv4.New() - p.SetClientIPAddr(net.IP{1, 2, 3, 4}) - p.SetGatewayIPAddr(net.IP{4, 3, 2, 1}) - p.SetHwType(iana.HwTypeEthernet) - hwAddr := [16]byte{1, 2, 3, 4, 5, 6} - p.SetClientHwAddr(hwAddr[:]) - p.SetHwAddrLen(6) - return p -} - -func TestNewReplyForInformList_NoDefaultImage(t *testing.T) { +func TestNewReplyForInformSelect_NoSelectedImage(t *testing.T) { inform := newInform() - _, err := NewReplyForInformList(inform, ReplyConfig{}) + _, err := NewReplyForInformSelect(inform, ReplyConfig{}) require.Error(t, err) } -func TestNewReplyForInformList_NoImages(t *testing.T) { +func TestNewReplyForInformSelect_NoImages(t *testing.T) { inform := newInform() fakeImage := BootImage{ ID: BootImageID{ImageType: BootImageTypeMacOSX}, } - _, err := NewReplyForInformList(inform, ReplyConfig{ - Images: []BootImage{}, - DefaultImage: &fakeImage, + _, err := NewReplyForInformSelect(inform, ReplyConfig{ + Images: []BootImage{}, + SelectedImage: &fakeImage, }) require.Error(t, err) - _, err = NewReplyForInformList(inform, ReplyConfig{ + _, err = NewReplyForInformSelect(inform, ReplyConfig{ Images: nil, SelectedImage: &fakeImage, }) + require.Error(t, err) } -// TODO (get9): clean up when #99 lands. -func TestNewReplyForInformList(t *testing.T) { +func TestNewReplyForInformSelect(t *testing.T) { inform := newInform() images := []BootImage{ BootImage{ @@ -110,12 +98,12 @@ func TestNewReplyForInformList(t *testing.T) { } config := ReplyConfig{ Images: images, - DefaultImage: &images[0], + SelectedImage: &images[0], ServerIP: net.IP{9, 9, 9, 9}, ServerHostname: "bsdp.foo.com", ServerPriority: 0x7070, } - ack, err := NewReplyForInformList(inform, config) + 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()) @@ -144,62 +132,221 @@ func TestNewReplyForInformList(t *testing.T) { vendorOpts := ack.GetOneOption(dhcpv4.OptionVendorSpecificInformation).(*OptVendorSpecificInformation) require.Equal( t, - &OptMessageType{Type: MessageTypeList}, + &OptMessageType{Type: MessageTypeSelect}, vendorOpts.GetOneOption(OptionMessageType).(*OptMessageType), ) require.Equal( t, - &OptServerPriority{Priority: 0x7070}, - vendorOpts.GetOneOption(OptionServerPriority).(*OptServerPriority), - ) - require.Equal( - t, - &OptDefaultBootImageID{ID: images[0].ID}, - vendorOpts.GetOneOption(OptionDefaultBootImageID).(*OptDefaultBootImageID), - ) - require.Equal( - t, - &OptBootImageList{Images: images}, - vendorOpts.GetOneOption(OptionBootImageList).(*OptBootImageList), - ) - - // Add in selected boot image, ensure it's in the generated ACK. - config.SelectedImage = &images[0] - ack, err = NewReplyForInformList(inform, config) - require.NoError(t, err) - vendorOpts = ack.GetOneOption(dhcpv4.OptionVendorSpecificInformation).(*OptVendorSpecificInformation) - require.Equal( - t, &OptSelectedBootImageID{ID: images[0].ID}, vendorOpts.GetOneOption(OptionSelectedBootImageID).(*OptSelectedBootImageID), ) } -func TestNewReplyForInformSelect_NoSelectedImage(t *testing.T) { +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, dhcpv4.HasOption(m, dhcpv4.OptionVendorSpecificInformation)) + require.True(t, dhcpv4.HasOption(m, dhcpv4.OptionParameterRequestList)) + require.True(t, dhcpv4.HasOption(m, dhcpv4.OptionMaximumDHCPMessageSize)) + require.True(t, dhcpv4.HasOption(m, dhcpv4.OptionEnd)) + + opt := m.GetOneOption(dhcpv4.OptionVendorSpecificInformation) + require.NotNil(t, opt, "vendor opts not present") + vendorInfo := opt.(*OptVendorSpecificInformation) + require.True(t, dhcpv4.HasOption(vendorInfo, OptionMessageType)) + require.True(t, dhcpv4.HasOption(vendorInfo, OptionVersion)) + + opt = vendorInfo.GetOneOption(OptionMessageType) + require.Equal(t, MessageTypeList, opt.(*OptMessageType).Type) +} + +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) + + opt := m.GetOneOption(dhcpv4.OptionVendorSpecificInformation) + vendorInfo := opt.(*OptVendorSpecificInformation) + require.True(t, dhcpv4.HasOption(vendorInfo, OptionReplyPort)) + + opt = vendorInfo.GetOneOption(OptionReplyPort) + require.Equal(t, replyPort, opt.(*OptReplyPort).Port) +} + +func newAck(hwAddr []byte, transactionID uint32) *dhcpv4.DHCPv4 { + ack, _ := dhcpv4.New() + ack.SetTransactionID(transactionID) + ack.SetHwType(iana.HwTypeEthernet) + ack.SetClientHwAddr(hwAddr) + ack.SetHwAddrLen(uint8(len(hwAddr))) + ack.AddOption(&dhcpv4.OptMessageType{MessageType: dhcpv4.MessageTypeAck}) + ack.AddOption(&dhcpv4.OptionGeneric{OptionCode: dhcpv4.OptionEnd}) + return ack +} + +func TestInformSelectForAck_Broadcast(t *testing.T) { + hwAddr := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66} + tid := uint32(22) + 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.AddOption(&dhcpv4.OptServerIdentifier{ServerID: serverID}) + + m, err := InformSelectForAck(*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, dhcpv4.HasOption(m, dhcpv4.OptionClassIdentifier)) + require.True(t, dhcpv4.HasOption(m, dhcpv4.OptionParameterRequestList)) + require.True(t, dhcpv4.HasOption(m, dhcpv4.OptionDHCPMessageType)) + opt := m.GetOneOption(dhcpv4.OptionDHCPMessageType) + require.Equal(t, dhcpv4.MessageTypeInform, opt.(*dhcpv4.OptMessageType).MessageType) + require.True(t, dhcpv4.HasOption(m, dhcpv4.OptionEnd)) + + // Validate vendor opts. + require.True(t, dhcpv4.HasOption(m, dhcpv4.OptionVendorSpecificInformation)) + opt = m.GetOneOption(dhcpv4.OptionVendorSpecificInformation) + vendorInfo := opt.(*OptVendorSpecificInformation) + require.True(t, dhcpv4.HasOption(vendorInfo, OptionMessageType)) + opt = vendorInfo.GetOneOption(OptionMessageType) + require.Equal(t, MessageTypeSelect, opt.(*OptMessageType).Type) + require.True(t, dhcpv4.HasOption(vendorInfo, OptionVersion)) + require.True(t, dhcpv4.HasOption(vendorInfo, OptionSelectedBootImageID)) + opt = vendorInfo.GetOneOption(OptionSelectedBootImageID) + require.Equal(t, bootImage.ID, opt.(*OptSelectedBootImageID).ID) + require.True(t, dhcpv4.HasOption(vendorInfo, OptionServerIdentifier)) + opt = vendorInfo.GetOneOption(OptionServerIdentifier) + require.True(t, serverID.Equal(opt.(*OptServerIdentifier).ServerID)) +} + +func TestInformSelectForAck_NoServerID(t *testing.T) { + hwAddr := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66} + tid := uint32(22) + bootImage := BootImage{ + ID: BootImageID{ + IsInstall: true, + ImageType: BootImageTypeMacOSX, + Index: 0x1000, + }, + Name: "bsdp-1", + } + ack := newAck(hwAddr, tid) + + _, err := InformSelectForAck(*ack, 0, bootImage) + require.Error(t, err, "expect error for no server identifier option") +} + +func TestInformSelectForAck_BadReplyPort(t *testing.T) { + hwAddr := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66} + tid := uint32(22) + 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.AddOption(&dhcpv4.OptServerIdentifier{ServerID: serverID}) + + _, err := InformSelectForAck(*ack, 11223, bootImage) + require.Error(t, err, "expect error for > 1024 replyPort") +} + +func TestInformSelectForAck_ReplyPort(t *testing.T) { + hwAddr := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66} + tid := uint32(22) + 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.AddOption(&dhcpv4.OptServerIdentifier{ServerID: serverID}) + + replyPort := uint16(999) + m, err := InformSelectForAck(*ack, replyPort, bootImage) + require.NoError(t, err) + + require.True(t, dhcpv4.HasOption(m, dhcpv4.OptionVendorSpecificInformation)) + opt := m.GetOneOption(dhcpv4.OptionVendorSpecificInformation) + vendorInfo := opt.(*OptVendorSpecificInformation) + require.True(t, dhcpv4.HasOption(vendorInfo, OptionReplyPort)) + opt = vendorInfo.GetOneOption(OptionReplyPort) + require.Equal(t, replyPort, opt.(*OptReplyPort).Port) +} + +// TODO(get9): Remove when #99 lands. +func newInform() *dhcpv4.DHCPv4 { + p, _ := dhcpv4.New() + p.SetClientIPAddr(net.IP{1, 2, 3, 4}) + p.SetGatewayIPAddr(net.IP{4, 3, 2, 1}) + p.SetHwType(iana.HwTypeEthernet) + hwAddr := [16]byte{1, 2, 3, 4, 5, 6} + p.SetClientHwAddr(hwAddr[:]) + p.SetHwAddrLen(6) + return p +} + +func TestNewReplyForInformList_NoDefaultImage(t *testing.T) { inform := newInform() - _, err := NewReplyForInformSelect(inform, ReplyConfig{}) + _, err := NewReplyForInformList(inform, ReplyConfig{}) require.Error(t, err) } -func TestNewReplyForInformSelect_NoImages(t *testing.T) { +func TestNewReplyForInformList_NoImages(t *testing.T) { inform := newInform() fakeImage := BootImage{ ID: BootImageID{ImageType: BootImageTypeMacOSX}, } - _, err := NewReplyForInformSelect(inform, ReplyConfig{ - Images: []BootImage{}, - SelectedImage: &fakeImage, + _, err := NewReplyForInformList(inform, ReplyConfig{ + Images: []BootImage{}, + DefaultImage: &fakeImage, }) require.Error(t, err) - _, err = NewReplyForInformSelect(inform, ReplyConfig{ + _, err = NewReplyForInformList(inform, ReplyConfig{ Images: nil, SelectedImage: &fakeImage, }) require.Error(t, err) } -func TestNewReplyForInformSelect(t *testing.T) { +// TODO (get9): clean up when #99 lands. +func TestNewReplyForInformList(t *testing.T) { inform := newInform() images := []BootImage{ BootImage{ @@ -221,12 +368,12 @@ func TestNewReplyForInformSelect(t *testing.T) { } config := ReplyConfig{ Images: images, - SelectedImage: &images[0], + DefaultImage: &images[0], ServerIP: net.IP{9, 9, 9, 9}, ServerHostname: "bsdp.foo.com", ServerPriority: 0x7070, } - ack, err := NewReplyForInformSelect(inform, config) + 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()) @@ -255,11 +402,32 @@ func TestNewReplyForInformSelect(t *testing.T) { vendorOpts := ack.GetOneOption(dhcpv4.OptionVendorSpecificInformation).(*OptVendorSpecificInformation) require.Equal( t, - &OptMessageType{Type: MessageTypeSelect}, + &OptMessageType{Type: MessageTypeList}, vendorOpts.GetOneOption(OptionMessageType).(*OptMessageType), ) require.Equal( t, + &OptServerPriority{Priority: 0x7070}, + vendorOpts.GetOneOption(OptionServerPriority).(*OptServerPriority), + ) + require.Equal( + t, + &OptDefaultBootImageID{ID: images[0].ID}, + vendorOpts.GetOneOption(OptionDefaultBootImageID).(*OptDefaultBootImageID), + ) + require.Equal( + t, + &OptBootImageList{Images: images}, + vendorOpts.GetOneOption(OptionBootImageList).(*OptBootImageList), + ) + + // Add in selected boot image, ensure it's in the generated ACK. + config.SelectedImage = &images[0] + ack, err = NewReplyForInformList(inform, config) + require.NoError(t, err) + vendorOpts = ack.GetOneOption(dhcpv4.OptionVendorSpecificInformation).(*OptVendorSpecificInformation) + require.Equal( + t, &OptSelectedBootImageID{ID: images[0].ID}, vendorOpts.GetOneOption(OptionSelectedBootImageID).(*OptSelectedBootImageID), ) diff --git a/dhcpv4/bsdp/option_vendor_specific_information.go b/dhcpv4/bsdp/option_vendor_specific_information.go index 645f0c8..e735b57 100644 --- a/dhcpv4/bsdp/option_vendor_specific_information.go +++ b/dhcpv4/bsdp/option_vendor_specific_information.go @@ -131,8 +131,8 @@ func (o *OptVendorSpecificInformation) Length() int { return length } -// GetOptions returns all suboptions that match the given OptionCode code. -func (o *OptVendorSpecificInformation) GetOptions(code dhcpv4.OptionCode) []dhcpv4.Option { +// GetOption returns all suboptions that match the given OptionCode code. +func (o *OptVendorSpecificInformation) GetOption(code dhcpv4.OptionCode) []dhcpv4.Option { var opts []dhcpv4.Option for _, opt := range o.Options { if opt.Code() == code { @@ -142,9 +142,9 @@ func (o *OptVendorSpecificInformation) GetOptions(code dhcpv4.OptionCode) []dhcp return opts } -// GetOption returns the first suboption that matches the OptionCode code. +// GetOneOption returns the first suboption that matches the OptionCode code. func (o *OptVendorSpecificInformation) GetOneOption(code dhcpv4.OptionCode) dhcpv4.Option { - opts := o.GetOptions(code) + opts := o.GetOption(code) if len(opts) == 0 { return nil } diff --git a/dhcpv4/bsdp/option_vendor_specific_information_test.go b/dhcpv4/bsdp/option_vendor_specific_information_test.go index bcd28ca..5e7689d 100644 --- a/dhcpv4/bsdp/option_vendor_specific_information_test.go +++ b/dhcpv4/bsdp/option_vendor_specific_information_test.go @@ -71,6 +71,17 @@ func TestParseOptVendorSpecificInformation(t *testing.T) { } o, err = ParseOptVendorSpecificInformation(data) require.Error(t, err) + + // Bad option + data = []byte{ + 43, // code + 7, // length + 1, 1, 1, // List option + 2, 2, 1, // Version option + 5, 3, 1, 1, 1, // Reply port option + } + o, err = ParseOptVendorSpecificInformation(data) + require.Error(t, err) } func TestOptVendorSpecificInformationString(t *testing.T) { @@ -125,7 +136,7 @@ func TestOptVendorSpecificInformationGetOptions(t *testing.T) { &OptVersion{Version1_1}, }, } - foundOpts := o.GetOptions(OptionBootImageList) + foundOpts := o.GetOption(OptionBootImageList) require.Empty(t, foundOpts, "should not get any options") // One option @@ -135,7 +146,7 @@ func TestOptVendorSpecificInformationGetOptions(t *testing.T) { &OptVersion{Version1_1}, }, } - foundOpts = o.GetOptions(OptionMessageType) + foundOpts = o.GetOption(OptionMessageType) require.Equal(t, 1, len(foundOpts), "should only get one option") require.Equal(t, MessageTypeList, foundOpts[0].(*OptMessageType).Type) @@ -147,7 +158,7 @@ func TestOptVendorSpecificInformationGetOptions(t *testing.T) { &OptVersion{Version1_0}, }, } - foundOpts = o.GetOptions(OptionVersion) + foundOpts = o.GetOption(OptionVersion) require.Equal(t, 2, len(foundOpts), "should get two options") require.Equal(t, Version1_1, foundOpts[0].(*OptVersion).Version) require.Equal(t, Version1_0, foundOpts[1].(*OptVersion).Version) |