summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorEmanuele Fia <name29@fb.com>2022-03-31 20:38:34 +0100
committerEmanuele Fia <name29@fb.com>2022-03-31 20:38:34 +0100
commit07cc76ec259f5fff3e81e11b340167a690bca9a1 (patch)
treec00d41eb17168b1ff9498f9a12a79db6aa6e2625
parent3c283ff8b7dd3a8ea2dbc37d13a35bba7aab00e5 (diff)
Extending support for more interface types in parse_circuit_id
Adding support for DHCPv6 for Ciena Signed-off-by: Emanuele Fia <name29@fb.com>
-rw-r--r--dhcpv6/dhcpv6.go4
-rw-r--r--dhcpv6/dhcpv6_test.go25
-rw-r--r--dhcpv6/option_userclass.go6
-rw-r--r--dhcpv6/option_userclass_test.go10
-rw-r--r--dhcpv6/options.go6
-rw-r--r--dhcpv6/ztpv6/parse_remote_id.go78
-rw-r--r--dhcpv6/ztpv6/parse_remote_id_test.go53
-rw-r--r--dhcpv6/ztpv6/parse_vendor_options.go24
-rw-r--r--dhcpv6/ztpv6/parse_vendor_options_test.go48
9 files changed, 212 insertions, 42 deletions
diff --git a/dhcpv6/dhcpv6.go b/dhcpv6/dhcpv6.go
index 59d9733..31e6abf 100644
--- a/dhcpv6/dhcpv6.go
+++ b/dhcpv6/dhcpv6.go
@@ -75,7 +75,7 @@ func RelayMessageFromBytes(data []byte) (*RelayMessage, error) {
}
// TODO: fail if no OptRelayMessage is present.
if err := d.Options.FromBytes(buf.Data()); err != nil {
- return nil, err
+ return nil, fmt.Errorf("Unable to parse Options: %v", err)
}
return d, nil
}
@@ -85,7 +85,7 @@ func FromBytes(data []byte) (DHCPv6, error) {
buf := uio.NewBigEndianBuffer(data)
messageType := MessageType(buf.Read8())
if buf.Error() != nil {
- return nil, buf.Error()
+ return nil, fmt.Errorf("Unable to parse MessageType: %v", buf.Error())
}
if messageType == MessageTypeRelayForward || messageType == MessageTypeRelayReply {
diff --git a/dhcpv6/dhcpv6_test.go b/dhcpv6/dhcpv6_test.go
index b1f952d..cd235f1 100644
--- a/dhcpv6/dhcpv6_test.go
+++ b/dhcpv6/dhcpv6_test.go
@@ -133,6 +133,31 @@ func TestFromAndToBytes(t *testing.T) {
expected := [][]byte{
{01, 0xab, 0xcd, 0xef, 0x00, 0x00, 0x00, 0x00},
[]byte("0000\x00\x01\x00\x0e\x00\x01000000000000"),
+ {
+ 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x09, 0x00, 0x72, 0x01, 0x00,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x12, 0x00, 0x00,
+ 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x44, 0x48,
+ 0x00, 0x06, 0x00, 0x0a, 0x00, 0x3b, 0x00, 0x17,
+ 0x00, 0x02, 0x00, 0x0c, 0x00, 0x38, 0x00, 0x08,
+ 0x00, 0x02, 0xff, 0xff, 0x00, 0x0f, 0x00, 0x15,
+ 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x2e, 0x30, 0x30, 0x35, 0x32, 0x00, 0x10, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0x11, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x03, 0x00, 0x0c, 0x1a, 0x8e, 0xee, 0x04,
+ 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x15, 0x18,
+ 0x00, 0x35, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00},
}
t.Parallel()
for i, packet := range expected {
diff --git a/dhcpv6/option_userclass.go b/dhcpv6/option_userclass.go
index e5d7b31..2900d2a 100644
--- a/dhcpv6/option_userclass.go
+++ b/dhcpv6/option_userclass.go
@@ -50,5 +50,9 @@ func ParseOptUserClass(data []byte) (*OptUserClass, error) {
len := buf.Read16()
opt.UserClasses = append(opt.UserClasses, buf.CopyN(int(len)))
}
- return &opt, buf.FinError()
+ var err = buf.FinError()
+ if err != nil {
+ err = fmt.Errorf("Unable to parse OptUserClass: %v", err)
+ }
+ return &opt, err
}
diff --git a/dhcpv6/option_userclass_test.go b/dhcpv6/option_userclass_test.go
index 16fae72..562ee74 100644
--- a/dhcpv6/option_userclass_test.go
+++ b/dhcpv6/option_userclass_test.go
@@ -91,3 +91,13 @@ func TestOptUserClassString(t *testing.T) {
"String() should contain the list of user classes",
)
}
+
+func TestOptUserClassParseOptUserClassCiena(t *testing.T) {
+ buf := []byte{
+ 0x00, 0x13, 0x31, 0x2f, 0x52,
+ 0x4c, 0x53, 0x2d, 0x30, 0x32, 0x2e, 0x30, 0x34,
+ 0x2e, 0x30, 0x32, 0x2e, 0x30, 0x30, 0x35, 0x32,
+ }
+ _, err := ParseOptUserClass(buf)
+ require.NoError(t, err)
+}
diff --git a/dhcpv6/options.go b/dhcpv6/options.go
index f8445bb..bf523dd 100644
--- a/dhcpv6/options.go
+++ b/dhcpv6/options.go
@@ -208,10 +208,12 @@ func (o *Options) FromBytesWithParser(data []byte, parser OptionParser) error {
// Consume, but do not Copy. Each parser will make a copy of
// pertinent data.
optData := buf.Consume(length)
-
+ if err := buf.Error(); err != nil {
+ return fmt.Errorf("Unable to read payload of option code %d with option length %d: %v", code, length, err)
+ }
opt, err := parser(code, optData)
if err != nil {
- return err
+ return fmt.Errorf("Unable to parse option code %d with option length %d : %v [data %x]", code, length, err, optData)
}
*o = append(*o, opt)
}
diff --git a/dhcpv6/ztpv6/parse_remote_id.go b/dhcpv6/ztpv6/parse_remote_id.go
index 9dd72b1..8b1488a 100644
--- a/dhcpv6/ztpv6/parse_remote_id.go
+++ b/dhcpv6/ztpv6/parse_remote_id.go
@@ -8,12 +8,32 @@ import (
"github.com/insomniacslk/dhcp/dhcpv6"
)
-var (
+var circuitRegexs = []*regexp.Regexp{
// Arista Port, Vlan Pattern
- aristaPVPattern = regexp.MustCompile("Ethernet(?P<port>[0-9]+):(?P<vlan>[0-9]+)")
+ regexp.MustCompile("Ethernet(?P<port>[0-9]+):(?P<vlan>[0-9]+)"),
// Arista Slot, Mod, Port Pattern
- aristaSMPPattern = regexp.MustCompile("Ethernet(?P<slot>[0-9]+)/(?P<module>[0-9]+)/(?P<port>[0-9]+)")
-)
+ regexp.MustCompile("Ethernet(?P<slot>[0-9]+)/(?P<mod>[0-9]+)/(?P<port>[0-9]+)"),
+ // Juniper QFX et-0/0/0:0.0 and xe-0/0/0:0.0
+ regexp.MustCompile("^(et|xe)-(?P<slot>[0-9]+)/(?P<mod>[0-9]+)/(?P<port>[0-9]+):(?P<subport>[0-9]+).*$"),
+ // Juniper PTX et-0/0/0.0
+ regexp.MustCompile("^et-(?P<slot>[0-9]+)/(?P<mod>[0-9]+)/(?P<port>[0-9]+).(?P<subport>[0-9]+)$"),
+ // Juniper EX ge-0/0/0.0
+ regexp.MustCompile("^ge-(?P<slot>[0-9]+)/(?P<mod>[0-9]+)/(?P<port>[0-9]+).(?P<subport>[0-9]+).*"),
+ // Arista Ethernet3/17/1
+ // Sometimes Arista prepend circuit id type(1 byte) and length(1 byte) not using ^
+ regexp.MustCompile("Ethernet(?P<slot>[0-9]+)/(?P<mod>[0-9]+)/(?P<port>[0-9]+)$"),
+ // Juniper QFX et-1/0/61
+ regexp.MustCompile("^et-(?P<slot>[0-9]+)/(?P<mod>[0-9]+)/(?P<port>[0-9]+)$"),
+ // Arista Ethernet14:Vlan2001
+ // Arista Ethernet10:2020
+ regexp.MustCompile("Ethernet(?P<port>[0-9]+):(?P<vlan>.*)$"),
+ // Cisco Gi1/10:2020
+ regexp.MustCompile("^Gi(?P<slot>[0-9]+)/(?P<port>[0-9]+):(?P<vlan>.*)$"),
+ // Nexus Ethernet1/3
+ regexp.MustCompile("^Ethernet(?P<slot>[0-9]+)/(?P<port>[0-9]+)$"),
+ // Juniper bundle interface ae52.0
+ regexp.MustCompile("^ae(?P<port>[0-9]+).(?P<subport>[0-9])$"),
+}
// CircuitID represents the structure of network vendor interface formats
type CircuitID struct {
@@ -53,38 +73,32 @@ func ParseRemoteID(packet dhcpv6.DHCPv6) (*CircuitID, error) {
}
func matchCircuitId(circuitInfo string) (*CircuitID, error) {
- var names, matches []string
+ for _, re := range circuitRegexs {
- switch {
- case aristaPVPattern.MatchString(circuitInfo):
- matches = aristaPVPattern.FindStringSubmatch(circuitInfo)
- names = aristaPVPattern.SubexpNames()
- case aristaSMPPattern.MatchString(circuitInfo):
- matches = aristaSMPPattern.FindStringSubmatch(circuitInfo)
- names = aristaSMPPattern.SubexpNames()
- }
-
- if len(matches) == 0 {
- return nil, fmt.Errorf("no circuitId regex matches for %v", circuitInfo)
- }
+ match := re.FindStringSubmatch(circuitInfo)
+ if len(match) == 0 {
+ continue
+ }
- var circuit CircuitID
- for i, match := range matches {
- switch names[i] {
- case "port":
- circuit.Port = match
- case "slot":
- circuit.Slot = match
- case "module":
- circuit.Module = match
- case "subport":
- circuit.SubPort = match
- case "vlan":
- circuit.Vlan = match
+ c := CircuitID{}
+ for i, k := range re.SubexpNames() {
+ switch k {
+ case "slot":
+ c.Slot = match[i]
+ case "mod":
+ c.Module = match[i]
+ case "port":
+ c.Port = match[i]
+ case "subport":
+ c.SubPort = match[i]
+ case "vlan":
+ c.Vlan = match[i]
+ }
}
- }
- return &circuit, nil
+ return &c, nil
+ }
+ return nil, fmt.Errorf("Unable to match circuit id : %s with listed regexes of interface types", circuitInfo)
}
// FormatCircuitID is the CircuitID format we send in our Bootfile URL for ZTP devices
diff --git a/dhcpv6/ztpv6/parse_remote_id_test.go b/dhcpv6/ztpv6/parse_remote_id_test.go
index 6147cec..8d17c80 100644
--- a/dhcpv6/ztpv6/parse_remote_id_test.go
+++ b/dhcpv6/ztpv6/parse_remote_id_test.go
@@ -15,9 +15,20 @@ func TestCircuitID(t *testing.T) {
fail bool
}{
{name: "Bogus string", circuit: "ope/1/2/3:ope", fail: true, want: nil},
- {name: "Arista Port Vlan Pattern", circuit: "Ethernet13:2001", want: &CircuitID{Port: "13", Vlan: "2001"}},
+ {name: "Bogus test", circuit: "bogusInterface", fail: true, want: nil},
+ {name: "juniperQFX pattern et", circuit: "et-0/0/0:0.0", want: &CircuitID{Slot: "0", Module: "0", Port: "0", SubPort: "0"}},
+ {name: "juniperQFX pattern xe", circuit: "xe-0/0/14:2", want: &CircuitID{Slot: "0", Module: "0", Port: "14", SubPort: "2"}},
+ {name: "juniperPTX pattern", circuit: "et-0/0/0.0", want: &CircuitID{Slot: "0", Module: "0", Port: "0", SubPort: "0"}},
+ {name: "Juniper QFX pattern", circuit: "et-1/0/61", want: &CircuitID{Slot: "1", Module: "0", Port: "61"}},
+ {name: "Arista Vlan pattern 1", circuit: "Ethernet14:Vlan2001", want: &CircuitID{Port: "14", Vlan: "Vlan2001"}},
+ {name: "Arista Vlan pattern 2", circuit: "Ethernet10:2020", want: &CircuitID{Port: "10", Vlan: "2020"}},
{name: "Arista Slot Module Port Pattern", circuit: "Ethernet1/3/4", want: &CircuitID{Slot: "1", Module: "3", Port: "4"}},
{name: "Arista Slot Module Port Pattern InterfaceID", circuit: "Ethernet1/3/4:default", want: &CircuitID{Slot: "1", Module: "3", Port: "4"}},
+ {name: "Cisco pattern", circuit: "Gi1/10:2020", want: &CircuitID{Slot: "1", Port: "10", Vlan: "2020"}},
+ {name: "Cisco Nexus pattern", circuit: "Ethernet1/3", want: &CircuitID{Slot: "1", Port: "3"}},
+ {name: "Juniper Bundle Pattern", circuit: "ae52.0", want: &CircuitID{Port: "52", SubPort: "0"}},
+ {name: "Arista Vlan pattern 1 with circuitid type and length", circuit: "\x00\x0fEthernet14:2001", want: &CircuitID{Port: "14", Vlan: "2001"}},
+ {name: "juniperEX pattern", circuit: "ge-0/0/0.0:RANDOMCHAR", want: &CircuitID{Slot: "0", Module: "0", Port: "0", SubPort: "0"}},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
@@ -39,8 +50,16 @@ func TestFormatCircuitID(t *testing.T) {
want string
}{
{name: "empty", circuit: &CircuitID{}, want: ",,,,"},
- {name: "Arista format Port/Vlan", circuit: &CircuitID{Port: "13", Vlan: "2001"}, want: ",,13,,2001"},
- {name: "Arista format Slot/Module/Port", circuit: &CircuitID{Slot: "1", Module: "3", Port: "4"}, want: "1,3,4,,"},
+ {name: "empty", circuit: &CircuitID{}, want: ",,,,"},
+ {name: "juniperQFX pattern et", circuit: &CircuitID{Slot: "0", Module: "0", Port: "0", SubPort: "0"}, want: "0,0,0,0,"},
+ {name: "juniperQFX pattern xe", circuit: &CircuitID{Slot: "0", Module: "0", Port: "14", SubPort: "2"}, want: "0,0,14,2,"},
+ {name: "juniperPTX pattern", circuit: &CircuitID{Slot: "0", Module: "0", Port: "0"}, want: "0,0,0,,"},
+ {name: "Arista pattern", circuit: &CircuitID{Slot: "3", Module: "17", Port: "1"}, want: "3,17,1,,"},
+ {name: "Juniper QFX pattern", circuit: &CircuitID{Slot: "1", Module: "0", Port: "61"}, want: "1,0,61,,"},
+ {name: "Arista Vlan pattern 1", circuit: &CircuitID{Port: "14", Vlan: "Vlan2001"}, want: ",,14,,Vlan2001"},
+ {name: "Arista Vlan pattern 2", circuit: &CircuitID{Port: "10", Vlan: "2020"}, want: ",,10,,2020"},
+ {name: "Cisco Nexus pattern", circuit: &CircuitID{Slot: "1", Port: "3"}, want: "1,,3,,"},
+ {name: "Juniper Bundle Pattern", circuit: &CircuitID{Port: "52", SubPort: "0"}, want: ",,52,0,"},
}
for _, tc := range tt {
@@ -60,8 +79,19 @@ func TestParseRemoteID(t *testing.T) {
fail bool
}{
{name: "Bogus string", circuit: []byte("ope/1/2/3:ope.1"), fail: true, want: nil},
- {name: "Arista Port Vlan Pattern", circuit: []byte("Ethernet13:2001"), want: &CircuitID{Port: "13", Vlan: "2001"}},
- {name: "Arista Slot Module Port Pattern", circuit: []byte("Ethernet1/3/4"), want: &CircuitID{Slot: "1", Module: "3", Port: "4"}},
+ {name: "Bogus test", circuit: []byte("bogusInterface"), fail: true, want: nil},
+ {name: "juniperQFX pattern et", circuit: []byte("et-0/0/0:0.0"), want: &CircuitID{Slot: "0", Module: "0", Port: "0", SubPort: "0"}},
+ {name: "juniperQFX pattern xe", circuit: []byte("xe-0/0/14:2"), want: &CircuitID{Slot: "0", Module: "0", Port: "14", SubPort: "2"}},
+ {name: "juniperPTX pattern", circuit: []byte("et-0/0/0.0"), want: &CircuitID{Slot: "0", Module: "0", Port: "0", SubPort: "0"}},
+ {name: "Arista pattern", circuit: []byte("Ethernet3/17/1"), want: &CircuitID{Slot: "3", Module: "17", Port: "1"}},
+ {name: "Juniper QFX pattern", circuit: []byte("et-1/0/61"), want: &CircuitID{Slot: "1", Module: "0", Port: "61"}},
+ {name: "Arista Vlan pattern 1", circuit: []byte("Ethernet14:Vlan2001"), want: &CircuitID{Port: "14", Vlan: "Vlan2001"}},
+ {name: "Arista Vlan pattern 2", circuit: []byte("Ethernet10:2020"), want: &CircuitID{Port: "10", Vlan: "2020"}},
+ {name: "Cisco pattern", circuit: []byte("Gi1/10:2020"), want: &CircuitID{Slot: "1", Port: "10", Vlan: "2020"}},
+ {name: "Cisco Nexus pattern", circuit: []byte("Ethernet1/3"), want: &CircuitID{Slot: "1", Port: "3"}},
+ {name: "Juniper Bundle Pattern", circuit: []byte("ae52.0"), want: &CircuitID{Port: "52", SubPort: "0"}},
+ {name: "Arista Vlan pattern 1 with circuitid type and length", circuit: []byte("\x00\x0fEthernet14:2001"), want: &CircuitID{Port: "14", Vlan: "2001"}},
+ {name: "juniperEX pattern", circuit: []byte("ge-0/0/0.0:RANDOMCHAR"), want: &CircuitID{Slot: "0", Module: "0", Port: "0", SubPort: "0"}},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
@@ -93,6 +123,19 @@ func TestParseRemoteIDWithInterfaceID(t *testing.T) {
fail bool
}{
{name: "Bogus string", circuit: []byte("ope/1/2/3:ope.1"), fail: true, want: nil},
+ {name: "Bogus test", circuit: []byte("bogusInterface"), fail: true, want: nil},
+ {name: "juniperQFX pattern et", circuit: []byte("et-0/0/0:0.0"), want: &CircuitID{Slot: "0", Module: "0", Port: "0", SubPort: "0"}},
+ {name: "juniperQFX pattern xe", circuit: []byte("xe-0/0/14:2"), want: &CircuitID{Slot: "0", Module: "0", Port: "14", SubPort: "2"}},
+ {name: "juniperPTX pattern", circuit: []byte("et-0/0/0.0"), want: &CircuitID{Slot: "0", Module: "0", Port: "0", SubPort: "0"}},
+ {name: "Arista pattern", circuit: []byte("Ethernet3/17/1"), want: &CircuitID{Slot: "3", Module: "17", Port: "1"}},
+ {name: "Juniper QFX pattern", circuit: []byte("et-1/0/61"), want: &CircuitID{Slot: "1", Module: "0", Port: "61"}},
+ {name: "Arista Vlan pattern 1", circuit: []byte("Ethernet14:Vlan2001"), want: &CircuitID{Port: "14", Vlan: "Vlan2001"}},
+ {name: "Arista Vlan pattern 2", circuit: []byte("Ethernet10:2020"), want: &CircuitID{Port: "10", Vlan: "2020"}},
+ {name: "Cisco pattern", circuit: []byte("Gi1/10:2020"), want: &CircuitID{Slot: "1", Port: "10", Vlan: "2020"}},
+ {name: "Cisco Nexus pattern", circuit: []byte("Ethernet1/3"), want: &CircuitID{Slot: "1", Port: "3"}},
+ {name: "Juniper Bundle Pattern", circuit: []byte("ae52.0"), want: &CircuitID{Port: "52", SubPort: "0"}},
+ {name: "Arista Vlan pattern 1 with circuitid type and length", circuit: []byte("\x00\x0fEthernet14:2001"), want: &CircuitID{Port: "14", Vlan: "2001"}},
+ {name: "juniperEX pattern", circuit: []byte("ge-0/0/0.0:RANDOMCHAR"), want: &CircuitID{Slot: "0", Module: "0", Port: "0", SubPort: "0"}},
{name: "Arista Slot Module Port Pattern", circuit: []byte("Ethernet1/3/4:default"), want: &CircuitID{Slot: "1", Module: "3", Port: "4"}},
}
for _, tc := range tt {
diff --git a/dhcpv6/ztpv6/parse_vendor_options.go b/dhcpv6/ztpv6/parse_vendor_options.go
index 212733c..3aef837 100644
--- a/dhcpv6/ztpv6/parse_vendor_options.go
+++ b/dhcpv6/ztpv6/parse_vendor_options.go
@@ -2,9 +2,12 @@ package ztpv6
import (
"errors"
+ "fmt"
+ "strconv"
"strings"
"github.com/insomniacslk/dhcp/dhcpv6"
+ "github.com/insomniacslk/dhcp/iana"
)
var (
@@ -66,6 +69,27 @@ func ParseVendorData(packet dhcpv6.DHCPv6) (*VendorData, error) {
vd.Model = p[1]
vd.Serial = p[2]
return &vd, nil
+
+ // For Ciena the class identifier (opt 60) is written in the following format:
+ // {vendor iana code}-{product}-{type}
+ // For Ciena the iana code is 1271
+ // The product type is a number that maps to a Ciena product
+ // The type is used to identified different subtype of the product.
+ // An example can be ‘1271-23422Z11-123’.
+ case strings.HasPrefix(d, strconv.Itoa(int(iana.EnterpriseIDCienaCorporation))):
+ v := strings.Split(d, "-")
+ if len(v) < 3 {
+ return nil, errVendorOptionMalformed
+ }
+ duid := packet.(*dhcpv6.Message).Options.ClientID()
+ if duid.Type != dhcpv6.DUID_EN {
+ return nil, errors.New(fmt.Sprintf("Unexpected DUID type %d for Ciena", duid.Type))
+ }
+
+ vd.VendorName = iana.EnterpriseIDCienaCorporation.String()
+ vd.Model = v[1] + "-" + v[2]
+ vd.Serial = string(duid.EnterpriseIdentifier)
+ return &vd, nil
}
}
return nil, errors.New("failed to parse vendor option data")
diff --git a/dhcpv6/ztpv6/parse_vendor_options_test.go b/dhcpv6/ztpv6/parse_vendor_options_test.go
index 45b42d6..ac5fc81 100644
--- a/dhcpv6/ztpv6/parse_vendor_options_test.go
+++ b/dhcpv6/ztpv6/parse_vendor_options_test.go
@@ -4,6 +4,7 @@ import (
"testing"
"github.com/insomniacslk/dhcp/dhcpv6"
+ "github.com/insomniacslk/dhcp/iana"
"github.com/stretchr/testify/require"
)
@@ -99,3 +100,50 @@ func TestParseVendorDataWithVendorClass(t *testing.T) {
})
}
}
+
+func TestParseVendorDataWithClientId(t *testing.T) {
+ tt := []struct {
+ name string
+ vc string
+ serial string
+ want *VendorData
+ fail bool
+ }{
+ {
+ name: "Ciena",
+ vc: "1271-23422Z11-123",
+ serial: "001234567",
+ want: &VendorData{VendorName: iana.EnterpriseIDCienaCorporation.String(), Model: "23422Z11-123", Serial: "001234567"},
+ },
+ }
+
+ for _, tc := range tt {
+ t.Run(tc.name, func(t *testing.T) {
+ packet, err := dhcpv6.NewMessage()
+ if err != nil {
+ t.Fatalf("failed to creat dhcpv6 packet object: %v", err)
+ }
+
+ packet.AddOption(&dhcpv6.OptVendorClass{
+ EnterpriseNumber: 0000, Data: [][]byte{[]byte(tc.vc)}})
+ packet.AddOption(dhcpv6.OptClientID(
+ dhcpv6.Duid{
+ Type: dhcpv6.DUID_EN,
+ EnterpriseIdentifier: []byte(tc.serial),
+ },
+ ),
+ )
+
+ vd, err := ParseVendorData(packet)
+ if err != nil && !tc.fail {
+ t.Errorf("unexpected failure: %v", err)
+ }
+
+ if vd != nil {
+ require.Equal(t, *tc.want, *vd, "comparing vendor option data")
+ } else {
+ require.Equal(t, tc.want, vd, "comparing vendor option data")
+ }
+ })
+ }
+}