Skip to main content
RunBook Academy

VyOSXXXIII · Route PolicyAS-path-list

AS-path-list — regular-expression matching over the BGP AS_PATH attribute

Advanced⏱ ~24 minvyosvtyshset policy as-path-listshow policy as-path-listvtysh -c show bgp as-path-access-listvtysh -c show ip bgp regexpvtysh -c show ip bgp filter-list

What you'll learn

  • Configure an as-path access-list on VyOS 1.5 LTS
  • Write POSIX regular expressions that match the intended AS_PATH shape
  • Distinguish `_N_` (anchored AS) from `^N` (string-anchored) and other regex idioms
  • Diagnose an as-path-list that matches an unintended path or no path at all

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-15

Not yet marked complete on this device.

The as-path access-list is the BGP-specific primitive that filters routes by their AS_PATH attribute. The AS_PATH is the sequence of ASes a BGP route has traversed; the operator uses it to identify the origin AS, the upstream provider, a transit path, or a customer.

On VyOS 1.5 LTS the as-path-list is the only policy primitive that understands AS_PATH. The matching engine treats the AS_PATH as a space-separated string of AS numbers; the operator writes POSIX regular expressions against that string.

This lesson covers the regex idioms, the configuration, and the production failure modes that arise from regexes that match too much or too little.

The matching engine

The BGP AS_PATH is internally a structured attribute (a sequence of AS segments), but the as-path access-list treats it as a string of AS numbers separated by spaces:

# Example: a route learned from AS 64512 with origin AS 64513
# has AS_PATH "64512 64513"
vyos@r1:~$ vtysh -c 'show ip bgp 10.10.0.0/16'
BGP routing table entry for 10.10.0.0/16
  64512 64513
    Origin IGP, metric 0, localpref 100, weight 0, valid, external, best

The matching engine applies the operator’s regex against this string. The regex is a POSIX extended regular expression; the operator uses standard regex syntax (^, $, _, |, [0-9], etc.).

flowchart TB
  R["Route with AS_PATH\n'64512 64513' (string)"]
  RE["Operator regex\n'_64513_'"]
  E["POSIX regex match\nagainst the AS_PATH string"]
  P["Permit"]
  X["Deny"]

  R --> E
  RE --> E
  E -->|"regex matches the string"| P
  E -->|"regex does not match"| X

The matching is straightforward: regex match against the string. The interesting part is the regex idioms the operator needs.

The four regex idioms

POSIX regular expressions over the AS_PATH string have four common idioms:

  • _N_ — the AS number N appears as a standalone token in the AS_PATH. The underscores are the BGP regex syntax for “word boundary” (space, start, or end). _64512_ matches any AS_PATH that contains 64512 as a token: 64512 64513, 64513 64512 64514, 64512, etc.
  • ^N — the AS number N is at the start of the AS_PATH (the rightmost AS, the most recent AS to advertise the route). ^64512 matches 64512 64513 (start is 64512) but does not match 64513 64512 (start is 64513).
  • N$ — the AS number N is at the end of the AS_PATH (the leftmost AS, the origin AS). 64513$ matches 64512 64513 (origin is 64513) but does not match 64513 64512 (origin is 64512).
  • _N$ — the AS number N is the origin AS (end of the AS_PATH) AND a standalone token. The _ is technically redundant after $ but is conventional.
flowchart LR
  PAT["Regex"]
  A1["_64513_\nmatches: '64512 64513'\n'64513 64512 64514'\n'64513'\nNOT: '645130' (no token boundary)\nNOT: '164513 64512' (64513 is not standalone)"]
  A2["^64513\nmatches: '64513 64512'\n'64513'\nNOT: '64512 64513' (start is 64512)"]
  A3["64513$\nmatches: '64512 64513'\n'64513'\nNOT: '64513 64512' (end is 64512)"]
  A4["^64513$\nmatches: '64513' only\n(start and end of the\nAS_PATH both 64513)"]

  PAT --> A1
  PAT --> A2
  PAT --> A3
  PAT --> A4

The operator chooses the idiom based on intent:

  • “Block all routes from AS 64513” — _64513_ (anywhere in the path).
  • “Block routes originated by AS 64513” — 64513$ (end of path).
  • “Block routes whose most recent AS is 64513 (i.e. directly from AS 64513)” — ^64513 (start of path).
  • “Block only the route whose AS_PATH is exactly 64513” — ^64513$.

The configuration

# Block routes originated by AS 64513
set policy as-path-list BLOCK-ORIGIN rule 10 action deny
set policy as-path-list BLOCK-ORIGIN rule 10 regex '64513$'

# Permit everything else (catch-all permit)
set policy as-path-list BLOCK-ORIGIN rule 20 action permit
set policy as-path-list BLOCK-ORIGIN rule 20 regex '.*'

The list evaluates top-to-bottom. Rule 10 denies any route whose origin AS is 64513. Rule 20 permits everything else (. * is the regex for “any string”).

# Block routes from customer AS 64513 AND any of its
# downstream ASes (i.e. customer and its customers)
set policy as-path-list BLOCK-CUSTOMER rule 10 action deny
set policy as-path-list BLOCK-CUSTOMER rule 10 regex '_64513'

# Block routes where AS 64513 is anywhere in the path
# (looser than the above)
set policy as-path-list BLOCK-64513-ANYWHERE rule 10 action deny
set policy as-path-list BLOCK-64513-ANYWHERE rule 10 regex '_64513_'

Note the difference between _64513 (no closing underscore — matches _64513_, _645130_, _645131_, etc.) and _64513_ (anchored as a standalone token).

Sequence semantics

