How to Install and Configure SNMP for BIRD BGP

28 Jun

Views: 4

Bird BGP Workflow

I’ve used BIRD as the routing engine for several projects, including my own company, AMSCLOUD, and Superserver.

When I needed better visibility into BGP sessions, peer states, and prefix updates in monitoring tools such as Cacti and LibreNMS, I ran into one important limitation: BIRD does not ship with native SNMP support.

The workaround is to expose selected BIRD data over SNMP using Net-SNMP and a lightweight exporter that parses birdc output. This approach allows an NMS to poll BGP peer health using custom OIDs, create graphs, and generate alerts when a BGP session goes down.

In this article, I’ll walk through the installation, configuration, exporter script, SNMP OID layout, testing, and NMS integration.


1. Architecture Overview

The monitoring flow is simple:

+----------------+        birdc         +---------------------+
| BIRD Routing   |  ----------------->  | BIRD SNMP Exporter  |
| Daemon         |                      | Script              |
+----------------+                      +----------+----------+
                                                   |
                                                   | pass_persist / AgentX
                                                   v
                                        +----------+----------+
                                        | Net-SNMP snmpd     |
                                        +----------+----------+
                                                   |
                                                   | SNMP polling
                                                   v
                                        +----------+----------+
                                        | LibreNMS / Cacti   |
                                        +---------------------+

BIRD remains the routing daemon. Net-SNMP remains the SNMP daemon. The exporter script acts as a bridge between the two systems.

The exporter reads the BGP state from BIRD using birdc, converts the data into numeric SNMP values, and exposes them under a private enterprise OID.


2. What Will Be Monitored

This setup focuses on operational BGP health, especially:

  • BGP peer state
  • BGP session up/down status
  • Imported prefixes
  • Exported prefixes
  • Preferred routes
  • Last protocol change timestamp, if available
  • Peer-level health for graphing and alerting

Example use cases:

  • Alert when a BGP peer changes from Established to Idle
  • Graph received prefixes per peer
  • Graph exported prefixes per peer
  • Compare prefix count before and after maintenance
  • Monitor route server or transit session health

3. Prerequisites

This guide assumes the server is already running BIRD or BIRD2.

Example environment:

Operating system : Debian / Ubuntu / Rocky Linux / AlmaLinux / FreeBSD
Routing daemon   : BIRD 2.x
SNMP daemon      : Net-SNMP
Monitoring       : LibreNMS or Cacti

Required packages:

sudo apt update
sudo apt install -y snmp snmpd snmp-mibs-downloader python3

For RHEL-based systems:

sudo dnf install -y net-snmp net-snmp-utils python3

For FreeBSD:

pkg install net-snmp python3

4. Verify BIRD Control Socket

Before integrating SNMP, make sure birdc can read protocol status.

Run:

birdc show protocols

Example output:

BIRD 2.15.1 ready.
Name       Proto      Table      State  Since         Info
device1    Device     ---        up     2026-06-20
kernel1    Kernel     master4    up     2026-06-20
bgp_ntt    BGP        ---        up     2026-06-20    Established
bgp_google BGP        ---        up     2026-06-20    Established
bgp_meta   BGP        ---        start  2026-06-20    Active

Also verify detailed protocol output:

birdc show protocols all bgp_ntt

Example output:

Name:       bgp_ntt
Proto:      BGP
Table:      master4
State:      up
Since:      2026-06-20 10:22:44
Info:       Established
Preference: 100
Input filter: ACCEPT
Output filter: ACCEPT
Routes:     950123 imported, 12 filtered, 10 exported, 950123 preferred
BGP state:          Established
Neighbor address:   203.0.113.1
Neighbor AS:        10217
Local AS:           59139

If birdc fails, fix BIRD first before continuing.

Common checks:

systemctl status bird
systemctl status bird6
systemctl status bird2
ls -lah /run/bird/
ls -lah /var/run/bird/

Depending on the operating system and BIRD package, the socket may be located at:

/run/bird/bird.ctl
/var/run/bird/bird.ctl
/usr/local/var/run/bird.ctl

5. Choose a Private Enterprise OID

For custom SNMP data, use a private enterprise OID subtree.

Example OID used in this article:

.1.3.6.1.4.1.55555.1.1

Example structure:

.1.3.6.1.4.1.55555.1.1.1.<peerIndex> = peer name
.1.3.6.1.4.1.55555.1.1.2.<peerIndex> = peer state numeric
.1.3.6.1.4.1.55555.1.1.3.<peerIndex> = imported prefixes
.1.3.6.1.4.1.55555.1.1.4.<peerIndex> = exported prefixes
.1.3.6.1.4.1.55555.1.1.5.<peerIndex> = preferred prefixes

