Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
barryo committed Oct 5, 2021
1 parent 3ed182b commit 5637ae0
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 104 deletions.
9 changes: 8 additions & 1 deletion app/Console/Commands/Switches/FdbTrawl.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,14 @@ public function handle()

foreach( $switches as $s ) {

$trawler = ( new FdbTrawler( $s, $vlan ) )->trawl();
try {
$trawler = ( new FdbTrawler( $s, $vlan ) )->trawl();
} catch( \Exception $e ) {
$this->error( "{$s->name}: could not trawl - exeception thrown - {$e->getMessage()}." );
continue;
}


die("\n\n\==ENDS==\n\n");
if( !$this->isVerbosityQuiet() ) {
$this->info( "Polling {$s->name} with SNMP requests to {$s->hostname}" );
Expand Down
249 changes: 147 additions & 102 deletions app/Tasks/Switch/FdbTrawl.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* http://www.gnu.org/licenses/gpl-2.0.html
*/

use Dompdf\Exception;
use IXP\Models\Switcher;

use IXP\Models\Vlan;
Expand Down Expand Up @@ -100,78 +101,89 @@ public function trawl()
'dot1qTpFdbPort' => '.1.3.6.1.2.1.17.7.1.2.2.1.2',
'dot1dTpFdbPort' => '.1.3.6.1.2.1.17.4.3.1.2',
'dot1dTpFdbAddress' => '.1.3.6.1.2.1.17.4.3.1.1',
'jnxExVlanTag' => '.1.3.6.1.4.1.2636.3.40.1.5.1.5.1.5',

'jnxL2aldVlanTag' => '.1.3.6.1.4.1.2636.3.48.1.3.1.1.3',
'jnxL2aldVlanFdbId' => '.1.3.6.1.4.1.2636.3.48.1.3.1.1.5',
);

logger( "FDBTRAWL: {$this->sw->name}: started query process" );

// $sysdescr = "ExtremeXOS (X670G2-48x-4q) version 21.1.3.7 21.1.3.7-patch1-7 by release-manager on Tue May 16 11:35:22 EDT 2017"
$sysdescr = $this->snmp->useSystem()->description();

if (preg_match('/Cisco\s+(NX-OS|IOS)/', $sysdescr)) {
logger()->warning( "FDBTRAWL: {$this->sw->name}: using community\@vlan hack to handle broken SNMP implementation" );
if( preg_match('/Cisco\s+(NX-OS|IOS)/', $sysdescr) ) {
logger()->warning( "FDBTRAWL: {$this->sw->name}: Cisco IOS/NX-OS detected - using community\@vlan hack to handle broken SNMP implementation" );
$this->snmp->setCommunity( $this->snmp->getCommunity() . '@' . $this->vlan->number );
}

$ifindex = $this->snmp->useIface()->descriptions();

if (!$ifindex) {
// $ifindexes => [
// 1001 => "X670G2-48x-4q Port 1"
// 1002 => "X670G2-48x-4q Port 2"
// 1003 => "X670G2-48x-4q Port 3"
// ...
//
if( !( $ifindexes = rescue( fn() => $this->snmp->useIface()->descriptions(), false, false ) ) ) {
logger()->warning( "FDBTRAWL: {$this->sw->name}: cannot read ifDescr. Not processing {$this->sw->name} further." );
return;
}

$interfaces = $this->snmp->useBridge()->basePortIfIndexes();
if (!$interfaces) {
// $interfaces => [
// 1 => 1001
// 2 => 1002
// 3 => 1003
// ...
if( !( $interfaces = rescue( fn() => $this->snmp->useBridge()->basePortIfIndexes(), false, false ) ) ) {
logger()->warning( "FDBTRAWL: {$this->sw->name}: cannot read dot1dBasePortIfIndex. Not processing {$this->sw->name} further." );
return;
}

$vlanmapping = null;

# if jnxExVlanTag returns something, then this is a juniper and we need to
# handle the interface mapping separately on these boxes
logger( "FDBTRAWL: {$this->sw->name}: pre-emptively trying Juniper jnxExVlanTag to see if we're on a J-EX box (" . $oids['jnxExVlanTag'] . ")" );
$juniperexmapping = false;
try { $vlanmapping = $this->snmp->walk1d($oids['jnxExVlanTag']); } catch( \Exception $e ) {}
if ( $vlanmapping ) {
// if jnxExVlanTag returns something, then this is a juniper and we need to
// handle the interface mapping separately on these boxes
//
// $vlanmapping = [
// 3 => 0
// 4 => 5
// 5 => 16
// 6 => 330 (330 is the 802.1q tag)
// ...
//
logger( "FDBTRAWL: {$this->sw->name}: pre-emptively trying Juniper jnxExVlanTag to see if we're on a J-EX box (" . self::OID_JNXEXVLANTAG . ")" );
if( $vlanmapping = rescue( fn() => $this->snmp->walk1d( self::OID_JNXEXVLANTAG ), false, false ) ) {
$juniperexmapping = true;
logger( "FDBTRAWL: {$this->sw->name}: looks like this is a Juniper EX" );
} else {
logger( "FDBTRAWL: {$this->sw->name}: this isn't a Juniper EX" );
}

if (!$vlanmapping) {
# Juniper KB32532:
#
# We start out with two arrays, jnxL2aldVlanTag and jnxL2aldVlanFdbId. We need to
# end up with a mapping from the value of jnxL2aldVlanFdbId pointing to the
# value of jnxL2aldVlanTag.
#
# jnxL2aldVlanTag.3 = 1
# jnxL2aldVlanTag.4 = 10
# jnxL2aldVlanTag.5 = 20
# jnxL2aldVlanFdbId.3 = 196608
# jnxL2aldVlanFdbId.4 = 262144
# jnxL2aldVlanFdbId.5 = 327680
#
# This gets mapped to
# array (
# 196608 => 1,
# 262144 => 10,
# 327680 => 20
# )
$jnxL2aldvlantag = false;
try { $jnxL2aldvlantag = $this->snmp->walk1d($oids['jnxL2aldVlanTag']); } catch( \Exception $e ) {}
if ($jnxL2aldvlantag) {
$juniperexmapping = false;
logger( "FDBTRAWL: {$this->sw->name}: this isn't a standard Juniper EX - now we test for EX running an ELS image" );

// Juniper KB32532:
//
// We start out with two arrays, jnxL2aldVlanTag and jnxL2aldVlanFdbId. We need to
// end up with a mapping from the value of jnxL2aldVlanFdbId pointing to the
// value of jnxL2aldVlanTag.
//
// jnxL2aldVlanTag.3 = 1
// jnxL2aldVlanTag.4 = 10
// jnxL2aldVlanTag.5 = 20
// jnxL2aldVlanFdbId.3 = 196608
// jnxL2aldVlanFdbId.4 = 262144
// jnxL2aldVlanFdbId.5 = 327680
//
// This gets mapped to
// array (
// 196608 => 1,
// 262144 => 10,
// 327680 => 20
// )

if( $jnxL2aldvlantag = rescue( fn() => $this->snmp->walk1d( self::OID_JNXL2ALDVLANTAG ), [], false ) ) {
logger( "FDBTRAWL: {$this->sw->name}: looks like this is a Juniper EX running an ELS image" );
$jnxL2aldvlanid = $this->snmp->walk1d($oids['jnxL2aldVlanFdbId']);
$jnxL2aldvlanid = rescue( fn() => $this->snmp->walk1d( self::OID_JNXL2ALDVLANFDBID ), [], false );

foreach (array_keys($jnxL2aldvlantag) as $index) {
foreach( array_keys( $jnxL2aldvlantag ) as $index) {
$vlanmapping[$jnxL2aldvlanid[$index]] = $jnxL2aldvlantag[$index];
}

if (!$vlanmapping) {
if( !$vlanmapping ) {
logger()->warning( "FDBTRAWL: {$this->sw->name}: Juniper ELS image detected but VLAN mapping retrieval failed. Not processing {$this->sw->name} further." );
return;
}
Expand All @@ -180,100 +192,103 @@ public function trawl()
}
}

# attempt to use Q-BRIDGE-MIB.
// attempt to use Q-BRIDGE-MIB.
logger( "FDBTRAWL: {$this->sw->name}: attempting to retrieve dot1qVlanFdbId mapping (".$oids['dot1qVlanFdbId'].")" );

if (!$vlanmapping) {
try { $vlanmapping = $this->snmp->walk1d($oids['dot1qVlanFdbId'].".0"); } catch( \Exception $e ) {}
if( !$vlanmapping ) {
$vlanmapping = rescue( fn() => $this->snmp->walk1d( self::OID_DOT1QVLANFDBID . ".0" ), [], false );
}

# At this stage we should have a dot1qVlanFdbId mapping, but
# some switches don't support it (e.g. Dell F10-S4810), so
# if it doesn't exist we'll attempt Q-BRIDGE-MIB with the
# VLAN IDs instead of mapped IDs.
// At this stage we should have a dot1qVlanFdbId mapping, but
// some switches don't support it (e.g. Dell F10-S4810), so
// if it doesn't exist we'll attempt Q-BRIDGE-MIB with the
// VLAN IDs instead of mapped IDs.

if ($vlanmapping) { # if this fails too, Q-BRIDGE-MIB is out
$vlan2idx = $this->array_reverse ($vlanmapping);
$vlanid = $vlan2idx[$vlan];
if ($debug) { print "DEBUG: $host: got mapping index: $vlan maps to $vlanid\n"; }
if( $vlanmapping ) { // if this fails too, Q-BRIDGE-MIB is out
$vlan2idx = array_flip( $vlanmapping );
$vlanid = $vlan2idx[$this->vlan->number];
logger( "FDBTRAWL: {$this->sw->name}: got mapping index: {$this->vlan->number} maps to switch DOT1Q VLAN FDB ID $vlanid" );
} else {
if ($debug) { print "DEBUG: $host: that didn't work either. attempting Q-BRIDGE-MIB with no fdb->ifIndex mapping\n"; }
$vlanid = $vlan;
logger( "FDBTRAWL: {$this->sw->name}: that didn't work either. attempting Q-BRIDGE-MIB with no fdb->ifIndex mapping" );
$vlanid = $this->vlan->number;
}

if ($debug) { print "DEBUG: $host: attempting Q-BRIDGE-MIB (".$oids['dot1qTpFdbPort'].".$vlanid)\n"; }
$qbridgehash = $this->snmpwalk2hash($oids['dot1qTpFdbPort'].".".$vlanid, [$this, 'oid2mac'], false, false);
logger( "FDBTRAWL: {$this->sw->name}: attempting Q-BRIDGE-MIB (" . self::OID_DOT1QTPFDBPORT . ".{$vlanid})" );

if ($qbridgehash) {
if ($debug) { print "DEBUG: $host: Q-BRIDGE-MIB query successful\n"; }
if( $qbridgehash = rescue( fn() => $this->snmp->walk1d( self::OID_DOT1QTPFDBPORT . ".{$vlanid}" ), [], false ) ) {
$qbridgehash = array_map( 'self::oid2mac', array_keys( $qbridgehash ), $qbridgehash );
logger( "FDBTRAWL: {$this->sw->name}: Q-BRIDGE-MIB query successful" );
} else {
if ($debug) { print "DEBUG: $host: dot1qTpFdbPort.$vlanid failed - attempting baseline dot1qTpFdbPort subtree walk in desperation\n"; }
logger( "FDBTRAWL: {$this->sw->name}: dot1qTpFdbPort.$vlanid failed - attempting baseline dot1qTpFdbPort subtree walk in desperation" );

# some stacks (e.g. Comware) don't support mib walk for
# dot1qTpFdbPort.$vlanid, so we'll attempt dot1qTpFdbPort instead, then
# filter out all the unwanted entries. This is inefficient and unusual, so
# it's the last option attempted.
// some stacks (e.g. Comware) don't support mib walk for
// dot1qTpFdbPort.$vlanid, so we'll attempt dot1qTpFdbPort instead, then
// filter out all the unwanted entries. This is inefficient and unusual, so
// it's the last option attempted.

$qbridgehash = $this->snmpwalk2hash($oids['dot1qTpFdbPort'], [$this, 'oid2mac'], false, $vlanid);
// $qbridgehash = snmpwalk2hash($host, $snmpcommunity, "$oids->{dot1qTpFdbPort}", \&oid2mac, undef, $vlanid);
// $qbridgehash = $this->snmpwalk2hash($oids['dot1qTpFdbPort'], [$this, 'oid2mac'], false, $vlanid);
// public function snmpwalk2hash ($queryoid, $keycallback, $valuecallback, $keyfilter)

if ($qbridgehash) {
if ($debug) { print "DEBUG: $host: Q-BRIDGE-MIB query ".($qbridgehash ? "successful" : "failed")."\n"; }
}
if ($debug) { print "DEBUG: $host: failed to retrieve Q-BRIDGE-MIB. falling back to BRIDGE-MIB\n"; }
}
if( $qbridgehashtmp = rescue( fn() => $this->snmp->subOidWalk( self::OID_DOT1QTPFDBPORT, count( explode ('.', self::OID_DOT1QTPFDBPORT ) ), -1 ), [], false ) ) {
$qbridgehash = [];
foreach( $qbridgehashtmp as $k => $v ) {
if( !preg_match( "/^($vlanid)\./", $k ) ) {
continue;
}

# special case: when the vlan is not specified, juniper EX boxes
# return data on Q-BRIDGE-MIB rather than BRIDGE-MIB
if (!$vlan && $juniperexmapping) {
if ($debug) { print "DEBUG: $host: attempting special Juniper EX Q-BRIDGE-MIB query for unspecified vlan\n"; }
$qbridgehash = $this->snmpwalk2hash($oids['dot1qTpFdbPort'], [$this, 'oid2mac'], false);
if ($debug) {
if ($qbridgehash) {
print "DEBUG: $host: Juniper EX Q-BRIDGE-MIB query successful\n";
} else {
print "DEBUG: $host: failed Juniper EX Q-BRIDGE-MIB retrieval\n";
$qbridgehash += self::oid2mac( $k, $v );
}
}

logger( "FDBTRAWL: {$this->sw->name}: Q-BRIDGE-MIB query " . ( $qbridgehash ? "successful" : "failed - falling back to BRIDGE-MIB") );
}

# if vlan wasn't specified or there's nothing coming in from the
# Q-BRIDGE mib, then use rfc1493 BRIDGE-MIB.
if (($vlan && !$qbridgehash) || (!$vlan && !$juniperexmapping)) {
if ($debug) { print "DEBUG: $host: attempting BRIDGE-MIB (".$oids['dot1dTpFdbPort'].")\n"; }
$dbridgehash = $this->snmpwalk2hash($oids['dot1dTpFdbPort'], false, false, false);
if ($debug and $dbridgehash) { print "DEBUG: $host: BRIDGE-MIB query successful\n"; }
// if there's nothing coming in from the Q-BRIDGE mib, then use rfc1493 BRIDGE-MIB.
$dbridgehash = [];
if( !$qbridgehash && !$juniperexmapping ) {
logger( "FDBTRAWL: {$this->sw->name}: attempting BRIDGE-MIB (" . self::OID_DOT1DTPFDBPORT . ")" );
if( $dbridgehash = rescue( fn() => $this->snmp->subOidWalk( self::OID_DOT1DTPFDBPORT, count( explode( '.', self::OID_DOT1DTPFDBPORT ) ), -1 ), [], false ) ) {
logger( "FDBTRAWL: {$this->sw->name}: BRIDGE-MIB query successful" );
}
}

# if this isn't supported, then panic. We could probably try
# community@vlan syntax, but this should be good enough.
if (!$qbridgehash && !$dbridgehash) {
print "WARNING: $host: cannot read BRIDGE-MIB or Q-BRIDGE-MIB. Not processing $host further.\n";
// if this isn't supported, then panic. We could probably try
// community@vlan syntax, but this should be good enough.
if( !$qbridgehash && !$dbridgehash ) {
logger( "FDBTRAWL: {$this->sw->name}: cannot read BRIDGE-MIB or Q-BRIDGE-MIB. Not processing {$this->sw->name} further." );
return;
}

if ($dbridgehash) {
$bridgehash2mac = $this->snmpwalk2hash($oids['dot1dTpFdbAddress'], false, [$this, 'normalize_mac'], false);
if( $dbridgehash ) {
$bridgehash2mac = array_map(
'self::normalize_mac',
rescue( fn() => $this->snmp->subOidWalk( self::OID_DOT1DTPFDBADDRESS, count( explode( '.', self::OID_DOT1DTPFDBADDRESS ) ) ), [], false )
);
$bridgehash = $dbridgehash;
$maptable = $bridgehash2mac;
} else {
$bridgehash = $qbridgehash;
$maptable = $qbridgehash;
}

foreach (array_keys($maptable) as $entry) {
if (isset($ifindex[$interfaces[$bridgehash[$entry]]])) {
$int = $ifindex[$interfaces[$bridgehash[$entry]]];
if ($juniperexmapping && preg_match('/\.\d+$/', $int)) {
dd($bridgehash2mac);
$macaddr = [];
foreach( $maptable as $entry => $v ) {
if( isset( $ifindex[ $interfaces[ $bridgehash[$entry] ] ] ) ) {
$int = $ifindex[ $interfaces[ $bridgehash[$entry] ] ];
if( $juniperexmapping && preg_match('/\.\d+$/', $int ) ) {
$int = preg_replace('/(\.\d+)$/','', $int);
}
if ($dbridgehash) {
if( $dbridgehash ) {
$entry = $bridgehash2mac[$entry];
}
$macaddr[$int][] = $entry;
}
}

return [];
dd($macaddr);

return $macaddr;

}

Expand Down Expand Up @@ -375,4 +390,34 @@ public function snmpwalk2hash ($queryoid, $keycallback, $valuecallback, $keyfilt
return ($returnhash);
}


# oid2mac: converts from dotted decimal format to contiguous hex

public static function oid2mac( $key, $value )
{
$hextets = explode(".", $key);

$hexmac = '';
foreach( $hextets as $hex ) {
$hexmac .= sprintf ('%02x', $hex);
}

return [ $hexmac => $value ];
}



public static function normalize_mac ($mac)
{
# translate this OCTET_STRING to hexadecimal, unless already translated
if ( strlen ($mac) != 12 ) {
$mac = bin2hex(stripslashes($mac));
}

$mac = strtolower($mac);

return ($mac);
}


}
1 change: 0 additions & 1 deletion bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ DROP DATABASE IF EXISTS \`ixpmanager\`;
CREATE DATABASE \`ixpmanager\` CHARACTER SET = 'utf8mb4' COLLATE = 'utf8mb4_unicode_ci';
CREATE USER \`ixpmanager\`@\`127.0.0.1\` IDENTIFIED BY 'ixpmanager';
GRANT ALL ON \`ixpmanager\`.* TO \`ixpmanager\`@\`127.0.0.1\`;
GRANT ALL ON \`ixpmanager\`.* TO \`ixpmanager\`@\`localhost\`;
FLUSH PRIVILEGES;
END_SQL

Expand Down

0 comments on commit 5637ae0

Please sign in to comment.