diff options
author | Sean Karlage <skarlage@fb.com> | 2018-07-26 22:51:50 -0500 |
---|---|---|
committer | Sean Karlage <skarlage@fb.com> | 2018-07-29 10:22:52 -0700 |
commit | 76761c89adf7dfd77abc1b601f3a6bac56724686 (patch) | |
tree | d3fb832af0ca4487acb244d111797b61fa96bf7e /dhcpv4/bsdp | |
parent | b30145cf5536cc1a134ad83cfce21f0b7249cf84 (diff) |
DHCPv4: Refactor methods for easier testing
This refactors the input parameters for construction DISCOVER/INFORM*
packets so that it's easier to write unit tests for DHCPv4 and BSDP
methods. It also adds a bunch of unit tests for both packages and rounds out
their test coverage.
Diffstat (limited to 'dhcpv4/bsdp')
-rw-r--r-- | dhcpv4/bsdp/bsdp.go | 10 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_generic_test.go | 10 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_test.go | 184 | ||||
-rw-r--r-- | dhcpv4/bsdp/client.go | 18 | ||||
-rw-r--r-- | dhcpv4/bsdp/option_vendor_specific_information.go | 2 | ||||
-rw-r--r-- | dhcpv4/bsdp/option_vendor_specific_information_test.go | 11 |
6 files changed, 228 insertions, 7 deletions
diff --git a/dhcpv4/bsdp/bsdp.go b/dhcpv4/bsdp/bsdp.go index 27de68e..22360ef 100644 --- a/dhcpv4/bsdp/bsdp.go +++ b/dhcpv4/bsdp/bsdp.go @@ -37,10 +37,10 @@ func needsReplyPort(replyPort uint16) bool { return replyPort != 0 && replyPort != dhcpv4.ClientPort } -// 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) +// 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 } @@ -77,7 +77,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 ad2a265..da82d43 100644 --- a/dhcpv4/bsdp/bsdp_test.go +++ b/dhcpv4/bsdp/bsdp_test.go @@ -1,12 +1,29 @@ package bsdp import ( + "net" "testing" "github.com/insomniacslk/dhcp/dhcpv4" + "github.com/insomniacslk/dhcp/iana" "github.com/stretchr/testify/require" ) +func RequireEqualIPAddr(t *testing.T, a, b net.IP, msg ...interface{}) { + if !net.IP.Equal(a, b) { + t.Fatalf("Invalid %s. %v != %v", msg, a, b) + } +} + +func RequireHasOption(t *testing.T, opts []dhcpv4.Option, opcode dhcpv4.OptionCode) { + for _, opt := range opts { + if opt.Code() == opcode { + return + } + } + require.FailNow(t, "option not present in opts", dhcpv4.OptionCodeToString[opcode]) +} + func TestParseBootImageListFromAck(t *testing.T) { expectedBootImages := []BootImage{ BootImage{ @@ -49,3 +66,170 @@ func TestNeedsReplyPort(t *testing.T) { 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) + RequireHasOption(t, m.Options(), dhcpv4.OptionVendorSpecificInformation) + RequireHasOption(t, m.Options(), dhcpv4.OptionParameterRequestList) + RequireHasOption(t, m.Options(), dhcpv4.OptionMaximumDHCPMessageSize) + RequireHasOption(t, m.Options(), dhcpv4.OptionEnd) + + opt := m.GetOneOption(dhcpv4.OptionVendorSpecificInformation) + require.NotNil(t, opt, "vendor opts not present") + vendorInfo := opt.(*OptVendorSpecificInformation) + RequireHasOption(t, vendorInfo.Options, OptionMessageType) + RequireHasOption(t, vendorInfo.Options, 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) + RequireHasOption(t, vendorInfo.Options, 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. + RequireHasOption(t, m.Options(), dhcpv4.OptionClassIdentifier) + RequireHasOption(t, m.Options(), dhcpv4.OptionParameterRequestList) + RequireHasOption(t, m.Options(), dhcpv4.OptionDHCPMessageType) + opt := m.GetOneOption(dhcpv4.OptionDHCPMessageType) + require.Equal(t, dhcpv4.MessageTypeInform, opt.(*dhcpv4.OptMessageType).MessageType) + RequireHasOption(t, m.Options(), dhcpv4.OptionEnd) + + // Validate vendor opts. + RequireHasOption(t, m.Options(), dhcpv4.OptionVendorSpecificInformation) + opt = m.GetOneOption(dhcpv4.OptionVendorSpecificInformation) + vendorInfo := opt.(*OptVendorSpecificInformation) + RequireHasOption(t, vendorInfo.Options, OptionMessageType) + opt = vendorInfo.GetOneOption(OptionMessageType) + require.Equal(t, MessageTypeSelect, opt.(*OptMessageType).Type) + RequireHasOption(t, vendorInfo.Options, OptionVersion) + RequireHasOption(t, vendorInfo.Options, OptionSelectedBootImageID) + opt = vendorInfo.GetOneOption(OptionSelectedBootImageID) + require.Equal(t, bootImage.ID, opt.(*OptSelectedBootImageID).ID) + RequireHasOption(t, vendorInfo.Options, OptionServerIdentifier) + opt = vendorInfo.GetOneOption(OptionServerIdentifier) + RequireEqualIPAddr(t, serverID, 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) + + RequireHasOption(t, m.Options(), dhcpv4.OptionVendorSpecificInformation) + opt := m.GetOneOption(dhcpv4.OptionVendorSpecificInformation) + vendorInfo := opt.(*OptVendorSpecificInformation) + RequireHasOption(t, vendorInfo.Options, OptionReplyPort) + opt = vendorInfo.GetOneOption(OptionReplyPort) + require.Equal(t, replyPort, opt.(*OptReplyPort).Port) +} diff --git a/dhcpv4/bsdp/client.go b/dhcpv4/bsdp/client.go index 3d885aa..2683e2a 100644 --- a/dhcpv4/bsdp/client.go +++ b/dhcpv4/bsdp/client.go @@ -2,6 +2,8 @@ package bsdp import ( "errors" + "fmt" + "net" "github.com/insomniacslk/dhcp/dhcpv4" ) @@ -49,10 +51,24 @@ func (c *Client) Exchange(ifname string, informList *dhcpv4.DHCPv4) ([]dhcpv4.DH if err != nil { return conversation, err } + iface, err := net.InterfaceByName(ifname) + if err != nil { + return conversation, err + } + + // Get currently configured IP. + addrs, err := iface.Addrs() + if err != nil { + return conversation, err + } + localIPs, err := dhcpv4.GetExternalIPv4Addrs(addrs) + if err != nil { + return conversation, fmt.Errorf("could not get local IPv4 addr for %s: %v", iface.Name, err) + } // INFORM[LIST] if informList == nil { - informList, err = NewInformListForInterface(ifname, dhcpv4.ClientPort) + informList, err = NewInformList(iface.HardwareAddr, localIPs[0], dhcpv4.ClientPort) if err != nil { return conversation, err } diff --git a/dhcpv4/bsdp/option_vendor_specific_information.go b/dhcpv4/bsdp/option_vendor_specific_information.go index 645f0c8..5c8533e 100644 --- a/dhcpv4/bsdp/option_vendor_specific_information.go +++ b/dhcpv4/bsdp/option_vendor_specific_information.go @@ -142,7 +142,7 @@ 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) if len(opts) == 0 { diff --git a/dhcpv4/bsdp/option_vendor_specific_information_test.go b/dhcpv4/bsdp/option_vendor_specific_information_test.go index bcd28ca..9827618 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) { |