summaryrefslogtreecommitdiffhomepage
path: root/dhcpv4
diff options
context:
space:
mode:
authorSean Karlage <skarlage@fb.com>2018-07-26 22:51:50 -0500
committerSean Karlage <skarlage@fb.com>2018-07-29 10:22:52 -0700
commit76761c89adf7dfd77abc1b601f3a6bac56724686 (patch)
treed3fb832af0ca4487acb244d111797b61fa96bf7e /dhcpv4
parentb30145cf5536cc1a134ad83cfce21f0b7249cf84 (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')
-rw-r--r--dhcpv4/bsdp/bsdp.go10
-rw-r--r--dhcpv4/bsdp/bsdp_option_generic_test.go10
-rw-r--r--dhcpv4/bsdp/bsdp_test.go184
-rw-r--r--dhcpv4/bsdp/client.go18
-rw-r--r--dhcpv4/bsdp/option_vendor_specific_information.go2
-rw-r--r--dhcpv4/bsdp/option_vendor_specific_information_test.go11
-rw-r--r--dhcpv4/client.go6
-rw-r--r--dhcpv4/dhcpv4.go58
-rw-r--r--dhcpv4/dhcpv4_test.go130
9 files changed, 365 insertions, 64 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) {
diff --git a/dhcpv4/client.go b/dhcpv4/client.go
index 1ce6a5c..4b10240 100644
--- a/dhcpv4/client.go
+++ b/dhcpv4/client.go
@@ -137,10 +137,14 @@ func (c *Client) Exchange(ifname string, discover *DHCPv4) ([]DHCPv4, error) {
if err != nil {
return conversation, err
}
+ iface, err := net.InterfaceByName(ifname)
+ if err != nil {
+ return conversation, err
+ }
// Discover
if discover == nil {
- discover, err = NewDiscoveryForInterface(ifname)
+ discover, err = NewDiscovery(iface.HardwareAddr)
if err != nil {
return conversation, err
}
diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go
index 2d73003..c7cf2cf 100644
--- a/dhcpv4/dhcpv4.go
+++ b/dhcpv4/dhcpv4.go
@@ -38,14 +38,11 @@ type DHCPv4 struct {
options []Option
}
-// IPv4AddrsForInterface obtains the currently-configured, non-loopback IPv4
-// addresses for iface.
-func IPv4AddrsForInterface(iface *net.Interface) ([]net.IP, error) {
- addrs, err := iface.Addrs()
+// GetExternalIPv4Addrs obtains the currently-configured, non-loopback IPv4
+// addresses from `addrs` coming from a particular interface (e.g.
+// net.Interface.Addrs).
+func GetExternalIPv4Addrs(addrs []net.Addr) ([]net.IP, error) {
var v4addrs []net.IP
- if err != nil {
- return v4addrs, err
- }
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
@@ -117,23 +114,18 @@ func New() (*DHCPv4, error) {
return &d, nil
}
-// NewDiscoveryForInterface builds a new DHCPv4 Discovery message, with a default
-// Ethernet HW type and the hardware address obtained from the specified
-// interface.
-func NewDiscoveryForInterface(ifname string) (*DHCPv4, error) {
+// NewDiscovery builds a new DHCPv4 Discovery message, with a default Ethernet
+// HW type and specified hardware address.
+func NewDiscovery(hwaddr net.HardwareAddr) (*DHCPv4, error) {
d, err := New()
if err != nil {
return nil, err
}
// get hw addr
- iface, err := net.InterfaceByName(ifname)
- if err != nil {
- return nil, err
- }
d.SetOpcode(OpcodeBootRequest)
d.SetHwType(iana.HwTypeEthernet)
- d.SetHwAddrLen(uint8(len(iface.HardwareAddr)))
- d.SetClientHwAddr(iface.HardwareAddr)
+ d.SetHwAddrLen(uint8(len(hwaddr)))
+ d.SetClientHwAddr(hwaddr)
d.SetBroadcast()
d.AddOption(&OptMessageType{MessageType: MessageTypeDiscover})
d.AddOption(&OptParameterRequestList{
@@ -147,38 +139,20 @@ func NewDiscoveryForInterface(ifname string) (*DHCPv4, error) {
return d, nil
}
-// NewInformForInterface builds a new DHCPv4 Informational message with default
-// Ethernet HW type and the hardware address obtained from the specified
-// interface.
-func NewInformForInterface(ifname string, needsBroadcast bool) (*DHCPv4, error) {
+// NewInform builds a new DHCPv4 Informational message with default Ethernet HW
+// type and specified hardware address. It does NOT put a DHCP End option at the
+// end.
+func NewInform(hwaddr net.HardwareAddr, localIP net.IP) (*DHCPv4, error) {
d, err := New()
if err != nil {
return nil, err
}
- // get hw addr
- iface, err := net.InterfaceByName(ifname)
- if err != nil {
- return nil, err
- }
d.SetOpcode(OpcodeBootRequest)
d.SetHwType(iana.HwTypeEthernet)
- d.SetHwAddrLen(uint8(len(iface.HardwareAddr)))
- d.SetClientHwAddr(iface.HardwareAddr)
-
- if needsBroadcast {
- d.SetBroadcast()
- } else {
- d.SetUnicast()
- }
-
- // Set Client IP as iface's currently-configured IP.
- localIPs, err := IPv4AddrsForInterface(iface)
- if err != nil || len(localIPs) == 0 {
- return nil, fmt.Errorf("could not get local IPs for iface %s", ifname)
- }
- d.SetClientIPAddr(localIPs[0])
-
+ d.SetHwAddrLen(uint8(len(hwaddr)))
+ d.SetClientHwAddr(hwaddr)
+ d.SetClientIPAddr(localIP)
d.AddOption(&OptMessageType{MessageType: MessageTypeInform})
return d, nil
}
diff --git a/dhcpv4/dhcpv4_test.go b/dhcpv4/dhcpv4_test.go
index 55e082d..204ba6a 100644
--- a/dhcpv4/dhcpv4_test.go
+++ b/dhcpv4/dhcpv4_test.go
@@ -14,6 +14,33 @@ func RequireEqualIPAddr(t *testing.T, a, b net.IP, msg ...interface{}) {
}
}
+func RequireHasOption(t *testing.T, packet *DHCPv4, opcode OptionCode) {
+ require.NotNil(t, packet, "packet cannot be nil")
+ packetOpt := packet.GetOneOption(opcode)
+ require.NotNil(t, packetOpt, "option not present in packet")
+}
+
+func TestIPv4AddrsForInterface(t *testing.T) {
+ addrs4and6 := []net.Addr{
+ &net.IPAddr{IP: net.IP{1, 2, 3, 4}},
+ &net.IPAddr{IP: net.IP{4, 3, 2, 1}},
+ &net.IPNet{IP: net.IP{4, 3, 2, 0}},
+ &net.IPAddr{IP: net.IP{1, 2, 3, 4, 1, 1, 1, 1}},
+ &net.IPAddr{IP: net.IP{4, 3, 2, 1, 1, 1, 1, 1}},
+ &net.IPAddr{}, // nil IP
+ &net.IPAddr{IP: net.IP{127, 0, 0, 1}}, // loopback IP
+ }
+
+ expected := []net.IP{
+ net.IP{1, 2, 3, 4},
+ net.IP{4, 3, 2, 1},
+ net.IP{4, 3, 2, 0},
+ }
+ actual, err := GetExternalIPv4Addrs(addrs4and6)
+ require.NoError(t, err)
+ require.Equal(t, expected, actual)
+}
+
func TestFromBytes(t *testing.T) {
data := []byte{
1, // dhcp request
@@ -153,6 +180,8 @@ func TestSettersAndGetters(t *testing.T) {
require.Equal(t, uint8(6), d.HwAddrLen())
d.SetHwAddrLen(12)
require.Equal(t, uint8(12), d.HwAddrLen())
+ d.SetHwAddrLen(22)
+ require.Equal(t, uint8(16), d.HwAddrLen())
// getter/setter for HopCount
require.Equal(t, uint8(3), d.HopCount())
@@ -240,6 +269,8 @@ func TestToStringMethods(t *testing.T) {
require.Equal(t, "Ethernet", d.HwTypeToString())
d.SetHwType(iana.HwTypeARCNET)
require.Equal(t, "ARCNET", d.HwTypeToString())
+ d.SetHwType(iana.HwTypeType(0))
+ require.Equal(t, "Invalid", d.HwTypeToString())
// FlagsToString
d.SetUnicast()
@@ -253,6 +284,8 @@ func TestToStringMethods(t *testing.T) {
d.SetHwAddrLen(6)
d.SetClientHwAddr([]byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
require.Equal(t, "aa:bb:cc:dd:ee:ff", d.ClientHwAddrToString())
+ d.SetClientHwAddr([]byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}) // 20 bytes
+ require.Equal(t, "01:02:03:04:01:02", d.ClientHwAddrToString())
// ServerHostNameToString
d.SetServerHostName([]byte("my.host.local"))
@@ -309,11 +342,11 @@ func TestGetOption(t *testing.T) {
}
hostnameOpt := &OptionGeneric{OptionCode: OptionHostName, Data: []byte("darkstar")}
- bootFileOpt1 := &OptionGeneric{OptionCode: OptionBootfileName, Data: []byte("boot.img")}
- bootFileOpt2 := &OptionGeneric{OptionCode: OptionBootfileName, Data: []byte("boot2.img")}
+ bootFileOpt1 := &OptBootfileName{[]byte("boot.img")}
+ bootFileOpt2 := &OptBootfileName{[]byte("boot2.img")}
d.AddOption(hostnameOpt)
- d.AddOption(bootFileOpt1)
- d.AddOption(bootFileOpt2)
+ d.AddOption(&OptBootfileName{[]byte("boot.img")})
+ d.AddOption(&OptBootfileName{[]byte("boot2.img")})
require.Equal(t, d.GetOption(OptionHostName), []Option{hostnameOpt})
require.Equal(t, d.GetOption(OptionBootfileName), []Option{bootFileOpt1, bootFileOpt2})
@@ -342,15 +375,57 @@ func TestAddOption(t *testing.T) {
require.Equal(t, options[3].Code(), OptionEnd)
}
-func TestDHCPv4RequestFromOffer(t *testing.T) {
+func TestStrippedOptions(t *testing.T) {
+ // Normal set of options that terminate with OptionEnd.
+ d, err := New()
+ require.NoError(t, err)
+ opts := []Option{
+ &OptBootfileName{[]byte("boot.img")},
+ &OptClassIdentifier{"something"},
+ &OptionGeneric{OptionCode: OptionEnd},
+ }
+ d.SetOptions(opts)
+ stripped := d.StrippedOptions()
+ require.Equal(t, len(opts), len(stripped))
+ for i := range stripped {
+ require.Equal(t, opts[i], stripped[i])
+ }
+
+ // Set of options with additional options after OptionEnd
+ opts = append(opts, &OptMaximumDHCPMessageSize{uint16(1234)})
+ d.SetOptions(opts)
+ stripped = d.StrippedOptions()
+ require.Equal(t, len(opts)-1, len(stripped))
+ for i := range stripped {
+ require.Equal(t, opts[i], stripped[i])
+ }
+}
+
+func TestRequestFromOffer(t *testing.T) {
offer, err := New()
require.NoError(t, err)
+ offer.SetBroadcast()
offer.AddOption(&OptMessageType{MessageType: MessageTypeOffer})
- offer.AddOption(&OptServerIdentifier{ServerID: net.IPv4(192, 168, 0, 1)})
req, err := RequestFromOffer(*offer)
+ require.Error(t, err)
+
+ // Now add the option so it doesn't error out.
+ offer.AddOption(&OptServerIdentifier{ServerID: net.IPv4(192, 168, 0, 1)})
+
+ // Broadcast request
+ req, err = RequestFromOffer(*offer)
require.NoError(t, err)
- require.NotEqual(t, (*MessageType)(nil), *req.MessageType())
+ require.NotNil(t, req.MessageType())
require.Equal(t, MessageTypeRequest, *req.MessageType())
+ require.False(t, req.IsUnicast())
+ require.True(t, req.IsBroadcast())
+
+ // Unicast request
+ offer.SetUnicast()
+ req, err = RequestFromOffer(*offer)
+ require.NoError(t, err)
+ require.True(t, req.IsUnicast())
+ require.False(t, req.IsBroadcast())
}
func TestNewReplyFromRequest(t *testing.T) {
@@ -363,20 +438,47 @@ func TestNewReplyFromRequest(t *testing.T) {
require.Equal(t, discover.GatewayIPAddr(), reply.GatewayIPAddr())
}
-func TestDHCPv4MessageTypeNil(t *testing.T) {
+func TestMessageTypeNil(t *testing.T) {
m, err := New()
require.NoError(t, err)
- require.Equal(t, (*MessageType)(nil), m.MessageType())
+ require.Nil(t, m.MessageType())
}
-func TestDHCPv4MessageTypeDiscovery(t *testing.T) {
- m, err := NewDiscoveryForInterface("lo")
+func TestNewDiscovery(t *testing.T) {
+ hwAddr := net.HardwareAddr{1, 2, 3, 4, 5, 6}
+ m, err := NewDiscovery(hwAddr)
require.NoError(t, err)
- require.NotEqual(t, (*MessageType)(nil), m.MessageType())
+ require.NotNil(t, m.MessageType())
require.Equal(t, MessageTypeDiscover, *m.MessageType())
+
+ // Validate fields of DISCOVER packet.
+ require.Equal(t, OpcodeBootRequest, m.Opcode())
+ require.Equal(t, iana.HwTypeEthernet, m.HwType())
+ var expectedHwAddr [16]byte
+ copy(expectedHwAddr[:], hwAddr)
+ require.Equal(t, expectedHwAddr, m.ClientHwAddr())
+ require.Equal(t, len(hwAddr), int(m.HwAddrLen()))
+ require.True(t, m.IsBroadcast())
+ RequireHasOption(t, m, OptionParameterRequestList)
+ RequireHasOption(t, m, OptionEnd)
+}
+
+func TestNewInform(t *testing.T) {
+ hwAddr := net.HardwareAddr{1, 2, 3, 4, 5, 6}
+ localIP := net.IPv4(10, 10, 11, 11)
+ m, err := NewInform(hwAddr, localIP)
+
+ require.NoError(t, err)
+ require.Equal(t, OpcodeBootRequest, m.Opcode())
+ require.Equal(t, iana.HwTypeEthernet, m.HwType())
+ var expectedHwAddr [16]byte
+ copy(expectedHwAddr[:], hwAddr)
+ require.Equal(t, expectedHwAddr, m.ClientHwAddr())
+ require.Equal(t, len(hwAddr), int(m.HwAddrLen()))
+ require.NotNil(t, m.MessageType())
+ require.Equal(t, MessageTypeInform, *m.MessageType())
+ RequireEqualIPAddr(t, localIP, m.ClientIPAddr())
}
// TODO
-// test broadcast/unicast flags
-// test Options setter/getter
// test Summary() and String()