pvl.snmp.bridge: query bridge FDB for hosts
authorTero Marttila <terom@paivola.fi>
Tue, 18 Mar 2014 14:02:26 +0200
changeset 395 9de553b50128
parent 394 7077379fb5a0
child 396 dea2635763e6
pvl.snmp.bridge: query bridge FDB for hosts
bin/pvl.hosts-lldp
pvl/snmp/bridge.py
--- 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()