-
Notifications
You must be signed in to change notification settings - Fork 0
/
supercommand.py
283 lines (248 loc) · 9.61 KB
/
supercommand.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#!/usr/bin/env python
#
# Copyright (C) 2013 Cisco Systems Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# This script is a simple 'supercommand', where a sequence of three
# common commands, show ip arp, show mac address-table and show cdp neighbor
# are all chained with their outputs fed into each other to gather information
# about a particular device connected to this switch.
#
# The logic here can be extended to include additional commands, and introduce
# branching logic to handle different situations. An example may be to treat a
# host that appears in the CDP table differently from one that does not, and
# collect a other command outputs in that case
#
# Tested and validated on Nexus 9000 6.1(2)I2(2a)
#
# A best effort was also made to try to run it on other platforms, but was only
# tested on Nexus 5000 5.2(1)N1(6)
#
# For Nexus 5000 support, this depends on xmltodict
# https://github.com/martinblech/xmltodict/blob/master/xmltodict.py
#
#
import re
import pprint
import json
from argparse import ArgumentParser
#
# This entire block of code is aimed at detecting of we have access to cli and
# the clid command. If we do not have them, we attempt to import cli from the
# cisco package (which is present on other NXOS platforms). If that fails, we
# cannot proceed further. However, if it does succeed, then we need to create
# an equivalent of clid(), so we introduce an external dependency on xmltodict
# which is used to convert the XML output of the commands into a JSON dict.
#
# Finally, after that we check to see if cli() returns a tuple, if so that means
# we are on a 5K or similar, and we need to patch the output to return it as
# just the command output, and not the two-tuple
class UnstructuredOutput(Exception):
pass
try:
from cli import clid, cli
except ImportError:
try:
from cisco import cli
except ImportError:
print 'Script is unsupported on this platform'
raise
def clid(cmd):
try:
import xmltodict
except ImportError:
print 'Script is unsupported on this platform: requires xmltodict'
raise
tag = '__readonly__'
starttag, endtag = '<' + tag + '>', '</' + tag + '>'
output = cli('{0} | xml'.format(cmd))
start_index, end_index = output.find(starttag), output.find(endtag)
if start_index == -1 or end_index == -1:
raise UnstructuredOutput(
'Command {0} does not support structured output: {1}'.format(
cmd, output))
output = xmltodict.parse(
output[start_index:end_index + len(endtag)])
json_output = json.dumps(output[tag])
return json_output
def cli_decorator(target_function):
def wrapper(cmd):
return target_function(cmd)[1]
return wrapper
if isinstance(cli('show version'), tuple):
cli = cli_decorator(cli)
def findkey(dct, key, value=None):
found = []
if isinstance(dct, list):
for item in dct:
f = findkey(item, key, value)
if f:
found.extend(f)
if isinstance(dct, dict):
for k, v in dct.items():
if isinstance(v, list) or isinstance(v, dict):
f = findkey(v, key, value)
if f:
found.extend(f)
if str(k) == str(key):
if (value and str(v) == str(value)) or not value:
found.append(v)
return found if len(found) > 0 else None
def getarpentry(ip=None, vrf='all'):
# Check the output of the ARP table for the IP address in question
if ip:
arpoutput = json.loads(clid('show ip arp {0} vrf {1}'.format(ip, vrf)))
else:
arpoutput = json.loads(clid('show ip arp vrf {0}'.format(vrf)))
rowadjlist = findkey(arpoutput, 'ROW_adj')
if not rowadjlist:
return None
# flatten out the data received from show ip arp into a list of dicts
arpentries = []
# print "TITU : ", arpoutput
for rowadj in rowadjlist:
if isinstance(rowadj, dict):
arpentries.append(rowadj)
elif isinstance(rowadj, list):
arpentries.extend(rowadj)
arplist = []
for arp in arpentries:
try:
# print "TITU Int : ", arp['intf-out']
arplist.append(
[arp['ip-addr-out'], arp['time-stamp'], arp['mac'], arp['intf-out']])
except KeyError:
continue
return arplist
def getmacentry(mac, vlanfilter=None):
try:
macaddroutput = json.loads(
clid('show mac address-table address {0}'.format(mac)))
except UnstructuredOutput:
return None
macaddrlist = findkey(macaddroutput, 'ROW_mac_address')
if not macaddrlist:
return None
macentries = []
for macaddr in macaddrlist:
if isinstance(macaddr, dict):
macentries.append(macaddr)
elif isinstance(macaddr, list):
macentries.extend(macaddr)
entries = []
# print "TITU:mac, ", mac
# print "TITU: ", macaddroutput
# print "TITU", macentries
for macaddr in macentries:
vlan = macaddr['disp_vlan']
mac = macaddr['disp_mac_addr']
entrytype = macaddr['disp_type']
age = macaddr['disp_age']
secure = macaddr['disp_is_secure']
ntfy = macaddr['disp_is_ntfy']
port = macaddr['disp_port']
# print "TITU", port
if vlanfilter and vlan != vlanfilter:
continue
# If a MAC is on a port channel, dereference it and use the first entry
if 'po' in port.lower():
members = getportchannelmembers(port)
if not members:
raise Exception(
'Unable to find any member interfaces in {0}'.format(port))
entries.extend(
[[vlan, mac, entrytype, age, secure, ntfy, memberport,
port] for memberport in members])
elif 'vlan' in port.lower():
continue
else:
entries.append(
[vlan, mac, entrytype, age, secure, ntfy, port, port])
return entries
def getportchannelmembers(port):
po = json.loads(
clid('show port-channel summary int {0}'.format(port)))
members = findkey(po, 'port')
return members
def getcdpentry(port):
# Next use the interface we found the device on from CAM and look it up in
# CDP
cdp = json.loads(clid('show cdp neighbor interface {0}'.format(port)))
cdp = findkey(cdp, 'ROW_cdp_neighbor_brief_info')
if not cdp:
raise Exception('Unable to find {0} in CDP output'.format(port))
if len(cdp) > 0:
cdp = cdp[0]
return cdp
def main():
# Perform some basic argument parsing for parameters passed to the script
parser = ArgumentParser('Supercommand')
parser.add_argument(
'ip', help='IP address to query. Use all for every IP in arp')
args = parser.parse_args()
ip = args.ip
output = []
try:
if ip == 'all':
arpentries = getarpentry()
else:
arpentries = getarpentry(ip)
if not arpentries:
print 'Unable to find {0} in ARP table'.format(ip)
arpentries = []
for arp in arpentries:
depth = 2
ip, timer, mac, interface = arp
output += ['Here is some information on {0}:'.format(ip)]
if len(arpentries) > 1:
output += [' ' * depth + 'ARP entry on {0}'.format(interface)]
depth = 4
output += [' ' * depth + 'MAC address: {0}'.format(mac)]
output += [' ' * depth + 'L3 gateway: {0}'.format(interface)]
if 'Vlan' in interface:
vlanfilter = interface.split('Vlan')[1]
else:
vlanfilter = None
macentries = getmacentry(mac, vlanfilter=vlanfilter)
if not macentries:
print 'Unable to find {0} in MAC table'.format(mac)
macentries = []
topdepth = depth
for macentry in macentries:
depth = topdepth
# print "TITU", macentry
vlan, mac, entrytype, age, secure, ntfy, port, parentport = macentry
# print "TITU VLAN : ", vlan
if len(macentries) > 1:
output += [' ' * depth +
'Port Channel {0} member {1}'.format(parentport, port)]
depth += 2
output += [' ' * depth + 'Local interface: {0}'.format(port)]
output += [' ' * depth + 'VLAN: {0}'.format(vlan)]
if 'Eth' in port.lower() or 'mgmt' in port.lower():
cdp = getcdpentry(port)
else:
cdp = None
if cdp:
output += [' ' * depth +
'CDP Platform: {0}'.format(cdp['platform_id'])]
output += [' ' * depth +
'CDP Device ID: {0}'.format(cdp['device_id'])]
output += [' ' * depth +
'CDP Remote Port ID: {0}'.format(cdp['port_id'])]
finally:
print(chr(10).join(output))
if __name__ == '__main__':
main()