State mapping:

1 = Idle / Down
2 = Connect
3 = Active
4 = OpenSent
5 = OpenConfirm
6 = Established

For alerting, the most important value is:

6 = Established

Any value other than 6 can be considered degraded or down, depending on your policy.


6. Create the BIRD SNMP Exporter Script

Create the script:

sudo mkdir -p /usr/local/libexec
sudo nano /usr/local/libexec/bird-snmp-passpersist.py

Paste the following script:

#!/usr/bin/env python3

import subprocess
import time
import re
import sys

BASE_OID = ".1.3.6.1.4.1.55555.1.1"
BIRDC = "/usr/sbin/birdc"
CACHE_TTL = 15

STATE_MAP = {
    "Idle": 1,
    "Connect": 2,
    "Active": 3,
    "OpenSent": 4,
    "OpenConfirm": 5,
    "Established": 6,
}

cache_time = 0
cache_data = {}


def run_cmd(cmd):
    try:
        output = subprocess.check_output(
            cmd,
            stderr=subprocess.DEVNULL,
            text=True,
            timeout=5
        )
        return output
    except Exception:
        return ""


def get_bgp_protocols():
    output = run_cmd([BIRDC, "show", "protocols"])
    peers = []

    for line in output.splitlines():
        line = line.strip()

        if not line:
            continue

        if line.startswith("BIRD") or line.startswith("Name"):
            continue

        parts = line.split()

        if len(parts) < 2:
            continue

        name = parts[0]
        proto = parts[1]

        if proto == "BGP":
            peers.append(name)

    return peers


def parse_peer_detail(peer):
    output = run_cmd([BIRDC, "show", "protocols", "all", peer])

    bgp_state = "Idle"
    imported = 0
    exported = 0
    preferred = 0

    state_match = re.search(r"BGP state:\s+([A-Za-z]+)", output)
    if state_match:
        bgp_state = state_match.group(1)
    else:
        info_match = re.search(r"Info:\s+([A-Za-z]+)", output)
        if info_match:
            bgp_state = info_match.group(1)

    routes_match = re.search(
        r"Routes:\s+(\d+)\s+imported.*?(\d+)\s+exported.*?(\d+)\s+preferred",
        output,
        re.IGNORECASE
    )

    if routes_match:
        imported = int(routes_match.group(1))
        exported = int(routes_match.group(2))
        preferred = int(routes_match.group(3))

    return {
        "name": peer,
        "state_text": bgp_state,
        "state": STATE_MAP.get(bgp_state, 1),
        "imported": imported,
        "exported": exported,
        "preferred": preferred,
    }


def build_cache():
    global cache_time, cache_data

    now = time.time()

    if now - cache_time < CACHE_TTL:
        return cache_data

    data = {}
    peers = get_bgp_protocols()

    index = 1
    for peer in peers:
        detail = parse_peer_detail(peer)

        data[f"{BASE_OID}.1.{index}"] = ("string", detail["name"])
        data[f"{BASE_OID}.2.{index}"] = ("integer", detail["state"])
        data[f"{BASE_OID}.3.{index}"] = ("integer", detail["imported"])
        data[f"{BASE_OID}.4.{index}"] = ("integer", detail["exported"])
        data[f"{BASE_OID}.5.{index}"] = ("integer", detail["preferred"])

        index += 1

    cache_data = dict(sorted(data.items()))
    cache_time = now

    return cache_data


def get_next_oid(current_oid):
    data = build_cache()
    oids = list(data.keys())

    for oid in oids:
        if oid > current_oid:
            return oid

    return None


def print_value(oid):
    data = build_cache()

    if oid not in data:
        print("NONE")
        sys.stdout.flush()
        return

    value_type, value = data[oid]

    print(oid)
    print(value_type)
    print(value)
    sys.stdout.flush()


def main():
    while True:
        line = sys.stdin.readline()

        if not line:
            break

        cmd = line.strip()

        if cmd == "PING":
            print("PONG")
            sys.stdout.flush()

        elif cmd == "get":
            oid = sys.stdin.readline().strip()
            print_value(oid)

        elif cmd == "getnext":
            oid = sys.stdin.readline().strip()
            next_oid = get_next_oid(oid)

            if next_oid:
                print_value(next_oid)
            else:
                print("NONE")
                sys.stdout.flush()

        else:
            print("NONE")
            sys.stdout.flush()


if __name__ == "__main__":
    main()

Make it executable:

sudo chmod +x /usr/local/libexec/bird-snmp-passpersist.py

Test the script manually:

/usr/local/libexec/bird-snmp-passpersist.py

Then type:

PING

Expected output:

