From 537f1f937790c0bd7826ec6feb6e99fe7ededf58 Mon Sep 17 00:00:00 2001 From: Pablo Mazzini Date: Wed, 22 Aug 2018 17:27:18 +0100 Subject: add OptVendorClass (#148) --- dhcpv6/option_vendorclass.go | 81 +++++++++++++++++++++++++++++++++++++++ dhcpv6/option_vendorclass_test.go | 40 +++++++++++++++++++ dhcpv6/options.go | 34 ++++++++-------- 3 files changed, 139 insertions(+), 16 deletions(-) create mode 100644 dhcpv6/option_vendorclass.go create mode 100644 dhcpv6/option_vendorclass_test.go diff --git a/dhcpv6/option_vendorclass.go b/dhcpv6/option_vendorclass.go new file mode 100644 index 0000000..b920fa7 --- /dev/null +++ b/dhcpv6/option_vendorclass.go @@ -0,0 +1,81 @@ +package dhcpv6 + +import ( + "encoding/binary" + "errors" + "fmt" + "strings" +) + +// OptVendorClass represents a DHCPv6 Vendor Class option +type OptVendorClass struct { + EnterpriseNumber uint32 + Data [][]byte +} + +// Code returns the option code +func (op *OptVendorClass) Code() OptionCode { + return OptionVendorClass +} + +// ToBytes serializes the option and returns it as a sequence of bytes +func (op *OptVendorClass) ToBytes() []byte { + buf := make([]byte, 8) + binary.BigEndian.PutUint16(buf[0:2], uint16(OptionVendorClass)) + binary.BigEndian.PutUint16(buf[2:4], uint16(op.Length())) + binary.BigEndian.PutUint32(buf[4:8], uint32(op.EnterpriseNumber)) + u16 := make([]byte, 2) + for _, data := range op.Data { + binary.BigEndian.PutUint16(u16, uint16(len(data))) + buf = append(buf, u16...) + buf = append(buf, data...) + } + return buf +} + +// Length returns the option length +func (op *OptVendorClass) Length() int { + ret := 0 + for _, data := range op.Data { + ret += 2 + len(data) + } + return 4 + ret +} + +// String returns a string representation of the VendorClass data +func (op *OptVendorClass) String() string { + vcStrings := make([]string, 0) + for _, data := range op.Data { + vcStrings = append(vcStrings, string(data)) + } + return fmt.Sprintf("OptVendorClass{enterprisenum=%d, data=[%s]}", op.EnterpriseNumber, strings.Join(vcStrings, ", ")) +} + +// ParseOptVendorClass builds an OptVendorClass structure from a sequence of +// bytes. The input data does not include option code and length bytes. +func ParseOptVendorClass(data []byte) (*OptVendorClass, error) { + opt := OptVendorClass{} + if len(data) < 4 { + return nil, fmt.Errorf("Invalid vendor opts data length. Expected at least 4 bytes, got %v", len(data)) + } + opt.EnterpriseNumber = binary.BigEndian.Uint32(data[:4]) + data = data[4:] + for { + if len(data) == 0 { + break + } + if len(data) < 2 { + return nil, errors.New("ParseOptVendorClass: short data: missing length field") + } + vcLen := int(binary.BigEndian.Uint16(data[:2])) + if len(data) < vcLen+2 { + return nil, fmt.Errorf("ParseOptVendorClass: short data: less than %d bytes", vcLen+2) + } + opt.Data = append(opt.Data, data[2:vcLen+2]) + data = data[2+vcLen:] + } + if len(opt.Data) < 1 { + return nil, errors.New("ParseOptVendorClass: at least one vendor class data is required") + } + return &opt, nil +} diff --git a/dhcpv6/option_vendorclass_test.go b/dhcpv6/option_vendorclass_test.go new file mode 100644 index 0000000..0db278f --- /dev/null +++ b/dhcpv6/option_vendorclass_test.go @@ -0,0 +1,40 @@ +package dhcpv6 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseOptVendorClass(t *testing.T) { + data := []byte{ + 0xaa, 0xbb, 0xcc, 0xdd, // EnterpriseNumber + 0, 10, 'H', 'T', 'T', 'P', 'C', 'l', 'i', 'e', 'n', 't', + 0, 4, 't', 'e', 's', 't', + } + opt, err := ParseOptVendorClass(data) + require.NoError(t, err) + require.Equal(t, len(opt.Data), 2) + require.Equal(t, opt.EnterpriseNumber, uint32(0xaabbccdd)) + require.Equal(t, []byte("HTTPClient"), opt.Data[0]) + require.Equal(t, []byte("test"), opt.Data[1]) +} + +func TestOptVendorClassToBytes(t *testing.T) { + opt := OptVendorClass{ + EnterpriseNumber: uint32(0xaabbccdd), + Data: [][]byte{ + []byte("HTTPClient"), + []byte("test"), + }, + } + data := opt.ToBytes() + expected := []byte{ + 0, 16, // OptionVendorClass + 0, 22, // length + 0xaa, 0xbb, 0xcc, 0xdd, // EnterpriseNumber + 0, 10, 'H', 'T', 'T', 'P', 'C', 'l', 'i', 'e', 'n', 't', + 0, 4, 't', 'e', 's', 't', + } + require.Equal(t, expected, data) +} diff --git a/dhcpv6/options.go b/dhcpv6/options.go index 0a0bdc1..5c4f321 100644 --- a/dhcpv6/options.go +++ b/dhcpv6/options.go @@ -72,38 +72,40 @@ func ParseOption(dataStart []byte) (Option, error) { opt, err = ParseOptClientId(optData) case OptionServerID: opt, err = ParseOptServerId(optData) - case OptionElapsedTime: - opt, err = ParseOptElapsedTime(optData) + case OptionIANA: + opt, err = ParseOptIANA(optData) + case OptionIAAddr: + opt, err = ParseOptIAAddress(optData) case OptionORO: opt, err = ParseOptRequestedOption(optData) + case OptionElapsedTime: + opt, err = ParseOptElapsedTime(optData) + case OptionRelayMsg: + opt, err = ParseOptRelayMsg(optData) + case OptionStatusCode: + opt, err = ParseOptStatusCode(optData) + case OptionUserClass: + opt, err = ParseOptUserClass(optData) + case OptionVendorClass: + opt, err = ParseOptVendorClass(optData) + case OptionInterfaceID: + opt, err = ParseOptInterfaceId(optData) case OptionDNSRecursiveNameServer: opt, err = ParseOptDNSRecursiveNameServer(optData) case OptionDomainSearchList: opt, err = ParseOptDomainSearchList(optData) - case OptionIANA: - opt, err = ParseOptIANA(optData) case OptionIAPD: opt, err = ParseOptIAForPrefixDelegation(optData) - case OptionIAAddr: - opt, err = ParseOptIAAddress(optData) case OptionIAPrefix: opt, err = ParseOptIAPrefix(optData) - case OptionStatusCode: - opt, err = ParseOptStatusCode(optData) - case OptionRelayMsg: - opt, err = ParseOptRelayMsg(optData) case OptionRemoteID: opt, err = ParseOptRemoteId(optData) - case OptionInterfaceID: - opt, err = ParseOptInterfaceId(optData) + case OptionBootfileURL: + opt, err = ParseOptBootFileURL(optData) case OptionClientArchType: opt, err = ParseOptClientArchType(optData) case OptionNII: opt, err = ParseOptNetworkInterfaceId(optData) - case OptionBootfileURL: - opt, err = ParseOptBootFileURL(optData) - case OptionUserClass: - opt, err = ParseOptUserClass(optData) default: opt = &OptionGeneric{OptionCode: code, OptionData: optData} } -- cgit v1.2.3