diff options
author | Emanuele Fia <name29@fb.com> | 2022-03-31 20:38:34 +0100 |
---|---|---|
committer | Emanuele Fia <name29@fb.com> | 2022-03-31 20:38:34 +0100 |
commit | 07cc76ec259f5fff3e81e11b340167a690bca9a1 (patch) | |
tree | c00d41eb17168b1ff9498f9a12a79db6aa6e2625 | |
parent | 3c283ff8b7dd3a8ea2dbc37d13a35bba7aab00e5 (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.go | 4 | ||||
-rw-r--r-- | dhcpv6/dhcpv6_test.go | 25 | ||||
-rw-r--r-- | dhcpv6/option_userclass.go | 6 | ||||
-rw-r--r-- | dhcpv6/option_userclass_test.go | 10 | ||||
-rw-r--r-- | dhcpv6/options.go | 6 | ||||
-rw-r--r-- | dhcpv6/ztpv6/parse_remote_id.go | 78 | ||||
-rw-r--r-- | dhcpv6/ztpv6/parse_remote_id_test.go | 53 | ||||
-rw-r--r-- | dhcpv6/ztpv6/parse_vendor_options.go | 24 | ||||
-rw-r--r-- | dhcpv6/ztpv6/parse_vendor_options_test.go | 48 |
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") + } + }) + } +} |