As-path access-lists are ordered and sequential, like prefix-lists and community-lists. The first rule that matches the AS_PATH decides the disposition; later rules are not consulted. The implicit deny at the end of the list applies for any AS_PATH that matches no rule.

The operator who writes a list with a catch-all permit (regex '.*' or regex '_.*_') at the end is overriding the implicit deny with an explicit permit. This is the standard production idiom: deny the bad paths, permit everything else.

# Production pattern: deny bad, permit good
set policy as-path-list MY-FILTER rule 10 action deny
set policy as-path-list MY-FILTER rule 10 regex '_64513_'

set policy as-path-list MY-FILTER rule 20 action deny
set policy as-path-list MY-FILTER rule 20 regex '^64512'

set policy as-path-list MY-FILTER rule 30 action permit
set policy as-path-list MY-FILTER rule 30 regex '.*'

The list denies any AS_PATH containing 64513 or starting with 64512. Every other AS_PATH is permitted by rule 30.

Operational commands

# The configured list
show policy as-path-list MY-FILTER

# The matching engine's view (FRR)
vtysh -c 'show bgp as-path-access-list'
vtysh -c 'show bgp as-path-access-list MY-FILTER'

# Routes matching a regex
vtysh -c 'show ip bgp regexp _64513_'

# Routes filtered by an as-path-list
vtysh -c 'show ip bgp filter-list MY-FILTER'

# Clear the hit counts
clear bgp as-path-access-list

The show ip bgp filter-list <name> command is the canonical validation: it lists every route whose AS_PATH was filtered by the list. The operator who has just written a new rule should run this command to confirm the filter is doing what it claims.

Failure modes

Unanchored regex matches too much

The operator writes regex '64513' (no underscores). The intent was “AS 64513 is in the path”. The actual matching is “the substring 64513 appears anywhere in the AS_PATH”. AS 645130 matches. AS 164513 matches. The list denies too much.

Diagnostic:

  • vtysh -c 'show ip bgp filter-list MY-FILTER' shows routes that should have been permitted.
  • show ip bgp <prefix> shows the AS_PATH of a denied route; the operator inspects the AS_PATH and sees that 64513 is not actually a standalone token (e.g. 645130 is in the path).

Fix: anchor the regex with underscores. _64513_.

Regex with ^ matches the rightmost AS, not the origin

The operator writes regex '^64513' (anchored start). The intent was “AS 64513 is the origin”. The actual matching is “the rightmost AS in the path is 64513” (the most recent AS to advertise the route). The origin AS is at the end of the path, not the start.

Diagnostic:

  • A route with AS_PATH 64513 64512 is matched (rightmost is 64513). The route was originated by AS 64512.
  • The operator wanted to deny AS 64512-originated routes but denied routes from AS 64513 instead.

Fix: use regex '64512$' for the origin AS (leftmost).

Catch-all permit missing; implicit deny filters too much

The operator writes a list with deny rules but no catch-all permit. The implicit deny at the end of the list filters every AS_PATH that does not match a deny rule — which is the opposite of intent. The operator wanted “deny bad, permit good”; the configuration does “deny bad, deny everything else”.

Diagnostic:

  • show ip bgp filter-list MY-FILTER shows zero routes.
  • show ip bgp shows routes that should be permitted but are not in the BGP table (filtered by the inbound filter-list).

Fix: add a catch-all permit at the end of the list. regex '.*' or regex '_.*_'.

Rollback

An as-path-list change is reversible through the standard VyOS mechanisms:

  • rollback N and commit to revert to a previous configuration revision.
  • delete policy as-path-list <name> and commit to remove the list. The referencing route-map is left dangling; the commit validator catches this.
  • delete policy as-path-list <name> rule <seq> and commit to remove a single rule.
  • set policy as-path-list <name> rule <seq> action permit regex '.*' (override with permit-all) during a debugging window — restore after.

The VyOS commit validator rejects a route-map that references a missing as-path-list.

Production discipline

Cross-course references

  • XXVI-VyOS-BGPAttributes (vyos-xxvi-02-as-path) covers the AS_PATH attribute at depth: prepend for outbound engineering, attribute loss on aggregation, and loop prevention.
  • XXVII-VyOS-BGPBestPath (vyos-xxvii-03-as-path-prepending) covers AS path prepend for outbound traffic engineering.
  • XXXIII-VyOS-RoutePolicy (next lesson, route-map) shows how an as-path-list is matched against a route in the route-map’s match clause.

Quiz

Knowledge check · 4 questions

  1. Q1. A route has AS_PATH '64512 64513' (origin AS 64513, rightmost AS 64512). Which regex matches the origin AS?

  2. Q2. A regex of '_64513' (single trailing underscore) matches the AS_PATH '64512 64513'.

  3. Q3. An operator wants to block all routes originated by AS 64513. They write `set policy as-path-list BLOCK-AS64513 rule 10 action deny regex '^64513'` and apply it inbound. A route with AS_PATH '64513 64512' is denied. The operator is confused because they wanted to deny AS 64513-originated routes, not routes from AS 64513. What went wrong?

    The regex is `^64513` (anchored at start). The route's AS_PATH is '64513 64512'. The rightmost AS is 64513 (the most recent AS to advertise). The origin AS is 64512 (the leftmost AS).

  4. Q4. An operator writes an as-path-list with three deny rules (regex '_64513_', '_64514_', and '_64515_') and no catch-all permit. They apply it inbound. The peer sends 100 routes. After the commit, 0 routes are in the BGP table. What happened?

    The list has three deny rules for ASes 64513, 64514, and 64515. No catch-all permit. The peer sends 100 routes with various AS_PATHs.

Passing score: 75%. Answers are checked in this browser.