Category: Tcl

Sending Wake-on-LAN (WOL) packet with IOS Tcl

Jónatan Þór Jónasson took the time to implement Wake-on-LAN functionality using UDP support introduced in Cisco IOS Tcl in release 15.1(1)T. He found a TCL/TK example of a magic packet being sent, used that as a base, and with small modifications got it to work on his router. Here‘s his code (it’s obviously a proof-of-concept, but you need just a few more lines to get a working Tclsh script):

read more see 6 comments

Did you notice 15.1T is released?

Unveiling of the Cisco IOS release 15.1(1)T was the extreme opposite of the CRS-3 and Catalyst 3750-X splashes; the next release of one of the foundations of Cisco’s core business deserved a modest two-paragraph mention in the What's New in Cisco Product Documentation page.

If you’re a voice guru, you’ll probably enjoy the list of 20+ voice-related new features, including the all-important Enhanced Music on Hold. For the rest of us, here’s what I found particularly interesting:

read more see 19 comments

Dance around IOS bugs with Tcl and EEM

Recently, on an IPSec-based customer network, we installed one of the brand new platforms introduced by Cisco Systems. The initial software release had memory leaks (no problem, we all know these things happen), so we upgraded the box to the latest software. It works perfectly … until you reload it. The software we’re forced to use cannot get IPSec to work if the startup configuration includes interface-level crypto-maps. Interestingly, you can configure crypto-maps manually and they work … until you save them into the startup configuration and reload the box.

read more add comment

Things you cannot do with Tclsh

What would you think if you’d receive three queries about the same (somewhat obscure) feature within six hours? It started with a nice e-mail from an engineer that I’ve corresponded with in the past. He wanted to send a Wake-on-LAN packet to a PC in a remote office. Usually you could use the ip directed-broadcast feature, but he wanted to use the remote office router to generate the packet.

read more see 8 comments

Generate HTTP(S) requests from Tcl shell

A few days ago, a reader sent me an e-mail titled “Telnet Automation from a Cisco Router” and complained that IOS Tcl does not support the expect commands (spawn, send and expect). Since Expect is a Tcl extension, not part of the core Tcl, it’s not included in Cisco IOS, which was the only answer I could give.

You might be able to port Expect to IOS as a Tcl package if it doesn’t require external libraries.
read more see 14 comments

This is QoS; Who Cares about Real-Time Response?

It all started with a innocuous question: can you detect voice traffic with EEM? Looks simple enough: create a QoS class-map that matches voice calls and read the cbQosClassMapStats table in the CISCO-CLASS-BASED-QOS-MIB. The first obstacle was finding the correct indexes, but a Tcl script quickly solved that; I was ready to create the EEM applet. The applet failed to work correctly and after lots of debugging I figured out the counters in the cbQosClassMapStats table change only every 10 seconds.

I couldn’t believe my eyes and simply had to test other MIB variables as well. As expected, the IF-MIB (standard interface MIB) counters increase in real-time, but obviously someone had the bright idea that we need to detect changes in traffic profile only every now and then. Although I've received numerous suggestions from my readers, none of them works on a Cisco 1800 or a Cisco 7200. Oh, well, Cisco developers from the days when I started working with routers would have known better…

read more see 14 comments

The most convoluted MIB I’ve seen

Jared Valentine sent me a really interesting problem: he would like to detect voice traffic and start shaping TCP traffic for the duration of the voice call. The ideal solution would be an EEM applet reacting to the changes in the CISCO-CLASS-BASED-QOS-MIB; one of its tables contains the amount of traffic for each class configured in a service policy.

The MIB navigation looks simple: you just read the values from the cbQosClassMapStats table, indexed by policy ID and class ID. The real problem is finding the correct index values. I could walk the MIB manually with a MIB browser or snmp_getnext TCL calls, but this approach is obviously not scalable, so I wrote a script that walks through the cbQosServicePolicy, cbQosObjects, cbQosPolicyMapCfg and cbQosClassMapCfg tables and prints the index values you need.

The following text written by Ivan Pepelnjak in 2008 was originally published on CT3 wiki. That web site became unreachable in early 2019. We retrieved the original text from the Internet Archive, cleaned it up, updated it with recent information if necessary, and republished it on ipSpace.net blog on November 17, 2020

