From cef7f4a9829be517745c0c07658f1344989d150f Mon Sep 17 00:00:00 2001 From: Emanuele Fia Date: Thu, 31 Mar 2022 20:38:34 +0100 Subject: Adding support for DHCPv6 for Ciena Signed-off-by: Emanuele Fia --- dhcpv6/ztpv6/parse_remote_id.go | 78 +++++++++++++------------------ dhcpv6/ztpv6/parse_remote_id_test.go | 53 ++------------------- dhcpv6/ztpv6/parse_vendor_options.go | 8 ++-- dhcpv6/ztpv6/parse_vendor_options_test.go | 69 +++++++-------------------- 4 files changed, 58 insertions(+), 150 deletions(-) (limited to 'dhcpv6/ztpv6') diff --git a/dhcpv6/ztpv6/parse_remote_id.go b/dhcpv6/ztpv6/parse_remote_id.go index 8b1488a..9dd72b1 100644 --- a/dhcpv6/ztpv6/parse_remote_id.go +++ b/dhcpv6/ztpv6/parse_remote_id.go @@ -8,32 +8,12 @@ import ( "github.com/insomniacslk/dhcp/dhcpv6" ) -var circuitRegexs = []*regexp.Regexp{ +var ( // Arista Port, Vlan Pattern - regexp.MustCompile("Ethernet(?P[0-9]+):(?P[0-9]+)"), + aristaPVPattern = regexp.MustCompile("Ethernet(?P[0-9]+):(?P[0-9]+)") // Arista Slot, Mod, Port Pattern - regexp.MustCompile("Ethernet(?P[0-9]+)/(?P[0-9]+)/(?P[0-9]+)"), - // Juniper QFX et-0/0/0:0.0 and xe-0/0/0:0.0 - regexp.MustCompile("^(et|xe)-(?P[0-9]+)/(?P[0-9]+)/(?P[0-9]+):(?P[0-9]+).*$"), - // Juniper PTX et-0/0/0.0 - regexp.MustCompile("^et-(?P[0-9]+)/(?P[0-9]+)/(?P[0-9]+).(?P[0-9]+)$"), - // Juniper EX ge-0/0/0.0 - regexp.MustCompile("^ge-(?P[0-9]+)/(?P[0-9]+)/(?P[0-9]+).(?P[0-9]+).*"), - // Arista Ethernet3/17/1 - // Sometimes Arista prepend circuit id type(1 byte) and length(1 byte) not using ^ - regexp.MustCompile("Ethernet(?P[0-9]+)/(?P[0-9]+)/(?P[0-9]+)$"), - // Juniper QFX et-1/0/61 - regexp.MustCompile("^et-(?P[0-9]+)/(?P[0-9]+)/(?P[0-9]+)$"), - // Arista Ethernet14:Vlan2001 - // Arista Ethernet10:2020 - regexp.MustCompile("Ethernet(?P[0-9]+):(?P.*)$"), - // Cisco Gi1/10:2020 - regexp.MustCompile("^Gi(?P[0-9]+)/(?P[0-9]+):(?P.*)$"), - // Nexus Ethernet1/3 - regexp.MustCompile("^Ethernet(?P[0-9]+)/(?P[0-9]+)$"), - // Juniper bundle interface ae52.0 - regexp.MustCompile("^ae(?P[0-9]+).(?P[0-9])$"), -} + aristaSMPPattern = regexp.MustCompile("Ethernet(?P[0-9]+)/(?P[0-9]+)/(?P[0-9]+)") +) // CircuitID represents the structure of network vendor interface formats type CircuitID struct { @@ -73,32 +53,38 @@ func ParseRemoteID(packet dhcpv6.DHCPv6) (*CircuitID, error) { } func matchCircuitId(circuitInfo string) (*CircuitID, error) { - for _, re := range circuitRegexs { + var names, matches []string - match := re.FindStringSubmatch(circuitInfo) - if len(match) == 0 { - continue - } + switch { + case aristaPVPattern.MatchString(circuitInfo): + matches = aristaPVPattern.FindStringSubmatch(circuitInfo) + names = aristaPVPattern.SubexpNames() + case aristaSMPPattern.MatchString(circuitInfo): + matches = aristaSMPPattern.FindStringSubmatch(circuitInfo) + names = aristaSMPPattern.SubexpNames() + } - 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] - } - } + if len(matches) == 0 { + return nil, fmt.Errorf("no circuitId regex matches for %v", circuitInfo) + } - return &c, nil + 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 + } } - return nil, fmt.Errorf("Unable to match circuit id : %s with listed regexes of interface types", circuitInfo) + + return &circuit, nil } // 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 8d17c80..6147cec 100644 --- a/dhcpv6/ztpv6/parse_remote_id_test.go +++ b/dhcpv6/ztpv6/parse_remote_id_test.go @@ -15,20 +15,9 @@ func TestCircuitID(t *testing.T) { fail bool }{ {name: "Bogus string", circuit: "ope/1/2/3:ope", fail: true, want: nil}, - {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 Port Vlan Pattern", circuit: "Ethernet13:2001", want: &CircuitID{Port: "13", Vlan: "2001"}}, {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) { @@ -50,16 +39,8 @@ func TestFormatCircuitID(t *testing.T) { want string }{ {name: "empty", circuit: &CircuitID{}, want: ",,,,"}, - {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,"}, + {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,,"}, } for _, tc := range tt { @@ -79,19 +60,8 @@ func TestParseRemoteID(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 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"}}, } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { @@ -123,19 +93,6 @@ 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 3aef837..13983f4 100644 --- a/dhcpv6/ztpv6/parse_vendor_options.go +++ b/dhcpv6/ztpv6/parse_vendor_options.go @@ -81,11 +81,11 @@ func ParseVendorData(packet dhcpv6.DHCPv6) (*VendorData, error) { 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)) + innerMessage, err := packet.GetInnerMessage() + if err != nil { + return nil, fmt.Errorf("Unable to get inner message: %v", err) } - + duid := innerMessage.Options.ClientID() vd.VendorName = iana.EnterpriseIDCienaCorporation.String() vd.Model = v[1] + "-" + v[2] vd.Serial = string(duid.EnterpriseIdentifier) diff --git a/dhcpv6/ztpv6/parse_vendor_options_test.go b/dhcpv6/ztpv6/parse_vendor_options_test.go index ac5fc81..cd2fb3d 100644 --- a/dhcpv6/ztpv6/parse_vendor_options_test.go +++ b/dhcpv6/ztpv6/parse_vendor_options_test.go @@ -57,10 +57,11 @@ func TestParseVendorDataWithVendorOpts(t *testing.T) { func TestParseVendorDataWithVendorClass(t *testing.T) { tt := []struct { - name string - vc string - want *VendorData - fail bool + name string + vc string + clientId *dhcpv6.Duid + want *VendorData + fail bool }{ {name: "empty", fail: true}, {name: "unknownVendor", vc: "VendorX;BFR10K;XX12345", fail: true, want: nil}, @@ -75,45 +76,14 @@ func TestParseVendorDataWithVendorClass(t *testing.T) { vc: "ZPESystems:NSC:001234567", want: &VendorData{VendorName: "ZPESystems", Model: "NSC", 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)}}) - - 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 class data") - } else { - require.Equal(t, tc.want, vd, "comparing vendor class data") - } - }) - } -} - -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"}, + name: "Ciena", + vc: "1271-23422Z11-123", + clientId: &dhcpv6.Duid{ + Type: dhcpv6.DUID_EN, + EnterpriseIdentifier: []byte("001234567"), + }, + want: &VendorData{VendorName: iana.EnterpriseIDCienaCorporation.String(), Model: "23422Z11-123", Serial: "001234567"}, }, } @@ -126,23 +96,18 @@ func TestParseVendorDataWithClientId(t *testing.T) { 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), - }, - ), - ) - + if tc.clientId != nil { + packet.AddOption(dhcpv6.OptClientID(*tc.clientId)) + } 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") + require.Equal(t, *tc.want, *vd, "comparing vendor class data") } else { - require.Equal(t, tc.want, vd, "comparing vendor option data") + require.Equal(t, tc.want, vd, "comparing vendor class data") } }) } -- cgit v1.2.3