VyOSXXIX · BGP CommunitiesCommunities
Community troubleshooting — community not propagated, transitive vs non-transitive, regex matching
What you'll learn
- Diagnose the case where the community is dropped on egress because replace was used instead of additive
- Diagnose the case where the community-list is defined but the route-map does not match it
- Diagnose the case where the regex community-list over-matches the value
- Read FRR JSON output for the raw community attribute rather than the formatted BGP table
- Use `tcpdump` to confirm the UPDATE carries the community attribute
- Use the audit checklist to walk a community failure from symptom to cause in five steps
Prerequisites
- BGP communities — the optional transitive attribute, the AA:NN format, and well-known communities
- Configuring community-lists and route-maps — set community add, replace, and the choice VyOS makes you state
- Community-driven policy — matching communities to set local-pref, AS-path prepend, MED, and geographic tagging
- BGP routes missing — not received, not advertised, not installed
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)
Community failures are the most common BGP policy bug. The community is small (four bytes per value), the format is permissive (the operator can attach any value), and nothing in BGP tells you when one goes missing — a community that was stripped and a community that was never attached produce exactly the same BGP table on the far side. The failure modes are predictable, though, so the operator should be able to walk from symptom to cause in five steps.
The workflow is:
- Confirm the route carries the community on ingress.
show bgp ipv4 unicast 198.51.100.0/24prints aCommunity:line for each path that has one. No line means no community arrived, and steps 2 to 5 are about a value that was never there. - Confirm the community-list rule type and pattern.
show policy community-list EU-TAGprints the rules as VyOS holds them. Read whether each rule is acommunityrule (exact value) or aregexrule (a regular expression over the whole community string) — the two match completely differently, and a list that looks right is usually the wrong one of the two. - Confirm the route-map actually fired.
vtysh -c 'show route-map TAG-OUT'prints anInvokedcounter per route-map and per sequence. A counter stuck at zero means the route-map is not being reached at all, which is a binding problem, not a matching problem. - Confirm the route-map is bound to the right direction on the right neighbour.
show configuration commands | match route-mapshows the bindings. On VyOS 1.5 the binding lives under the neighbour’s address-family, so a route-map that exists but is bound under the wrong address-family is invisible to the session you are debugging. - Confirm the wire format with tcpdump.
tcpdump -n -i any -vvv 'port 179'shows the UPDATE on the wire. Path Attribute type 8 is the standard community, type 32 the large community. This is the only step that distinguishes “the router did not attach it” from “the router attached it and the peer discarded it”.
The five-step walk is the standard playbook.
Symptom 1: the community is dropped on egress
The most common community bug. The operator writes set community '64512:100' and the route leaves the router with only 64512:100. The upstream’s tag is silently stripped.
$ show bgp ipv4 unicast 198.51.100.0/24BGP routing table entry for 198.51.100.0/24, version 4
Paths: (1 available, best #1, table default)
Advertised to non peer-group peers:
10.0.0.2
64500
192.0.2.2 from 192.0.2.2 (10.255.0.1)
Origin IGP, metric 0, valid, external, best (First path received)
Community: 64512:100
Last update: Sat Aug 15 09:14:23 2026Illustrative output
There is one Community: line and it carries one value. The tag
the upstream set — 64512:200 no-export — is not on it. Note what
the output does not contain: any record that a second community
was ever present. The BGP table shows the attribute as it stands
now, so a stripped community and a community that was never sent
look identical here.
The cause is the route-map using set community without additive. The default form of set community is replace: the clause sets the COMMUNITIES attribute to exactly the list it names, discarding whatever was there. additive changes it to an append.
flowchart TD
IN["Route arrives with:<br/>64512:200 no-export"]
IN --> Q{"Which `set community` form?"}
Q -->|"replace (default)"| R["Outbound: 64512:100<br/>(upstream's tag stripped)"]
Q -->|"additive (explicit)"| K["Outbound: 64512:200 no-export 64512:100<br/>(upstream's tag preserved)"]
The fix is to add additive to the set community clause:
set policy route-map TAG-IN rule 10 action 'permit'
set policy route-map TAG-IN rule 10 set community '64512:100 additive'
The route-map now appends rather than replaces, and the upstream’s tag survives.
Symptom 2: the community-list is defined but never matches
The second most common bug, and the one that most often gets shipped because it looks fine. The operator writes a community-list with regex '64512:.*', binds it to a route-map, and the policy fires on far more routes than intended.
The reason is the rule type. A VyOS community-list rule takes either a community value or a regex, and the two compile into different things in FRR:
set policy community-list EU-TAG rule 10 community '64512:100'becomes a standard list entry. The value is compared to each community on the route, one at a time, as a value.64512:1000does not match it.set policy community-list EU-TAG rule 10 regex '64512:.*'becomes an expanded list entry. FRR renders every community on the route into one space-separated string —64512:200 no-export 64512:100— and runs the regular expression against that whole string.
That second sentence is the entire bug. 64512:.* is unanchored, so it matches anywhere in that string; it also matches 164512:5 and 64512:1000, because nothing says where the value starts or ends.
Check which kind you actually have. The set-line view is the one
to read, because the leaf name — community or regex — is the
thing that decides the matching engine, and it is the thing a
rendered list display can hide:
$ show configuration commands | match 'community-list EU-TAG'set policy community-list EU-TAG rule 10 action 'permit'
set policy community-list EU-TAG rule 10 regex '64512:.*'Illustrative output
show policy community-list EU-TAG gives the same list in VyOS’s
own display form, which is easier to read for a long list; the set
lines are what you paste into a ticket.
The fix depends on which behaviour you wanted:
# exact values, one rule per value - matches the value, not a substring
set policy community-list EU-TAG rule 10 action 'permit'
set policy community-list EU-TAG rule 10 community '64512:100'
set policy community-list EU-TAG rule 20 action 'permit'
set policy community-list EU-TAG rule 20 community '64512:101'
# or one expanded rule, with the delimiters written explicitly
set policy community-list EU-TAG rule 10 action 'permit'
set policy community-list EU-TAG rule 10 regex '(^| )64512:10[01]( |$)'
The regex form is POSIX ERE. The special characters are ., *, +, ?, ^, $, [, ], (, ), |, \. Test any expanded rule against a route that carries several communities, not against a single-community route — the single-community case is the one that hides this failure.
Symptom 3: the community never leaves the router
The third case is the one that wastes the most time, because the local router’s own BGP table shows the community attached and correct. The route-map fired, the value is there, and the peer still does not see it.
The first place to look is the peer configuration, not the policy. FRR sends standard and extended communities to a peer by default, and VyOS exposes a switch that turns that off per address-family:
set protocols bgp neighbor 192.0.2.2 address-family ipv4-unicast disable-send-community standard
With that set, every route-map in the estate can attach the community perfectly and the UPDATE still goes out without a COMMUNITIES attribute. Nothing logs it; the local table is unchanged, because the local table records what the route carries, not what was serialised into the UPDATE.
show configuration commands | match 'neighbor 192.0.2.2' is the
whole diagnostic. The rest is worth stating explicitly:
- The route was aggregated. A route that is suppressed under an aggregate is not advertised, so its communities are not advertised either. The aggregate is a new route and carries only what a route-map on the aggregate gives it.
- Some route-map on the path used replace. Symptom 1, one hop further away, and it looks identical from here.
- The peer discarded it on ingress. Their inbound policy, your
outbound UPDATE.
tcpdumpon your side is what separates this from the two above, because it shows the attribute leaving.
Symptom 4: the route-map rule order is wrong
The fourth most common bug. The operator writes a route-map with multiple rules, but the catch-all rule (rule 30) is before the specific rule (rule 10). The catch-all wins and the specific rule is never evaluated.
set policy route-map POLICY-OUT rule 30 action 'permit'
set policy route-map POLICY-OUT rule 30 set local-preference '100'
set policy route-map POLICY-OUT rule 10 action 'permit'
set policy route-map POLICY-OUT rule 10 match community 'PEER-A'
set policy route-map POLICY-OUT rule 10 set local-preference '200'
The rule order is wrong. Rule 30 is before rule 10. The route-map is processed in rule order; rule 30 matches every route and rule 10 is never evaluated.
The fix is to reorder the rules. The rule with the most specific match should have the lowest rule number.
set policy route-map POLICY-OUT rule 10 action 'permit'
set policy route-map POLICY-OUT rule 10 match community 'PEER-A'
set policy route-map POLICY-OUT rule 10 set local-preference '200'
set policy route-map POLICY-OUT rule 30 action 'permit'
set policy route-map POLICY-OUT rule 30 set local-preference '100'
The route-map is processed in order: rule 10 matches PEER-A and sets local-pref 200; rule 30 is the catch-all.
Symptom 5: the route-map is bound to the wrong direction
The fifth case is a binding problem rather than a policy problem: the route-map is correct and is attached to the wrong place, so it never runs on the routes the operator is looking at.
On VyOS 1.5 the neighbour binding lives under the neighbour’s address-family, and the direction is a value of the route-map node rather than a trailing keyword:
set protocols bgp neighbor 192.0.2.2 address-family ipv4-unicast route-map export 'TAG-OUT'
set protocols bgp neighbor 192.0.2.2 address-family ipv4-unicast route-map import 'TAG-IN'
export is the direction the route leaves by and import the direction it arrives by. Three bindings go wrong often enough to check every time:
- The wrong direction. An egress tag bound as
importruns on routes arriving from the peer and never touches what you advertise. - The wrong address-family. A binding under
ipv6-unicastis syntactically fine and does nothing at all to the IPv4 routes you are debugging. This one survives review because the command looks correct in isolation. - The wrong neighbour. Two upstreams, one policy, one address typed from memory.
Confirm the binding rather than remembering it:
show configuration commands | match 'neighbor 192.0.2.2'
Then confirm the route-map is being reached at all. FRR keeps a per-route-map and per-sequence invocation counter:
vtysh -c 'show route-map TAG-OUT'
A counter of zero with a correct-looking route-map is a binding failure, and it is the fastest way to tell a binding failure apart from a match failure — a match failure still increments the route-map’s counter, because the route-map ran and simply did not match.
The five-step audit
The audit is the standard playbook for community debugging:
- Confirm the route carries the community on ingress.
show bgp ipv4 unicast 198.51.100.0/24prints aCommunity:line for each path that has one. No line means no community arrived, and steps 2 to 5 are about a value that was never there. - Confirm the community-list rule type and pattern.
show policy community-list EU-TAGprints the rules as VyOS holds them. Read whether each rule is acommunityrule (exact value) or aregexrule (a regular expression over the whole community string) — the two match completely differently, and a list that looks right is usually the wrong one of the two. - Confirm the route-map actually fired.
vtysh -c 'show route-map TAG-OUT'prints anInvokedcounter per route-map and per sequence. A counter stuck at zero means the route-map is not being reached at all, which is a binding problem, not a matching problem. - Confirm the route-map is bound to the right direction on the right neighbour.
show configuration commands | match route-mapshows the bindings. On VyOS 1.5 the binding lives under the neighbour’s address-family, so a route-map that exists but is bound under the wrong address-family is invisible to the session you are debugging. - Confirm the wire format with tcpdump.
tcpdump -n -i any -vvv 'port 179'shows the UPDATE on the wire. Path Attribute type 8 is the standard community, type 32 the large community. This is the only step that distinguishes “the router did not attach it” from “the router attached it and the peer discarded it”.
The audit is the evidence that the policy is doing what the operator intended. The operator who carries the audit checklist in the change ticket can defend the change in the post-incident review.
How it fails in production
The most common failure modes in production:
- The community is dropped on egress because the route-map uses
set communitywithoutadditive. The upstream’s tag is silently replaced. The fix isadditive— on that one route-map, after deciding whether replace was deliberate. - The community-list over-matches because an expanded rule’s regex is unanchored:
64512:.*matches64512:1000and164512:5too. The fix is delimiter-aware anchoring, or a standardcommunityrule if exact values were what you wanted. - The community-list under-matches from the day a second tag appears because an expanded rule was anchored
^...$against the whole community string. Nothing changed locally; an upstream added a tag. This one arrives months after the change that caused it. - The community never reaches the peer because
disable-send-communityis set on that neighbour’s address-family, or the route is suppressed under an aggregate. The local BGP table looks correct throughout. - The route-map rule order is wrong because the catch-all has a lower rule number than the specific rule. First match wins, so the specific rule is never evaluated. The fix is to renumber.
- The route-map is bound to the wrong address-family — a valid, committable binding under
ipv6-unicastthat does nothing to the IPv4 routes being debugged. The route-map’sInvokedcounter stays at zero and the configuration reviews clean.
Rollback
Community troubleshooting is a read activity. The operator is not changing the configuration; they are inspecting the existing state. The rollback path is for the eventual fix:
compareto read the diff beforecommit.commit-confirm 15for any change made over the network.save /config/pre-TICKET.confbefore the change, so the reversion is aloadof a known file rather than a reconstruction from memory.load /config/pre-TICKET.conf, thencompare, thencommit-confirmto revert.
Avoid rollback N here. It applies the stored revision and then reboots the router, which for a policy change means every BGP session on the box drops in order to undo one route-map.
The fix is not free either. Changing an inbound policy does not retroactively re-tag routes already in the table: the routes have to be re-learned. reset bgp ipv4 192.0.2.2 soft in re-requests the table from the peer without dropping the session, which works as long as the peer supports route refresh or the local side holds the routes with soft-reconfiguration inbound. Without either, the only way to re-evaluate is the hard reset bgp ipv4 192.0.2.2, which is a session bounce and a reconvergence event. Decide which one you are doing before the change window, not during it.
Production discipline
Cross-course references
The Linux course’s XIX-Linux-NetFoundations covers the FIB. The OPNsense course’s XXX-OPNsense-DynamicRouting covers the equivalent FRR-managed community policy on the firewall side. The BGP lessons vyos-xxxi-02-routes-missing covers the upstream debugging for routes that do not arrive; the lesson vyos-xxix-02-community-config covers the configuration of community-lists and route-maps, and vyos-xxix-05-community-routing covers the production pattern of using communities to drive policy.
Quiz
Knowledge check · 4 questions
Q1. An operator writes `set policy route-map TAG rule 10 set community '64512:100'` (no additive). The upstream sends routes with `64512:200 no-export`. The peer reports the routes no longer carry `64512:200`. What is the cause, and what is the fix?
Q2. A BGP speaker that does not recognise an optional transitive attribute strips it from the UPDATE and sets the Partial bit to record that it did so, which is why a stripped community can be detected by looking for the Partial flag.
Q3. An operator writes a community-list with `regex '64512:.*'` and binds it to a route-map that sets local-pref 200. The intent is to match only `64512:100` and `64512:101`. The route-map matches every 64512:NN community. Why?
The operator writes `set policy community-list EU-TAG rule 10 regex '64512:.*'` and binds it to a route-map. The route-map matches every 64512:NN community, not just `64512:100` and `64512:101`. The operator is confused.
Q4. The local router's BGP table shows 198.51.100.0/24 carrying `64512:100`, the egress route-map's Invoked counter is incrementing, and the peer insists the route arrives with no communities at all. Where does the attribute go?
R1 advertises 198.51.100.0/24 to the eBGP peer 192.0.2.2. `show bgp ipv4 unicast 198.51.100.0/24` on R1 shows `Community: 64512:100`. `vtysh -c 'show route-map TAG-OUT'` shows the sequence that sets the community being invoked. The peer's operator confirms the prefix arrives and confirms it carries no community. Both sides are certain their own configuration is right, and both are looking at their own BGP table to prove it.
Passing score: 75%. Answers are checked in this browser.