2
0
mirror of https://github.com/xcat2/confluent.git synced 2024-11-26 11:30:23 +00:00

Implement the next layer of switch discovery

Refactor the snmputil to be object oriented to simplify upstream code.  Implement
a method to generate a mac address to ifName/ifDescr for a given switch.
This commit is contained in:
Jarrod Johnson 2016-06-29 11:26:46 -04:00
parent 54e135f210
commit 147b3952e0
3 changed files with 153 additions and 42 deletions

View File

@ -0,0 +1,97 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016 Lenovo
#
# 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 provides the implementation of locating MAC addresses on ethernet
# switches. It is, essentially, a port of 'MacMap.pm' to confluent.
# However, there are enhancements.
# For one, each switch interrogation is handled in an eventlet 'thread'
# For another, MAC addresses are checked in the dictionary on every
# switch return, rather than waiting for all switches to check in
# (which makes it more responsive when there is a missing or bad switch)
# Also, we track the quantity, actual ifName value, and provide a mechanism
# to detect ambiguous result (e.g. if two matches are found, can log an error
# rather than doing the wrong one, complete with the detected ifName value).
# Further, the map shall be available to all facets of the codebase, not just
# the discovery process, so that the cached data maintenance will pay off
# for direct queries
# this module will provide mac to switch and full 'ifName' label
# This functionality is restricted to the null tenant
import confluent.exceptions as exc
import confluent.snmputil as snmp
_macmap = {}
def _map_switch(switch, password, user=None):
"""Manipulate portions of mac address map relevant to a given switch
"""
# 1.3.6.1.2.1.17.7.1.2.2.1.2 - mactoindex (qbridge - preferred)
# if not, check for cisco and if cisco, build list of all relevant vlans:
# .1.3.6.1.4.1.9.9.46.1.6.1.1.5 - trunk port vlan map (cisco only)
# .1.3.6.1.4.1.9.9.68.1.2.2.1.2 - access port vlan map (cisco only)
# if cisco, vlan community string indexed or snmpv3 contest for:
# 1.3.6.1.2.1.17.4.3.1.2 - mactoindx (bridge - low-end switches and cisco)
# .1.3.6.1.2.1.17.1.4.1.2 - bridge index to if index map
# no vlan index or context for:
# .1.3.6.1.2.1.31.1.1.1.1 - ifName... but some switches don't do it
# .1.3.6.1.2.1.2.2.1.2 - ifDescr, usually useless, but a
# fallback if ifName is empty
#
haveqbridge = False
mactobridge = {}
conn = snmp.Session(switch, password, user)
for vb in conn.walk('1.3.6.1.2.1.17.7.1.2.2.1.2'):
haveqbridge = True
oid, bridgeport = vb
oid = str(oid).rsplit('.', 6) # if 7, then oid[1] would be vlan id
macaddr = '{0:02x}:{1:02x}:{2:02x}:{3:02x}:{4:02x}:{5:02x}'.format(
*([int(x) for x in oid[-6:]])
)
mactobridge[macaddr] = int(bridgeport)
if not haveqbridge:
raise exc.NotImplementedException('TODO: Bridge-MIB without QBRIDGE')
bridgetoifmap = {}
for vb in conn.walk('1.3.6.1.2.1.17.1.4.1.2'):
bridgeport, ifidx = vb
bridgeport = int(str(bridgeport).rsplit('.', 1)[1])
bridgetoifmap[bridgeport] = int(ifidx)
ifnamemap = {}
havenames = False
for vb in conn.walk('1.3.6.1.2.1.31.1.1.1.1'):
ifidx, ifname = vb
if not ifname:
continue
havenames = True
ifidx = int(str(ifidx).rsplit('.', 1)[1])
ifnamemap[ifidx] = str(ifname)
if not havenames:
for vb in conn.walk( '1.3.6.1.2.1.2.2.1.2'):
ifidx, ifname = vb
ifidx = int(str(ifidx).rsplit('.', 1)[1])
ifnamemap[ifidx] = str(ifname)
localmap = {}
for mac in mactobridge:
localmap[mac] = ifnamemap[bridgetoifmap[mactobridge[mac]]]
print(repr(localmap))
if __name__ == '__main__':
# invoke as switch community
import sys
_map_switch(sys.argv[1], sys.argv[2])

