PhpIPAM MAC-Adressen per arp ermitteln und hinzufügen
Aus znilwiki
Changelog:
- 19.05.2025 erste Version
Vorwort
phpIPAM kann zu den IP-Adressen auch die MAC-Adressen zuordnen:
Dazu wird jedoch ausschließlich SNMP verwendet. Man muss ein Gerät vom Typ Switch einrichten und die Abfrage der MAC-Adress-Tabelle per SNMP einrichten.
Das macht ja durchaus sinn, jedoch sind meine Allnet-Switche zum Beispiel nicht kompatibel damit.
Im Internet fand ich dieses Skript hier:
https://github.com/lazynooblet/scripts/tree/main/bash/phpipam-update-mac-from-arp
welches die ARP-Tabelle auf dem Linux-System nutzt vom welches es aufgerufen wird.
Ich habe das Skript in 2 Punkten abgepasst:
- Im Original wird sich per Benutzername und Passwort angemeldet um sich den API-Key zu holen. Meine Variante nutzt einfach gleich den API Key (und versucht auch nicht diesen am Ende zu löschen)
- Ich lasse vorher einen fping über den Bereich laufen damit der ARP-Cache auch frisch gefüllt ist.
Das Skript kann natürlich nur MAC-Adressen von Systemen ermitteln die im gleichen IP-Adressbereich liegen wie die das System von dem aus es ausgeführt wird.
Das Skript muss aber auch nicht unbedingt auf dem gleichen Host wie phpIPAM laufen
Voraussetzungen
Folgende Pakete müssen installiert sein:
apt install fping jq net-tools
Das geänderte Skript
nano phpipam-update-mac-from-arp_V1.1.sh
#!/bin/bash
# V1.1
# Original by "lazynooblet" => https://github.com/lazynooblet/scripts/tree/main/bash/phpipam-update-mac-from-arp
# Modified 2025-05-19 by Bernhard Linz => https://znil.net/index.php?title=PhpIPAM_MAC-Adressen_per_arp_ermitteln_und_hinzuf%C3%BCgen
# Changes V1.1:
# - Remove USER and PASSWORD Authentification, use TOKEN instead (with App security "SSL with App code token")
# - Add fping before checking MAC-Adresses to fill up ARP-Cache
# - Skip faulty MACs also on german OS withot error message
URL='https://phpipam.znil.local/api'
APP='ARP-CronJob'
TOKEN='dI4iAJlvuo9z78xlCUt5N4ubmy1FPyoL'
SUBNET='192.168.0.0/22'
INTERFACE='ens33'
# INVALIDTAGS=("3" "4")
INVALIDTAGS=("99")
echo "[INFO] fping subnet ${SUBNET}"
fping -4 -a -q -g ${SUBNET}
echo "[INFO] Fetching subnet id"
SUBNETID=$(curl -X GET --header "token: ${TOKEN}" ${URL}/${APP}/subnets/search/${SUBNET} -s | jq --raw-output .data[0].id)
if [ "$?" != "0" ]; then
echo "[FAIL] Error fetching subnet id from PHPIPAM" 1>&2
exit 1
fi
if [ -z "${SUBNETID}" ]; then
echo "[FAIL] Didn't receive valid subnet id from PHPIPAM" 1>&2
exit 1
fi
_update() {
ip=${1}
mac=${2}
IPID=$(curl -X GET --header "token: ${TOKEN}" ${URL}/${APP}/addresses/${ip}/${SUBNETID} -s | jq --raw-output .data.id)
if [ "$?" != "0" ]; then
echo "[FAIL] Error fetching ip id from PHPIPAM" 1>&2
exit 1
fi
if [ -z "${IPID}" ] || [ "${IPID}" = "null" ]; then
echo "[INFO] address not in phpipam database: ${ip}"
return
fi
NOUPDATE=0
JSON=$(curl -X GET --header "token: ${TOKEN}" ${URL}/${APP}/addresses/${IPID}/ -s)
if [ "$?" != "0" ]; then
echo "[FAIL] Error fetching ip data from PHPIPAM" 1>&2
exit 1
fi
IPTAG=$(echo ${JSON} | jq --raw-output .data.tag)
for tag in ${INVALIDTAGS[@]}; do
if [ "${IPTAG}" = "${tag}" ]; then
echo "[INFO] refusing to update ip with tag: ${IPTAG}"
NOUPDATE=1
fi
done
if [ ${NOUPDATE} -eq 1 ]; then return; fi
IPMAC=$(echo ${JSON} | jq --raw-output .data.mac)
if [ -z "${IPMAC}" ]; then
echo "[INFO] No mac set for ip"
fi
if [ "${IPMAC}" = "${mac}" ]; then
echo "[INFO] no update required for mac"
return
fi
JSON=$(curl -X PATCH --header "token: ${TOKEN}" ${URL}/${APP}/addresses/${IPID}/ -s --data "mac=${mac}")
if [ "$?" != "0" ]; then
echo "[FAIL] Error updating ip mac on PHPIPAM" 1>&2
exit 1
fi
RESULT=$(echo ${JSON} | jq --raw-output .success)
if [ "${RESULT}" != "true" ]; then
echo "[FAIL] API returned error updating ip on PHPIPAM: ${JSON}"
exit 1
fi
echo "[INFO] Updated id:${IPID} ip:${ip} mac:${mac}"
}
while read a b c d e; do
# ? (192.168.1.10) at a1:b2:c3:d4:e5:f6 [ether] on eth0
line="${a} ${b} ${c} ${d} ${e}"
ip=$(echo ${b} | sed 's/[^0-9\.]//g')
mac=${d}
if [ "${mac}" = "<incomplete>" ]; then
# we silently skip these
continue
fi
if [ "${mac}" = "<unvollständig>" ]; then
# we silently skip these
continue
fi
if ! [[ ${mac} =~ ^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$ ]]; then
echo "[INFO] Invalid mac: ${mac}"
continue
fi
echo "[INFO] Found ip:${ip} mac:${mac}"
if [ -z "${ip}" ]; then
echo "[WARN] Unable to parse ip from '${b}': ${line}" 1>&2
fi
if [ -z "${mac}" ]; then
echo "[WARN] Unable to parse mac from '${d}': ${line}" 1>&2
fi
_update ${ip} ${mac}
done < <(arp -an -i ${INTERFACE})
if [ "$?" != "0" ]; then
echo "[FAIL] Error running arp" 1>&2
exit 1
fi
# get json output from ip for local interfaces
IPJSON=$(ip -j a show ${INTERFACE})
if [ "$?" != "0" ]; then
echo "[FAIL] Error running ip a show" 1>&2
exit 1
fi
localmac=$(echo ${IPJSON} | jq --raw-output '.[0].address')
if [[ ${localmac} =~ ^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$ ]]; then
LENGTH=$(echo ${IPJSON} | jq --raw-output '.[0].addr_info | length')
for (( i=0; i<${LENGTH}; i++)); do
localip=$(echo ${IPJSON} | jq --raw-output .[0].addr_info[${i}].local)
if [ -z "${localip}" ]; then
echo "[WARN] Unable to parse ip [${i}] from '${IPJSON}'" 1>&2
continue
fi
echo "[INFO] Found interface:${INTERFACE} ip:${localip} mac:${localmac} index:$((${i} +1))/${LENGTH}"
_update ${localip} ${localmac}
done
else
echo "[INFO] Invalid mac from interface: ${localmac}"
fi
echo "[INFO] Script finished!"
Und noch ausführbar machen:
chmod +x phpipam-update-mac-from-arp_V1.1.sh
Konfiguration
URL='https://phpipam.znil.local/api' APP='ARP-CronJob' TOKEN='dI4iAJlvuo9z78xlCUt5N4ubmy1FPyoL' SUBNET='192.168.0.0/22' INTERFACE='ens33' INVALIDTAGS=("3" "4")
Bei der URL setzt Ihr den Aufruf zu eurer phpIPAM Installation ein.
Für APP und TOKEN geht ihr in phpIPAM auf
Administration => API
und führt dort + Create API key aus:
Den Inhalt des Feldes App id könnt Ihr selbst wählen und tragt diesen dann bei APP im Skript ein.
In TOKEN kopiert Ihr den Inhalt des Feldes App code
Die Einstellungen für die Berechtigungen App permissions stellt Ihr auf Read / Write / Admin,
und bei App security nehmt Ihr SSL with App code token. Das die Oberfläche von phpIPAM per https erreichbar ist, ist also zwingend erforderlich.
SUBNET='192.168.0.0/22' ist das Netzwerk was gescannt werden soll,
INTERFACE='ens33' die Netzwerkschnittstelle. Den genauen Namen könnt ihr per
ip addr | grep BROAD
abfragen, Beispielausgabe:
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
in diesem Fall also ens33. Da kann auch so etwas wie enp1s0, vmbr0 oder eth0 stehen.
INVALIDTAGS=("3" "4") sorgt dafür, das er die MAC-Adresse bei Adressen vom Typ reserved oder DHCP nicht ändert.
Wenn Ihr das nicht wollt, tragt da z.B. INVALIDTAGS=("99") ein.
Aufruf
./phpipam-update-mac-from-arp_V1.1.sh
sollte zunächst eine Liste der erreichbaren IP-Adressen ausgeben,
im Anschluss werden die Adressen zugeordnet: