Skip to content

Commit 506712e

Browse files
authored
Enhanced "show arp/ndp" output (sonic-net#296)
* Enhanced "show arp/ndp" output to have vlan member info * Fixes and restructure * Addressed review comments, pep8online comments
1 parent 8de7bc7 commit 506712e

File tree

3 files changed

+265
-7
lines changed

3 files changed

+265
-7
lines changed

scripts/nbrshow

+249
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
#!/usr/bin/python
2+
"""
3+
Script to show Ipv4/Ipv6 neighbor entries
4+
5+
usage: nbrshow [-h] [-ip IPADDR] [-if IFACE] v
6+
optional arguments:
7+
-ip IPADDR, --ipaddr IPADDR
8+
Neigbhor for a specific address
9+
-if IFACE, --iface IFACE
10+
Neigbhors learned on specific L3 interface
11+
12+
Example of the output:
13+
admin@str~$nbrshow -4
14+
Address MacAddress Iface Vlan
15+
------------ ----------------- --------------- ------
16+
10.0.0.57 52:54:00:87:8f:2c PortChannel0001 -
17+
10.64.246.1 00:00:5e:00:01:f6 eth0 -
18+
192.168.0.2 24:8a:07:4c:f5:0a Ethernet20 1000
19+
..
20+
Total number of entries 10
21+
admin@str:~$ nbrshow -6 -ip fc00::72
22+
Address MacAddress Iface Vlan Status
23+
--------- ----------------- --------------- ------ ---------
24+
fc00::72 52:54:00:87:8f:2c PortChannel0001 - REACHABLE
25+
Total number of entries 1
26+
27+
"""
28+
import argparse
29+
import json
30+
import sys
31+
import subprocess
32+
import re
33+
34+
from natsort import natsorted
35+
from swsssdk import SonicV2Connector, port_util
36+
from tabulate import tabulate
37+
38+
"""
39+
Base class for v4 and v6 neighbor.
40+
"""
41+
42+
43+
class NbrBase(object):
44+
45+
HEADER = []
46+
NBR_COUNT = 0
47+
48+
def __init__(self, cmd):
49+
super(NbrBase, self).__init__()
50+
self.db = SonicV2Connector(host="127.0.0.1")
51+
self.if_name_map, self.if_oid_map = port_util.get_interface_oid_map(self.db)
52+
self.if_br_oid_map = port_util.get_bridge_port_map(self.db)
53+
self.fetch_fdb_data()
54+
self.cmd = cmd
55+
self.err = None
56+
self.nbrdata = []
57+
return
58+
59+
def fetch_fdb_data(self):
60+
"""
61+
Fetch FDB entries from ASIC DB.
62+
@Todo, this code can be reused
63+
"""
64+
self.db.connect(self.db.ASIC_DB)
65+
self.bridge_mac_list = []
66+
67+
fdb_str = self.db.keys('ASIC_DB', "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY:*")
68+
if not fdb_str:
69+
return
70+
71+
if self.if_br_oid_map is None:
72+
return
73+
74+
oid_pfx = len("oid:0x")
75+
for s in fdb_str:
76+
fdb_entry = s.decode()
77+
fdb = json.loads(fdb_entry .split(":", 2)[-1])
78+
if not fdb:
79+
continue
80+
81+
ent = self.db.get_all('ASIC_DB', s, blocking=True)
82+
br_port_id = ent[b"SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID"][oid_pfx:]
83+
ent_type = ent[b"SAI_FDB_ENTRY_ATTR_TYPE"]
84+
if br_port_id not in self.if_br_oid_map:
85+
continue
86+
port_id = self.if_br_oid_map[br_port_id]
87+
if_name = self.if_oid_map[port_id]
88+
if 'vlan' in fdb:
89+
vlan_id = fdb["vlan"]
90+
elif 'bvid' in fdb:
91+
vlan_id = port_util.get_vlan_id_from_bvid(self.db, fdb["bvid"])
92+
self.bridge_mac_list.append((int(vlan_id),) + (fdb["mac"],) + (if_name,))
93+
94+
return
95+
96+
def fetch_nbr_data(self):
97+
"""
98+
Fetch Neighbor data (ARP/IPv6 Neigh) from kernel.
99+
"""
100+
p = subprocess.Popen(self.cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
101+
(output, err) = p.communicate()
102+
rc = p.wait()
103+
104+
if rc == 0:
105+
rawdata = output
106+
else:
107+
self.err = err
108+
rawdata = None
109+
110+
return rawdata
111+
112+
def display(self, vpos=3):
113+
"""
114+
Display formatted Neighbor entries (ARP/IPv6 Neigh).
115+
"""
116+
117+
output = []
118+
119+
for ent in self.nbrdata:
120+
121+
self.NBR_COUNT += 1
122+
vlan = '-'
123+
if 'Vlan' in ent[2]:
124+
vlanid = int(re.search(r'\d+', ent[2]).group())
125+
mac = unicode(ent[1].upper())
126+
fdb_ent = next((fdb for fdb in self.bridge_mac_list[:]
127+
if fdb[0] == vlanid and fdb[1] == mac), None)
128+
if fdb_ent is not None:
129+
vlan = vlanid
130+
ent[2] = fdb_ent[2]
131+
132+
ent.insert(vpos, vlan)
133+
output.append(ent)
134+
135+
self.nbrdata = natsorted(output, key=lambda x: x[0])
136+
137+
print tabulate(self.nbrdata, self.HEADER)
138+
print "Total number of entries {0} ".format(self.NBR_COUNT)
139+
140+
def display_err(self):
141+
print "Error fetching Neighbors: {} ".format(self.err)
142+
143+
144+
class ArpShow(NbrBase):
145+
146+
HEADER = ['Address', 'MacAddress', 'Iface', 'Vlan']
147+
CMD = "/usr/sbin/arp -n "
148+
149+
def __init__(self, ipaddr, iface):
150+
151+
if ipaddr is not None:
152+
self.CMD += ipaddr
153+
154+
if iface is not None:
155+
self.CMD += ' -i ' + iface
156+
157+
NbrBase.__init__(self, self.CMD)
158+
return
159+
160+
def display(self):
161+
"""
162+
Format "arp -n" output from kernel
163+
Address HWtype HWaddress Flags Mask Iface
164+
10.64.246.2 ether f4:b5:2f:79:b3:f0 C eth0
165+
10.0.0.63 ether 52:54:00:ae:11:49 C PortChannel0004
166+
"""
167+
self.arpraw = self.fetch_nbr_data()
168+
169+
if self.arpraw is None:
170+
self.display_err()
171+
return
172+
173+
for line in self.arpraw.splitlines()[1:]:
174+
ent = line.split()[::2]
175+
self.nbrdata.append(ent)
176+
177+
super(ArpShow, self).display()
178+
179+
180+
class NeighShow(NbrBase):
181+
182+
HEADER = ['Address', 'MacAddress', 'Iface', 'Vlan', 'Status']
183+
CMD = "/bin/ip -6 neigh show "
184+
185+
def __init__(self, ipaddr, iface):
186+
187+
if ipaddr is not None:
188+
self.CMD += ipaddr
189+
190+
if iface is not None:
191+
self.CMD += ' dev ' + iface
192+
193+
self.iface = iface
194+
NbrBase.__init__(self, self.CMD)
195+
return
196+
197+
def display(self):
198+
"""
199+
Format "ip -6 neigh show " output from kernel
200+
"fc00::76 dev PortChannel0002 lladdr 52:54:00:33:90:d0 router REACHABLE"
201+
Format "ip -6 neigh show dev PortChannel0003"
202+
"fc00::7a lladdr 52:54:00:6b:1d:0a router STALE"
203+
"""
204+
self.arpraw = self.fetch_nbr_data()
205+
206+
if self.arpraw is None:
207+
self.display_err()
208+
return
209+
210+
for line in self.arpraw.splitlines()[:]:
211+
ent = line.split()[::2]
212+
213+
if self.iface is not None:
214+
ent.insert(2, self.iface)
215+
else:
216+
ent[1], ent[2] = ent[2], ent[1]
217+
218+
self.nbrdata.append(ent)
219+
220+
super(NeighShow, self).display()
221+
222+
223+
def main():
224+
225+
parser = argparse.ArgumentParser(description='Show Neigbhor entries',
226+
formatter_class=argparse.RawTextHelpFormatter)
227+
parser.add_argument('-ip', '--ipaddr', type=str,
228+
help='Neigbhor for a specific address', default=None)
229+
parser.add_argument('-if', '--iface', type=str,
230+
help='Neigbhors learned on specific L3 interface', default=None)
231+
parser.add_argument('v', help='IP Version -4 or -6')
232+
233+
args = parser.parse_args()
234+
235+
try:
236+
if (args.v == '-6'):
237+
neigh = NeighShow(args.ipaddr, args.iface)
238+
neigh.display()
239+
else:
240+
arp = ArpShow(args.ipaddr, args.iface)
241+
arp.display()
242+
243+
except Exception as e:
244+
print e.message
245+
sys.exit(1)
246+
247+
248+
if __name__ == "__main__":
249+
main()

setup.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ def get_test_suite():
5757
'scripts/pfcstat',
5858
'scripts/queuestat',
5959
'scripts/reboot',
60-
'scripts/teamshow'
60+
'scripts/teamshow',
61+
'scripts/nbrshow'
6162
],
6263
data_files=[
6364
('/etc/bash_completion.d', glob.glob('data/etc/bash_completion.d/*')),

show/main.py

+14-6
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,17 @@ def cli():
149149

150150
@cli.command()
151151
@click.argument('ipaddress', required=False)
152+
@click.option('-if', '--iface')
152153
@click.option('--verbose', is_flag=True, help="Enable verbose output")
153-
def arp(ipaddress, verbose):
154+
def arp(ipaddress, iface, verbose):
154155
"""Show IP ARP table"""
155-
cmd = "/usr/sbin/arp -n"
156+
cmd = "nbrshow -4"
156157

157158
if ipaddress is not None:
158-
cmd += " {}".format(ipaddress)
159+
cmd += " -ip {}".format(ipaddress)
160+
161+
if iface is not None:
162+
cmd += " -if {}".format(iface)
159163

160164
run_command(cmd, display_cmd=verbose)
161165

@@ -165,13 +169,17 @@ def arp(ipaddress, verbose):
165169

166170
@cli.command()
167171
@click.argument('ip6address', required=False)
172+
@click.option('-if', '--iface')
168173
@click.option('--verbose', is_flag=True, help="Enable verbose output")
169-
def ndp(ip6address):
174+
def ndp(ip6address, iface, verbose):
170175
"""Show IPv6 Neighbour table"""
171-
cmd = "/bin/ip -6 neigh show"
176+
cmd = "nbrshow -6"
172177

173178
if ip6address is not None:
174-
cmd += ' {}'.format(ip6address)
179+
cmd += " -ip {}".format(ip6address)
180+
181+
if iface is not None:
182+
cmd += " -if {}".format(iface)
175183

176184
run_command(cmd, display_cmd=verbose)
177185

0 commit comments

Comments
 (0)