Scripting the WLC

Update: This post has been updated, please see Scripting SSH to network devices.

Automation, software defined networks, and network programmability are hot topics in the world of networking. Numerous platforms and architectures exist to facilitate programmatic interaction with the network, from direct API access to individual components, up through the various layers of abstraction offered by numerous management platforms and software controllers.

Today we will be looking at interacting with Cisco’s wireless controller directly using Python. Unfortunately, unlike certain other platforms, the WLC does not yet offer an API with which we can interact. This will no doubt be rectified in the (hopefully) not too distant future, however at the time of writing the available methods for interacting with a WLC are as follows:

HTTP/HTTPS
Telnet/SSH
SNMP

Obviously graphical access using HTTP/HTTPS does not lend itself well to this task so we won’t be considering it here. We will also park the SNMP idea for now, although this is relatively feasible approach.

For today, let’s focus on some good ol’ screen scraping using Telnet/SSH. Specifically, SSH, as it makes more sense, for numerous reasons. Firstly, Telnet is cleartext and has been considered insecure and a bad idea for ages. Most well-managed devices will not even have this enabled. Telnet also requires expect-style programming which can be tedious (as we will see later), and lastly, the default Python telnetlib seems to not play well with the Telnet implementation on the WLC. Which brings us neatly to SSH.

Before we continue it is worthwhile to note that depending on the task at hand it may be easier to simply parse the show run-config output of the WLC from a text file, or use one of the existing tools that do this. We are doing it this way, well, because we can..

To make this easier we’ll use the Paramiko SSH libraries for Python. The lab setup is straightforward, just a WLC reachable on the network (vWLC is this case) and a Python 3 interpreter.

Let’s start with some code.

import time, paramiko
wlc_session = paramiko.SSHClient()
wlc_session.set_missing_host_key_policy(paramiko.AutoAddPolicy())
wlc_session.connect('192.168.6.21', port='22', username='null', password='null')

Above we initiated the Paramiko SSH client session, added a policy to accept self-signed certificates, and started the connection to the WLC.
The ‘null’ usernames and passwords are not a mistake, as there is no need to pass the real credentials to the WLC during this step.
The reason for this is the familiar double login prompt presented by the WLC, as shown below.

This deviates somewhat from a standard SSH implementation, and causes Paramiko to fail when the credentials are passed along with the original connect request. More specifically, the credentials are ignored which means the session is not authenticated and closes when we try to pass further commands. See CSCve45024 for more details.
To work around this we need to switch into an interactive shell and pass the credentials manually.

wlc_ssh_class = wlc_session.invoke_shell()
time.sleep(0.1)
wlc_ssh_class.send('admin'+'\n')
time.sleep(0.1)
wlc_ssh_class.send('password'+'\n')
time.sleep(0.1)
wlc_ssh_class.send('config paging disable'+'\n')
time.sleep(0.1)
strip_login_text = wlc_ssh_class.recv(1024).decode('utf-8')

After the shell is invoked we pass the username, password, and disable config paging. We add a wait timer after each input to give the WLC time to respond, the duration will need to be increased for slower systems and/or larger outputs. The last command reads back the information (1024 bytes) from the buffer, effectively just ‘clearing the screen’. Not elegant, but necessary.

After the connection setup process the rest is easy, we just need to remember to increase the read buffer and wait time for larger outputs. The ‘backslashreplace’ function prevents the script from crashing on decode failures of any unrecognised characters.

wlc_ssh_class.send('show sysinfo'+'\n')
time.sleep(0.1)
output = wlc_ssh_class.recv(2048).decode('utf-8', 'backslashreplace')

And the resulting output:

That’s about it. From here we can manipulate the output using standard Python string operations as required.

As we can see the process of interacting with the WLC is not all that complicated, but also not the most efficient. The largest drawback is the need to use interactive shell mode and adjust the wait timers and receive buffer sizes according to the expected output, which requires prior knowledge of the expected output and doesn’t make for a very modular program. As an example, our test system required 75 seconds to return the full configuration! Of course this is no longer that it would take using a standard terminal emulator, except you don’t need to know the exact size and duration up front. This method is suitable for smaller, specific outputs, and configuration changes.

This demo was written using Python 3.6.3 and Paramiko 2.4.0, the complete script (as well as others) are available on GitHub.