Automating the Console using

pySerial

Author: Kirk Byers
Date: 2015-10-15

You have a brand new router and you want to fully automate the configuration process?

Now once the device is on the network, then you can configure it using SSH or using an API. But you still might need a minimal starting configuration to safely add the device to the network. For example, disable any DHCP server, configure SSH, add a user, etc.

There are obviously "zero touch" ways to accomplish this like Cisco's POAP or Arista's ZTP. But for a quick and easy solution--why can't you just programmatically configure the device using the serial console connection?

You can using pySerial.

Let's do some experimentation.

First, I am working on an old Windows machine that has Python 2.7.6 installed. I also have installed pySerial. The first thing I need to do is establish a serial connection. After some experimentation I was able to do the following:

>>> import serial
>>>
>>> console = serial.Serial(
...         port='COM1',
...         baudrate=9600,
...         parity="N",
...         stopbits=1,
...         bytesize=8,
...         timeout=8
...
)

I can then verify that the serial port is open using the isOpen() method.

>>> console.isOpen()
True

At this point, I need to write and read from the serial port. Let's try sending a newline down the channel (note, this is a Windows-style newline)

>>> console.write("\r\n")
2L

Now let's check the inWaiting() method. inWaiting() will tell us the number of bytes ready to be read.

>>> console.inWaiting()
225L

We see there are 225 bytes available. Let's read them

>>> input_data = console.read(225)
>>> print input_data


User Access Verification

Username:
% Username:  timeout expired!

There was more on the screen, but you get the picture.

As you can see we are able to send data to the console and read from it. Now let's create a Python script and handle the login process. In order to do this, I will need to send a newline, wait for a second, and then read the data. If 'Username' is present in the input data, then I can proceed.

Here is a crude script to accomplish this:

import serial
import sys
import time

import credentials

READ_TIMEOUT = 8


def main():

    print "\nInitializing serial connection"

    console = serial.Serial(
        port='COM1',
        baudrate=9600,
        parity="N",
        stopbits=1,
        bytesize=8,
        timeout=READ_TIMEOUT
    )

    if not console.isOpen():
        sys.exit()

    console.write("\r\n\r\n")
    time.sleep(1)
    input_data = console.read(console.inWaiting())
    print input_data
    if 'Username' in input_data:
        console.write(credentials.username + '\r\n')
    time.sleep(1)
    input_data = console.read(console.inWaiting())

if __name__ == "__main__":
    main()

Note, I have stored the username and password in an external file called credentials.py.

At this point if I run the script I receive the following back:

$ python serial1.py

Initializing serial connection


User Access Verification

Username:

Note, I do not see the 'Username:' prompt consistently when I run this script. This is due to the behavior of the Cisco router--it will show 'Username:' prompt three times; then it will delay for about 3 seconds; and then it will show me a message that 'router_name con0 is now available'.

Even with that problem let's proceed and try to send the password down the channel.

import serial
import sys
import time

import credentials

READ_TIMEOUT = 8


def main():

    print "\nInitializing serial connection"

    console = serial.Serial(
        port='COM1',
        baudrate=9600,
        parity="N",
        stopbits=1,
        bytesize=8,
        timeout=READ_TIMEOUT
    )

    if not console.isOpen():
        sys.exit()

    console.write("\r\n\r\n")
    time.sleep(1)
    input_data = console.read(console.inWaiting())
    if 'Username' in input_data:
        console.write(credentials.username + '\r\n')
    time.sleep(1)
    input_data = console.read(console.inWaiting())
    if 'Password' in input_data:
        console.write(credentials.password + '\r\n')
    time.sleep(1)
    input_data = console.read(console.inWaiting())
    print input_data


if __name__ == "__main__":
    main()

As you can see I am now logging in properly:

$ python serial2.py

Initializing serial connection


pynet-rtr1>
pynet-rtr1>

Now the script (as written above) has some significant reliability issues--but we are able to send and receive data; we are also able to login to the router.

I have expanded on this script here. Now this program is still pretty rough, but it better handles some of the issues. Here you can see the script being executed:

$ python cisco_serial.py

Initializing serial connection
Logging into router
We are logged in

show ip int brief
Interface          IP-Address      OK? Method Status    Protocol
FastEthernet0      unassigned      YES unset  down      down
FastEthernet1      unassigned      YES unset  down      down
FastEthernet2      unassigned      YES unset  down      down
FastEthernet3      unassigned      YES unset  down      down
FastEthernet4      10.220.88.20    YES NVRAM  up        up
Vlan1              unassigned      YES unset  down      down
pynet-rtr1>
pynet-rtr1>
Logging out from router
Successfully logged out from router

Note, I have added the command 'show ip int brief' to the script.

Hopefully, you can see that using pySerial to configure a router via its console port is not too complicated. There are a few more steps that we would need to take to bootrap a router onto the network (entering enable mode, entering configuration mode, executing our changes). But accomplishing this should be fairly straight-forward.

Kirk Byers

@kirkbyers

You might also be interested in: