diff --git a/cmd/netbox-oxidized-sync/netbox-oxidized-sync.go b/cmd/netbox-oxidized-sync/netbox-oxidized-sync.go index 455b402..1da28d1 100644 --- a/cmd/netbox-oxidized-sync/netbox-oxidized-sync.go +++ b/cmd/netbox-oxidized-sync/netbox-oxidized-sync.go @@ -2,7 +2,6 @@ package main import ( "log" - "log/slog" "slices" "strconv" @@ -29,7 +28,7 @@ func worker(id int, jobs <-chan httphelper.OxidizedNode, results chan<- int, net log.Println("IOS not supported for now") case "FortiOS": log.Printf("Device: '%s' has fortiOS", j.Name) - fortigateInterfaces,_ := configparser.ParseFortiOSConfig(&config) + fortigateInterfaces, _ := configparser.ParseFortiOSConfig(&config) var netboxDevice = (*netboxdevices)[idx] netboxInterfaceForDevice := netboxhttp.GetIntefacesForDevice(strconv.Itoa(netboxDevice.ID)) netboxVlansForSite, err := netboxhttp.GetVlansForSite(strconv.Itoa(netboxDevice.Site.ID)) diff --git a/internal/configparser/fortios.go b/internal/configparser/fortios.go index 2c2c536..a76c6fa 100644 --- a/internal/configparser/fortios.go +++ b/internal/configparser/fortios.go @@ -21,17 +21,21 @@ const ( intefaceMember = " set member " interfaceStatus = " set status " interfaceDescription = " set description " + virtualSwitchPortPrefix = " edit " ) func ParseFortiOSConfig(config *string) (*[]model.FortigateInterface, error) { const ( - start = "config system interface" - end = "end" + start = "config system interface" + end = "end" + startVirtualSwitch = "config system virtual-switch" ) var ( - configInterfacesTracking bool - configInterfaces []string + configVirtualSwitchTracking bool + configInterfacesTracking bool + configInterfaces []string + configVirtualSwitch []string ) scanner := bufio.NewScanner(strings.NewReader(*config)) @@ -40,18 +44,93 @@ func ParseFortiOSConfig(config *string) (*[]model.FortigateInterface, error) { switch { case configInterfacesTracking && line == end: configInterfacesTracking = false + case configVirtualSwitchTracking && line == end: + configVirtualSwitchTracking = false + case configVirtualSwitchTracking: + configVirtualSwitch = append(configVirtualSwitch, line) case configInterfacesTracking: configInterfaces = append(configInterfaces, line) case line == start: configInterfacesTracking = true + case line == startVirtualSwitch: + configVirtualSwitchTracking = true } } deviceInterfaces := parseInterfaces(configInterfaces) + deviceVirtualSwitches := parseVirtualSwitch(configVirtualSwitch) + convertVirtualSwitch(deviceVirtualSwitches, deviceInterfaces) return deviceInterfaces, nil } +func parseVirtualSwitch(virtualSwitches []string) *[]model.FortigateVirtualSwitch{ + + var deviceVirtualSwitches []model.FortigateVirtualSwitch + + var ( + configVirtualSwitch []string + configVirtualSwitchTracking bool + ) + + for _, element := range virtualSwitches { + if strings.HasPrefix(element, interfaceNamePrefix) { + configVirtualSwitchTracking = true + configVirtualSwitch = []string{element} + continue + } + + if strings.HasPrefix(element, " next") { + if configVirtualSwitchTracking { + configVirtualSwitchTracking = false + parseSingleVirtualSwitch(configVirtualSwitch, &deviceVirtualSwitches) + } + continue + } + + if configVirtualSwitchTracking { + configVirtualSwitch = append(configVirtualSwitch, element) + } + } + return &deviceVirtualSwitches +} + +func parseSingleVirtualSwitch(virtualSwitchData []string, results *[]model.FortigateVirtualSwitch) { + var name string + var portNames []string + + portPrefixes := map[string]*[]string{ + virtualSwitchPortPrefix: &portNames, + } + + prefixes := map[string]*string{ + interfaceNamePrefix: &name, + } + + for _, element := range virtualSwitchData { + for prefix, value := range prefixes { + if strings.HasPrefix(element, prefix) { + *value = getElementValue(element, prefix) + } + } + + for portPrefix, portValue := range portPrefixes { + if strings.HasPrefix(element, portPrefix) { + *portValue = append(*portValue, getElementValue(element, portPrefix)) + } + } + } + + if name == "''" { + return + } + + var vSwitch model.FortigateVirtualSwitch + vSwitch.Name = name + vSwitch.Members = portNames + *results = append(*results, vSwitch) +} + func parseInterfaces(interfaces []string) *[]model.FortigateInterface { var deviceInterfaces []model.FortigateInterface @@ -89,6 +168,28 @@ func getElementValue(element string, filter string) string { return strings.ReplaceAll(strings.ReplaceAll(element, filter, ""), "\"", "") } +func convertVirtualSwitch(virtutalSwitches *[]model.FortigateVirtualSwitch, deviceInterfaces *[]model.FortigateInterface ) { + + var virtualSwitchNames = map[string]string{} + + for _, member := range *virtutalSwitches { + var vswitch model.FortigateInterface + vswitch.Name = member.Name + vswitch.InterfaceType = "bridge" + vswitch.Members = member.Members + *deviceInterfaces = append(*deviceInterfaces, vswitch) + for _, vswitchMember := range member.Members { + virtualSwitchNames[vswitchMember] = member.Name + } + } + + for index, dinterface := range *deviceInterfaces { + if virtualSwitchNames[dinterface.Name] != "" { + (*deviceInterfaces)[index].Parent = virtualSwitchNames[dinterface.Name] + } + } +} + func parseSingleInterface(interfaceData []string, results *[]model.FortigateInterface) { var name, interfaceType, vlanId, parentName, alias, vdom, ip, speed, member, status, description string @@ -155,7 +256,7 @@ func createVlan(name string, alias string, vdom string, vlanId string, parentNam vid.Name = alias } else { vid.Name = name - } + } vid.Description = createDescription(alias, vdom, description) vid.VlanId = vlanId vid.Parent = parentName diff --git a/internal/model/DeviceInterface.go b/internal/model/DeviceInterface.go index 191ab9e..736c933 100644 --- a/internal/model/DeviceInterface.go +++ b/internal/model/DeviceInterface.go @@ -34,7 +34,10 @@ type NetboxInterface struct { } `json:"type"` Enabled bool `json:"enabled"` Parent interface{} `json:"parent"` - Bridge interface{} `json:"bridge"` + Bridge struct{ + ID int `json:"id"` + Name string `json:"name"` + } `json:"bridge"` Lag struct { ID int `json:"id"` URL string `json:"url"` @@ -199,6 +202,11 @@ type NetboxDevice struct { InventoryItemCount int `json:"inventory_item_count"` } +type FortigateVirtualSwitch struct { + Name string + Members []string +} + type NetboxInterfaceUpdateCreate struct { DeviceId string PortType string diff --git a/internal/netboxparser/fortitonetbox.go b/internal/netboxparser/fortitonetbox.go index 57600fd..6a1bb1c 100644 --- a/internal/netboxparser/fortitonetbox.go +++ b/internal/netboxparser/fortitonetbox.go @@ -36,16 +36,28 @@ func processPort(port model.FortigateInterface, allMembers map[string]int, forti } if port.InterfaceType == "physical" && len(allMembers) > 0 { if parentIndex, ok := allMembers[port.Name]; ok { - if netboxInterface.Lag.ID == 0 { - matched.Parent = (*fortiInterfaces)[parentIndex].Name - } else { - if !strings.EqualFold(netboxInterface.Lag.Name, (*fortiInterfaces)[parentIndex].Name) { + if (*fortiInterfaces)[parentIndex].InterfaceType == "aggregate" { + if netboxInterface.Lag.ID == 0 { + matched.Parent = (*fortiInterfaces)[parentIndex].Name + } else { + if !strings.EqualFold(netboxInterface.Lag.Name, (*fortiInterfaces)[parentIndex].Name) { + matched.Parent = (*fortiInterfaces)[parentIndex].Name + } + } + } else if (*fortiInterfaces)[parentIndex].InterfaceType == "bridge" { + if netboxInterface.Bridge.ID == 0 { matched.Parent = (*fortiInterfaces)[parentIndex].Name + } else { + if !strings.EqualFold(netboxInterface.Bridge.Name, (*fortiInterfaces)[parentIndex].Name) { + matched.Parent = (*fortiInterfaces)[parentIndex].Name + } } } + if matched.Parent != "" { matched.ParentId = getParentID(matched.Parent, netboxDeviceInterfaces) } + } } if port.InterfaceType == "vlan" {