-
Notifications
You must be signed in to change notification settings - Fork 3
peers
Pre-Alpha. This page describes behavior that may change.
A BGP peer in Ze is a named entry under bgp { peer <name> { } }. The name is a label you choose, not the peer's IP. You can rename it without losing state, you can refer to it from CLI commands by name, and you can group peers that share defaults under a group { } block.
This page covers what you need to bring up a session, the capabilities and address families you negotiate, prefix limits, authentication, and the per-session policy knobs (route reflection, next-hop control, community filtering, AS override). The configuration model is hierarchical, and most values can live at the global, group, or peer level.
bgp {
router-id 192.0.2.1;
session {
asn { local 65000; }
}
peer upstream {
connection {
remote { ip 10.0.0.1; }
local { ip auto; }
}
session {
asn { remote 65001; }
family {
ipv4/unicast { prefix { maximum 1000000; } }
ipv6/unicast { prefix { maximum 200000; } }
}
}
}
}
That is a complete peer. The router ID and local AS live at the BGP level and are inherited. The peer's transport settings go under connection (remote IP, local bind address). The peer's protocol settings go under session (remote AS, the address families to negotiate, their prefix maximums). Ze will not start without per-family maximums (RFC 4486), and the migration from ExaBGP fills them in with a conservative default that you should adjust before you point it at a full table.
After inheritance has been resolved, every peer must have these set somewhere in its parent chain.
| Field | Where it can live | Notes |
|---|---|---|
connection/remote/ip |
Peer | Peer's IP address. |
session/asn/remote |
Peer | Peer's AS. |
session/asn/local |
BGP, group, or peer | Your AS. |
connection/local/ip |
Suggested | Local bind address. auto lets Ze pick. |
If any required field is missing after inheritance, validators reject the commit with a message naming the field.
The same peer block works for eBGP, iBGP, route reflector clients, and route server clients. The difference is in the values, not in the syntax. eBGP is session/asn/local and session/asn/remote set to different ASNs. iBGP is the same ASN on both sides. A route reflector client adds session/route-reflector-client true; plus the cluster-id configuration described under Session policy.
A peer can be passive (connection/local/connect false), accept-only (connection/remote/accept true), or both. The defaults initiate outbound and accept inbound, which is what you want for a normal eBGP session.
Capabilities live under session/capability/ at any inheritance level. The most common ones:
session {
capability {
asn4; # 4-byte ASN, RFC 6793, on by default
route-refresh; # RFC 2918
extended-message; # RFC 8654, 65535-byte messages
graceful-restart { restart-time 120; }
}
}
role { import provider; } # RFC 9234 BGP Role, at the peer level
asn4 is on by default and you should not turn it off. route-refresh lets you ask the peer to re-send its full table without bouncing the session. graceful-restart is what keeps the RIB across a daemon restart and pairs with the bgp-persist plugin for the disk side. The role container is a peer-level leaf (not inside capability) as role { import <role>; }; values are provider, customer, peer, rs, and rs-client.
These knobs live under session { } and shape how Ze builds outbound UPDATEs and how it treats inbound routes. They are cumulative across BGP, group, and peer levels.
Mark a peer as a route reflector client to have Ze apply RFC 4456 iBGP reflection rules: routes learned from this peer are forwarded to all other peers (clients and non-clients), ORIGINATOR_ID is set if absent, and CLUSTER_LIST is prepended with the local cluster ID.
peer client-a {
connection { remote { ip 10.0.0.1; } }
session {
asn { remote 65000; } # iBGP: same AS as local
route-reflector-client true;
cluster-id 10.0.0.254; # optional, defaults to router-id
}
}
session/cluster-id is kept in sync with the loop-detection filter's cluster-id automatically, so ingress CLUSTER_LIST checks and egress CLUSTER_LIST prepending use the same value even if you only set one.
Controls the NEXT_HOP attribute on outbound UPDATEs.
session {
next-hop auto; # default
# next-hop self; # always rewrite to the local address
# next-hop unchanged; # never rewrite (route-server / third-party NH)
# next-hop 192.0.2.1; # explicit address
}
| Value | Behaviour |
|---|---|
auto |
RFC 4271 default: rewrite for eBGP peers, preserve for iBGP peers. |
self |
Always rewrite to the local address. Common for iBGP overlays. |
unchanged |
Never rewrite. Used by route servers and third-party next-hop setups. |
<ip> |
Set next-hop to the literal address. |
session/as-override rewrites the peer's own ASN in the outbound AS_PATH with the local ASN, so the route is accepted at the far end even though its AS_PATH contains the receiver's ASN. This is the standard trick for VPN or multi-site setups where the same customer ASN appears at several sites.
session { as-override true; }
AS override respects the peer's ASN4 negotiation, so 2-byte and 4-byte sessions both work.
session/asn/local-options tunes how a non-default local ASN shows up in AS_PATH. Both options are leaf-list values; use them individually or together.
session {
asn {
local 65001;
local-options [ no-prepend replace-as ];
}
}
| Option | Behaviour |
|---|---|
no-prepend |
Do not prepend the real local ASN in front of the advertised local-as. |
replace-as |
Replace the real local ASN entirely with the advertised local-as. |
With both set, AS_PATH contains only the advertised local-as and the true ASN is never visible to the peer.
session/community/send controls which community types Ze includes in outbound UPDATEs. Leave it unset to send everything.
session {
community { send [ standard large ]; }
}
| Value | Type |
|---|---|
standard |
RFC 1997 communities (type 8). |
large |
RFC 8092 large communities (type 32). |
extended |
RFC 4360 extended communities (type 16). |
all |
Send all types (default). |
none |
Suppress all community attributes. |
Use none for peers that must never receive your internal tags.
Per-family: inject a synthetic default route towards the peer. Live under session/family/<name>/.
session {
family {
ipv4/unicast {
prefix { maximum 1000000; }
default-originate true;
# default-originate-filter <filter-name>; # optional gate
}
}
}
default-originate is a boolean: true originates 0.0.0.0/0 for IPv4 families, ::/0 for IPv6 families. default-originate-filter is the name of a filter the default must pass before it is emitted. If the filter rejects, the default is not sent. Leave the filter leaf empty to originate unconditionally.
Each family is a child of session/family/ and carries its own prefix limit.
session {
family {
ipv4/unicast { prefix { maximum 1000000; } }
ipv6/unicast { prefix { maximum 200000; } }
ipv4/mpls-vpn { prefix { maximum 500; } }
l2vpn/evpn { prefix { maximum 10000; } }
}
}
IPv4 and IPv6 unicast and multicast are built into the engine. Every other family (FlowSpec, EVPN, VPN, BGP-LS, MPLS labels, MUP, RTC, MVPN, VPLS) is provided by an nlri plugin and only available if the plugin is loaded. Run ze --plugins to see which families are registered.
Prefix limits are mandatory. Every leaf in the prefix container belongs to the family that holds it, and the family that exceeded its maximum is the one that decides what happens next. Another family's value never applies.
| Setting | Default | Behaviour |
|---|---|---|
maximum |
required | Hard cap. Exceeded means the session is torn down with NOTIFICATION Cease / MaxPrefixes (subcode 1). |
warning |
90 % of maximum
|
Warning threshold. Logged and exported as a metric. |
count |
offered |
offered counts every announcement on the wire, so a re-announcement raises the count again. installed counts the prefix set the family currently holds. |
teardown |
true |
Tear down on exceed. Set false to warn-only and drop further NLRIs in the family. |
idle-timeout |
0 |
Seconds to wait before reconnect. The wait doubles on each repeat teardown, up to one hour. |
reconnect |
see below |
never, backoff, or timer. |
session {
family {
ipv4/unicast {
prefix {
maximum 1000000;
idle-timeout 300;
reconnect timer;
}
}
ipv6/unicast {
prefix {
maximum 250000;
teardown false;
}
}
}
}
reconnect with no value means timer when idle-timeout is above zero, and never when it is zero. A family that configured neither leaf therefore keeps the peer down after a teardown. The peer state reads idle-hold, the log line names the family and the reason, and ze show warnings carries the prefix-hold warning until an operator recreates the peer.
The teardown NOTIFICATION and the error carry the family that exceeded its maximum.
ze resolve peeringdb max-prefix <ASN> queries PeeringDB for each peer's ASN and updates the maximums automatically with a configurable margin. Review the change with ze config diff and commit it like any other edit.
Transport-level security lives under connection/.
peer upstream {
connection {
md5 { password "shared-secret"; }
ttl { set 255; max 254; }
}
}
connection/md5/password enables TCP MD5 (RFC 2385). connection/ttl/set and connection/ttl/max together implement RFC 5082 GTSM. TCP-AO is not implemented.
Ze validates the BGP Identifier on both sides of the OPEN exchange.
Locally, a router-id of zero is rejected, both on the global leaf and on a per-peer override. On receive, an OPEN is answered with OPEN Message Error / Bad BGP Identifier when its identifier is zero, or when it equals Ze's own identifier and the peer is internal. The same identifier from an external peer is accepted, which is what section 2.2 requires. Both receive paths validate: the ordinary one and the connection that won collision resolution.
Both checks run for a dynamic peer too. A dynamic peer has no configured remote AS when its OPEN arrives, so Ze reads the AS the peer advertises in its 4-octet AS capability (RFC 6793), and falls back to the My AS field when the peer advertises none. Reading the absent configured AS as zero made "internal peer" false for every dynamic peer, so a genuine iBGP dynamic peer presenting Ze's own identifier used to be accepted.
AS-wide uniqueness is enforced by default. A second peer in the same AS claiming an identifier already held by an established session is rejected. The claim is taken during OPEN validation and released on teardown. It reads the same 4-octet AS capability: a 4-byte-AS speaker must send AS_TRANS (23456) in My AS, so reading My AS put every such peer in one shared bucket and refused the second of two peers in genuinely different 4-byte ASes that shared an identifier. Section 2.1 scopes uniqueness per AS and licenses no such rejection.
bgp {
session {
allow-shared-router-id true;
}
}
RFC 6286 section 2.1 makes AS-wide uniqueness a SHOULD, not a MUST, so accepting a shared identifier is conformant. Set this when the duplication is deliberate, such as one anycast speaker peering over both IPv4 and IPv6.
When two peers present identical identifiers in a connection collision, the connection initiated by the larger AS number survives. The collision path validates the OPEN's declared length against the read buffer before reading the body, as both other receive paths already did. Without it, any host able to open a colliding TCP connection to a configured peer address could crash the reactor before capability negotiation.
A reload that moves an identifier between peers, which covers any router-id rotation or swap and any re-address pointing a new peer at the router an outgoing one served, releases the outgoing peer's claim synchronously in the removal pass. Before that, the claim was given up later on the departing peer's own goroutine, so the add pass could dial while the outgoing generation still held the identifier and Ze answered its own legitimate reloaded peer with OPEN Message Error / Bad BGP Identifier.
A peer receives exactly one End-of-RIB per address family per session. Two producers can emit it, the initial-sync loop and the route server's own post-replay marker, and the marker is now claimed before it is sent rather than gated on a time window, so a replay finishing after initial sync cleared cannot put a second identical marker on the wire. The claim is released if the send fails, so the other producer can still deliver, and the counter only advances when the send succeeded.
The End-of-RIB also waits for every plugin that declares a peer-up barrier to have taken delivery of the peer-up event. That makes "End-of-RIB sent" mean "every barrier plugin has registered this peer", which is what a peer needs before treating the marker as a go-ahead to send. The wait is bounded and never blocks establishment.
Attributes are written in type-code order (RFC 4271 Section 5.1, Appendix F.3) whatever builder emits them. MP_REACH_NLRI sits between the lower-coded optional attributes (ATOMIC_AGGREGATE, AGGREGATOR, COMMUNITIES, ORIGINATOR_ID, CLUSTER_LIST) and the higher-coded ones (EXT_COMMUNITIES, IPV6_EXT_COMMUNITIES, LARGE_COMMUNITIES). One route previously encoded to two different byte strings depending on whether it drained through the initial-sync queue or the post-establishment batch builder.
Import and export filter chains live under peer/filter/ and reference filter instances by name. See Route filters for the filter framework.
peer upstream {
filter {
import [ rpki:validate community:scrub ];
export [ aspath:prepend ];
}
}
Filter chains are cumulative from BGP globals to group to peer.
Timers live under timer/ at any inheritance level.
timer {
receive-hold-time 90;
send-hold-time 480;
keepalive 0;
connect-retry 120;
}
| Leaf | Type | Default | Description |
|---|---|---|---|
receive-hold-time |
uint16 | 90 |
Hold time proposed in OPEN (RFC 4271). 0 or 3–65535. |
send-hold-time |
uint16 | 0 |
Send-side hold timer (RFC 9687). 0 or 480–65535. 0 means auto: max(480, 2 * receive-hold-time). |
keepalive |
uint16 | 0 |
Keepalive interval in seconds (RFC 4271 Section 10). 0 means auto: negotiated hold-time / 3. Non-zero overrides the derivation. Validation rejects values >= hold-time. During negotiation, clamped to negotiated-hold/3 if the peer proposes a lower hold-time. |
connect-retry |
uint16 | 120 |
Connect retry interval in seconds (RFC 4271 Section 8). |
RFC 9687 introduces a send-side hold timer: if Ze cannot write a KEEPALIVE or UPDATE to a peer within send-hold-time seconds, it tears the session down. This protects against a peer that accepts TCP data but never reads from the socket. With the default receive-hold-time of 90, the effective send hold time is 480 seconds.
Ze runs the full RFC 4271 Section 8.2.2 Event 10 action list. It sends NOTIFICATION Hold Timer Expired (error 4), releases the session resources, moves the FSM to Idle, and increases the ConnectRetryCounter. The NOTIFICATION had no producer at all until 2026-08-03: a hold expiry dropped the connection and told the peer nothing.
Ze also granted one reprieve on a hold expiry when the read loop had recently seen traffic. That grace is removed. Event 10 permits no such deviation, and worst-case dead-peer detection falls from two hold times to one. A daemon whose CPU is saturated now drops sessions the grace used to keep.
ConnectRetryCounter is RFC 4271 Section 8.1.1 mandatory session attribute 2. Read it as connect-retry-counter in show bgp peer <name> detail, or as the ze_bgp_connect_retry_counter gauge. It is a gauge and not a counter, because the RFC's own clauses reset it to zero on an operator start and on an operator stop.
A second OPEN is refused. An OPEN that arrives while the connection is already in Established or OpenConfirm is answered with NOTIFICATION Cease (error 6) and the session ends. Ze does not process it, so a peer cannot rewrite the negotiated capability set of a live session. The ze_bgp_open_in_established_total counter names the peer that tried. RFC 4271 Section 8.2.2 routes Event 19 through collision detection in both of those states, and the termination action there is a Cease.
Every read-loop exit ends the session. An import policy teardown, an OPEN that fails to unpack, an OPEN the peer-level validator rejects, and a local capability parse error each close the connection and mark the peer down. A peer left marked up behind a dead socket, with its routes retained and replayed on reconnect, cannot happen through these paths.
The behavior { } container holds operational knobs that affect how Ze builds and sends UPDATEs for a peer. It can live at the group or peer level.
behavior {
group-updates true;
manual-eor false;
auto-flush true;
}
| Leaf | Type | Default | Description |
|---|---|---|---|
group-updates |
boolean | true |
Batch NLRI that share the same path attributes into a single UPDATE rather than one UPDATE per prefix. |
manual-eor |
boolean | — | Manual End-of-RIB. When set, Ze does not send EOR automatically after the initial table dump; trigger it from a plugin or the CLI. |
auto-flush |
boolean | — | Auto-flush routes. When set, Ze flushes pending outbound updates to the peer immediately rather than waiting for the reactor loop. |
rib { } controls per-peer Adj-RIB storage and outbound batching.
rib {
adj {
in true;
out true;
}
out {
group-updates true;
auto-commit-delay 0;
max-batch-size 0;
}
}
| Leaf | Type | Default | Description |
|---|---|---|---|
adj/in |
boolean | — | Maintain Adj-RIB-In for this peer. Required for ze show rib routes received. |
adj/out |
boolean | — | Maintain Adj-RIB-Out for this peer. Required for ze show rib routes sent. |
out/group-updates |
boolean | true |
Group outbound updates (same as behavior/group-updates but scoped to the RIB out path). |
out/auto-commit-delay |
uint32 (ms) | 0 |
Delay before auto-committing a batch of outbound updates. 0 means commit immediately. |
out/max-batch-size |
int32 | 0 |
Max updates per outbound batch. 0 means unlimited. |
attach process <name> connects a peer to a program (built-in or external). It is written as one statement per program, and Ze prints it back in the same flat form.
attach process my-plugin {
receive [ update-received state ];
send [ update ];
content {
encoding json;
format raw;
attribute minimal;
}
}
The block is authoritative in both directions. receive is what this peer feeds the program. send is what the program may originate toward this peer. A peer that attaches no program feeds it no peer-scoped event and cannot be announced to by it. An event that carries no peer address is not peer-scoped, so no attach block describes it and every subscriber still receives it.
The keyword was process <name> until 2026-08-15, and the daemon read neither list. Both lists are now read at delivery, and the send permission is enforced at every rail that reaches a peer's socket.
| Leaf | Type | Description |
|---|---|---|
receive |
leaf-list | Event types this peer feeds the program. |
send |
leaf-list | Message types the program may send toward this peer: update, refresh, enhanced-refresh, or *. |
content/encoding |
string | Event encoding format (json, text, …). |
content/format |
string | Event output format (raw, …). |
content/attribute |
string | Attribute filter expression controlling which path attributes are included. |
The BGP namespace registers update, open, notification, keepalive, refresh, state, negotiated, eor, congested, resumed, rpki, listener-ready, and update-notification. Plugins register more, such as update-rpki.
| Token | Grants |
|---|---|
update |
Both directions of that type. |
update-received |
The inbound half only. |
update-sent |
The outbound half only. |
* |
Every registered type, in both directions. |
A token that names a registered type keeps its whole name, so a plugin type ending in -sent is never read as a direction. all is not accepted: a word that reads like a type would silently grant a type a plugin registers later, which * says on purpose. A bare sent or received is a direction and not a type; Ze refuses it and names the token to write instead.
Both directions of update deadlock bgp-rs and bgp-rr: forwarding an UPDATE raises the sent event that the forward is waiting on. Give those plugins update-received.
Ze logs a warning when the two halves of an edge disagree. The four cases are a grant the plugin never declared, a declaration no peer grants, a direction mismatch, and a program that no peer attaches at all.
An operator command is not gated by send. The list grants authority to a program, and AAA already checks the operator before dispatch.
ze# peer list
NAME REMOTE AS STATE
upstream 10.0.0.1 65001 ESTABLISHED
ze# peer upstream show
peer upstream {
connection { remote { ip 10.0.0.1; } }
session { asn { remote 65001; } }
state established
uptime 2h13m
capabilities [ four-octet-asn add-path route-refresh graceful-restart ]
families [ ipv4/unicast ipv6/unicast ]
prefixes received 947213 / max 1000000
}
ze# peer upstream capabilities
ze show peer <name> detail is the long version. ze show bgp summary is the table view across all peers, including per-session uptime.
A group whose connection/remote/ip is dynamic accepts a session from any address inside its ranges. Ze builds one peer per accepted connection. Use it for an IXP route server, or for any fabric whose members are not known when you write the configuration.
bgp {
group ix {
connection {
remote {
ip dynamic;
connect false;
range 198.51.100.0/24;
range 2001:db8:1::/64;
max-peers 500;
}
local {
ip 198.51.100.1;
accept true;
}
}
session {
asn { local 64500; }
router-id 198.51.100.1;
family { ipv4/unicast { prefix { maximum 200000; } } }
}
role { import rs; }
attach process rs {
receive [ update-received state open-received refresh ];
send [ update ];
}
attach process adj-rib-in {
receive [ update-received state ];
}
}
}
| Leaf | Type | Default | Description |
|---|---|---|---|
connection/remote/ip |
dynamic |
none | Marks the group as a dynamic peer group. Valid at group level only; a peer that states it is rejected. |
connection/remote/range |
leaf-list of prefixes | none | The addresses that may open a session. Overlapping ranges take the longest match. |
connection/remote/max-peers |
uint32 | 1000 | Maximum members of this group. |
Two statements are required and the configuration is refused without them: at least one range, and an explicit connect false. Ze only accepts on a dynamic group and never dials one, so an absent connect leaf, which YANG defaults to true, is refused rather than corrected.
Inheritance is the default and divergence is the exception. A member is built from the group's own resolved settings, so it carries every leaf the group states: address families and their prefix limits, ADD-PATH require and refuse sets, import and export filter chains, static routes, MD5, BFD, capture, attach process blocks, community send, AS override, route-reflector-client, cluster ID, and the local-AS options.
Seven values diverge, and only these seven.
| Field | Value on a member |
|---|---|
| Name | dyn-<address> |
| Group name | The group's name |
| Address | The address the connection arrived from |
| Port | The canonical BGP port, because the source port names no configuration |
| Remote AS | Learned from the peer's OPEN (RFC 4271 Section 4.2) |
| Connection mode | Passive |
| Dynamic flag | Set |
A dynamic group is held to the same rules as a static peer. A family with no prefix { maximum } is refused (RFC 4486), and a hold time RFC 4271 Section 4.2 refuses is refused here too.
The group opens its own listening socket on connection/local/ip and the group's listen port. A configuration that names no static peer at all is therefore enough to accept members, which is the shape a route server has.
When a static peer and a dynamic group share one socket, Ze attributes each connection by remote address. A static peer whose address is inside the group's range keeps its own session and its own settings. A reload that adds a group opens a listener for it without restarting the others.
A per-peer plugin setting stated on the group is delivered to its members. Ze resolves a member's config by address first and by its group second, so a member that states its own value keeps it. This covers the RFC 9234 role gates and the Role capability, the RPKI action, the RFC 7999 blackhole agreement, and the community policy.
See the BGP Role page for the role side.
A configuration reload leaves a member's session up when its settings did not change. Ze removes a member only when its group is gone, when its address left every range, or when the group template would now build different settings for that one address.
A wider range or a raised max-peers changes nothing a running member holds, so neither bounces a session. A member whose template changed is removed and rebuilt, so the next connection is accepted under the new settings.
See bgp-rs for the route server plugin configuration.
- Validation fails with "required field missing": one of
connection/remote/ip,session/asn/remote, orsession/asn/localis not set anywhere in the inheritance chain. - Ze refuses to start with "prefix maximum required": every negotiated address family must have a
prefix { maximum }. Add it undersession/family/<afi-safi>/prefix. - Session bounces immediately after Established: check
peer <name> showfor capability mismatches and the log for the peer's NOTIFICATION code. - Session never makes it past Active: TCP is not getting through. Check
connection/md5/password, the source IP thatconnection/local/ipresolves to, and anyconnection/ttlor firewall rules in the path.
- Route filters for import/export filter chains.
- Policies for what the policy surface covers.
- Route reflection for the route server model and iBGP RR context.
- Configuration overview for the YANG model and the commit workflow.
- Quick start for the shortest path to a session.
- ExaBGP migration if you are converting an existing config.
Adapted from main/docs/guide/configuration.md, the
ze-bgp-conf.yangschema, and main/docs/features/bgp-protocol.md.
Unreviewed draft. This wiki was authored in bulk and has not been reviewed. File corrections on the issue tracker.
- Overview
- YANG Model
- Editor Workflow
- Archive and Rollback
- System
- Interfaces
- VRRP
- BFD
- FIB
- OSPF
- IS-IS
- MPLS / LDP / RSVP-TE
- RSVP-TE
- SRv6
- Static Routes
- Policy Routing
- Firewall
- Traffic Control
- Class of Service
- L2TP/PPP
- PPPoE
- VPP Data Plane
- RPKI
- IPsec VPN
- TACACS+ AAA
- RADIUS AAA
- AS112 DNS
- DNS
- Authorization
- Fleet
- BGP
- Starting and Stopping
- Show Commands
- Monitoring
- Flow Export
- DDoS Mitigation
- Anomaly Detection
- Health Checks
- Audit Trail
- Production Diagnostics
- Logging
- Operational Reports
- Healthcheck
- Self-Update
- Zero-Touch Provisioning
- MRT Analysis
- Upgrade and Restart
- Storage
- Policy
- Core
- Resilience
- Validation
- Capabilities
- Address Families
- Protocol
- Subsystems
- Infrastructure
- Route Server at an IXP
- Transit Edge with RPKI
- Public Looking Glass
- ExaBGP Migration Walkthrough
- FlowSpec Injection
- Chaos-Tested Peering
- AS Path Topology