From 506712ee0f6e5c4afb8e5b7f58685e543c8193aa Mon Sep 17 00:00:00 2001 From: Prince Sunny Date: Thu, 9 Aug 2018 09:08:13 -0700 Subject: [PATCH] Enhanced "show arp/ndp" output (#296) * Enhanced "show arp/ndp" output to have vlan member info * Fixes and restructure * Addressed review comments, pep8online comments --- scripts/nbrshow | 249 ++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 3 +- show/main.py | 20 ++-- 3 files changed, 265 insertions(+), 7 deletions(-) create mode 100644 scripts/nbrshow diff --git a/scripts/nbrshow b/scripts/nbrshow new file mode 100644 index 0000000000..046840494d --- /dev/null +++ b/scripts/nbrshow @@ -0,0 +1,249 @@ +#!/usr/bin/python +""" + Script to show Ipv4/Ipv6 neighbor entries + + usage: nbrshow [-h] [-ip IPADDR] [-if IFACE] v + optional arguments: + -ip IPADDR, --ipaddr IPADDR + Neigbhor for a specific address + -if IFACE, --iface IFACE + Neigbhors learned on specific L3 interface + + Example of the output: + admin@str~$nbrshow -4 + Address MacAddress Iface Vlan + ------------ ----------------- --------------- ------ + 10.0.0.57 52:54:00:87:8f:2c PortChannel0001 - + 10.64.246.1 00:00:5e:00:01:f6 eth0 - + 192.168.0.2 24:8a:07:4c:f5:0a Ethernet20 1000 + .. + Total number of entries 10 + admin@str:~$ nbrshow -6 -ip fc00::72 + Address MacAddress Iface Vlan Status + --------- ----------------- --------------- ------ --------- + fc00::72 52:54:00:87:8f:2c PortChannel0001 - REACHABLE + Total number of entries 1 + +""" +import argparse +import json +import sys +import subprocess +import re + +from natsort import natsorted +from swsssdk import SonicV2Connector, port_util +from tabulate import tabulate + +""" + Base class for v4 and v6 neighbor. +""" + + +class NbrBase(object): + + HEADER = [] + NBR_COUNT = 0 + + def __init__(self, cmd): + super(NbrBase, self).__init__() + self.db = SonicV2Connector(host="127.0.0.1") + self.if_name_map, self.if_oid_map = port_util.get_interface_oid_map(self.db) + self.if_br_oid_map = port_util.get_bridge_port_map(self.db) + self.fetch_fdb_data() + self.cmd = cmd + self.err = None + self.nbrdata = [] + return + + def fetch_fdb_data(self): + """ + Fetch FDB entries from ASIC DB. + @Todo, this code can be reused + """ + self.db.connect(self.db.ASIC_DB) + self.bridge_mac_list = [] + + fdb_str = self.db.keys('ASIC_DB', "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY:*") + if not fdb_str: + return + + if self.if_br_oid_map is None: + return + + oid_pfx = len("oid:0x") + for s in fdb_str: + fdb_entry = s.decode() + fdb = json.loads(fdb_entry .split(":", 2)[-1]) + if not fdb: + continue + + ent = self.db.get_all('ASIC_DB', s, blocking=True) + br_port_id = ent[b"SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID"][oid_pfx:] + ent_type = ent[b"SAI_FDB_ENTRY_ATTR_TYPE"] + if br_port_id not in self.if_br_oid_map: + continue + port_id = self.if_br_oid_map[br_port_id] + if_name = self.if_oid_map[port_id] + if 'vlan' in fdb: + vlan_id = fdb["vlan"] + elif 'bvid' in fdb: + vlan_id = port_util.get_vlan_id_from_bvid(self.db, fdb["bvid"]) + self.bridge_mac_list.append((int(vlan_id),) + (fdb["mac"],) + (if_name,)) + + return + + def fetch_nbr_data(self): + """ + Fetch Neighbor data (ARP/IPv6 Neigh) from kernel. + """ + p = subprocess.Popen(self.cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (output, err) = p.communicate() + rc = p.wait() + + if rc == 0: + rawdata = output + else: + self.err = err + rawdata = None + + return rawdata + + def display(self, vpos=3): + """ + Display formatted Neighbor entries (ARP/IPv6 Neigh). + """ + + output = [] + + for ent in self.nbrdata: + + self.NBR_COUNT += 1 + vlan = '-' + if 'Vlan' in ent[2]: + vlanid = int(re.search(r'\d+', ent[2]).group()) + mac = unicode(ent[1].upper()) + fdb_ent = next((fdb for fdb in self.bridge_mac_list[:] + if fdb[0] == vlanid and fdb[1] == mac), None) + if fdb_ent is not None: + vlan = vlanid + ent[2] = fdb_ent[2] + + ent.insert(vpos, vlan) + output.append(ent) + + self.nbrdata = natsorted(output, key=lambda x: x[0]) + + print tabulate(self.nbrdata, self.HEADER) + print "Total number of entries {0} ".format(self.NBR_COUNT) + + def display_err(self): + print "Error fetching Neighbors: {} ".format(self.err) + + +class ArpShow(NbrBase): + + HEADER = ['Address', 'MacAddress', 'Iface', 'Vlan'] + CMD = "/usr/sbin/arp -n " + + def __init__(self, ipaddr, iface): + + if ipaddr is not None: + self.CMD += ipaddr + + if iface is not None: + self.CMD += ' -i ' + iface + + NbrBase.__init__(self, self.CMD) + return + + def display(self): + """ + Format "arp -n" output from kernel + Address HWtype HWaddress Flags Mask Iface + 10.64.246.2 ether f4:b5:2f:79:b3:f0 C eth0 + 10.0.0.63 ether 52:54:00:ae:11:49 C PortChannel0004 + """ + self.arpraw = self.fetch_nbr_data() + + if self.arpraw is None: + self.display_err() + return + + for line in self.arpraw.splitlines()[1:]: + ent = line.split()[::2] + self.nbrdata.append(ent) + + super(ArpShow, self).display() + + +class NeighShow(NbrBase): + + HEADER = ['Address', 'MacAddress', 'Iface', 'Vlan', 'Status'] + CMD = "/bin/ip -6 neigh show " + + def __init__(self, ipaddr, iface): + + if ipaddr is not None: + self.CMD += ipaddr + + if iface is not None: + self.CMD += ' dev ' + iface + + self.iface = iface + NbrBase.__init__(self, self.CMD) + return + + def display(self): + """ + Format "ip -6 neigh show " output from kernel + "fc00::76 dev PortChannel0002 lladdr 52:54:00:33:90:d0 router REACHABLE" + Format "ip -6 neigh show dev PortChannel0003" + "fc00::7a lladdr 52:54:00:6b:1d:0a router STALE" + """ + self.arpraw = self.fetch_nbr_data() + + if self.arpraw is None: + self.display_err() + return + + for line in self.arpraw.splitlines()[:]: + ent = line.split()[::2] + + if self.iface is not None: + ent.insert(2, self.iface) + else: + ent[1], ent[2] = ent[2], ent[1] + + self.nbrdata.append(ent) + + super(NeighShow, self).display() + + +def main(): + + parser = argparse.ArgumentParser(description='Show Neigbhor entries', + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument('-ip', '--ipaddr', type=str, + help='Neigbhor for a specific address', default=None) + parser.add_argument('-if', '--iface', type=str, + help='Neigbhors learned on specific L3 interface', default=None) + parser.add_argument('v', help='IP Version -4 or -6') + + args = parser.parse_args() + + try: + if (args.v == '-6'): + neigh = NeighShow(args.ipaddr, args.iface) + neigh.display() + else: + arp = ArpShow(args.ipaddr, args.iface) + arp.display() + + except Exception as e: + print e.message + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py index 8f93c2ce7a..3f6a7f73ce 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,8 @@ def get_test_suite(): 'scripts/pfcstat', 'scripts/queuestat', 'scripts/reboot', - 'scripts/teamshow' + 'scripts/teamshow', + 'scripts/nbrshow' ], data_files=[ ('/etc/bash_completion.d', glob.glob('data/etc/bash_completion.d/*')), diff --git a/show/main.py b/show/main.py index 1899c83d7b..b779652efb 100755 --- a/show/main.py +++ b/show/main.py @@ -149,13 +149,17 @@ def cli(): @cli.command() @click.argument('ipaddress', required=False) +@click.option('-if', '--iface') @click.option('--verbose', is_flag=True, help="Enable verbose output") -def arp(ipaddress, verbose): +def arp(ipaddress, iface, verbose): """Show IP ARP table""" - cmd = "/usr/sbin/arp -n" + cmd = "nbrshow -4" if ipaddress is not None: - cmd += " {}".format(ipaddress) + cmd += " -ip {}".format(ipaddress) + + if iface is not None: + cmd += " -if {}".format(iface) run_command(cmd, display_cmd=verbose) @@ -165,13 +169,17 @@ def arp(ipaddress, verbose): @cli.command() @click.argument('ip6address', required=False) +@click.option('-if', '--iface') @click.option('--verbose', is_flag=True, help="Enable verbose output") -def ndp(ip6address): +def ndp(ip6address, iface, verbose): """Show IPv6 Neighbour table""" - cmd = "/bin/ip -6 neigh show" + cmd = "nbrshow -6" if ip6address is not None: - cmd += ' {}'.format(ip6address) + cmd += " -ip {}".format(ip6address) + + if iface is not None: + cmd += " -if {}".format(iface) run_command(cmd, display_cmd=verbose)