Python

Using Python to track down switch ports from the ARP table

Using Python to track down switch ports from the ARP table
In: Python

Recently, I came across a task where I had to track down the switch ports the devices are connected to using the ARP table. If I want to search for just a bunch of devices then it is not an issue, I can just log in to the switches and find the ports. However, imagine if you have to go through multiple VLANS and find hundreds of switch ports.

This was a manual, time-consuming task so, I decided to look into a way to automate it. Initially, I thought of using Ansible but couldn't really find a way to do it (please let me know in the comments if Ansible can be used).

After a bit of research, Napalm appeared to be the ideal candidate for this task and I managed to write the script and decided to document the process to benefit others who are looking for a similar solution.

Assumptions

This blog post assumes you are somewhat familiar with the following Python topics

  • Python import modules
  • Python list and dictionary
  • if statements
  • for loops

Diagram

The example is based on the following diagram where four devices are connected between the two access switches.

When you run the script, this is the output you will see. The script also outputs a CSV file as shown below.  

Why Napalm?

Napalm is a Python library that makes interacting with multi-vendor devices feel like a breeze. Initially, I thought of using Netmiko with TextFSM but then decided to go with Nalapm because it already outputs structured data.

The Script

A few things to note here:

  1. vlan == 20 - I'm only interested in the devices connected to VLAN 20. You can remove this condition if you want to see all the devices on all the VLANs. You can also add an 'or' statement if you want to see the output for a few specific VLANs.
  2. napalm_output_switch[n]["interface"] != Gi0/1 - As both switches are connected via the distribution switch, the Mac addresses of the devices can also be seen on the other switch. By using this condition, I'm excluding the Gi0/1 uplink interfaces.
  3. getpass prompts the user for a value, password in our case, without echoing what the user types to the terminal. getpass then reads input from the user and saves it as a string.
  4. The switches are added to the Python List called 'switches'. In this example, I'm only using two switches.
from napalm import get_network_driver
import json
import csv
import getpass

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

driver = get_network_driver('ios')

cisco_01 = {
    "hostname": 'cisco-router-01.packet.lan', 
    "username": "cisco",
    "password": passwd,
    "optional_args": {"secret": passwd}
    }

switches = ['switch-01.packet.lan', 'switch-02.packet.lan']
switch_list = list()

for switch_ip in switches:
    switch_dict = {
        "hostname": switch_ip,
        "username": "cisco",
        "password": passwd,
        "optional_args": {"secret": passwd}
    }
    switch_list.append(switch_dict)


device_router = driver(**cisco_01)
device_router.open()
print(f'Connecting to {cisco_01["hostname"]}')
napalm_output_router = device_router.get_arp_table()
print(json.dumps(napalm_output_router, indent=4))
device_router.close()

for each_switch in switch_list:
    device_switch = driver(**each_switch)
    device_switch.open()
    print(f'Connecting to {each_switch["hostname"]}')
    napalm_output_switch = device_switch.get_mac_address_table()
    print(json.dumps(napalm_output_switch, indent=4)) 

    for i in range(len(napalm_output_router)):
        arp_mac = napalm_output_router[i]["mac"]

        for n in range(len(napalm_output_switch)):
            if arp_mac in napalm_output_switch[n]["mac"] and napalm_output_switch[n]["vlan"] == 20 and napalm_output_switch[n]["interface"] != 'Gi0/1':
                print(f'{arp_mac} is connected to {each_switch["hostname"]} on {napalm_output_switch[n]["interface"]} and the IP is {napalm_output_router[i]["ip"]} ')

                with open('mac_data.csv', 'a', newline='') as csvfile:
                    writer = csv.writer(csvfile)
                    csvdata = (napalm_output_router[i]["ip"], arp_mac, each_switch["hostname"], napalm_output_switch[n]["interface"])
                    writer.writerow(csvdata)
    
    device_switch.close()

The ARP table from the router and the Mac address tables from switches would like the below. As you can see, the output is already structured as a Python dictionary. All we need to do is, pick each mac from the ARP table and search it through the mac-address table using for loop statements.

For example, let's pick the 4th item in the list which is 00:50:79:66:68:04, if the mac address is found in the switches, Python will print the port number, switch name and the IP address.

[
    {
        "interface": "GigabitEthernet0/0",
        "mac": "B4:2E:99:FC:83:76",
        "ip": "10.10.0.10",
        "age": 15.0
    },
    {
        "interface": "GigabitEthernet0/0",
        "mac": "50:00:00:01:00:00",
        "ip": "10.10.20.17",
        "age": -1.0
    },
    {
        "interface": "GigabitEthernet0/1",
        "mac": "50:00:00:01:00:01",
        "ip": "192.168.15.1",
        "age": -1.0
    },
    {
        "interface": "GigabitEthernet0/1",
        "mac": "00:50:79:66:68:04",
        "ip": "192.168.15.11",
        "age": 0.0
    },
    {
        "interface": "GigabitEthernet0/1",
        "mac": "00:50:79:66:68:06",
        "ip": "192.168.15.12",
        "age": 0.0
    },
    {
        "interface": "GigabitEthernet0/1",
        "mac": "00:50:79:66:68:07",
        "ip": "192.168.15.13",
        "age": 0.0
    },
    {
        "interface": "GigabitEthernet0/1",
        "mac": "00:50:79:66:68:08",
        "ip": "192.168.15.14",
        "age": 0.0
    }
]
[
    {
        "mac": "50:00:00:01:00:00",
        "interface": "Gi0/0",
        "vlan": 10,
        "static": false,
        "active": true,
        "moves": -1,
        "last_move": -1.0
    },
    {
        "mac": "50:00:00:05:00:00",
        "interface": "Gi0/0",
        "vlan": 10,
        "static": false,
        "active": true,
        "moves": -1,
        "last_move": -1.0
    },
    {
        "mac": "B4:2E:99:FC:83:76",
        "interface": "Gi0/0",
        "vlan": 10,
        "static": false,
        "active": true,
        "moves": -1,
        "last_move": -1.0
    },
    {
        "mac": "DC:A6:32:04:D2:56",
        "interface": "Gi0/0",
        "vlan": 10,
        "static": false,
        "active": true,
        "moves": -1,
        "last_move": -1.0
    },
    {
        "mac": "00:50:79:66:68:04",
        "interface": "Gi1/1",
        "vlan": 20,
        "static": false,
        "active": true,
        "moves": -1,
        "last_move": -1.0
    },
    {
        "mac": "00:50:79:66:68:07",
        "interface": "Gi1/2",
        "vlan": 20,
        "static": false,
        "active": true,
        "moves": -1,
        "last_move": -1.0
    },
    {
        "mac": "50:00:00:01:00:01",
        "interface": "Gi0/1",
        "vlan": 20,
        "static": false,
        "active": true,
        "moves": -1,
        "last_move": -1.0
    }
]

That's all for now, please let me know in the comments if there is a better way of doing this.

Written by
Suresh Vinasiththamby
I'm very excited to start blogging and share with you insights about my favourite Networking, Cloud and Automation topics. Simple guy with simple taste and lots of love for Networking and Automation.
Comments
More from Packetswitch
Table of Contents
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to Packetswitch.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.