VyOSXXVIII · BGP Prefix FilteringPrefix filters
Prefix-list configuration — sequence numbers, ge/le operators, descriptions, hit counters
What you'll learn
- Configure a prefix-list with sequence numbers and the ge/le operators
- Add a description to each rule for self-documentation
- Read the configured list back from FRR, with its hit counters, and confirm FRR accepted it
- Attach a list to a BGP neighbour with the VyOS import/export nodes rather than the FRR in/out spelling
- Recognise the production failure modes around prefix-list configuration
Prerequisites
Verified against VyOS 1.5.x LTS (circinus) · VyOS 1.4.x (sagitta) — legacy · FRRouting 10.x (VyOS 1.5) · Linux kernel 6.6 LTS (VyOS 1.5 base) · strongSwan 5.9.x (IPsec) · WireGuard 1.0.x (kernel module + userspace tooling) · 2026-08-19
The prefix-list configuration on VyOS 1.5 LTS follows the set policy prefix-list syntax. Each rule has a sequence number, an action (permit or deny), a prefix, optional ge and le operators, and an optional description. The configuration is committed to the running config and rendered into FRR’s ip prefix-list commands on the backend.
The configuration discipline is the same as for any list-based filter: define the rules in order, use sparse sequence numbers for future insertion, document the intent with descriptions, and test the filter with commit-confirm before applying to production.
The anatomy of a prefix-list rule
A single rule has the following structure:
set policy prefix-list LIST-NAME rule 10 action 'permit'
set policy prefix-list LIST-NAME rule 10 prefix '198.51.100.0/24'
set policy prefix-list LIST-NAME rule 10 ge '26'
set policy prefix-list LIST-NAME rule 10 le '28'
set policy prefix-list LIST-NAME rule 10 description 'why this rule exists'
The fields:
- The list name — the operator chooses it; the convention is uppercase and hyphen-separated. IPv6 lists live in a separate tree,
set policy prefix-list6, and a v4 name and a v6 name never collide because they are different nodes. - The rule number — rules are evaluated in ascending order and this number becomes the FRR sequence number. Use sparse numbers (10, 20, 30) so a later insertion does not need a renumber.
action—permitordeny. It is not optional; a rule with a prefix and no action is incomplete.prefix— what to match, as network and prefix length.ge— the minimum prefix length the rule will match. Must be strictly greater than the length inprefix.le— the maximum prefix length the rule will match. Must be at least thegevalue.description— the intent, in words.
The ge/le invariant is FRR’s, and FRR states it in the message it
rejects a bad range with: len < ge-value <= le-value. Read that as
three separate requirements:
gemust be strictly greater than the prefix’s own length.198.51.100.0/24 ge 24is invalid, because ageequal to the length adds nothing and FRR refuses to guess what you meant.lemust be at leastge.ge 28 le 26is invalid.- With
lealone and noge, the same “must be longer than the prefix” logic applies:lehas to exceed the prefix’s own length to describe anything the plain prefix does not already cover.
The canonical configuration patterns
The four canonical patterns:
Pattern 1: Exact-match filter — accept a single prefix:
configure
set policy prefix-list MY-PREFIXES rule 10 action 'permit'
set policy prefix-list MY-PREFIXES rule 10 prefix '198.51.100.0/24'
set policy prefix-list MY-PREFIXES rule 10 description 'customer edged routes'
commit
save
The list accepts only 198.51.100.0/24. Every other route is rejected by the implicit deny.
Pattern 2: Prefix + subnets filter — accept the prefix and any more-specific subnets:
configure
set policy prefix-list MY-PREFIXES rule 10 action 'permit'
set policy prefix-list MY-PREFIXES rule 10 prefix '198.51.100.0/24'
set policy prefix-list MY-PREFIXES rule 10 le '32'
set policy prefix-list MY-PREFIXES rule 10 description 'prefix and subnets up to /32'
commit
save
The le 32 operator says “prefix-length ≤ 32”. The rule accepts 198.51.100.0/24, /25, /26, …, /32. The implicit deny catches everything else.
Pattern 3: More-specific only filter — accept subnets but not the prefix itself:
configure
set policy prefix-list MY-PREFIXES rule 10 action 'permit'
set policy prefix-list MY-PREFIXES rule 10 prefix '198.51.100.0/24'
set policy prefix-list MY-PREFIXES rule 10 ge '26'
set policy prefix-list MY-PREFIXES rule 10 description 'subnets /26 or more-specific'
commit
save
The ge 26 operator says “prefix-length ≥ 26”. The rule accepts 198.51.100.0/26, /27, …, /32. It does not accept /24 or /25. The implicit deny catches everything else.
Pattern 4: Specific range filter — accept subnets of a specific length range:
configure
set policy prefix-list MY-PREFIXES rule 10 action 'permit'
set policy prefix-list MY-PREFIXES rule 10 prefix '198.51.100.0/24'
set policy prefix-list MY-PREFIXES rule 10 ge '26'
set policy prefix-list MY-PREFIXES rule 10 le '28'
set policy prefix-list MY-PREFIXES rule 10 description 'subnets /26 to /28'
commit
save
The ge 26 le 28 operator says “prefix-length in [26, 28]”. The rule accepts 198.51.100.0/26, /27, /28. It does not accept /24, /25, /29, or more-specific.
The operator who designs the prefix-list chooses the pattern that matches the routing policy intent.
Adding, editing and removing rules
Every set happens inside a configure session and lands in the
candidate configuration, not the running one, until commit. Rule
numbers are independent of each other: there is no requirement that
they be contiguous, and no requirement that a lower number exist
before you create a higher one. Rule 15 can be created on a list that
only has rules 10 and 20, and it will be evaluated between them.
configure
set policy prefix-list MY-FILTER rule 10 action 'permit'
set policy prefix-list MY-FILTER rule 10 prefix '198.51.100.0/24'
set policy prefix-list MY-FILTER rule 20 action 'deny'
set policy prefix-list MY-FILTER rule 20 prefix '198.51.100.0/16'
set policy prefix-list MY-FILTER rule 20 le '20'
The two rules are evaluated in order: 10, then 20. The first rule that matches wins.
The operator who edits a rule (e.g., changes the prefix) uses set again with the same sequence number:
set policy prefix-list MY-FILTER rule 10 prefix '203.0.113.0/24'
The set command overwrites the existing prefix. The compare output shows the change.
The delete syntax
The operator deletes a single rule with delete:
delete policy prefix-list MY-FILTER rule 20
The rule is removed. Nothing is renumbered — rule 10 stays rule 10 and rule 30 stays rule 30, leaving a gap at 20. That is the intended behaviour and it is why sparse numbering works: the numbers are addresses, not positions.
There is one consequence worth stating plainly, because it is the
usual way a prefix-list change causes an outage. Deleting a permit
rule does not make the list neutral about those prefixes — it hands
them to the implicit deny at the end. Every prefix-list ends in an
implicit deny any, so removing the only rule that matched a
customer’s aggregate silently stops accepting it. compare before
commit and read the removal as “these prefixes are now denied”,
not as “this rule is now absent”.
The operator deletes the entire prefix-list with:
delete policy prefix-list MY-FILTER
All rules are removed. The prefix-list is gone.
The description field
The description is where the intent lives. A prefix-list rule is four
numbers and a verb; six months later nobody remembers which customer
203.0.113.0/24 was, and the person who has to decide whether it can
be removed is not the person who added it.
set policy prefix-list MY-FILTER rule 10 description 'CUST-A aggregate, contract NET-1187'
The discipline is one line per rule, naming the why rather than
restating the what. 'permit the /24' is worthless — the rule
already says that. 'CUST-A aggregate, contract NET-1187' tells the
next operator who to ask.
Counting matches, without a count field
There is no count node on a VyOS prefix-list rule and no
count NAME clause on an FRR ip prefix-list line. Anything that
tells you to configure one is describing a router that does not
exist.
What does exist is a hit counter that FRR maintains automatically for
every sequence, with no configuration at all. Read it with the
detail form:
vtysh -c 'show ip prefix-list detail MY-FILTER'
Each sequence is printed with a hit count alongside it. That number
is the answer to the question the fabricated count field was
reaching for:
- A sequence with a rising hit count is matching, which confirms the rule is doing something.
- A sequence stuck at zero is either never reached — an earlier rule matched first — or is matching nothing that the peer actually sends. Those are different problems and the next command distinguishes them.
To ask which sequence a specific prefix lands on, give FRR the prefix:
vtysh -c 'show ip prefix-list MY-FILTER 198.51.100.0/26 first-match'
That is the single most useful prefix-list debugging command there is, because it replaces reasoning about rule order with an answer from the thing doing the matching.
The inbound and outbound BGP filter patterns
The prefix-list is applied to a BGP neighbour in either direction. VyOS
names the directions import and export, and the words are worth
taking literally: import is what the router takes in from the peer,
export is what the router sends out to it.
Inbound filter — applied to routes the peer advertises to the local router. It decides which routes the local router accepts.
configure
set policy prefix-list PEER-FROM-ISP rule 10 action 'permit'
set policy prefix-list PEER-FROM-ISP rule 10 prefix '198.51.100.0/24'
set policy prefix-list PEER-FROM-ISP rule 10 description 'customer routes from ISP'
set protocols bgp system-as '64512'
set protocols bgp neighbor 192.0.2.2 remote-as '65001'
set protocols bgp neighbor 192.0.2.2 address-family ipv4-unicast prefix-list import 'PEER-FROM-ISP'
commit
save
The peer advertises routes to the local router; the prefix-list filters them; only the matching routes are accepted. Note the AS number: on 1.4 and 1.5 it is set protocols bgp system-as '64512', and the neighbour hangs off set protocols bgp neighbor ... rather than off the AS number. The 1.3 form set protocols bgp 64512 neighbor ... is rejected.
Outbound filter — applied to routes the local router advertises to the peer. It decides which routes the local router sends.
configure
set policy prefix-list PEER-TO-ISP rule 10 action 'permit'
set policy prefix-list PEER-TO-ISP rule 10 prefix '198.51.100.0/24'
set policy prefix-list PEER-TO-ISP rule 10 description 'local customer routes'
set protocols bgp neighbor 192.0.2.2 address-family ipv4-unicast prefix-list export 'PEER-TO-ISP'
commit
save
The local router advertises routes to the peer; the prefix-list filters them; only the matching routes are sent. The peer receives only the routes the local router permits.
The canonical pattern:
- Inbound filter for “what I will accept from the peer” — protects the local router from accepting bad routes.
- Outbound filter for “what I will send to the peer” — protects the peer from receiving bad routes from the local router.
Both filters are required for production BGP. The operator who has an inbound filter but no outbound filter is exposed to accepting routes from the peer but also exposing the local network to the peer. The operator who has an outbound filter but no inbound filter is exposed to the other direction.
The set policy prefix-list to set protocols bgp neighbor connection
The connection between the prefix-list and the BGP neighbour is made
inside address-family ipv4-unicast, which matters because a filter
is per-address-family. A router with both ipv4-unicast and
ipv6-unicast up on the same neighbour needs a filter under each;
configuring one and assuming it covers both leaves the other
direction of the internet entirely unfiltered.
The VyOS BGP node renders into FRR’s spelling:
router bgp 64512
neighbor 192.0.2.2 remote-as 65001
address-family ipv4-unicast
neighbor 192.0.2.2 prefix-list PEER-FROM-ISP in
neighbor 192.0.2.2 prefix-list PEER-TO-ISP out
exit-address-family
This is the render that makes the import/export versus in/out
confusion so durable: the operator configures import, reads in
back from vtysh, and then types in the next time. Confirm the
render with vtysh -c 'show running-config', and remember which
half of the pair each tool speaks.
The sparse sequence number convention
The convention is to use sparse sequence numbers (10, 20, 30, …) for room to insert new rules. The operator who uses sparse numbers can insert a new rule between two existing rules without renumbering:
# Rules 10, 20, 30 exist
# Insert rule 15 between 10 and 20
set policy prefix-list MY-FILTER rule 15 action 'deny'
set policy prefix-list MY-FILTER rule 15 prefix '198.51.100.0/24'
Rule 15 is evaluated between rule 10 and rule 20. The existing rules are not renumbered.
The operator who uses dense sequence numbers (1, 2, 3) ends up renumbering every rule to insert a new one. The cost of dense numbers is high.
How the result is validated
Four questions, in order, each with the command that answers it.
1. What did I ask for?
show configuration commands | match 'policy prefix-list MY-FILTER'
The VyOS tree, descriptions included. This is intent, not state.
2. What did FRR accept?
vtysh -c 'show ip prefix-list MY-FILTER'
vtysh -c 'show ip prefix-list detail MY-FILTER'
The compiled list, with hit counts in the detail form. A rule
present in step 1 and absent here was rejected downstream, which is
the failure a clean commit hides.
3. Which rule does a given prefix hit?
vtysh -c 'show ip prefix-list MY-FILTER 198.51.100.0/26 first-match'
This settles rule-ordering arguments without anyone having to reason about them.
4. What is the filter actually doing to the session?
PEER=192.0.2.2
vtysh -c "show ip bgp neighbors $PEER received-routes"
vtysh -c "show ip bgp neighbors $PEER routes"
The first is the pre-filter view — everything the peer sent — and it
requires soft-reconfiguration inbound on that neighbour to have
been kept. The second is the post-filter view: what survived. The
difference between the two lists is your filter, stated as data
rather than as an argument about what the rules ought to do.
For the rendered FRR configuration as a whole:
vtysh -c 'show running-config' | grep -A20 prefix-list
How it fails
The production failure modes the engineer must recognise:
- The
gevalue is not greater than the prefix length.198.51.100.0/24 ge 24violateslen < ge-value. Setgeto 25 or higher, or drop it if the plain prefix is what you meant. - The
levalue is less thange.ge 28 le 26describes an empty range. Swap them or correct the intent. - The list is referenced by name but never defined. A neighbour pointing at a prefix-list that does not exist is a filter that filters nothing. Define the list before you reference it, and re-read the neighbour with
vtyshafterwards. - The direction is wrong.
prefix-list importwhereexportwas meant. This one is quiet in the worst way: an inbound filter applied outbound usually still permits a plausible-looking set of prefixes, so the session stays up and the leak is discovered by someone else. in/outtyped instead ofimport/export. The path is rejected at commit, which is the good outcome — but a runbook or automation template carrying the FRR spelling will fail at exactly the moment it is being relied on.- Applied to the wrong neighbour. Peer A gets peer B’s policy.
show configuration commands | match 'neighbor'and read the pairs rather than trusting the one you just typed. - Only one address family filtered. The v4 filter is present and the v6 session is wide open. Every filter is per-address-family.
- The implicit deny catches something you meant to accept. Every list ends in
deny any. If routes are missing, comparereceived-routesagainstroutesand look at what fell out. - The commit succeeded and FRR refused the rule. The most dangerous of the set, because there is no error to notice.
vtysh -c 'show ip prefix-list MY-FILTER'after every prefix-list change is the habit that catches it.
Rollback
Prefix-list changes are ordinary configuration changes, but they act on a BGP session, which means the blast radius is other people’s traffic rather than this router’s uptime. That shapes which mechanism is right.
configure
# See exactly what is about to change, as prefixes rather than as lines.
compare
# For a change to a filter protecting the session you are reached over:
commit-confirm 5
# ... verify received-routes against routes ...
confirm
save
Undo the specific rule where you can — delete policy prefix-list MY-FILTER rule 20 and commit is precise, and it leaves the rest of
the list untouched. For a broader revert, load a known-good file
from /config/archive/, compare it, and commit that.
Do not reach for rollback N. The VyOS documentation states that it
applies the stored revision by rebooting the router, and rebooting an
edge router to undo a filter typo takes every session on it down —
including the ones that were fine.
One property of filter rollbacks catches people out: undoing the configuration does not immediately undo the effect. The routes the filter dropped are gone from the table and will not come back until the peer resends them. Finish the rollback with a soft refresh:
clear ip bgp 192.0.2.2 soft in
Production discipline
Cross-course references
The Linux course’s XIX-Linux-NetFoundations covers the kernel FIB. The OPNsense course’s XXX-OPNsense-DynamicRouting covers the same FRR prefix-list pattern on the firewall side. The BGP lessons vyos-xxvi-02-as-path (the AS_PATH attribute in detail), vyos-xxviii-01-prefix-list-concept (the semantics), and vyos-xxviii-03-distribute-list (the BGP application) cover the broader context. The lesson vyos-xxviii-06-filter-troubleshoot walks the debugging of filter failures.
Quiz
Knowledge check · 4 questions
Q1. What is the operator constraint violation in this rule: `permit 198.51.100.0/24 ge 24`?
Q2. A BGP neighbour can have an inbound prefix-list applied and no outbound prefix-list.
Q3. An operator configures a prefix-list with rule 10 `permit 198.51.100.0/24 ge 26` and applies it inbound on a BGP peer. The operator expects the rule to accept `198.51.100.0/24` and any more-specific subnets. The rule only accepts the more-specific subnets. What is the fix?
The rule `198.51.100.0/24 ge 26` matches prefixes with prefix-length ≥ 26. The /24 has prefix-length 24, which is less than 26. The rule does not match the /24.
Q4. An operator adds a new deny rule to a prefix-list between rule 10 and rule 20, using rule 15. The operator did not add a `description` field. The commit succeeds but the next operator on call struggles to understand the rule's intent. What is the discipline lesson?
The rule is added without a description. The next operator on call reads the prefix-list and does not know why the deny rule is there. The discipline is to document every rule with a description.
Passing score: 75%. Answers are checked in this browser.