diff options
Diffstat (limited to 'dhcpv4')
-rw-r--r-- | dhcpv4/bsdp/bsdp.go | 12 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_option_generic_test.go | 10 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_test.go | 169 | ||||
-rw-r--r-- | dhcpv4/bsdp/client.go | 18 | ||||
-rw-r--r-- | dhcpv4/bsdp/option_vendor_specific_information.go | 8 | ||||
-rw-r--r-- | dhcpv4/bsdp/option_vendor_specific_information_test.go | 17 | ||||
-rw-r--r-- | dhcpv4/client.go | 6 | ||||
-rw-r--r-- | dhcpv4/dhcpv4.go | 70 | ||||
-rw-r--r-- | dhcpv4/dhcpv4_test.go | 142 |
9 files changed, 369 insertions, 83 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) diff --git a/dhcpv4/client.go b/dhcpv4/client.go index 9fd8501..8a44338 100644 --- a/dhcpv4/client.go +++ b/dhcpv4/client.go @@ -137,10 +137,14 @@ func (c *Client) Exchange(ifname string, discover *DHCPv4, modifiers ...Modifier 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 5005375..d452f6e 100644 --- a/dhcpv4/dhcpv4.go +++ b/dhcpv4/dhcpv4.go @@ -42,14 +42,11 @@ type DHCPv4 struct { // structures. This is used to simplify packet manipulation 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) { - 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) { @@ -121,23 +118,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{ @@ -151,38 +143,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 } @@ -715,3 +689,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 6e3394c..fe9fe0d 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,17 +454,44 @@ 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)) } // TODO -// test broadcast/unicast flags -// test Options setter/getter // test Summary() and String() |