diff --git a/.gitignore b/.gitignore index 3715a4f..93a406b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ vpp-snmp-agent govpp-snmp-example +vppcfg.yaml diff --git a/README.md b/README.md index 3826065..8670a7d 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ CGO_ENABLED=0 go build -ldflags "-X main.version=${VERSION} -X main.buildTime=${ |------|---------|-------------| | `-agentx-addr` | `localhost:705` | AgentX master agent address (hostname:port or Unix socket path) | | `-debug` | `false` | Enable debug logging | +| `-vppcfg` | `""` | VPP configuration YAML file to read interface descriptions from | #### VPP Statistics Module Flags @@ -99,15 +100,48 @@ CGO_ENABLED=0 go build -ldflags "-X main.version=${VERSION} -X main.buildTime=${ # Custom interface index offset (start at 2000) ./vpp-snmp-agent -vppstats.ifindex-offset 2000 +# With VPP configuration file for interface descriptions +./vpp-snmp-agent -vppcfg /etc/vpp/vppcfg.yaml + # Full configuration ./vpp-snmp-agent \ -agentx-addr /var/agentx/master \ -debug \ + -vppcfg /etc/vpp/vppcfg.yaml \ -vppstats.addr /var/run/vpp/stats.sock \ -vppstats.period 5 \ -vppstats.ifindex-offset 1000 ``` +## VPP Configuration File + +The `-vppcfg` flag accepts a YAML configuration file that describes VPP interfaces and their descriptions. This file is used to populate the `ifAlias` (.18) field in the ifXTable with meaningful interface descriptions. + +### YAML Format Example + +```yaml +interfaces: + GigabitEthernet82/0/0: + description: 'Infra: Management interface' + TenGigabitEthernet1/0/2: + description: 'Infra: Core uplink' + sub-interfaces: + 100: + description: 'Cust: Customer VLAN 100' + 200: + description: 'Transit: Provider VLAN 200' +loopbacks: + loop0: + description: 'Core: Router loopback' +``` + +### Description Mapping + +- **Main interfaces**: Use the `description` field directly +- **Sub-interfaces**: Use the `description` field from the `sub-interfaces` section +- **Loopbacks**: Use the `description` field from the `loopbacks` section +- **Fallback**: If no description is found, the interface name is used as the alias + ## SNMP Interface Mapping VPP interfaces are mapped to SNMP indices with a configurable offset (default 1000): @@ -135,6 +169,7 @@ The application implements the ifXTable (1.3.6.1.2.1.31.1.1.1) with the followin | `.11.{index}` | ifHCOutUcastPkts | Counter64 | TX unicast packets (high capacity) | | `.12.{index}` | ifHCOutMulticastPkts | Counter64 | TX multicast packets (high capacity) | | `.13.{index}` | ifHCOutBroadcastPkts | Counter64 | TX broadcast packets (high capacity) | +| `.18.{index}` | ifAlias | DisplayString | Interface description/alias (from VPP config or interface name) | ## SNMP Query Examples @@ -148,6 +183,16 @@ snmpwalk -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1.1 snmpget -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1.1.1000 ``` +### Query Interface Descriptions + +```bash +# Get all interface descriptions/aliases +snmpwalk -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1.18 + +# Get specific interface description (interface 0 with default offset) +snmpget -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1.18.1000 +``` + ### Query Interface Counters ```bash diff --git a/go.mod b/go.mod index 41831c8..3e14401 100644 --- a/go.mod +++ b/go.mod @@ -7,11 +7,13 @@ toolchain go1.23.10 require ( github.com/posteo/go-agentx v0.2.1 go.fd.io/govpp v0.12.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff // indirect + github.com/kr/text v0.2.0 // indirect github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe // indirect github.com/sirupsen/logrus v1.9.3 // indirect golang.org/x/sys v0.31.0 // indirect diff --git a/go.sum b/go.sum index 2d7299f..cfe7f80 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -7,6 +8,10 @@ github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff h1:zk1wwii7uXmI0znwU+ github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff/go.mod h1:yUhRXHewUVJ1k89wHKP68xfzk7kwXUx/DV1nx4EBMbw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe h1:ewr1srjRCmcQogPQ/NCx6XCk6LGVmsVCc9Y3vvPZj+Y= github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= @@ -29,6 +34,8 @@ golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ifmib/ifmib.go b/ifmib/ifmib.go index f3c4184..7b819d0 100644 --- a/ifmib/ifmib.go +++ b/ifmib/ifmib.go @@ -2,6 +2,7 @@ package ifmib import ( "fmt" + "io/ioutil" "sync" "time" @@ -9,6 +10,7 @@ import ( "github.com/posteo/go-agentx/pdu" "github.com/posteo/go-agentx/value" "go.fd.io/govpp/api" + "gopkg.in/yaml.v3" "govpp-snmp-example/logger" "govpp-snmp-example/vppstats" @@ -56,22 +58,36 @@ import ( // ifHCOutUcastPkts .11 - Counter64 // ifHCOutMulticastPkts .12 - Counter64 // ifHCOutBroadcastPkts .13 - Counter64 +// ifAlias .18 - DisplayString const ifEntryOID = "1.3.6.1.2.1.2.2.1" const ifXTableOID = "1.3.6.1.2.1.31.1.1.1" +// VPP Config YAML structures +type VPPConfig struct { + Interfaces map[string]VPPInterface `yaml:"interfaces"` + Loopbacks map[string]VPPInterface `yaml:"loopbacks"` +} + +type VPPInterface struct { + Description string `yaml:"description"` + SubInterfaces map[string]VPPInterface `yaml:"sub-interfaces"` +} + type InterfaceMIB struct { mutex sync.RWMutex handler *agentx.ListHandler ifEntrySession *agentx.Session ifXTableSession *agentx.Session stats map[uint32]*api.InterfaceCounters // indexed by interface index + descriptions map[string]string // interface name -> description mapping } func NewInterfaceMIB() *InterfaceMIB { return &InterfaceMIB{ - handler: &agentx.ListHandler{}, - stats: make(map[uint32]*api.InterfaceCounters), + handler: &agentx.ListHandler{}, + stats: make(map[uint32]*api.InterfaceCounters), + descriptions: make(map[string]string), } } @@ -79,6 +95,51 @@ func (m *InterfaceMIB) GetHandler() *agentx.ListHandler { return m.handler } +func (m *InterfaceMIB) LoadVPPConfig(configPath string) error { + m.mutex.Lock() + defer m.mutex.Unlock() + + // Read YAML file + data, err := ioutil.ReadFile(configPath) + if err != nil { + return fmt.Errorf("failed to read VPP config file: %v", err) + } + + // Parse YAML + var config VPPConfig + if err := yaml.Unmarshal(data, &config); err != nil { + return fmt.Errorf("failed to parse VPP config YAML: %v", err) + } + + // Extract interface descriptions + for ifName, ifConfig := range config.Interfaces { + if ifConfig.Description != "" { + m.descriptions[ifName] = ifConfig.Description + logger.Debugf("Loaded description for interface %s: %s", ifName, ifConfig.Description) + } + + // Process sub-interfaces + for subID, subConfig := range ifConfig.SubInterfaces { + if subConfig.Description != "" { + subIfName := fmt.Sprintf("%s.%s", ifName, subID) + m.descriptions[subIfName] = subConfig.Description + logger.Debugf("Loaded description for sub-interface %s: %s", subIfName, subConfig.Description) + } + } + } + + // Extract loopback descriptions + for ifName, ifConfig := range config.Loopbacks { + if ifConfig.Description != "" { + m.descriptions[ifName] = ifConfig.Description + logger.Debugf("Loaded description for loopback %s: %s", ifName, ifConfig.Description) + } + } + + logger.Printf("Loaded %d interface descriptions from VPP config", len(m.descriptions)) + return nil +} + func (m *InterfaceMIB) UpdateStats(interfaceStats *api.InterfaceStats) { m.mutex.Lock() defer m.mutex.Unlock() @@ -298,6 +359,16 @@ func (m *InterfaceMIB) addIfXTable(iface *api.InterfaceCounters, idx int) { item = m.handler.Add(fmt.Sprintf("%s.13.%d", ifXTableOID, idx)) item.Type = pdu.VariableTypeCounter64 item.Value = iface.TxBroadcast.Packets + + // ifAlias (.18) - Interface description/alias + item = m.handler.Add(fmt.Sprintf("%s.18.%d", ifXTableOID, idx)) + item.Type = pdu.VariableTypeOctetString + // Use description from VPP config if available, otherwise use interface name + if desc, exists := m.descriptions[iface.InterfaceName]; exists { + item.Value = desc + } else { + item.Value = iface.InterfaceName + } } func (m *InterfaceMIB) RegisterWithClient(client *agentx.Client) error { diff --git a/main.go b/main.go index bad1d9d..2dd437d 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( func main() { addr := flag.String("agentx-addr", "localhost:705", "Address to connect to (hostname:port or Unix socket path)") debug := flag.Bool("debug", false, "Enable debug logging") + vppcfg := flag.String("vppcfg", "", "VPP configuration YAML file to read interface descriptions from") flag.Parse() // Set global debug flag @@ -40,6 +41,13 @@ func main() { // Create the interface MIB interfaceMIB := ifmib.NewInterfaceMIB() + // Load VPP config if specified + if *vppcfg != "" { + if err := interfaceMIB.LoadVPPConfig(*vppcfg); err != nil { + log.Fatalf("Failed to load VPP config: %v", err) + } + } + // Register the interface MIB with the AgentX client if err := interfaceMIB.RegisterWithClient(client); err != nil { log.Fatalf("Failed to register interface MIB: %v", err)