- not sure if this is the right section of the forum to post it (I guess it really is not opnsense specific)
- not sure if this could be added to opnsense directly or as a plugin
- you might want to audit the script and the python library prior to use in a PRODUCTION setting.
- I am using it successfully on omada SDN controlling tplink siwtches and AcccessPoints from a ubuntu host runnning the OMADA SDN as a container and usign opnsense unbound as primary local DNS.
but anyway, it it helps others...
here is a very simple script which can be run once or in cron to automatically rename OMADA clients based on a reverse DNS lookup.
#!/usr/bin/bash
version=0
changelog="version=$version, 13-Jun-2024, initial build"
version=2
changelog="version=$version, 14-Jun-2024, swapped loop from by-subnet to by-omada-clients-mac, saving before and after files in /var/tmp"
HELP="
dns2omada.sh is the most simplistic script ever meant to paliate one of dumbest laziest lack of feature of the tp-link OMADA SDN software....
it simply populate the OMADA client's name based on DNS name... everytime the script runs it will keep a copy of the omada clients output
before and after the script execution in /var/tmp/omadaclients.*.dns2omada
using a python omada api...
Assumptions:
- you already have a local DNS server able to perform reverse DNS resolution per IP.
- you already have omada SDN setup on the network
- you should create an OMADA admin account dedicated to this process using a very long random password string without any special character
- my subnet is simply from 192.168.0.1 to 192.168.0.256 so I have a simple loop, you might have to modify the script if your subnet is class B
with an outer loop etc...
usage:
1) install python: sudo apt install python3-pip
2) install the omada python api: pip install tplink-omada-client
3) update the parameters CHANGE_ME values in the scipt below
4) run the script manually or in cron...
"
# these should be self-explanatory
omada_username="CHANGEME_mrpotatoe"
omada_password="CHANGEME_lfdaruiRWGFD335qw324z"
omada_site="CHANGEME_homesweethome"
omada_url="https://CHANGEME_omada.mylocaldomain"
dns_server=CHANGEME_192.168.0.1
omadabin=/CHANGEME/bin/omada # the absolute path to the omada binarie whch gets installed with the client.
if [[ $# -gt 0 ]]
then
echo $HELP
exit 3
fi
if fgrep -v grep $0 | fgrep CHANGEME >/dev/null
then
echo "ERROR: you forgot to change these varialbes from $0:"
fgrep -v grep $0 | fgrep CHANGEME
echo "$HELP"
exit 4
fi
$omadabin -t myomada target --url $omada_url --user $omada_username --password $omada_password --site $omada_site --set-default
omadaclients=/tmp/omadaclients.$$.txt
$omadabin clients > $omadaclients
cat $omadaclients | while read mac ip name device port junk
do
clientname=""
clientmac=$mac
#
# debugging
#echo "parsing mac=$mac ip=$ip name=$name device=$device port=$port junk=$junk"
if [[ $ip = "" ]] || [[ $ip = "-" ]]
then
echo "skiping $mac because omada does not have an IP for it"
else
clientname=$(nslookup $ip $dns_server 2>/dev/null | fgrep "=" |cut -f2 -d"=" |cut -f1 -d"."|tail -1)
if [[ $clientname = "" ]]
then
echo "skipping $ip because I cant resolve it"
else
echo "setting hostname for $ip to $clientname for mac $clientmac "
$omadabin set-client-name $clientmac $clientname
fi
fi
unset mac ip name device port junk
done
cat $omadaclients > /var/tmp/omadaclients.before.dns2omada
$omadabin clients > /var/tmp/omadaclients.after.dns2omada
Hi,
some questions:
- do you run the script in the OMADA container?
- what's the path the to OMADA binarie? In my container (in Proxmox LXC) unter /opt/tplink there is no binarie
- Is it sufficient to provice the MACs with names in OPNsense in the DHCP-server ("Services: ISC DHCPv4: [LAN]")?
1) Which host to run it from?
It can run from anywhere as long as you have connectivity to the omada url AND can do a reverse dns lookup.
2) omada binary location?
That depends on your own python installation... when you run the pip install it should tell you the destination. I think by default if you are not root it is something like ~/.local/bin
3) Is it sufficient to provice the MACs with names in OPNsense in the DHCP-server ("Services: ISC DHCPv4: [LAN]")?
AFAIK, to get the reverse dns and local name resolution you need:
3.1) to use ISC DHCP with the option to (Kea DHCP self registration is not working yet)
3.2) to run unbound DNS on opensense and check these 2 options:
Register ISC DHCP4 Leases
Register DHCP Static Mappings
that basically allow you to retrieve the name of dhcp hosts by IP addresses over DNS.
All right, thank you for the reply. I'll give that a try!
Hello guys,
I didn't want to open a new post because what I want to add feels like a continuation to this very same topic.
The problem that op solved with his solution, can be tackled also in a different way, with a script in OPNsense, using Omada Open API.
I created this script (disclosing the help of AI here), to read
dnsmasq leases, create a
local cache and update
Omada Client List through its API. The API calls are executed only if there is a change in the cache, so traffic is minimized. There is also a
small logging feature to track last syncs.
Omada requirements:- In Global View - Settings - Platform Integration create a New App with Client mode and Admin role. You will be rewarded with a Client ID and a Client Secret.
- Get the Controller ID in the browser, opening the link https://omada_ip:8043/api/info. The field we are looking for is omadacId.
- To find the Site ID, open Omada, open the Developer Tools of your browser, go to the Network section and open a page of your Omada Site. Search (CTRL+F) for the prefix /sites/ among the requests. You will see a series of request with the format <controller_id>/api/v2/sites/<site_id> (xxxxxxxxxx/api/v2/sites/xxxxxxxxxx). Get the Site ID from there.
#!/bin/sh
# dnsmasq_omada_sync.sh
# Sync dnsmasq hostnames -> Omada Controller.
# Skips Omada login if cache shows no changes.
# To create a cron job for this file in OPNsense:
# 1. Copy this file in /root/scripts
# 2. Create the file /usr/local/opnsense/service/conf/actions.d/actions_omada.conf
# 3. Append the following lines to the file:
# [start]
# command:/root/scripts/dnsmasq_omada_sync.sh
# parameters:
# type: script
# message: Synchronize dnsmasq hostnames with Omada
# description: Synchronize dnsmasq hostnames with Omada
# 4. Restart configd: service configd restart
# 5. Create the cron job in "System - Settings - Cron"
# --------------------------------
# cyruz - https://ciroprincipe.net
set -e
### ==============================
### CONFIGURATION
### ==============================
OMADA_URL="https://omada_ip:8043"
OMADAC_ID="" # Omada Controller ID
SITE_ID="" # Omada Site ID
CLIENT_ID="" # Open API Client ID
CLIENT_SECRET="" # Open API Client Secret
LEASE_FILE="/var/db/dnsmasq.leases"
LEASE_CACHE="/var/db/dnsmasq_omada.cache"
LOG_FILE="/var/log/dnsmasq_omada_sync.log"
MAX_LOG_SIZE=$((1024 * 1024))
#######################################
# REQUIREMENTS CHECK
#######################################
require_cmd() {
for cmd in "$@"; do
command -v "$cmd" >/dev/null 2>&1 || {
echo "Error: required command missing: $cmd" >&2
exit 1
}
done
}
require_cmd curl jq awk date sort mktemp tr sed wc stat mkdir
#######################################
# LOG ROTATION
#######################################
rotate_logs() {
log_dir=$(dirname "$LOG_FILE")
[ -d "$log_dir" ] || mkdir -p "$log_dir"
if [ -f "$LOG_FILE" ]; then
size=$(stat -f %z "$LOG_FILE" 2>/dev/null || echo 0)
if [ "$size" -ge "$MAX_LOG_SIZE" ]; then
[ -f "${LOG_FILE}.1" ] && rm -f "${LOG_FILE}.1"
mv "$LOG_FILE" "${LOG_FILE}.1"
fi
fi
}
rotate_logs
exec >>"$LOG_FILE" 2>&1
if [ ! -r "$LEASE_FILE" ]; then
echo "$(date -Iseconds) Error: dnsmasq lease file not readable: $LEASE_FILE"
exit 1
fi
echo "===== $(date -Iseconds) - dnsmasq_omada_sync start ====="
#######################################
# STEP 1: Parse dnsmasq leases
#######################################
echo "[*] Reading dnsmasq leases..."
NOW_EPOCH=$(date +%s)
TMP_LEASES=$(mktemp -t dnsmasq.XXXXXX)
TMP_NEW_HOST=$(mktemp -t newhost.XXXXXX)
cleanup() {
rm -f "$TMP_LEASES" "$TMP_NEW_HOST"
}
trap cleanup EXIT
# Keep only valid (non-expired, hostname present) leases, last entry per MAC.
awk -v now="$NOW_EPOCH" '
now <= $1 && $4 != "" && $4 != "*" {
line[tolower($2)] = $0
}
END {
for (m in line) print line[m]
}
' "$LEASE_FILE" > "$TMP_LEASES"
# Build NEW_HOST list: "mac hostname".
# dnsmasq format: expiry mac ip hostname clientid.
while read -r expiry mac ip host cid; do
[ -z "$mac" ] && continue
mac_lc=$(printf "%s\n" "$mac" | tr "A-Z" "a-z")
printf "%s %s\n" "$mac_lc" "$host"
done < "$TMP_LEASES" > "$TMP_NEW_HOST"
#######################################
# STEP 2: Compare with cache BEFORE API
#######################################
echo "[*] Loading previous cache (if any) and comparing..."
CHANGED=0
if [ -f "$LEASE_CACHE" ]; then
while read -r mac new_host; do
[ -z "$mac" ] && continue
old_host=$(awk -v m="$mac" 'tolower($1)==m {print $2; exit}' "$LEASE_CACHE" 2>/dev/null || true)
if [ -z "$old_host" ] || [ "$old_host" != "$new_host" ]; then
CHANGED=$((CHANGED + 1))
fi
done < "$TMP_NEW_HOST"
else
# No cache yet: everything is considered "changed".
CHANGED=$(wc -l < "$TMP_NEW_HOST" | awk '{print $1}')
fi
if [ "$CHANGED" -eq 0 ]; then
echo "[*] No hostname updates detected — exiting without Omada API calls."
echo "===== $(date -Iseconds) - dnsmasq_omada_sync end (no changes) ====="
exit 0
fi
echo "[*] Detected $CHANGED hostname changes — requesting access token..."
#######################################
# STEP 3: Get access token (client_credentials)
#######################################
TOKEN=$(
curl -sk -X POST \
"$OMADA_URL/openapi/authorize/token?grant_type=client_credentials" \
-H "content-type:application/json" \
-d '{
"omadacId": "'"$OMADAC_ID"'",
"client_id": "'"$CLIENT_ID"'",
"client_secret": "'"$CLIENT_SECRET"'"
}' \
| jq -r '.result.accessToken'
)
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
echo "Error: failed to obtain access token from Omada OpenAPI"
echo "===== $(date -Iseconds) - dnsmasq_omada_sync end (token error) ====="
exit 1
fi
#######################################
# STEP 4: Apply hostname updates
#######################################
echo "[*] Applying hostname updates via OpenAPI..."
UPDATED=0
MISSING=0
FAILED=0
while read -r mac new_host; do
[ -z "$mac" ] && continue
# Load old host from cache (if any).
if [ -f "$LEASE_CACHE" ]; then
old_host=$(awk -v m="$mac" 'tolower($1)==m {print $2; exit}' "$LEASE_CACHE" 2>/dev/null || true)
else
old_host=""
fi
# Only process entries that changed vs cache.
if [ -n "$old_host" ] && [ "$old_host" = "$new_host" ]; then
continue
fi
# Build clientMac as required by OpenAPI path: upper-case with dashes.
mac_id=$(printf "%s\n" "$mac" | tr 'a-f' 'A-F' | tr ':' '-')
# Sanitize hostname for JSON (escape double quotes).
safe_host=$(printf "%s" "$new_host" | sed 's/"/\\"/g')
echo "[*] Setting name for $mac_id -> $safe_host"
RESP=$(curl -sk -X PATCH \
"$OMADA_URL/openapi/v1/$OMADAC_ID/sites/$SITE_ID/clients/$mac_id/name" \
-H "content-type:application/json" \
-H "Authorization:AccessToken=$TOKEN" \
-d "{\"name\":\"$safe_host\"}" )
ERR=$(printf "%s" "$RESP" | jq -r '.errorCode' 2>/dev/null || echo "unknown")
if [ "$ERR" = "0" ]; then
UPDATED=$((UPDATED + 1))
elif [ "$ERR" = "-41011" ]; then
echo "[!] Client $mac_id does not exist in this site (errorCode -41011)."
MISSING=$((MISSING + 1))
else
echo "[!] Failed to update $mac_id (errorCode $ERR)."
FAILED=$((FAILED + 1))
fi
done < "$TMP_NEW_HOST"
echo "[*] Updated clients: $UPDATED"
echo "[*] Missing in Omada: $MISSING"
echo "[*] Failed updates: $FAILED"
#######################################
# STEP 5: Rewrite cache
#######################################
echo "[*] Writing updated cache..."
sort "$TMP_NEW_HOST" > "$LEASE_CACHE.tmp"
mv "$LEASE_CACHE.tmp" "$LEASE_CACHE"
echo "===== $(date -Iseconds) - dnsmasq_omada_sync end ====="