rustbgpd
Cookbook

MANRS IXP Programme Action 1 on rustbgpd

Map rustbgpd controls to MANRS IXP Programme Action 1.

Map rustbgpd controls to MANRS IXP Programme Action 1.

When this is you: Your exchange participates in or is applying to the MANRS IXP Programme. You need to show, control by control, how a rustbgpd route server implements Action 1 — and how members can verify each control from the outside.

The normative source is MANRS-004.01, "MANRS Actions for IXP Program". Action 1 is mandatory for programme participation:

Action 1. Prevent propagation of incorrect routing information. (Mandatory) The IXP implements filtering of route announcements at the Route Server based on routing information data (IRR and/or RPKI). Based on the outcome of the validation process, the invalid announcements are filtered in accordance with the IXP published policy.

The specification's guidance names the usual validation inputs: IRR data (resolving the member's AS-SET), RPKI data (ROA objects or a validated cache), and checking announcements against bogons/martians — prefixes per RFC 1918, RFC 5735, RFC 6598, and ASNs in the AS_PATH per RFC 5398, RFC 6793, RFC 6996, RFC 7300, RFC 7607.

This page maps each element to the config that implements it and the surface that proves it. Every fragment below is drawn from a complete configuration that passes rustbgpd --check --strict (the full example is at the end).

Requirement map

Action 1 elementrustbgpd implementationVerify with
RPKI-based filtering (validated cache)[rpki] RTR client + a one-statement deny policy; maxLength is enforced in the Invalid determination (RFC 6811)rbgp policy explain, looking-glass filtered view
IRR-based filtering (AS-SET resolution)Per-member prefix/origin sets rendered from arouteserver's resolved IRR data (IXP filter pipeline)render receipt + looking-glass filtered view
Bogon / martian hygieneShared hygiene chain: special-purpose prefix rejection, AS_SET reject, ASPA-invalid reject (examples/route-server/hygiene.rpol); the rendered rs-hygiene.rpol adds transit-free and path-length capsrbgp rib received <member> --rejected
Filtering per published policy, fail-closedRFC 8212 posture (ebgp_requires_policy), fail-stale rendering, parse-then-swap reloadrustbgpd --check --strict, rbgp policy surfaces
Containment of misbehaving membersPer-family max-prefix ceilings with latched teardown (ADR-0108), outbound mirrors (ADR-0113)rbgp neighbor <ip> limit state, metrics
Member-visible transparencyReject-retention store + looking-glass filtered view with reject-reason communitiesAlice-LG, rbgp rib received <member> --rejected

RPKI: invalid = reject

Connect to your validated cache (Routinator, rpki-client, StayRTR, OctoRPKI — anything speaking RTR) and deny Invalid at import. A route is Invalid when a covering VRP exists but the origin AS does not match or the announced prefix exceeds the VRP's maxLength — so maxLength violations are rejected by the same statement, per RFC 6811:

[rpki]
[[rpki.cache_servers]]
address = "127.0.0.1:3323"

[policy.definitions.reject-rpki-invalid]
[[policy.definitions.reject-rpki-invalid.statements]]
match_rpki_validation = "invalid"
action = "deny"

Later VRP updates trigger an inbound Route Refresh for established members whose import policy depends on validation state, so verdicts track the cache rather than freezing at ingress time. Details: [rpki] reference, ADR-0034. Reject-at-import against a live RTR cache is exercised in the M83 interop lab (RECEIPTS.md).

IRR filtering: the pipeline

Action 1's IRR half expects each member's announcements checked against its registered AS-SET. rustbgpd's supported path is the IXP filter pipeline: keep your existing arouteserver general.yml / clients.yml, dump the resolved data model with arouteserver template-context, and render per-member .rpol prefix/origin sets plus per-family max-prefix ceilings with rs-config-render. The render is fail-stale, never fail-open: a broken or implausibly empty IRR answer aborts the whole render and the last good filters stay live (ADR-0110).

Honesty note for your MANRS documentation: the daemon has no native IRRd/bgpq4 client — IRR ingest rides arouteserver's resolver, caches, and refresh cadence. That is the same ingest incumbent route servers use; the rustbgpd-specific part is the render step, maintained in this repository.

Bogons, martians, and path hygiene

