Netmiko introduction and ARP to MAC finder using TextFSM

Over the past 3-6 months I’ve had the opportunity to be a lot more focused on Network Automation and what benefit it can provide to the business, one of the main things that we are seeing across the board is the time and cost saving benefit and the fact that it removes human error.

The one thing that I hear regularly is that people say “you’ll do me out of a job”, I don’t think this is the case there will always need to be some form of human intervention in networking and you still need to understand the underlying protocols. One thing I would say is that Network Automation has helped me skip the boring tasks that are repetitive and allowed me to concentrate on the bigger tasks.

My manager rung me up today and said I’ve been sent a list of device IP addresses, is there a way we can get the MAC addresses of these devices from ARP. We could do this task manually by logging onto the relevant nodes and running “show arp”, but hey who want’s to do that boring task so why not automate it.

In the following example I have used Netmiko with the inbuilt TextFSM support, TextFSM is a brilliant tool it parses well known Cisco Commands for you, yes we could create a custom parser ourselves using the RE module but why reinvent the wheel. All the available parsers can be found below here https://github.com/networktocode/ntc-templates/tree/master/ntc_templates/templates.

Before we go into how I used TextFSM, let’s get an understanding on creating an SSH session to a single device. Let’s get Netmiko installed in our environment using pip install netmiko to view the latest version go to https://pypi.org/project/netmiko/.

We first need to import the Netmiko ConnectHandler, this is the module that allows you create an SSH to a device. If you print(connect) it will show you if the SSH connection was successful, if it fails Netmiko has inbuilt error handling e.g. Authentication Failure this will show up in the terminal when you run the code.

from netmiko import ConnectHandler

devices = {
                "device_type": "cisco_ios",
                "host":   "10.1.99.10",
                "username": "User1",
                "password": "Password",
                "secret": "Password"
        }

connect = ConnectHandler(**devices)

print(connect)

The above code uses static variables, we could always change these to ask for user input e.g. for device ip, username, password & secret. We import getpass so that the password is hidden when you input it into the terminal. We then replace our devices dictionary to include the variables.

from netmiko import ConnectHandler
from getpass import getpass 

device_ip = input("Enter Device IP: ")
username = input("Enter Username: ")
password = getpass("Enter Password: ")
secret = getpass("Enter enable password if applicable: ")

devices = {
                "device_type": "cisco_ios",
                "host":   device_ip,
                "username": username,
                "password": password,
                "secret": secret
        }

connect = ConnectHandler(**devices)

print(connect)

Now we know how to connect to a device with Netmiko, let’s work out how we send a command to a device, Netmiko makes this extremely simple see below.

show_version = connect.send_command("show version")

print(show_version)

Now if we run that now we will get the following, great a show version, now you could output this to a file or do whatever you like with it. What if we want to grab certain details out of it e.g. Uptime, Firmware Version and Hostname, this is where TextFSM comes in handy.

(venv) markrobinson@marks-air-2 cisco_arp_to_mac_finder % python3 test.py
Cisco IOS Software, vios_l2 Software (vios_l2-ADVENTERPRISEK9-M), Experimental Version 15.2(20170321:233949) [mmen 101]
Copyright (c) 1986-2017 by Cisco Systems, Inc.
Compiled Wed 22-Mar-17 08:38 by mmen


ROM: Bootstrap program is IOSv

SW1 uptime is 3 hours, 43 minutes
System returned to ROM by reload
System image file is "flash0:/vios_l2-adventerprisek9-m"
Last reload reason: Unknown reason



This product contains cryptographic features and is subject to United
States and local country laws governing import, export, transfer and
use. Delivery of Cisco cryptographic products does not imply
third-party authority to import, export, distribute or use encryption.
Importers, exporters, distributors and users are responsible for
compliance with U.S. and local country laws. By using this product you
agree to comply with applicable laws and regulations. If you are unable
to comply with U.S. and local laws, return this product immediately.

A summary of U.S. laws governing Cisco cryptographic products may be found at:
http://www.cisco.com/wwl/export/crypto/tool/stqrg.html

If you require further assistance please contact us by sending email to
[email protected].

Cisco IOSv () processor (revision 1.0) with 935289K/111616K bytes of memory.
Processor board ID 9LEMEJMCDW1
1 Virtual Ethernet interface
8 Gigabit Ethernet interfaces
DRAM configuration is 72 bits wide with parity disabled.
256K bytes of non-volatile configuration memory.
2097152K bytes of ATA System CompactFlash 0 (Read/Write)
0K bytes of ATA CompactFlash 1 (Read/Write)
0K bytes of ATA CompactFlash 2 (Read/Write)
0K bytes of ATA CompactFlash 3 (Read/Write)

Configuration register is 0x101

Below is how we use TextFSM in Netmiko, it’s super simple thanks to how well integrated it is. In the send_command just add use_textfsm=True and do a print again to see the difference. Look how the data is now separated into a list of dictionaries.

send_command("show version", use_textfsm=True))