This script traverses the Class-based QoS MIB and displays service policies and classes attached to individual interfaces. The policy index and class index values are printed next to the policy/class name to help the operator fetch the desired SNMP variable from the statistics tables of the CISCO-CLASS-BASED-QOS-MIB.

Installation

  • Download the source file into flash:cbindex.tcl
  • Configure alias exec cbindex tclsh flash:cbindex.tcl
  • Configure persistent CBQoS indexes with the snmp mib persist cbqos (otherwise the indexes will change after the router reload).

Usage guidelines

Usage: cbindex community

Command line parameters:

  • Community: SNMP community with R/O access to the CISCO-CLASS-BASED-QOS-MIB

Source code

#
# title:    Displays MQC class map indexes
# name:     cbindex.tcl
# desc:     The script traverses the Class-based QoS MIB and
#           displays service policies and classes attached to 
#           individual interfaces. The policy index and class
#           index values are printed next to the policy/class
#           name to help the operator fetch the desired SNMP 
#           variable from the statistics tables of the 
#           CISCO-CLASS-BASED-QOS-MIB.
#

proc snmpInit { oid } {
  global snmpCommunity
  set getResult [ snmp_getnext $snmpCommunity $oid ]
  if { [ regexp {snmp error} $getResult ] } { 
    puts "SNMP calls with community $snmpCommunity fail"; return 0 
  }
  if { [ regexp {oid='(.*)'} $getResult ignore nxtoid ] } {
    if { [string first $oid $nxtoid] == 0 } { return 1 }
  }
  puts "MIB $oid not implemented in this IOS release"; return 0;
}
  
proc snmpGet { oid result } {
  global snmpCommunity
  upvar $result r
  if { [info exists r] } { unset r }

  set getResult [ snmp_getone $snmpCommunity $oid ]
  if { [ regexp {snmp error.*text='(.*)'} $getResult ignore errtxt ] } { 
    error "snmpGet - $errtxt"; return 0 
  }
  if { [ regexp {oid='(.*)'.*val='(.*)'} $getResult ignore oid result ] } {
    if { ! [ string equal $result "NO_SUCH_INSTANCE_EXCEPTION" ] } {
      set r(OID) $oid ;
      set r(VALUE) $result ; 
      return 1;
    }
  }
  return 0;
}

proc snmpGetNext { oid result } {
  global snmpCommunity
  upvar $result r
  if { [info exists r] } { unset r }

  set getResult [ snmp_getnext $snmpCommunity $oid ]
  if { [ regexp {snmp error.*text='(.*)'} $getResult ignore errtxt ] } { 
    error "snmpGet - $errtxt"; return 0 
  }
  if { [ regexp {oid='(.*)'.*val='(.*)'} $getResult ignore oid result ] } {
    if { ! [ string equal $result "NO_SUCH_INSTANCE_EXCEPTION" ] } {
      set r(OID) $oid ;
      set r(VALUE) $result ;
      set oidSplit [ split $oid "." ]
      set r(NAME)  [ lindex $oidSplit 0 ]
      set r(INDEX) [ lreplace $oidSplit 0 0 ]
      set r(IDXLIST) [ join $r(INDEX) "." ]
      return 1;
    }
  }
  return 0;
}

proc snmpGetInTable { oid result { parentoid "" }} {
  global snmpCommunity
  upvar $result r

  snmpGetNext $oid r
  if { ! [info exists r(OID)] } { return 0 }
  if { [string equal $parentoid ""] } {
    set oidSplit [ split $oid "." ]
    set parentoid [lindex $oidSplit 0]
  }
  if { [string first $parentoid $r(OID)] != 0 } { return 0 }
  return 1;
}

proc printQosClassIndex {} {
  global snmpCommunity
  set oid "cbQosIfIndex"
  array set dirLookup { 1 in 2 out }
  set cnt 0
  while { [ snmpGetInTable $oid svcPolicy ] } {
    if { [snmpGet "ifDescr.$svcPolicy(VALUE)" ifDescr] } {
      snmpGet "cbQosPolicyDirection.$svcPolicy(INDEX)" svcDirection
      snmpGetNext "cbQosConfigIndex.$svcPolicy(INDEX)" policyObject
      snmpGet "cbQosPolicyMapName.$policyObject(VALUE)" policyName
      puts "\n$ifDescr(VALUE) ($dirLookup($svcDirection(VALUE))): $policyName(VALUE) ($svcPolicy(INDEX))"
      set coid "cbQosObjectsType.$svcPolicy(INDEX)"
      set parentoid $coid
      while { [ snmpGetInTable $coid svcClass $parentoid ] } {
        if { $svcClass(VALUE) == 2 } {
          snmpGet "cbQosConfigIndex.$svcClass(IDXLIST)" svcClassConfig
          snmpGet "cbQosCMName.$svcClassConfig(VALUE)" svcClassName
          puts "  $svcClassName(VALUE) $svcClass(IDXLIST)"
        }
        set coid $svcClass(OID)
      }
    } else { error "Cannot get interface name for service policy $svcPolicy(VALUE)" }
    set oid $svcPolicy(OID)
  }
}

