Compare commits
17 Commits
4fdd0769a5
...
main
Author | SHA1 | Date | |
---|---|---|---|
ccc2b5ad4d | |||
4f368e625d | |||
35165b0464 | |||
42dbbded3d | |||
f16a2b41ea | |||
5533ab00de | |||
1cbca296c4 | |||
a73c7cbf91 | |||
1ddc77ec73 | |||
6063db7311 | |||
7f81b51c1f | |||
c0bcdd5449 | |||
fa437ddaf1 | |||
0b4ff36130 | |||
82db92f344 | |||
adf033318a | |||
6969e609c0 |
10
.gitignore
vendored
10
.gitignore
vendored
@ -1,2 +1,12 @@
|
||||
govpp-snmp-agentx
|
||||
vppcfg.yaml
|
||||
|
||||
# Debian packaging artifacts
|
||||
debian/.debhelper/
|
||||
debian/.gocache/
|
||||
debian/go/
|
||||
debian/govpp-snmp-agentx/
|
||||
debian/files
|
||||
debian/*.substvars
|
||||
debian/debhelper-build-stamp
|
||||
debian/*.debhelper
|
||||
|
22
Makefile
Normal file
22
Makefile
Normal file
@ -0,0 +1,22 @@
|
||||
PROG = govpp-snmp-agentx
|
||||
|
||||
.PHONY: build test clean pkg-deb
|
||||
|
||||
# Build the binary
|
||||
build:
|
||||
cd src && go build -o ../$(PROG) .
|
||||
|
||||
# Run all tests
|
||||
test:
|
||||
cd src && go test ./...
|
||||
|
||||
# Clean build artifacts
|
||||
clean:
|
||||
rm -f $(PROG)
|
||||
[ -d debian/go ] && chmod -R +w debian/go || true
|
||||
rm -rf debian/.debhelper debian/.gocache debian/go debian/$(PROG) debian/files debian/*.substvars debian/debhelper-build-stamp
|
||||
rm -f ../$(PROG)_*.deb ../$(PROG)_*.changes ../$(PROG)_*.buildinfo
|
||||
|
||||
# Build Debian package
|
||||
pkg-deb:
|
||||
fakeroot dpkg-buildpackage -us -uc -b
|
321
README.md
321
README.md
@ -1,315 +1,46 @@
|
||||
# VPP SNMP AgentX Bridge
|
||||
|
||||
A Go application that bridges VPP (Vector Packet Processing) interface statistics to SNMP using the
|
||||
AgentX protocol. It queries VPP interface counters and exposes them via the standard IF-MIB
|
||||
for SNMP monitoring.
|
||||
SNMP AgentX daemon that exposes VPP interface statistics via standard IF-MIB.
|
||||
|
||||
## Features
|
||||
|
||||
- **Real-time VPP interface statistics** via SNMP
|
||||
- **Standard IF-MIB compliance** (ifXTable)
|
||||
- **AgentX protocol support** (TCP and Unix sockets)
|
||||
- **Configurable interface index offset** to avoid conflicts
|
||||
- **Configurable polling intervals**
|
||||
- **Thread-safe operation** with proper synchronization
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
VPP Stats Socket → VPP Stats Client → Interface MIB → AgentX → SNMPd
|
||||
```
|
||||
|
||||
The application consists of four main components:
|
||||
|
||||
1. **VPP Stats Client** (`vppstats/`): Connects to VPP stats socket and retrieves interface counters
|
||||
2. **Interface MIB** (`ifmib/`): Maps VPP statistics to SNMP IF-MIB structure
|
||||
3. **AgentX Client** (`agentx/`): Handles AgentX protocol connection and MIB registration
|
||||
4. **Main Application**: Orchestrates the components and handles configuration
|
||||
|
||||
## Build Instructions
|
||||
|
||||
### Development Build
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
go build -o govpp-snmp-agentx .
|
||||
# Build
|
||||
make build
|
||||
|
||||
# Test
|
||||
make test
|
||||
|
||||
# Create Debian package
|
||||
make pkg-deb
|
||||
|
||||
# Install package
|
||||
sudo dpkg -i ../govpp-snmp-agentx_*.deb
|
||||
```
|
||||
|
||||
### Static Binary Build
|
||||
## Configuration
|
||||
|
||||
For deployment without Go runtime dependencies:
|
||||
The Debian package installs a systemd service that reads configuration from `/etc/default/govpp-snmp-agentx`:
|
||||
|
||||
```bash
|
||||
# Linux static binary
|
||||
CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o govpp-snmp-agentx .
|
||||
# Edit service configuration
|
||||
sudo nano /etc/default/govpp-snmp-agentx
|
||||
|
||||
# Cross-compile for different architectures
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags '-extldflags "-static"' -o govpp-snmp-agentx-linux-amd64 .
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -a -ldflags '-extldflags "-static"' -o govpp-snmp-agentx-linux-arm64 .
|
||||
# Start service
|
||||
sudo systemctl start govpp-snmp-agentx
|
||||
```
|
||||
|
||||
### Release Build with Version Info
|
||||
|
||||
```bash
|
||||
VERSION=$(git describe --tags --always --dirty)
|
||||
BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S')
|
||||
CGO_ENABLED=0 go build -ldflags "-X main.version=${VERSION} -X main.buildTime=${BUILD_TIME}" -o govpp-snmp-agentx .
|
||||
Default configuration:
|
||||
```
|
||||
GOVPP_SNMP_AGENTX_FLAGS="-agentx.addr /var/agentx/master -vppcfg /etc/vpp/vppcfg.yaml -vppstats.period 10"
|
||||
```
|
||||
|
||||
## Usage
|
||||
## Documentation
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Run with default settings
|
||||
./govpp-snmp-agentx
|
||||
|
||||
# Run with custom AgentX address
|
||||
./govpp-snmp-agentx -agentx.addr 127.0.0.1:705
|
||||
|
||||
# Run with Unix socket AgentX connection
|
||||
./govpp-snmp-agentx -agentx.addr /var/agentx/master
|
||||
```
|
||||
|
||||
### Command Line Flags
|
||||
|
||||
#### General Application Flags
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `-debug` | `false` | Enable debug logging |
|
||||
| `-vppcfg` | `""` | VPP configuration YAML file to read interface descriptions from |
|
||||
|
||||
#### AgentX Module Flags
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `-agentx.addr` | `localhost:705` | AgentX master agent address (hostname:port or Unix socket path) |
|
||||
|
||||
#### VPP Statistics Module Flags
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `-vppstats.api.addr` | `/var/run/vpp/api.sock` | VPP API socket path |
|
||||
| `-vppstats.stats.addr` | `/var/run/vpp/stats.sock` | VPP statistics socket path |
|
||||
| `-vppstats.period` | `10` | Interval in seconds for querying VPP interface stats |
|
||||
| `-vppstats.ifindex-offset` | `1000` | Offset to add to VPP interface indices for SNMP |
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Enable debug logging
|
||||
./govpp-snmp-agentx -debug
|
||||
|
||||
# Custom polling interval (5 seconds)
|
||||
./govpp-snmp-agentx -vppstats.period 5
|
||||
|
||||
# Custom VPP stats socket
|
||||
./govpp-snmp-agentx -vppstats.stats.addr /custom/path/stats.sock
|
||||
|
||||
# Custom VPP API socket
|
||||
./govpp-snmp-agentx -vppstats.api.addr /custom/path/api.sock
|
||||
|
||||
# Custom interface index offset (start at 2000)
|
||||
./govpp-snmp-agentx -vppstats.ifindex-offset 2000
|
||||
|
||||
# With VPP configuration file for interface descriptions
|
||||
./govpp-snmp-agentx -vppcfg /etc/vpp/vppcfg.yaml
|
||||
|
||||
# Full configuration
|
||||
./govpp-snmp-agentx \
|
||||
-agentx.addr /var/agentx/master \
|
||||
-debug \
|
||||
-vppcfg /etc/vpp/vppcfg.yaml \
|
||||
-vppstats.api.addr /var/run/vpp/api.sock \
|
||||
-vppstats.stats.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):
|
||||
|
||||
- **VPP Interface 0** → **SNMP Index 1000**
|
||||
- **VPP Interface 1** → **SNMP Index 1001**
|
||||
- **VPP Interface N** → **SNMP Index (N + offset)**
|
||||
|
||||
## Supported MIB Objects
|
||||
|
||||
The application implements the ifXTable (1.3.6.1.2.1.31.1.1.1) with the following objects:
|
||||
|
||||
| OID | Object | Type | Description |
|
||||
|-----|--------|------|-------------|
|
||||
| `.1.{index}` | ifName | DisplayString | Interface name |
|
||||
| `.2.{index}` | ifInMulticastPkts | Counter32 | RX multicast packets |
|
||||
| `.3.{index}` | ifInBroadcastPkts | Counter32 | RX broadcast packets |
|
||||
| `.4.{index}` | ifOutMulticastPkts | Counter32 | TX multicast packets |
|
||||
| `.5.{index}` | ifOutBroadcastPkts | Counter32 | TX broadcast packets |
|
||||
| `.6.{index}` | ifHCInOctets | Counter64 | RX bytes (high capacity) |
|
||||
| `.7.{index}` | ifHCInUcastPkts | Counter64 | RX unicast packets (high capacity) |
|
||||
| `.8.{index}` | ifHCInMulticastPkts | Counter64 | RX multicast packets (high capacity) |
|
||||
| `.9.{index}` | ifHCInBroadcastPkts | Counter64 | RX broadcast packets (high capacity) |
|
||||
| `.10.{index}` | ifHCOutOctets | Counter64 | TX bytes (high capacity) |
|
||||
| `.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
|
||||
|
||||
### Query Interface Names
|
||||
|
||||
```bash
|
||||
# Get all interface names
|
||||
snmpwalk -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1.1
|
||||
|
||||
# Get specific interface name (interface 0 with default offset)
|
||||
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
|
||||
# Get RX bytes for interface 0
|
||||
snmpget -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1.6.1000
|
||||
|
||||
# Get TX bytes for interface 1
|
||||
snmpget -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1.10.1001
|
||||
|
||||
# Walk all interface counters
|
||||
snmpwalk -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1
|
||||
```
|
||||
|
||||
### Query with Custom Offset
|
||||
|
||||
If running with `-vppstats.ifindex-offset 2000`:
|
||||
|
||||
```bash
|
||||
# Interface 0 counters at index 2000
|
||||
snmpget -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1.6.2000
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### VPP Requirements
|
||||
|
||||
- VPP must be running with stats socket enabled
|
||||
- Stats socket accessible at `/var/run/vpp/stats.sock` (or custom path)
|
||||
- Application must have read permissions on the stats socket
|
||||
|
||||
### SNMP Requirements
|
||||
|
||||
- SNMP master agent running (net-snmp's snmpd)
|
||||
- AgentX protocol enabled in snmpd configuration
|
||||
- AgentX socket accessible (TCP port 705 or Unix socket)
|
||||
|
||||
### SNMP Agent Configuration
|
||||
|
||||
Add to `/etc/snmp/snmpd.conf`:
|
||||
|
||||
```
|
||||
# Enable AgentX
|
||||
master agentx
|
||||
agentXSocket tcp:localhost:705
|
||||
# or for Unix socket:
|
||||
# agentXSocket /var/agentx/master
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **"Failed to connect to VPP stats"**
|
||||
- Check VPP is running: `vppctl show version`
|
||||
- Verify stats socket exists: `ls -la /var/run/vpp/stats.sock`
|
||||
- Check permissions on stats socket
|
||||
|
||||
2. **"Failed to dial AgentX"**
|
||||
- Verify SNMP agent is running: `systemctl status snmpd`
|
||||
- Check AgentX is enabled in snmpd.conf
|
||||
- Test AgentX socket: `netstat -ln | grep 705`
|
||||
|
||||
3. **"Failed to register IF-MIB: ErrorDuplicateRegistration"**
|
||||
- Another agent is already serving the IF-MIB
|
||||
- Check with: `snmpwalk -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1`
|
||||
|
||||
4. **"Retrieved stats for 0 interfaces"**
|
||||
- VPP may have no interfaces configured
|
||||
- Check VPP interfaces: `vppctl show interface`
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# Check VPP interfaces
|
||||
vppctl show interface
|
||||
|
||||
# Check VPP stats
|
||||
vppctl show stats
|
||||
|
||||
# Test SNMP master agent
|
||||
snmpwalk -v2c -c public localhost 1.3.6.1.2.1.1
|
||||
|
||||
# Check AgentX registration
|
||||
snmpwalk -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1
|
||||
```
|
||||
- **Manual page**: `man govpp-snmp-agentx` (after package installation)
|
||||
- **Detailed documentation**: [docs/DETAILS.md](docs/DETAILS.md)
|
||||
|
||||
## License
|
||||
|
||||
This project uses the LGPL 3.0 licensed go-agentx library. It has been modified due to a bug,
|
||||
see details in [[GitHub PR#7](https://github.com/posteo/go-agentx/pull/11)], and as such is
|
||||
licensed also LGPL 3.0. The go-agentx source code in this project will be removed once the
|
||||
upstream PR is merged.
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Add tests if applicable
|
||||
5. Submit a pull request
|
||||
|
||||
## Version History
|
||||
|
||||
- **v1.0.0**: Initial release with IF-MIB support
|
||||
- **v1.1.0**: Added configurable interface index offset
|
||||
- **v1.2.0**: Added Unix socket support for AgentX
|
||||
LGPL 3.0 (due to modified go-agentx dependency)
|
||||
|
||||
|
46
debian/changelog
vendored
Normal file
46
debian/changelog
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
govpp-snmp-agentx (1.1.0-1) bookworm; urgency=medium
|
||||
|
||||
* Add interface event monitoring with VPP API integration
|
||||
* Populate IF-MIB with real interface details (MAC address, speed, status)
|
||||
* Consolidate VPP-related modules into unified vpp package
|
||||
* Implement real-time interface state updates via event-driven callbacks
|
||||
* Retrieve and expose actual interface properties: MTU, admin/oper status
|
||||
* Add comprehensive interface details caching and management
|
||||
|
||||
-- Pim van Pelt <pim@ipng.ch> Sun, 23 Jun 2025 00:00:00 +0000
|
||||
|
||||
govpp-snmp-agentx (1.0.3-1) bookworm; urgency=medium
|
||||
|
||||
* Remove unnecessary toolchain configuration
|
||||
* Simplify and shorten Makefile
|
||||
* Refactor documentation structure with detailed DETAILS.md
|
||||
* Update and improve README documentation
|
||||
|
||||
-- Pim van Pelt <pim@ipng.ch> Thu, 19 Jun 2025 00:00:00 +0000
|
||||
|
||||
govpp-snmp-agentx (1.0.2-1) bookworm; urgency=medium
|
||||
|
||||
* Reorganize source code into src/ subdirectory for cleaner project structure
|
||||
* Add environment file support (/etc/default/govpp-snmp-agentx)
|
||||
* Move service configuration to environment variables
|
||||
* Improve systemd service configurability
|
||||
|
||||
-- Pim van Pelt <pim@ipng.ch> Tue, 17 Jun 2025 00:55:00 +0000
|
||||
|
||||
govpp-snmp-agentx (1.0.1-1) bookworm; urgency=medium
|
||||
|
||||
* Add manual page for govpp-snmp-agentx(1)
|
||||
* Make VPP config file optional - log warning and continue if missing
|
||||
* Fix Debian package build reproducibility issues
|
||||
* Improve build system with proper cleanup targets
|
||||
|
||||
-- Pim van Pelt <pim@ipng.ch> Tue, 17 Jun 2025 00:35:00 +0000
|
||||
|
||||
govpp-snmp-agentx (1.0.0-1) bookworm; urgency=medium
|
||||
|
||||
* Initial release
|
||||
* SNMP AgentX daemon for VPP statistics
|
||||
* Interface MIB support
|
||||
* Systemd service integration
|
||||
|
||||
-- Pim van Pelt <pim@ipng.ch> Mon, 16 Jun 2025 00:00:00 +0000
|
23
debian/control
vendored
Normal file
23
debian/control
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
Source: govpp-snmp-agentx
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: Pim van Pelt <pim@ipng.ch>
|
||||
Build-Depends: debhelper-compat (= 13), golang-go (>= 1.23.8)
|
||||
Standards-Version: 4.6.2
|
||||
Homepage: https://git.ipng.ch/ipng/govpp-agentx-snmp
|
||||
Vcs-Git: https://git.ipng.ch/ipng/govpp-agentx-snmp
|
||||
Vcs-Browser: https://git.ipng.ch/ipng/govpp-agentx-snmp
|
||||
|
||||
Package: govpp-snmp-agentx
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}, snmpd
|
||||
Description: GoVPP SNMP AgentX Daemon
|
||||
A SNMP AgentX daemon that provides SNMP access to VPP (Vector Packet Processing)
|
||||
statistics and interface information. This daemon acts as a subagent that
|
||||
connects to the main SNMP daemon via the AgentX protocol.
|
||||
.
|
||||
Features:
|
||||
- Interface MIB support
|
||||
- VPP statistics exposure
|
||||
- AgentX protocol implementation
|
||||
- Systemd integration
|
12
debian/govpp-snmp-agentx.postrm.debhelper
vendored
Normal file
12
debian/govpp-snmp-agentx.postrm.debhelper
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
# Automatically added by dh_installsystemd/13.11.4
|
||||
if [ "$1" = remove ] && [ -d /run/systemd/system ] ; then
|
||||
systemctl --system daemon-reload >/dev/null || true
|
||||
fi
|
||||
# End automatically added section
|
||||
# Automatically added by dh_installsystemd/13.11.4
|
||||
if [ "$1" = "purge" ]; then
|
||||
if [ -x "/usr/bin/deb-systemd-helper" ]; then
|
||||
deb-systemd-helper purge 'govpp-snmp-agentx.service' >/dev/null || true
|
||||
fi
|
||||
fi
|
||||
# End automatically added section
|
23
debian/postinst
vendored
Executable file
23
debian/postinst
vendored
Executable file
@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
# Enable and start the service
|
||||
systemctl daemon-reload
|
||||
systemctl enable govpp-snmp-agentx.service
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
22
debian/prerm
vendored
Executable file
22
debian/prerm
vendored
Executable file
@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
case "$1" in
|
||||
remove|upgrade|deconfigure)
|
||||
systemctl stop govpp-snmp-agentx.service || true
|
||||
systemctl disable govpp-snmp-agentx.service || true
|
||||
;;
|
||||
|
||||
failed-upgrade)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "prerm called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
30
debian/rules
vendored
Executable file
30
debian/rules
vendored
Executable file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
export DH_VERBOSE = 1
|
||||
export GO111MODULE = on
|
||||
export GOPROXY = direct
|
||||
export GOCACHE = $(CURDIR)/debian/.gocache
|
||||
export GOPATH = $(CURDIR)/debian/go
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_build:
|
||||
cd src && go build -v -ldflags="-s -w" -o ../govpp-snmp-agentx .
|
||||
|
||||
override_dh_auto_install:
|
||||
install -D -m 0755 govpp-snmp-agentx debian/govpp-snmp-agentx/usr/sbin/govpp-snmp-agentx
|
||||
install -D -m 0644 govpp-snmp-agentx.service debian/govpp-snmp-agentx/lib/systemd/system/govpp-snmp-agentx.service
|
||||
install -D -m 0644 govpp-snmp-agentx.default debian/govpp-snmp-agentx/etc/default/govpp-snmp-agentx
|
||||
install -D -m 0644 docs/govpp-snmp-agentx.1 debian/govpp-snmp-agentx/usr/share/man/man1/govpp-snmp-agentx.1
|
||||
|
||||
override_dh_auto_configure:
|
||||
# Skip auto configure
|
||||
|
||||
override_dh_auto_test:
|
||||
# Skip tests during packaging
|
||||
|
||||
override_dh_auto_clean:
|
||||
rm -f govpp-snmp-agentx
|
||||
[ -d debian/go ] && chmod -R +w debian/go || true
|
||||
rm -rf debian/.gocache debian/go obj-*
|
266
docs/DETAILS.md
Normal file
266
docs/DETAILS.md
Normal file
@ -0,0 +1,266 @@
|
||||
# VPP SNMP AgentX Bridge - Detailed Documentation
|
||||
|
||||
## Features
|
||||
|
||||
- **Real-time VPP interface statistics** via SNMP
|
||||
- **Standard IF-MIB compliance** (ifXTable)
|
||||
- **AgentX protocol support** (TCP and Unix sockets)
|
||||
- **Configurable interface index offset** to avoid conflicts
|
||||
- **Configurable polling intervals**
|
||||
- **Thread-safe operation** with proper synchronization
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
VPP Stats Socket → VPP Stats Client → Interface MIB → AgentX → SNMPd
|
||||
```
|
||||
|
||||
The application consists of four main components:
|
||||
|
||||
1. **VPP Stats Client** (`src/vppstats/`): Connects to VPP stats socket and retrieves interface counters
|
||||
2. **Interface MIB** (`src/ifmib/`): Maps VPP statistics to SNMP IF-MIB structure
|
||||
3. **AgentX Client** (`src/agentx/`): Handles AgentX protocol connection and MIB registration
|
||||
4. **Main Application** (`src/main.go`): Orchestrates the components and handles configuration
|
||||
|
||||
## Command Line Flags
|
||||
|
||||
### General Application Flags
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `-debug` | `false` | Enable debug logging |
|
||||
| `-vppcfg` | `""` | VPP configuration YAML file to read interface descriptions from |
|
||||
|
||||
### AgentX Module Flags
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `-agentx.addr` | `localhost:705` | AgentX master agent address (hostname:port or Unix socket path) |
|
||||
|
||||
### VPP Statistics Module Flags
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `-vppstats.api.addr` | `/var/run/vpp/api.sock` | VPP API socket path |
|
||||
| `-vppstats.stats.addr` | `/var/run/vpp/stats.sock` | VPP statistics socket path |
|
||||
| `-vppstats.period` | `10` | Interval in seconds for querying VPP interface stats |
|
||||
| `-vppstats.ifindex-offset` | `1000` | Offset to add to VPP interface indices for SNMP |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
```bash
|
||||
# Enable debug logging
|
||||
./govpp-snmp-agentx -debug
|
||||
|
||||
# Custom polling interval (5 seconds)
|
||||
./govpp-snmp-agentx -vppstats.period 5
|
||||
|
||||
# Custom VPP stats socket
|
||||
./govpp-snmp-agentx -vppstats.stats.addr /custom/path/stats.sock
|
||||
|
||||
# Custom VPP API socket
|
||||
./govpp-snmp-agentx -vppstats.api.addr /custom/path/api.sock
|
||||
|
||||
# Custom interface index offset (start at 2000)
|
||||
./govpp-snmp-agentx -vppstats.ifindex-offset 2000
|
||||
|
||||
# With VPP configuration file for interface descriptions
|
||||
./govpp-snmp-agentx -vppcfg /etc/vpp/vppcfg.yaml
|
||||
|
||||
# Full configuration
|
||||
./govpp-snmp-agentx \
|
||||
-agentx.addr /var/agentx/master \
|
||||
-debug \
|
||||
-vppcfg /etc/vpp/vppcfg.yaml \
|
||||
-vppstats.api.addr /var/run/vpp/api.sock \
|
||||
-vppstats.stats.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):
|
||||
|
||||
- **VPP Interface 0** → **SNMP Index 1000**
|
||||
- **VPP Interface 1** → **SNMP Index 1001**
|
||||
- **VPP Interface N** → **SNMP Index (N + offset)**
|
||||
|
||||
## Supported MIB Objects
|
||||
|
||||
The application implements the ifXTable (1.3.6.1.2.1.31.1.1.1) with the following objects:
|
||||
|
||||
| OID | Object | Type | Description |
|
||||
|-----|--------|------|-------------|
|
||||
| `.1.{index}` | ifName | DisplayString | Interface name |
|
||||
| `.2.{index}` | ifInMulticastPkts | Counter32 | RX multicast packets |
|
||||
| `.3.{index}` | ifInBroadcastPkts | Counter32 | RX broadcast packets |
|
||||
| `.4.{index}` | ifOutMulticastPkts | Counter32 | TX multicast packets |
|
||||
| `.5.{index}` | ifOutBroadcastPkts | Counter32 | TX broadcast packets |
|
||||
| `.6.{index}` | ifHCInOctets | Counter64 | RX bytes (high capacity) |
|
||||
| `.7.{index}` | ifHCInUcastPkts | Counter64 | RX unicast packets (high capacity) |
|
||||
| `.8.{index}` | ifHCInMulticastPkts | Counter64 | RX multicast packets (high capacity) |
|
||||
| `.9.{index}` | ifHCInBroadcastPkts | Counter64 | RX broadcast packets (high capacity) |
|
||||
| `.10.{index}` | ifHCOutOctets | Counter64 | TX bytes (high capacity) |
|
||||
| `.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
|
||||
|
||||
### Query Interface Names
|
||||
|
||||
```bash
|
||||
# Get all interface names
|
||||
snmpwalk -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1.1
|
||||
|
||||
# Get specific interface name (interface 0 with default offset)
|
||||
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
|
||||
# Get RX bytes for interface 0
|
||||
snmpget -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1.6.1000
|
||||
|
||||
# Get TX bytes for interface 1
|
||||
snmpget -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1.10.1001
|
||||
|
||||
# Walk all interface counters
|
||||
snmpwalk -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1
|
||||
```
|
||||
|
||||
### Query with Custom Offset
|
||||
|
||||
If running with `-vppstats.ifindex-offset 2000`:
|
||||
|
||||
```bash
|
||||
# Interface 0 counters at index 2000
|
||||
snmpget -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1.6.2000
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### VPP Requirements
|
||||
|
||||
- VPP must be running with stats socket enabled
|
||||
- Stats socket accessible at `/var/run/vpp/stats.sock` (or custom path)
|
||||
- Application must have read permissions on the stats socket
|
||||
|
||||
### SNMP Requirements
|
||||
|
||||
- SNMP master agent running (net-snmp's snmpd)
|
||||
- AgentX protocol enabled in snmpd configuration
|
||||
- AgentX socket accessible (TCP port 705 or Unix socket)
|
||||
|
||||
### SNMP Agent Configuration
|
||||
|
||||
Add to `/etc/snmp/snmpd.conf`:
|
||||
|
||||
```
|
||||
# Enable AgentX
|
||||
master agentx
|
||||
agentXSocket tcp:localhost:705
|
||||
# or for Unix socket:
|
||||
# agentXSocket /var/agentx/master
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **"Failed to connect to VPP stats"**
|
||||
- Check VPP is running: `vppctl show version`
|
||||
- Verify stats socket exists: `ls -la /var/run/vpp/stats.sock`
|
||||
- Check permissions on stats socket
|
||||
|
||||
2. **"Failed to dial AgentX"**
|
||||
- Verify SNMP agent is running: `systemctl status snmpd`
|
||||
- Check AgentX is enabled in snmpd.conf
|
||||
- Test AgentX socket: `netstat -ln | grep 705`
|
||||
|
||||
3. **"Failed to register IF-MIB: ErrorDuplicateRegistration"**
|
||||
- Another agent is already serving the IF-MIB
|
||||
- Check with: `snmpwalk -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1`
|
||||
|
||||
4. **"Retrieved stats for 0 interfaces"**
|
||||
- VPP may have no interfaces configured
|
||||
- Check VPP interfaces: `vppctl show interface`
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# Check VPP interfaces
|
||||
vppctl show interface
|
||||
|
||||
# Check VPP stats
|
||||
vppctl show stats
|
||||
|
||||
# Test SNMP master agent
|
||||
snmpwalk -v2c -c public localhost 1.3.6.1.2.1.1
|
||||
|
||||
# Check AgentX registration
|
||||
snmpwalk -v2c -c public localhost 1.3.6.1.2.1.31.1.1.1
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project uses the LGPL 3.0 licensed go-agentx library. It has been modified due to a bug,
|
||||
see details in [[GitHub PR#7](https://github.com/posteo/go-agentx/pull/11)], and as such is
|
||||
licensed also LGPL 3.0. The go-agentx source code in this project will be removed once the
|
||||
upstream PR is merged.
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Add tests if applicable
|
||||
5. Submit a pull request
|
||||
|
||||
## Version History
|
||||
|
||||
- **v1.0.0**: Initial release with IF-MIB support
|
||||
- **v1.1.0**: Added configurable interface index offset
|
||||
- **v1.2.0**: Added Unix socket support for AgentX
|
72
docs/govpp-snmp-agentx.1
Normal file
72
docs/govpp-snmp-agentx.1
Normal file
@ -0,0 +1,72 @@
|
||||
.TH GOVPP-SNMP-AGENTX 1 "June 2025" "version 1.0.0" "User Commands"
|
||||
.SH NAME
|
||||
govpp-snmp-agentx \- VPP SNMP AgentX daemon for interface statistics
|
||||
.SH SYNOPSIS
|
||||
.B govpp-snmp-agentx
|
||||
[\fIOPTION\fR]...
|
||||
.SH DESCRIPTION
|
||||
.B govpp-snmp-agentx
|
||||
is an SNMP AgentX subagent that provides SNMP access to VPP (Vector Packet Processing) interface statistics and information. It connects to a master SNMP daemon via the AgentX protocol and populates standard IF-MIB tables with real-time VPP interface data.
|
||||
.PP
|
||||
The daemon implements two MIB tables:
|
||||
.TP
|
||||
.B ifEntry
|
||||
Classic interface table (1.3.6.1.2.1.2.2.1) with basic interface statistics including counters for packets, bytes, errors, and discards.
|
||||
.TP
|
||||
.B ifXTable
|
||||
Extended interface table (1.3.6.1.2.1.31.1.1.1) with high-capacity 64-bit counters and additional interface information.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-agentx.addr " " \fIADDRESS\fR
|
||||
AgentX master agent address. Can be a TCP address (hostname:port) or Unix socket path.
|
||||
Default: localhost:705
|
||||
.TP
|
||||
.BR \-debug
|
||||
Enable debug logging to show detailed operational information.
|
||||
.TP
|
||||
.BR \-vppcfg " " \fIFILE\fR
|
||||
VPP configuration YAML file to read interface descriptions from.
|
||||
.TP
|
||||
.BR \-vppstats.api.addr " " \fIPATH\fR
|
||||
VPP API socket path for interface enumeration.
|
||||
Default: /var/run/vpp/api.sock
|
||||
.TP
|
||||
.BR \-vppstats.stats.addr " " \fIPATH\fR
|
||||
VPP statistics socket path for interface counters.
|
||||
Default: /var/run/vpp/stats.sock
|
||||
.TP
|
||||
.BR \-vppstats.ifindex-offset " " \fINUMBER\fR
|
||||
Offset added to VPP interface indices for SNMP interface numbering.
|
||||
Default: 1000
|
||||
.TP
|
||||
.BR \-vppstats.period " " \fISECONDS\fR
|
||||
Interval in seconds for querying VPP interface statistics.
|
||||
Default: 10
|
||||
.SH EXAMPLES
|
||||
.TP
|
||||
Connect to AgentX master via TCP:
|
||||
.B govpp-snmp-agentx -agentx.addr snmp.example.com:705
|
||||
.TP
|
||||
Connect via Unix socket with debug logging:
|
||||
.B govpp-snmp-agentx -agentx.addr /var/agentx/master -debug
|
||||
.TP
|
||||
Use custom VPP sockets and config:
|
||||
.B govpp-snmp-agentx -vppstats.api.addr /opt/vpp/api.sock -vppcfg /etc/vpp/vppcfg.yaml
|
||||
.SH FILES
|
||||
.TP
|
||||
.I /var/run/vpp/api.sock
|
||||
Default VPP API socket
|
||||
.TP
|
||||
.I /var/run/vpp/stats.sock
|
||||
Default VPP statistics socket
|
||||
.TP
|
||||
.I /var/agentx/master
|
||||
Common AgentX Unix socket path
|
||||
.SH SEE ALSO
|
||||
.BR snmpd (8),
|
||||
.BR snmpwalk (1),
|
||||
.BR vpp (8)
|
||||
.SH AUTHOR
|
||||
Pim van Pelt <pim@ipng.ch>
|
||||
.SH COPYRIGHT
|
||||
Copyright 2025, IPng Networks GmbH
|
15
govpp-snmp-agentx.default
Normal file
15
govpp-snmp-agentx.default
Normal file
@ -0,0 +1,15 @@
|
||||
# Default configuration for govpp-snmp-agentx
|
||||
# This file contains environment variables for the GoVPP SNMP AgentX daemon
|
||||
#
|
||||
# Command line flags for govpp-snmp-agentx
|
||||
GOVPP_SNMP_AGENTX_FLAGS="-agentx.addr /var/agentx/master -vppcfg /etc/vpp/vppcfg.yaml -vppstats.period 10"
|
||||
|
||||
# Additional options that can be added to GOVPP_SNMP_AGENTX_FLAGS:
|
||||
# -debug Enable debug logging
|
||||
# -agentx.addr host:port SNMPd Agentx address (example: localhost:705)
|
||||
# -vppstats.api.addr PATH VPP API socket path (default: /var/run/vpp/api.sock)
|
||||
# -vppstats.stats.addr PATH VPP stats socket path (default: /var/run/vpp/stats.sock)
|
||||
# -vppstats.ifindex-offset NUM Interface index offset (default: 1000)
|
||||
#
|
||||
# Example with debug logging:
|
||||
# GOVPP_SNMP_AGENTX_FLAGS="-agentx.addr localhost:705 -vppcfg /etc/vpp/vppcfg.yaml -vppstats.period 10 -debug"
|
18
govpp-snmp-agentx.service
Normal file
18
govpp-snmp-agentx.service
Normal file
@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=GoVPP SNMP AgentX Daemon
|
||||
After=network.target vpp.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
EnvironmentFile=-/etc/default/govpp-snmp-agentx
|
||||
ExecStartPre=-+/usr/bin/chmod 770 /var/agentx /var/agentx/master
|
||||
ExecStartPre=-+/usr/bin/chown Debian-snmp:vpp /var/agentx /var/agentx/master
|
||||
ExecStart=/usr/sbin/govpp-snmp-agentx $GOVPP_SNMP_AGENTX_FLAGS
|
||||
User=Debian-snmp
|
||||
Group=vpp
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -41,6 +41,7 @@ func TestFlagRegistration(t *testing.T) {
|
||||
f := flag.Lookup("agentx.addr")
|
||||
if f == nil {
|
||||
t.Error("Expected agentx.addr flag to be registered")
|
||||
return
|
||||
}
|
||||
|
||||
if f.DefValue != "localhost:705" {
|
@ -2,8 +2,6 @@ module govpp-snmp-agentx
|
||||
|
||||
go 1.23.8
|
||||
|
||||
toolchain go1.23.10
|
||||
|
||||
require (
|
||||
github.com/posteo/go-agentx v0.2.1
|
||||
go.fd.io/govpp v0.12.0
|
@ -4,7 +4,7 @@ package ifmib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -15,7 +15,7 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"govpp-snmp-agentx/logger"
|
||||
"govpp-snmp-agentx/vppstats"
|
||||
"govpp-snmp-agentx/vpp"
|
||||
)
|
||||
|
||||
// IF-MIB OID bases:
|
||||
@ -83,6 +83,7 @@ type InterfaceMIB struct {
|
||||
ifXTableSession *agentx.Session
|
||||
stats map[uint32]*api.InterfaceCounters // indexed by interface index
|
||||
descriptions map[string]string // interface name -> description mapping
|
||||
interfaceDetails map[uint32]*vpp.InterfaceDetails // indexed by interface index
|
||||
}
|
||||
|
||||
func NewInterfaceMIB() *InterfaceMIB {
|
||||
@ -90,6 +91,7 @@ func NewInterfaceMIB() *InterfaceMIB {
|
||||
handler: &agentx.ListHandler{},
|
||||
stats: make(map[uint32]*api.InterfaceCounters),
|
||||
descriptions: make(map[string]string),
|
||||
interfaceDetails: make(map[uint32]*vpp.InterfaceDetails),
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,7 +104,7 @@ func (m *InterfaceMIB) LoadVPPConfig(configPath string) error {
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
// Read YAML file
|
||||
data, err := ioutil.ReadFile(configPath)
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read VPP config file: %v", err)
|
||||
}
|
||||
@ -142,6 +144,22 @@ func (m *InterfaceMIB) LoadVPPConfig(configPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *InterfaceMIB) UpdateInterfaceDetails(details []vpp.InterfaceDetails) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
logger.Debugf("Updating interface details for %d interfaces", len(details))
|
||||
|
||||
// Update interface details map
|
||||
for _, detail := range details {
|
||||
m.interfaceDetails[uint32(detail.SwIfIndex)] = &detail
|
||||
logger.Debugf("Updated details for interface %d (%s): MAC=%x, Speed=%d",
|
||||
detail.SwIfIndex, detail.InterfaceName, detail.MacAddress, detail.Speed)
|
||||
}
|
||||
|
||||
logger.Debugf("Interface details updated for %d interfaces", len(details))
|
||||
}
|
||||
|
||||
func (m *InterfaceMIB) UpdateStats(interfaceStats *api.InterfaceStats) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
@ -172,7 +190,7 @@ func (m *InterfaceMIB) UpdateStats(interfaceStats *api.InterfaceStats) {
|
||||
}
|
||||
|
||||
func (m *InterfaceMIB) addInterfaceToMIB(iface *api.InterfaceCounters) {
|
||||
idx := int(iface.InterfaceIndex) + *vppstats.IfIndexOffset
|
||||
idx := int(iface.InterfaceIndex) + *vpp.IfIndexOffset
|
||||
|
||||
// Add ifEntry (classic interface table) entries
|
||||
m.addIfEntry(iface, idx)
|
||||
@ -186,6 +204,9 @@ func (m *InterfaceMIB) addInterfaceToMIB(iface *api.InterfaceCounters) {
|
||||
func (m *InterfaceMIB) addIfEntry(iface *api.InterfaceCounters, idx int) {
|
||||
var item *agentx.ListItem
|
||||
|
||||
// Get interface details if available
|
||||
details := m.interfaceDetails[iface.InterfaceIndex]
|
||||
|
||||
// ifIndex (.1)
|
||||
item = m.handler.Add(fmt.Sprintf("%s.1.%d", ifEntryOID, idx))
|
||||
item.Type = pdu.VariableTypeInteger
|
||||
@ -201,30 +222,58 @@ func (m *InterfaceMIB) addIfEntry(iface *api.InterfaceCounters, idx int) {
|
||||
item.Type = pdu.VariableTypeInteger
|
||||
item.Value = int32(6)
|
||||
|
||||
// ifMtu (.4) - Default MTU 1500
|
||||
// ifMtu (.4) - Use real MTU if available, otherwise default to 1500
|
||||
mtu := int32(1500)
|
||||
if details != nil {
|
||||
mtu = int32(details.MTU)
|
||||
}
|
||||
item = m.handler.Add(fmt.Sprintf("%s.4.%d", ifEntryOID, idx))
|
||||
item.Type = pdu.VariableTypeInteger
|
||||
item.Value = int32(1500)
|
||||
item.Value = mtu
|
||||
|
||||
// ifSpeed (.5) - Default to 1Gbps (1000000000 bits/sec)
|
||||
// ifSpeed (.5) - Use real speed if available, otherwise default to 1Gbps
|
||||
speed := uint32(1000000000)
|
||||
if details != nil && details.Speed > 0 {
|
||||
speed = uint32(details.Speed)
|
||||
}
|
||||
item = m.handler.Add(fmt.Sprintf("%s.5.%d", ifEntryOID, idx))
|
||||
item.Type = pdu.VariableTypeGauge32
|
||||
item.Value = uint32(1000000000)
|
||||
item.Value = speed
|
||||
|
||||
// ifPhysAddress (.6) - Empty for now
|
||||
// ifPhysAddress (.6) - Use real MAC address if available
|
||||
macAddr := ""
|
||||
if details != nil && len(details.MacAddress) > 0 {
|
||||
macAddr = string(details.MacAddress)
|
||||
}
|
||||
item = m.handler.Add(fmt.Sprintf("%s.6.%d", ifEntryOID, idx))
|
||||
item.Type = pdu.VariableTypeOctetString
|
||||
item.Value = ""
|
||||
item.Value = macAddr
|
||||
|
||||
// ifAdminStatus (.7) - up(1)
|
||||
// ifAdminStatus (.7) - Use real admin status if available
|
||||
adminStatus := int32(1) // default up
|
||||
if details != nil {
|
||||
if details.AdminStatus {
|
||||
adminStatus = 1 // up
|
||||
} else {
|
||||
adminStatus = 2 // down
|
||||
}
|
||||
}
|
||||
item = m.handler.Add(fmt.Sprintf("%s.7.%d", ifEntryOID, idx))
|
||||
item.Type = pdu.VariableTypeInteger
|
||||
item.Value = int32(1)
|
||||
item.Value = adminStatus
|
||||
|
||||
// ifOperStatus (.8) - up(1)
|
||||
// ifOperStatus (.8) - Use real operational status if available
|
||||
operStatus := int32(1) // default up
|
||||
if details != nil {
|
||||
if details.OperStatus {
|
||||
operStatus = 1 // up
|
||||
} else {
|
||||
operStatus = 2 // down
|
||||
}
|
||||
}
|
||||
item = m.handler.Add(fmt.Sprintf("%s.8.%d", ifEntryOID, idx))
|
||||
item.Type = pdu.VariableTypeInteger
|
||||
item.Value = int32(1)
|
||||
item.Value = operStatus
|
||||
|
||||
// ifLastChange (.9) - 0 (unknown)
|
||||
item = m.handler.Add(fmt.Sprintf("%s.9.%d", ifEntryOID, idx))
|
@ -26,7 +26,7 @@ func TestPrintf(t *testing.T) {
|
||||
|
||||
// Read captured output
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
_, _ = io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Check output format: "INFO file.go:function message"
|
||||
@ -64,7 +64,7 @@ func TestDebugfWithDebugEnabled(t *testing.T) {
|
||||
|
||||
// Read captured output
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
_, _ = io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Check output format: "DEBUG file.go:function message"
|
||||
@ -98,7 +98,7 @@ func TestDebugfWithDebugDisabled(t *testing.T) {
|
||||
|
||||
// Read captured output
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
_, _ = io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Should be empty when debug is disabled
|
@ -13,7 +13,7 @@ import (
|
||||
"govpp-snmp-agentx/config"
|
||||
"govpp-snmp-agentx/ifmib"
|
||||
"govpp-snmp-agentx/logger"
|
||||
"govpp-snmp-agentx/vppstats"
|
||||
"govpp-snmp-agentx/vpp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -30,7 +30,8 @@ func main() {
|
||||
// Load VPP config if specified
|
||||
if *vppcfg != "" {
|
||||
if err := interfaceMIB.LoadVPPConfig(*vppcfg); err != nil {
|
||||
log.Fatalf("Failed to load VPP config: %v", err)
|
||||
logger.Printf("Warning: Failed to load VPP config from %s: %v", *vppcfg, err)
|
||||
logger.Printf("Continuing without VPP config file...")
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,8 +40,11 @@ func main() {
|
||||
log.Fatalf("Failed to start AgentX: %v", err)
|
||||
}
|
||||
|
||||
// Set up interface event callback to update interface details
|
||||
vpp.SetInterfaceEventCallback(interfaceMIB.UpdateInterfaceDetails)
|
||||
|
||||
// Start VPP stats routine with callback to update MIB
|
||||
vppstats.StartStatsRoutine(interfaceMIB.UpdateStats)
|
||||
vpp.StartStatsRoutine(interfaceMIB.UpdateStats)
|
||||
|
||||
// Set up signal handling for graceful shutdown
|
||||
sigChan := make(chan os.Signal, 1)
|
145
src/vpp/vpp_iface.go
Normal file
145
src/vpp/vpp_iface.go
Normal file
@ -0,0 +1,145 @@
|
||||
// Copyright 2025, IPng Networks GmbH, Pim van Pelt <pim@ipng.ch>
|
||||
|
||||
package vpp
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"go.fd.io/govpp/api"
|
||||
interfaces "go.fd.io/govpp/binapi/interface"
|
||||
"go.fd.io/govpp/binapi/interface_types"
|
||||
|
||||
"govpp-snmp-agentx/logger"
|
||||
)
|
||||
|
||||
// InterfaceDetails holds detailed information about a VPP interface
|
||||
type InterfaceDetails struct {
|
||||
SwIfIndex interface_types.InterfaceIndex
|
||||
InterfaceName string
|
||||
MacAddress []byte
|
||||
Speed uint64
|
||||
AdminStatus bool
|
||||
OperStatus bool
|
||||
MTU uint32
|
||||
}
|
||||
|
||||
// InterfaceEventCallback is called when interface events occur
|
||||
type InterfaceEventCallback func(details []InterfaceDetails)
|
||||
|
||||
// GetAllInterfaceDetails retrieves detailed information for all interfaces
|
||||
func GetAllInterfaceDetails(ch api.Channel) ([]InterfaceDetails, error) {
|
||||
logger.Debugf("Retrieving all interface details from VPP")
|
||||
|
||||
// Get all interfaces
|
||||
reqCtx := ch.SendMultiRequest(&interfaces.SwInterfaceDump{
|
||||
SwIfIndex: ^interface_types.InterfaceIndex(0), // All interfaces
|
||||
})
|
||||
|
||||
var details []InterfaceDetails
|
||||
|
||||
for {
|
||||
iface := &interfaces.SwInterfaceDetails{}
|
||||
stop, err := reqCtx.ReceiveReply(iface)
|
||||
if stop {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
logger.Debugf("Error retrieving interface details: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert VPP interface flags to admin/oper status
|
||||
adminUp := (iface.Flags & interface_types.IF_STATUS_API_FLAG_ADMIN_UP) != 0
|
||||
operUp := (iface.Flags & interface_types.IF_STATUS_API_FLAG_LINK_UP) != 0
|
||||
|
||||
detail := InterfaceDetails{
|
||||
SwIfIndex: iface.SwIfIndex,
|
||||
InterfaceName: string(iface.InterfaceName),
|
||||
MacAddress: iface.L2Address[:],
|
||||
Speed: uint64(iface.LinkSpeed) * 1000, // Convert Kbps to bps
|
||||
AdminStatus: adminUp,
|
||||
OperStatus: operUp,
|
||||
MTU: uint32(iface.LinkMtu),
|
||||
}
|
||||
|
||||
details = append(details, detail)
|
||||
|
||||
logger.Debugf("Interface %d (%s): MAC=%x, Speed=%d, Admin=%t, Oper=%t, MTU=%d",
|
||||
detail.SwIfIndex, detail.InterfaceName, detail.MacAddress,
|
||||
detail.Speed, detail.AdminStatus, detail.OperStatus, detail.MTU)
|
||||
}
|
||||
|
||||
logger.Debugf("Retrieved details for %d interfaces", len(details))
|
||||
return details, nil
|
||||
}
|
||||
|
||||
func WatchInterfaceEvents(ch api.Channel, callback InterfaceEventCallback) error {
|
||||
logger.Debugf("WatchInterfaceEvents() called - starting interface event monitoring")
|
||||
|
||||
notifChan := make(chan api.Message, 100)
|
||||
|
||||
// subscribe for specific event message
|
||||
logger.Debugf("Subscribing to interface events...")
|
||||
sub, err := ch.SubscribeNotification(notifChan, &interfaces.SwInterfaceEvent{})
|
||||
if err != nil {
|
||||
logger.Debugf("error subscribing to interface events: %v", err)
|
||||
return err
|
||||
}
|
||||
logger.Debugf("Successfully subscribed to interface events")
|
||||
|
||||
// enable interface events in VPP
|
||||
logger.Debugf("Enabling interface events in VPP...")
|
||||
err = ch.SendRequest(&interfaces.WantInterfaceEvents{
|
||||
PID: uint32(os.Getpid()),
|
||||
EnableDisable: 1,
|
||||
}).ReceiveReply(&interfaces.WantInterfaceEventsReply{})
|
||||
if err != nil {
|
||||
logger.Debugf("error enabling interface events: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Debugf("Interface events enabled in VPP, starting event listener goroutine")
|
||||
|
||||
// receive notifications
|
||||
go func() {
|
||||
logger.Debugf("Interface event listener goroutine started")
|
||||
defer func() {
|
||||
logger.Debugf("Interface event listener goroutine shutting down")
|
||||
// disable interface events in VPP
|
||||
err = ch.SendRequest(&interfaces.WantInterfaceEvents{
|
||||
PID: uint32(os.Getpid()),
|
||||
EnableDisable: 0,
|
||||
}).ReceiveReply(&interfaces.WantInterfaceEventsReply{})
|
||||
if err != nil {
|
||||
logger.Debugf("error disabling interface events: %v", err)
|
||||
}
|
||||
|
||||
// unsubscribe from receiving events
|
||||
err = sub.Unsubscribe()
|
||||
if err != nil {
|
||||
logger.Debugf("error unsubscribing from interface events: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
logger.Debugf("Interface event listener waiting for events...")
|
||||
for notif := range notifChan {
|
||||
e := notif.(*interfaces.SwInterfaceEvent)
|
||||
logger.Debugf("interface event: SwIfIndex=%d, Flags=%d, Deleted=%t",
|
||||
e.SwIfIndex, e.Flags, e.Deleted)
|
||||
|
||||
// When an interface event occurs, retrieve all interface details and call callback
|
||||
if callback != nil {
|
||||
details, err := GetAllInterfaceDetails(ch)
|
||||
if err != nil {
|
||||
logger.Debugf("Failed to retrieve interface details after event: %v", err)
|
||||
} else {
|
||||
logger.Debugf("Calling interface event callback with %d interfaces", len(details))
|
||||
callback(details)
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.Debugf("Interface event listener goroutine ended")
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
// Copyright 2025, IPng Networks GmbH, Pim van Pelt <pim@ipng.ch>
|
||||
|
||||
package vppstats
|
||||
package vpp
|
||||
|
||||
import (
|
||||
"flag"
|
||||
@ -17,6 +17,9 @@ import (
|
||||
|
||||
type StatsCallback func(*api.InterfaceStats)
|
||||
|
||||
// Global callback for interface events
|
||||
var interfaceEventCallback InterfaceEventCallback
|
||||
|
||||
var (
|
||||
// Flags for VPP stats configuration
|
||||
ApiAddr = flag.String("vppstats.api.addr", "/var/run/vpp/api.sock", "VPP API socket path")
|
||||
@ -25,6 +28,11 @@ var (
|
||||
Period = flag.Int("vppstats.period", 10, "Interval in seconds for querying VPP interface stats")
|
||||
)
|
||||
|
||||
// SetInterfaceEventCallback sets the callback for interface events
|
||||
func SetInterfaceEventCallback(callback InterfaceEventCallback) {
|
||||
interfaceEventCallback = callback
|
||||
}
|
||||
|
||||
// StartStatsRoutine starts a goroutine that queries VPP interface stats at the configured interval
|
||||
func StartStatsRoutine(callback StatsCallback) {
|
||||
period := time.Duration(*Period) * time.Second
|
||||
@ -131,6 +139,32 @@ func statsRoutine(period time.Duration, callback StatsCallback) {
|
||||
logger.Printf("Connected to VPP (API: %s, Stats: %s)", *ApiAddr, *StatsAddr)
|
||||
connected = true
|
||||
wasConnected = true
|
||||
|
||||
// Start watching interface events
|
||||
logger.Debugf("Creating API channel for interface events...")
|
||||
ch, err := conn.NewAPIChannel()
|
||||
if err != nil {
|
||||
logger.Debugf("Failed to create API channel for interface events: %v", err)
|
||||
} else {
|
||||
logger.Debugf("API channel created successfully, calling WatchInterfaceEvents...")
|
||||
if err := WatchInterfaceEvents(ch, interfaceEventCallback); err != nil {
|
||||
logger.Debugf("Failed to start interface event watching: %v", err)
|
||||
ch.Close()
|
||||
} else {
|
||||
logger.Printf("Interface event watching started successfully")
|
||||
|
||||
// Do initial retrieval of interface details
|
||||
if interfaceEventCallback != nil {
|
||||
details, err := GetAllInterfaceDetails(ch)
|
||||
if err != nil {
|
||||
logger.Debugf("Failed to get initial interface details: %v", err)
|
||||
} else {
|
||||
logger.Debugf("Retrieved initial interface details for %d interfaces", len(details))
|
||||
interfaceEventCallback(details)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Query stats if connected
|
||||
@ -142,10 +176,7 @@ func statsRoutine(period time.Duration, callback StatsCallback) {
|
||||
}
|
||||
|
||||
// Wait for next tick
|
||||
select {
|
||||
case <-ticker.C:
|
||||
continue
|
||||
}
|
||||
<-ticker.C
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,4 +265,3 @@ func checkVPPLiveness(conn *core.Connection) bool {
|
||||
logger.Debugf("VPP liveness check passed (version: %s)", string(reply.Version))
|
||||
return true
|
||||
}
|
||||
|
@ -1,163 +0,0 @@
|
||||
// Copyright 2025, IPng Networks GmbH, Pim van Pelt <pim@ipng.ch>
|
||||
|
||||
package vppstats
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.fd.io/govpp/api"
|
||||
)
|
||||
|
||||
func TestVPPStatsFlags(t *testing.T) {
|
||||
// Test default values
|
||||
if *ApiAddr != "/var/run/vpp/api.sock" {
|
||||
t.Errorf("Expected default API address to be '/var/run/vpp/api.sock', got '%s'", *ApiAddr)
|
||||
}
|
||||
|
||||
if *StatsAddr != "/var/run/vpp/stats.sock" {
|
||||
t.Errorf("Expected default stats address to be '/var/run/vpp/stats.sock', got '%s'", *StatsAddr)
|
||||
}
|
||||
|
||||
if *IfIndexOffset != 1000 {
|
||||
t.Errorf("Expected default interface index offset to be 1000, got %d", *IfIndexOffset)
|
||||
}
|
||||
|
||||
if *Period != 10 {
|
||||
t.Errorf("Expected default period to be 10, got %d", *Period)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagRegistrations(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
flagName string
|
||||
defValue string
|
||||
}{
|
||||
{"API address", "vppstats.api.addr", "/var/run/vpp/api.sock"},
|
||||
{"Stats address", "vppstats.stats.addr", "/var/run/vpp/stats.sock"},
|
||||
{"Index offset", "vppstats.ifindex-offset", "1000"},
|
||||
{"Period", "vppstats.period", "10"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f := flag.Lookup(tt.flagName)
|
||||
if f == nil {
|
||||
t.Errorf("Expected %s flag to be registered", tt.flagName)
|
||||
return
|
||||
}
|
||||
|
||||
if f.DefValue != tt.defValue {
|
||||
t.Errorf("Expected %s flag default value to be '%s', got '%s'",
|
||||
tt.flagName, tt.defValue, f.DefValue)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatsCallbackType(t *testing.T) {
|
||||
// Test that we can create a valid callback function
|
||||
var called bool
|
||||
var receivedStats *api.InterfaceStats
|
||||
|
||||
callback := func(stats *api.InterfaceStats) {
|
||||
called = true
|
||||
receivedStats = stats
|
||||
}
|
||||
|
||||
// Create mock stats
|
||||
mockStats := &api.InterfaceStats{
|
||||
Interfaces: []api.InterfaceCounters{
|
||||
{
|
||||
InterfaceIndex: 1,
|
||||
InterfaceName: "test",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Call the callback
|
||||
callback(mockStats)
|
||||
|
||||
if !called {
|
||||
t.Error("Expected callback to be called")
|
||||
}
|
||||
|
||||
if receivedStats != mockStats {
|
||||
t.Error("Expected callback to receive the same stats object")
|
||||
}
|
||||
|
||||
if len(receivedStats.Interfaces) != 1 {
|
||||
t.Errorf("Expected 1 interface, got %d", len(receivedStats.Interfaces))
|
||||
}
|
||||
|
||||
if receivedStats.Interfaces[0].InterfaceName != "test" {
|
||||
t.Errorf("Expected interface name 'test', got '%s'", receivedStats.Interfaces[0].InterfaceName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeriodConversion(t *testing.T) {
|
||||
// Test that period conversion works correctly
|
||||
originalPeriod := *Period
|
||||
defer func() { *Period = originalPeriod }()
|
||||
|
||||
testPeriods := []struct {
|
||||
input int
|
||||
expected time.Duration
|
||||
}{
|
||||
{1, time.Second},
|
||||
{5, 5 * time.Second},
|
||||
{10, 10 * time.Second},
|
||||
{60, time.Minute},
|
||||
}
|
||||
|
||||
for _, tt := range testPeriods {
|
||||
t.Run(fmt.Sprintf("period_%d", tt.input), func(t *testing.T) {
|
||||
*Period = tt.input
|
||||
result := time.Duration(*Period) * time.Second
|
||||
|
||||
if result != tt.expected {
|
||||
t.Errorf("Expected period %v, got %v", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagValues(t *testing.T) {
|
||||
// Save original flag values
|
||||
originalApiAddr := *ApiAddr
|
||||
originalStatsAddr := *StatsAddr
|
||||
originalOffset := *IfIndexOffset
|
||||
originalPeriod := *Period
|
||||
|
||||
defer func() {
|
||||
*ApiAddr = originalApiAddr
|
||||
*StatsAddr = originalStatsAddr
|
||||
*IfIndexOffset = originalOffset
|
||||
*Period = originalPeriod
|
||||
}()
|
||||
|
||||
// Test setting flag values
|
||||
*ApiAddr = "/custom/api.sock"
|
||||
*StatsAddr = "/custom/stats.sock"
|
||||
*IfIndexOffset = 2000
|
||||
*Period = 30
|
||||
|
||||
if *ApiAddr != "/custom/api.sock" {
|
||||
t.Errorf("Expected API address to be '/custom/api.sock', got '%s'", *ApiAddr)
|
||||
}
|
||||
|
||||
if *StatsAddr != "/custom/stats.sock" {
|
||||
t.Errorf("Expected stats address to be '/custom/stats.sock', got '%s'", *StatsAddr)
|
||||
}
|
||||
|
||||
if *IfIndexOffset != 2000 {
|
||||
t.Errorf("Expected interface index offset to be 2000, got %d", *IfIndexOffset)
|
||||
}
|
||||
|
||||
if *Period != 30 {
|
||||
t.Errorf("Expected period to be 30, got %d", *Period)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user