Cisco – Automatic Port Shutdown and Quarantine VLAN Setting

Use TCL to shutdown ports after a certain amount of days. This script has been modified to additionally add the port to a “dead” or “quarantine” VLAN. It can easily be further modified to add a description to the port, such as “SHUT PER POLICY”. 😉

Create the following two files:

sl_suspend_ports.tcl

::cisco::eem::event_register_syslog pattern "LINEPROTO-5-UPDOWN" maxrun 600
#-
# Copyright (c) 2009 Joe Marcus Clarke <jclarke@cisco.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
#
# This policy listens for link up syslog messages, and removes the port from
# the list of down ports.
#
# This policy uses the following environment variables:
#
# suspend_ports_config      : Path to configuration file.
#

if { ![info exists suspend_ports_config] } {
    set result "ERROR: Policy cannot be run: variable suspend_ports_config has not been set"
    error $result $errorInfo
}

namespace import ::cisco::eem::*
namespace import ::cisco::lib::*

proc run_cli { clist } {
    set rbuf ""

    if {[llength $clist] < 1} {
	return -code ok $rbuf
    }

    if {[catch {cli_open} result]} {
        return -code error $result
    } else {
	array set cliarr $result
    }

    if {[catch {cli_exec $cliarr(fd) "enable"} result]} {
        return -code error $result
    }

    foreach cmd $clist {
	if {[catch {cli_exec $cliarr(fd) $cmd} result]} {
            return -code error $result
	}

	append rbuf $result
    }

    if {[catch {cli_close $cliarr(fd) $cliarr(tty_id)} result]} {
        puts "WARNING: $result"
    }

    return -code ok $rbuf
}

array set arr_einfo [event_reqinfo]
if { ! [regexp {Interface ([^,]+), changed state to up} $arr_einfo(msg) -> iface] } {
    exit
}

while { 1 } {
    set results [run_cli [list "show event manager policy pending | include tm_suspend_ports.tcl"]]
    if { ! [regexp {tm_suspend_ports.tcl} $results] } {
	break
    }
    after 1000
}

if { [catch {open $suspend_ports_config "r"} result] } {
    exit
}

set fd $result
set contents [read $fd]
close $fd

set contents [string trim $contents]
array set ports [split $contents]

if { [info exists ports($iface)] } {
    array unset ports $iface

    set fd [open $suspend_ports_config "w"]
    puts -nonewline $fd [array get ports]
    close $fd
}

tm_suspend_ports.tcl

::cisco::eem::event_register_timer cron cron_entry "0 10 * * *" queue_priority normal maxrun 600
#-
# Copyright (c) 2009 - 2014 Joe Marcus Clarke <jclarke@cisco.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
#
# This policy runs at a configured time, then checks to see if inactive ports
# have been inactive for a configured amount of time.  If so, then the ports
# will be shutdown.
#
# This policy uses the following environment variables:
#
# suspend_ports_days        : Number of days before a port is suspended.
#
# suspend_ports_config      : Path to configuration file.
#
# suspend_quarantine_vlan   : (optional) VLAN number into which ports will be moved
#                             instead of being shutdown.  If not defined, ports will be
#                             shutdown.
#

if { ![info exists suspend_ports_days] } {
    set result "ERROR: Policy cannot be run: variable suspend_ports_days has not been set"
    error $result $errorInfo
}

if { ![info exists suspend_ports_config] } {
    set result "ERROR: Policy cannot be run: variable suspend_ports_config has not been set"
    error $result $errorInfo
}

namespace import ::cisco::eem::*
namespace import ::cisco::lib::*