set snmpCommunity [lindex $argv 0]
if { [string equal $snmpCommunity ""] } { set snmpCommunity "public" }
if { ! [ snmpInit "cbQosObjectsType" ] } return
printQosClassIndex

Sample usage scenario

The following QoS classes and policies have been configured on the router:

class-map match-all Mail
 match protocol smtp
!
class-map match-all Web
 match protocol http
!
class-map match-all SecureWeb
 match protocol secure-http
!
class-map match-any Surfing
 match class-map Web
 match class-map SecureWeb
!
class-map match-all Files
 match protocol ftp
!
policy-map Internet
 class Web
    bandwidth 128
 class SecureWeb
    priority 64
 class Mail
    bandwidth 32
!
policy-map MailOrFtp
 class Mail
  set ip precedence 0
 class Files
  set ip precedence 0
 class Surfing
    police 16000
 class class-default
   police cir 8000
     exceed-action drop 
!
interface Serial1/0
 service-policy input MailOrFtp
 service-policy output Internet
!
interface Serial1/1
 service-policy output MailOrFtp

The cbindex script reported the following SNMP indexes:

c7200#cbindex Test

Serial1/0 (in): MailOrFtp (48)
  Web 48.383777
  Surfing 48.1970017
  Mail 48.4297921
  Files 48.13110129
  class-default 48.14779377
  SecureWeb 48.15077857

Serial1/0 (out): Internet (50)
  Mail 50.10516033
  Web 50.14007809
  SecureWeb 50.14520625
  class-default 50.15008753

Serial1/1 (out): MailOrFtp (66)
  Web 66.383777
  Surfing 66.1584993
  Files 66.4236097
  Mail 66.11615889
  SecureWeb 66.15077857
  class-default 66.15082481

Based on these indexes, you could monitor the bit rate of the Web class in outbound policy configured on Serial 1/1 with SNMP variable cbQosCMPrePolicyBitRate.66.383777.

c7200#tclsh
c7200(tcl)#snmp_getone Test cbQosCMPrePolicyBitRate.66.383777
{<obj oid='cbQosCMPrePolicyBitRate.66.383777' val='0'/>}
see 3 comments

Simple CLI extensions: handling special characters

Last week I've described how you can extend the exec-mode CLI commands with almost no knowledge of Tcl. A bit more work is required if your commands include Tcl special characters (quotes, braces or backslashes).

For example, to display all routes advertised by customers of AS X, you'd use the following show command: show ip bgp regexp _X_([0-9]+)(_\1)*$ (the regular expression is explained in the AS-path based filter of customer BGP routes post). This command cannot be entered as a Tcl string with variable substitution; Tcl would interpret the [ and \ characters. You could enter the whole command in curly braces, but then there would be no variable substitution that we need to insert command line parameters. To make Tcl happy, use the following Tcl commands:
  1. set cmd {first-part-of-command} stores the command prefix into the cmd variable;
  2. append cmd $argv appends the command line arguments to the command;
  3. append cmd {rest-of-command} appends the rest of the IOS exec command;
  4. puts [exec $cmd] executes the command and prints the results.

For example, the following code will display the customers of a BGP AS specified in the command line (after being stored in a flash file and defined in an alias, of course):

set cmd {show ip bgp regexp _}
append cmd $argv
append cmd {_([0-9]+)(_\1)*$}
puts [exec $cmd]

add comment

Simple extensions to exec-mode CLI