View File

@ -37,53 +37,67 @@ def _get_transport(name):
return snmp.UdpTransportTarget(res[0][4])
def walk(server, oid, secret, username=None, context=None):
"""Walk over children of a given OID
class Session(object):
This is roughly equivalent to snmpwalk. It will automatically try to be
an snmpbulkwalk if possible. If username is not given, it is assumed that
the secret is a community string, and v2c is used. If a username given,
it'll assume SHA auth and DES privacy with the secret being the same for
both.
def __init__(self, server, secret, username=None, context=None):
"""Create a new session to interrogate a switch
:param server: The network name/address to target
:param oid: The SNMP object identifier
:param secret: The community string or password
:param username: The username for SNMPv3
:param context: The SNMPv3 context or index for community string indexing
"""
# SNMP is a complicated mess of things. Will endeavor to shield caller
# from as much as possible, assuming reasonable defaults where possible.
# there may come a time where we add more parameters to override the
# automatic behavior (e.g. DES is weak, so it's a likely candidate to be
# overriden, but some devices only support DES)
tp = _get_transport(server)
ctx = snmp.ContextData(context)
if '::' in oid:
mib, field = oid.split('::')
obj = snmp.ObjectType(snmp.ObjectIdentity(mib, field))
else:
obj = snmp.ObjectType(snmp.ObjectIdentity(oid))
eng = snmp.SnmpEngine()
if username is None:
# SNMP v2c
authdata = snmp.CommunityData(secret, mpModel=1)
else:
authdata = snmp.UsmUserData(username, authKey=secret, privKey=secret)
walking = snmp.bulkCmd(eng, authdata, tp, ctx, 0, 10, obj,
lexicographicMode=False)
for rsp in walking:
errstr, errnum, erridx, answers = rsp
if errstr:
raise exc.TargetEndpointUnreachable(str(errstr))
elif errnum:
raise exc.ConfluentException(errnum.prettyPrint())
for ans in answers:
yield ans
If username is not given, it is assumed that
the secret is community string, and v2c is used. If a username given,
it'll assume SHA auth and DES privacy with the secret being the same
for both.
:param server: The network name/address to target
:param secret: The community string or password
:param username: The username for SNMPv3
:param context: The SNMPv3 context or index for community indexing
"""
self.server = server
self.context = context
if username is None:
# SNMP v2c
self.authdata = snmp.CommunityData(secret, mpModel=1)
else:
self.authdata = snmp.UsmUserData(username, authKey=secret,
privKey=secret)
self.eng = snmp.SnmpEngine()
def walk(self, oid):
"""Walk over children of a given OID
This is roughly equivalent to snmpwalk. It will automatically try to
be a snmpbulkwalk if possible.
:param oid: The SNMP object identifier
"""
# SNMP is a complicated mess of things. Will endeavor to shield caller
# from as much as possible, assuming reasonable defaults when possible.
# there may come a time where we add more parameters to override the
# automatic behavior (e.g. DES is weak, so it's likely to be
# overriden, but some devices only support DES)
tp = _get_transport(self.server)
ctx = snmp.ContextData(self.context)
if '::' in oid:
mib, field = oid.split('::')
obj = snmp.ObjectType(snmp.ObjectIdentity(mib, field))
else:
obj = snmp.ObjectType(snmp.ObjectIdentity(oid))
walking = snmp.bulkCmd(self.eng, self.authdata, tp, ctx, 0, 10, obj,
lexicographicMode=False)
for rsp in walking:
errstr, errnum, erridx, answers = rsp
if errstr:
raise exc.TargetEndpointUnreachable(str(errstr))
elif errnum:
raise exc.ConfluentException(errnum.prettyPrint())
for ans in answers:
yield ans
if __name__ == '__main__':
import sys
for kp in walk(sys.argv[1], sys.argv[2], 'public'):
ts = Session(sys.argv[1], 'public')
for kp in ts.walk(sys.argv[2]):
print(str(kp[0]))
print(str(kp[1]))