Finding Well-Known BGP Communities with monocle

· Mingwei Zhang
monocle tutorial bgp

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:

  1. search public BGP updates for communities in the 65535:* range,
  2. summarize where each value appears,
  3. join observed values with the IANA BGP Well-known Communities registry,
  4. check specific values, such as BLACKHOLE and NOPEER, 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:

RangeRegistration rule
0x00000000-0x0000FFFFReserved
0x00010000-0xFFFEFFFFPrivate Use
0xFFFF0000-0xFFFF8000First Come First Served
0xFFFF8001-0xFFFFFFFFStandards 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:

MetricValue
Matching update rows769,537
Distinct numeric 65535:* values109
Values not assigned in the IANA registry snapshot102

The highest-volume values were:

CommunityIANA statusRowsDistinct prefixesCollectors
65535:6939Unassigned553,3522027
65535:1200Unassigned63,19410,4552
65535:34Unassigned41,5846,02258
65535:30Unassigned37,4362,89654
65535:65284NOPEER, RFC 376527,7661,03351
65535:9957Unassigned13,2451,1251
65535:0GRACEFUL_SHUTDOWN, RFC 83262,17621051
65535:666BLACKHOLE, RFC 79991,27049918

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 /32 for IPv4 or /128 for IPv6?
  • does the route also carry NO_EXPORT or NO_ADVERTISE?

In the 15-minute scan, 65535:666 appeared in 1,270 rows:

Observed propertyRows with propertyRows without property
/32 or /128474 (37.3%)796 (62.7%)
Carries NO_EXPORT460 (36.2%)810 (63.8%)
Carries NO_ADVERTISE24 (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:

MetricValue
Rows27,766
Distinct prefixes1,033
Collectors51
Peer ASNs103
Origin ASNs146
IPv4 rows25,387
IPv6 rows2,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;
  • BLACKHOLE appeared both on highly specific prefixes with NO_EXPORT and on broader prefixes without NO_EXPORT/NO_ADVERTISE;
  • NOPEER was 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: