← Back to posts
AI & Automation

Using Claude to Automate Cisco Router Configuration and Diagnostics

Router Automation with Claude

Cisco routers — whether running IOS-XE on ASR 1000s or IOS-XR on ASR 9000s — are where complex routing policy lives. BGP policy, route-maps, prefix-lists, MPLS VPN configuration, QoS policy — all of this is configuration-intensive and easy to get wrong. A single incorrect route-map can black-hole traffic silently.

Claude excels here because routing problems require reasoning, not just scripting. This post covers patterns for using Claude to generate router configuration, analyze routing state, and build intelligent WAN management tooling.


Setup

import anthropic
import json
from netmiko import ConnectHandler

client = anthropic.Anthropic()

def ask_claude(prompt: str, system: str = None, max_tokens: int = 3000) -> str:
    kwargs = {
        "model": "claude-opus-4-5",
        "max_tokens": max_tokens,
        "messages": [{"role": "user", "content": prompt}]
    }
    if system:
        kwargs["system"] = system
    return client.messages.create(**kwargs).content[0].text

def connect(host: str, os_type: str = "cisco_ios") -> ConnectHandler:
    return ConnectHandler(
        device_type=os_type,  # "cisco_ios" or "cisco_xr"
        host=host,
        username="admin",
        password="password",
    )

Pattern 1: BGP Policy Generator

BGP policy (route-maps, prefix-lists, community matching) is notoriously complex to write correctly. Claude generates complete, consistent policy from a plain description:

def generate_bgp_policy(policy_description: str, platform: str = "IOS-XE") -> str:
    prompt = f"""
Generate a complete Cisco {platform} BGP routing policy configuration for:

{policy_description}

Requirements:
- Create all referenced prefix-lists, community-lists, and AS-path access-lists
- Use numbered route-map sequences with meaningful gaps (10, 20, 30...)
- Add descriptions to all policy elements
- Use explicit permit/deny — no implicit dependencies
- Include the neighbor statement applying the route-map
- Follow Cisco best practices for BGP policy

Output ONLY {platform} CLI configuration, no explanations.
"""
    return ask_claude(
        prompt,
        system=f"You are a senior Cisco BGP engineer. Output only {platform} CLI configuration."
    )

# IOS-XE example
policy = generate_bgp_policy("""
Provider: AS64512, neighbor 203.0.113.1
Our ASN: 65001

Inbound policy:
- Accept default route (0.0.0.0/0) and set local-preference 150
- Accept any /24 or shorter from 10.0.0.0/8 and set local-preference 100
- Reject all /25 or longer prefixes
- Tag all accepted routes with community 65001:100

Outbound policy:
- Advertise only our prefixes: 192.0.2.0/24 and 198.51.100.0/24
- Prepend our ASN twice on the backup link (this is the primary)
- Set MED to 100 on all advertised routes
""", platform="IOS-XE")

print(policy)

Pattern 2: IOS-XR Route Policy Language Generator

IOS-XR uses its own policy language (RPL) which is very different from IOS-XE route-maps:

def generate_xr_rpl(policy_description: str) -> str:
    prompt = f"""
Generate Cisco IOS-XR Route Policy Language (RPL) configuration for:

{policy_description}

IOS-XR RPL requirements:
- Use 'route-policy POLICY-NAME' blocks
- Use proper RPL syntax: if/elseif/else/endif
- Reference prefix-sets, community-sets, as-path-sets properly
- Define all sets BEFORE the policies that reference them
- Use 'pass', 'drop', 'done' correctly
- Apply policy under 'router bgp' with 'route-policy' statement

Output ONLY IOS-XR CLI configuration. Do NOT use IOS-XE route-map syntax.
"""
    return ask_claude(
        prompt,
        system="You are a Cisco IOS-XR expert. Output only valid IOS-XR RPL and CLI configuration."
    )

xr_policy = generate_xr_rpl("""
On ASR 9000 edge router:
- Inbound from PEER-AS 64512: accept prefixes in community 64512:100, 
  set local-pref 200. Accept 0.0.0.0/0, set local-pref 50. Reject all else.
- Outbound to 64512: advertise only prefixes tagged with community 65001:EXTERNAL.
  Strip all internal communities before advertising.
""")

def analyze_wan_links(router_host: str) -> dict:
    commands_iosxe = [
        "show interfaces | include line protocol|input rate|output rate|error|reset",
        "show ip bgp summary",
        "show ip route summary",
        "show processes cpu sorted | head 15",
        "show processes memory sorted | head 10",
    ]
    
    with connect(router_host) as conn:
        outputs = {cmd.split()[2]: conn.send_command(cmd) for cmd in commands_iosxe}
    
    prompt = f"""
Analyze this WAN router's operational state and provide a capacity and health assessment.

Router: {router_host}
Data: {json.dumps(outputs, indent=2)}

Return JSON with:
- health_status: "healthy" | "warning" | "critical"
- interface_issues: list of interfaces with errors, resets, or high utilization
- bgp_status: summary of BGP peer states
- cpu_concern: true/false with explanation
- memory_concern: true/false with explanation
- capacity_warnings: list of links at >70% utilization
- top_recommendations: list of 3 most important actions to take

Output ONLY valid JSON.
"""
    return json.loads(ask_claude(prompt))

