Skip to content

exabgp migration

Thomas Mangin edited this page Nov 9, 2026 · 6 revisions

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

This is the first page to read if you already run ExaBGP. Ze ships two tools that, between them, get most existing setups running with very little manual work: a config converter (ze exabgp migrate) and a bridge plugin (ze exabgp plugin) that runs your existing process scripts unchanged. The order matters: convert the config first, then keep your scripts running through the bridge while you decide which ones to port to native Ze plugins.

Convert the config

ze exabgp migrate exabgp.conf > ze.conf       # Convert an ExaBGP config
ze exabgp migrate --env exabgp.env            # Convert an ExaBGP INI environment file
ze config validate ze.conf                    # Verify against the YANG schema

Older Ze syntax is upgraded by a separate tool, ze config migrate, a chain of named transformations you can list with ze config migrate --list. Each one renames one piece of legacy syntax into its current Ze form. The most visible ones rename neighbor blocks to peer, lift root-level globs into template { match }, and rename template { neighbor } to template { group }. You can read the full list in the in-tree migration guide linked at the bottom.

One automatic change is worth knowing about. The converter adds prefix { maximum 10000; } to every address family it touches. ExaBGP has no per-family prefix limit, and Ze refuses a family that has none. That requirement is Ze's own: RFC 4486 defines only the Cease subcode, Maximum Number of Prefixes Reached, that a speaker sends when it ends a session over a limit. 10,000 is a conservative starting point. Edit the value before you go anywhere near a full table peer.

What the converter writes for you

A neighbor that sends the Software Version capability gets software-version { encoding legacy; }, because ExaBGP (and FRR) expect the older length-prefixed framing. The ExaBGP processes land under exabgp { bridge { process <name> { ... } } }, and each neighbor that bound a process becomes a feed <peer> entry in that process block. attach process is written only for the internal plugins the migrated config needs: bgp-rib, exabgp-bridge and bgp-watchdog.

A static route with watchdog <name> becomes update { watchdog { name <name>; } }, and one that also says withdraw gets withdraw true, so it starts withdrawn until the script sends announce watchdog <name>, as in ExaBGP.

Every migrated peer gets an explicit route-exchange policy. Ze rejects eBGP import and export when the effective direction has no explicit policy (RFC 8212); ExaBGP has no such rule. The converter therefore writes, on every migrated peer (iBGP neighbors and neighbors expanded from templates included):

filter {
    import { final accept; }
    export { final accept; }
}

Each peer's warning names RFC 8212. The policies are never placed on the BGP root or a group, so a peer added later does not inherit acceptance because its group holds a migrated peer. This does not exempt the bridge from policy: a hand-written Ze config that runs ExaBGP scripts must state its own policies wherever routes should flow. Review the emitted accepts and narrow them where appropriate. A config that validates has not yet proved that its scripts exchange routes: run it with its scripts against a peer.

A few more values are rewritten rather than copied:

ExaBGP What the converter writes
family { all; } Every registered family, each with prefix { maximum 10000; }, as an omitted family block gets.
add-path { ipv4 unicast limit N; } limit { receive N; } on the add-path family: ExaBGP's limit is the PATHS-LIMIT receive request its OPEN advertises, and it has no send-side limit. A family named without a limit gets no limit.
A FlowSpec traffic-class above 63 (ExaBGP 4.2 and 5.0 accept up to 0xFFFF) Its six least significant bits as the DSCP, which is how RFC 8955 Section 4.2.2.11 reads the field and how ExaBGP 6.0 decodes it. Ze refuses a DSCP above 63.

When a capability word does not carry over, the converter converts the rest of the neighbor and names the word on stderr, one warning per peer. multi-session and operational are dropped this way: Ze supports neither BGP multi-session nor the OPERATIONAL message.

The neighbor qualifiers (neighbor <ip> local-as <asn> peer-as <asn> announce ...), which pick one of several sessions to the same address, are read by the bridge and ignored, apart from family-allowed. Ze does not support BGP multi-session, so an address names one session and the command goes to it.

ExaBGP 6.0 receive policy: as-set and tunnel-encapsulation

ExaBGP 6.0 added two neighbor leaves that decide what happens to a received route. The converter writes nothing for a value whose effect is Ze's default, writes Ze's own leaf where one gives the same effect, and refuses the config, naming the leaf, for a value it cannot reproduce.

ExaBGP line Migration Why
as-set withdraw (the default) Nothing written. Ze treats every received route whose AS_PATH or AS4_PATH holds an AS_SET or AS_CONFED_SET as withdrawn (RFC 9774 Section 3).
as-set accept session { as-set accept; } on the peer. The peer keeps such a route, as the ExaBGP neighbor did. RFC 9774 Section 3 allows it when explicitly configured by the operator; any Ze peer may set the leaf, and a peer without it keeps the RFC's behaviour. Structure and AS 0 checks still apply.
tunnel-encapsulation accept Nothing written. Ze keeps a received Tunnel Encapsulation attribute on every session.
tunnel-encapsulation auto (the default) Nothing written. On an external neighbor ExaBGP drops the attribute and Ze does not yet: a known RFC 9012 Section 11 gap.
tunnel-encapsulation filter Refused. Ze cannot yet drop the attribute from one peer.

Removing a refused line migrates the peer with Ze's behaviour, which the refusal names.

ExaBGP 6 behaviours the converter keeps

ExaBGP What the converter writes
on-exit keep or on-exit withdraw in a process block The same leaf, as written, and nothing when ExaBGP leaves it unset. The bridge then treats an exited script's routes as ExaBGP 6.0 does.
shutdown true on a neighbor The peer shutdown {} container, so the peer stays held down. request peer <selector> enable releases it.
No hold-time timer { receive-hold-time 180 }, the hold time ExaBGP offers by default. Ze's own default is 90.
graceful-restart; or graceful-restart 0; graceful-restart <hold time>, capped at 4095, which is the Restart Time ExaBGP advertises. Ze's own default is 120.
An IPv4 route with an IPv6 next hop The Extended Next Hop capability for that family, with one warning per family. RFC 8950 Section 4 forbids sending such a route to a peer that has not agreed to the capability. A config that writes capability { nexthop disable; } is refused, as ExaBGP 6.0 refuses it.
An IPv6 route with an IPv4 next hop The next hop written IPv4-mapped, ::ffff:<ipv4>, as ExaBGP 6.0 sends it.
auto-flush Nothing. ExaBGP has ignored it since 4.0.4, and the migrated peer also sends every route at once.

The API version a script speaks is set by environment { exabgp { api { version auto|4|6; } } }, which is ExaBGP's exabgp.api.version. ze exabgp migrate --env carries it from an ExaBGP environment file. With auto, the default, the bridge holds each script to the API of the first command that tells: neighbor, announce, withdraw and the other API 4 words mean API 4, and peer, daemon, session, system and rib mean API 6. A JSON script reads ExaBGP 6.0 JSON until its version is known, 5.x JSON once held to API 4, and 6.0 JSON once held to API 6. A script can ask with api version, and api version <value> changes the setting for the scripts started after it.

Run your existing scripts through the bridge

You do not need to rewrite scripts to migrate. The converter writes the bridge for you; a hand-written config gives the script a process block under exabgp { bridge }, loads the exabgp-bridge internal plugin, and attaches that plugin to each peer:

exabgp {
    bridge {
        process my-script {
            run /opt/scripts/my-plugin.py
            encoder json
            feed 10.0.0.1 {
                event [ receive-update neighbor-changes ]
            }
        }
    }
}
plugin {
    internal exabgp-bridge {
        use exabgp-bridge
    }
}
bgp {
    peer upstream {
        connection {
            local {
                ip 10.0.0.100
            }
            remote {
                ip 10.0.0.1
            }
        }
        session {
            router-id 10.0.0.100
            asn {
                local 65000
                remote 65001
            }
            family {
                ipv4/unicast { prefix { maximum 10000; } }
            }
        }
        attach process exabgp-bridge {
            receive [ * ]
            send [ * ]
        }
    }
}

ze exabgp plugin <command> also runs one script on its own, outside a config.

Several processes, text output, and per-peer feeds

A converted configuration carries every ExaBGP process under the exabgp root, and the bridge starts one child per block. A block with no run is refused.

