Skip to content

policies

Thomas Mangin edited this page May 29, 2026 · 5 revisions

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

Ze does not have a built-in filter language in the style of BIRD's filter DSL or FRR's route-map. Policy is a composition of plugins, assembled by referencing named filters in a peer's filter { import/export } block and (for filters that need tuning) defining their instances under bgp { policy { } }. This is a deliberate design choice: plugins can be written in Go or in any external language, and the engine stays out of the policy business.

This page covers what the current policy surface looks like, how the chain is built, and what the shipping plugins provide. The filter API itself, including filter block syntax, default-filter auto-population, and the inactive: / deactivate mechanism, is documented under route filters; this page is for operators picking policy, not authors writing it.

Loop detection is on by default

Ze always runs loop detection on ingress. Without any config, you get:

  • Local ASN in AS_PATH (AS_SEQUENCE or AS_SET), per RFC 4271 Section 9.
  • Local Router ID as ORIGINATOR_ID on iBGP sessions, per RFC 4456 Section 8.
  • Local Router ID in CLUSTER_LIST on iBGP sessions, per RFC 4456 Section 8.

Routes that fail any check are treated as withdrawn. No NOTIFICATION, the session stays up, the route never enters the RIB.

To tune the check (allow-own-as count, custom cluster-id), define a loop-detection filter instance under bgp/policy:

bgp {
    policy {
        loop-detection no-self-as { allow-own-as 0; }
    }
}

The instance name is then auto-populated at the front of every peer's import chain. To suppress loop detection on a specific peer, use inactive:<name> or deactivate in the CLI editor. See route filters for the mechanics.

The Role plugin enforces RFC 9234

When the role plugin is loaded and a peer has a role { import <role> } declaration, RFC 9234 role filtering runs on both ingress and egress. On receipt, routes from an invalid role direction are marked as leaks based on the OTC attribute. On send, OTC is stamped with the local ASN for peers whose role is customer, route-server-client, or peer (but not provider or route-server).

Role enforcement lives in the role plugin, not as an entry in the filter chain. See BGP Role for the reference.

Community filtering with bgp-filter-community

bgp-filter-community is a user filter for tagging, stripping, and matching communities (standard, large, extended). A typical use:

bgp {
    peer customer {
        connection { remote { ip 10.0.0.1; } }
        session {
            asn { remote 65001; }
            family { ipv4/unicast { prefix { maximum 1000000; } } }
        }
        filter {
            import [ community:scrub ];
            export [ community:mark-customer ];
        }
    }
}

The exact named filters (community:scrub, community:mark-customer, and so on) come from the plugin's own config, which you author to match your community policy. The in-tree plugin ships with a small set of examples, and the plugin reference has the full list.

If you only need to stop Ze from sending standard, large, or extended communities to a specific peer, use the session/community/send leaf instead of a filter. That is a session-level knob, not a filter.

Policy plugins under bgp { policy { } }

Beyond loop detection, several filter and modifier plugins define their instances under bgp { policy { } } and are then referenced by name in a peer's filter chain. Each plugin registers a filter type; you create named instances of that type.

Plugin Filter type Policy block Decision
bgp-filter-modify modify modify <name> { set/increment/decrement } Always modify (set/inc/dec attributes, community add/remove). See route attribute modifier.
bgp-filter-remove-private-as remove-private-as remove-private-as <name> { replace-with strip|peer-as } Modify (strips RFC 6996 private ASNs from AS_PATH, or replaces them with the peer AS).
bgp-filter-aspath-length as-path-length as-path-length <name> { max N; min N } Accept or reject based on AS_PATH hop count.
bgp-filter-community-match community-match community-match <name> { entry <community> { type standard|large|extended; action accept|reject } } Accept or reject on first matching entry; implicit reject if none match.

A small example combining a match filter with a modifier:

bgp {
    policy {
        as-path-length too-long { max 50; }
        remove-private-as scrub { replace-with strip; }
        modify set-pref { set { local-preference 150; } }
    }
    peer upstream {
        filter {
            import [ too-long set-pref ];
            export [ scrub ];
        }
    }
}

