Skip to content

redistribution

Thomas Mangin edited this page Jul 25, 2026 · 4 revisions

Pre-Alpha. This page describes behavior that may change.

Route filters let plugins act on BGP UPDATEs as they move through the daemon. Filters attach on import (ingress) and export (egress), per peer, per group, or globally. You reference them by name in a filter { import/export } block, and you define the tunable instances themselves under bgp { policy { } }. The engine runs the chain on every received or forwarded UPDATE.

Renamed from "redistribution". This page used to describe a redistribution { import/export } block. Both the block name and the default-filter naming changed. See Migrating from redistribution at the bottom.

The plugin-author side is documented on the plugin development pages. This page is for operators picking filters, not authors writing them.

A minimal example

bgp {
    policy {
        loop-detection no-self-as {
            allow-own-as 0;
        }
    }
    filter {
        import [ no-self-as rpki:validate ];
    }
    group customers {
        filter {
            import [ community:scrub ];
            export [ aspath:prepend ];
        }
        peer customer-a {
            connection { remote { ip 10.0.0.1; } }
            session    { asn { remote 65001; } }
            filter {
                export [ community:mark-customer-a ];
            }
        }
    }
}

Two things happen in this config:

  1. The bgp/policy block defines filter instances. Here, loop-detection no-self-as is an instance of the built-in loop-detection filter type with a tuning leaf. External plugin filters like rpki:validate or community:scrub do not need a policy entry, because the plugin itself declares them at startup.
  2. The filter { import/export } blocks reference filter instances by name, and the chains are cumulative from global to group to peer.

The effective import chain for customer-a is no-self-as -> rpki:validate -> community:scrub. The effective export chain is aspath:prepend -> community:mark-customer-a.

Filter types vs filter instances

This is the biggest conceptual change from the old redistribution model.

  • A filter type is a YANG list under bgp/policy marked with the ze:filter extension. Built-in types ship with Ze. External plugins add new types via YANG augment. The type defines the configurable leaves.
  • A filter instance is a named entry in that list. You pick the name. You tune the leaves. Each instance is then referenceable by that name in any filter { import/export } chain.

You can create multiple instances of the same type with different tunings. For example, one loop-detection strict { allow-own-as 0; } for transit peers and another loop-detection relaxed { allow-own-as 3; } for an AS-confederation member, and reference each from the peers that need it.

External plugin filters use the <plugin>:<filter> naming (rpki:validate, community:scrub). Those come from the plugin's stage-1 declare-registration call, not from a policy block.

Built-in filter types

loop-detection

Configures the BGP loop-detection that runs on every received UPDATE. Loop detection itself is always on, with sensible defaults (reject on the first AS match, cluster ID equal to Router ID). You only need a loop-detection entry if you want to tune those defaults or make the check visible in the filter chain.

Leaf Type Default Purpose
allow-own-as 0 – 10 0 How many times the local ASN may already appear in AS_PATH before the route is rejected. 0 is RFC 4271 Section 9 behaviour.
cluster-id IPv4 address Router ID Override the cluster ID used for CLUSTER_LIST loop detection (RFC 4456 Section 8). If session/cluster-id is set on the peer, the two are kept in sync automatically.

Checks performed on every received UPDATE:

  1. AS loop — local ASN in AS_PATH (all sessions, RFC 4271 Section 9).
  2. Originator-ID loopORIGINATOR_ID == local Router ID (iBGP only, RFC 4456 Section 8).
  3. Cluster-list loop — local Router ID (or cluster-id) in CLUSTER_LIST (iBGP only, RFC 4456 Section 8).

When any check fires, the UPDATE is rejected. The AS-loop threshold is tunable via allow-own-as.

External plugin filters

External plugin filters are declared by the plugin at stage 1 of startup with a direction (import, export, or both), a list of attributes they want to receive, and a failure mode. Operators only see the references in config. The declarations live in the plugin.

You reference them as <plugin>:<filter>, for example:

filter { import [ rpki:validate community:scrub ]; }

The default chain