exabgp {
    bridge {
        process public {
            run "./run/public.py"
            encoder text
            feed 127.0.0.1 {
                event [ receive-update send-update ]
            }
        }
        process private {
            run "./run/private.py"
            respawn disable
            feed 192.168.0.1 {
                event [ ]
            }
        }
    }
}
Setting Effect
encoder text The script reads ExaBGP's own line format (neighbor 127.0.0.1 receive update announced ...) instead of JSON. encoder json is the other value.
no feed block The script is fed every event of every peer.
feed <peer> { event [ ... ]; } The script is fed only that peer, and only the listed ExaBGP api keys (receive-update, send-open, neighbor-changes, negotiated, fsm, signal, ...).
feed <peer> { event [ ]; } Nothing from that peer.
respawn disable A script that exits stays stopped. By default it is restarted, at most five times in a 64-second window, as ExaBGP does.

Each script gets its own done for every command it writes, and disable-ack silences that script alone. A line with no neighbor <ip> in front of it goes to every peer that feeds the script.

Commands the bridge answers

Route commands (announce, withdraw) are translated and acknowledged only after the route is flushed to the wire. They may name their peers in the API 4 form, neighbor <ip> announce ..., or the API 6 form, peer <selector> announce .... An announce carrying an attribute Ze cannot encode is refused by name rather than sent without it; atomic-aggregate, aggregator, originator-id, cluster-list and aigp are carried.

These other commands are answered, each in its API 4 spelling and, where ExaBGP 6.0 has one, its API 6 spelling:

API 4 API 6 What Ze does
clear adj-rib in, clear adj-rib out, flush adj-rib out rib clear in, rib clear out, rib flush out clear bgp rib in, clear bgp rib advertised, clear bgp rib out for the script's peers
announce watchdog <name>, withdraw watchdog <name> request bgp watchdog announce <name>, request bgp watchdog withdraw <name>
neighbor <ip> teardown [<code>] [<subcode>] [<text>] peer <ip> teardown ... request peer <ip> notify <code> <subcode> hex <data>, with the Data field ExaBGP would send, octet for octet. No number is Cease, Administrative Shutdown.
neighbor <ip> disable [<text>], neighbor <ip> enable peer <ip> disable ..., peer <ip> enable request peer <ip> disable [<text>], request peer <ip> enable
create neighbor ..., delete neighbor ... peer create ..., peer delete ... A peer that lives in the running daemon only, as in ExaBGP
shutdown daemon shutdown request shutdown
status daemon status Answered by the bridge, with ExaBGP's daemon status. ze exabgp plugin run on its own refuses it, because it cannot read the peers' states
ping [text] session ping Answered by the bridge
enable-ack, disable-ack, silence-ack session ack enable, session ack disable, session ack silence Answered by the bridge, for that script only
api version [<value>] Answered by the bridge
group start, group end, group <command> ; <command> The routes between start and end are held, then sent as one set
help system help Passed to Ze

The bridge refuses any other line by name, operational included, and sends nothing for it.

Events use ExaBGP's own JSON spelling: attribute 9 is originator-id, an AS path can be read as a set or a sequence, an IPv4 route with an IPv6 next hop carries the right address, and events name the local end of the session.

The bridge is tested against ExaBGP's own suite: the upstream qa/api scripts and encoding cases run against Ze.

The bridge translates Ze events into ExaBGP JSON in one direction, and ExaBGP text commands into Ze commands in the other. Your script does not know it is talking to Ze. After each batch of route commands, the bridge sends request peer <selector> flush once for each selector the batch reached, and answers done only when the flush is complete, so a script never reads done for a route that is still queued.

A practical workflow

The shortest path that works is six steps.

  1. ze exabgp migrate exabgp.conf > new.conf to write the converted file, and read the stderr for any capability word it could not carry.
  2. ze config validate new.conf to check the result.
  3. Read every prefix { maximum } line and adjust to your real route counts.
  4. ze config validate new.conf.
  5. Read each exabgp { bridge { process } } block: its run, its encoder, and the feed entries that say which peers and events reach the script.
  6. Start Ze, watch the sessions come up, and only then start porting plugins.

Port plugins to native Ze when you actually need something the bridge does not give you: RPKI events, the cache commands, the commit workflow, or the lower-latency in-process Go SDK. The bridge has measurable overhead and an extra translation surface, but it is honest about what it does and it is the cheapest way to keep production moving while you migrate.

See also

Adapted from main/docs/exabgp/exabgp-migration.md, main/docs/config-migration.md, and main/docs/exabgp/exabgp-differences.md.

Home

About

First Steps

Configuration

Operation

Interfaces

Plugins

Plugin Development

Chaos Testing

Blueprints

Development

Reference

Clone this wiki locally