Similar to determining the port's VLAN assignment, finding the MAC address on the port is not entirely straightforward. The way a switch works is that it creates a table of port and MAC address assignments, and then sends data at the second layer (OSI model layer 2) based on this table. Moreover, it maintains these tables independently for each VLAN. This means that we need to download this table from the switch. This table is referred to as the CAM table (Content Addressable Memory) or simply the MAC address table.
The general procedure looks like this:
- create a list of ports with basic information
- assign to the ports whether they are in trunk mode or not
- create a list of VLANs
- go through the VLANs and create a MAC address and port table for each one
- go through the MAC address tables and based on the port, add the MAC address to the port list where the port is not a trunk
Note: In the example, I assume that only an end device is connected to the port, so a maximum of one address will be found for the port. Or that the port is a trunk that connects it to another switch, and then I'm not interested in the MAC addresses. If a switch/hub or, for example, an IP phone to which a PC is connected is connected somewhere, I will get multiple MAC addresses for one port and would have to create an array.
Creating a List of Ports/Interfaces
This step was resolved in the article Switch port information using SNMP and PHP.
Determining Trunk Ports
This step was addressed in the article Port to VLAN information for Cisco Switches using SNMP and PHP.
List of VLANs
This step was addressed in the article Port to VLAN information for Cisco Switches using SNMP and PHP.
Iterating Through VLANs and Loading CAM Tables
To download the MAC address and port table, we will use the BRIDGE-MIB MIB file. In it, there is .iso.org.dod.internet.mgmt.mib-2.dot1dBridge.dot1dTp.dot1dTpFdbTable with OID = 1.3.6.1.2.1.17.4.3, and we are interested in the following OIDs:
| Name | OID | Description |
|---|---|---|
| dot1dTpFdbAddress | .1.3.6.1.2.1.17.4.3.1.1 | list of MAC addresses for which the switch has forward information |
| dot1dTpFdbPort | .1.3.6.1.2.1.17.4.3.1.2 | port number on which the given MAC address was learned |
| dot1dTpFdbStatus | .1.3.6.1.2.1.17.4.3.1.3 | record status, we're interested in 3 = learned |
To access these values, the hybrid community string is used again (Cisco calls this method community string indexing), because there is only one instance in the MIB and we need to get the values for each VLAN. We append @VLAN to the community string, so for example public@30 to load the values from VLAN 30. If we don't use this extension, the values from VLAN 1 are returned.
Using snmpwalk, we go through the OID for dot1dTpFdbAddress, which gives us a list of MAC addresses. The OID is supplemented with several numeric indices after its number. One returned record may look like this:
[17.4.3.1.1.0.11.205.197.114.60] => Hex: 00 0B CD C5 72 3C
In the same way, we go through dot1dTpFdbStatus and determine whether to count this MAC address.
[17.4.3.1.1.0.11.205.197.114.60] => 3
And using dot1dTpFdbPort, we get the corresponding port numbers.
[17.4.3.1.1.0.11.205.197.114.60] => 54
By combining the values with the same index, in this case 0.11.205.197.114.60, we get that the MAC address 00:0B:CD:C5:72:3C is on port 54. However, we work with port indices (IfIndex), so we still need to find the assignment of the index to the port number (we have already used this OID for VLANs).
| Name | OID | Description |
|---|---|---|
| dot1dBasePortIfIndex | .1.3.6.1.2.1.17.1.4.1.2 | returns the interface index for the given port number |
Here, the index is given by the last value, which is the port number for which we are looking for the `ifIndex`.
[17.1.4.1.2.54] => 10202
Implementation in PHP
The following code contains only the function for obtaining MAC addresses and builds on the code from the previous parts.
// loads all values under the OID and returns them as an array, the index is the last number of the OID function getInterPart2(&$interfaces, $ip, $comm, $oid) { $array = snmprealwalk($ip, $comm, $oid); foreach($array as $key => $val) { $index = ereg_replace('.*\.([0-9]+)$', "\\1", $key); $val = format_snmp_string($val); $interfaces[$index] = $val; } } // loads all values under the OID and returns them as an array, the index is composed of several last numbers of the OID function getInterPart3(&$interfaces, $ip, $comm, $oid) { $array = snmprealwalk($ip, $comm, $oid); $oidl = strlen($oid) - 12; foreach($array as $key => $val) { $index = substr($key, $oidl); $val = format_snmp_string($val); $interfaces[$index] = $val; } } // fills the port array with the MAC address function getMACs(&$interfaces, $ip, $comm) { $vlans = array(); getInterPart($vlans, $ip, $comm, ".1.3.6.1.4.1.9.9.46.1.3.1.1.18", "Index"); // VLAN ifIndex getInterPart($vlans, $ip, $comm, ".1.3.6.1.4.1.9.9.46.1.3.1.1.3", "Type"); // VLAN type, 1 - OK foreach($vlans as $vlan_id => $vlan) if($vlan["Type"] == 1) { $macs = array(); getInterPart3($macs, $ip, $comm."@".$vlan_id, ".1.3.6.1.2.1.17.4.3.1.1"); // list of MAC addresses for the given VLAN $bridge = array(); getInterPart3($bridge, $ip, $comm."@".$vlan_id, ".1.3.6.1.2.1.17.4.3.1.2"); // determine which bridge port the MAC address belongs to $ifI = array(); getInterPart2($ifI, $ip, $comm."@".$vlan_id, ".1.3.6.1.2.1.17.1.4.1.2"); // determine the interface index for the bridge port foreach($macs as $id => $mac) { // go through the list of MAC addresses $ifIndex = $ifI[$bridge[$id]]; // get the ifIndex from the record index, converted to port number and then to ifIndex if($interfaces[$ifIndex]["Trunk"] == 2) $interfaces[$ifIndex]["MAC"] = $mac; else $interfaces[$ifIndex]["MAC"] = ""; } } }
Mam prosbu.
Jak mam zjistit fyzickou topologii?
Resp chci detekovat i spoje mezi switchi, ktere spannig tree algoritmus "blokuje".
S tim ze je i zakazan(neni podporovan) CDP.
Moc dik za odpověďl
great post ;)
my Czech is not that good so excuse me if you have pointed this out, but just to clarify - the numerical indexes after dot1dTpFdbAddress in the example are actually the decimal equivalents of the 6 hex tuples of the mac address ;)
ve funkci getMACs je imho chybka, alespon u me to takhle nefungovalo. Konkretne se jedna o radek
$ifI[$bridge[$id]]; // ifIndex dostaneme z indexu záznamu, převedený na číslo portu a pak na ifIndex
ve kterem je predano $id, ale toto $id se musi v jednom cisle OID lisit. Da se to vyresit nasledovne:
$ifI[$bridge[str_replace(".1.17.4.3.1.1", ".1.17.4.3.1.2", $id]];
respond to [3]jimmy: jeste opravene TYPO
$ifI[$bridge[str_replace(\".1.17.4.3.1.1\", \".1.17.4.3.1.2\", $id)]];
respond to [3]jimmy: Je to už hodně dlouho, co jsem se tomu věnoval, než bych si to osvěžil, tak by to zabralo moc času. Ale tak, jak je to v příkladu, to mám i v aplikaci, kterou stále používám, a vše funguje korektně.