#!/bin/sh
export PATH=/bin:/usr/bin:/sbin:/usr/sbin:/home/root
# Network discovery script v2.64 - ARM and MIPSR2 - 02/2025 - rs232
#------------------------------------------------------
#	FreshTomato Network Discovery - usage
#
#	arping = * scanning via arping (preferred)
#	traceroute = scanning via traceroute
#	nc = scanning via netcat
#	all = all the above in round-robin
#
#	lan = * only LANs interfaces
#	wan = only WANs interfaces
#	both = scan LANs and WANs
#
#	clear = removes dirty records from the arp table post scan
#
#	<5-200> = default 60, maximum number of concurrent scans
#------------------------------------------------------
script_name=$(basename "$0")
pid_file="/tmp/var/run/${script_name}.pid"

cleanup() {
	rm -f "$pid_file"
	exit 0
}

trap cleanup INT TERM EXIT

if [ -e "$pid_file" ]; then
	pid_age=$(($(date +%s) - $(stat -c %Y "$pid_file")))
	if [ "$pid_age" -gt 120 ]; then
		echo "PID file is older than 2 minutes. Removing stale PID file."
		rm -f "$pid_file"
	fi
fi

debug=$(nvram get discovery_debug 2>/dev/null)
debug="${debug:-0}"
[ "$debug" -eq 1 ] && { echo "Debugging..."; date >> /tmp/discovery.debug; echo "$@" >> /tmp/discovery.debug; }
lim=$(echo "$@" | grep -oE '[0-9]+' | head -n 1 | awk '{if($1 < 5) print 5; else if($1 > 200) print 200; else print $1}')
[ -z "$lim" ] && lim=60
alias slp='usleep 250000'
alias low='nice -n 19'
tmp="/tmp/discovery.tmp"
scan=0
cl=0
runlan=0
runwan=0
fkill=0

iplist() {
	input_cidr="$1"
	cidr=$(echo "$input_cidr" | cut -d '/' -f 2)
	ip=$(echo "$input_cidr" | cut -d '/' -f 1)
	awk -v cidr="$cidr" -v ip="$ip" '
	BEGIN {
		split(ip, a, ".");
		ip_int = a[1] * 256^3 + a[2] * 256^2 + a[3] * 256 + a[4];
		num_addrs = 2^(32 - cidr);
		base_ip = int(ip_int / num_addrs) * num_addrs;
		for (i = 1; i < num_addrs - 1; i++) {
		current_ip = base_ip + i;
		printf "%d.%d.%d.%d\n",
		int(current_ip / 256^3) % 256,
		int(current_ip / 256^2) % 256,
		int(current_ip / 256) % 256,
		current_ip % 256;
		}
	}'
}

check_procs() {
	case "$1" in
		arping) pidof arping ;;
		traceroute) pidof traceroute ;;
		nc) pidof nc ;;
	esac | wc -w
}

md5_param=$(echo "$@" | md5sum | cut -d' ' -f1)
if [ -f "$tmp" ]; then
	if ! grep -q "$md5_param" "$tmp"; then
		# Kill previous instances with different parameters
		for PID in $(pidof "$script_name"); do
			[ "$PID" != "$$" ] && kill -9 "$PID" &>/dev/null
		done
		# Kill scanning tools
		for tool in arping traceroute nc; do
			[ "$(check_procs "$tool")" -gt 0 ] && killall -q "$tool"
		done
		sleep 1
		fkill=1
	fi
else
	echo 0 > "$tmp"
	echo "$md5_param" >> "$tmp"
fi

for param in "$@"; do
	case "$param" in
		clear)		cl=1 ;;
		lan)		runlan=1 ;;
		wan)		runlan=0; runwan=1 ;;
		both)		runlan=1; runwan=1 ;;
		traceroute)	scan=1 ;;
		nc)			scan=2 ;;
		all)		scan=$(head -1 "$tmp") ;;
	esac
done
[ "$runlan" -eq 0 ] && [ "$runwan" -eq 0 ] && runlan=1

if [ -e "$pid_file" -a "$fkill" -ne 1 ]; then
	echo "Status-devices - Background discovery processes already running; skipping this run." | tee /dev/tty | logger
	cleanup
fi

scanthis() {
	for iface in $this; do
		cidr=$(ip a l dev "$iface" | grep inet | grep -v inet6 | awk '{print $2}')
		[ "$(echo "$cidr" | cut -d/ -f2)" -lt 22 ] && {
			echo "$iface - subnet mask shorter than /22. Too many IPs to scan - skipping..." | tee /dev/tty | logger
			continue
		}
		[ "$(echo "$cidr" | cut -d/ -f2)" -lt 32 ] && {
			ip=$(echo "$cidr" | cut -d/ -f1)
			ipn=$(ip neigh show dev "$iface" | grep 'REACHABLE\|PERMANENT' | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
			iplist "$cidr" | grep -vwE "$ip|$(echo "$ipn" | tr ' ' '\n')" | while read -r ip_addr; do
				if [ "$scan" -eq 0 ]; then
					while [ "$(check_procs arping)" -gt "$lim" ]; do slp; done
					low arping -I "$iface" -q -c1 -w1 "$ip_addr" &>/dev/null &
				elif [ "$scan" -eq 1 ]; then
					while [ "$(check_procs traceroute)" -gt "$lim" ]; do slp; done
					low traceroute -i "$iface" -r -F -m1 -q1 -s "$ip" "$ip_addr" &>/dev/null &
				elif [ "$scan" -eq 2 ]; then
					while [ "$(check_procs nc)" -gt "$lim" ]; do slp; done
					low nc -w 1 "$ip_addr" &>/dev/null &
				fi
			done
			wait
		}
	done
}

prune() {
	ip n | grep -w "$1" | while read -r badip; do
		ip=$(echo "$badip" | awk '{print $1}')
		dev=$(echo "$badip" | awk '{print $3}')
		ip neigh change "$ip" nud none dev "$dev"
	done
}

echo $$ > "$pid_file"

[ "$runlan" -eq 1 ] && {
	this=$(brctl show | grep -Eo ^br[0-9])
	scanthis
}

unset wans
[ "$runwan" -eq 1 ] && {
	[ "$(ip rule | grep -Eo 'WAN[1-4]' | sort -u | wc -l)" -gt 0 ] && {
		for WAN in $(ip rule | grep -Eo 'WAN[1-4]' | sort -u); do
		wans="$wans $(ip r l t "$WAN" | grep default | grep -Eo '[vlan|eth]+[0-9]{1,4}')"
	done
	} || {
		wans=$(ip route | grep ^default | awk '{print $5}')
	}
	this="$wans"
	scanthis
}

[ "$cl" -eq 1 ] && {
	prune "FAILED\|INCOMPLETE"
	usleep $((lim * 30000))
	prune "STALE\|DELAY\|PROBE"
}

scan=$(((${scan:-0} + 1) % 3))
{
	echo "$scan"
	echo "$md5_param"
} >> "$tmp"

cleanup