proc run_cli { clist } {
    set rbuf ""

    if {[llength $clist] < 1} {
	return -code ok $rbuf
    }

    if {[catch {cli_open} result]} {
        return -code error $result
    } else {
	array set cliarr $result
    }

    if {[catch {cli_exec $cliarr(fd) "enable"} result]} {
        return -code error $result
    }

    foreach cmd $clist {
	if {[catch {cli_exec $cliarr(fd) $cmd} result]} {
            return -code error $result
	}

	append rbuf $result
    }

    if {[catch {cli_close $cliarr(fd) $cliarr(tty_id)} result]} {
        puts "WARNING: $result"
    }

    return -code ok $rbuf
}

set SECS_IN_DAYS 86400
set DOWN 0
set UP 1
set ADMIN_DOWN 2

set now [clock seconds]
set susp_time [expr $suspend_ports_days * $SECS_IN_DAYS]
array set suspend_ports [list]

if { [catch {open $suspend_ports_config "r"} result] } {
    array set ports [list]
} else {
    set fd $result
    set contents [read $fd]
    close $fd

    set contents [string trim $contents]
    array set ports [split $contents]
}

set result [run_cli [list "show ip interface brief | include Ethernet"]]
foreach line [split $result "\n"] {
    set line [string trim $line]
    regsub -all {\s+} $line " " line
    set elems [split $line]
    set iface [lindex $elems 0]
    if { ! [regexp {Ethernet} $iface] || [llength $elems] < 6 } {
	continue
    }
    if { [lindex $elems 4] == "administratively" && [lindex $elems 5] == "down" } {
	set status $ADMIN_DOWN
    } elseif { [lindex $elems 4] == "down" } {
	set status $DOWN
    } elseif { [lindex $elems 4] == "up" && [lindex $elems 5] == "up" } {
	set status $UP
    } else {
	set status $DOWN
    }

    if { [info exists ports($iface)] } {
	if { $status == $UP || $status == $ADMIN_DOWN } {
	    array unset ports $iface
	} else {
	    if { [expr $now - $ports($iface)] >= $susp_time } {
		set suspend_ports($iface) $ports($iface)
	    }
	}
    } else {
	if { $status == $DOWN } {
	    set ports($iface) $now
	}
    }
}

set fd [open $suspend_ports_config "w"]
puts -nonewline $fd [array get ports]
close $fd

set cli [list "config t"]
foreach port [array name suspend_ports] {
    if { [info exists suspend_quarantine_vlan] } {
        set cli [concat $cli [list "interface $port" "switchport access vlan $suspend_quarantine_vlan"]]
        action_syslog msg "Moving port $port into quarantine VLAN $suspend_quarantine_vlan since it was last used on [clock format $suspend_ports($port)]"
    } else {
        set cli [concat $cli [list "interface $port" "shut"]]
        action_syslog msg "Shutting down port $port since it was last used on [clock format $suspend_ports($port)]"
    }
}

if { [info exists suspend_quarantine_vlan] } {
	foreach port [array name suspend_ports] {
			set cli [concat $cli [list "interface $port" "shut"]]
			action_syslog msg "Shutting down port $port since it was last used on [clock format $suspend_ports($port)]"
		}
}

lappend cli "end"
if { [catch {run_cli $cli} result] } {
    action_syslog priority err msg "Failed to shutdown ports: '$result'"
}

Create the “policies” directory and copy these two files into it:
mkdir flash:/policies
copy usbflash0:/policies/sl_suspend_ports.tcl flash:/policies/sl_suspend_ports.tcl
copy usbflash0:/policies/tm_suspend_ports.tcl flash:/policies/tm_suspend_ports.tcl

Enter the following into your Cisco device’s config:
event manager environment suspend_ports_days 21
event manager environment suspend_ports_config flash:/susp_ports.dat
event manager environment suspend_quarantine_vlan 3333
event manager directory user policy "flash:/policies"
no event manager policy sl_suspend_ports.tcl
event manager policy sl_suspend_ports.tcl
no event manager policy tm_suspend_ports.tcl
event manager policy tm_suspend_ports.tcl

Sauce

Leave a Reply

Your email address will not be published. Required fields are marked *