Skip to main content
RunBook Academy

VyOSXXIX · BGP CommunitiesCommunities

Configuring community-lists and route-maps — set community add, replace, and the choice VyOS makes you state

Advanced⏱ ~24 minshow ip bgpshow bgp ipv4 neighborsshow configuration commands | match communityvtysh

What you'll learn

  • Write a `policy community-list` and predict what its `regex` leaf will match
  • Use `set community replace` in a route-map to replace the entire community list on a route
  • Use `set community add` to append new communities without stripping existing ones
  • Match a community-list inside a route-map and apply a different action for each rule
  • Bind a route-map to a BGP peer in the correct direction under the address family
  • Diagnose the case where a route leaves the AS with the upstream's tags stripped

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)

Not yet marked complete on this device.

Communities are the most common way for BGP operators to attach metadata to routes. The values are arbitrary; the operator who sets a value is the operator who has to explain what it means. The configuration side has two primitives: a community-list that classifies a route by the communities it already carries, and a route-map that acts when the classification succeeds. The action either replaces the route’s community list or appends to it, and VyOS 1.5 makes you say which.

This lesson is the configuration bridge between vyos-xxix-01-community-concept, which introduced the attribute, and vyos-xxix-05-community-routing, which uses community-driven policy to set local-pref, prepend and MED.

The two-primitive model

A community-list is a named, ordered set of matching rules. A route-map is a named, ordered list of match and set clauses. The route-map can match on a community-list; where the match succeeds, the route-map’s set clauses modify the route. The community-list modifies nothing — it is purely a classifier.

flowchart LR
  ROUTE["Inbound route"]
  RL["route-map 'IN-FROM-PEER-A'"]
  CL["community-list 'PEER-A-EU'"]
  RL -->|"match community community-list"| CL
  CL -->|"hit"| S1["set community add 64512:100"]
  CL -->|"miss"| S2["next rule, or implicit deny"]
  ROUTE --> RL
  S1 --> OUT["Route as re-advertised"]
  S2 --> OUT

The two live under separate nodes in the configuration tree — policy community-list and policy route-map — and the commit engine renders both into FRR’s configuration, resolving the cross-reference by name.

The community-list, and its one value leaf

This is the first place a 1.3-era runbook goes wrong. VyOS 1.5 gives a community-list rule exactly one leaf that carries a value, and it is called regex:

set policy community-list PEER-A description 'routes carrying peer A tags'
set policy community-list PEER-A rule 10 action 'permit'
set policy community-list PEER-A rule 10 description 'the 64512:100 tag'
set policy community-list PEER-A rule 10 regex '64512:100'

set policy community-list PEER-A-CUSTOMERS rule 10 action 'permit'
set policy community-list PEER-A-CUSTOMERS rule 10 description 'any 64512:NN'
set policy community-list PEER-A-CUSTOMERS rule 10 regex '64512:.*'

set policy community-list NO-EXPORT-TAGGED rule 10 action 'permit'
set policy community-list NO-EXPORT-TAGGED rule 10 regex 'no-export'

There is no separate community leaf for an exact value and no separate node for a well-known keyword. All three of the above are the same leaf carrying different strings: a plain aa:nn pair, a pattern, or one of the reserved names (no-export, no-advertise, local-AS, and additive) that the leaf accepts by name.

Rules are evaluated in numeric order. The first rule whose value matches decides the outcome — permit means the route is in the set, deny means it is not, and a route matching no rule is not in the set either. The list answers one question: does this route carry a community that matches?

The replace-or-append choice

The critical production decision in community policy is what the route-map does to the communities already on the route. On VyOS 1.5 you cannot leave it implicit — the node requires the verb:

set policy route-map TAG-OUT rule 10 action 'permit'
set policy route-map TAG-OUT rule 10 set community replace '64512:100'

set policy route-map TAG-OUT-ADD rule 10 action 'permit'
set policy route-map TAG-OUT-ADD rule 10 set community add '64512:100'

set community replace '64512:100' discards whatever the route arrived with and leaves it carrying only 64512:100. A route that arrived with 64512:200 no-export loses both.

set community add '64512:100' keeps what the route arrived with and appends. The same route leaves carrying 64512:200 no-export 64512:100.

flowchart TD
  IN["Route arrives with:<br/>64512:200 no-export"]
  IN --> Q{"set community ...?"}
  Q -->|"replace"| R["Discard existing list"]
  Q -->|"add"| K["Append to existing list"]
  R --> OR1["Re-advertised: 64512:100"]
  K --> OR2["Re-advertised: 64512:200 no-export 64512:100"]

