Python - Netmiko (IX)

Finally, we've arrived at an exciting part of our journey, working directly with Network Devices. In this section, we'll dive into Netmiko, the most popular library for interacting with network devices via Python. Netmiko simplifies the process of connecting to, managing, and automating network devices from various manufacturers.

Throughout this part of the course, we will explore what Netmiko is, how to install it, and how to use it. You'll learn through practical examples how to leverage Netmiko to send commands to your devices, retrieve outputs, and automate routine network tasks. This will not only boost your efficiency but also open up new possibilities for network management and automation.

Installation and Basic Use of Netmiko

In the previous section, we discussed installing external modules so, installing netmiko is as simple as running pip install netmiko. Once installed, you're ready to start scripting and interacting with your network devices. Below is a basic example that uses Netmiko to SSH into a Cisco router and execute a single show command.

from netmiko import ConnectHandler

connection = ConnectHandler(host='10.10.20.17', port='22', username='cisco', password='cisco123', device_type='cisco_ios')

output = connection.send_command('show ip interface brief')
print(output)

print('Closing Connection')
connection.disconnect()
  • ConnectHandler - This is a part of Netmiko and acts as a bridge between your script and the network device. It takes several parameters to establish the connection.
    • host - The IP address of the device you want to connect to.
    • port - The port number for the SSH connection (22 is the default for SSH).
    • username & password - The credentials required to access the device.
    • device_type - Specifies the type of device (e.g., cisco_ios for Cisco IOS devices). Netmiko uses this to understand how to interact with the device correctly.
  • send_command - Once the connection is established, send_command method is used to send a command to the device. Here, we're sending 'show ip interface brief'
  • Closing the Connection - It's important to close the connection after you're done with it. connection.disconnect() ensures the session is properly terminated.

Here is the output when you run the script

C:\Users\suresh\Projects\>python netmiko_01.py
Interface                  IP-Address      OK? Method Status                Protocol
GigabitEthernet0/0         10.10.20.17     YES manual up                    up
GigabitEthernet0/1         unassigned      YES unset  administratively down down
GigabitEthernet0/2         unassigned      YES unset  administratively down down
GigabitEthernet0/3         unassigned      YES unset  administratively down down
Closing Connection

What Exactly is ConnectHandler?

Though classes and object-oriented programming (OOP) are beyond this section's scope, it's helpful to know that ConnectHandler is a class. In simple terms, a class in programming defines a blueprint for creating objects. An object, such as connection in our script, is an instance of a class and can perform the actions (methods) defined in the class, like send_command or disconnect. Think of the class as a template and objects as specific instances that follow that template, each with its own data and behaviour.

Connecting Using a Dictionary

If you recall, I emphasized how crucial lists and dictionaries are as data types in Python. Now, we're going to see dictionaries in action and understand their value in our scripts.

In this second example with Netmiko, we've used a dictionary, cisco_01, to store connection details for a Cisco device. This method offers a clear advantage, simplicity and organization. Instead of passing connection parameters directly into the ConnectHandler method call, we group them in a dictionary.

from netmiko import ConnectHandler

cisco_01 = {
    "device_type": "cisco_ios",
    "host": "10.10.20.17",
    "username": "cisco",
    "password": "cisco123"
}

connection = ConnectHandler(**cisco_01) # unpacking the dictionary

output = connection.send_command('show interface desc')
print(output)

print('Closing Connection')
connection.disconnect()

Using the dictionary and unpacking it with **cisco_01 when calling ConnectHandler is an elegant way to pass multiple keyword arguments.

Sending Multiple Commands

In this section, we'll explore how to send multiple commands to a network device using Netmiko. This process highlights the practical use of lists and for loops, powerful tools we've learnt earlier.

from netmiko import ConnectHandler

cisco_01 = {
    "device_type": "cisco_ios",
    "host": "10.10.20.17",
    "username": "cisco",
    "password": "cisco123",
    "secret": "cisco123" # Enable password
}

connection = ConnectHandler(**cisco_01)
connection.enable() # Go to Priv EXEC mode

show_commands = ['show ip route', 'show interface desc', 'show clock'] # List of commands to send

for command in show_commands: # for loop
    print(f'\n *** Sending { command} *** \n')
    output = connection.send_command(command)
    print(output)

