Podobně jako určení zařazení portu do VLANy, ani nalezení MAC adresy na portu není úplně jednoduché. Princip práce switche je takový, že si vytváří tabulku přiřazení portu a MAC adresy a podle ní pak posílá data na druhé vrstvě (layer 2 OSI model). Navíc si tyto tabulky udržuje nezávisle pro každou VLANu. Z toho plyne, že my potřebujeme stáhnout ze switche tuto tabulku. Ta se označuje jako CAM tabulka (Content Addressable Memory) nebo jednoduše mac address table.
Obecný postup vypadá takto:
- vytvoříme seznam portů se základními údaji
- k portům přiřadíme, zda jsou v trunk módu či ne
- vytvoříme seznam VLAN
- projdeme VLANy a pro každou vytvoříme tabulku MAC adres a portů
- projdeme tabulky MAC adres a podle portu doplníme seznam portů o MAC adresu tam, kde port není trunk
Pozn.: V příkladu vycházím z toho, že mám do portu zapojeno pouze koncové zařízení, takže se pro port nalezne maximálně jedna adresa. Nebo že port je trunk, který ho spojuje s jiným switchem, a pak mne MAC adresy nezajímají. Pokud by někde byl připojen switch/hub nebo například IP telefon, do kterého je připojeno PC, tak dostanu pro jeden port více MAC adres a musel bych vytvářet pole.
Vytvoření seznamu portů/interfaců
Tento bod byl vyřešen v článku Informace o portech switche pomocí SNMP a PHP.
Určení trunk portů
Tento bod byl řešen v článku Informace o zařazení portu do VLANy u Cisco Switchů pomocí SNMP a PHP.
Seznam VLAN
Tento bod byl řešen v článku Informace o zařazení portu do VLANy u Cisco Switchů pomocí SNMP a PHP.
Procházení VLAN a načtení CAM tabulek
Pro stažení tabulky MAC adres a portů využijeme MIB soubor BRIDGE-MIB. V něm se nachází .iso.org.dod.internet.mgmt.mib-2.dot1dBridge.dot1dTp.dot1dTpFdbTable
s OID = 1.3.6.1.2.1.17.4.3 a zajímat nás budou tato OID:
jméno | OID | popis |
---|---|---|
dot1dTpFdbAddress | .1.3.6.1.2.1.17.4.3.1.1 | seznam MAC adres, pro které má switch forward informace |
dot1dTpFdbPort | .1.3.6.1.2.1.17.4.3.1.2 | číslo portu, na kterém byla naučena daná MAC adresa |
dot1dTpFdbStatus | .1.3.6.1.2.1.17.4.3.1.3 | stav záznamu, nás zajímá 3 = learned |
Pro přístup k těmto hodnotám se opět využívá hybridní community string (Cisco tuto metodu nazývá community string indexing), protože v MIB existuje pouze jedna instance a my potřebujeme získat hodnoty pro každou VLANu. Za community string doplníme @VLAN
, takže například public@30
pro načtení hodnot z VLAN 30. Pokud nepoužijeme toto rozšíření, tak se vrací hodnoty z VLAN 1.
Pomocí snmpwalk
projdeme OID pro dot1dTpFdbAddress
, tím získáme seznam MAC adres. OID je za svým číslem doplněno několika číselným indexem. Jeden vrácený záznam může vypadat třeba takto:
[17.4.3.1.1.0.11.205.197.114.60] => Hex: 00 0B CD C5 72 3C
Stejným způsobem projdeme dot1dTpFdbStatus
a určíme, zda tuto MAC adresu počítat.
[17.4.3.1.1.0.11.205.197.114.60] => 3
A pomocí dot1dTpFdbPort
dostaneme patřičná čísla portu.
[17.4.3.1.1.0.11.205.197.114.60] => 54
Tím, že složíme hodnoty se stejným indexem, v tomto případě 0.11.205.197.114.60
, tak dostaneme, že na portu 54 je MAC adresa 00:0B:CD:C5:72:3C. My však pracujeme s indexy portů (IfIndex), takže ještě nalezneme přiřazení indexu k číslu portu (toto OID jsme již použili u VLAN).
jméno | OID | popis |
---|---|---|
dot1dBasePortIfIndex | .1.3.6.1.2.1.17.1.4.1.2 | pro číslo portu vrátí index interfacu |
Zde je index dán poslední hodnotou a to je naše číslo portu, pro které hledáme ifIndex
.
[17.1.4.1.2.54] => 10202
Realizace v PHP
Následující kód obsahuje pouze funkci pro získání MAC adres a navazuje na kód v předchozích dílech.
// načte všechny hodnoty pod OID a vrátí je jako pole, index – poslední číslo 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; } } // načte všechny hodnoty pod OID a vrátí je jako pole, index – složený z několika posledních čísel 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; } } // pole portů doplní o MAC adresu 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"); // seznam MAC adres pro danou VLAN $bridge = array(); getInterPart3($bridge, $ip, $comm."@".$vlan_id, ".1.3.6.1.2.1.17.4.3.1.2"); // urceni, kteremu bridge portu MAC adresa patri $ifI = array(); getInterPart2($ifI, $ip, $comm."@".$vlan_id, ".1.3.6.1.2.1.17.1.4.1.2"); // k bridge portu urci jeho interface index foreach($macs as $id => $mac) { // projdeme seznam MAC adres $ifIndex = $ifI[$bridge[$id]]; // ifIndex dostaneme z indexu záznamu, převedený na číslo portu a pak na 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]];
odpověď na [3]jimmy: jeste opravene TYPO
$ifI[$bridge[str_replace(\".1.17.4.3.1.1\", \".1.17.4.3.1.2\", $id)]];
odpověď na [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ě.