Add -vppcfg flag to set ifAlias from the 'description' fields in vppcfg.yaml
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
vpp-snmp-agent
|
||||
govpp-snmp-example
|
||||
vppcfg.yaml
|
||||
|
45
README.md
45
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
|
||||
|
2
go.mod
2
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
|
||||
|
7
go.sum
7
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=
|
||||
|
@ -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 {
|
||||
|
8
main.go
8
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)
|
||||
|
Reference in New Issue
Block a user