summaryrefslogtreecommitdiffhomepage
path: root/dhcpv4/bsdp
diff options
context:
space:
mode:
Diffstat (limited to 'dhcpv4/bsdp')
-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
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) {