From 683f696e19e352fe45b61daadb6260ec4aa1070f Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 1 Feb 2026 21:02:49 +0100 Subject: [PATCH] utils: add 'scan' feature to ixll Signed-off-by: Joachim Wiberg --- utils/ixll | 9 +++ utils/libll.sh | 152 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) diff --git a/utils/ixll b/utils/ixll index f7cdffa7..91a85247 100755 --- a/utils/ixll +++ b/utils/ixll @@ -22,6 +22,12 @@ may be supplied in places where a hostname is otherwise expected. Commands: + scan [-a] [] + Discover Infix devices on the LAN using mDNS-SD (avahi-browse). + Use -a to show all mDNS devices, not just Infix. Falls back to + IPv6 link-local multicast ping if avahi-browse is not installed + and is specified. + peer Return the address of the first IPv6 neighbor to respond on 's local LAN. @@ -75,6 +81,9 @@ case "$cmd" in help) usage && exit 0 ;; + scan) + llscan "$@" + ;; peer) llpeer "$@" ;; diff --git a/utils/libll.sh b/utils/libll.sh index a4441832..3a049a1e 100644 --- a/utils/libll.sh +++ b/utils/libll.sh @@ -144,3 +144,155 @@ llscp() $sshpasscmd scp $LLSCP_OPTS "$src" "$dst" } + +# Usage: llscan [-a] [] +# +# Discover Infix devices (or all mDNS devices with -a) on the LAN. +# Prefers mDNS-SD via avahi-browse; falls back to IPv6 link-local +# multicast ping if avahi-browse is not available and an interface +# is specified. +llscan() +{ + local all=0 + local iface= + + while [ $# -gt 0 ]; do + case "$1" in + -a) + all=1 + shift + ;; + *) + iface="$1" + shift + ;; + esac + done + + if [ "$iface" ] && [ ! -d "/sys/class/net/$iface" ]; then + echo "Error: Interface \"$iface\" does not exist." >&2 + return 1 + fi + + if command -v avahi-browse >/dev/null 2>&1; then + llscan_mdns "$all" + elif [ "$iface" ]; then + echo "Note: avahi-browse not found, falling back to link-local scan." >&2 + llscan_ll "$iface" + else + cat >&2 <<-EOF + Error: avahi-browse not found. + + Install avahi-utils to scan for Infix devices via mDNS: + sudo apt install avahi-utils + + Or specify an interface to use IPv6 link-local fallback: + $(basename $0) scan + EOF + return 1 + fi +} + +llscan_mdns() +{ + local all="$1" + local flags="-tarp" + + if avahi-browse --help 2>&1 | grep -q -- '-k'; then + flags="-tarpk" + fi + + avahi-browse $flags | awk -F';' -v show_all="$all" ' + $1 == "=" { + host = $7 + proto = $3 + addr = $8 + txt = $10 + + on = ""; ov = ""; product = ""; serial = ""; devid = "" + n = split(txt, parts, "\" \"") + for (i = 1; i <= n; i++) { + gsub(/"/, "", parts[i]) + if (parts[i] ~ /^on=/) { split(parts[i], kv, "="); on = kv[2] } + else if (parts[i] ~ /^ov=/) { split(parts[i], kv, "="); ov = kv[2] } + else if (parts[i] ~ /^product=/) { split(parts[i], kv, "="); product = kv[2] } + else if (parts[i] ~ /^serial=/) { split(parts[i], kv, "="); serial = kv[2] } + else if (parts[i] ~ /^deviceid=/) { split(parts[i], kv, "="); devid = kv[2] } + } + + if (!show_all && on != "Infix") next + + # Use deviceid (MAC) as unique key; fall back to hostname + key = devid ? devid : host + + if (!product) product = on ? on : "-" + if (!ov) ov = "-" + if (!serial || serial == "null") serial = "-" + + # Prefer IPv4 for display address + if (proto == "IPv4") { + ipv4[key] = addr + } else if (proto == "IPv6" && !ipv6[key]) { + ipv6[key] = addr + } + + if (!seen[key]++) { + keys[++ndevs] = key + hosts[key] = host + products[key] = product + versions[key] = ov + serials[key] = serial + } else if (length(host) > length(hosts[key])) { + # Prefer the unique hostname (e.g., infix-c0-ff-ee.local) + # over the generic one (e.g., infix.local) + hosts[key] = host + } + } + + END { + if (ndevs == 0) { + if (show_all) + print "No mDNS devices found." | "cat >&2" + else + print "No Infix devices found. Use -a to show all mDNS devices." | "cat >&2" + exit 1 + } + + fmt = "%-26s %-18s %-24s %-22s %s\n" + hdr = sprintf(fmt, "HOSTNAME", "ADDRESS", "PRODUCT", "VERSION", "SERIAL") + sub(/\n$/, "", hdr) + printf "\033[7m%s\033[0m\n", hdr + + for (i = 1; i <= ndevs; i++) { + k = keys[i] + a = (ipv4[k] ? ipv4[k] : (ipv6[k] ? ipv6[k] : "-")) + p = products[k]; if (length(p) > 23) p = substr(p, 1, 22) "~" + v = versions[k]; if (length(v) > 21) v = substr(v, 1, 20) "~" + printf fmt, hosts[k], a, p, v, serials[k] + } + + printf "\n%d device(s) found.\n", ndevs + } + ' +} + +llscan_ll() +{ + local iface="$1" + + printf "Scanning %s for IPv6 link-local neighbors ...\n\n" "$iface" + printf "\033[7m%-6s %s\033[0m\n" "#" "ADDRESS" + + llping "$iface" -c3 -w3 2>/dev/null | awk ' + /bytes from/ { + sub(/:$/, "", $4) + addr = $4 + if (!seen[addr]++) { + printf "%-6d %s\n", ++n, addr + } + } + END { + printf "\n%d neighbor(s) found on '"$iface"'.\n", n+0 + } + ' +}