Automating the Console using pySerial
By Kirk Byers
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.
If you want to learn more about network automation, Python, and Ansible—then join my email-list. I also periodically run a free Python for Network Engineers email course which you can sign-up for here.
CCIE #6243 emeritus