summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorSean Karlage <skarlage@fb.com>2018-08-15 14:52:30 -0700
committerSean Karlage <skarlage@fb.com>2018-08-15 14:52:30 -0700
commitfcd40679d2ed749dccb751167d15b7c6683e9dcb (patch)
treeea9722c6a73947edb7c21ad71c50e45c8c95b077
parentd614fa996b8fb38528191fe52c258037ae9c539a (diff)
parentbcc8c0ec8cd12d2777e9085ffafcbef4c16bf73b (diff)
Merge branch 'master' into bsdp-acks
-rw-r--r--dhcpv4/bsdp/bsdp.go30
-rw-r--r--dhcpv4/bsdp/bsdp_option_generic_test.go10
-rw-r--r--dhcpv4/bsdp/bsdp_test.go280
-rw-r--r--dhcpv4/bsdp/option_vendor_specific_information.go8
-rw-r--r--dhcpv4/bsdp/option_vendor_specific_information_test.go17
-rw-r--r--dhcpv4/dhcpv4.go80
-rw-r--r--dhcpv4/dhcpv4_test.go142
7 files changed, 453 insertions, 114 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)
diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go
index ad6e319..2519e2c 100644
--- a/dhcpv4/dhcpv4.go
+++ b/dhcpv4/dhcpv4.go
@@ -45,11 +45,21 @@ type Modifier func(d *DHCPv4) *DHCPv4
// IPv4AddrsForInterface obtains the currently-configured, non-loopback IPv4
// addresses for iface.
func IPv4AddrsForInterface(iface *net.Interface) ([]net.IP, error) {
+ if iface == nil {
+ return nil, errors.New("IPv4AddrsForInterface: iface cannot be nil")
+ }
addrs, err := iface.Addrs()
- var v4addrs []net.IP
if err != nil {
- return v4addrs, err
+ return nil, err
}
+ return GetExternalIPv4Addrs(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
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
@@ -125,19 +135,25 @@ func New() (*DHCPv4, error) {
// Ethernet HW type and the hardware address obtained from the specified
// interface.
func NewDiscoveryForInterface(ifname string) (*DHCPv4, error) {
- d, err := New()
+ iface, err := net.InterfaceByName(ifname)
if err != nil {
return nil, err
}
- // get hw addr
- iface, err := net.InterfaceByName(ifname)
+ return NewDiscovery(iface.HardwareAddr)
+}
+
+// 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
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{
@@ -155,34 +171,44 @@ func NewDiscoveryForInterface(ifname string) (*DHCPv4, error) {
// Ethernet HW type and the hardware address obtained from the specified
// interface.
func NewInformForInterface(ifname string, needsBroadcast bool) (*DHCPv4, error) {
- d, err := New()
+ // get hw addr
+ iface, err := net.InterfaceByName(ifname)
if err != nil {
return nil, err
}
- // get hw addr
- iface, err := net.InterfaceByName(ifname)
+ // 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)
+ }
+ pkt, err := NewInform(iface.HardwareAddr, localIPs[0])
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()
+ pkt.SetBroadcast()
} else {
- d.SetUnicast()
+ pkt.SetUnicast()
}
+ return pkt, nil
+}
- // 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)
+// 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
}
- d.SetClientIPAddr(localIPs[0])
+ d.SetOpcode(OpcodeBootRequest)
+ d.SetHwType(iana.HwTypeEthernet)
+ d.SetHwAddrLen(uint8(len(hwaddr)))
+ d.SetClientHwAddr(hwaddr)
+ d.SetClientIPAddr(localIP)
d.AddOption(&OptMessageType{MessageType: MessageTypeInform})
return d, nil
}
@@ -728,3 +754,15 @@ func (d *DHCPv4) ToBytes() []byte {
}
return ret
}
+
+// OptionGetter is a interface that knows how to retrieve an option from a
+// structure of options given an OptionCode.
+type OptionGetter interface {
+ GetOption(OptionCode) []Option
+ GetOneOption(OptionCode) Option
+}
+
+// HasOption checks whether the OptionGetter `o` has the given `opcode` Option.
+func HasOption(o OptionGetter, opcode OptionCode) bool {
+ return o.GetOneOption(opcode) != nil
+}
diff --git a/dhcpv4/dhcpv4_test.go b/dhcpv4/dhcpv4_test.go
index eaa9266..28f38d4 100644
--- a/dhcpv4/dhcpv4_test.go
+++ b/dhcpv4/dhcpv4_test.go
@@ -8,10 +8,25 @@ import (
"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 TestGetExternalIPv4Addrs(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) {
@@ -53,9 +68,9 @@ func TestFromBytes(t *testing.T) {
require.Equal(t, d.TransactionID(), uint32(0xaabbccdd))
require.Equal(t, d.NumSeconds(), uint16(3))
require.Equal(t, d.Flags(), uint16(1))
- RequireEqualIPAddr(t, d.ClientIPAddr(), net.IPv4zero)
- RequireEqualIPAddr(t, d.YourIPAddr(), net.IPv4zero)
- RequireEqualIPAddr(t, d.GatewayIPAddr(), net.IPv4zero)
+ require.True(t, d.ClientIPAddr().Equal(net.IPv4zero))
+ require.True(t, d.YourIPAddr().Equal(net.IPv4zero))
+ require.True(t, d.GatewayIPAddr().Equal(net.IPv4zero))
clientHwAddr := d.ClientHwAddr()
require.Equal(t, clientHwAddr[:], []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
hostname := d.ServerHostName()
@@ -153,6 +168,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())
@@ -175,24 +192,24 @@ func TestSettersAndGetters(t *testing.T) {
require.Equal(t, uint16(0), d.Flags())
// getter/setter for ClientIPAddr
- RequireEqualIPAddr(t, net.IPv4(1, 2, 3, 4), d.ClientIPAddr())
+ require.True(t, d.ClientIPAddr().Equal(net.IPv4(1, 2, 3, 4)))
d.SetClientIPAddr(net.IPv4(4, 3, 2, 1))
- RequireEqualIPAddr(t, net.IPv4(4, 3, 2, 1), d.ClientIPAddr())
+ require.True(t, d.ClientIPAddr().Equal(net.IPv4(4, 3, 2, 1)))
// getter/setter for YourIPAddr
- RequireEqualIPAddr(t, net.IPv4(5, 6, 7, 8), d.YourIPAddr())
+ require.True(t, d.YourIPAddr().Equal(net.IPv4(5, 6, 7, 8)))
d.SetYourIPAddr(net.IPv4(8, 7, 6, 5))
- RequireEqualIPAddr(t, net.IPv4(8, 7, 6, 5), d.YourIPAddr())
+ require.True(t, d.YourIPAddr().Equal(net.IPv4(8, 7, 6, 5)))
// getter/setter for ServerIPAddr
- RequireEqualIPAddr(t, net.IPv4(9, 10, 11, 12), d.ServerIPAddr())
+ require.True(t, d.ServerIPAddr().Equal(net.IPv4(9, 10, 11, 12)))
d.SetServerIPAddr(net.IPv4(12, 11, 10, 9))
- RequireEqualIPAddr(t, net.IPv4(12, 11, 10, 9), d.ServerIPAddr())
+ require.True(t, d.ServerIPAddr().Equal(net.IPv4(12, 11, 10, 9)))
// getter/setter for GatewayIPAddr
- RequireEqualIPAddr(t, net.IPv4(13, 14, 15, 16), d.GatewayIPAddr())
+ require.True(t, d.GatewayIPAddr().Equal(net.IPv4(13, 14, 15, 16)))
d.SetGatewayIPAddr(net.IPv4(16, 15, 14, 13))
- RequireEqualIPAddr(t, net.IPv4(16, 15, 14, 13), d.GatewayIPAddr())
+ require.True(t, d.GatewayIPAddr().Equal(net.IPv4(16, 15, 14, 13)))
// getter/setter for ClientHwAddr
hwaddr := d.ClientHwAddr()
@@ -240,6 +257,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 +272,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 +330,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 +363,57 @@ func TestAddOption(t *testing.T) {
require.Equal(t, options[3].Code(), OptionEnd)
}
+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 TestDHCPv4NewRequestFromOffer(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 := NewRequestFromOffer(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 = NewRequestFromOffer(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 = NewRequestFromOffer(offer)
+ require.NoError(t, err)
+ require.True(t, req.IsUnicast())
+ require.False(t, req.IsBroadcast())
}
func TestDHCPv4NewRequestFromOfferWithModifier(t *testing.T) {
@@ -391,14 +454,43 @@ func TestNewReplyFromRequestWithModifier(t *testing.T) {
func TestDHCPv4MessageTypeNil(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())
+ require.True(t, HasOption(m, OptionParameterRequestList))
+ require.True(t, HasOption(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())
+ require.True(t, m.ClientIPAddr().Equal(localIP))
}
func TestIsOptionRequested(t *testing.T) {
@@ -412,6 +504,4 @@ func TestIsOptionRequested(t *testing.T) {
}
// TODO
-// test broadcast/unicast flags
-// test Options setter/getter
// test Summary() and String()