connection.disconnect()
  • We start by importing ConnectHandler from Netmiko and defining a dictionary (cisco_01) with the necessary details to connect to our Cisco device.
  • Using the ConnectHandler and the unpacked cisco_01 dictionary, we establish a connection to the device.
  • We then enter privileged EXEC mode using connection.enable().
  • The commands we intend to send are stored in a list named show_commands. This is where lists come in handy, allowing us to organize multiple commands easily.
  • A for loop iterates through each command in the show_commands list. For each command,
    • Print a message indicating which command is being sent.
    • Use connection.send_command(command) to send the command to the device and store the output.
    • Print the output of the command.
  • Finally, we ensure to properly disconnect from the device using connection.disconnect().
#output

 *** Sending show ip route ***

Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
       ia - IS-IS inter area, * - candidate default, U - per-user static route
       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP
       a - application route
       + - replicated route, % - next hop override

Gateway of last resort is not set

      10.0.0.0/8 is variably subnetted, 2 subnets, 2 masks
C        10.10.0.0/16 is directly connected, GigabitEthernet0/0
L        10.10.20.17/32 is directly connected, GigabitEthernet0/0

 *** Sending show interface desc ***

Interface                      Status         Protocol Description
Gi0/0                          up             up       MANAGEMENT
Gi0/1                          admin down     down
Gi0/2                          admin down     down
Gi0/3                          admin down     down

 *** Sending show clock ***

*09:48:45.139 UTC Wed Apr 13 2022

Securing Passwords with Getpass

So far in our examples, we've stored passwords directly in our scripts as plain text, which is not a secure practice, especially in production environments. To enhance security, Python provides a helpful module named getpass, which allows for more secure password handling by prompting the user to enter their password at runtime without displaying it on the screen.

getpass works by presenting a password prompt to the user and then reading the input without echoing it back to the console. This means that passwords are not visible to bystanders, nor are they stored in the script.

from netmiko import ConnectHandler
import getpass

passwd = getpass.getpass('Please enter the password: ') # Reads the output from the user and save it as a string

cisco_01 = {
    "device_type": "cisco_ios",
    "host": "10.10.20.17",
    "username": "cisco",
    "password": passwd, # Log in password from getpass
    "secret": passwd # Enable password from getpass
}

connection = ConnectHandler(**cisco_01)
connection.enable() # Go to Priv EXEC mode

output = connection.send_command('show interface desc')
print(output)

connection.disconnect()

In this example, when the script is run, getpass.getpass('Please enter the password: ') displays the prompt "Please enter the password: ". The user's input is then stored in the passwd variable without showing any characters the user types. This password is used to log into the device and for entering privileged EXEC mode. The rest of the script functions as before, establishing a connection to the network device, executing a command, and then disconnecting.

Sending Configuration Command

First, we're starting with the basics by sending a single configuration command using Netmiko. To keep things simple, we'll configure an NTP server on a device. The command we use for this is ntp server x.x.x.x, where x.x.x.x is the IP address of the NTP server. Here's a quick run-through of how to do this with Netmiko.

from netmiko import ConnectHandler

username = 'admin'
password = 'Cisco123'

device = {
        "device_type": "cisco_ios",
        "host": '10.10.10.10',
        "username": username,
        "password": password,
        "secret": password
}

commands = ['ntp server 1.1.1.1']
connection = ConnectHandler(**device)
output = connection.send_config_set(commands)
print(output)

connection.disconnect()

Here, the first part of the code remains the same but the difference is that we specify the configuration command we want to send to the device in a list. Here, our command is ['ntp server 1.1.1.1']. With the connection established, we send our configuration command using the send_config_set method. This method takes our list of commands and applies them to the device.

Here is the output where you can see the send_config_set method automatically enters configure terminal mode, execute the command and then exits from the config mode.

configure terminal
Enter configuration commands, one per line.  End with CNTL/Z.
c9300(config)#ntp server 1.1.1.1
c9300(config)#end
c9300#

You can also send multiple commands by adding the commands to the list as shown below.

from netmiko import ConnectHandler

username = 'admin'
password = 'Cisco123'

device = {
        "device_type": "cisco_ios",
        "host": '10.10.10.10',
        "username": username,
        "password": password,
        "secret": password
}

commands = ['no ntp server 1.1.1.1', 'ntp server 8.8.8.8', 'ntp server 8.8.4.4']
connection = ConnectHandler(**device)
output = connection.send_config_set(commands)
print(output)

connection.disconnect()
configure terminal
Enter configuration commands, one per line.  End with CNTL/Z.
c9300(config)#no ntp server 1.1.1.1
c9300(config)#ntp server 8.8.8.8
c9300(config)#ntp server 8.8.4.4
c9300(config)#end
c9300#
Python - Further Reading
Here are some of the blog posts I’ve written to further enhance your knowledge and build on top of what we’ve learned so far.