Zabbix MAC-Adressen an Switchports auslesen und beim Client anzeigen lassen an welchen Switch und Port dieser angeschlossen ist
Aus znilwiki
Changelog:
- 07.10.2026 erste öffentliche Version
Vorwort
Bei einem Kunden überwachen wir mit Zabbix auch alle CLients. Mit einem stark abgespeckten Template, die Admins interessieren sich an den Clients nur für wenige Dinge wie die Speicherplatzbelegung auf Laufwerk C:, wie schon nicht mehr gebootet wurde, die Seriennummern der Monitore und ob die IP-Adresseinstellungen stimmen.
In dem Zuge kam die Idee auf, beim Client auch gleich anzeigen zu lassen, an welchem Switch und Port dieser hängt, unter Latest data kann man sich das hier nun bei allen Clienst anzeigen lassen:
zusätzlich wird die Information auch umgekehrt hinterlegt, man könnte beim Switchport anzeigen lassen welcher Client daran hängt, die Information wird nach dem gleichen Prinzip (mehr dazu weiter in diesem Artikel) hinterlegt:
Das ganze funktioniert wie folgt:
- Aus den Switchen werden die MAC-Adressen ausgelesen die direkt an einem Switchport hängen, also nicht von einem anderem intelligenten Switch kommen. Ist da ein dummer Switch hinter, werden alle MACs von diesem übernommen.
- Das geschieht über ein Template welches ein Item vom Typ External Skript anstößt.
- Pro Switch werden diese Informationen im JSON-Format in einem Verzeichnis auf dem Zabbix-Server gespeichert (unterhalb von {{code|/var/lib/zabbix/externalscripts), pro MAC-Adresse eine Zeile in der Textdatei
- Die Client-Hosts haben ein Template welches per WMI die aktuelle MAC-Adresse ausliest
- Mit dieser MAC macht ein Item vom Typ External Skript ein grep auf das Verzeichnis mit den ablegten JSON der Switche. Es bekommt die Zeile mit den Daten im JSON-Format zurück
- Das JSON wird per Items vom Typ Dependent item in seine Einzelteile zerlegt.
In meinem Client-Template wird in einem 2. Unterordner pro Client ein JSON abgelegt mit diesen Daten. Darauf könnte man mit der gleichen Methode am Switch die Client-IP und Namen hinterlegen. Das habe ich nicht umgesetzt da hier die Beschreibung der Ports gepflegt wird, die Information wäre also redundant.
MAC-Adressen von Switchen abrufen
Meldet euch per SSH am Zabbix-Server an und wechselt in das External-Skript Verzeichnis:
cd /usr/lib/zabbix/externalscripts
Erstellt das Verzeichnis im welchen die JSONs abgelegt werden und setzt den Zabbix-Benutzer als Besitzer:
mkdir macfinder chown zabbix:zabbix macfinder
Das funktioniert je nach Switch anders, deshalb gibt es für verschiedene Switch-Hersteller und -Typen verschiedene Skripte:
Avaya-Switche
Das folgende Skript wurde mit Avaya-Switchen vom Typ
Modelle: 3510GT-PWR+
3524GT-PWR+
4950GTS
Firmware: 5.x
Software: v5.3.x
getestet.
Die MAC-Adressen bei diesen Modellen finden sich hinter OID
1.3.6.1.2.1.17.4.3.1.2
ein snmpwalk, z.B. so ein Aufruf
snmpwalk -v2c -On -c testcommu 192.168.0.1 1.3.6.1.2.1.17.4.3.1.2
gibt eine Ausgabe wie folgt:
.1.3.6.1.2.1.17.4.3.1.2.40.99.54.224.67.188 = INTEGER: 8193 .1.3.6.1.2.1.17.4.3.1.2.40.99.54.236.96.83 = INTEGER: 10 .1.3.6.1.2.1.17.4.3.1.2.40.99.54.236.96.84 = INTEGER: 10 .1.3.6.1.2.1.17.4.3.1.2.48.19.137.21.14.81 = INTEGER: 14 .1.3.6.1.2.1.17.4.3.1.2.48.19.137.21.148.216 = INTEGER: 77 .1.3.6.1.2.1.17.4.3.1.2.48.19.137.37.139.121 = INTEGER: 8193 .1.3.6.1.2.1.17.4.3.1.2.48.19.137.37.241.97 = INTEGER: 8193 .1.3.6.1.2.1.17.4.3.1.2.48.19.137.37.241.103 = INTEGER: 8193 .1.3.6.1.2.1.17.4.3.1.2.48.19.137.37.241.109 = INTEGER: 15 .1.3.6.1.2.1.17.4.3.1.2.48.19.137.37.241.110 = INTEGER: 15 .1.3.6.1.2.1.17.4.3.1.2.48.19.137.37.241.112 = INTEGER: 11 .1.3.6.1.2.1.17.4.3.1.2.48.19.137.37.241.113 = INTEGER: 11 .1.3.6.1.2.1.17.4.3.1.2.48.19.137.37.241.121 = INTEGER: 8193
Wenn der Wert am Ende 0 oder 8193 ist, ist es eine MAC die er von einem anderem Switch hat.
Die MAC selbst ist in der OID codiert, die letzten 6 Zahlen:
OID:
.1.3.6.1.2.1.17.4.3.1.2.48.19.137.37.241.112 = INTEGER: 11
die letzten 6 Zahlen:
48.19.137.37.241.112
sind die Dezimalwerte, in HEX ist das dann
30:13:89:25:f1:70
Der Wert dahinter ist die Portnummer, in diesem Fall die 11. Über die OID
.1.3.6.1.2.1.2.2.1.2.11
bekommen wir den Port Alias
.1.3.6.1.2.1.2.2.1.2.11 = STRING: Avaya Ethernet Routing Switch 3524GT PWR+ Module - Unit 1 Port 11
Über
.1.3.6.1.2.1.31.1.1.1.18.11
die Port Beschreibung
.1.3.6.1.2.1.31.1.1.1.18.11 = STRING: Panel W1
Aus diesen Informationen baut sich das Skript das zusammen.
Das eigentliche Skript:
nano macportnameiptable-avaya
#!/bin/bash
# $1 = Community
# $2 = IP-Address or FQDN
# $3 = Base OID
if [[ $# -ne 3 ]]; then
echo "Error - not 3 Parameter!"
echo "Usage:"
echo "macportnameiptable community IP-Address-or-FQDN Base-OID"
echo " "
exit 1
fi
#s_systemname=$(snmpget -v2c -O qv -c $1 $2 1.3.6.1.2.1.1.5.0)
# 1. Systemnamen abfragen (Fehlermeldungen abfangen)
s_systemname=$(snmpget -v2c -O qv -c $1 $2 1.3.6.1.2.1.1.5.0 2>/dev/null | xargs)
# 2. Wenn s_systemname leer ist oder "No Such..." enthält, IP-Adresse ($2) nutzen
if [ -z "$s_systemname" ] || [[ "$s_systemname" == *"No Such"* ]]; then
s_systemname="$2"
else
# Leerzeichen im Systemnamen durch Unterstriche ersetzen
s_systemname="${s_systemname// /_}"
fi
output_from_command=$(snmpwalk -v2c -O nq -c $1 $2 $3)
# -O Parameter:
# n = OID numerisch darstellen
# q = kurze Ausgabe - OID, Leerzeichen, Wert
result="[\n"
while IFS= read -r line; do
# Zeile in OID und Port aufteilen
# [0] ist die OID
# [1] ist der Port
stringarray=($line)
if [ "${stringarray[1]}" -ne "8193" ] && [ "${stringarray[1]}" -ne "0" ]; then
stringoid=${stringarray[0]}
#echo "${stringoid}"
IFS='.'; macnumbers=($stringoid); unset IFS;
# .1.3.6.1.2.1.17.4.3.1.2.248.177.86.223.76.59 8193
mac1=$( printf "%02x" ${macnumbers[12]} )
mac2=$( printf "%02x" ${macnumbers[13]} )
mac3=$( printf "%02x" ${macnumbers[14]} )
mac4=$( printf "%02x" ${macnumbers[15]} )
mac5=$( printf "%02x" ${macnumbers[16]} )
mac6=$( printf "%02x" ${macnumbers[17]} )
#echo "MAC: ${macnumbers[12]}:${macnumbers[13]}:${macnumbers[14]}:${macnumbers[15]}:${macnumbers[16]}:${macnumbers[17]}"
mac_complete="${mac1}:${mac2}:${mac3}:${mac4}:${mac5}:${mac6}"
# Beschreibung des Ports holen
s_portdescription=$(snmpget -v2c -O qv -c $1 $2 .1.3.6.1.2.1.31.1.1.1.18.${stringarray[1]})
s_portalias=$(snmpget -v2c -O qv -c $1 $2 .1.3.6.1.2.1.2.2.1.2.${stringarray[1]} | cut -d "-" -f 2 | xargs)
result="${result}{\"switchname\":\"${s_systemname}\",\"portnumber\":\"${stringarray[1]}\",\"portalias\":\"${s_portalias}\",\"portdescription\":\"${s_portdescription}\",\"MAC\":\"${mac_complete}\"},\n"
#result="${result}\"${stringarray[1]}\":\"${mac_complete}\",\n"
#echo ${stringarray[1]} --- ${stringarray[0]}
fi
done <<< "$output_from_command"
result="${result::-3}\n]"
echo -e $result > /usr/lib/zabbix/externalscripts/macfinder/${s_systemname}.json
echo -e $result
Das Skript ausführbar machen und den Besitz an den Benutzer zabbix übertragen:
chmod +x macportnameiptable-avaya chown zabbix:zabbix macportnameiptable-avaya
Testen könnt Ihr das Skript dann mit
sudo -u zabbix ./macportnameiptable snmp-community-string 192.168.0.1 1.3.6.1.2.1.17.4.3.1.2
Beispielausgabe:
[
{"switchname":"TESTSWITCH01","portnumber":"1","portalias":"Port 1","portdescription":"Zeiterfassungsterminal 54","MAC":"00:01:02:03:04:05"},
{"switchname":"TESTSWITCH01","portnumber":"4","portalias":"Port 4","portdescription":"PC Testbenutzer","MAC":"aa:bb:cc:00:00:f1"},
{"switchname":"TESTSWITCH01","portnumber":"4","portalias":"Port 4","portdescription":"PC Testbenutzer","MAC":"aa:bb:cc:00:00:f2"}
]
Zeitgleich zur Ausgabe hat das Skript auch eine Datei TESTSWITCH01.json im Unterverzeichnis macfinder abgelegt.
Der Name der Datei ist der des Switches, dieser wird per SNMP ermittelt. Ist der Name leer, wird die IP-Adresse genommen.
Der Inhalt ist der gleiche wie die Ausgabe.
Deshalb wurde der Befehl mit sudo ausgeführt damit der Besitzer der Datei dann auch zabbix ist.
Hier ist das Template für Zabbix 7.0.0:
Template_Avaya_Switch_MAC_V2026-10-07.zip
Das besteht nur aus einem Item:
In der Oberfläche könnte ihr unter Latest data kontrollieren ob der Abruf klappt.
Fortinet Switche
Das folgende Skript wurde mit Fortinet-Switchen / Fortiswitch vom Typ
Modelle: FortiSwitch-2048F
FortiSwitch-1048E
FortiSwitch-124F-FPOE
Firmware: v7.04.9
v7.06.6
getestet.
Die MAC-Adressen bei diesen Modellen finden sich hinter OID
.1.3.6.1.2.1.17.4.3.1.1
ein snmpwalk, z.B. so ein Aufruf
snmpwalk -v2c -On -c testcommu 192.168.0.1 .1.3.6.1.2.1.17.4.3.1.1
gibt eine Ausgabe wie folgt:
.1.3.6.1.2.1.17.4.3.1.1.1 = Hex-STRING: 58 38 79 3E 2D F2 .1.3.6.1.2.1.17.4.3.1.1.2 = Hex-STRING: DE 00 9E 6E 91 01 .1.3.6.1.2.1.17.4.3.1.1.3 = Hex-STRING: 62 72 E3 37 2D ED .1.3.6.1.2.1.17.4.3.1.1.4 = Hex-STRING: 00 26 73 E6 1A CD .1.3.6.1.2.1.17.4.3.1.1.5 = Hex-STRING: 4C D7 17 91 C7 32 .1.3.6.1.2.1.17.4.3.1.1.6 = Hex-STRING: 0A 3F 12 95 42 75 .1.3.6.1.2.1.17.4.3.1.1.7 = Hex-STRING: AC B4 80 3F 64 B6 .1.3.6.1.2.1.17.4.3.1.1.8 = Hex-STRING: 4C D7 17 7F DA D0 .1.3.6.1.2.1.17.4.3.1.1.9 = Hex-STRING: 4C D7 17 7F DA AD .1.3.6.1.2.1.17.4.3.1.1.10 = Hex-STRING: 1C D1 1A 0D 43 FF .1.3.6.1.2.1.17.4.3.1.1.11 = Hex-STRING: 1C D1 1A 0D 43 FE .1.3.6.1.2.1.17.4.3.1.1.12 = Hex-STRING: 7A 05 9F 21 1E BC .1.3.6.1.2.1.17.4.3.1.1.13 = Hex-STRING: 08 00 0F F7 15 A9 .1.3.6.1.2.1.17.4.3.1.1.14 = Hex-STRING: DC 67 23 08 29 88 .1.3.6.1.2.1.17.4.3.1.1.15 = Hex-STRING: 00 26 73 B8 99 50 ...
Die Hex-Werte sind schon die MAC-Adresse.
Leider stehen an dieser Stelle wirklich alle MAC-Adressen und man kann nicht erkennen, ob diese direkt verbunden sind oder über einen anderen Switch kommen.
Aber mit der letzten Nummer der OID kann man an diese OID gehen:
.1.3.6.1.2.1.17.4.3.1.2
und dort z.B. den Status des der Einträge abfragen:
.1.3.6.1.2.1.17.4.3.1.2.8 = INTEGER: 32768 .1.3.6.1.2.1.17.4.3.1.2.9 = INTEGER: 32768 .1.3.6.1.2.1.17.4.3.1.2.10 = INTEGER: 32768 .1.3.6.1.2.1.17.4.3.1.2.11 = INTEGER: 12 .1.3.6.1.2.1.17.4.3.1.2.12 = INTEGER: 6 .1.3.6.1.2.1.17.4.3.1.2.13 = INTEGER: 32768 .1.3.6.1.2.1.17.4.3.1.2.14 = INTEGER: 32768 .1.3.6.1.2.1.17.4.3.1.2.15 = INTEGER: 32768
Ist die Zahl eine große ist, hier also 32768 (16 Bit Ganzzahl maximum), ist es keine lokale MAC.
Bei 12 steht hier als Wert eine 6.
Damit lässt sich per OID
.1.3.6.1.2.1.17.1.4.1.2
die echte Portnummer abfragen:
.1.3.6.1.2.1.17.1.4.1.2.1 = INTEGER: 3 .1.3.6.1.2.1.17.1.4.1.2.2 = INTEGER: 4 .1.3.6.1.2.1.17.1.4.1.2.3 = INTEGER: 5 .1.3.6.1.2.1.17.1.4.1.2.4 = INTEGER: 6 .1.3.6.1.2.1.17.1.4.1.2.5 = INTEGER: 9 .1.3.6.1.2.1.17.1.4.1.2.6 = INTEGER: 10 .1.3.6.1.2.1.17.1.4.1.2.7 = INTEGER: 15 .1.3.6.1.2.1.17.1.4.1.2.8 = INTEGER: 16 .1.3.6.1.2.1.17.1.4.1.2.9 = INTEGER: 17 .1.3.6.1.2.1.17.1.4.1.2.10 = INTEGER: 18 .1.3.6.1.2.1.17.1.4.1.2.11 = INTEGER: 19 .1.3.6.1.2.1.17.1.4.1.2.12 = INTEGER: 20
Hier sehen wir, das die ID 6 für den Port 10 steht.
Über die OID
.1.3.6.1.2.1.2.2.1.2.10
bekommen wir den Port Alias
.1.3.6.1.2.1.2.2.1.2.10 = STRING: TESTPC02
Über
.1.3.6.1.2.1.31.1.1.1.18.10
die Port Beschreibung
.1.3.6.1.2.1.31.1.1.1.18.10 = STRING: port10
Aus diesen Informationen baut sich das Skript das zusammen.
Das eigentliche Skript:
nano macportnameiptable-fortinet
#!/bin/bash
# $1 = Community
# $2 = IP-Address or FQDN
# $3 = Base OID
if [[ $# -ne 3 ]]; then
echo "Error - not 3 Parameter!"
echo "Usage:"
echo "macportnameiptable community IP-Address-or-FQDN Base-OID"
echo " "
exit 1
fi
#s_systemname=$(snmpget -v2c -O qv -c $1 $2 1.3.6.1.2.1.1.5.0)
# 1. Systemnamen abfragen (Fehlermeldungen abfangen)
s_systemname=$(snmpget -v2c -O qv -c $1 $2 1.3.6.1.2.1.1.5.0 2>/dev/null | xargs)
# 2. Wenn s_systemname leer ist oder "No Such..." enthält, IP-Adresse ($2) nutzen
if [ -z "$s_systemname" ] || [[ "$s_systemname" == *"No Such"* ]]; then
s_systemname="$2"
else
# Leerzeichen im Systemnamen durch Unterstriche ersetzen
s_systemname="${s_systemname// /_}"
fi
output_from_command=$(snmpwalk -v2c -O nqx -c $1 $2 $3)
#echo "${output_from_command}"
# -O Parameter:
# n = OID numerisch darstellen
# q = kurze Ausgabe - OID, Leerzeichen, Wert
result="[\n"
while IFS= read -r line; do
# Beispielzeile: .1.3.6.1.2.1.17.4.3.1.1.248.177.86.223.76.59 "FE A0 49 BE 58 12 "
# 1. Vollständige OID der Zeile extrahieren
stringoid=$(echo "$line" | awk '{print $1}')
# 2. Suffix ab .1.3.6.1.2.1.17.4.3.1.1. isolieren (das ist die Dezimal-MAC)
mac_suffix=$(echo "$stringoid" | sed 's/^\.1\.3\.6\.1\.2\.1\.17\.4\.3\.1\.1\.//')
if [ -n "$mac_suffix" ]; then
# 3. Bridge-Port (bp) über dot1dTpFdbPort abfragen
fdb_port=$(snmpget -v2c -O qv -c $1 $2 .1.3.6.1.2.1.17.4.3.1.2.${mac_suffix} 2>/dev/null | xargs)
# Wenn kein Bridge-Port gefunden wird oder der Port 0 ist -> überspringen (Trunk/Uplink oder ungültig)
if [ -z "$fdb_port" ] || [ "$fdb_port" = "0" ] || [[ "$fdb_port" == *"No Such"* ]]; then
continue
fi
# 4. Bridge-Port in echten Interface Index (ifIndex) übersetzen
if_index=$(snmpget -v2c -O qv -c $1 $2 .1.3.6.1.2.1.17.1.4.1.2.${fdb_port} 2>/dev/null | xargs)
# 5. Filtern: Nur fortfahren, wenn ein gültiger numeric ifIndex ermittelt wurde
if [[ "$if_index" =~ ^[0-9]+$ ]]; then
# 6. Portbeschreibung und Alias abfragen
s_portdescription=$(snmpget -v2c -O qv -c $1 $2 .1.3.6.1.2.1.31.1.1.1.18.${if_index} 2>/dev/null | xargs)
s_portalias=$(snmpget -v2c -O qv -c $1 $2 .1.3.6.1.2.1.2.2.1.2.${if_index} 2>/dev/null | cut -d "-" -f 2 | xargs)
# Prüfen, ob eine gültige Portbeschreibung zurückkam (schließt Uplink/Virtuelle Interfaces aus)
if [[ "$s_portdescription" == *"No Such"* ]] || [[ "$s_portalias" == *"No Such"* ]]; then
continue
fi
# 7. MAC-Adresse aus der Ausgabe parsen (wie bisher)
raw_mac=$(echo "$line" | sed -n 's/.*"\([^"]*\)".*/\1/p' | xargs | tr '[:upper:]' '[:lower:]')
read -r mac1 mac2 mac3 mac4 mac5 mac6 <<< "$raw_mac"
mac_complete="${mac1}:${mac2}:${mac3}:${mac4}:${mac5}:${mac6}"
# JSON-Objekt zusammenbauen
result="${result}{\"switchname\":\"${s_systemname}\",\"portnumber\":\"${if_index}\",\"portalias\":\"${s_portalias}\",\"portdescription\":\"${s_portdescription}\",\"mac\":\"${mac_complete}\"},\n"
#echo "Port ${if_index} (${s_portalias}) --- MAC: ${mac_complete}"
fi
fi
done <<< "$output_from_command"
# 1. Prüfen, ob nach der Schleife nur der Initialwert "[\n" in $result steht
if [ "$result" = "[\n" ]; then
# Fallback: Kein einziges finales Endgerät am Switch gefunden
result_output="Keine direkten MAC-Adressen"
#echo "Info: Keine direkten MAC-Adressen an lokalen Ports gefunden."
else
# Validieren & Schließen des JSON-Arrays:
result_output="${result::-3}\n]"
fi
#result="${result::-3}\n]"
echo -e $result_output > /usr/lib/zabbix/externalscripts/macfinder/${s_systemname}.json
echo -e $result_output
#echo "Systemname: ${s_systemname}"
Das Skript ausführbar machen und den Besitz an den Benutzer zabbix übertragen:
chmod +x macportnameiptable-fortinet chown zabbix:zabbix macportnameiptable-fortinet
Testen könnt Ihr das Skript dann mit
sudo -u zabbix ./macportnameiptable-fortinet snmp-community-string 192.168.0.1 .1.3.6.1.2.1.17.4.3.1.1
Beispielausgabe:
[
{"switchname":"TESTSWITCH01","portnumber":"1","portalias":"Port 1","portdescription":"Zeiterfassungsterminal 54","MAC":"00:01:02:03:04:05"},
{"switchname":"TESTSWITCH01","portnumber":"4","portalias":"Port 4","portdescription":"PC Testbenutzer","MAC":"aa:bb:cc:00:00:f1"},
{"switchname":"TESTSWITCH01","portnumber":"4","portalias":"Port 4","portdescription":"PC Testbenutzer","MAC":"aa:bb:cc:00:00:f2"}
]
Zeitgleich zur Ausgabe hat das Skript auch eine Datei TESTSWITCH01.json im Unterverzeichnis macfinder abgelegt.
Der Name der Datei ist der des Switches, dieser wird per SNMP ermittelt. Ist der Name leer, wird die IP-Adresse genommen.
Der Inhalt ist der gleiche wie die Ausgabe.
Deshalb wurde der Befehl mit sudo ausgeführt damit der Besitzer der Datei dann auch zabbix ist.
Hier ist das Template für Zabbix 7.0.0:
Template_Avaya_Switch_MAC_V2026-10-07.zip
Das besteht nur aus einem Item:
In der Oberfläche könnte ihr unter Latest data kontrollieren ob der Abruf klappt.

