The BGP Monitoring Protocol (BMP) is a one way TCP feed that lets a router stream what its BGP sessions are doing to a monitoring station: the routes each neighbor sent before and after inbound policy, peer up and peer down events, and counters. It was standardised as RFC 7854 in June 2016 and later extended to Adj-RIB-Out (RFC 8671), the Loc-RIB (RFC 9069) and many more statistics. BMP has no authentication or encryption of its own, so securing it means a protected management path, strict limits on who may connect at both ends, and a hardened collector and data pipeline.
This post goes one level below our BGP explainer and sits next to our posts on BGP Flowspec, BFD and what RPKI route origin validation does not stop. Here we cover how a BMP session runs byte by byte, the pre-policy and post-policy views, what each later RFC changed, how operators use the feed, which open source collectors exist, how to secure it, and a lab that alerts when a prefix changes origin.
Key takeaways
- BMP runs over TCP and data flows only from router to station; RFC 7854 says no BMP message is ever sent the other way. Which side opens the connection is configured, and most deployments have the router dial out.
- There are seven message types: Route Monitoring (0), Statistics Report (1), Peer Down (2), Peer Up (3), Initiation (4), Termination (5) and Route Mirroring (6), each behind a 6 byte common header carrying version 3.
- A 42 byte per-peer header names the neighbor behind each message. Its L flag separates the pre-policy view (what the neighbor sent) from the post-policy view (what survived your import policy).
- RFC 8671 (November 2019) added Adj-RIB-Out, RFC 9069 (February 2022) added the Loc-RIB, RFC 9515 (December 2023) and RFC 9736 (March 2025) fixed registry rules, and RFC 9972 (May 2026) added statistics types 18 to 43, including RPKI validation state counts.
- RFC 7854 warns that without mutual authentication and confidentiality, an attacker can pose as the router, pose as the collector, or tamper with the feed. It recommends IPsec in tunnel mode with pre-shared keys.
- A feed can expose private VPN routes, every neighbor's address and AS, the router's software version and, through the pre-policy versus post-policy comparison, your import policy.
What problem BMP solves
Before BMP, getting a complete, timestamped view of what a router's neighbors were sending meant screen scraping (logging in, running show commands and parsing the text, which the RFC 7854 abstract names as the most common approach) or setting up a BGP session from the router to a monitoring box.
Screen scraping is slow, breaks across software versions, loads the control plane when you poll often, and gives snapshots, not a stream of changes. A passive BGP peer sees the wrong thing: only what the router chooses to advertise to it, which is its best paths after outbound policy. If a transit provider sends you a leaked route and your filter drops it, the monitoring peer never sees the leak. RFC 9069 adds the cost: a second router and an extra peering just to observe the first, plus correlation of BGP identifiers and addresses back to device names using inventory data from outside BGP.
BMP removes both problems. RFC 7854 states the design goals as keeping BMP "simple, useful, easily implemented, and minimally service affecting." For each monitored neighbor, the router sends the routes that neighbor advertised, tagged with which neighbor and when, first as a full dump and then as a stream of changes, plus session events and counters.
A fully configured station can receive an Initiation message naming the router; a Peer Up for every Established neighbor including both OPEN messages; a full per-neighbor table dump ending in an End-of-RIB marker; incremental changes; Peer Down messages with reason codes; Statistics Reports; optional verbatim Route Mirroring; and, with the later RFCs, the Adj-RIB-Out and Loc-RIB.
BMP standards timeline
Every date below was checked against the RFC Editor, IANA, the IETF Datatracker or the publishing project.
| Date | Event | Source |
|---|---|---|
| June 2016 | RFC 7854 publishes BMP version 3 on the Standards Track; versions 1 and 2 were draft only | RFC 7854 |
| November 2019 | RFC 8671 adds Adj-RIB-Out, the O flag, statistics types 14 to 17 and the Admin Label TLV | RFC 8671 |
| February 2022 | RFC 9069 adds the Loc-RIB Instance Peer (type 3), its F flag, the VRF/Table Name TLV and Peer Down reason 6 | RFC 9069 |
| December 2023 | RFC 9515 makes registry ranges 32768 to 65530 First Come First Served | RFC 9515 |
| March 2025 | RFC 9736 gives Peer Up its own TLV namespace, separate from Initiation | RFC 9736 |
| May 2026 | RFC 9972 defines statistics types 18 to 43, including RPKI origin validation counts | RFC 9972 |
| 21 July 2026 | Revision 21 of the Internet-Draft "BMP v4: Extended TLV Support" is posted; it is not yet an RFC | IETF Datatracker |
| 29 July 2026 | RouteViews reports its LiveStream missed events present in its MRT archive and deploys an FRR patch for pre-policy BMP on two collectors | RouteViews |
| 11 August 2026 | RouteViews restarts collectors in scheduled windows to roll out pre-policy BMP fleet wide | RouteViews |
The RouteViews episode shows a point this post returns to: which BMP view you collect decides what you can see.
How a BMP session works
Transport and who connects
For each router and station pair, RFC 7854 makes one side active (it opens the TCP connection) and one passive (it listens), chosen by configuration. FRR, for example, offers both bmp connect (outbound) and bmp listener (inbound). RFC 7854 assigns no well known port: pmacct documents 1790 as its default, the OpenBMP container publishes 5000, and GoBGP's example uses 11019.
The connection rules matter for both reliability and abuse resistance:
- The active side retries with backoff; the suggested default is exponential, starting at 30 seconds with a 720 second maximum.
- The router may restrict which addresses may connect, SHOULD allow only one connection per address by default, and MUST rate limit session setup (suggested default: 2 per minute).
- There is no handshake: the router starts sending as soon as TCP is up. The station never sends BMP messages, and the router may half close the connection or zero its receive window to enforce that. To stop, the station drops the connection.
A worked backoff example: with 30 seconds doubling to a 720 second cap, a router retries after 30, 60, 120, 240, 480 and then every 720 seconds. If the collector returns two minutes into an outage, the router has waited 30 plus 60 seconds and is inside a 120 second wait, so it reconnects at 210 seconds. That is why a collector restart during an incident can cost minutes of view per router unless you tune the minimum retry (FRR's min-retry and max-retry, in milliseconds).
Lifecycle
RFC 7854 Section 3.3 sets the order. The router MUST send Initiation first, then a Peer Up for each Established monitored neighbor, then each neighbor's Adj-RIB-In (pre-policy, post-policy or both) in Route Monitoring messages, ending each neighbor's dump with an End-of-RIB marker. After that come incremental changes, Statistics Reports as configured, and Peer Up or Peer Down as neighbors change state. The router MAY send Termination before closing, and a station must cope without it.
The dump and the change stream run concurrently. If a withdraw arrives for a prefix not yet dumped, the router drops the route and the station may see a withdraw for something it never heard about, which it should ignore.
The seven message types and the common header
| Type | Name | Per-peer header | What it carries |
|---|---|---|---|
| 0 | Route Monitoring | Yes | One BGP UPDATE PDU from one neighbor's RIB view |
| 1 | Statistics Report | Yes | A count, then counters and gauges as type, length, value entries |
| 2 | Peer Down | Yes | A reason code and, for some reasons, the NOTIFICATION or FSM event |
| 3 | Peer Up | Yes | Local address, ports, sent OPEN, received OPEN, optional TLVs |
| 4 | Initiation | No | sysDescr and sysName (mandatory), optional strings |
| 5 | Termination | No | A mandatory reason code, optional strings |
| 6 | Route Mirroring | Yes | A verbatim BGP message and an information code |
IANA lists types 0 to 6 as assigned, 251 to 254 as experimental and 255 as reserved, and RFC 7854 requires receivers to ignore unknown types, which is how new types can be added safely.
Every message starts with 6 bytes: Version (1 byte, always 3), Message Length (4 bytes, the whole message) and Message Type (1 byte). A collector frames the TCP stream by reading 6 bytes, then the remaining length.
Worked example: an Initiation with sysName edge-lab-1 (10 bytes) and sysDescr lab bgp speaker (15 bytes). Each TLV adds a 2 byte type and a 2 byte length, so the TLVs are 14 and 19 bytes, and the length field reads 6 plus 14 plus 19, which is 39.
Initiation and Termination
Initiation identifies the router with sysDescr (type 1) and sysName (type 2), which must equal the SNMP MIB-II values, plus optional strings (type 0). Since RFC 9736 its TLV is called the Initiation Information TLV and is used only here.
Termination carries a mandatory reason: 0 administratively closed (may return), 1 unspecified, 2 out of resources, 3 redundant connection, 4 permanently closed (slow or stop reconnecting). Reason 2 means the router gave up because it could not keep up: your collector is too slow, the path is congested, or the router's BMP buffer is too small for the tables you monitor.
The per-peer header and the pre-policy and post-policy views
The per-peer header
| Field | Size | Meaning |
|---|---|---|
| Peer Type | 1 byte | 0 Global Instance, 1 RD Instance, 2 Local Instance, 3 Loc-RIB Instance (RFC 9069) |
| Peer Flags | 1 byte | V (IPv6), L (post-policy), A (legacy 2 byte AS_PATH), O (Adj-RIB-Out, RFC 8671) |
| Peer Distinguisher | 8 bytes | Zero for the global instance, the route distinguisher for a VRF peer, or a local value |
| Peer Address | 16 bytes | The neighbor's address; IPv4 sits in the last 4 bytes |
| Peer AS | 4 bytes | The neighbor's AS; 2 byte AS numbers are zero padded |
| Peer BGP ID | 4 bytes | The neighbor's BGP identifier |
| Timestamp | 4 plus 4 bytes | Seconds and microseconds since 1 January 1970 UTC when the routes were received; zero means unknown |
That adds up to 42 bytes, so every Route Monitoring message has 48 bytes of BMP framing in front of its BGP UPDATE. A 55 byte UPDATE becomes a 103 byte BMP message.
Peer Type plus Peer Distinguisher is what keeps overlapping address space apart. Two customers in different VRFs can both peer from 10.0.0.1 with AS 65001; only the route distinguisher tells them apart. A collector keyed on peer address alone merges them. The timestamp is when the route was received, not when the BMP message left, and its precision is implementation dependent, so record your own receive time too.
Pre-policy and post-policy Adj-RIB-In
For each neighbor, a BGP speaker keeps the routes that neighbor advertised (the Adj-RIB-In); import policy then filters and rewrites them before best path selection. BMP can show either side:
- Pre-policy (L flag clear): exactly what the neighbor sent. This shows a leak or hijack attempt even when your filters caught it.
- Post-policy (L flag set): what remained after import policy, with rewritten attributes. This shows what your router actually considered.
Sending both multiplexes two streams on one session, told apart only by the L flag. RFC 7854 Section 5 adds a rule that catches home grown collectors: a withdraw must carry the same L flag as the announcement, and a route announced in both views is withdrawn twice.
Not every implementation keeps filtered routes, so some can only send post-policy without extra configuration. FRR's docs say its bgpd keeps the Adj-RIB-In automatically for peers under pre-policy monitoring, and that enabling pre-policy at runtime triggers a route refresh, resetting peers that lack route refresh support. Turning on a view can bounce sessions.
State compression and Route Mirroring
Route Monitoring messages are not copies of received UPDATEs. If a prefix changes several times before the router sends a BMP message, it may send only the final state, which RFC 7854 calls state compression, and attribute encoding may differ from what the peer sent. RFC 9069's example: if 192.0.2.0/24 changes five times in one second, only the last change may be sent.
Route Mirroring carries verbatim BGP messages, with information code 0 (Errored PDU, treated as withdraw under RFC 7606) or 1 (Messages Lost). RFC 7854 calls lossless mirroring on conventional routers "NOT RECOMMENDED" without careful thought about router resource exhaustion, interference with BGP and slower convergence. Mirroring only errored PDUs with a bounded buffer is the safer use. FRR documents bmp mirror buffer-limit, dropping the oldest messages and sending Messages Lost when it fills; GoBGP describes mirroring as mainly for debugging; pmacct's collector does not implement Route Mirroring.
Peer Up and Peer Down
Peer Up arrives when a monitored neighbor reaches Established, and once for every Established neighbor when the BMP session starts. After the per-peer header come the local address (16 bytes), local and remote ports (2 bytes each), the full OPEN sent, the full OPEN received and optional TLVs, so the station sees every negotiated capability. With two 59 byte OPENs and no TLVs, a Peer Up is 6 plus 42 plus 16 plus 2 plus 2 plus 59 plus 59, which is 186 bytes.
| Peer Down reason | Meaning |
|---|---|
| 1 | Local system closed; the NOTIFICATION it would have sent follows |
| 2 | Local system closed without NOTIFICATION; a 2 byte FSM event follows |
| 3 | Remote system closed; the NOTIFICATION received follows |
| 4 | Remote system closed without NOTIFICATION, including unexpected transport loss |
| 5 | Peer de-configured: data for this peer will no longer be sent |
| 6 | Local system closed, TLV data follows (Loc-RIB, RFC 9069) |
A Peer Down implicitly withdraws every route from that neighbor, and the router may skip explicit withdraws, so a collector that waits for them keeps stale routes. Reason 5 does not mean the BGP session failed; it means you will stop hearing about that neighbor, usually after a BMP configuration change. Alerting on it catches monitoring gaps created by accident or on purpose.
Statistics Reports
A Statistics Report carries a count and that many type, length, value entries: 32 bit counters that only rise and wrap at 4,294,967,295, or 64 bit gauges. RFC 7854 defines types 0 to 13: prefixes rejected by inbound policy (0), duplicate advertisements and withdraws (1, 2), updates invalidated by CLUSTER_LIST, AS_PATH, ORIGINATOR_ID and AS_CONFED loops (3 to 6), routes in the Adj-RIBs-In and Loc-RIB, in total and per AFI/SAFI (7 to 10), updates and prefixes treated as withdraw (11, 12) and duplicate update messages (13).
Reports are optional and timing is left to implementations. GoBGP sends none by default and accepts intervals of 15 to 65,535 seconds; FRR's bmp stats interval takes milliseconds. FRR's docs list the types it implements (0, 2, 3, 4, 5, 7, 8, 11 and an experimental 65531 for invalid next hops) and say it counts 3, 4 and 5 per prefix rather than per update, so read your sender's docs before setting thresholds.
Two worked examples:
- Rate. Type 0 reads 1,200,000 at 10:00:00 and 1,215,000 300 seconds later: 15,000 divided by 300 is 50 rejected prefixes per second. If that neighbor normally runs at 2 per second, something upstream is sending routes your policy refuses, often the first sign of a leak.
- Wrap. At 1,000 increments per second, a 32 bit counter wraps after 4,294,967,295 divided by 1,000, about 4.29 million seconds or 49.7 days. Compute deltas modulo 2 to the power 32, or you will see a huge negative rate every seven weeks.
What RFC 8671 and RFC 9069 added, and the later updates
The RFC Editor lists RFC 7854 as updated by RFC 8671, RFC 9069, RFC 9515 and RFC 9736. RFC 9972 adds statistics types without formally updating it.
RFC 8671: Adj-RIB-Out
RFC 8671 adds the O flag (bit 3): 0 for Adj-RIB-In, 1 for Adj-RIB-Out. With O set, the peer fields describe the neighbor the routes are sent to, and the timestamp is when they were advertised. Pre-policy Adj-RIB-Out (L clear) is the candidate set before outbound policy; post-policy (L set) MUST convey what is actually transmitted. Next hop and similar attributes are set at transmission, so pre-policy entries may show them as zero or empty.
It also adds statistics types 14 to 17 (routes in pre-policy and post-policy Adj-RIB-Out, total and per AFI/SAFI) and an Admin Label TLV for Peer Up, a free form string such as type=wholesale or region=west. The RFC's example: wholesale peers should never receive routes carrying a particular community, and post-policy Adj-RIB-Out plus labels lets you check that continuously. Any change that alters an existing session's behavior MUST bounce it with Peer Down and Peer Up. Its security section adds that implementations SHOULD only establish sessions with authorized and trusted monitoring devices.
RFC 9069: Loc-RIB
The Loc-RIB holds the routes the decision process selected. RFC 9069 lets the router report it directly through a Loc-RIB Instance Peer (type 3): zero filled peer address, the router's own AS and router ID, and a timestamp for installation in the Loc-RIB. A separate F flag (bit 0 for type 3) MUST be set when the router filters what it sends, so F set means you are not seeing the whole table. Peer Up is fabricated (zero addresses and ports, the same fabricated OPEN as sent and received), Loc-RIB updates MUST use 4 byte AS encoding, a VRF/Table Name TLV names the table (default global), and Peer Down uses reason 6. Mirroring does not apply, and only statistics types 8 and 10 are relevant.
RFC 9069 replaces RFC 7854 Section 8.2: locally originated routes now travel as Loc-RIB routes. GoBGP's docs reflect this, saying routes it originates through its API or CLI, VRF injection or redistribution appear under its local-rib type, not pre-policy or post-policy. IANA also lists an F flag at bit 4 for peer types 0 to 2 as deprecated, so treat that bit as unused. The RFC's practical case: a five minute performance problem is hard to explain afterwards without Loc-RIB history showing which decisions changed.
RFC 9515, RFC 9736 and RFC 9972
RFC 9515 changed registry procedure only: ranges 32768 to 65530 marked Specification Required became First Come First Served, which is why new codepoints now appear faster. RFC 9736 fixed a shared namespace that, in its own words, "hampers the extension of the protocol": it created a separate Peer Up Information TLV registry and moved Admin Label (type 4) and VRF/Table Name (type 3) into it. Implementations compliant with RFCs 7854, 8671 and 9069 already comply, but custom decoders should use the two registries.
RFC 9972 adds types 18 to 43 (24 and 25 were registered separately as temporary codepoints for a draft): current pre-policy and post-policy Adj-RIB-In counts, routes suppressed by damping, routes stale under graceful restart and long lived graceful restart, headroom before maximum prefix thresholds (29 to 32), routes rejected for over-long AS_PATHs (33, 34, 39, 40), and RPKI origin validation counts. Types 35 to 37 count post-policy Adj-RIB-In routes that are Invalid with a prefix length mismatch, Valid or NotFound; types 41 to 43 do the same for post-policy Adj-RIB-Out. Its security section says monitored devices SHOULD rate limit reporting of the new gauges. Check sender and collector support before depending on them.
The IANA registries
The IANA "BGP Monitoring Protocol (BMP) Parameters" page holds every codepoint: Message Types, Peer Types, Peer Flags for types 0 to 2 and separately for the Loc-RIB type 3, Statistics Types (now 0 to 43 plus experimental 65531 to 65534), Initiation Information TLVs, Peer Up Message TLVs, Termination TLVs and Reason Codes, Peer Down Reason Codes (1 to 6), and Route Mirroring TLVs and Information Codes. Its last update when fetched for this post was 22 May 2026.
How operators use BMP
Route leak and hijack detection
The OpenBMP docs list the use cases: alerting on topology and policy changes and on route leaking and hijacking "by origination, by better transit paths, or deviation from baseline." Signals that work on pre-policy data:
- Origin change for your prefixes: alert when any neighbor shows a different origin AS (the last AS in the AS_PATH) than your baseline.
- Unexpected more specifics inside your space that you do not announce.
- Valley paths: a route from a peer or customer whose AS_PATH contains one of your transit providers often indicates a leak. Admin Labels let you tag neighbors by role so the rule is written once.
- Rejected prefix rate jumps (type 0) and sudden Adj-RIB-In growth (types 7 and 9), which is what a full table leak looks like from the receiving side.
BMP sees only what your routers hear. A hijack that never reaches your neighbors is invisible to it, which is why public collectors exist and why our RPKI post covers what origin validation alone misses.
RPKI validation state monitoring
RFC 6811 (January 2013) defines three origin validation states: Valid (a VRP matches), Invalid (a VRP covers the prefix but none matches) and NotFound (no VRP covers it). You can compute them at the collector by loading the same validated ROA payloads your routers use (OpenBMP lists RPKI and IRR integration), which shows invalid routes neighbors send even when routers drop them. Or you can read RFC 9972 types 35 to 37 and 41 to 43 from the router. Any Invalid count above zero in post-policy Adj-RIB-Out means you are propagating RPKI invalid routes to someone, and is worth paging on.
Policy audit, churn and troubleshooting
Pre-policy versus post-policy Adj-RIB-In shows what each import policy dropped or changed (RFC 7854 notes an attacker with the feed could do the same comparison). Pre-policy versus post-policy Adj-RIB-Out audits export policy, as RFC 8671 describes. Post-policy Adj-RIB-In versus Loc-RIB checks the decision process, such as whether multipath routes install as expected, a case RFC 9069 gives.
RFC 7854 Section 7 names churn and looped AS_PATHs as what Statistics Reports are for. Operators graph per-neighbor route counts, duplicates (1, 2, 13), treat-as-withdraw counts (11, 12, which expose neighbors sending malformed attributes) and the RFC 9972 headroom gauges to see who is near a maximum prefix limit.
Public route collectors
RouteViews' July and August 2026 posts show BMP at internet scale. The project found its LiveStream was missing events its MRT archive recorded, confirmed the gap using RIPE's BGP beacon prefixes, and rolled out pre-policy BMP across its collectors on 11 August 2026. It also strips its own AS 6447 from streamed paths to match its MRT data. If you build hijack detection on a public stream, read which view the collector exports.
Open source BMP collectors and senders
From each project's own documentation; not a ranking.
| Project | Role | What its docs say |
|---|---|---|
| OpenBMP | Collector | Accepts any compliant BMP sender, produces parsed and raw feeds to Apache Kafka, stores RIB state and update history in PostgreSQL, lists RPKI and IRR integration and BGP-LS. Compose file runs openbmp/collector:2.2.3. The Unlicense. |
| pmacct (pmbmpd, or a thread in nfacctd and sfacctd) | Collector | Based on RFC 7854, in sync with RFC 8671, RFC 9069 and current drafts; no Route Mirroring. Writes JSON or Avro to files, AMQP or Kafka. Default port 1790. Thread mode correlates BMP with NetFlow, IPFIX and sFlow. |
| FRRouting bgpd | Sender | Loadable module (bgpd -M bmp); pre-policy, post-policy and loc-rib for IPv4 and IPv6 unicast, multicast, EVPN and VPN; mirroring with a buffer limit; access lists for BMP sessions. |
| GoBGP | Sender | bmp-servers section; pre-policy (default), post-policy, local-rib or all; statistics off by default. Its docs point to OpenBMP and yabmp as open source servers. |
Two caveats from those docs: FRR says BMP behaves unpredictably on ADD-PATH sessions (no path ID, an arbitrary path may be sent), and its page opens by saying only RFC 7854 features are implemented while also documenting a loc-rib option, so test which views your version actually sends.
How BMP compares with other ways to watch routing
| Approach | What it sees | What it misses | Cost and load | Security built in |
|---|---|---|---|---|
| BMP | Per-neighbor pre-policy and post-policy routes, Adj-RIB-Out, Loc-RIB, session events with OPEN contents, counters | Data plane traffic; views you did not enable | One TCP stream per router; full dumps per neighbor at start; storage grows with neighbors times views | None; RFC 7854 recommends IPsec tunnel mode |
| Passive iBGP or eBGP peer | Best paths after outbound policy toward that peer | Pre-policy and filtered routes, alternative paths unless ADD-PATH, other neighbors' events | A BGP session per router; correlation by BGP ID and address | BGP session protections such as TCP-AO if configured |
| Streaming telemetry (for example gNMI) | What the device's data models expose: counters, neighbor state, sometimes RIB summaries | Standard full per-neighbor route streams | gRPC streams; on change and sampled subscriptions | The gNMI spec requires TLS with mutual certificate authentication |
| NetFlow or IPFIX (RFC 7011) | Traffic flow records | Control plane state | Export load; needs BGP or BMP data to map flows to paths | RFC 7011 Section 11 covers TLS and DTLS; use depends on exporter and collector |
Most networks want BMP plus flow data: BMP says which path traffic should take and why, flows say what traffic did. pmacct runs BMP inside its flow collectors for that join, and RFC 9069 cites correlating flow records to Loc-RIB entries as a reason to collect the Loc-RIB.
How to secure a BMP feed
What is in the feed
RFC 7854's security section says BMP could let an outside party "obtain information not otherwise obtainable." A full feed from an edge or provider edge router can contain:
- Private routes: L3VPN and VRF routes for every customer, with route distinguishers that map customers to sites.
- Your policy: comparing pre-policy and post-policy views reveals what you filter, which tells an attacker which crafted announcements you would accept.
- Topology: every neighbor's address, AS and BGP ID, local addresses and ports, and negotiated capabilities from the OPEN messages.
- Software versions: sysDescr usually includes the router's OS and version, a ready made target list.
- Operational state: which sessions are down and how close each neighbor is to its prefix limit.
Treat the feed and everything that stores it (Kafka topics, databases, files, dashboards) as confidential network configuration data.
The threats
Without mutual authentication, RFC 7854 says an attacker could masquerade as the router and feed the station false information, or masquerade as the station and read BMP data; without confidentiality, an on-path attacker can read or tamper with it. A spoofed feed can bury a real leak behind a fake normal view, or fire false alarms until people ignore real ones.
Controls that work
- Keep BMP on a management path. Source it from a management VRF or out-of-band interface (FRR's
bmp connecttakessource-interface), and give the collector's BMP address no route to or from the internet. - Restrict who connects, at both ends. RFC 7854 says implementations SHOULD require manual configuration of both sides. FRR supports
ip access-listandipv6 access-listinsidebmp targets, checked for active and passive sessions. pmacct documentsbmp_daemon_ipto bind one address (default is all, and the docs call setting it highly advised),bmp_daemon_allow_filefor permitted routers (default allows all) andbmp_daemon_max_peers(default 10). - Protect the transport. RFC 7854 recommends IPsec in tunnel mode with pre-shared keys. TCP-AO (RFC 5925, June 2010, obsoleting TCP MD5) adds strong segment authentication and replay protection but no confidentiality, and both router and collector OS must support it for that session; check your platform docs. pmacct documents TCP MD5 only for its BGP daemon and warns it breaks through NAT. Without either, use a physically separate management network.
- Harden the collector. It decodes BGP attributes that originate anywhere on the internet, some deliberately malformed. Run it unprivileged, patch it, isolate it, and treat crashes as possible security events.
- Lock down the pipeline. The published OpenBMP compose file maps the collector (5000), Kafka (9092), PostgreSQL (5432), Grafana (3000) and other services to host ports, and sets the PostgreSQL user and password to
openbmp. Fine on a laptop, dangerous on a routable server. Bind ports to the management address or remove them, change credentials, enable Kafka TLS and authentication (pmacct points to librdkafka settings), and give consumers read only accounts. - Set retention deliberately. The OpenBMP compose file sets per-table retention, such as one year for peer events and four weeks for stats and RIB logs. Keep what incident timelines and audits need and delete the rest.
- Watch the watcher. Alert on Peer Down reason 5, Termination reasons 2 and 4, unexpected Loc-RIB F flags, and routers that stop sending Statistics Reports. A feed that quietly narrows blinds you.
Step by step: BMP in a lab with FRR and pmacct
This lab uses two FRR routers and a pmacct BMP collector on an isolated network. It only uses documentation and private address and AS ranges. Run it on a disposable lab host or virtual machine, not on production equipment. Exact package names and paths differ between distributions; check your FRR and pmacct versions against their docs.
The topology:
upstream (AS 64500, 198.51.100.2) ---- edge (AS 64501, 198.51.100.1)
| mgmt 192.0.2.1
|
collector 192.0.2.10 (pmbmpd, TCP 1790)
Step 1: install the pieces
On Debian or Ubuntu lab hosts, both FRR and pmacct are packaged. Install FRR on the two router hosts and pmacct on the collector host:
# router hosts
sudo apt-get update && sudo apt-get install -y frr
# collector host
sudo apt-get update && sudo apt-get install -y pmacct jq
pmbmpd -V
Step 2: configure the collector to accept only the lab router
Create an allow list and a minimal configuration. bmp_daemon_ip binds to the management address only, and the allow file limits who can connect.
sudo mkdir -p /etc/pmacct /var/log/pmacct
echo "192.0.2.1" | sudo tee /etc/pmacct/bmp_allow.lst
sudo tee /etc/pmacct/pmbmpd.conf > /dev/null <<'EOF'
bmp_daemon_ip: 192.0.2.10
bmp_daemon_port: 1790
bmp_daemon_allow_file: /etc/pmacct/bmp_allow.lst
bmp_daemon_max_peers: 4
!
bmp_daemon_msglog_file: /var/log/pmacct/bmp-$peer_src_ip.log
bmp_daemon_msglog_output: json
!
bmp_dump_file: /var/log/pmacct/bmp-$peer_src_ip-%H%M.dump
bmp_dump_refresh_time: 300
EOF
sudo pmbmpd -f /etc/pmacct/pmbmpd.conf
sudo ss -ltnp | grep 1790
The ss output should show the daemon listening on 192.0.2.10:1790 only, not on 0.0.0.0. Add a host firewall rule as a second layer:
sudo nft add table inet bmp
sudo nft add chain inet bmp input '{ type filter hook input priority 0; policy accept; }'
sudo nft add rule inet bmp input tcp dport 1790 ip saddr != 192.0.2.1 drop
Step 3: set up the upstream router
On the upstream host, announce two test prefixes. In /etc/frr/daemons, set bgpd=yes, then configure through vtysh:
router bgp 64500
bgp router-id 198.51.100.2
no bgp ebgp-requires-policy
neighbor 198.51.100.1 remote-as 64501
address-family ipv4 unicast
network 203.0.113.0/25
network 203.0.113.128/25
exit-address-family
You will also need static or connected routes for those prefixes on the upstream host so network statements are active, for example ip route 203.0.113.0/25 Null0 and ip route 203.0.113.128/25 Null0 in the same configuration.
Step 4: enable BMP on the edge router
FRR loads BMP as a module, so bgpd must start with -M bmp. In /etc/frr/daemons on the edge host:
bgpd=yes
bgpd_options=" -A 127.0.0.1 -M bmp"
Restart FRR (sudo systemctl restart frr), then configure BGP with an import policy (so pre-policy and post-policy differ), and a BMP target that sends all three views, statistics every 60 seconds, and only to the allowed collector:
access-list BMP-COLLECTOR seq 5 permit 192.0.2.10/32
!
ip prefix-list FROM-UPSTREAM seq 10 permit 203.0.113.0/25
ip prefix-list FROM-UPSTREAM seq 20 deny any
!
route-map UPSTREAM-IN permit 10
match ip address prefix-list FROM-UPSTREAM
!
router bgp 64501
bgp router-id 198.51.100.1
neighbor 198.51.100.2 remote-as 64500
address-family ipv4 unicast
neighbor 198.51.100.2 route-map UPSTREAM-IN in
exit-address-family
!
bmp targets lab-collector
ip access-list BMP-COLLECTOR
bmp connect 192.0.2.10 port 1790 min-retry 5000 max-retry 60000 source-interface eth1
bmp monitor ipv4 unicast pre-policy
bmp monitor ipv4 unicast post-policy
bmp monitor ipv4 unicast loc-rib
bmp stats interval 60000
exit
Here eth1 is the management interface that holds 192.0.2.1. The import policy accepts 203.0.113.0/25 and rejects 203.0.113.128/25, so the pre-policy view should show both and the post-policy view only one. The short retry values suit a lab; in production, keep backoff closer to the RFC's suggestion so a dead collector does not cause constant reconnect attempts.
Step 5: verify the session and the peer up messages
On the edge router, check that the BGP session is up and, on FRR versions that provide it, the BMP target state:
show bgp summary
show bmp
On the collector, confirm the TCP session and read the event log. pmacct's documented msglog format uses bmp_msg_type values of init, term, peer_up, stats, peer_down and route_monitor:
sudo ss -tnp | grep 1790
jq -c 'select(.bmp_msg_type=="init")' /var/log/pmacct/bmp-192.0.2.1.log
jq -c 'select(.bmp_msg_type=="peer_up") | {peer_ip, timestamp}' /var/log/pmacct/bmp-192.0.2.1.log
You should see one init message, then a peer_up for 198.51.100.2, and for the Loc-RIB view a second peer up for the router's own emulated Loc-RIB peer. Then look at routes. The exact JSON key names for the prefix and the AS path depend on your pmacct version and settings, so print one record first and adjust the filters to match:
jq -c 'select(.bmp_msg_type=="route_monitor")' /var/log/pmacct/bmp-192.0.2.1.log | head -n 3
Check three things:
- 203.0.113.128/25 appears in a pre-policy record but not in any post-policy record. That is your import filter working, and it is visible only because you enabled pre-policy.
statsrecords arrive about once a minute, and the rejected-by-policy counter (type 0) for the upstream neighbor is non-zero.- Shutting down the upstream session (
neighbor 198.51.100.2 shutdownunderrouter bgp 64501) produces apeer_downrecord, and bringing it back produces a freshpeer_upfollowed by a new dump.
Step 6: alert on unexpected origin changes
The script below reads pmacct's JSON lines, keeps a baseline of the expected origin AS for prefixes you care about, and prints an alert when a route monitoring update for one of those prefixes arrives with a different origin. It handles both common key spellings, skips withdrawals, and never acts on the network; it only reports. Save it on the collector as origin_watch.py:
#!/usr/bin/env python3
"""Watch a pmacct BMP msglog (JSON lines) for origin AS changes on baseline prefixes."""
import json
import sys
import time
BASELINE = {
"203.0.113.0/25": 64500,
"203.0.113.128/25": 64500,
}
def origin_as(as_path):
if not as_path:
return None
if isinstance(as_path, list):
tokens = [str(x) for x in as_path]
else:
tokens = str(as_path).replace("{", " ").replace("}", " ").split()
for tok in reversed(tokens):
if tok.isdigit():
return int(tok)
return None
def follow(path):
with open(path, "r", encoding="utf-8") as fh:
fh.seek(0, 2)
while True:
line = fh.readline()
if not line:
time.sleep(0.5)
continue
yield line
def main(path):
for line in follow(path):
try:
rec = json.loads(line)
except json.JSONDecodeError:
continue
if rec.get("bmp_msg_type") != "route_monitor":
continue
if rec.get("log_type") not in (None, "update"):
continue
prefix = rec.get("ip_prefix") or rec.get("prefix")
if prefix not in BASELINE:
continue
seen = origin_as(rec.get("as_path"))
if seen is not None and seen != BASELINE[prefix]:
print(json.dumps({
"alert": "unexpected_origin",
"prefix": prefix,
"expected_origin": BASELINE[prefix],
"seen_origin": seen,
"bmp_router": rec.get("bmp_router"),
"peer_ip": rec.get("peer_ip"),
"bmp_timestamp": rec.get("timestamp"),
}), flush=True)
if __name__ == "__main__":
main(sys.argv[1])
Run it, then test it safely inside the lab by changing the baseline (for example set 203.0.113.0/25 to expect 64499) and bouncing the upstream session so the prefix is announced again:
python3 origin_watch.py /var/log/pmacct/bmp-192.0.2.1.log
An alert line should appear for the prefix whose baseline no longer matches. In production, send these lines to your paging or ticketing system, keep the baseline in version control next to your ROAs, and apply the rule to pre-policy records so you see bad announcements even when your filters drop them.
Step 7: clean up
sudo pkill pmbmpd
sudo nft delete table inet bmp
Remove the bmp targets block from the edge router when you are done, and note that this produces a Peer Down with reason 5 on any collector still connected, which is a good final test of your "monitoring removed" alert.
Common mistakes
- Exposing the collector to the internet. BMP has no authentication, so a routable listener accepts whatever your allow list permits. Bind to the management address, firewall the port, keep the list short.
- Publishing Kafka, the database and dashboards on every interface with default credentials. The data behind them is the sensitive part.
- Monitoring only post-policy. You see what you accepted, never what you were offered, so leaks your filters caught stay invisible. RouteViews' August 2026 rollout exists because of this gap.
- Monitoring only pre-policy. You cannot prove what you accepted or sent. Add post-policy and, where supported, Adj-RIB-Out and Loc-RIB.
- Ignoring statistics reports, or mishandling 32 bit counter wraps. Rejected prefix, treat-as-withdraw and route count values show trouble before routes visibly change.
- Ignoring the L, O and peer type fields. The collector merges VRFs, mixes views and applies withdraws to the wrong table.
- Waiting for explicit withdraws after Peer Down. Peer Down withdraws everything from that neighbor implicitly.
- Full Route Mirroring in production. RFC 7854 says NOT RECOMMENDED without careful thought; mirror errored PDUs with a bounded buffer instead.
- Enabling a new view at a busy hour. It can trigger route refresh or session resets, and RFC 8671 requires a Peer Down and Peer Up for behavior changes.
FAQ
What is BMP in networking?
The BGP Monitoring Protocol, RFC 7854 (June 2016). A router streams its BGP state to a monitoring station over TCP: each neighbor's routes before and after inbound policy, session events and statistics. RFC 8671 added outbound routes and RFC 9069 the selected best routes.
What port does BMP use?
None is assigned; the port is configured. pmacct documents 1790 as its default, the OpenBMP container publishes 5000 and GoBGP's example uses 11019. Standardise on one and filter on it.
Does the router or the collector open the BMP connection?
Either, by configuration; RFC 7854 defines an active and a passive party. Most deployments have the router connect out. Data flows only from router to station either way.
What is the difference between pre-policy and post-policy in BMP?
Pre-policy (L flag clear) is what a neighbor sent before your import policy; post-policy (L flag set) is what remained after it. Pre-policy shows leaks your filters blocked; post-policy shows what your router actually considered.
Is BMP encrypted or authenticated?
No. RFC 7854 recommends IPsec in tunnel mode with pre-shared keys where spoofing or eavesdropping matters. Also keep BMP on a management network, restrict connections at both ends and harden the collector and its storage.
Can BMP detect BGP hijacks and route leaks?
It gives you the data to detect them in what your routers hear: compare pre-policy routes with expected origins, watch for unexpected more specifics and valley paths, and track rejected prefix counters. It cannot see hijacks that never reach your neighbors.
What is the Loc-RIB in BMP?
The table of routes the router's decision process selected. RFC 9069 (February 2022) reports it through a Loc-RIB Instance Peer (type 3), so you no longer need a second router to see the first one's choices.
Which open source BMP collectors exist?
OpenBMP (Kafka and PostgreSQL based) and pmacct (pmbmpd, or a thread in its flow collectors) are documented here. FRRouting and GoBGP act as BMP senders, and GoBGP's docs also point to yabmp.
Where Safeguard fits
Safeguard does not run on routers, does not collect or parse BMP, does not detect route leaks or hijacks, and does not analyze router firmware or network operating systems. The fit is with the software around a BMP pipeline that your team builds and runs. A typical monitoring stack is a set of open source services and your own glue: a collector, a message broker, a database, dashboards, and Python or Go jobs like the origin watcher above, usually shipped as container images. Safeguard scans those repositories, container images and SBOMs, lists the open source components and versions inside them, matches them against vulnerability databases, ranks findings by reachability, and can open fix pull requests (autonomous remediation ships switched off). When an advisory lands for a library your collector or consumers depend on, the platform shows which of your projects contain that component and version, so you know which images to rebuild.
Software supply chain hygiene matters for a BMP pipeline because the collector is a parser that decodes data originating from the whole internet, and every library under it is part of that attack surface. The fix paths below apply to those open source dependencies and container images, not to the routers' own operating systems, which still have to be patched through their vendors.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.
3. Gold Registry: start from components that are already fixed
Gold Registry provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | Safeguard policies and gates in CI |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
This demo sets up the Safeguard runner on your own machine, approves it with its device code and scans from the terminal. Pointed at the container image of your BMP collector or one of its consumers, the same scan lists the packages and versions inside it and their known vulnerabilities, so you know which images to rebuild after an advisory for a parsing, messaging or database library.
Click through it at your own pace, or open it full screen.
This demo is a real CLI session: ask it to fix the lodash CVE in a repository and watch it read the finding and the advisory, ask before each command, move lodash to 4.17.21, run npm install and npm test with your approval, and push nothing. The same approve each step flow applies when you bump a pinned dependency in the Python or Go jobs that read your BMP feed, where you want a human to see every change before it reaches the monitoring stack.
Click through it at your own pace, or open it full screen.
This demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. You can then ask which of your projects ship a given package version, such as a specific Kafka client library in your collector and consumer images, and get the answer from your own scan data rather than from memory.
Click through it at your own pace, or open it full screen.
Safeguard is available at safeguard.sh/download, and the CLI documentation is at docs.safeguard.sh/docs/cli.
Quick check
A BMP collector shows a leaked route in the pre-policy view from a transit neighbor, but not in the post-policy view. What does that tell you?
The neighbor sent you the leaked route, and your import policy filtered it out, so your router never used it. It is still worth acting on: tell the neighbor, check whether other routers without the same filter accepted it, and confirm the rejected-by-policy counter (statistics type 0) rose at the same time.
RFC 7854 says BMP has no built in security. What does it recommend when spoofing or eavesdropping is a concern?
IPsec in tunnel mode with pre-shared keys between the router and the monitoring station. In practice, combine that with a management only network path, manual configuration of collector addresses, access lists on the router and an allow list and single address binding on the collector.
Sources
- RFC 7854: BGP Monitoring Protocol (BMP)
- RFC 7854 metadata (updated by list), RFC Editor
- RFC 8671: Support for Adj-RIB-Out in BMP
- RFC 9069: Support for Local RIB in BMP
- RFC 9515: Revision to Registration Procedures for Multiple BMP Registries
- RFC 9736: The BMP Peer Up Message Namespace
- RFC 9972: Advanced BMP Statistics Types
- IANA: BGP Monitoring Protocol (BMP) Parameters
- IETF Datatracker: draft-ietf-grow-bmp-tlv
- RFC 6811: BGP Prefix Origin Validation
- RFC 5925: The TCP Authentication Option
- RFC 7011: IPFIX Protocol Specification
- OpenConfig gNMI specification
- OpenBMP documentation
- OpenBMP obmp-docker README
- OpenBMP obmp-docker docker-compose.yml
- pmacct QUICKSTART (BMP daemon section)
- pmacct CONFIG-KEYS
- pmacct MSGLOG_DUMP_FORMATS
- FRRouting documentation: BMP
- GoBGP documentation: BGP Monitoring Protocol
- RouteViews: Alignment of MRT and BMP (29 July 2026)
- RouteViews: BMP pre-policy rollout (August 2026)
Last reviewed: 24 October 2026. BMP implementations differ in which RFCs, views and statistics types they support; check your router and collector documentation before relying on any behavior described here.