(venv) markrobinson@marks-air-2 cisco_arp_to_mac_finder % python3 test.py
[{'version': '', 'rommon': 'Bootstrap', 'hostname': 'SW1', 'uptime': '3 hours, 49 minutes', 'uptime_years': '', 'uptime_weeks': '', 'uptime_days': '', 'uptime_hours': '3', 'uptime_minutes': '49', 'reload_reason': 'Unknown reason', 'running_image': '/vios_l2-adventerprisek9-m', 'hardware': ['IOSv'], 'serial': ['9LEMEJMCDW1'], 'config_register': '0x101', 'mac': [], 'restarted': ''}]

The above output isn’t very well formatted or easy to read, we can fix this by importing json and using the json.dumps module. Look how much better that reads now it’s been properly formatted.

import json

show_version = connect.send_command("show version", use_textfsm=True)

print(json.dumps(show_version, indent=4))

(venv) markrobinson@marks-air-2 cisco_arp_to_mac_finder % python3 test.py
[
{
“version”: “”,
“rommon”: “Bootstrap”,
“hostname”: “SW1”,
“uptime”: “3 hours, 52 minutes”,
“uptime_years”: “”,
“uptime_weeks”: “”,
“uptime_days”: “”,
“uptime_hours”: “3”,
“uptime_minutes”: “52”,
“reload_reason”: “Unknown reason”,
“running_image”: “/vios_l2-adventerprisek9-m”,
“hardware”: [
“IOSv”
],
“serial”: [
“9LEMEJMCDW1”
],
“config_register”: “0x101”,
“mac”: [],
“restarted”: “”
}
]

Let’s say we want to grab a specific piece of information from the above out e.g. hostname & uptime. This is super easy and we can just turn this into a variable using the below. The variables hostname and uptime contain a lookup into the show_version TextFSM output.

show_version = connect.send_command("show version", use_textfsm=True)
hostname = show_version[0]["hostname"]
uptime = show_version[0]["uptime"]

print(f"Switch {hostname} has been up for {uptime}")

The above prints out the following, Switch SW1 has been up for 4 hours, 1 minute to include variables inside strings you need to use something called F-String, at the start of my string you can see an f this calls the f-string module. This is one of my favourite features of Python and is great for outputs like the above.

Now one last thing before I move onto the ARP to MAC mapper that I have created, what if we have multiple devices. Running this script for each device would be a pain, we can address this easily in Python lets just loop over a text file full of host addresses.

with open(file="hosts.txt") as hosts:

    for host in hosts:

        host = host.strip()

        devices = {
                "device_type": "cisco_ios",
                "host":   host,
                "username": username,
                "password": password,
                "secret": secret
        }


Below is an example of what is possible with Netmiko, it was something that took less than 20 minutes to spin up but completes task with ease and manual intervention. The only other module I have imported here is XlsxWriter to output my results into a nice excel spreadsheet.

from netmiko import ConnectHandler
from getpass import getpass
import xlsxwriter

username = input("Enter Username: ")
password = getpass("Enter Password: ")
secret = getpass("Enter enable password if applicable: ")
workbook = xlsxwriter.Workbook("Cisco_ARP_to_MAC_Finder.xlsx")
worksheet = workbook.add_worksheet("ARP_to_MAC")

row = 0
col = 0

with open(file="hosts.txt") as hosts:

    for host in hosts:

        host = host.strip()

        try:

            devices = {
                "device_type": "cisco_ios",
                "host":   host,
                "username": username,
                "password": password,
                "secret": secret
        }

            print(f"Attempting to connect to {host}")

            connect = ConnectHandler(**devices)

            show_version = connect.send_command("show version", use_textfsm=True, read_timeout=120)
            show_arp = connect.send_command("show ip arp", use_textfsm=True, read_timeout=120)

            for item in show_version:

                hostname = item["hostname"]

                print(f"Connected to {hostname}")

            for item in show_arp:

                mac_address = item["mac"]
                ip_address = item["address"]

                with open(file="ip_address_to_find.txt") as ip_finder:

                    for ip in ip_finder:

                        ip = ip.strip()

                        if ip == ip_address:

                            worksheet.write(row, col, hostname)
                            worksheet.write(row, col + 1, ip_address)
                            worksheet.write(row, col + 2, mac_address)
                            row += 1
            
        except:

            print(f"Failed connect to {host}")
            pass

workbook.close()

The first part of the above code goes through everything we talked about above connecting to a device and running some show commands and getting output in a list of dictionaries. We then go onto looping round some of the output to gather the router hostname.

We then move onto looping through the show arp output and open up another text file which includes the IP addresses we are looking to find the MAC address of. We then loop through these IP’s and compare them to the IP addresses from the show arp command we ran, we use the == which means if equals to then write out the data to excel worksheet.

I’ve included a Try and Except in the above code to handle most errors, there are nicer ways to do this but this was something I put together in 15-20 minutes.

Now you have laid out the base code, we can keep reusing that for other tasks that you’d like to run, though my advice would be this is kept to a small number of devices. If you are wanting to run commands across multiple devices all at once then Nornir would be the thing to use, https://nornir.readthedocs.io/en/latest/.

The above code is very basic and could be much better written, but shows the possibility of basic network automation skills that anyone can pick up. My advice is just keep practicing.

I hope this article was useful and feel free to leave any comments below 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *