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.
Hinweis:
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
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
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
# - Add fping before checking MAC-Adresses to fill up ARP-Cache
URL='https://phpipam.znil.local/api'
APP='ARP-CronJob'
#USER='user'
#PASS='passwort'
TOKEN='dI4iAJlvuo9z78xlCUt5N4ubmy1FPyoL'
SUBNET='192.168.0.0/22'
INTERFACE='ens33'
INVALIDTAGS=("3" "4")
#echo "[INFO] Fetching token"
#TOKEN=$(curl -X POST --user ${USER}:${PASS} ${URL}/${APP}/user/ -s | jq --raw-output .data.token)
#if [ "$?" != "0" ]; then
# echo "[FAIL] Error fetching token from PHPIPAM" 1>&2
# exit 1
#fi
#if [ -z "${TOKEN}" ]; then
# echo "[FAIL] Didn't receive valid token from PHPIPAM" 1>&2
# exit 1
#fi
#echo "[INFO] Fetching token expiry (token test)"
#EXPIRES=$(curl -X GET --header "token: ${TOKEN}" ${URL}/${APP}/user/ -s | jq --raw-output .data.expires)
#if [ "$?" != "0" ]; then
# echo "[FAIL] Error fetching token expiry from PHPIPAM" 1>&2
# exit 1
#fi
#if [ -z "${EXPIRES}" ]; then
# echo "[FAIL] Didn't receive valid token expiry from PHPIPAM" 1>&2
# exit 1
#fi
echo "[INFO] fping 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} =~ ^([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
#JSON=$(curl -X DELETE --header "token: ${TOKEN}" ${URL}/${APP}/user/ -s)
#if [ "$?" != "0" ]; then
# echo "[FAIL] Error removing token" 1>&2
# exit 1
#fi
#RESULT=$(echo ${JSON} | jq --raw-output .success)
#if [ "${RESULT}" != "true" ]; then
# echo "[FAIL] API returned error when attempting to remove token: ${JSON}"
# exit 1
#fi
#echo "[INFO] Removed token"
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.