// 2017 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 spansessions audits the span session configuration on a list of IDS devices. package spansessions import ( "fmt" "net/http" "regexp" "strconv" "github.com/sirupsen/logrus" "github.rackspace.com/DCM/migration/pkg/collection/ctk" "github.rackspace.com/DCM/migration/pkg/collection/ebi" "github.rackspace.com/gorax/clients/dcxAutohost" "github.rackspace.com/gorax/clients/unipede" ) const ( mirrorInt = "mirrorInterface" mirrorVlan = "mirrorVlan" ) var ( mirrorPortRe = regexp.MustCompile(`(?i)\S+\/(\d+)$`) mirrorVlanRe = regexp.MustCompile(`(?i)VLAN(0|)(\d+)$`) errNoDestSPAN = "no destination span sessions found for device" errNoSrcSPAN = "no source span sessions found for device" ) type ( // Client type represents the client connectivity required for the // port mirror config calls. Client struct { u *unipede.Client dcx *dcxAutohost.Client ctk *ctk.Client spanSessions map[string][]unipede.MirrorPort audit map[string]*spanAudit } // AuditResp type is the resulting data type for a network status // audit. This will contain ping results, and unipede data about the device. AuditResp struct { Mirror []spanAudit `json:"result"` Errors []string `json:"errors"` } spanAudit struct { IDS int `json:"ids_device_id"` Source []device `json:"source"` Destination []device `json:"destination"` Errors []string `json:"errors"` portsChecked []string } device struct { ID string `json:"device_id"` SpanID int `json:"span_id"` Platform string `json:"platform"` AccountID int `json:"account_number"` AccountName string `json:"account_name"` Port string `json:"port"` } idsDevice struct { ctk.Device dcxAutohost.DevicesMigrations mp []spanPort devResp spanAudit } spanPort struct { port unipede.MirrorPort sw string dc string } ) // NewSpanSessionContext is a init method that will return the initiated clients needed // for the network status calls, using the *http.Request context. func NewSpanSessionContext(r *http.Request) (*Client, error) { c, err := ctk.NewContext(r) return &Client{ u: unipede.FromContext(r.Context()), dcx: dcxAutohost.FromContext(r.Context()), ctk: c, }, err } // Audit method will take a device that is searchable in core, gather information about the device // and poll unipede and the live network for device details. func (c *Client) Audit(idsDevices []ctk.Device, age int) (AuditResp, error) { c.spanSessions = make(map[string][]unipede.MirrorPort) c.audit = make(map[string]*spanAudit) resp := AuditResp{ Mirror: make([]spanAudit, 0), } // Start iterating through each device in the list // typically all devices in a move-group, for example. for _, ids := range idsDevices { // Pull switch-port information from DCX API idsPorts, err := c.dcx.MigrationsByDevice(ids.ID) if err != nil { logrus.Errorf("dcx.MigrationsByDevice (%d): %s", ids.ID, err) resp.Errors = append(resp.Errors, fmt.Sprintf("dcx.MigrationsByDevice (%d): %s", ids.ID, err)) continue } if len(idsPorts.Current.ParsedSwitchPorts) == 0 { logrus.Infof("no ports found for %d. Skipping...", ids.ID) continue } newIDS := &idsDevice{ ids, idsPorts, nil, spanAudit{ IDS: ids.ID, }, } // Will populate the spanSessions type with map[string][]unipede.MirrorPort where // the key will be the switch name and the value will be a list of mirror port config. c.getSPANSessions(newIDS, age) c.getSPANDest(newIDS) c.getSPANSource(newIDS) resp.Mirror = append(resp.Mirror, newIDS.devResp) } logrus.Debug(resp.Mirror) return resp, nil } func (c *Client) getSPANSessions(ids *idsDevice, age int) { // For each device, go through its list of assigned switch-ports, // checking for aggregated ExNet configs that could have span-sessions. for _, p := range ids.Current.ParsedSwitchPorts { // We've found an AggExNet port, now lets see if we have its mirror port data, or if we // need to go get it. if _, ok := c.spanSessions[p.SwitchName]; !ok { mpc, err := c.u.MirrorPortConfig(fmt.Sprintf("%s.%s", p.SwitchName, p.DataCenter), &age) if err != nil { ids.error(err.Error()) continue } // Add the mirror port config to the map c.spanSessions[p.SwitchName] = mpc.Items logrus.Infof("collected SPAN sessions on %s", p.SwitchName) } } } func (c *Client) getSPANDest(ids *idsDevice) { // For each device, go through its list of assigned switch-ports, // checking for aggregated ExNet configs that could have span-sessions. for _, idsPort := range ids.Current.ParsedSwitchPorts { idsSpanID := 0 // Range through each span-session that is configured in on this switch for _, mp := range c.spanSessions[idsPort.SwitchName] { // Regex method to pull either the Vlan number or Port number from the Mirror-Port // config. Also return the type of port. portID, _ := getPortID(mp.Port) // If the switch-port number matches the mirror-port number, and it's a destination // span session, then the IDS is correctly configured for destination. if portID == strconv.Itoa(idsPort.PortNumber) && mp.Direction == "destination" { // Record the span session once we find the destination // port config assigned to the IDS. idsSpanID = mp.Session // Record the span session info into the response struct ids.devResp.Destination = append(ids.devResp.Destination, device{ Port: fmt.Sprintf("%s[%s]", idsPort.SwitchName, portID), Platform: ids.Device.Device.MakeModel, AccountID: ids.Device.Account.Number, AccountName: ids.Device.Account.Name, ID: strconv.Itoa(ids.Device.ID), SpanID: mp.Session, }) } } if idsSpanID != 0 { // Range back through the mirror ports to grab the IDS span sessions based on the // span ID. for _, mp := range c.spanSessions[idsPort.SwitchName] { if mp.Session == idsSpanID { ids.mp = append(ids.mp, spanPort{ port: mp, sw: idsPort.SwitchName, dc: idsPort.DataCenter, }) } } } } if len(ids.mp) == 0 { // At least record the ids device info, but also record an error because we couldn't find a // span session on any of the device ports. ids.devResp.Destination = append(ids.devResp.Destination, device{ Platform: ids.Device.Device.MakeModel, AccountID: ids.Device.Account.Number, AccountName: ids.Device.Account.Name, ID: strconv.Itoa(ids.Device.ID), }) ids.error(errNoDestSPAN) } } func (c *Client) getSPANSource(ids *idsDevice) { // Range through each span-session that is configured in on this switch for _, mp := range ids.mp { // Regex method to pull either the Vlan number or Port number from the Mirror-Port // config. Also return the type of port. portID, portType := getPortID(mp.port.Port) switch { // If the port type is a VLAN, then create the source vlan session. Destination VLAN // session do not exist, so this should just be for source. case portType == mirrorVlan: envInfo, err := c.dcx.DeviceEnvironmentData(ids.ID) if err != nil { ids.error(fmt.Sprintf("dcx.DeviceEnvironment (%d): %s", ids.ID, err)) break } for _, vlan := range envInfo.EnvironmentData.Vlans { // Match if we find that the vlan source ID matches a VLAN number // in the same environment as the IDS. if strconv.Itoa(vlan.VlanNumber) == portID { portName := fmt.Sprintf("%s[VLAN%s]", mp.sw, portID) if exist := ids.devResp.portExist(portName); exist { continue } ids.devResp.Source = append(ids.devResp.Source, device{ Port: portName, Platform: "None", AccountID: ids.Account.Number, AccountName: ids.Account.Name, ID: "None", SpanID: mp.port.Session, }) break } } case mp.port.Direction != "destination": portName := fmt.Sprintf("%s[%s]", mp.sw, portID) if exist := ids.devResp.portExist(portName); exist { logrus.Infof("port %s already exist", portName) break } portLookup, err := c.dcx.DeviceAndMACs(fmt.Sprintf("%s.%s", mp.sw, mp.dc), portID) if err != nil { ids.error(fmt.Sprintf("unable to track device for %s (%s)", portName, err)) break } ctkResp, err := c.ctk.CollectFromCTK([]int{portLookup.Device}, map[string]ebi.DeviceMRR{}) if err != nil { ids.error(fmt.Sprintf("unable to get CORE data for %d (%s)", portLookup.Device, err)) break } for _, ctkDev := range ctkResp { ids.devResp.Source = append(ids.devResp.Source, device{ Port: portName, Platform: ctkDev.Device.MakeModel, AccountID: ctkDev.Account.Number, AccountName: ctkDev.Account.Name, ID: strconv.Itoa(portLookup.Device), SpanID: mp.port.Session, }) } } } if len(ids.devResp.Source) == 0 { ids.error(errNoSrcSPAN) } } // vlanExist method will determine if a vlan has already been recorded for // a specific monitor session. func (c *spanAudit) portExist(portName string) bool { for _, port := range c.portsChecked { if port == portName { return true } } c.portsChecked = append(c.portsChecked, portName) return false } func (d *idsDevice) error(err string) { logrus.Error(err) d.devResp.Errors = append(d.devResp.Errors, err) } func getPortID(port string) (number, portType string) { if res := mirrorPortRe.FindStringSubmatch(port); res != nil { if len(res) != 0 { number = res[1] portType = mirrorInt } } if res := mirrorVlanRe.FindStringSubmatch(port); res != nil { if len(res) > 1 { number = res[2] portType = mirrorVlan } } return }