--- a/bin/pvl.hosts-lldp Tue Mar 18 13:59:22 2014 +0200
+++ b/bin/pvl.hosts-lldp Tue Mar 18 14:02:26 2014 +0200
@@ -8,7 +8,7 @@
import pvl.args
import pvl.hosts
from pvl.invoke import merge
-from pvl.snmp import snmp, lldp, vlan
+from pvl.snmp import snmp, lldp, vlan, bridge
import logging; log = logging.getLogger('pvl.hosts-lldp')
import optparse
@@ -79,6 +79,27 @@
yield host, agent
+def hosts_bridge (options, hosts) :
+ """
+ Discover Bridge-supporting hosts.
+
+ Yields Host, BridgeAgent
+ """
+
+ for host, host_snmp in hosts_snmp(options, hosts) :
+ agent = bridge.BridgeAgent.apply(options, host.fqdn(), community=host_snmp.get('community'))
+
+ try :
+ agent.ping()
+ except snmp.SNMPError as ex :
+ log.warning("%s: %s", host, ex)
+ continue
+
+ log.info("%s", host)
+
+ yield host, agent
+
+
def apply_hosts_lldp (options, hosts) :
"""
Query host LLDP info.
@@ -111,6 +132,8 @@
def apply_hosts_vlan (options, hosts) :
"""
Query host VLAN ports.
+
+ Yields host, { port: (untagged, [tagged]) }
"""
_hosts_vlan = list(hosts_vlan(options, hosts))
@@ -141,6 +164,22 @@
) for port in set(vlan_untagged) | set(vlan_tagged)
)
+def apply_hosts_bridge (options, hosts) :
+ """
+ Query host bridge tables.
+
+ Yields host, { port: (macs) }
+ """
+
+ for host, agent in hosts_bridge(options, hosts) :
+ ports = collections.defaultdict(list)
+
+ for ether, port in agent.fdb() :
+ if port :
+ ports[port].append(ether)
+
+ yield host, ports
+
COLOR_VLANS = {
1: 'grey', # pvl-lan
2: 'blue', # pvl-lan2
@@ -344,6 +383,9 @@
# discover node/port graph
items = apply_hosts_lldp(options, hosts)
+ # discover edge nodes
+ leafs = apply_hosts_bridge(options, hosts)
+
# print
if options.graph_dot :
apply_graph(options, items, vlans)
@@ -353,8 +395,10 @@
print "{host:30} {host.location:>30} {local[port]:>25} <-> {remote[port]:<25} {remote_host.location:>30} # {remote[chassis]} ({remote_host})".format(host=host, local=local, remote=remote, remote_host=remote_host)
else :
print "{host:30} {host.location:>30} {local[port]:>25} <-- {remote[port]:<25} {empty:30} # {remote[chassis]} ({remote[sys_name]})".format(host=host, local=local, remote=remote, empty='')
-
-
+
+ for host, ports in leafs :
+ for port, ethers in ports.iteritems() :
+ print "{host:30} {host.location:>30} {port:25} <== # {ethers}".format(host=host, port=port, ethers=' '.join(ethers))
if __name__ == '__main__':
pvl.args.main(main)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/snmp/bridge.py Tue Mar 18 14:02:26 2014 +0200
@@ -0,0 +1,57 @@
+"""
+ Requirements:
+ memoized-property
+
+ Setup:
+ ./opt/bin/build-pysnmp-mib -o usr/mibs/BRIDGE-MIB.py etc/mibs/rfc1493.mib
+
+ Run:
+ PYSNMP_MIB_DIRS=usr/mibs/ ./opt/bin/python ...
+
+ SNMP:
+ BRIDGE-MIB::dot1dTpFdbTable
+
+"""
+
+from pvl.snmp import snmp
+from pvl.snmp.snmp import pysnmp
+
+from memoized_property import memoized_property
+
+import logging; log = logging.getLogger('pvl.snmp.vlan')
+
+DOT1D_TP_AGING_TIME = pysnmp.MibVariable('BRIDGE-MIB', 'dot1dTpAgingTime', 0)
+DOT1D_TP_FDB_PORT = pysnmp.MibVariable('BRIDGE-MIB', 'dot1dTpFdbPort')
+
+def macaddr (value) :
+ """
+ Excepts a MAC address from an SNMP OctetString.
+ """
+
+ return ':'.join('{octet:02x}'.format(octet=c) for c in value.asNumbers())
+
+class BridgeAgent (snmp.SNMPAgent) :
+ """
+ Query Bridge configuration.
+
+ XXX: this is not VLAN-aware!
+ """
+
+ def ping (self) :
+ for name, value in self.get(DOT1D_TP_AGING_TIME) :
+ # XXX: ????
+ if value.tagSet == pysnmp.rfc1905.NoSuchInstance.tagSet :
+ continue
+
+ return True
+
+ def fdb (self) :
+ for idx, data in self.table(DOT1D_TP_FDB_PORT) :
+ addr, = idx
+ port = data[DOT1D_TP_FDB_PORT]
+
+ yield macaddr(addr), int(port)
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()