The various show filters available in Cisco IOS are a great tool to minimize the amount of printout you have to analyze, their only problem (from my perspective) is that you cannot make an alias out of them, as you usually have to supply one or more parameters to the show command and these parameters have to be inserted before the filter (and the alias command does not support replaceable parameters). You could solve the problem with Tcl shell, but I'm not sure many networking engineers are fluent Tcl programmers. Fortunately, the code you need is so simple anyone can create a working solution.
read more add comment

Continuous display of top CPU processes

When you have to monitor which processes consume router’s CPU over a period of time, a Tcl script that emulates the Unix top command might come handy. The following Tcl script continuously displays top 20 Cisco IOS processes and refreshes the update every 5 seconds.

The following text written by Ivan Pepelnjak in 2008 was originally published on CT3 wiki. That web site became unreachable in early 2019. We retrieved the original text from the Internet Archive, cleaned it up, updated it with recent information if necessary, and republished it on ipSpace.net blog on November 17, 2020

Installation

  • Download the source file into flash:top.tcl.
  • Configure alias exec top tclsh flash:top.tcl.
  • Invoke with top.

Usage guidelines

Usage: top [ 5sec | 1min | 5min ]

The script changes the escape character to Ctrl/C. Use terminal escape default to restore default settings

If anyone discovered a reliable technique that detects a keypress event (= character available on stdin) in the Tcl loop, please let me know. The Ctrl/C solution is a kludge.

Source code

#
# title:    Emulate the Unix top command
# name:     top.tcl
# desc:     The script displays top CPU processes every 5 seconds
#
# ios config:
#
#           * download the file into flash:top.tcl
#           * configure alias exec top tclsh flash:top.tcl
#
#           invoke with top [5sec|1min|5min]
#

set IOS [string equal $tcl_platform(os) "Cisco IOS"];

if { $IOS } { 
  exec "terminal international"; 
  exec "terminal escape 3";
}

set arg [lindex $argv 0];
if { [string length $arg] == 0 } { set arg "5sec" } ;
if { [lsearch -exact { 5sec 1min 5min } $arg] < 0 } {
  puts {Usage: top [5sec|1min|5min]};
  return 0;
}

fconfigure stdout -buffering none;

while {1} {
  set lines [split [exec "show process cpu sorted $arg | exclude 0.00% +0.00% +0.00%"] "\n"];

  puts -nonewline "\033\[2J\033\[H";
  for { set lc 1 } { $lc < 23 } { incr lc } {
    set curline [lindex $lines $lc];
    if { [string length $curline] > 0 } { puts "$curline"; }
  }
  puts -nonewline "\nBreak with Ctrl/C --> ";
  after 5000;
}
see 10 comments

Tcl-based IOS backdoor

Andy Davis from the Information Risk Management has written an interesting "application note": how to create a backdoor to Cisco IOS using Tclsh (I've tested it and it works quite nicely). His backdoor implementation relies on a bug in Cisco IOS that allows a "hung" (or never-ending) Tclsh script to continue executing even after the user session has been disconnected (the only means of stopping such a script is with the clear line command). That bug has been fixed in recent IOS versions (I've tested that as well, the Tclsh script is killed as soon as the Telnet session is disconnected in IOS 12.4(15)T), but you can still use the same technique (although it might be a bit less convenient) if you convert the Tclsh script into an EEM policy and trigger it periodically with a timer event.
see 2 comments

Predefine your own Tcl functions

If you want to have your own Tcl functions available when you start tclsh, you could use the scripting tcl init file configuration command that I've briefly mentioned in one of the previous posts. This command specifies a source file that is executed every time you start Tcl shell. The source file can contain function definitions, package declarations or any other Tcl code.

If you need to, you can specify multiple initialization files.

For example, if you'd like to implement a comfortable Tcl-based pinger (similar to the one Ethan Banks found in the Sadikhov forums, store the following Tcl code into the file flash:pinger.tcl

proc pinger { iplist } {
  foreach ip $iplist {
    if { [regexp "(!!!)" [exec "ping $ip timeout 1" ]] } {
      puts "$ip"
    } else { puts "$ip **** failed ***" }
  }
}
… and configure scripting tcl init flash:pinger.tcl. Now you can ping a number of hosts in a single operation:
R1#tclsh
R1(tcl)#pinger { 10.0.0.1 10.0.0.2 10.0.0.3 10.0.0.4 }
10.0.0.1
10.0.0.2
10.0.0.3 **** failed ***
10.0.0.4 **** failed ***

add comment
Sidebar