Aktionen

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 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.