PONG

Press CTRL+C to exit.


7. Configure Net-SNMP

Edit snmpd.conf.

Debian / Ubuntu:

sudo nano /etc/snmp/snmpd.conf

RHEL-based systems:

sudo nano /etc/snmp/snmpd.conf

FreeBSD:

sudo nano /usr/local/etc/snmpd.conf

Add or adjust the following configuration:

agentAddress udp:161

rocommunity YOUR_SNMP_COMMUNITY 127.0.0.1
rocommunity YOUR_SNMP_COMMUNITY 192.0.2.0/24

pass_persist .1.3.6.1.4.1.55555.1.1 /usr/local/libexec/bird-snmp-passpersist.py

Replace:

YOUR_SNMP_COMMUNITY

with your actual SNMP community.

For production, restrict access only from your monitoring server IP address:

rocommunity YOUR_SNMP_COMMUNITY 198.51.100.10

Do not expose SNMP publicly to the internet.


8. Restart SNMPD

Debian / Ubuntu / RHEL:

sudo systemctl restart snmpd
sudo systemctl enable snmpd
sudo systemctl status snmpd

FreeBSD:

sysrc snmpd_enable="YES"
service snmpd restart
service snmpd status

9. Test SNMP Walk Locally

Run:

snmpwalk -v2c -c YOUR_SNMP_COMMUNITY 127.0.0.1 .1.3.6.1.4.1.55555.1.1

Example output:

SNMPv2-SMI::enterprises.55555.1.1.1.1 = STRING: "bgp_ntt"
SNMPv2-SMI::enterprises.55555.1.1.2.1 = INTEGER: 6
SNMPv2-SMI::enterprises.55555.1.1.3.1 = INTEGER: 950123
SNMPv2-SMI::enterprises.55555.1.1.4.1 = INTEGER: 10
SNMPv2-SMI::enterprises.55555.1.1.5.1 = INTEGER: 950123

SNMPv2-SMI::enterprises.55555.1.1.1.2 = STRING: "bgp_google"
SNMPv2-SMI::enterprises.55555.1.1.2.2 = INTEGER: 6
SNMPv2-SMI::enterprises.55555.1.1.3.2 = INTEGER: 1200
SNMPv2-SMI::enterprises.55555.1.1.4.2 = INTEGER: 6
SNMPv2-SMI::enterprises.55555.1.1.5.2 = INTEGER: 1200

Meaning:

.1.<index> = BGP peer name
.2.<index> = BGP state
.3.<index> = imported prefixes
.4.<index> = exported prefixes
.5.<index> = preferred prefixes

If peer state is 6, the BGP session is established.


10. Test from Monitoring Server

From your LibreNMS or Cacti server:

snmpwalk -v2c -c YOUR_SNMP_COMMUNITY ROUTER_IP .1.3.6.1.4.1.55555.1.1

If this fails, check firewall rules.

Example Linux firewall rule:

sudo ufw allow from MONITORING_SERVER_IP to any port 161 proto udp

Example firewalld rule:

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="MONITORING_SERVER_IP" port protocol="udp" port="161" accept'
sudo firewall-cmd --reload

11. LibreNMS Integration

LibreNMS already has an application-style BIRD2 monitoring approach, but if you want to use your own custom OIDs, you can add them as custom OID graphs.

Example custom OIDs:

BGP NTT State
OID: .1.3.6.1.4.1.55555.1.1.2.1
Type: gauge
Unit: state

BGP NTT Imported Prefixes
OID: .1.3.6.1.4.1.55555.1.1.3.1
Type: gauge
Unit: prefixes

BGP NTT Exported Prefixes
OID: .1.3.6.1.4.1.55555.1.1.4.1
Type: gauge
Unit: prefixes

Suggested alert rule:

BGP peer state is not equal to 6

Meaning:

state != 6

This alert will trigger when the peer is not established.

For prefix monitoring, create threshold rules such as:

Imported prefixes lower than expected baseline

Example:

imported_prefixes < 800000

Use a threshold that matches your real network. For a full-route transit peer, the baseline will be different from a route-server, CDN, or private peer.


12. Cacti Integration

In Cacti, create a new Data Input Method using SNMP.

Example data source:

Name: BIRD BGP Peer State
Method: SNMP - Get
OID: .1.3.6.1.4.1.55555.1.1.2.1
Data Type: Gauge

Example graph items:

Peer state
Imported prefixes
Exported prefixes
Preferred prefixes

For BGP state, graphing is useful, but alerting is more important.

For prefix counters, graphing is very useful because it helps detect route leaks, upstream filtering issues, and unexpected route drops.


13. Recommended OID Naming Table

Example peer mapping:

