Simple script for wireless station monitoring

Some one post a a while ago a script to monitor wireless stations.
https://forum.archive.openwrt.org/viewtopic.php?id=29075
Not sure where this is maintained now, but I took a stab at making it work in /bin/sh

Some complained it did not work with /bin/sh, with a couple quick google searches found parameter parsing and simulated arrays for /bin/sh.

DISCLAIMER: Took my best shot at porting maybe it will be useful to someone.

#!/bin/sh
#
# WLAN Minitoring Script Version v2.1
# OpenWrt Atheros wireless radio STA status monitor
# Developed by dir2cas <kalin.t.ivanov@gmail.com>
#
# Comments:
# Required packages: bash, iw, kmod- packages&drivers to get your wireless interface running
# Optional packages: 
# install with $ opkg update; opkg install iw bash ....
# To check if it's installed on your OpenWrt box, try $ opkg list-installed | grep "iw", etc.
# It is also recommended to have bash shell installed on your OpenWrt router
#
# Generally, the script should work without modifying the source, but you are free to do it if satisfying your needs.

SCRIPTNAME="wlan-monitor"

################ SCRIPT GLOBAL VARIABLES ################ 
# Defining varialbles (usually no need to modify)
DIR="/tmp/${SCRIPTNAME}"
FILE="${SCRIPTNAME}.log"
ARP_CACHE="/proc/net/arp"
#########################################################

for i in $(seq 1 6)
do	
	eval sta_$i=""
	eval sta_ip_$i=""
	eval sta_hostname_$i=""
	eval sta_linenum_$i=""
done

array_traverse()
{
    for i in $(seq 1 $2)
    do
    current_value=$1$i
    echo $(eval echo \$$current_value)
    done
    return 1
}

array_index()
{
    i=$2
    current_value=$1$i
    echo $(eval echo \$$current_value)
}


 Usage() {
# Define the help/warning message:
echo -e "\tUsage: ./${SCRIPTNAME}.sh [OPTION 1] [ARG 1]...[OPTION n] [ARG n]\n\n\tList of available options:\n\t-i :\tscan intensity in seconds\n\t-d :\twireless device/interface to monitor on\n\t-t :\t[STA_MAC | STA_IP | STA_Hostname]\n\t-h, --help :\tshow this menu\n\tDefault is to monitor all stations associated\n\tusing wireless device ${WLAN_DEFAULT} and scan intensity ${MON_INTERVAL_DEFAULT} sec"
}