For community-match, entry order is significant: the first entry whose community is present in the route decides the outcome, and a route matching no entry is rejected (implicit deny). The type leaf selects which attribute is checked (standard, large, or extended) and defaults to standard; the action leaf defaults to accept.

Referencing filters by name

A chain entry can reference a policy instance in three forms:

  • Plain name: [ scrub ]. Plain names (no colon) are validated against the policy registry at commit time, so a typo is rejected up front.
  • Type form: [ remove-private-as:scrub ].
  • Plugin form: [ bgp-filter-remove-private-as:scrub ].

Dry-running a policy with show policy test

show policy test runs a peer's configured filter chain (or a single named filter) against a hex-encoded BGP UPDATE without forwarding the route, touching the RIB, populating cache, or mutating peer state:

show policy test peer upstream import update <HEX>
show policy test peer upstream export update <HEX>
show policy test peer upstream export filter scrub update <HEX>

<HEX> is a full BGP UPDATE message including the 19-byte header (the 0x prefix is optional). The peer selector comes first, then the direction, then optional filter <NAME>, then update <HEX>. Add source-asn4 false to test with an ASN2 encoding context (default ASN4), which makes AS4_PATH (RFC 6793) the active path carrier.

Output is structured JSON with direction, peer, action (accept/reject/modify), trace (per-filter decisions), text-before, text-after, changed-attrs, and wire-changes (wire-level ops such as AS4_PATH suppressed that the flat filter text cannot express).

Prefix list and AS-path regex matching

Two things people reach for on other daemons are not in Ze: prefix list matching with ge/le modifiers, and AS-path regular expression matching. Neither is implemented.

If you need prefix list matching, the workaround is a custom plugin. The plugin receives UPDATE events, keeps its own prefix table, and calls UpdateRoute with a rewritten attribute set when the prefix matches. This is more code than a filter expression, but it works with what the engine provides.

If you need AS-path regex matching, the same workaround applies.

The shape of a policy chain

Every filter chain has the same shape:

  1. Default filters (auto-populated from loop-detection entries under bgp/policy).
  2. User filters from the bgp { filter { } } block.
  3. User filters from the group { filter { } } block.
  4. User filters from the peer { filter { } } block.

The chains are cumulative. A peer inherits the BGP-level chain plus its group's chain plus its own. Each filter in the chain sees the previous filter's output, and each one can accept, reject, or modify. A reject short-circuits the chain.

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

insert filter import reject-bogons before rpki:validate

A filter that modifies an attribute writes into a ModAccumulator, which is lazy: if no modification happens, no copy of the wire bytes is allocated. This is the engine's copy-on-need story, described in architecture.

Ingress vs egress, when it matters

Ingress filters run on routes as they come in from a peer. They can mark metadata on the cached entry (which shows up in best-path selection and in later filters) and they can reject routes before they enter the RIB.

Egress filters run per destination peer, per route, as the route is being forwarded. They are the right place to modify per-peer attributes (prepending AS path for a specific peer, adding a customer community, rewriting next-hop).

Because the destination peer is different on every egress, the same underlying cached UPDATE can produce different wire bytes on the way out to different peers. Ze only materialises those different bytes when a filter actually changes something. A destination peer with no egress-modifying filters gets the cached bytes unchanged when source and destination share an encoding context.

What to use today

The honest picture of what the shipping policy surface covers.

What works well today. Loop detection (loop-detection filter type), RFC 9234 role enforcement (role plugin), RPKI origin validation (bgp-rpki), community tagging and stripping (bgp-filter-community), route attribute modification including set/increment/decrement and community add/remove (bgp-filter-modify, see route attribute modifier), private-AS removal (bgp-filter-remove-private-as), AS-path length filtering (bgp-filter-aspath-length), community matching (bgp-filter-community-match), show policy test dry-run, session-level next-hop control / AS override / community-send control / default-originate (under session), iBGP route reflection (route-reflector-client and cluster-id), and custom policy through external plugins.

What does not. Prefix list matching with ge/le, AS-path regex, a named-policy DSL. If any of these are load-bearing for your deployment, Ze is the wrong answer today.

See also

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

Home

About

First Steps

Configuration

Operation

Interfaces

Plugins

Plugin Development

Chaos Testing

Blueprints

Development

Reference

Clone this wiki locally