Introduction to Route-Policy Stacks
Route-policy stacks are layered constructs that combine filtering mechanisms used by most network operating systems to decide which routes are admitted, modified, or advertised. The typical building blocks are:
| Component | Purpose | Typical CLI representation |
|---|---|---|
| AS-Path ACL / AS-Path Set | Match on BGP AS_PATH attributes (regular-expression or exact-match lists) | ip as-path access-list, as-path-set |
| Prefix List | Match on NLRI prefixes (exact, range, or le/ge) | ip prefix-list, prefix-set |
| Route-Map / Route-Policy | Combine matches, set actions (local-pref, MED, community, weight, etc.) and optionally call other policies | route-map, route-policy (with apply) |
In practice, engineers often start with simple ACLs and prefix lists, then copy-paste the same route-map stanza for each neighbor or peer-group. Over time, the stack becomes dense and hard to read, diff, or test.
Challenges with Overlapping AS-Path ACLs, Prefix Lists, and Route-Maps
- Visibility loss: When the same AS-path regex appears in multiple ACLs, a change in one place must be replicated elsewhere; otherwise, you get silent policy drift.
- Evaluation cost: Each route-map is walked sequentially; overlapping matches cause redundant CPU work and can exhaust TCAM on hardware-based platforms.
- Rollback fragility: A manual edit to a route-map may unintentionally affect another peer because the same ACL is referenced implicitly.
- Testing difficulty: Unit-testing a monolithic route-map requires building a large matrix of prefix/AS-path combinations; isolating a single logical block is impractical.
- Auditability: Diffs show line-by-line changes but cannot convey that a “new” line is merely a duplicate of an existing block.
These pains motivate a refactor toward named policy stages – reusable, independently versioned policy fragments that are composed rather than duplicated.
Identifying Refactoring Opportunities
Analyzing Existing Route-Policy Configurations
- Export the current policy:
show running-config | section route-map(IOS) orshow configuration policy-options policy-statement(Junos) orshow route-map(FRR). - Flatten the hierarchy: Convert every
matchclause into a tuple(type, value)and note the originating ACL/prefix-list name. - Build a dependency graph: Nodes are ACLs/prefix-lists/route-maps; edges indicate “uses”. Overlap appears as multiple incoming edges to the same node from different parents.
- Quantify duplication: Count identical regex strings or prefix-list entries across ACLs; a count > 1 signals a refactor candidate.
RP/0/RP0/CPU0:router# show running-config route-policy
!
route-policy PEER-A-IN
if destination in PFX-LIST-100 then
set local-preference 120
endif
if as-path in AS-PATH-ACL-200 then
set med 10
endif
apply COMMON-OUT
end-policy
!
route-policy PEER-B-IN
if destination in PFX-LIST-100 then
set local-preference 120
endif
if as-path in AS-PATH-ACL-200 then
set med 10
endif
apply COMMON-OUT
end-policy
Both PEER-A-IN and PEER-B-IN repeat the same two if blocks – a clear duplication.
Detecting Overlapping and Redundant Configurations
- AS-Path ACL overlap: Use
grep -E '^ip as-path access-list'and extract the regex; runsort | uniq -dto find duplicates. - Prefix list overlap: Same technique with
ip prefix-list. - Route-map stanza overlap: Parse each
matchline; hash the concatenation of match type + value; duplicates indicate redundant logic. - Tool assistance:
batfishcan answer questions like “Which routes match AS-PATH-ACL-200?” and expose overlapping match sets across policies.
Identifying Performance Bottlenecks and Scaling Limitations
- TCAM consumption: Each unique
matchentry consumes a TCAM line on ASIC-based platforms (e.g., Cisco Nexus 9000, Juniper MX). Overlap leads to wasted lines. - CPU walk-time: In software-based routers (FRR, IOS XR software forwarding), each route-map is interpreted per route; nested
applyadds function-call overhead but reduces linear scan length. - Policy-recompile latency: Large monolithic route-maps trigger a full policy recompile on every change; modular stages limit recompile to the changed stage only.
If you observe rising CPU during BGP updates or TCAM exhaustion alerts, the policy stack is a prime scaling target.
Refactoring Route-Policy Stacks
Defining Named Policy Stages
A named policy stage is a self-contained policy fragment that performs a single logical function (e.g., “set local-pref for routes with AS-65000 origin”, “drop prefixes longer than /24”). Stages are invoked via an apply (IOS XR/Junos) or a call-like construct (FRR via continue or external scripting). The key properties:
- Atomic: Does one thing and one thing only.
- Reusable: Can be applied from any parent policy.
- Versionable: Lives in its own file or config block, enabling diff-friendly changes.
- Testable: Can be exercised in isolation with a minimal set of test prefixes/AS-paths.
Converting AS-Path ACLs to Named Policy Stages
Replace an ip as-path access-list with an as-path-set (IOS XR) or a community-set-like construct, then reference that set from a dedicated route-policy that only performs the match and returns a Boolean.
as-path-set AS65000-SET
_65000_
end-set
!
route-policy MATCH_AS65000
if as-path in AS65000-SET then
pass
else
drop
endif
end-policy
!
route-policy PEER-A-IN
apply MATCH_AS65000
set local-preference 130
end-policy
Now the AS-path match lives in MATCH_AS65000; any change to the regex is made in one place.
Converting Prefix Lists to Named Policy Stages
Similarly, extract prefix lists into prefix-sets and wrap them in a policy that only returns pass/drop.
prefix-set PFX-100-SET
10.0.0.0/8 le 24
end-set
!
route-policy MATCH_PFX100
if destination in PFX-100-SET then
pass
else
drop
endif
end-policy
!
route-policy PEER-B-IN
apply MATCH_PFX100
set community 65000:100 additive
end-policy
Converting Route-Maps to Named Policy Stages
Large route-maps that perform multiple independent actions are split into stage policies each handling one action set, then composed.
route-policy STAGE_SET_LOCALPREF_PFX100
if destination in PFX-100-SET then
set local-preference 150
endif
end-policy
!
route-policy STAGE_SET_MED_COMM_AS65000
if as-path in AS65000-SET then
set med 5
set community 65000:200 additive
endif
end-policy
!
route-policy STAGE_SET_WEIGHT_COMMS300
if community matches-any COMMS-300-SET then
set weight 200
endif
end-policy
!
route-policy PEER-C-IN
apply STAGE_SET_LOCALPREF_PFX100
apply STAGE_SET_MED_COMM_AS65000
apply STAGE_SET_WEIGHT_COMMS300
end-policy
Each stage can be versioned, tested, and rolled back independently.
Implementing Named Policy Stages
Configuring Named Policy Stages using CLI
On IOS XR (similar on Junos with policy-statement and apply-path):
configure
as-path-set AS65000-SET
_65000_
end-set
!
prefix-set PFX-100-SET
10.0.0.0/8 le 24
end-set
!
route-policy MATCH_AS65000
if as-path in AS65000-SET then
pass
else
drop
endif
end-policy
!
route-policy MATCH_PFX100
if destination in PFX-100-SET then
pass
else
drop
endif
end-policy
!
route-policy PEER-A-IN
apply MATCH_AS65000
set local-preference 130
end-policy
!
route-policy PEER-B-IN
apply MATCH_PFX100
set community 65000:100 additive
end-policy
commit
Verification:
show route-policy PEER-A-IN detail
show as-path-set AS65000-SET
show prefix-set PFX-100-SET
Configuring Named Policy Stages using Code (e.g., Python, Ansible)
Ansible (using cisco.iosxr.iosxr_config module) – keep the staged policy in a Jinja2 template.
File: templates/route-policy-stages.j2
as-path-set {{ as_path_set.name }}
{% for regex in as_path_set.regexes %}
{{ regex }}
{% endfor %}
end-set
!
prefix-set {{ prefix_set.name }}
{% for prefix in prefix_set.prefixes %}
{{ prefix }}
{% endfor %}
end-set
!
route-policy {{ match_as_policy.name }}
if as-path in {{ as_path_set.name }} then
pass
else
drop
endif
end-policy
!
route-policy {{ match_pfx_policy.name }}
if destination in {{ prefix_set.name }} then
pass
else
drop
endif
end-policy
!
route-policy {{ peer_policy.name }}
apply {{ match_as_policy.name }}
set local-preference {{ peer_policy.local_pref }}
end-policy
Playbook snippet:
- name: Deploy staged BGP policy
hosts: ixr
vars:
as_path_set:
name: AS65000-SET
regexes:
- "_65000_"
prefix_set:
name: PFX-100-SET
prefixes:
- "10.0.0.0/8 le 24"
match_as_policy:
name: MATCH_AS65000
match_pfx_policy:
name: MATCH_PFX100
peer_policy:
name: PEER-A-IN
local_pref: 130
tasks:
- name: Configure route-policy stages
iosxr_config:
lines: "{{ lookup('template', 'route-policy-stages.j2') }}"
before: []