summaryrefslogtreecommitdiffhomepage
path: root/dhcpv4
diff options
context:
space:
mode:
authorAkshay Navale <akna8887@colorado.edu>2019-03-13 16:56:17 -0600
committerPablo Mazzini <pmazzini@gmail.com>2019-03-13 22:56:17 +0000
commit47951531f8c905cc79209028dd08a8894763043e (patch)
treea4e9fe1ba9fe7fd136b27d2cb669cef8c710f8e8 /dhcpv4
parent1c542dbd7c4522d965f58dfff8090184d38fca2d (diff)
Adding CircuitId parsing logic into dhcpv4 lib (#255)
Diffstat (limited to 'dhcpv4')
-rw-r--r--dhcpv4/option_relay_agent_information.go20
-rw-r--r--dhcpv4/ztpv4/parse_circuitid.go94
-rw-r--r--dhcpv4/ztpv4/parse_circuitid_test.go109
3 files changed, 223 insertions, 0 deletions
diff --git a/dhcpv4/option_relay_agent_information.go b/dhcpv4/option_relay_agent_information.go
index 8b88b0c..56d93f2 100644
--- a/dhcpv4/option_relay_agent_information.go
+++ b/dhcpv4/option_relay_agent_information.go
@@ -36,3 +36,23 @@ func (r *RelayOptions) FromBytes(data []byte) error {
func OptRelayAgentInfo(o ...Option) Option {
return Option{Code: OptionRelayAgentInformation, Value: RelayOptions{OptionsFromList(o...)}}
}
+
+type raiSubOptionCode uint8
+
+func (o raiSubOptionCode) Code() uint8 {
+ return uint8(o)
+}
+
+func (o raiSubOptionCode) String() string {
+ if s, ok := raiSubOptionCodeToString[o]; ok {
+ return s
+ }
+ return fmt.Sprintf("unknown (%d)", o)
+}
+
+// AgentCircuitIDSubOption as per https://tools.ietf.org/html/rfc3046#section-2.0
+const AgentCircuitIDSubOption raiSubOptionCode = 1
+
+var raiSubOptionCodeToString = map[raiSubOptionCode]string{
+ AgentCircuitIDSubOption: "Agent Circuit ID Sub-option",
+}
diff --git a/dhcpv4/ztpv4/parse_circuitid.go b/dhcpv4/ztpv4/parse_circuitid.go
new file mode 100644
index 0000000..b9db358
--- /dev/null
+++ b/dhcpv4/ztpv4/parse_circuitid.go
@@ -0,0 +1,94 @@
+package ztpv4
+
+import (
+ "fmt"
+ "regexp"
+
+ "github.com/insomniacslk/dhcp/dhcpv4"
+)
+
+// CircuitID represents the structure of network vendor interface formats
+type CircuitID struct {
+ Slot string
+ Module string
+ Port string
+ SubPort string
+ Vlan string
+}
+
+var circuitRegexs = []*regexp.Regexp{
+ // Juniper QFX et-0/0/0:0.0
+ regexp.MustCompile("^et-(?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]+)$"),
+ // Arista Ethernet3/17/1
+ 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])$"),
+}
+
+// ParseCircuitID will parse dhcpv4 packet and return CircuitID info
+func ParseCircuitID(packet *dhcpv4.DHCPv4) (*CircuitID, error) {
+ // CircuitId info is stored as sub-option field in RelayAgentInformationOption
+ relayOptions := packet.RelayAgentInfo()
+
+ if relayOptions == nil {
+ return nil, fmt.Errorf("No relay agent information option found in the dhcpv4 pkt")
+ }
+
+ // As per RFC 3046 sub-Option 1 is circuit-id. Look at 2.0 section in that RFC
+ // https://tools.ietf.org/html/rfc3046
+ circuitIdStr := string(relayOptions.Options.Get(dhcpv4.AgentCircuitIDSubOption))
+ if circuitIdStr == "" {
+ return nil, fmt.Errorf("no circuit-id suboption found in dhcpv4 packet")
+ }
+ circuitId, err := matchCircuitID(circuitIdStr)
+ if err != nil {
+ return nil, err
+ }
+ return circuitId, nil
+}
+
+func matchCircuitID(circuitIdStr string) (*CircuitID, error) {
+
+ for _, re := range circuitRegexs {
+
+ match := re.FindStringSubmatch(circuitIdStr)
+ if len(match) == 0 {
+ continue
+ }
+
+ 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 &c, nil
+ }
+ return nil, fmt.Errorf("Unable to match circuit id : %s with listed regexes of interface types", circuitIdStr)
+}
+
+// FormatCircuitID is the CircuitID format we send in our Bootfile URL for ZTP devices
+func (c *CircuitID) FormatCircuitID() string {
+ return fmt.Sprintf("%v,%v,%v,%v,%v", c.Slot, c.Module, c.Port, c.SubPort, c.Vlan)
+}
diff --git a/dhcpv4/ztpv4/parse_circuitid_test.go b/dhcpv4/ztpv4/parse_circuitid_test.go
new file mode 100644
index 0000000..b075a16
--- /dev/null
+++ b/dhcpv4/ztpv4/parse_circuitid_test.go
@@ -0,0 +1,109 @@
+package ztpv4
+
+import (
+ "testing"
+
+ "github.com/insomniacslk/dhcp/dhcpv4"
+ "github.com/stretchr/testify/require"
+)
+
+func TestMatchCircuitID(t *testing.T) {
+ tt := []struct {
+ name string
+ circuit string
+ want *CircuitID
+ fail bool
+ }{
+ {name: "Bogus string", circuit: "bogus_interface", fail: true, want: nil},
+ {name: "juniperQFX pattern", circuit: "et-0/0/0:0.0", want: &CircuitID{Slot: "0", Module: "0", Port: "0", SubPort: "0"}},
+ {name: "juniperPTX pattern", circuit: "et-0/0/0.0", want: &CircuitID{Slot: "0", Module: "0", Port: "0", SubPort: "0"}},
+ {name: "Arista pattern", circuit: "Ethernet3/17/1", want: &CircuitID{Slot: "3", Module: "17", Port: "1"}},
+ {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: "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"}},
+ }
+ for _, tc := range tt {
+ t.Run(tc.name, func(t *testing.T) {
+ circuit, err := matchCircuitID(tc.circuit)
+ if err != nil && !tc.fail {
+ t.Errorf("unexpected failure: %v", err)
+ }
+ if circuit != nil {
+ require.Equal(t, tc.want, circuit, "comparing CircuitID")
+ } else {
+ require.Equal(t, tc.want, circuit, "comparing CircuitID")
+ }
+ })
+ }
+}
+
+func TestFormatCircuitID(t *testing.T) {
+ tt := []struct {
+ name string
+ circuit *CircuitID
+ want string
+ fail bool
+ }{
+ {name: "empty", circuit: &CircuitID{}, want: ",,,,"},
+ {name: "juniperQFX pattern", circuit: &CircuitID{Slot: "0", Module: "0", Port: "0", SubPort: "0"}, want: "0,0,0,0,"},
+ {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 {
+ t.Run(tc.name, func(t *testing.T) {
+ circuit := tc.circuit.FormatCircuitID()
+ require.Equal(t, tc.want, circuit, "FormatCircuitID data")
+ })
+ }
+
+}
+
+func TestParseCircuitID(t *testing.T) {
+ tt := []struct {
+ name string
+ circuit []byte
+ want *CircuitID
+ fail bool
+ }{
+ {name: "Bogus test", circuit: []byte("bogusInterface"), fail: true, want: nil},
+ {name: "juniperQFX pattern", circuit: []byte("et-0/0/0:0.0"), want: &CircuitID{Slot: "0", Module: "0", Port: "0", SubPort: "0"}},
+ {name: "Bogus string", circuit: []byte("bogus_interface"), fail: true, want: nil},
+ {name: "juniperQFX pattern", circuit: []byte("et-0/0/0:0.0"), want: &CircuitID{Slot: "0", Module: "0", Port: "0", SubPort: "0"}},
+ {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"}},
+ }
+ for _, tc := range tt {
+ t.Run(tc.name, func(t *testing.T) {
+
+ addOption := dhcpv4.WithOption(dhcpv4.OptRelayAgentInfo(dhcpv4.OptGeneric(dhcpv4.GenericOptionCode(1), tc.circuit)))
+ packet, err := dhcpv4.New(addOption)
+ if err != nil {
+ t.Errorf("Unable to create dhcpv4 packet with circuiti")
+ }
+ c, err := ParseCircuitID(packet)
+ if err != nil && !tc.fail {
+ t.Errorf("Testcase Failed %v", err)
+ }
+ if c != nil {
+ require.Equal(t, *tc.want, *c, "Comparing DHCPv4 Relay Agent Info CircuitId")
+ } else {
+ require.Equal(t, tc.want, c, "Comparing DHCPv4 Relay Agent Info CircuitId")
+ }
+ })
+ }
+}