📜 ⬆️ ⬇️

Search by MAC address on Juniper switches

On a local network, you often need to find out which switch port the device’s specific MAC address is on. The problem is solved easily if there are several switches in the network, but when there are more than 30 of them, everything becomes much more complicated. I want to share a small Python script that looks for the desired MAC address on the network and returns the name and port of the switch on which this MAC is registered.



Constructive criticism is welcome. Details under the cut.

If the network design is correct, that is, the root switch CORE , to which distribution switches DS (Distribution Switch) are connected, and, in turn, switches of access level AS (Access Switch). This rule is not always executed, access switches can be connected in series. In any case, the port of the upstream switch contains all the MAC addresses of devices connected to the downstream switch.
')
For example, if the device of interest to us is connected to the AS3 switch, then, starting the search with CORE , we will find this address on the port leading to DS1 . Going to DS1 , we find this MAC on the port leading to AS2 , going to AS2 , we see that it leads us to AS3 , and only on AS3 will we find the specific port to which the device of interest is connected.

I didn’t want to do all this with my hands, sort through all the switches in the loop and determine where the uplink and where not, too, so the next solution was born, which I want to share.

A bit of theory.


To find the MAC 08: 62: 66: c7: b3: 45 on the Juniper switch, run the following command:

show ethernet-switching table | match 08:62:66:c7:b3:45 

If there is such a MAC, the answer will be as follows:

 vlan151 08:62:66:c7:b3:45 D - xe-0/0/23.0 

The last column will be the interface name of the switch on which the MAC is registered. But how to understand where this interface leads? And here Interface Descriptions come to the rescue. These are lines in the switch configuration file that allow you to assign text labels to interfaces.

Team

 show interfaces xe-0/0/23 descriptions 

will show the following:

 Interface Admin Link Description xe-0/0/23 up up SW>DS1 

In the configuration, we indicate that this interface leads to the downstream switch:

 set interfaces xe-0/0/23 description SW>DS1 

Implementation


The proposed script will do the following:

  1. connect via SSH to the root switch;
  2. check which interface is the MAC address transmitted in the parameters;
  3. check the description of this interface;
  4. if the interface leads to the switch, recursively go to the next switch in the chain.

 #       searchpass = [] #main      MAC-    checkswitch,           MAC-.       searchpass   json. def main(argv): mac_addr = argv[0] checkswitch('CORE',mac_addr) for switch in searchpass: print (json.dumps(switch, ensure_ascii=False)) if __name__ == "__main__": main(sys.argv[1:]) #   MAC- def checkswitch(hostname,mac_addr): try: #    ,  host    returnvalue = {} returnvalue['host']=hostname #sendCommand      SSH     MAC-    answer = sendCommand(hostname,'show ethernet-switching table | match '+mac_addr) #   ,      #vlan151 08:62:66:c7:b3:45 D - xe-0/0/23.0 if(answer!=0): iface = answer.split()[4] returnvalue['iface']=iface # description ,    2  .0       #xe-0/0/23 up up SW>DS01 answer = sendCommand(hostname,'show interfaces '+iface[:-2]+' descriptions | last 1 | no-more') iface = answer.split() # description   ,      ,     SW>.  ,   3 ,         . if(len(iface)>2): iface=iface[3] returnvalue['description']=iface else: returnvalue['description']='none' searchpass.append(returnvalue) if (iface[:3]=='SW>'): checkswitch(iface[3:],mac_addr) else: returnvalue['iface']='none' searchpass.append(returnvalue) except Exception as e: print(e) 

Thus, the script will go through all switches on the network, starting with the kernel, and will try to find the necessary MAC. For successful work, it is enough to keep descriptions on the interfaces up to date, and the topology can be of almost any complexity.

An example of the script:

 python findmac.py 00:17:fc:21:e8:f9 {"host": "CORE", "iface": "xe-0/0/23.0", "description": "SW>DS1"} {"host": "DS1", "iface": "xe-0/0/11.0", "description": "SW>AS2"} {"host": "AS2", "iface": "xe-1/0/1.0", "description": "SW>AS3"} {"host": "AS3", "iface": "ge-0/0/26.0", "description": "none"} 

If the MAC is missing, we get

 {"host": "CORE", "iface": "none"} 

The last line is the switch and port of interest to us, but at the same time we can track the entire search path.

The full code is under the spoiler, thank you for your attention.

findmac.py
 import paramiko import time import sys import json import threading import logging login = 'user1' password = 'pass1234' searchpass = [] port = 22 class LogPipe(threading.Thread): def __init__(self, level): threading.Thread.__init__(self) self.daemon = False self.level = level self.fdRead, self.fdWrite = os.pipe() self.pipeReader = os.fdopen(self.fdRead) self.start() def fileno(self): return self.fdWrite def run(self): for line in iter(self.pipeReader.readline, ''): logging.log(self.level, line.strip('\n')) self.pipeReader.close() def close(self): os.close(self.fdWrite) def execute_ssh_command(host, port, username, password, command): try: # Create the SSH client. ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # Connect to the host. ssh.connect(host, port, username, password, look_for_keys=False) # Send the command (non-blocking) stdin, stdout, stderr = ssh.exec_command(command) # Wait for the command to terminate while not stdout.channel.exit_status_ready() and not stdout.channel.recv_ready(): time.sleep(1) stdoutstring = stdout.readlines() stderrstring = stderr.readlines() return stdoutstring, stderrstring finally: if ssh is not None: # Close client connection. ssh.close() def sendCommand (hostname,command): returnvalue = 0 logging.info('Host '+hostname+', command: '+command) Try: #add .mydomain for FQDN (stdoutstring, stderrstring) = execute_ssh_command(hostname+'.mydomain', port, login, password, command+'\n') if (len(stdoutstring)>0): logging.info(stdoutstring[0]) if (len(stderrstring)>0): logging.info(stderrstring[0]) except Exception as e: return returnvalue else: returnvalue = stdoutstring[0] finally: return returnvalue def checkswitch(hostname,mac_addr): try: returnvalue = {} returnvalue['host']=hostname answer = sendCommand(hostname,'show ethernet-switching table | match '+mac_addr) if(answer!=0): iface = answer.split()[4] returnvalue['iface']=iface #cut .0 prefix in the interface name answer = sendCommand(hostname,'show interfaces '+iface[:-2]+' descriptions | last 1 | no-more') iface = answer.split() if(len(iface)>2): iface=iface[3] returnvalue['description']=iface else: returnvalue['description']='none' searchpass.append(returnvalue) if (iface[:3]=='SW>'): checkswitch(iface[3:],mac_addr) else: returnvalue['iface']='none' searchpass.append(returnvalue) except Exception as e: logging.info(e) def main(argv): mac_addr = argv[0] #configure log logging.basicConfig(filename='/var/log/findmac.log', level=logging.INFO, format='%(asctime)s %(message)s') logging.info('Find MAC: '+mac_addr) checkswitch('CORE',mac_addr) for switch in searchpass: print (json.dumps(switch, ensure_ascii=False)) if __name__ == "__main__": main(sys.argv[1:]) 

Source: https://habr.com/ru/post/420013/


All Articles