Python to find Switch & Port using Mac Address

Python to find Switch & Port using Mac Address
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 library 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.

Mapping Mac Address to Switch Port

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()

Here is the breakdown of the code.

  • Imports the required libraries - napalm, json, csv, and getpass.
  • Prompts the user to enter a password, which is saved as a string variable called passwd using the getpass library.
  • Defines a dictionary cisco_01 that contains the login details for a Cisco router with the hostname cisco-router-01.packet.lan. This dictionary also includes an optional_args field, which sets the secret to the value of passwd.
  • Defines a list switches containing the hostnames of two switches, and then creates a list of dictionaries switch_list, where each dictionary contains the login details for one of the switches in switches list.
  • Uses get_network_driver from the napalm library to retrieve the driver for IOS devices and uses it to create a device_router object with the login details from cisco_01.
  • Opens a connection to the router using device_router.open(), prints a message indicating that it is connecting to the router, and retrieves the ARP table using device_router.get_arp_table(). The output is then printed in JSON format using json.dumps() with an indentation of 4.
  • Closes the connection to the router using device_router.close().
  • Loops over each dictionary in switch_list, connects to the switch using the driver object and the login details, retrieves the MAC address table using device_switch.get_mac_address_table(), and prints the output in JSON format.
  • Loops over each entry in the ARP table from the router, and for each entry, loops over each entry in the MAC address table from the switch.
  • If the MAC address from the ARP table matches the MAC address from the MAC address table, and the VLAN is 20 and the interface is not Gi0/1, prints a message indicating that the MAC address is connected to the switch on the specified interface and IP address. It also writes the data to a CSV file called mac_data.csv.
  • Closes the connection to the switch using device_switch.close().

The ARP table from the router and the Mac address tables from switches would be 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.

Table of Contents
Written by
Suresh Vina
Tech enthusiast sharing Networking, Cloud & Automation insights. Join me in a welcoming space to learn & grow with simplicity and practicality.
Comments
More from Packetswitch
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.