Pattern 4: MPLS VPN Config Generator

MPLS L3VPN configuration requires precise coordination of VRF definitions, RD/RT values, and BGP VPNv4 peering:

def generate_mpls_vpn_config(vpn_params: dict) -> dict:
    prompt = f"""
Generate Cisco IOS-XE MPLS L3VPN configuration for a PE router.

VPN Parameters:
- VPN Customer: {vpn_params['customer']}
- VRF Name: {vpn_params['vrf_name']}
- Route Distinguisher: {vpn_params['rd']}
- Route Target Import: {vpn_params['rt_import']}
- Route Target Export: {vpn_params['rt_export']}
- CE Router IP: {vpn_params['ce_ip']}
- PE Interface to CE: {vpn_params['pe_interface']}
- PE Interface IP: {vpn_params['pe_ip']}
- CE Routing Protocol: {vpn_params['ce_protocol']}
- CE ASN (if BGP): {vpn_params.get('ce_asn', 'N/A')}
- PE BGP ASN: {vpn_params['pe_bgp_asn']}
- Route Reflector: {vpn_params['route_reflector']}

Return JSON with:
- vrf_config: VRF definition commands
- interface_config: PE-CE interface configuration  
- bgp_config: BGP VPNv4 and CE peering configuration
- verification_commands: commands to verify the VPN is working

Output ONLY valid JSON.
"""
    return json.loads(ask_claude(
        prompt,
        system="You are a Cisco MPLS expert. Generate valid IOS-XE configuration."
    ))

vpn = generate_mpls_vpn_config({
    "customer": "ACME-CORP",
    "vrf_name": "ACME",
    "rd": "65000:100",
    "rt_import": "65000:100",
    "rt_export": "65000:100",
    "ce_ip": "10.0.0.2",
    "pe_interface": "GigabitEthernet0/0/1",
    "pe_ip": "10.0.0.1/30",
    "ce_protocol": "BGP",
    "ce_asn": "65100",
    "pe_bgp_asn": "65000",
    "route_reflector": "10.255.255.1"
})

Pattern 5: Router Incident Responder

When something breaks, speed matters. This multi-turn assistant guides real-time incident response:

class RouterIncidentResponder:
    def __init__(self, router_host: str, platform: str = "IOS-XE"):
        self.host = router_host
        self.platform = platform
        self.history = []
        self.evidence = {}
        self.conn = connect(router_host, "cisco_ios" if platform == "IOS-XE" else "cisco_xr")
        
        self.system = f"""You are a senior network engineer responding to a {platform} router incident.
Your job is to quickly identify root cause and provide fix commands.
At each step: state what you learned from the last output, then ask for ONE specific command.
When root cause is found, immediately provide exact fix commands.
Be concise and urgent — every minute of downtime matters."""

    def investigate(self, symptom: str) -> str:
        """Start or continue investigation"""
        self.history.append({"role": "user", "content": symptom})
        response = client.messages.create(
            model="claude-opus-4-5",
            max_tokens=512,
            system=self.system,
            messages=self.history
        ).content[0].text
        self.history.append({"role": "assistant", "content": response})
        return response
    
    def run_command(self, command: str) -> str:
        """Run command on router and feed output back"""
        output = self.conn.send_command(command)
        self.evidence[command] = output
        return self.investigate(f"Command: {command}\n\nOutput:\n{output}")
    
    def __del__(self):
        if hasattr(self, 'conn'):
            self.conn.disconnect()

# Usage during an incident
responder = RouterIncidentResponder("10.0.0.1")

# Start the investigation
print(responder.investigate(
    "URGENT: BGP session to 203.0.113.1 (AS64512, our primary upstream) went down 10 minutes ago. "
    "We're losing about 40% of our routes. Secondary link is up but saturated."
))

# Claude asks for a specific command — run it
print(responder.run_command("show ip bgp neighbors 203.0.113.1 | include BGP state|notification|reset"))

# Claude analyzes and asks for next command
print(responder.run_command("show logging | include 203.0.113.1 | tail 20"))

# Continue until root cause found and fix commands provided

Key Considerations for Router Automation

Test in a lab first. BGP policy mistakes can cause widespread routing outages. Always validate generated configs in a lab or against a mock topology before production deployment.

Use commit confirmed on IOS-XR. IOS-XR’s commit confirmed 5 (auto-rollback after 5 minutes unless confirmed) is invaluable when pushing Claude-generated configs to production:

RP/0/RSP0/CPU0:router# commit confirmed 5

Validate prefix-lists before applying. Use show ip prefix-list detail and test with specific prefixes before attaching to a BGP neighbor.

For IOS-XR, always validate policy before committing:

RP/0/RSP0/CPU0:router# show rpl route-policy MY-POLICY detail
// Found this useful? Share it or start a conversation.