// 2020 Rackspace, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package netparse import ( "fmt" "net" "regexp" "strconv" "strings" ) var ( portNumberRE = regexp.MustCompile(`.*?([0-9]+)$`) ifcConfigRE = regexp.MustCompile(`(?i)(\ni|)nterface\s+(.*?/[0-9]+|.*?net[0-9]+|vlan[0-9]+|port-channel[0-9]+)\n((.|\n)*?)(!\n|\ni|$)`) startCfgItemRE = regexp.MustCompile(`^\S+`) ifcDescRE = regexp.MustCompile(`(?i)description\s+(.*?)(\n)`) ipAddrRE = regexp.MustCompile(`(?i)ip\s+address\s+((\S+/\d+)|(\S+)\s+(\S+))(\s+secondary\n|\n)`) standbyIPRE = regexp.MustCompile(`(?i)standby\s+(\d+)\s+ip\s+(\S+)(\s+secondary\n|\n)`) standbyPriRE = regexp.MustCompile(`(?i)standby\s+(\d+)\s+priority\s+(\d+)\n`) standbyPreRE = regexp.MustCompile(`(?i)standby\s+(\d+)\s+preempt(\n|\s+delay((\s+\S+\s+\d+)+)\n)`) standbyAuthRE = regexp.MustCompile(`(?i)standby\s+(\d+)\s+authentication\s+(text|md5|)\s+(\S+)\n`) duplexRE = regexp.MustCompile(`(?i)duplex\s+(\S+)`) bpduGuardRE = regexp.MustCompile(`(?i)(spanning-tree\s+bpduguard\s+enable)`) stpGuardRE = regexp.MustCompile(`(?i)spanning-tree\s+guard\s+(\S+)`) speedRE = regexp.MustCompile(`(?i)speed\s+(\S+)`) accessVlanRE = regexp.MustCompile(`(?i)switchport\s+access\s+vlan\s+(\S+)`) modeRE = regexp.MustCompile(`(?i)switchport\s+mode\s+(.*)`) portSecRE = regexp.MustCompile(`(?i)switchport\s+(port-security)`) nativeVlanRE = regexp.MustCompile(`(?i)switchport\s+trunk\s+native\s+vlan\s+([0-9]+)`) portFastRE = regexp.MustCompile(`(?i)(spanning-tree\s+portfast)`) portFastTrunkRE = regexp.MustCompile(`(?i)(spanning-tree\s+portfast\s+trunk)`) portFastEdgeRE = regexp.MustCompile(`(?i)(spanning-tree\s+portfast\s+edge)`) portFastEdgeRENexus = regexp.MustCompile(`(?i)(spanning-tree\s+port\s+type\s+edge\s+trunk)`) nonegotiateRE = regexp.MustCompile(`(?i)(switchport\s+nonegotiate)`) vpcRE = regexp.MustCompile(`(?i)vpc\s+([0-9]+)`) mlagRE = regexp.MustCompile(`(?i)mlag\s+([0-9]+)`) channelGroupRE = regexp.MustCompile(`(?i)channel-group\s+([0-9]+)\s+mode\s+\S+`) channelModeRE = regexp.MustCompile(`(?i)channel-group\s+[0-9]+\s+mode\s+(\S+)`) portProtectedRE = regexp.MustCompile(`(?i)(switchport\s+protected)`) vrfRE = regexp.MustCompile(`(?i)ip\s+vrf\s+forwarding\s+(\S+)`) trunkAllowedRE = regexp.MustCompile(`(?i)switchport\s+trunk\s+allowed\s+vlan(?:\s+add)?\s+(.*)`) privTrunkAllowedRE = regexp.MustCompile(`(?i)switchport\s+private-vlan\s+trunk\s+allowed\s+vlan(?:\s+add)?\s+(.*)`) assocTrunkAllowedRE = regexp.MustCompile(`(?i)switchport\s+private-vlan\s+association\s+trunk\s+(\d+)\s+(\d+)`) assocHostRE = regexp.MustCompile(`(?i)switchport\s+private-vlan\s+host-association\s+(\d+)\s+(\d+)`) noCdpEnabledRE = regexp.MustCompile(`(?i)(no\s+cdp\s+enable)`) cdpEnabledRE = regexp.MustCompile(`(?i)(\n[ ]+cdp\s+enable)`) noShutdownRE = regexp.MustCompile(`(?i)(no[ ]+shutdown)`) shutdownRE = regexp.MustCompile(`(?i)(\n[ ]+shutdown)`) ) type ( // IfcConfig defines the parsed available values we are tracking as a // port config item. IfcConfig struct { Zone string `json:"zone"` IPAddress []IP `json:"ip_address"` Standby []Standby `json:"standby"` Name string `json:"name"` PortNum int `json:"port_num"` ChannelGroup int `json:"channel_group"` ChannelMode string `json:"channel_group_mode"` Description string `json:"description"` Duplex string `json:"duplex"` BPDUGuard bool `json:"bpduguard"` STPGuard string `json:"spanning_tree_guard"` Speed string `json:"speed"` AccessVlan int `json:"access_vlan"` Mode string `json:"mode"` PortSec bool `jsoN:"port_security"` VlanAllowed []int `json:"vlan_allowed"` PrivVlanAssoc []VlanAssociation `json:"private_vlan_association"` NativeVlan int `json:"native_vlan"` PortFast bool `json:"port_fast"` PortFastTrunk bool `json:"port_fast_trunk"` CdpEnable bool `json:"cdp_enable"` NoNegotiate bool `json:"nonegotiate"` Protected bool `json:"switchport_protected"` Shutdown bool `json:"shutdown"` VPC int `json:"vpc"` MLAG int `json:"mlag"` Vrf string `json:"forwarding_vrf"` RawCommands []string `json:"raw_commands"` } // VlanAssociation type is used to grab the configuration portion of a switch that // defines private vlan association configurations. VlanAssociation struct { Primary int `json:"primary_vlan"` Secondary int `json:"secondary_vlan"` } // Standby represents an FHRP implemented on a switch interface. Standby struct { Group int `json:"group"` Address IP `json:"ip"` Priority *int `json:"priority"` Preempt bool `json:"preempt"` PreemptDelay string `json:"preempt_delay"` AuthString string `json:"auth_string"` AuthType string `json:"auth_type"` } // IP represents a IP address configuration on a // switch interface IP struct { Address net.IP `json:"address"` Netmask net.IP `json:"netmask"` Secondary bool `json:"secondary"` } ) func IfcConfigParse(s string) []IfcConfig { // Find an instance of the interface config. The response from the regexp parser will // look similar to: // [ // "nterface Ethernet116/1/24\n no cdp enable\n switchport mode trunk\n switchport trunk allowed vlan 810,3557-3562\n", // "", // "Ethernet116/1/24", // " no cdp enable\n switchport mode trunk\n switchport trunk allowed vlan 810,3557-3562\n", // "\n", // "" // ] // We will take the 4th position of the the regexp results to work with as that value should be // the nested config under the interface * prefix, up until a empty line. retVal := make([]IfcConfig, 0) for _, i := range ifcConfigRE.FindAllStringSubmatch(s, -1) { if startCfgItemRE.MatchString(i[3]) { continue } if startCfgItemRE.MatchString(i[3]) || i[2] == "" || i[2] == "!Command:" || i[3] == "ip tftp source-interface FastEthernet1" || strings.Contains(i[3], "no passive-interface") { return retVal } retVal = append(retVal, IfcConfig{ Name: i[2], IPAddress: parseIPAddress(i[3]), Standby: parseStandby(i[3]), PortNum: regexpAtoiOrNegOne(portNumberRE, i[2]), Description: regexpStrOrEmpty(ifcDescRE, i[3]), Duplex: regexpStrOrEmpty(duplexRE, i[3]), BPDUGuard: bpduGuardRE.MatchString(i[3]), STPGuard: regexpStrOrEmpty(stpGuardRE, i[3]), Speed: regexpStrOrEmpty(speedRE, i[3]), AccessVlan: regexpAtoiOrNegOne(accessVlanRE, i[3]), Mode: regexpStrOrEmpty(modeRE, i[3]), PortSec: portSecRE.MatchString(i[3]), NativeVlan: regexpAtoiOrNegOne(nativeVlanRE, i[3]), PrivVlanAssoc: func() []VlanAssociation { r := make([]VlanAssociation, 0) if assoc := assocTrunkAllowedRE.FindAllStringSubmatch(i[3], -1); assoc != nil { for _, a := range assoc { r = append(r, VlanAssociation{Primary: Atoi(a[1]), Secondary: Atoi(a[2])}) } } if assoc := assocHostRE.FindAllStringSubmatch(i[3], -1); assoc != nil { for _, a := range assoc { r = append(r, VlanAssociation{Primary: Atoi(a[1]), Secondary: Atoi(a[2])}) } } return r }(), PortFast: portFastRE.MatchString(i[3]), PortFastTrunk: func(s string) bool { if portFastTrunkRE.MatchString(s) { return true } if portFastEdgeRE.MatchString(s) { return true } if portFastEdgeRENexus.MatchString(s) { return true } return false }(i[3]), NoNegotiate: nonegotiateRE.MatchString(i[3]), VPC: regexpAtoiOrNegOne(vpcRE, i[3]), MLAG: regexpAtoiOrNegOne(mlagRE, i[3]), ChannelGroup: regexpAtoiOrNegOne(channelGroupRE, i[3]), ChannelMode: regexpStrOrEmpty(channelModeRE, i[3]), Protected: portProtectedRE.MatchString(i[3]), Vrf: regexpStrOrEmpty(vrfRE, i[3]), VlanAllowed: vlansAllowed(i[3]), CdpEnable: cdpEnabledRE.MatchString(i[3]) || !noCdpEnabledRE.MatchString(i[3]), Shutdown: func() bool { if noShutdownRE.MatchString(i[3]) { return false } if shutdownRE.MatchString(i[3]) { return true } return false }(), RawCommands: splitAndTrim(i[3], "\n"), }) } return retVal } // IfcByName will lookup an interface by the interface name and return the // the full type back to the caller. func IfcByName(ifcName string, ifcs []IfcConfig) (IfcConfig, error) { for i := range ifcs { if strings.EqualFold(ifcs[i].Name, ifcName) { return ifcs[i], nil } } return IfcConfig{}, fmt.Errorf("unable to find interface %s", ifcName) } func vlansAllowed(s string) []int { vlan := make([]int, 0) if vMatch := trunkAllowedRE.FindAllStringSubmatch(s, -1); len(vMatch) != 0 { vlan = append(vlan, vlanAllowedParser(vMatch)...) } if vMatch := privTrunkAllowedRE.FindAllStringSubmatch(s, -1); len(vMatch) != 0 { vlan = append(vlan, vlanAllowedParser(vMatch)...) } return vlan } func vlanAllowedParser(s [][]string) []int { vlan := make([]int, 0) for _, v := range s { if v[1] == "none" { return vlan } vlans := strings.Split(v[1], ",") for _, vl := range vlans { vl = strings.TrimSpace(vl) if strings.Contains(vl, "-") { vl2 := strings.Split(vl, "-") vlStart, err := strconv.Atoi(vl2[0]) if err != nil { return nil } vlEnd, err := strconv.Atoi(vl2[1]) if err != nil { return nil } for i := vlStart; i <= vlEnd; i++ { vlan = append(vlan, i) } continue } if i, err := strconv.Atoi(vl); err == nil { vlan = append(vlan, i) } } } return vlan } func parseStandby(s string) []Standby { retVal := make([]Standby, 0) tmp := make(map[int]Standby) if sbIPs := standbyIPRE.FindAllStringSubmatch(s, -1); sbIPs != nil { for _, sb := range sbIPs { if x, err := strconv.Atoi(sb[1]); err == nil { if _, ok := tmp[x]; !ok { tmp[x] = Standby{} } tt := tmp[x] tt.Address = IP{ Address: net.ParseIP(sb[2]), Secondary: strings.Contains(sb[3], "secondary"), } tmp[x] = tt } } } if sbPris := standbyPriRE.FindAllStringSubmatch(s, -1); sbPris != nil { for _, sb := range sbPris { if x, err := strconv.Atoi(sb[1]); err == nil { if _, ok := tmp[x]; !ok { tmp[x] = Standby{} } if pri, err := strconv.Atoi(sb[1]); err == nil { tt := tmp[x] tt.Priority = &pri tmp[x] = tt } } } } if sbPris := standbyAuthRE.FindAllStringSubmatch(s, -1); sbPris != nil { for _, sb := range sbPris { if x, err := strconv.Atoi(sb[1]); err == nil { if _, ok := tmp[x]; !ok { tmp[x] = Standby{} } tt := tmp[x] tt.AuthType = "text" if strings.Contains(sb[1], "md5") { tt.AuthType = "md5" } tt.AuthString = sb[2] tmp[x] = tt } } } if sbPris := standbyPreRE.FindAllStringSubmatch(s, -1); sbPris != nil { for _, sb := range sbPris { if x, err := strconv.Atoi(sb[1]); err == nil { if _, ok := tmp[x]; !ok { tmp[x] = Standby{} } tt := tmp[x] tt.Preempt = true tt.PreemptDelay = strings.TrimSpace(sb[3]) tmp[x] = tt } } } for k, v := range tmp { v.Group = k retVal = append(retVal, v) } return retVal } func parseIPAddress(s string) []IP { retVal := make([]IP, 0) ipRE := ipAddrRE.FindAllStringSubmatch(s, -1) if ipRE == nil { return nil } for _, i := range ipRE { if len(i) != 6 { continue } if i[2] == "" { retVal = append(retVal, IP{ Address: net.ParseIP(i[3]), Netmask: net.ParseIP(i[4]), Secondary: strings.Contains(i[5], "secondary"), }) } else { ip, nm, err := net.ParseCIDR(i[2]) if err != nil { continue } retVal = append(retVal, IP{ Address: ip, Netmask: net.ParseIP(ipv4MaskString(nm.Mask)), Secondary: strings.Contains(i[5], "secondary"), }) } } return retVal }