Skip to content
Back to skills

Routing

ASecurity

Path selection investigation — PBR, route-map and prefix-list policy influence, ECMP behavior, routing table verification

  • 11 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 26, 2026
developmentdatabase

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add pdudotdev/aiNOC --skill routing --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Routing?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Routing
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/pdudotdev-routing/badge)](https://www.skillsdirectory.com/skills/pdudotdev-routing)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: Routing Policy & Path Selection
description: "Path selection investigation — PBR, route-map and prefix-list policy influence, ECMP behavior, routing table verification"
---

> **PREREQUISITE**: Before investigating path selection or routing policy, you MUST have verified that interfaces are Up/Up and protocol neighbors are healthy on the device in question. Run `get_interfaces(device)` and `get_<protocol>(device, "neighbors")`. If any interface is down or any neighbor is missing, fix that first — path selection issues cannot be the root cause if basic adjacency prerequisites are missing.

# Routing Policy & Path Selection Skill

## Scope
Path selection troubleshooting for all devices. Read this skill when traffic takes an unexpected or consistently asymmetric path and protocol adjacency/route presence are confirmed correct.

## Start Here: Policy Check Sequence

When traffic consistently takes an unexpected path, check in this order — do not skip steps:

> **Step 0 — Longest-prefix-match override:** Before checking any policy, verify no more-specific route exists: `get_routing(device, prefix=<exact_destination>)`. A /32 host route or a more-specific subnet overrides ALL policy, cost, and AD calculations. If an unexpected specific route is present, that is the root cause — stop here.

    get_routing_policies(device, "policy_based_routing")
    get_routing_policies(device, "route_maps")
    get_routing_policies(device, "prefix_lists")
    get_routing(device, prefix="<destination>")

> **PBR must be checked before any protocol cost math.** PBR overrides the routing table entirely — OSPF and BGP best-path calculations are irrelevant if a `ip policy route-map` is applied on the ingress interface.

| Finding | Root Cause |
|---------|-----------|
| Interface has `ip policy route-map X` | PBR is active — inspect the route-map and ACL before anything else |
| Route-map `set metric` biasing a redistributed path | Redistribution policy is skewing path preference |
| Prefix-list `deny` at a redistribution or distribution point | Route blocked from being advertised upstream — missing from downstream RIBs |
| Single RIB entry when two equal-cost paths are expected | PBR, cost asymmetry, `maximum-paths 1`, or distribute-list filtering |
| Two equal-cost RIB entries but one path always selected | Normal CEF per-destination hashing — not a Router ID tie-breaker |

---

## Symptom: Policy-Based Routing

`policy_based_routing` returns only the **interface binding** — which interface has `ip policy route-map X` applied. It does not show the route-map logic. Always follow with two more queries:

    get_routing_policies(device, "policy_based_routing")   → identifies which interface has PBR
    get_routing_policies(device, "route_maps")             → shows match/set clauses for that route-map
    get_routing_policies(device, "access_lists")           → shows ACL referenced in the match clause

### What to look for in route-map output

- `match ip address <ACL>` — which traffic is matched (source/destination criteria)
- `set ip next-hop <IP>` — matched traffic is forwarded here, bypassing the RIB
- `set interface <intf>` — matched traffic exits a specific interface regardless of routing
- `deny` sequence with no `set` — traffic in a deny sequence falls through to the RIB (not PBR-forwarded)

> **Never conclude "PBR is not the cause" from `policy_based_routing` alone.** If an interface shows `ip policy`, always fetch both `route_maps` and `access_lists` before forming a conclusion. The three queries work as a chain: binding → logic → match criteria.

---

## Symptom: Route Filtering by Route Map or Prefix List

Route-maps applied at redistribution points can modify metrics, biasing path preference on downstream devices. Prefix-lists used as distribute-lists or in redistribution route-maps can block routes from being accepted into or advertised from the RIB — making them absent on downstream routers.

    get_routing_policies(device, "route_maps")     → check for set metric or deny sequences
    get_routing_policies(device, "prefix_lists")   → check permit/deny entries for the affected prefix
    get_ospf(device, "config")                     → check for distribute-list under router ospf

**Direction matters:** filters are applied either inbound (blocking routes from entering the local RIB from a neighbor/protocol) or outbound (blocking routes from being advertised). The filter is configured on the device doing the filtering — the downstream device is where the route will be absent.

**Implicit deny:** every prefix-list ends with an implicit `deny any`. Routes not explicitly permitted are silently dropped.

**Distribute-list filtering** (`distribute-list` under `router ospf`) is a special case: it blocks routes from entering the RIB without removing them from the LSDB. Symptom: `get_ospf(device, "database")` shows the LSA but `get_routing(device, prefix=<X>)` returns no route. See the OSPF skill's "Distribute-List Filtering" section for the full investigation flow — use `get_routing_policies(device, "access_lists")` or `get_routing_policies(device, "prefix_lists")` to inspect the filter logic.

> For BGP attribute manipulation (`set local-preference`, `set weight`, `set as-path prepend`), see `skills/bgp/SKILL.md` — Wrong Best Path section.

---

## Symptom: ECMP — Traffic Always Takes One Path

When two equal-cost paths are expected but traffic consistently uses the same one:

    get_routing(device, prefix="<destination>")   → must show 2+ equal-cost entries for true ECMP
    traceroute(device, destination="<dest>")      → confirms actual forwarding path for that flow

| RIB result | Interpretation |
|-----------|---------------|
| Two entries, equal cost | True ECMP. CEF uses per-destination hashing — a single src/dst IP pair always takes the same path. This is normal, not a misconfiguration. |
| One entry only | ECMP never established — investigate below. |

> **On Cisco IOS with CEF, per-destination load balancing means a single flow always takes the same path.** This is deterministic CEF hashing of the src/dst IP pair — it is NOT a Router ID tie-breaker. Attributing consistent path selection to "higher Router ID wins" is incorrect for IOS OSPF ECMP.

If only one path is installed when two are expected:

1. **Check cost symmetry** — unequal costs mean only the better path is installed:

        get_ospf(device, "database")     → compare LSA costs on both candidate paths

2. **Check `maximum-paths`** — default is 4 on IOS-XE for OSPF; if set to 1, only one path installs:

        get_ospf(device, "config")       → look for maximum-paths under router ospf

3. **Check BGP `maximum-paths`** — BGP defaults to 1 path (no ECMP). Unlike OSPF (default 4), BGP requires explicit configuration:

        get_bgp(device, "config")   → look for maximum-paths under address-family ipv4

4. **Check PBR** — a route-map on the ingress interface can force one path regardless of the RIB:

        get_routing_policies(device, "policy_based_routing")

---

## Symptom: Route From Wrong Protocol (AD Conflict)

When traffic takes a path that doesn't match the expected routing protocol (e.g., iBGP instead of OSPF, or a static route unexpectedly active):

```
get_routing(device, prefix=<destination>)   → check [AD/metric] field in RIB output
```

The RIB entry format `[AD/metric]` identifies the source. When multiple protocols have routes for the same prefix, lowest AD wins:

| Protocol | Default AD |
|----------|-----------|
| Connected | 0 |
| Static | 1 |
| eBGP | 20 |
| OSPF | 110 |
| iBGP | 200 |

**Floating static route:** A static route with AD > 1 (e.g., `ip route X Y Z 254`) acts as a backup — it activates only when all lower-AD routes for that prefix are withdrawn. If a floating static unexpectedly becomes active, the primary dynamic route has been lost — investigate the routing protocol, not the static itself.

---

## Symptom: NAT/PAT Translation Issues

> **Low priority** — only investigate NAT after all routing, adjacency, and policy checks pass. NAT issues are rare root causes in this network.

When the breaking hop is a **NAT_EDGE** device (E1C, E2C per INTENT.json) and all of the following are true:
- All interfaces Up/Up
- All protocol neighbors FULL
- Routes present in RIB with correct next-hops
- No PBR, route-map, or prefix-list anomalies

Then check NAT translations via `run_show`:

    run_show(device, "show ip nat translations")
    run_show(device, "show ip nat statistics")

| Finding | Root Cause |
|---------|-----------|
| Empty translation table | No traffic is being NATed — check `ip nat inside`/`ip nat outside` interface designations and NAT ACL/route-map |
| Translations present but destination unreachable | NAT is working; issue is upstream (ISP) or return-path related |
| Only inside→outside entries, no outside→inside | One-way NAT — return traffic may be blocked by ISP or missing reverse route |

Look for: inside/outside interface counts, active translations, expired translations, and pool exhaustion.

---

## Query Reference

| Query | What it returns |
|-------|----------------|
| `policy_based_routing` | Interfaces with `ip policy route-map X` applied |
| `route_maps` | All route-map definitions (match/set clauses, sequences) |
| `prefix_lists` | Prefix-list definitions (permit/deny per range) |
| `access_lists` | ACL definitions — used to identify PBR match criteria |
| `redistribution` | Active redistribution statements |

---

## Verification Checklist (Post-Fix)

- [ ] `get_routing_policies(device, "policy_based_routing")` — no unexpected `ip policy` bindings on ingress interfaces
- [ ] `get_routing_policies(device, "route_maps")` — no `deny` sequences or `set metric` clauses unexpectedly biasing the path
- [ ] `get_routing_policies(device, "prefix_lists")` — affected prefix is covered by `permit`, not `deny`
- [ ] `get_routing(device, prefix="<destination>")` — correct number of next-hops installed in RIB
- [ ] `traceroute(device, destination="<dest>")` — actual forwarding path matches expected topology
- [ ] `run_show(device, "show ip nat translations")` — if NAT_EDGE device (E1C, E2C): translations present (check only when relevant)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.