Finding Well-Known BGP Communities with monocle
BGP communities are easy to parse and hard to interpret. A route can carry a list of 32-bit values, but the meaning of each value depends on a mix of RFCs, IANA registries, operator conventions, and local routing policy.
This post walks through a small ad-hoc monocle analysis for turning raw MRT updates into a protocol-aware summary:
- search public BGP updates for communities in the
65535:*range, - summarize where each value appears,
- join observed values with the IANA BGP Well-known Communities registry,
- check specific values, such as
BLACKHOLEandNOPEER, against the relevant RFC text.
The goal is not to infer operator intent from route collector data. This is a one-off research note, not production automation. The goal is narrower: show how BGPKIT tooling can make this kind of evidence gathering easier to repeat and audit.
Why 65535:*?
RFC 1997 introduced the BGP COMMUNITIES path attribute. In the “Well-known Communities” part of the document, it defines several globally significant values and says:
The following communities have global significance and their operations shall be implemented in any community-attribute-aware BGP speaker.
The current IANA registry is the BGP Well-known Communities registry.
The registry divides the community space into ranges, including:
| Range | Registration rule |
|---|---|
0x00000000-0x0000FFFF | Reserved |
0x00010000-0xFFFEFFFF | Private Use |
0xFFFF0000-0xFFFF8000 | First Come First Served |
0xFFFF8001-0xFFFFFFFF | Standards Action |
That makes values with high-order 16 bits set to 65535 interesting: they sit in the part of the 32-bit community space where many globally significant values live.
Setup
On macOS, install monocle with Homebrew:
brew install monocle
If you prefer building from Rust crates, or are not using Homebrew:
cargo install monocle
For reproducibility, record the version before running the scan:
monocle --version
This run used:
monocle 1.3.0
This analysis used the public MRT index available to monocle at run time. To make the collector/file set auditable, first write the matching MRT file list:
START=1782324000 # 2026-06-24T18:00:00Z
END=$((START+900)) # 15 minutes
OUT=updates-20260624T1800Z-15m-allcollectors-wk
monocle search \
--dump-type updates \
--start-ts $START \
--end-ts $END \
--community '65535:*' \
--broker-files \
> "$OUT-files.txt"
wc -l "$OUT-files.txt"
For this run, monocle found 125 matching MRT update files.
The exact file count can vary if the public collector index changes or if late files appear. If you need a fixed rerun, save this file list with the analysis artifacts and use the same monocle version.
Searching MRT updates with monocle
For this run, I scanned the same 15-minute update window:
START=1782324000 # 2026-06-24T18:00:00Z
END=$((START+900)) # 15 minutes
OUT=updates-20260624T1800Z-15m-allcollectors-wk
monocle search \
--dump-type updates \
--start-ts $START \
--end-ts $END \
--community '65535:*' \
--fields timestamp,collector,prefix,peer_asn,origin_asns,as_path,communities \
--format json-line \
> "$OUT.jsonl"
A representative JSONL row looks like this:
{
"timestamp": 1782324014.12,
"collector": "rrc07",
"prefix": "83.171.244.0/24",
"peer_asn": 3399,
"origin_asns": ["197761"],
"as_path": "3399 5511 6204 51202 51202 197761",
"communities": ["3399:100", "3399:107", "5511:500", "6204:1000", "65535:65284"]
}
The important part is the community filter:
--community '65535:*'
monocle handles the collector/file discovery and MRT parsing. The output is JSON Lines, so the next step can be a short post-processing script rather than a custom MRT parser.
For this post, I used small Python scripts rather than building a reusable tool. The main script, summarize_existing_well_known.py, groups rows by observed 65535:* value and counts rows, distinct prefixes, collectors, peer ASNs, origin ASNs, address families, and prefix lengths. It also normalizes symbolic community names that monocle may emit, such as no-export, back to their numeric well-known-community values before doing the 65535:* grouping.
python3 summarize_existing_well_known.py "$OUT.jsonl" "$OUT"
The second script, enrich_iana_status.py, joins the per-community CSV against a local transcription of the IANA BGP Well-known Communities registry, so each observed value can be labeled as BLACKHOLE, NOPEER, GRACEFUL_SHUTDOWN, Unassigned, and so on. The registry source is the IANA BGP Well-known Communities registry, checked for this write-up on 2026-06-25.
python3 enrich_iana_status.py \
"$OUT-by-community.csv" \
"$OUT-by-community-iana.csv"
What the scan found
In this 15-minute window:
| Metric | Value |
|---|---|
| Matching update rows | 769,537 |
Distinct numeric 65535:* values | 109 |
| Values not assigned in the IANA registry snapshot | 102 |
The highest-volume values were:
| Community | IANA status | Rows | Distinct prefixes | Collectors |
|---|---|---|---|---|
65535:6939 | Unassigned | 553,352 | 20 | 27 |
65535:1200 | Unassigned | 63,194 | 10,455 | 2 |
65535:34 | Unassigned | 41,584 | 6,022 | 58 |
65535:30 | Unassigned | 37,436 | 2,896 | 54 |
65535:65284 | NOPEER, RFC 3765 | 27,766 | 1,033 | 51 |
65535:9957 | Unassigned | 13,245 | 1,125 | 1 |
65535:0 | GRACEFUL_SHUTDOWN, RFC 8326 | 2,176 | 210 | 51 |
65535:666 | BLACKHOLE, RFC 7999 | 1,270 | 499 | 18 |
This table is a good example of why registry-aware scripting helps. A raw list of communities is just numbers. Once joined with IANA, the same data separates into named RFC-defined values and values that need local/operator-specific interpretation.
Importantly, “unassigned” does not mean “bad”. Public BGP data alone does not tell us whether a value is a local convention, a route-server policy signal, a legacy convention, an implementation artifact, or something else. It only tells us where the value appeared and gives us concrete rows to inspect next.
Checking BLACKHOLE behavior against RFC 7999
BLACKHOLE is assigned as 65535:666 by RFC 7999.
Two parts of the RFC are useful for a mechanical check.
RFC 7999 Section 3.2 says:
A BGP speaker receiving an announcement tagged with the BLACKHOLE community SHOULD add the NO_ADVERTISE or NO_EXPORT community as defined in RFC 1997, or a similar community, to prevent propagation of the prefix outside the local AS.
RFC 7999 Section 3.3 says:
However, blackhole prefix length should be as long as possible in order to limit the impact of discarding traffic for adjacent IP space that is not under DDoS duress. The blackhole prefix length is typically as specific as possible, /32 for IPv4 or /128 for IPv6.
Those sentences translate into simple checks:
- is the prefix
/32for IPv4 or/128for IPv6? - does the route also carry
NO_EXPORTorNO_ADVERTISE?
In the 15-minute scan, 65535:666 appeared in 1,270 rows:
| Observed property | Rows with property | Rows without property |
|---|---|---|
/32 or /128 | 474 (37.3%) | 796 (62.7%) |
Carries NO_EXPORT | 460 (36.2%) | 810 (63.8%) |
Carries NO_ADVERTISE | 24 (1.9%) | 1,246 (98.1%) |
Address-family split: 463 IPv4 rows (36.5%) and 807 IPv6 rows (63.5%).
The resulting rows include examples that match the RFC 7999 profile:
{
"collector": "decix.fra",
"peer_asn": 6695,
"prefix": "193.56.63.113/32",
"origin_asns": ["215514"],
"as_path": "201053 50607 215514",
"communities": ["65535:666", "no-export"]
}
It also found examples that lack those observed properties. This is not a compliance judgment: RFC 7999 Section 3.2 describes behavior for a receiving BGP speaker, and collector data may show the route before or after other policy actions. Still, rows like this are useful starting points for follow-up:
{
"collector": "iix.cgk",
"peer_asn": 7597,
"prefix": "2001:4:112::/48",
"origin_asns": ["112"],
"as_path": "136106 35280 50628 44097 112",
"communities": ["65535:666", "7597:1000:1"]
}
That is as far as the data should be taken without more context. The collector peer may be a route server. The community may have been attached earlier in the AS path. The value may reflect a local convention. The useful part is that this ad-hoc monocle analysis can separate these cases quickly and produce concrete rows for follow-up.
Checking NOPEER visibility against RFC 3765
NOPEER is assigned as 65535:65284 by RFC 3765.
RFC 3765 Section 2 describes it as:
an advisory qualification to readvertisement of a route prefix, permitting an AS not to readvertise the route prefix to all external bilateral peer neighbour AS’s.
In the same 15-minute update window, NOPEER was broadly visible:
| Metric | Value |
|---|---|
| Rows | 27,766 |
| Distinct prefixes | 1,033 |
| Collectors | 51 |
| Peer ASNs | 103 |
| Origin ASNs | 146 |
| IPv4 rows | 25,387 |
| IPv6 rows | 2,379 |
Example row:
{
"collector": "rrc07",
"peer_asn": 3399,
"prefix": "83.171.244.0/24",
"origin_asns": ["197761"],
"as_path": "3399 5511 6204 51202 51202 197761",
"communities": [
"3399:100",
"3399:107",
"5511:500",
"6204:1000",
"65535:65284"
]
}
Route data alone does not prove whether any downstream AS enforces the advisory signal. But it does show that the value is operationally present across many collectors, peer ASNs, and origin ASNs in this window.
A note on policy behavior
It is worth being careful when interpreting well-known communities. RFC 8642 exists because well-known community handling has not always been consistent across implementations and policies. Its introduction says:
In hindsight, RFC 1997 did not prescribe as fully as it should have how well-known communities may be manipulated by policies applied by operators. Currently, implementations differ in this regard, and these differences can result in inconsistent behaviors that operators find difficult to identify and resolve.
That is another reason to keep the analysis evidence-focused. The data can say which routes carried which values at which collectors; it should not assign intent by itself.
Takeaways
A small ad-hoc monocle analysis can turn public MRT updates into useful, RFC-aware evidence. The first step is the MRT query; the second step is ordinary JSONL processing that groups rows, counts distinct routing dimensions, and annotates observed values with registry metadata.
The interesting parts are not hidden inside the MRT files anymore:
- which
65535:*values are visible, - whether those values are assigned in the IANA registry,
- where the values appear,
- whether a route matches a simple RFC-derived profile,
- which rows are worth deeper operator-policy investigation.
For this particular window, the main observations were:
- many observed
65535:*values were not assigned in the current IANA well-known-community registry snapshot; BLACKHOLEappeared both on highly specific prefixes withNO_EXPORTand on broader prefixes withoutNO_EXPORT/NO_ADVERTISE;NOPEERwas broadly visible across collectors, peers, origins, IPv4, and IPv6.
The broader lesson is the monocle research pattern: query a narrow slice of MRT data, summarize it with simple scripts, join with source-of-truth registries, then inspect specific rows with the RFC text next to the data.
The scripts used for this note are available here:
summarize_existing_well_known.py— summarize an existing JSONL file;enrich_iana_status.py— add IANA status labels to the per-community CSV;filter_well_known.py— filter generic JSONL output to rows carrying well-known communities;summarize_well_known.py— stream-oriented variant that reads JSONL from stdin.