Summary: TCL script for Cisco IOS-XE that safely swaps the IPv4 default route, verifies internet reachability after the change, and automatically reverts if connectivity is lost.
Upload the script to flash and run from the IOS-XE CLI:
copy tftp://10.x.x.x/default_route_change.tcl flash:
tclsh flash:default_route_change.tcl
Edit the configuration block at the top of the script before running — set NEW_NEXTHOP, OLD_NEXTHOP, and adjust the probe and retry parameters as needed.
##############################################################################
# default_route_change.tcl
#
# Purpose : Safely change the IPv4 default route on an IOS-XE switch/router.
# 1. Verify internet reachability BEFORE the change.
# 2. Remove the old default route and install the new one.
# 3. Verify internet reachability AFTER the change (with retries).
# 4. If reachability is lost after all retries, revert to the
# original default route automatically.
#
# Usage : tclsh flash:default_route_change.tcl
#
# Platform: Cisco IOS-XE (16.x / 17.x)
##############################################################################
# ===========================================================================
# >>> CONFIGURATION – edit only this section <<<
# ===========================================================================
# IP address of the NEW next-hop for the default route
set NEW_NEXTHOP "10.0.0.1"
# IP address of the OLD (current) next-hop to remove and restore on revert
set OLD_NEXTHOP "192.168.1.1"
# Public IP to ping for internet reachability checks
set PROBE_HOST "8.8.8.8"
# Number of ICMP echo requests per reachability check
set PING_COUNT 5
# Per-ping timeout in seconds
set PING_TIMEOUT 2
# Minimum successful replies to consider the probe host reachable
set PING_THRESHOLD 3
# How many times to re-check reachability after the route change before
# declaring failure and reverting
set POST_CHANGE_RETRIES 3
# Seconds to wait between each post-change retry
set RETRY_DELAY 10
# ===========================================================================
# >>> END OF CONFIGURATION <<<
# ===========================================================================
# ---------------------------------------------------------------------------
# log_msg - print a timestamped message and push it to IOS syslog
# ---------------------------------------------------------------------------
proc log_msg {severity text} {
set ts [clock format [clock seconds] -format "%Y-%m-%dT%H:%M:%S"]
set msg "\[TCL-ROUTE\] $ts $severity: $text"
puts $msg
catch { ios_config "do send log $msg" }
}
# ---------------------------------------------------------------------------
# ping_host - returns 1 if host is reachable, 0 otherwise
# ---------------------------------------------------------------------------
proc ping_host {host count timeout threshold} {
set reachable 0
set rc [catch {
set output [exec "ping $host repeat $count timeout $timeout"]
# IOS-XE ping output: "Success rate is N percent (rcvd/sent)"
if {[regexp {(\d+)/(\d+)} $output -> rcvd sent]} {
if {$rcvd >= $threshold} { set reachable 1 }
}
} err]
if {$rc != 0} {
log_msg WARNING " ping exec error: $err"
}
return $reachable
}
# ---------------------------------------------------------------------------
# set_default_route - add or remove 'ip route 0.0.0.0 0.0.0.0 <nexthop>'
# action : "add" | "remove"
# ---------------------------------------------------------------------------
proc set_default_route {action nexthop} {
set prefix [expr {$action eq "remove" ? "no " : ""}]
set cmd "${prefix}ip route 0.0.0.0 0.0.0.0 $nexthop"
log_msg INFO " Config: $cmd"
set rc [catch { ios_config "$cmd" } err]
if {$rc != 0} {
log_msg ERROR " ios_config error: $err"
return 0
}
return 1
}
# ---------------------------------------------------------------------------
# revert - remove new route, restore old route, then exit with given code
# ---------------------------------------------------------------------------
proc revert {new_nh old_nh exit_code} {
log_msg WARNING "--- REVERTING ---"
set_default_route "remove" $new_nh
set ok [set_default_route "add" $old_nh]
if {$ok} {
log_msg WARNING "Revert SUCCESSFUL. Original next-hop $old_nh restored."
} else {
log_msg ERROR "Revert FAILED. Run manually: ip route 0.0.0.0 0.0.0.0 $old_nh"
}
exit $exit_code
}
# ===========================================================================
# MAIN
# ===========================================================================
log_msg INFO "============================================================"
log_msg INFO "Default route change script started."
log_msg INFO " Old next-hop : $OLD_NEXTHOP"
log_msg INFO " New next-hop : $NEW_NEXTHOP"
log_msg INFO " Probe host : $PROBE_HOST"
# ---------------------------------------------------------------------------
# STEP 1 - Pre-change reachability check
# ---------------------------------------------------------------------------
log_msg INFO "------------------------------------------------------------"
log_msg INFO "STEP 1: Checking internet reachability via current route ..."
set pre_ok [ping_host $PROBE_HOST $PING_COUNT $PING_TIMEOUT $PING_THRESHOLD]
if {$pre_ok} {
log_msg INFO "STEP 1 PASS: $PROBE_HOST is reachable. Proceeding with route change."
} else {
log_msg WARNING "STEP 1 FAIL: $PROBE_HOST is NOT reachable before the change."
log_msg WARNING "Proceeding anyway - post-change check will still validate."
}
# ---------------------------------------------------------------------------
# STEP 2 - Apply new default route
# ---------------------------------------------------------------------------
log_msg INFO "------------------------------------------------------------"
log_msg INFO "STEP 2: Removing old default route (next-hop $OLD_NEXTHOP) ..."
if {![set_default_route "remove" $OLD_NEXTHOP]} {
log_msg ERROR "STEP 2 FAIL: Could not remove old route. No change made. Exiting."
exit 2
}
log_msg INFO "STEP 2: Installing new default route (next-hop $NEW_NEXTHOP) ..."
if {![set_default_route "add" $NEW_NEXTHOP]} {
log_msg ERROR "STEP 2 FAIL: Could not install new route. Restoring old route ..."
set_default_route "add" $OLD_NEXTHOP
exit 3
}
log_msg INFO "STEP 2 PASS: New default route installed."
# Brief pause to allow routing table to converge
after 2000
# ---------------------------------------------------------------------------
# STEP 3 - Post-change reachability check with retries
# ---------------------------------------------------------------------------
log_msg INFO "------------------------------------------------------------"
log_msg INFO "STEP 3: Verifying internet reachability via new route ..."
log_msg INFO " (up to $POST_CHANGE_RETRIES attempt(s), ${RETRY_DELAY}s apart)"
set post_ok 0
for {set attempt 1} {$attempt <= $POST_CHANGE_RETRIES} {incr attempt} {
log_msg INFO " Attempt $attempt of $POST_CHANGE_RETRIES: pinging $PROBE_HOST ..."
set post_ok [ping_host $PROBE_HOST $PING_COUNT $PING_TIMEOUT $PING_THRESHOLD]
if {$post_ok} {
log_msg INFO " Attempt $attempt PASS: $PROBE_HOST reachable."
break
}
log_msg WARNING " Attempt $attempt FAIL: $PROBE_HOST not reachable."
if {$attempt < $POST_CHANGE_RETRIES} {
log_msg INFO " Waiting ${RETRY_DELAY}s before next attempt ..."
after [expr {$RETRY_DELAY * 1000}]
}
}
# ---------------------------------------------------------------------------
# STEP 4 - Revert if all retries failed, otherwise confirm success
# ---------------------------------------------------------------------------
log_msg INFO "------------------------------------------------------------"
if {!$post_ok} {
log_msg WARNING "STEP 4: Internet NOT reachable after $POST_CHANGE_RETRIES attempt(s)."
revert $NEW_NEXTHOP $OLD_NEXTHOP 4
} else {
log_msg INFO "STEP 4 PASS: Internet reachable via new next-hop $NEW_NEXTHOP."
log_msg INFO "Route change validated and committed."
}
log_msg INFO "============================================================"
log_msg INFO "Script completed successfully."
exit 0