Sunday, September 30, 2012

Tcl Expect with Python - part 1

Note. highly recommended the go-to book on the subject: Exploring Expect: A Tcl-based Toolkit for Automating Interactive Programs (Nutshell Handbooks)

I have a love-hate relationship with Expect. I love what it can do for automating interactive sessions with routers, but I hate the unpredictability associated with interactive sessions. It seems that we will make a script, tweak it enough to make it work, just to do it all over again in a few months. It is just a fragile process, whenever there is a new hardware, new terminal server, or even a new OS, we will hit something that require some more fine tuning. We are much better off with known API, autoprovioning via DHCP/TFTP, or other tools for what we use Expect for today.

But until the day when all the network vendor can agree on a given set of API and hooks (if ever), Expect is still a must have in any network engineer's tool bag. It will save you time and keep you sane. 

Note that this is a post for the regular Expect based on Tcl, more on PExpect module in later post.

For those who have not heard of Expect, or want a refresher, here are a few documentation link. 
References:  
http://wiki.tcl.tk/201 (updated link on 12/29/2016)


Here is a useful link for special ASCII Characters, when you want to send those Ctrl-C, Ctrl-X, space, etc:
http://www.bbdsoft.com/ascii.html
[Update 12/29/2016, this link expired (thank you Kirk for pointing it out). Still searching for a good link]

At the heart of the game, Expect is about 1. spawing a child process, 2. look for certain words in the stream via expect, 3. send something over. 

So say I want to telnet to a bunch of Public route-servers and look at how bing.com's IP looks from the route server. I find out that bing.com resolves to 131.253.13.32 from where I am at. 

Non-authoritative answer:
Name: bing.com
Address: 131.253.13.32

