diff options
-rw-r--r-- | dhcpv6/duid_test.go | 88 | ||||
-rw-r--r-- | dhcpv6/option_iaaddress.go | 78 | ||||
-rw-r--r-- | dhcpv6/option_iaaddress_test.go | 69 | ||||
-rw-r--r-- | dhcpv6/option_nontemporaryaddress.go | 10 | ||||
-rw-r--r-- | dhcpv6/options.go | 6 | ||||
-rw-r--r-- | netboot/netconf.go | 6 |
6 files changed, 189 insertions, 68 deletions
diff --git a/dhcpv6/duid_test.go b/dhcpv6/duid_test.go index 23ce709..f28b26b 100644 --- a/dhcpv6/duid_test.go +++ b/dhcpv6/duid_test.go @@ -2,27 +2,93 @@ package dhcpv6 import ( "bytes" + "net" "testing" + + "github.com/insomniacslk/dhcp/iana" + "github.com/stretchr/testify/require" ) -func TestDuidUuid(t *testing.T) { +func TestDuidInvalidTooShort(t *testing.T) { + // too short DUID at all (must be at least 2 bytes) + _, err := DuidFromBytes([]byte{0}) + require.Error(t, err) + + // too short DUID_LL (must be at least 4 bytes) + _, err = DuidFromBytes([]byte{0, 3, 0xa}) + require.Error(t, err) + + // too short DUID_EN (must be at least 6 bytes) + _, err = DuidFromBytes([]byte{0, 2, 0xa, 0xb, 0xc}) + require.Error(t, err) + + // too short DUID_LLT (must be at least 8 bytes) + _, err = DuidFromBytes([]byte{0, 1, 0xa, 0xb, 0xc, 0xd, 0xe}) + require.Error(t, err) + + // too short DUID_UUID (must be at least 18 bytes) + _, err = DuidFromBytes([]byte{0, 4, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}) + require.Error(t, err) +} + +func TestDuidLLTFromBytes(t *testing.T) { buf := []byte{ - 0x00, 0x04, // type - 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, // uuid + 0, 1, // DUID_LLT + 0, 1, // HwTypeEthernet + 0x01, 0x02, 0x03, 0x04, // time + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // link-layer addr } duid, err := DuidFromBytes(buf) - if err != nil { - t.Fatal(err) + require.NoError(t, err) + require.Equal(t, 14, duid.Length()) + require.Equal(t, DUID_LLT, duid.Type) + require.Equal(t, uint32(0x01020304), duid.Time) + require.Equal(t, iana.HwTypeEthernet, duid.HwType) + require.Equal(t, net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, duid.LinkLayerAddr) +} + +func TestDuidLLFromBytes(t *testing.T) { + buf := []byte{ + 0, 3, // DUID_LL + 0, 1, // HwTypeEthernet + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // link-layer addr } - if dt := duid.Type; dt != DUID_UUID { - t.Fatalf("Invalid Preferred Lifetime. Expected 4, got %d", dt) + duid, err := DuidFromBytes(buf) + require.NoError(t, err) + require.Equal(t, 10, duid.Length()) + require.Equal(t, DUID_LL, duid.Type) + require.Equal(t, iana.HwTypeEthernet, duid.HwType) + require.Equal(t, net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, duid.LinkLayerAddr) +} + +func TestDuidUuidFromBytes(t *testing.T) { + buf := []byte{ + 0x00, 0x04, // DUID_UUID } - if uuid := duid.Uuid; !bytes.Equal(uuid, buf[2:]) { - t.Fatalf("Invalid UUID. Expected %v, got %v", buf[2:], uuid) + uuid := []byte{0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08} + buf = append(buf, uuid...) + duid, err := DuidFromBytes(buf) + require.NoError(t, err) + require.Equal(t, 18, duid.Length()) + require.Equal(t, DUID_UUID, duid.Type) + require.Equal(t, uuid, duid.Uuid) +} + +func TestDuidLLTToBytes(t *testing.T) { + expected := []byte{ + 0, 1, // DUID_LLT + 0, 1, // HwTypeEthernet + 0x01, 0x02, 0x03, 0x04, // time + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // link-layer addr } - if mac := duid.LinkLayerAddr; mac != nil { - t.Fatalf("Invalid MAC. Expected nil, got %v", mac) + duid := Duid{ + Type: DUID_LLT, + HwType: iana.HwTypeEthernet, + Time: uint32(0x01020304), + LinkLayerAddr: []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, } + toBytes := duid.ToBytes() + require.Equal(t, expected, toBytes) } func TestDuidUuidToBytes(t *testing.T) { diff --git a/dhcpv6/option_iaaddress.go b/dhcpv6/option_iaaddress.go index 7d0f4a6..1a89a25 100644 --- a/dhcpv6/option_iaaddress.go +++ b/dhcpv6/option_iaaddress.go @@ -9,78 +9,62 @@ import ( "net" ) +// OptIAAddress represents an OPTION_IAADDR type OptIAAddress struct { - ipv6Addr [16]byte - preferredLifetime uint32 - validLifetime uint32 - options []byte + IPv6Addr net.IP + PreferredLifetime uint32 + ValidLifetime uint32 + Options []Option } +// Code returns the option's code func (op *OptIAAddress) Code() OptionCode { return OPTION_IAADDR } +// ToBytes serializes the option and returns it as a sequence of bytes func (op *OptIAAddress) ToBytes() []byte { buf := make([]byte, 28) binary.BigEndian.PutUint16(buf[0:2], uint16(OPTION_IAADDR)) binary.BigEndian.PutUint16(buf[2:4], uint16(op.Length())) - copy(buf[4:20], op.ipv6Addr[:]) - binary.BigEndian.PutUint32(buf[20:24], op.preferredLifetime) - binary.BigEndian.PutUint32(buf[24:28], op.validLifetime) - buf = append(buf, op.options...) + copy(buf[4:20], op.IPv6Addr[:]) + binary.BigEndian.PutUint32(buf[20:24], op.PreferredLifetime) + binary.BigEndian.PutUint32(buf[24:28], op.ValidLifetime) + for _, opt := range op.Options { + buf = append(buf, opt.ToBytes()...) + } return buf } -func (op *OptIAAddress) IPv6Addr() []byte { - return op.ipv6Addr[:] -} - -func (op *OptIAAddress) SetIPv6Addr(addr [16]byte) { - op.ipv6Addr = addr -} - -func (op *OptIAAddress) PreferredLifetime() uint32 { - return op.preferredLifetime -} - -func (op *OptIAAddress) SetPreferredLifetime(pl uint32) { - op.preferredLifetime = pl -} - -func (op *OptIAAddress) ValidLifetime() uint32 { - return op.validLifetime -} - -func (op *OptIAAddress) SetValidLifetime(vl uint32) { - op.validLifetime = vl -} -func (op *OptIAAddress) Options() []byte { - return op.options -} - -func (op *OptIAAddress) SetOptions(options []byte) { - op.options = options -} - +// Length returns the option length func (op *OptIAAddress) Length() int { - return 24 + len(op.options) + opLen := 24 + for _, opt := range op.Options { + opLen += 4 + opt.Length() + } + return opLen } func (op *OptIAAddress) String() string { return fmt.Sprintf("OptIAAddress{ipv6addr=%v, preferredlifetime=%v, validlifetime=%v, options=%v}", - net.IP(op.ipv6Addr[:]), op.preferredLifetime, op.validLifetime, op.options) + net.IP(op.IPv6Addr[:]), op.PreferredLifetime, op.ValidLifetime, op.Options) } -// build an OptIAAddress structure from a sequence of bytes. -// The input data does not include option code and length bytes. +// ParseOptIAAddress builds an OptIAAddress structure from a sequence +// of bytes. The input data does not include option code and length +// bytes. func ParseOptIAAddress(data []byte) (*OptIAAddress, error) { + var err error opt := OptIAAddress{} if len(data) < 24 { return nil, fmt.Errorf("Invalid IA Address data length. Expected at least 24 bytes, got %v", len(data)) } - copy(opt.ipv6Addr[:], data[:16]) - opt.preferredLifetime = binary.BigEndian.Uint32(data[16:20]) - opt.validLifetime = binary.BigEndian.Uint32(data[20:24]) - copy(opt.options, data[24:]) + opt.IPv6Addr = net.IP(data[:16]) + opt.PreferredLifetime = binary.BigEndian.Uint32(data[16:20]) + opt.ValidLifetime = binary.BigEndian.Uint32(data[20:24]) + opt.Options, err = OptionsFromBytes(data[24:]) + if err != nil { + return nil, err + } return &opt, nil } diff --git a/dhcpv6/option_iaaddress_test.go b/dhcpv6/option_iaaddress_test.go new file mode 100644 index 0000000..ad4f69c --- /dev/null +++ b/dhcpv6/option_iaaddress_test.go @@ -0,0 +1,69 @@ +package dhcpv6 + +import ( + "net" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOptIAAddressParse(t *testing.T) { + ipaddr := []byte{0x24, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} + data := append(ipaddr, []byte{ + 0xa, 0xb, 0xc, 0xd, // preferred lifetime + 0xe, 0xf, 0x1, 0x2, // valid lifetime + 0, 8, 0, 2, 0xaa, 0xbb, // options + }...) + opt, err := ParseOptIAAddress(data) + require.NoError(t, err) + require.Equal(t, 30, opt.Length()) + require.Equal(t, net.IP(ipaddr), opt.IPv6Addr) + require.Equal(t, uint32(0x0a0b0c0d), opt.PreferredLifetime) + require.Equal(t, uint32(0x0e0f0102), opt.ValidLifetime) +} + +func TestOptIAAddressParseInvalidTooShort(t *testing.T) { + data := []byte{ + 0x24, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0xa, 0xb, 0xc, 0xd, // preferred lifetime + // truncated here + } + _, err := ParseOptIAAddress(data) + require.Error(t, err) +} + +func TestOptIAAddressParseInvalidBrokenOptions(t *testing.T) { + data := []byte{ + 0x24, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0xa, 0xb, 0xc, 0xd, // preferred lifetime + 0xe, 0xf, 0x1, 0x2, // valid lifetime + 0, 8, 0, 2, 0xaa, // broken options + } + _, err := ParseOptIAAddress(data) + require.Error(t, err) +} + +func TestOptIAAddressToBytes(t *testing.T) { + expected := []byte{ + 0, 5, // OPTION_IAADDR + 0, 30, // length + } + ipBytes := []byte{0x24, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} + expected = append(expected, ipBytes...) + expected = append(expected, []byte{ + 0xa, 0xb, 0xc, 0xd, // preferred lifetime + 0xe, 0xf, 0x1, 0x2, // valid lifetime + 0, 8, 0, 2, 0xaa, 0xbb, // options + }...) + opt := OptIAAddress{ + IPv6Addr: net.IP(ipBytes), + PreferredLifetime: 0x0a0b0c0d, + ValidLifetime: 0x0e0f0102, + Options: []Option{ + &OptElapsedTime{ + ElapsedTime: 0xaabb, + }, + }, + } + require.Equal(t, expected, opt.ToBytes()) +} diff --git a/dhcpv6/option_nontemporaryaddress.go b/dhcpv6/option_nontemporaryaddress.go index e303ae7..cefca64 100644 --- a/dhcpv6/option_nontemporaryaddress.go +++ b/dhcpv6/option_nontemporaryaddress.go @@ -48,6 +48,7 @@ func (op *OptIANA) String() string { // build an OptIANA structure from a sequence of bytes. // The input data does not include option code and length bytes. func ParseOptIANA(data []byte) (*OptIANA, error) { + var err error opt := OptIANA{} if len(data) < 12 { return nil, fmt.Errorf("Invalid IA for Non-temporary Addresses data length. Expected at least 12 bytes, got %v", len(data)) @@ -55,12 +56,9 @@ func ParseOptIANA(data []byte) (*OptIANA, error) { copy(opt.IaId[:], data[:4]) opt.T1 = binary.BigEndian.Uint32(data[4:8]) opt.T2 = binary.BigEndian.Uint32(data[8:12]) - var err error - if len(data[12:]) > 0 { - opt.Options, err = OptionsFromBytes(data[12:]) - if err != nil { - return nil, err - } + opt.Options, err = OptionsFromBytes(data[12:]) + if err != nil { + return nil, err } return &opt, nil } diff --git a/dhcpv6/options.go b/dhcpv6/options.go index 3ee5dad..d64d1d9 100644 --- a/dhcpv6/options.go +++ b/dhcpv6/options.go @@ -120,11 +120,15 @@ func ParseOption(dataStart []byte) (Option, error) { func OptionsFromBytes(data []byte) ([]Option, error) { // Parse a sequence of bytes until the end and build a list of options from // it. Returns an error if any invalid option or length is found. + options := make([]Option, 0, 10) + if len(data) == 0 { + // no options, no party + return options, nil + } if len(data) < 4 { // cannot be shorter than option code (2 bytes) + length (2 bytes) return nil, fmt.Errorf("Invalid options: shorter than 4 bytes") } - options := make([]Option, 0, 10) idx := 0 for { if idx == len(data) { diff --git a/netboot/netconf.go b/netboot/netconf.go index a9751dd..f7a117c 100644 --- a/netboot/netconf.go +++ b/netboot/netconf.go @@ -47,11 +47,11 @@ func GetNetConfFromPacketv6(d *dhcpv6.DHCPv6Message) (*NetConf, error) { for _, iaaddr := range iaaddrs { netconf.Addresses = append(netconf.Addresses, AddrConf{ IPNet: net.IPNet{ - IP: iaaddr.IPv6Addr(), + IP: iaaddr.IPv6Addr, Mask: netmask, }, - PreferredLifetime: int(iaaddr.PreferredLifetime()), - ValidLifetime: int(iaaddr.ValidLifetime()), + PreferredLifetime: int(iaaddr.PreferredLifetime), + ValidLifetime: int(iaaddr.ValidLifetime), }) } // get DNS configuration |