# Defining the Away() function to terminate the script
 Away() {
# Show the message sent to the terminating function, if any
echo -e >&2 "$@\n"
sleep 1

#do some cleaning
rm -f ${DIR}/* > /dev/null

unset INTERFACE
unset DIR
unset FILE
unset CLIENTS
unset ARP_CACHE
unset HOST
unset WLAN_MON_CMD
unset sta_num
unset sta_list
unset sta_count
#unset sta[@]
#unset sta_ip[@]
#unset sta_hostname[@]
#unset sta_linenum[@]
unset sta_mac_arp
unset sta_mac
unset flag
sleep 1
exit 0
}

# Defining the Scan() function to print the current status of the wireless clients to the temp file:
 Scan() {
#clear
local WLAN_MON_CMD="$1"
${WLAN_MON_CMD} > ${DIR}/${FILE}
}

# Defining the GetHosts() function to learn the IPs and Hostnames of the wireless clients' stations (STAs):
 GetHosts() {
local i=1
sta_num=$(grep -c 'Station' ${DIR}/${FILE})

# Check if there are STAs connected to the AP
if [ "${sta_num}" -eq "0" ]; then
    #exiting
    Away "NO Wireless STAs associated"
fi 

# Get list with the associated STAs' MAC addresses
sta_list=$(cat ${DIR}/${FILE} | grep -e "Station" | cut -f 2 -d" ")

if [ -z "${sta_list}" ]; then     # -n the argument is non empty, -z the argument is empty
    #exiting
    Away "NO Wireless STAs associated"
fi

# Check again the real scanned wireless STAs (MAC addresses in the array)
sta_count=$(echo ${sta_list} | wc -w)

# Get each STA its IP and Hostname 
while [ "${i}" -le "${sta_count}" ]
do
    eval sta_${i}=$(echo ${sta_list} | awk -v num=$i '{print $num}')
    #sta[${i}]=$(echo ${sta_list} | cut -f${i} -d" "

    eval sta_ip_${i}=$(cat ${CLIENTS} | grep "$(array_index sta_ ${i})" | awk '{print $3}')
    if [ -z "$(array_index sta_ip_ ${i})" ]; then     # -n the argument is non empty, -z the argument is empty    
        eval sta_ip_${i}=$(grep -e "$(array_index sta_ ${i})" "${ARP_CACHE}" | awk '{print $1}')
    fi

    eval sta_hostname_${i}=$(cat ${CLIENTS} | grep "$(array_index sta_ ${i})" | awk '{print $4}')
    if [ -z "$(array_index sta_hostname_ ${i})" ] || [ "$(array_index sta_hostname_ ${i})" = "*" ]; then   # fix * if hostname for the device is not set    
        eval sta_hostname_${i}="noname"
    fi
    #identify the line number of the beginning of each entry
    eval sta_linenum_${i}=$(grep -n -E "$(array_index sta_ ${i})" ${DIR}/${FILE} | cut -f1 -d: )
    let "i += 1"
done  
}

# Defining the Format() function to prepare the final output of the script
 Format() {
local i=1
local divider="----------------------------------------------------------------------"

while [ "${i}" -le "${sta_count}" ]
do
    line=$(grep -e "$(array_index sta_ ${i})" ${DIR}/${FILE})
    sed -i '/'"$(array_index sta_ ${i})"'/ s/.*/'"${line} => IP:$(array_index sta_ip_ ${i}) | Host:$(array_index sta_hostname_ ${i})"'/g' ${DIR}/${FILE}
    sed -i '/'"$(array_index sta_ ${i})"'/ i'"${divider}"'' ${DIR}/${FILE}
    let "i += 1"
done
}

# Defining the STA_select() function to choose the correct STA, specified by the user with an IP or hostname
 STA_select() {
#Initialize the script argument
local ARG="$1"
local i=1

#Get the probable STA MAC address from the arp cache (the STA may have a static IP, not found in the dhcp file)
#sta_mac_arp=$(arp | grep "${ARG}" | awk '{print $4}')   #this method seems broken, if no arp binary
sta_mac_arp=$(grep -e "${ARG}" "${ARP_CACHE}" | awk '{print $4}')

#Loop to find the STA MAC address that corresponds to the given argument
while [ "${i}" -le "${sta_count}" ]
do
    if [ "${ARG}" = "$(array_index sta_hostname_ ${i})" ] || [ "${ARG}" = "$(array_index sta_ip_ ${i})" ] || [ "${sta_mac_arp}" = "$(array_index sta_ ${i})" ] ; then
        #if the corresponding MAC is found(confirmed by dhcp), parse it to a variable that will be returned
        sta_mac=$(array_index sta_ ${i})
    fi
    let "i += 1"    
done

if [ -n "${sta_mac}" ]; then   # -n the argument is non empty, -z the argument is empty
    return "1"
else
    #the given IP or hosname may be of a device that is not a wireless STA, in this case, exiting
    Away "The given IP/Hostname <${ARG}> is not a Wireless STA"
fi
}

# Defining the Main() function
 Main() {

#Define what to do when Ctrl-C is pressed - in this case - to terminate the script jumping to a terminating function
trap '{ echo -e "\nControl-C trap caught, exiting"; Away; }' INT #traps Ctrl-C

#Run an initial scan for wireless clients first to get the information needed:
WLAN_MON_CMD="iw dev ${INTERFACE} station dump"
Scan "${WLAN_MON_CMD}"
GetHosts

#Check which mode of the script to be run:
if [ -z "${HOST}" ]; then     # -n the argument is non empty, -z the argument is empty
    #if no argument given, the script will monitor all STAs, default mode
    WLAN_MON_CMD="iw dev ${INTERFACE} station dump"
else 
    STA_select "${HOST}"
    WLAN_MON_CMD="iw dev ${INTERFACE} station get ${sta_mac}"
fi 

# Start actual monitoring
while :
do
Scan "${WLAN_MON_CMD}"
GetHosts
Format
clear
printf "%s STA(s) associated\n" "${sta_num}"
cat ${DIR}/${FILE}
sleep "${MON_INTERVAL}"
done
}