Some filters are auto-populated into every peer's import chain without being named. Today the only defaults come from loop-detection entries: if you define loop-detection no-self-as { ... } in bgp/policy, that name is prepended to every peer's import chain unless already referenced (explicitly or as inactive:). If you do not define any loop-detection entry at all, no default name shows up in the chain, but loop detection itself still runs with built-in defaults.

Default names always run before any user filter in the chain.

Deactivating a default filter

Sometimes you need to turn a default filter off on one peer, for example to accept your own ASN on a specific session. There are two equivalent ways.

In the config, prefix the filter name with inactive: in the import list:

bgp {
    peer special {
        filter {
            import [ inactive:no-self-as ];
        }
    }
}

In the CLI editor, use deactivate / activate on the individual list entry:

deactivate bgp peer special filter import no-self-as
activate   bgp peer special filter import no-self-as

Both forms leave every other peer's chain untouched. For loop-detection specifically, deactivation also suppresses the in-process loop check for that peer — not just the filter-chain reference.

Chain order

Chains are cumulative across config levels:

Level Merge rule
Default filters Auto-populated first (e.g. loop-detection entries).
bgp { filter } Base user chain.
group { filter } Appended to the BGP chain.
peer { filter } Appended to the group chain.

If the BGP globals declare [ rpki:validate ], the group declares [ community:scrub ], and the peer declares [ aspath:prepend ], the effective import chain for that peer is:

no-self-as -> rpki:validate -> community:scrub -> aspath:prepend

To insert a filter at a specific position rather than appending, use insert in the CLI editor:

insert filter import reject-bogons before rpki:validate
insert filter import new-filter last

insert accepts first, last, before <name>, and after <name> positions.

Filter responses

Every filter call returns one of three responses.

Response Meaning
accept Pass the UPDATE through unchanged.
reject Drop the UPDATE. The chain short-circuits: no subsequent filter runs.
modify Change specific attributes (delta only).

On modify, the filter returns only the changed fields. The engine uses wire-level dirty tracking to re-encode only the touched attributes into the wire bytes. When a filter returns accept with no changes, nothing is re-encoded.

Failure handling

Each filter plugin declares its failure mode at stage 1. Failure means an IPC timeout or an explicit error from the plugin.

Mode Behaviour on failure
reject Fail-closed. The UPDATE is dropped.
accept Fail-open. The UPDATE passes through.

The plugin author picks based on the filter's security semantics. An RPKI validator that fails closed rejects everything when the cache becomes unreachable. An RPKI validator that fails open accepts unvalidated routes. Neither is universally right. The decision lives with the plugin.

Copy-on-need on egress

This is where the filter framework interacts with Ze's zero-copy architecture. On the egress side, a filter writes attribute modifications into a ModAccumulator that is lazy: if nothing writes to it, no copy of the wire bytes is allocated. A destination peer with no egress-modifying filters receives the cached bytes unchanged when the source and destination share an encoding context.

This is why a route server with identical peers stays fast even with filters declared: the filters run, but they do not allocate when they do not modify.

Migrating from redistribution

If you previously wrote:

bgp {
    redistribution {
        import [ rpki:validate ]
    }
}

the new equivalent is:

bgp {
    filter {
        import [ rpki:validate ]
    }
}

External plugin filter references (<plugin>:<filter> like rpki:validate or community:scrub) carry over unchanged. What changes is:

  • Block name. redistribution { ... } becomes filter { ... }.
  • Loop-detection naming. The old hardcoded default filter names (rfc:no-self-as, rfc:bogon-check) are gone. Loop detection is now surfaced as the loop-detection filter type under bgp/policy; you get the same behaviour by default without writing any config, and you tune it by adding a loop-detection <name> { ... } entry.
  • Mandatory / default / user categories collapsed. There are now only default filters (auto-populated from loop-detection entries) and user filters (explicit in filter { import/export }).

Writing your own filter

Writing a filter plugin is the same as writing any Ze plugin, except that the plugin's stage-1 declare-registration includes a filters field listing the filters it provides. See plugin development for the general plugin surface and the in-tree architecture reference for the wire protocol.

See also

Adapted from main/docs/guide/redistribution.md.

Home

About

First Steps

Configuration

Operation

Interfaces

Plugins

Plugin Development

Chaos Testing

Blueprints

Development

Reference

Clone this wiki locally