The checked-in starter hygiene chain (examples/route-server/hygiene.rpol) rejects announcements carrying AS_SET segments, rejects ASPA-invalid paths, and applies a dated dual-stack special-purpose (bogon/martian) prefix snapshot; the rendered pipeline's rs-hygiene.rpol adds transit-free rejection and a path-length cap, with RPKI origin validation folded in. Wire it into the import chain ahead of member-specific filters, as the route-server cookbook does.

The RFC 8212 posture

Action 1 requires that what the route server propagates is a policy decision, not a default. With ebgp_requires_policy = true, an eBGP direction that resolves no explicit operator policy runs a reserved internal deny — deleting the filter chain makes members stop receiving routes loudly instead of inheriting permit-all by omission:

[global]
asn = 64500
router_id = "192.0.2.1"
ebgp_requires_policy = true

# Transparent route-server export is explicit, not permit-by-omission.
[policy.definitions.rs-transparent-export]
default_action = "permit"

State it explicitly for a MANRS route server. With the key omitted, enforcement stays off for configs without config_epoch or with config_epoch = 1, and is on by default under config_epoch = 2 (ADR-0119, config_epoch). It is restart-required, and --check names every eBGP neighbor still resolving no explicit policy — --strict turns those warnings into a failing exit. See ADR-0112 and the ebgp_requires_policy reference.

Per-member prefix limits

Contain a member that de-aggregates or leaks past its registered ceiling. Limits are per family, enforced on accepted routes, and latch the session off on breach until explicit re-enable (optionally with one timed restart attempt):

[[neighbors]]
address = "192.0.2.10"
remote_asn = 64501
route_server_client = true
role = "route_server"
import_policy_chain = ["reject-rpki-invalid", "member-a-irr"]
export_policy_chain = ["rs-transparent-export"]
max_prefixes_ipv4 = 10000
max_prefixes_ipv6 = 2000
max_prefix_restart_seconds = 900

The pipeline sources these ceilings from PeeringDB via arouteserver's resolved context. References: ADR-0108 (per-family inbound), ADR-0113 (outbound mirrors), neighbor options.

What members can verify

Action 1 filtering is only credible if members can see it working:

  • Looking glass (examples/birdwatcher-adapter/
    • Alice-LG): accepted routes per member, and a filtered-routes view served from the daemon's reject-retention store, each rejection carrying machine-readable reject-reason communities Alice-LG renders as human-readable causes. This also satisfies the programme's Action 5 (provide monitoring and debugging tools — a route-server looking glass for members).
  • rbgp rib received <member> --rejected: enumerates a member's rejected announcements with reasons, without needing the prefix known in advance.
  • rbgp policy explain: replays a specific prefix through the import ladder and names the statement that rejected it — the support-ticket workflow is in explain.md.

Copyable example

The checked-in examples/manrs-action1/ is the minimal complete example. Its config.toml keeps RPKI-invalid rejection ahead of the member filter, references the .rpol file through rpol_files, enables the RFC 8212 posture, and declares transparent export explicitly while retaining a per-family prefix limit.

The member authorization is deliberately conjunctive. Both the resolved AS-SET and prefix-set must match; the tail rejects everything else:

asn-set member-a-origins { 64501 }
prefix-set member-a-prefixes { 203.0.113.0/24 }

policy member-a-irr {
    term accept-authorized {
        if route.origin-as in member-a-origins && route.prefix in member-a-prefixes { accept }
    }
    term reject-unauthorized { reject }
}

The adjacent in-language fixtures prove the authorized route is accepted, the same prefix with the wrong origin is rejected, and the registered origin cannot announce a different prefix. Validate the copy before deployment:

rustbgpd --check --strict examples/manrs-action1/config.toml
rbgp policy check examples/manrs-action1/member-a-irr.rpol

Publishing your policy

MANRS asks for the filtering policy to be published (or at least member-visible). The pieces to link from your IXP policy page: which inputs you filter on (IRR via your arouteserver site files, RPKI invalid = reject including maxLength, the bogon snapshot), the max-prefix action and restart window, and where members can see their own filtered routes (your Alice-LG instance). The evaluation matrix is the capability-level summary; this page is the per-control detail behind it.

Source on GitHub

On this page