From Route Server (http://traceroute.org/#Route%20Servers) I can find a list of public route servers that  I can telnet to. 

1. Using AT&T's route server as a first example, I am going to write a simple script that telnets to the box and displays the bgp information: 

#!/usr/bin/expect
set timout 5
# Spawns a child process that telnets to AT&T public route server
spawn telnet route-server.ip.att.net
# The expect command waits until the child process returns "Username:"
# then sends 'reviews' and a return key
expect "Username:" {send "rviews\r"};
# The process then sits and wait for the route-server> prompt
# Then sends the command for route 131.253.13.32 (one of the ip for bing.com)
expect "route-server>" {send "show ip bgp 131.253.13.32\r"};
# The output is longer than 1 page, so it sendds the ASCII key for space twice
send "\032\032"
# Looks for the same prompt, then types 'exit'
expect "route-server>" {send exit\r};
2. The first script can now be cron'd and give me the information that I wanted. But what if I want to telnet to a bunch of route-server, not just AT&T? Here is a modified version that uses the first argument as the telnet target: 

#!/usr/bin/expect
set routeServ [lindex $argv 0]
set timout 20
spawn telnet $routeServ
expect "Username:" {send "rviews\r"};
expect {
  -re "route.*" {send "show ip bgp 131.253.13.32\r"};
}
send "\032\032"
expect {
  -re "route.*" {send exit\r};
}

Notice that the script also now uses -re to indicate the expected string back will be regular expression. This is because some of the route server prompt are 'route-server>' as in the case of AT&T and Comcast, while some are 'route-view>', as in the case of Oregon Route-View servers. 

Now that is just another script that I can use Python to tie in, reading from a list of route-servers:

$cat route-servers 
route-server.ip.att.net 
route-server.newyork.ny.ibone.comcast.net
route-views.oregon-ix.net
$

Here is the Python script: 

#!/usr/bin/env python
import subprocess
f = open("route-servers", "r")
for i in f.readlines():
subprocess.check_call(["./routeServ_2.exp", i.strip()])


3. Now we are getting somewhere, but there is another problem: the command is embedded into the Expect script itself. So if I want to run more commands, I need to modify the expect script itself. That is not very flexible. I will make one more modification to the Expect script itself so that it reads all the command from another text file: 

$cat commands.txt 
show ip route 131.253.13.32
show ip bgp 131.253.13.32

Here is the new version of the Expect script. Note that 'timeout -1' disables the default timeout of 10 seconds, as some of the route-servers are pretty slow. 

#!/usr/bin/expect
set routeServ [lindex $argv 0]
# disable the timeout value in the next line
# since some of the route-servers are really slow
set timout -1

spawn telnet $routeServ
expect "Username:" {send "rviews\r"};
expect {
  -re "route-.*" {send \r}
}
#process the command file
#this can be more elegant, but this is more clear
set file [open "commands.txt" r]
set file_data [read $file]
set command [split $file_data "\n"]
foreach line $command {
  puts $line
  expect {
    -re "route-.*" {send $line\r}
    continue
  }
  send "\032\032\032"
  sleep 2
}

Now I will tie it in with Python, there is no change to the Python script except that it calls the new Expect script. By using Python, I can leverage the getpass, subprocess, sys, and other standard libraries and not have to deal with Tcl or Expect. 

#!/usr/bin/env python
import subprocess
f = open("route-servers", "r")
for i in f.readlines():
    subprocess.check_call(["./routeServ_3.exp", i.strip()])

4. Here is the output of the result as it shows on the screen (Comcast route server does not allow for 'show ip route x.x.x.x', boo):

$./routeServ_3.py 
spawn telnet route-server.ip.att.net
Trying 12.0.1.28...
Connected to route-server.cbbtier3.att.net.
Escape character is '^]'.

-------------- route-server.ip.att.net ---------------
---------  AT&T IP Services Route Monitor  -----------

The information available through route-server.ip.att.net is offered
by AT&T's Internet engineering organization to the Internet community.

This router maintains eBGP peerings with customer-facing routers
throughout the AT&T IP Services Backbone:

IPv4:
 12.123.21.243  Atlanta   12.123.133.124 Austin    12.123.41.250  Cambridge
 12.123.5.240   Chicago   12.123.17.244  Dallas    12.123.139.124 Detroit
 12.123.37.250  Denver    12.123.134.124 Houston   12.123.29.249  LA
 12.123.1.236   New York  12.123.33.249  Orlando   12.123.137.124 Philly
 12.123.142.124 Phoenix   12.123.145.124 SanDiego  12.123.13.241  SanFran
 12.123.25.245  St.Louis  12.123.45.252  Seattle   12.123.9.241   WashDC

IPv6:
 2001:1890:FF:FFFF:12:122:124:12   Atlanta
 2001:1890:FF:FFFF:12:122:127:66   Chicago
 2001:1890:FF:FFFF:12:122:124:138  Dallas
 2001:1890:FF:FFFF:12:122:120:7    Fort Lauderdale
 2001:1890:FF:FFFF:12:122:125:6    Los Angeles
 2001:1890:FF:FFFF:12:122:125:44   New York
 2001:1890:FF:FFFF:12:122:125:106  Philadelphia
 2001:1890:FF:FFFF:12:122:125:132  Phoenix
 2001:1890:FF:FFFF:12:122:126:232  San Francisco
 2001:1890:FF:FFFF:12:122:125:224  Seattle
 2001:1890:FF:FFFF:12:122:126:9    St. Louis
 2001:1890:FF:FFFF:12:122:126:64   Washington

*** Please Note:
Ping and traceroute delay figures measured here are unreliable, due to the
high CPU load experienced when complicated "show" commands are running.

For questions about this route-server, send email to: jayb@att.com

*** Log in with username "rviews", no password required ***


User Access Verification

Username: Kerberos: No default realm defined for Kerberos!
rviews
route-server>show ip route 131.253.13.32

route-server>show ip bgp 131.253.13.32
show ip route 131.253.13.32
Routing entry for 131.253.12.0/22
  Known via "bgp 65000", distance 20, metric 0
  Tag 7018, type external
  Last update from 12.123.1.236 22:16:35 ago
  Routing Descriptor Blocks:
  * 12.123.1.236, from 12.123.1.236, 22:16:35 ago
      Route metric is 0, traffic share count is 1
      AS Hops 3
      Route tag 7018

route-server>
show ip bgp 131.253.13.32
BGP routing table entry for 131.253.12.0/22, version 36560492
Paths: (18 available, best #2, table Default-IP-Routing-Table)
  Not advertised to any peer
  7018 8075 8075, (received & used)
    12.123.13.241 from 12.123.13.241 (12.123.13.241)
      Origin IGP, localpref 100, valid, external
      Community: 7018:2500 7018:32113
  7018 8075 8075, (received & used)
    12.123.1.236 from 12.123.1.236 (12.123.1.236)
      Origin IGP, localpref 100, valid, external, best
      Community: 7018:2500 7018:38001
  7018 8075 8075, (received & used)
    12.123.142.124 from 12.123.142.124 (12.123.142.124)
      Origin IGP, localpref 100, valid, external
      Community: 7018:2500 7018:33051
  7018 8075 8075, (received & used)
    12.123.133.124 from 12.123.133.124 (12.123.133.124)
      Origin IGP, localpref 100, valid, external
      Community: 7018:2500 7018:36244
  7018 8075 8075, (received & used)
    12.123.17.244 from 12.123.17.244 (12.123.17.244)
      Origin IGP, localpref 100, valid, external
      Community: 7018:2500 7018:36244
  7018 8075 8075, (received & used)
    12.123.21.243 from 12.123.21.243 (12.123.21.243)
      Origin IGP, localpref 100, valid, external
      Community: 7018:2500 7018:36244
  7018 8075 8075, (received & used)
    12.123.137.124 from 12.123.137.124 (12.123.137.124)
      Origin IGP, localpref 100, valid, external
      Community: 7018:2500 7018:38001
  7018 8075 8075, (received & used)
    12.123.41.250 from 12.123.41.250 (12.123.41.250)
      Origin IGP, localpref 100, valid, external
      Community: 7018:2500 7018:38001
  7018 8075 8075, (received & used)
    12.123.139.124 from 12.123.139.124 (12.123.139.124)
          
route-server>spawn telnet route-server.newyork.ny.ibone.comcast.net
Trying 66.208.229.1...
Connected to route-server.newyork.ny.ibone.comcast.net.
Escape character is '^]'.

********************************************************************************
                       Comcast NETO Route Server
        This route server is provided by Comcast National Engineering &
Technical Operations to provide visibility into the Internet routing table
from the perspective of Comcast's network.  This server has BGP peerings to
the following routers and the Internet routing table view from each device:

68.86.80.0 - Ashburn, VA
68.86.80.2 - New York, NY
68.86.80.5 - Chicago, IL
68.86.80.6 - Chicago, IL 2
68.86.80.7 - Denver, CO
68.86.80.10 - San Jose, CA
68.86.80.11 - Los Angeles, CA
68.86.80.12 - Atlanta, GA
68.86.80.13 - Dallas, TX
          

Note: Due to high CPU utilization on this device, ping and traceroute results
may be unreliable.  This route server should not be used to measure network
performance as a result.

Login with username: rviews

Location:   New York City
Network:  Comcast Route Server
********************************************************************************

User Access Verification

Username: Kerberos: No default realm defined for Kerberos!
rviews
route-server.newyork.ny.ibone>show ip route 131.253.13.32

route-server.newyork.ny.ibone>show ip bgp 131.253.13.32
show ip route 131.253.13.32
Command authorization failed.

route-server.newyork.ny.ibone>
show ip bgp 131.253.13.32
BGP routing table entry for 131.253.12.0/22, version 1378467282
Paths: (8 available, best #8, table Default-IP-Routing-Table)
  Advertised to update-groups:
     2         
  8075 8075, (received & used)
    68.86.1.38 (metric 81950) from 68.86.80.11 (68.86.1.11)
      Origin IGP, metric 0, localpref 300, valid, internal
      Community: 7922:38 7922:3020
      Originator: 68.86.1.38, Cluster list: 68.86.1.11, 68.86.1.10
  8075 8075, (received & used)
    68.86.1.75 (metric 74391) from 68.86.80.13 (68.86.1.13)
      Origin IGP, metric 0, localpref 300, valid, internal
      Community: 7922:75 7922:3020
      Originator: 68.86.1.75, Cluster list: 68.86.1.13, 68.86.1.7
  8075 8075, (received & used)
    68.86.1.40 (metric 69750) from 68.86.80.12 (68.86.1.12)
      Origin IGP, metric 0, localpref 300, valid, internal
      Community: 7922:40 7922:3020
      Originator: 68.86.1.40, Cluster list: 68.86.1.12
  8075 8075, (received & used)
    68.86.1.42 (metric 66845) from 68.86.80.0 (68.86.1.0)
      Origin IGP, metric 0, localpref 300, valid, internal
      Community: 7922:42 7922:3020
      Originator: 68.86.1.42, Cluster list: 68.86.1.0
  8075 8075, (received & used)
    68.86.1.38 (metric 81950) from 68.86.80.10 (68.86.1.10)
      Origin IGP, metric 0, localpref 300, valid, internal
      Community: 7922:38 7922:3020
      Originator: 68.86.1.38, Cluster list: 68.86.1.10
  8075 8075, (received & used)
    68.86.1.43 (metric 69675) from 68.86.80.6 (68.86.1.6)
      Origin IGP, metric 0, localpref 300, valid, internal
      Community: 7922:43 7922:3020
      Originator: 68.86.1.43, Cluster list: 68.86.1.6
  8075 8075, (received & used)
    68.86.1.75 (metric 74391) from 68.86.80.7 (68.86.1.7)
      Origin IGP, metric 0, localpref 300, valid, internal
          
route-server.newyork.ny.ibone>spawn telnet route-views.oregon-ix.net
Trying 128.223.51.103...
Connected to route-views.oregon-ix.net.
Escape character is '^]'.

 **********************************************************************

                    Oregon Exchange BGP Route Viewer
          route-views.oregon-ix.net / route-views.routeviews.org

 route views data is archived on http://archive.routeviews.org

 This hardware is part of a grant from Cisco Systems.
 Please contact help@routeviews.org if you have questions or
 comments about this service, its use, or if you might be able to
 contribute your view.

 This router has views of the full routing tables from several ASes.
 The list of ASes is documented under "Current Participants" on
 http://www.routeviews.org/.

                          **************

 route-views.routeviews.org is now using AAA for logins.  Login with
 username "rviews".  See http://routeviews.org/aaa.html

 **********************************************************************


User Access Verification

Username: rviews
route-views>show ip route 131.253.13.32

route-views>show ip bgp 131.253.13.32
show ip route 131.253.13.32
Routing entry for 131.253.12.0/22
  Known via "bgp 6447", distance 20, metric 0
  Tag 8075, type external
  Last update from 207.46.32.34 7w0d ago
  Routing Descriptor Blocks:
  * 207.46.32.34, from 207.46.32.34, 7w0d ago
      Route metric is 0, traffic share count is 1
      AS Hops 1
      Route tag 8075

route-views>
show ip bgp 131.253.13.32
BGP routing table entry for 131.253.12.0/22, version 215869
Paths: (33 available, best #20, table Default-IP-Routing-Table)
  Not advertised to any peer
  3277 3267 8075 8075
    194.85.102.33 from 194.85.102.33 (194.85.4.4)
      Origin IGP, localpref 100, valid, external
      Community: 3277:3267 3277:65100 3277:65320 3277:65326 3277:65330
  3333 3356 8075 8075
    193.0.0.56 from 193.0.0.56 (193.0.0.56)
      Origin IGP, localpref 100, valid, external
  3561 3356 8075 8075
    206.24.210.102 from 206.24.210.102 (206.24.210.102)
      Origin IGP, localpref 100, valid, external
  3356 8075 8075
    4.69.184.193 from 4.69.184.193 (4.69.184.193)
      Origin IGP, metric 0, localpref 100, valid, external
      Community: 209:80 209:888 701:30 703:30 1120:2 1299:2869 1299:5509 1299:5583 2914:470 3356:3 3356:22 3356:100 3356:123 3356:575 3356:2012 3549:200 3549:666 4657:905 5511:1550 6453:80 6762:20008 7018:80 7473:22009 7473:23009 10026:11010 10026:11020 10026:11030 10026:11040 10026:11050 10026:11060 10026:11070 10026:11080 10026:11090 10026:11100 25160:209 25160:1239 65009:7018
  2914 3356 8075 8075
    129.250.0.11 from 129.250.0.11 (129.250.0.12)
      Origin IGP, metric 6, localpref 100, valid, external
      Community: 2914:420 2914:1008 2914:2000 2914:3000 65504:3356
  19214 12989 8075 8075
    208.74.64.40 from 208.74.64.40 (208.74.64.40)
      Origin IGP, localpref 100, valid, external
  101 101 8075 8075
    209.124.176.223 from 209.124.176.223 (209.124.176.223)
      Origin IGP, localpref 100, valid, external
      Community: 101:20400 101:22200
      Extended Community: RT:101:22200
  6939 8075 8075
    216.218.252.164 from 216.218.252.164 (216.218.252.164)
      Origin IGP, localpref 100, valid, external
  1239 3356 8075 8075
    144.228.241.130 from 144.228.241.130 (144.228.241.130)
          
route-views>$





7 comments:

  1. Hey Thanks for this Eric. Your Python posts have inspired me to start scripting (hacking) more like a bit more in work.

    ReplyDelete
  2. Thanks for this interesting post. Its really helpful. I like to ask you what one can do to be able to use expect to hop from one server to a device just like when hoping thru a Cisco terminal server to access a device thru console. But in this case the Cisco terminal server is a RedHat server.
    Linux Server(Telnet) --> Linux Server(Telnet) --> Device
    Does one spawn another telnet child process on the second Linux Server?
    If that is the case how can one achieve this.
    I will Appreciate your suggestions and help.

    ReplyDelete
    Replies
    1. Thanks for reading and the kind words. Have you tried using Subprocess to spun off Minicom or other serial programs? You might have to alter the program default with command line options to go directly into the program itself.

      Another option that is more scalable is to use a Linux-based terminal server. Cisco TS has limited configuration options in serial line and with human in mind, so sometimes you are stuck. I am not affiliated with Digi and you are free to use anything, they make a resonably priced TS such as https://www.digi.com/products/consoleservers/digicm#overview.

      As a Linux-based server, the options are wide open for you.

      Let me know what you think.

      Delete
  3. You can try pyexpect, it's very easy to use and has the same basic functionalities as expet has but in pure python.

    @Eric Chou: You can also use pyserial for that porpuse.

    ReplyDelete
    Replies
    1. Ah, good point. I have used it with Arduino for serial connection but forgot about that here. Thanks!

      Delete
    2. You know, I had a chance to do this earlier, using Raspberry Pi with /dev/ttyUSB0 Keyspan serial-to-usb adapter to console in to a switch. Minicom works, serial.Serial().readline() works when I power cycle the device but the loop I put up for continuously reading the input didnt work.

      Minicom works, so I know the serial cable is good. But I ended up just resorting back to PExpect instead of using PySerial. Personally I think PySerial is better if you need a serial connection that reads one or a few lines at a time; for router provisioning as a continuous interactive session PExpect/Expect is still a better option.

      Delete