Peer IndexPeer NameState OIDImported OIDExported OID
1bgp_ntt.1.3.6.1.4.1.55555.1.1.2.1.1.3.6.1.4.1.55555.1.1.3.1.1.3.6.1.4.1.55555.1.1.4.1
2bgp_google.1.3.6.1.4.1.55555.1.1.2.2.1.3.6.1.4.1.55555.1.1.3.2.1.3.6.1.4.1.55555.1.1.4.2
3bgp_meta.1.3.6.1.4.1.55555.1.1.2.3.1.3.6.1.4.1.55555.1.1.3.3.1.3.6.1.4.1.55555.1.1.4.3

Important note: this simple script assigns peer index based on the order returned by birdc show protocols.

For a small deployment, this is usually enough. For production with many peers, it is better to define a static peer index mapping so OIDs do not change when peers are added or removed.


14. Production Improvement: Static Peer Index

For production use, create a static mapping file:

sudo nano /etc/bird-snmp-peers.conf

Example:

1 bgp_ntt
2 bgp_google
3 bgp_meta
4 bgp_cloudflare
5 bgp_akamai

Then modify the exporter script to read this file instead of dynamically assigning index numbers.

This prevents graph data from moving to the wrong peer when a new BGP session is added.


15. Security Recommendations

SNMP should be treated carefully, especially on internet-facing routers.

Recommended security practices:

  1. Use SNMPv3 where possible.
  2. Restrict SNMP access to your NMS IP address only.
  3. Never expose UDP/161 to the public internet.
  4. Use firewall rules.
  5. Use a strong community string if SNMPv2c is still required.
  6. Do not expose sensitive BGP policy details unnecessarily.
  7. Monitor snmpd logs for polling abuse or unexpected source IPs.

Example SNMPv3 user:

sudo net-snmp-create-v3-user -ro -A 'AUTH_PASSWORD_HERE' -X 'PRIV_PASSWORD_HERE' -a SHA -x AES birdmon

Example SNMPv3 test:

snmpwalk -v3 -l authPriv -u birdmon -a SHA -A 'AUTH_PASSWORD_HERE' -x AES -X 'PRIV_PASSWORD_HERE' 127.0.0.1 .1.3.6.1.4.1.55555.1.1

16. Troubleshooting

Problem: SNMP walk returns nothing

Check snmpd.conf:

grep pass_persist /etc/snmp/snmpd.conf

Restart SNMP:

sudo systemctl restart snmpd

Check logs:

journalctl -u snmpd -f

Problem: Script works manually but not from SNMP

Check permissions:

ls -lah /usr/local/libexec/bird-snmp-passpersist.py

Make sure it is executable:

sudo chmod +x /usr/local/libexec/bird-snmp-passpersist.py

Check whether the snmpd user can run birdc.

Depending on your system, snmpd may run as:

Debian-snmp
snmp
root

Check:

ps aux | grep snmpd

If birdc requires access to the BIRD control socket, adjust socket permissions carefully.


Problem: BGP peers show as down even when established

Run:

birdc show protocols all PEER_NAME

Check whether the output format matches the parser.

Look for:

BGP state:
Routes:

If your BIRD version formats output differently, adjust the regular expression in the script.


Problem: Prefix counters are zero

Check detailed BIRD output:

birdc show protocols all PEER_NAME | grep Routes

Expected line:

Routes: 950123 imported, 12 filtered, 10 exported, 950123 preferred

If the line is different, update the parser.


Problem: OID index changes after adding a peer

Use a static peer index file.

Dynamic indexing is acceptable for lab usage, but static indexing is better for production monitoring.


17. Operational Notes

This exporter is intentionally simple. It does not replace full BGP telemetry, BMP, streaming telemetry, or route collector systems.

However, it is very useful when your monitoring system is already SNMP-based and you need practical visibility into BIRD BGP session health.

For most ISP and hosting environments, this is enough to answer the most common operational questions:

  • Is the BGP peer established?
  • Did the prefix count drop?
  • Is the upstream still sending full routes?
  • Is the route server session healthy?
  • Did a maintenance window affect BGP?

18. Conclusion

BIRD is a powerful and flexible routing daemon, but it does not provide native SNMP metrics out of the box.

By combining BIRD, birdc, Net-SNMP, and a small exporter script, we can expose BGP health data to traditional NMS platforms such as LibreNMS and Cacti.

This approach is lightweight, easy to audit, and practical for production networks that still rely on SNMP-based monitoring.

The most important metrics to start with are:

BGP peer state
Imported prefixes
Exported prefixes
Preferred prefixes

Once those are available in your NMS, you can create useful graphs and alerts for BGP session health.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.