Two more verbs exist and both are worth knowing:

  • set community none removes the community attribute entirely. The route is re-advertised with no communities at all, which is different from an empty one.
  • set community delete <community-list> removes only the communities matching a named community-list and leaves the rest. This is the surgical option — strip an upstream’s internal tags without discarding the customer’s.

The canonical inbound policy

Tag routes from a peer that already carry the peer’s own marker, without discarding it:

set policy community-list FROM-PEER-A rule 10 action 'permit'
set policy community-list FROM-PEER-A rule 10 regex '^64512:100$'

set policy route-map TAG-INBOUND-PEER-A rule 10 action 'permit'
set policy route-map TAG-INBOUND-PEER-A rule 10 match community community-list 'FROM-PEER-A'
set policy route-map TAG-INBOUND-PEER-A rule 10 set community add '64512:200'

set policy route-map TAG-INBOUND-PEER-A rule 20 action 'permit'

set protocols bgp neighbor 192.0.2.2 address-family ipv4-unicast route-map import 'TAG-INBOUND-PEER-A'

Three things in that block deserve attention.

The community-list matches routes already carrying 64512:100, anchored so it does not also catch 64512:1000.

Rule 20 is an empty permit and it is not optional. A route-map ends with an implicit deny: any route that reaches the end without matching a permit rule is dropped, not passed through unchanged. Rule 10 only matches routes carrying peer A’s tag; without rule 20, every other route from that peer is discarded and the session looks healthy while the table empties. This is the most destructive mistake in the lesson and it produces no error anywhere.

The binding is route-map import under the neighbour’s address family. On 1.3 it was set protocols bgp <asn> neighbor <ip> route-map <name> in; on 1.5 the ASN is not a node, the direction word is import or export, and the whole thing hangs off address-family ipv4-unicast because a peer can carry several families with different policy in each.

The canonical outbound policy

Tag everything advertised to a customer with a single marker that identifies the feed:

set policy route-map TAG-OUTBOUND-EU rule 10 action 'permit'
set policy route-map TAG-OUTBOUND-EU rule 10 set community replace '64512:500'

set protocols bgp neighbor 203.0.113.5 address-family ipv4-unicast route-map export 'TAG-OUTBOUND-EU'

There is no match here: every route advertised to 203.0.113.5 gets 64512:500 and loses whatever else it carried. The replace is deliberate — internal tags are internal, and a customer who can read your engineering communities can also infer your topology.

If the customer is meant to see the upstream’s markers as well, the verb changes and nothing else does:

set policy route-map TAG-OUTBOUND-EU rule 10 set community add '64512:500'

And if the intent is “keep the customer-visible tags, drop only ours”, the surgical form is the right one:

set policy community-list INTERNAL-TAGS rule 10 action 'permit'
set policy community-list INTERNAL-TAGS rule 10 regex '^64512:9[0-9][0-9]$'

set policy route-map TAG-OUTBOUND-EU rule 10 set community delete 'INTERNAL-TAGS'
set policy route-map TAG-OUTBOUND-EU rule 10 set community add '64512:500'

Multi-rule route-maps

A production route-map has several rules, evaluated in numeric order, and the first rule whose match clauses all succeed is the one that applies. Order from most specific to least:

set policy route-map INBOUND-POLICY rule 10 action 'permit'
set policy route-map INBOUND-POLICY rule 10 match community community-list 'BLACKHOLE'
set policy route-map INBOUND-POLICY rule 10 set local-preference '50'
set policy route-map INBOUND-POLICY rule 10 set community add '64512:666'

set policy route-map INBOUND-POLICY rule 20 action 'permit'
set policy route-map INBOUND-POLICY rule 20 match community community-list 'CUSTOMER-EU'
set policy route-map INBOUND-POLICY rule 20 set community add '64512:200'

set policy route-map INBOUND-POLICY rule 30 action 'permit'
set policy route-map INBOUND-POLICY rule 30 set community add '64512:999'

Rule 10 catches blackhole-tagged routes and lowers their local preference. Rule 20 catches the EU customer tag. Rule 30 has no match, so it matches everything that got that far, tags it with the default marker, and — just as importantly — permits it. Put rule 30 first and it swallows every route before rules 10 and 20 are ever consulted; leave it out and every route that matches neither of the first two rules is silently dropped.

How the result is validated

show ip bgp 198.51.100.0/24
show bgp ipv4 neighbors 192.0.2.2 routes
show bgp ipv4 neighbors 203.0.113.5 advertised-routes
show configuration commands | match community

show ip bgp <prefix> shows the communities on the local copy of the route — after any import policy has run. advertised-routes shows what the router is actually sending a peer, after export policy, which is the only view that proves an outbound route-map did what you intended.

show bgp ipv4 neighbors <peer> routes shows routes received from that peer after import policy. To see them as the peer sent them, before your own route-map touched them, you need the pre-policy copy, and that requires asking for it in advance:

set protocols bgp neighbor 192.0.2.2 address-family ipv4-unicast soft-reconfiguration inbound

With that enabled, show bgp ipv4 neighbors 192.0.2.2 received-routes shows the untouched originals — which is exactly the before-and-after pair you need to prove an import policy stripped something. It costs memory proportional to the table, so enable it deliberately rather than everywhere.

After changing a route-map, existing routes are not automatically re-evaluated. Push the policy through:

reset bgp ipv4 192.0.2.2 soft in
reset bgp ipv4 203.0.113.5 soft out

The soft form re-runs policy without tearing down the session. A hard reset would also work and would drop the adjacency, which is not a trade anybody wants for a tag change.

How it fails

  • The route-map used replace where add was meant, and the upstream’s tags are gone. No error, a clean commit, and the loss is only visible from the peer’s side or in advertised-routes. The fix is one word.
  • An unanchored regex over-matches. 64512:.* covers the whole 64512:NN space; 64512:100 also catches 64512:1000. Anchor with ^ and $ whenever you mean one value.
  • A route-map with no catch-all rule drops everything it does not match. The implicit deny at the end of a route-map is the highest-consequence failure here: the session stays up, the prefix count collapses, and nothing logs a reason. Every import or export route-map needs a final empty permit rule unless dropping the remainder is the actual intent.
  • The route-map is bound in the wrong direction. route-map import runs against routes received; route-map export runs against routes advertised. Tagging on the wrong side produces a policy that appears configured and changes nothing the peer can see.
  • The change was committed and never pushed. BGP does not re-run policy on routes it already holds. Without a soft reset the configuration is right and the table is stale, which reads exactly like a policy that does not work.
  • A route-map references a community-list that does not exist. VyOS validates the reference at commit and refuses, which is the one failure in this list that tells you about itself.

Rollback

Community configuration is ordinary VyOS configuration, so the ordinary discipline applies:

  • compare before commit, and read the diff for the verb on every set community line.
  • commit-confirm 5 for a policy change on a live peering session.
  • To undo after committing, delete the specific policy nodes and commit again — this is a routine forward change, not a recovery.
  • load /config/archive/<known-good-file> followed by compare and commit restores a specific snapshot when the change is larger than a couple of nodes.

rollback N also exists, but it currently requires a reboot to take effect. It is the tool for a router you have lost control of, not for backing out a tag.

Whichever path you take, the change is not proven until the peer’s view changes. Re-check advertised-routes after the soft reset.

Production discipline

Cross-course references

The Linux course’s XIX-Linux-NetFoundations covers the routing table. The OPNsense course’s XXX-OPNsense-DynamicRouting covers the equivalent FRR-managed community policy on the firewall side. The BGP lessons vyos-xxviii-02-prefix-list-config and vyos-xxviii-04-outbound-filtering cover the route-map filtering this lesson builds on; vyos-xxix-01-community-concept introduced the attribute, and vyos-xxix-05-community-routing walks the production pattern of using communities to drive local-pref, prepend and MED policy.

Quiz

Knowledge check · 4 questions

  1. Q1. An operator writes `set policy route-map TAG rule 10 set community replace '64512:100'`. A route arrives from the upstream carrying `64512:200 no-export`. What communities does the route carry after the route-map runs?

  2. Q2. On VyOS 1.5, writing `set policy route-map TAG rule 10 set community '64512:100'` with no further keyword appends the value to the route's existing community list.

  3. Q3. An operator wants inbound routes from peer A to gain `64512:200` while keeping the upstream's `64512:100`. The upstream reports that its tag is disappearing. What is wrong and how is the fix proven?

    The import route-map contains `set policy route-map TAG-IN rule 10 set community replace '64512:200'`. The upstream sends routes carrying `64512:100`. `show ip bgp 198.51.100.0/24` on this router shows `Community: 64512:200` and nothing else. The upstream is complaining that the routes it can see from this AS no longer carry the `64512:100` tag it originally set.

  4. Q4. A community-list written with `regex '64512:.*'` is intended to match only `64512:100` and `64512:101`, and is matching far more. Why, and what is the fix?

    The operator wrote `set policy community-list EU-TAG rule 10 action 'permit'` and `set policy community-list EU-TAG rule 10 regex '64512:.*'`, intending to select the EU-tagged routes. The route-map that matches EU-TAG is applying its local-preference change to every route carrying any 64512 community, including internal engineering tags that were never meant to be affected.

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