summaryrefslogtreecommitdiffhomepage
path: root/dhcpv4/bsdp
diff options
context:
space:
mode:
Diffstat (limited to 'dhcpv4/bsdp')
-rw-r--r--dhcpv4/bsdp/bsdp.go12
-rw-r--r--dhcpv4/bsdp/bsdp_option_generic_test.go10
-rw-r--r--dhcpv4/bsdp/bsdp_test.go169
-rw-r--r--dhcpv4/bsdp/client.go18
-rw-r--r--dhcpv4/bsdp/option_vendor_specific_information.go8
-rw-r--r--dhcpv4/bsdp/option_vendor_specific_information_test.go17
6 files changed, 220 insertions, 14 deletions
diff --git a/dhcpv4/bsdp/bsdp.go b/dhcpv4/bsdp/bsdp.go
index 27de68e..42edf7f 100644
--- a/dhcpv4/bsdp/bsdp.go
+++ b/dhcpv4/bsdp/bsdp.go
@@ -26,7 +26,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...)
}
@@ -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..4cc55a6 100644
--- a/dhcpv4/bsdp/bsdp_test.go
+++ b/dhcpv4/bsdp/bsdp_test.go
@@ -1,9 +1,11 @@
package bsdp
import (
+ "net"
"testing"
"github.com/insomniacslk/dhcp/dhcpv4"
+ "github.com/insomniacslk/dhcp/iana"
"github.com/stretchr/testify/require"
)
@@ -49,3 +51,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)
+ 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)
+}
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..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)