#################################################################################################
#MAIN
ARGC=$(echo "$argv" | awk '{print NF}')
#echo "Script parameters are: $@ \nNumber of parameters in the Array: $ARGC"
#declare -a ARGUMENTS=("$@")

# Global variables defaults
WLAN_DEFAULT="wlan0"
#Monitor interval in seconds
MON_INTERVAL_DEFAULT="3"

# Initialize script parameters and usage
if [ "$#" -gt "6" ]; then
    #Usage
    Away "Too many arguments provided\n"
fi

# Handling Script Arguments
while [ "$1" != "" ]; do
    PARAM=`echo $1 | awk -F= '{print $1}'`
    VALUE=`echo $1 | awk -F= '{print $2}'`
    case $PARAM in
        -h | --help)
            Usage
            exit
            ;;
        -i)
             [ -z "$VALUE" ] && Away "###WARNING###\n<$PARAM> monitoring intensity provided without argument" && exit 0
             MON_INTERVAL="$VALUE"
            ;;
        -d)
             [ -z "$VALUE" ] && Away "###WARNING###\n<$PARAM> wireless device provided without argument" && exit 0
             INTERFACE="$VALUE";
            ;;
        -t)
             [ -z "$VALUE" ] && Away "###WARNING###\n<$PARAM> target station provided without argument" && exit 0
	     HOST="$VALUE";
            ;;
        *)
            Away "###WARNING###\n<$PARAM> is invalid script option\nCheck the help option for a list of available options"
            exit 1
            ;;
    esac
    shift
done

# Defining unspecified vital global parameters
if [ -z "${HOST}" ]; then echo -e "Force scanning of all associated STAs"; unset ${HOST}; fi
if [ -z "${INTERFACE}" ]; then echo -e "Force usage of default wireless interface ${WLAN_DEFAULT}"; INTERFACE="${WLAN_DEFAULT}"; fi
if [ -z "${MON_INTERVAL}" ]; then echo -e "Force usage of default scan intensity ${MON_INTERVAL_DEFAULT} seconds"; MON_INTERVAL="${MON_INTERVAL_DEFAULT}"; fi 

CLIENTS=$(cat /etc/config/dhcp | grep "leasefile" | sed 's/.*leasefile//' | tr -d " '") >&- 2>&-
if [ -z "${CLIENTS}" ]; then     # -n the argument is non empty, -z the argument is empty
    CLIENTS="/tmp/dhcp.leases"
fi

# Check if the specified interface is a valid wireless device
$(which iw) dev ${INTERFACE} info 2>/dev/null > /dev/null || { Away "\n<${INTERFACE}> is not a valid wireless interface,\nor not supported by mac80211-based driver"; };
sleep 1

# Check for the script directory in /tmp (RAM):
if [ -d "${DIR}" ]; then
    #echo "Directory exists"
    rm -f ${DIR}/* > /dev/null
    cd ${DIR} || exit 0
    Main
else 
    echo "Directory does not exists"
    echo "Creating directory"
    mkdir -p ${DIR}
    cd ${DIR} || exit 0
    Main
fi
4 Likes

That looks neat!

When you're referring to /bin/sh, are you talking about Ash (which is what Busybox and OpenWrt use), or are you referring to Bash? If it's the latter, it might be less confusing to have the shebang as /bin/bash.

I'm not an expert on memory usage of functions and whatnot, but if you're writing a POSIX shell script that requires bash for some of its functionality, it's not POSIX anymore and you might as well just run it completely as a bash script.

The original script depended on bash using the default shell in busybox now, I believe is ash. If you follow the original post some people had issue with installing bash. OS I took a quick stab at converting it. I was not concerned about the memory usage as much as just getting it to work. It was an old post the the original author , had forgotten about it. I was just was playing around.

2 Likes

I find the (desktop) utility shellcheck very useful for porting bash scripts to ash or POSIX sh.

1 Like

How should the script be modified if I wanted to just run it once and have it write the output to a file? I'm looking for a solution to scan for active clients, maybe once every 5 minutes and use that information with my home automation script on the PC.

'Main' part of script is below #MAIN, above functions are located.

1 Like

y like a script execution every 5 minutes, then please take a look at the standard time scheduler solution in openwrt: