VyOSXXIX · BGP CommunitiesCommunities
Community-driven policy — matching communities to set local-pref, AS-path prepend, MED, and geographic tagging
What you'll learn
- Explain why the community tag belongs on the import route-map and the policy on the point where the decision applies
- Configure a community-list and a route-map that matches it to set local-preference
- Configure a route-map that matches a community-list and prepends the local ASN on export
- Configure a route-map that matches a community-list and sets MED on export
- Build a geographic tagging scheme and state honestly what local-preference can and cannot do across regions
- Diagnose the case where the import route-map sets the community but the matching route-map never fires
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
- Local preference — the iBGP outbound path selector
- AS path — prepend, aggregation loss, and the loop prevention
- BGP MED — multi-exit discriminator, eBGP-only, and always-compare-med
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
Community-driven policy is the production pattern every commercial BGP operator uses. The design is simple: tag routes where they enter your AS with a community that encodes a decision (“transit customer”, “EU feed”, “preferred”); then, wherever the decision has to be applied, match the community and apply the policy (local-pref, prepend, MED). The community itself is the contract between the operator who sets it and the operator who reads it.
The pattern has three advantages over setting attributes directly at every policy point. First, it is explicit — the operator can read the community list and know which routes are tagged for which treatment. Second, it is composable — several import policies can write communities onto the same route, and a later policy can match on any combination. Third, it is portable — communities travel with the route across AS boundaries, so an upstream can tag a customer route and the customer’s AS can match on it.
Import and export are not “in” and “out”
VyOS binds route-maps to a neighbour inside an address family, and it names the directions import and export:
set protocols bgp neighbor 192.0.2.2 address-family ipv4-unicast route-map import 'TAG-IN'
set protocols bgp neighbor 192.0.2.2 address-family ipv4-unicast route-map export 'POLICY-OUT'
There is no route-map NAME in / out leaf hanging directly off the neighbour, and there is no ASN in the path: from VyOS 1.4 onward the local AS number is a leaf (set protocols bgp system-as 65001) and every peer lives under set protocols bgp neighbor <ip>. A runbook written against set protocols bgp 65001 neighbor 10.0.0.1 route-map 'LP-POLICY' in fails on the set itself, which is the good outcome — it is loud rather than silent.
import is evaluated on routes arriving from the neighbour, before they are considered for best path. export is evaluated on routes being advertised to the neighbour, after best path has already picked them. That ordering is the reason the two directions are good at different jobs.
The ingress/egress asymmetry
The canonical pattern is:
- On import from an eBGP peer — attach a community to the route. The output is a route sitting in the BGP table carrying a label that says what it is.
- Wherever the decision applies — match the community and apply the policy. For “how should my AS choose between paths”, that point is still the import route-map (see the local-preference section below). For “what should the neighbour think of this path”, it is the export route-map towards that neighbour.
flowchart LR
subgraph IN["import route-map on eBGP session with peer A"]
RA["Route 198.51.100.0/24"]
RM1["policy route-map TAG-IN rule 10"]
S1["set community add '64512:100'<br/>set local-preference '200'"]
RA --> RM1 --> S1
end
subgraph TBL["BGP table"]
RIB["198.51.100.0/24<br/>community 64512:100<br/>localpref 200"]
end
subgraph OUT["export route-map on eBGP session with peer B"]
RM2["policy route-map POLICY-OUT rule 10"]
CL2["match community community-list PREFERRED-EU"]
S2["set as-path prepend '65001 65001'"]
RM2 --> CL2 --> S2
end
S1 --> RIB --> RM2
The asymmetry is critical: the import route-map writes attributes onto a route entering the AS; the export route-map reads them to decide how the route is presented to a neighbour. The community is the carrier between the two.
The four canonical actions
| Action | VyOS clause | Effect | When to use |
|---|---|---|---|
| Local preference | set local-preference '200' | Higher wins; propagates to every iBGP speaker in the AS | Choosing which exit your AS uses |
| AS-path prepend | set as-path prepend '65001 65001' | The path looks longer to whoever receives it | Discouraging a peer from sending you traffic |
| MED | set metric '50' | Lower wins, compared only between paths from the same neighbouring AS | Hinting to one neighbour which of your links to use |
| Community | set community add '64512:300' | Adds a label the next policy can match on | Propagating the decision to another policy or another AS |
Each action is one set clause on a route-map rule. The matching is done by a community-list. The rule numbers determine which classifier is consulted first.
Building the classifier: policy community-list
set policy community-list FROM-A rule 10 action 'permit'
set policy community-list FROM-A rule 10 regex '64512:100'
set policy community-list FROM-B rule 10 action 'permit'
set policy community-list FROM-B rule 10 regex '64512:200'
The leaf is regex, not community, and the name is not decorative: the value is a regular expression evaluated against the route’s community string, not an equality test. 64512:100 therefore also matches a route carrying 64512:1000, and matches a route carrying 164512:100. On a real deployment that is the difference between “EU customers” and “EU customers plus whatever else happens to share a prefix of digits”.
Two habits keep this safe:
- Choose community values that are not prefixes of one another within the same scheme (
64512:100,64512:200,64512:300are fine;64512:10alongside64512:100is not). - Verify the classifier against the real table rather than trusting the expression, with
show bgp ipv4 unicast community-list FROM-A. If that returns prefixes you did not expect, the regex is wider than you think.
Canonical example: shaping your own AS’s exit with local-preference
The operator runs AS 65001 with two upstream providers, A (192.0.2.2) and B (198.51.100.2). A is preferred; B is the backup.
Local-preference is the right tool here because it is the second step of the best-path algorithm and it propagates unchanged to every iBGP speaker in the AS — one router setting it decides the exit for the whole AS. Because it propagates from the point of entry, it has to be set on import from the upstream, not on export towards anything:
set policy community-list FROM-A rule 10 action 'permit'
set policy community-list FROM-A rule 10 regex '64512:100'
set policy route-map FROM-UPSTREAM-A rule 10 action 'permit'
set policy route-map FROM-UPSTREAM-A rule 10 set community add '64512:100'
set policy route-map FROM-UPSTREAM-A rule 10 set local-preference '200'
set policy route-map FROM-UPSTREAM-B rule 10 action 'permit'
set policy route-map FROM-UPSTREAM-B rule 10 set community add '64512:200'
set policy route-map FROM-UPSTREAM-B rule 10 set local-preference '100'
set protocols bgp system-as 65001
set protocols bgp neighbor 192.0.2.2 remote-as '64500'
set protocols bgp neighbor 192.0.2.2 address-family ipv4-unicast route-map import 'FROM-UPSTREAM-A'
set protocols bgp neighbor 198.51.100.2 remote-as '64501'
set protocols bgp neighbor 198.51.100.2 address-family ipv4-unicast route-map import 'FROM-UPSTREAM-B'
Each import route-map does both halves of the pattern at once: it tags the route with the community that records where it came from, and it sets the local-preference that acts on that fact immediately. Every other router in AS 65001 receives the route over iBGP with local-preference already on it and needs no policy at all.
The community earns its keep later. A route that is already labelled 64512:100 can be matched by any subsequent policy — an export map towards a customer, a filter that refuses to re-advertise upstream routes to another upstream — without that policy having to know anything about which session the route arrived on.
Canonical example: discouraging a peer with AS-path prepend
The operator wants upstream A to stop sending inbound traffic for the local customer prefixes, preferring upstream B instead. The tool is a longer AS path, applied on export to A:
set policy community-list MY-CUSTOMERS rule 10 action 'permit'
set policy community-list MY-CUSTOMERS rule 10 regex '64512:500'
set policy route-map PREPEND-TO-A rule 10 action 'permit'
set policy route-map PREPEND-TO-A rule 10 match community community-list 'MY-CUSTOMERS'
set policy route-map PREPEND-TO-A rule 10 set as-path prepend '65001 65001 65001'
set policy route-map PREPEND-TO-A rule 20 action 'permit'
set protocols bgp neighbor 192.0.2.2 address-family ipv4-unicast route-map export 'PREPEND-TO-A'
Rule 10 catches customer-tagged routes and makes the path 65001 65001 65001 65001 … as A sees it — the prepend is added to the AS path A would have received anyway. Rule 20 is the catch-all that keeps every other prefix advertised unchanged; delete it and the export map denies everything rule 10 did not match.
The prepend is a request, not an instruction. It works only while A has an alternative path whose AS path is shorter, and only until something earlier in A’s best-path algorithm — A’s own local-preference, most commonly — overrules path length entirely. Prepending is the weakest of the traffic-engineering levers precisely because it acts on the fourth step of an algorithm the other operator controls.
Canonical example: hinting to one neighbour with MED
MED is the optional non-transitive attribute that says “of my links, use this one”. It is compared only between paths learned from the same neighbouring AS, which makes it the right tool when you have two sessions with the same provider and the wrong tool for almost everything else.
set policy community-list EU-CUSTOMER rule 10 action 'permit'
set policy community-list EU-CUSTOMER rule 10 regex '64512:501'
set policy route-map MED-EU rule 10 action 'permit'
set policy route-map MED-EU rule 10 match community community-list 'EU-CUSTOMER'
set policy route-map MED-EU rule 10 set metric '50'
set policy route-map MED-EU rule 20 action 'permit'
set protocols bgp neighbor 203.0.113.5 address-family ipv4-unicast route-map export 'MED-EU'
Routes carrying the EU customer tag are advertised to 203.0.113.5 with MED 50; everything else goes out with whatever MED it already had. The neighbour prefers the lower MED — if it compares MEDs at all. MED comparison is conditional on the paths coming from the same AS unless the neighbour has enabled always-compare-med, and it is evaluated after local-preference and AS-path length, so a neighbour with a local-preference policy of its own will ignore your MED entirely. Treat MED as a hint you have no way to enforce.
Canonical example: geographic tagging
The operator runs a global network with three regions: EU, NA, ASIA. Each region has its own upstream. The community scheme records which region a route entered through:
64512:100— entered via the EU upstream64512:200— entered via the NA upstream64512:300— entered via the ASIA upstream
Each regional border router tags on import:
set policy route-map EU-IMPORT rule 10 action 'permit'
set policy route-map EU-IMPORT rule 10 set community add '64512:100'
set protocols bgp neighbor 192.0.2.2 address-family ipv4-unicast route-map import 'EU-IMPORT'
Now the useful part, and the part where the naive version of this design falls over. Suppose the intent is “routers in NA should reach EU destinations through the EU upstream”. Setting local-preference 200 on EU-tagged routes at the EU border does not achieve a regional preference — it achieves an AS-wide one, and NA’s own upstream loses to it everywhere.
Two mechanisms actually deliver regional behaviour, and they are worth naming separately because they fail differently:
-
Per-region route reflectors with their own import policy. Each region’s clients receive routes from their regional RR, and that RR applies an import route-map that rewrites local-preference for its own clients. The community is what makes this possible: the RR in NA can tell an EU-entered route from an NA-entered one without knowing the topology.
set policy community-list EU-FEED rule 10 action 'permit' set policy community-list EU-FEED rule 10 regex '64512:100' set policy route-map NA-REGIONAL rule 10 action 'permit' set policy route-map NA-REGIONAL rule 10 match community community-list 'EU-FEED' set policy route-map NA-REGIONAL rule 10 set local-preference '80' set policy route-map NA-REGIONAL rule 20 action 'permit' set protocols bgp neighbor 10.0.0.1 address-family ipv4-unicast route-map import 'NA-REGIONAL'The failure mode is inconsistency: two regions can now disagree about the best path for the same prefix, which is how routing loops between route reflectors get built.
-
Hot-potato routing on the IGP metric. Leave local-preference equal everywhere and let each router pick the BGP next hop that is closest in the IGP. This needs no communities and no per-region policy, and it is what most networks actually run. The failure mode is that traffic follows your topology rather than your commercial intent — cheap transit in one region does not get preferred just because it is cheap.
The community scheme is valuable in either case, because it is what lets you see which upstream a prefix came from when you are looking at a table on the far side of the world.
Verifying the policy
The audit has four steps, and each one has a command that can disagree with the previous one.
1 — What did the peer actually send? An import route-map rewrites the route before it lands in the table, so the table cannot tell you what arrived. To see the pre-policy version you have to ask the router to keep it:
set protocols bgp neighbor 192.0.2.2 address-family ipv4-unicast soft-reconfiguration inbound
vyos@vyos:~$ show bgp ipv4 unicast neighbors 192.0.2.2 received-routesWithout soft-reconfiguration inbound this command returns nothing, and the empty output reads exactly like “the peer is not sending the prefix”. That misreading has cost more debugging hours than any other command in this lesson.
2 — What community is on the route now?
vyos@vyos:~$ show bgp ipv4 unicast 198.51.100.0/243 — Does the route-map say what you think it says? VyOS keeps the policy in its own configuration tree, and FRR keeps the compiled version. Read both when they might disagree:
show configuration commands | match 'route-map FROM-UPSTREAM-A'
vyos@vyos:~$ vtysh -c 'show route-map FROM-UPSTREAM-A'The per-clause hit counters are the fastest way to answer “is rule 10 firing at all”, which is a different question from “is rule 10 correct”.
4 — Did the change take effect on routes already in the table? It did not. A route-map edit applies to routes received after the edit; the prefixes already sitting in the BGP table were evaluated under the old policy. Force a re-evaluation without tearing the session down:
vyos@vyos:~$ clear bgp ipv4 unicast 192.0.2.2 soft inUse soft out for an export map. A soft clear does not reset the session, but it does re-run best-path selection for every prefix the peer sends, so treat it as a change with a blast radius rather than a read-only command.
How it fails
Production failure modes, in rough order of how often they occur:
- The route-map is bound in the wrong direction.
importwhereexportwas meant, or the reverse. Local-preference set on an export map towards an eBGP peer is the classic version: local-preference is not sent to eBGP peers at all, so the clause commits, runs, and changes nothing anybody can observe. - The route-map is bound outside the address family. The binding lives at
neighbor <ip> address-family ipv4-unicast route-map import|export. Configuring the map and forgetting the binding leaves a policy that is present, syntactically valid and never consulted. - The community-list name in the match clause is a typo.
match community community-list 'FORM-A'refers to a list that does not exist. FRR treats an unresolvable community-list as a match that never succeeds, so the rule silently never fires and the route falls through to the next rule — or to the implicit deny. - The route never carried the community. The import map that was supposed to attach it is not bound, or its own catch-all rule is matching first.
show bgp ipv4 unicast <prefix>on the tagging router settles it in one command. - The rule order is wrong. The catch-all sits at rule 10 and the specific classifier at rule 30. The catch-all matches everything, evaluation stops, and rule 30 is dead configuration. Leave gaps in your numbering so a more specific rule can be inserted above an existing one without renumbering.
- The regex is wider than intended.
regex '64512:10'matches64512:100,64512:101and64512:1000. This one produces the worst kind of incident, because the policy works correctly for months and then misbehaves the day someone adds a new community to the scheme. - Nothing was re-evaluated. The policy is right, the binding is right, and the table still shows the old values because no soft clear was issued.
Rollback
Community-driven policy is ordinary VyOS configuration, so the ordinary configuration discipline applies:
comparebeforecommit, and read the leading path of each added line.commit-confirmfor any change made over the session it might break — which for a BGP policy change includes any change to the session carrying your management traffic.- To undo a specific clause, delete that clause:
delete policy route-map FROM-UPSTREAM-A rule 10 set local-preference, thencommit. rollbackrestores a whole archived revision rather than one clause, and is not a quiet in-place edit — read what your release prompts before confirming it.
One thing rollback does not do: put the BGP table back. Removing a policy changes what is applied to routes received from now on. The prefixes already in the table keep the attributes they were given until the session is refreshed, so a rollback usually has to be followed by clear bgp ipv4 unicast <peer> soft in before the table matches the configuration again.
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-driven policy on the firewall side. The BGP lessons vyos-xxvi-01-local-preference, vyos-xxvi-02-as-path, vyos-xxvi-04-med, and vyos-xxvi-05-community cover the individual attributes that the community-driven policy manipulates; the lesson vyos-xxix-02-community-config covers the configuration of community-lists and route-maps, and vyos-xxix-06-community-troubleshoot walks the debugging of community-policy failures.
Quiz
Knowledge check · 5 questions
Q1. An operator runs AS 65001 with two upstream providers, A and B. A is the preferred exit; B is the backup. The operator wants traffic originated inside AS 65001 to leave via A. Which policy achieves this?
Q2. The AS-path prepend is the operator's way of saying 'I prefer not to be your egress for this route'. The peer that is the real preferred egress is the one that does not get the prepend.
Q3. `show bgp ipv4 unicast neighbors 192.0.2.2 received-routes` prints nothing, but the session is Established and the peer insists it is advertising the prefix. What is the most likely explanation?
Q4. An operator configures a route-map to match community `64512:100` and set local-preference 200. The upstream is attaching `64512:100` to its routes. The route arrives inside the AS carrying the community, but local-preference is 100, not 200. What is wrong?
The operator configured `set policy route-map LP-POLICY rule 10 match community community-list FROM-A` and `set policy route-map LP-POLICY rule 10 set local-preference 200`, then bound LP-POLICY with `route-map export` on the eBGP session with the upstream. `show bgp ipv4 unicast 198.51.100.0/24` shows the community present and localpref 100.
Q5. A global operator tags routes by the region they entered through and wants routers in NA to reach EU destinations via the EU upstream. They set local-preference 200 on EU-tagged routes at the EU border. Traffic in every region now leaves through EU. What went wrong?
Three regions (EU, NA, ASIA), each with its own upstream. Import route-maps tag `64512:100`, `64512:200`, `64512:300` respectively. The EU border also sets local-preference 200 on the routes it imports. Every router in the AS, including those in NA, now prefers the EU exit for prefixes learned in EU.
Passing score: 75%. Answers are checked in this browser.