Netmiko is a “Multi-vendor library to simplify Paramiko SSH connections to network devices”. It is written an maintained by Kirk Byers and is widely used by the python community and Python modules alike. Once such module is NTC Ansible which I’ve posted about previously and will post about again in the near future.
Before we start dissecting Kirk’s code though, I’d like to give both him and Gabriele Gerbino a shout out for all their assistance with my Python and Ansible queries. Both of these gentlemen have been invaluable for my studies and I thank them for it.
Follow the Bouncing BallNote:As time goes by, Netmiko’s code will be changed. The code which is being analysed in this series of posts can be found here.
The Netmiko README filegives us an example of how it can be used to obtain a ‘show’ command output from a Cisco router. After adapting this example to suit my lab environment, it looks like this:
from netmiko import ConnectHandlercisco_gns3 = {
'device_type': 'cisco_ios',
'ip': '10.255.0.254',
'username': 'cisco',
'password': 'cisco',
'secret': 'cisco', # optional, defaults to ''
'verbose': False, # optional, defaults to False
}
net_connect = ConnectHandler(**cisco_gns3)
output = net_connect.send_command('show ip int brief')
print(output)
Running this codes provides the following output:
(new_venv) will@ubuntu:~/all/netmiko$ python ../nettest.pyInterface IP-Address OK? Method Status Protocol
FastEthernet0/0 10.255.0.254 YES NVRAM up up
FastEthernet0/1 10.255.2.1 YES NVRAM up up
FastEthernet1/0 10.255.3.1 YES NVRAM up up
FastEthernet2/0 unassigned YES NVRAM up up
FastEthernet3/0 unassigned YES NVRAM up up
FastEthernet4/0 unassigned YES unset up down
FastEthernet4/1 unassigned YES unset up down
FastEthernet4/2 unassigned YES unset up down
FastEthernet4/3 unassigned YES unset up down
FastEthernet4/4 unassigned YES unset up down
FastEthernet4/5 unassigned YES unset up down
FastEthernet4/6 unassigned YES unset up down
FastEthernet4/7 unassigned YES unset up down
FastEthernet4/8 unassigned YES unset up down
FastEthernet4/9 unassigned YES unset up down
FastEthernet4/10 unassigned YES unset up down
FastEthernet4/11 unassigned YES unset up down
FastEthernet4/12 unassigned YES unset up down
FastEthernet4/13 unassigned YES unset up down
FastEthernet4/14 unassigned YES unset up down
FastEthernet4/15 unassigned YES unset up down
Vlan1 unassigned YES NVRAM up down
Now that we know it works, let’s see what Netmiko is actually doing but where do we start? Well, given that we’re using a function called ConnectHandler , let’s start looking there.
ssh_dispatcher.pyConnectHanldercan be found in ssh_dispatcher.py , however, instead of jumping straight to the ConnectHandler section, let’s approach this file in a top down approach.
First we see a bunch of imports, a small subset of which are below:
from netmiko.cisco import CiscoIosSSHfrom netmiko.arista import AristaSSH
from netmiko.huawei import HuaweiSSH
from netmiko.f5 import F5LtmSSH
from netmiko.juniper import JuniperSSH
Whatthese imports do is make these classes (CiscoIosSSH, AristaSSH, HuaweiSSH, etc which can be found in the vendor directories ) available tothe code in this file.
If you’re wondering why one would want to split the code up into different files when this file (ssh_dispatcher.py) needs access to it all, it’s becausesplitting it up makse the code more modular and easier to manage than it would be if everything were contained in a single file.
Next we see a CLASS_MAPPER_BASE dictionary which contains key:value pairs, some of which are listed below:
CLASS_MAPPER_BASE = {'cisco_ios': CiscoIosSSH,
'huawei': HuaweiSSH,
'f5_ltm': F5LtmSSH,
'juniper': JuniperSSH,
'arista_eos': AristaSSH,
}
Notice how the values in the CLASS_MAPPER_BASE match class imports we saw earlier? Also notice how the ‘cisco_ios’ key matches the ‘device_type’ code at the start of this post? As you might have guessed, thesebycoincidence. Let’s keep dissecting and see what we find…
The next block of code we come across is this:
# Also support keys that end in _sshnew_mapper = {}
for k, v in CLASS_MAPPER_BASE.items():
new_mapper[k] = v
alt_key = k + u"_ssh"
new_mapper[alt_key] = v
CLASS_MAPPER = new_mapper
# Add telnet drivers
CLASS_MAPPER['cisco_ios_telnet'] = CiscoIosTelnet
What this code does is it takes the CLASS_MAPPER_BASE dictionary we saw earlier and does the following:
Adds the contents of CLASS_MAPPER_BASE to new_mapper . Adds a _ssh suffix to CLASS_MAPPER_BASE keys and adds them to new_mapper .The above results in new_mapper being twice the size of CLASS_MAPPER_BASE . The reason being that contains both an ‘original’ key and new ‘_ssh’ key for each entry. For example:
'cisco_ios': CiscoIosSSH,'cisco_ios_ssh': CiscoIosSSH,
'huawei': HuaweiSSH,
'huawei_ssh': HuaweiSSH The code then saves the new_mapper dictionary as CLASS_MAPPER and adds a new ‘cisco_ios_telnet’:Cisc