IPC in bash to list WiFi stations fails

Hi everyone,

I'm running openwrt 19.07.1 on an RBM33g.

I noticed the output of the live AP client screen on Luci is far from accurate, mostly not even useful.
So, to experiment with it, I wrote this shell script:

#!/bin/bash

declare -A IPHOST
declare -A IPDNSMAP
declare -a MACHOSTLIST
declare -A MACSTATIONSENDMAP
declare -A MACSTATIONRECEIVEMAP
declare -A MACSTATIONSIGNALMAP
INTERFACES=("wlan0" "wlan1")

#not used currently, only for documentation, values are hardcoded in code
DNS="10.0.0.5"
SUBNET="10.0.0."
ZONE="myzone\."

mkfifo /dev/shm/memorymap;

check_host() {
                export=$(arping -f -w 5 -I br-lan 10.0.0.$1);
                if [[ $? == 0 ]]; then
                        macaddr=$(echo $export | sed 's/.*10\.0\.0\.[0-9]\+\ \[\([0-9A-Fa-f:]\{17\}\)\]\ .*/\1/')
                        dnsaddr=$(kdig -t PTR -x 10.0.0.$1 @10.0.0.5 | grep -E "myzone\." | grep -v hostmaster | cut -f 5| cut -d'.' -f 1)
                        echo "$macaddr $1" > /dev/shm/memorymap
                fi
                return 0
}

check_station() {
                data=($(iw dev $1 station dump | grep -E "($1)|(bitrate)|(signal:\ )" | sed 's/Station\ \([A-Za-z0-9:]\{17\}\)\ [(]on\ [a-zA-Z0-9\-]\+[)]/\1/;s/.*signal:.*\-\([0-9]\{2\}\)\ .*[\[].*dBm/\1/;s/.*tx\ bitrate:[\ \t]\+\([0-9\.]\+\)\ MBit.*/\1/;s/.*rx\ bitrate:[\ \t]\+\([0-9\.]\+\)\ MBit.*/\1/'))
                total=${#data[@]}
                for (( i=0; i<$total; i+=4 ));
                do
                        identifier=${data[$i]}
                        MACHOSTLIST=($identifier $MACHOSTLIST)
                        MACSTATIONSIGNALMAP[$identifier]=${data[$i + 1]}
                        MACSTATIONSENDMAP[$identifier]=${data[$i + 2]}
                        MACSTATIONRECEIVEMAP[$identifier]=${data[$i + 3]}
                done
}

for d in $(seq 80 82); do
                check_host $d &
done

for a in ${INTERFACES[@]}; do
                check_station $a
done
echo "waiting for processes to finish ..."
wait
echo "terminating EOF write buffer"
echo 'qi' > /dev/shm/memorymap

echo "running through processes"
while true
do
    if read line < /dev/shm/memorymap; then
        if [[ "$line" == 'qi' ]]; then
            break
        fi
        echo "$line"
        IPHOST[$(echo $line | cut -d' ' -f 1)]=$(echo $line | cut -d' ' -f 2)
        IPDNSMAP[$(echo $line | cut -d' ' -f 1)]=$(echo $line | cut -d' ' -f 3)
    fi
done
echo "removing pipe"
rm -rf /dev/shm/memorymap
echo "loop through results"
for i in $MACHOSTLIST; do

  echo "$i : ${IPHOST[$i]}: ${IPDNSMAP[$i]} - Send:  ${MACSTATIONSENDMAP[$i]} - Receive: ${MACSTATIONRECEIVEMAP[$i]} - quality: ${MACSTATIONSIGNALMAP[$i]} / 70"

done
# some formatting stuff ...

All subcommands seem to work perfectly, but this output comes out:

waiting for processes to finish ...
terminating EOF write buffer

... and that's it. It never gets past "echo 'qi' > /dev/shm/memorymap", even when playing around with seq to reduce the fifo input.

Any ideas?

Your "wait" commands stops until all "check_host" children die, but all of them are waiting untill someone reads from the "memorymap" fifo, and your script will not read from that fifo until later.

Then why does it output "terminating EOF write buffer..." doesn't this mean we're past the "wait" instruction?

True, did not catch that...

Anyway, who is going to read the "qi" text?

Nobody is ... it's some sort of EOF line to tell the loop we're at the end of line.
Anyway, this one works better, though I do not know about the thread-safety: I'd like to know whether bash supports atomic writes to /dev/shm/memorymap, otherwise this is a rather dangerous construction:

#!/bin/bash

declare -A IPHOST
declare -A IPDNSMAP
declare -a MACHOSTLIST
declare -A MACSTATIONSENDMAP
declare -A MACSTATIONRECEIVEMAP
declare -A MACSTATIONSIGNALMAP
INTERFACES=("wlan0" "wlan1")

#not used currently, only for documentation, values are hardcoded in code
DNS="10.0.0.5"
SUBNET="10.0.0."
ZONE="myzone\."

touch /dev/shm/memorymap

check_host() {
                export=$(arping -f -w 5 -I br-lan 10.0.0.$1);
                if [[ $? == 0 ]]; then
                        macaddr=$(echo $export | sed 's/.*10\.0\.0\.[0-9]\+\ \[\([0-9A-Fa-f:]\{17\}\)\]\ .*/\1/')
                        dnsaddr=$(kdig -t PTR -x 10.0.0.$1 @10.0.0.5 | grep -E "myzone\." | grep -v hostmaster | cut -f 5| cut -d'.' -f 1)
                        echo "$macaddr $1 $dnsaddr" >> /dev/shm/memorymap
                fi
                return 0
}

check_station() {
                data=($(iw dev $1 station dump | grep -E "($1)|(bitrate)|(signal:\ )" | sed 's/Station\ \([A-Za-z0-9:]\{17\}\)\ [(]on\ [a-zA-Z0-9\-]\+[)]/\1/;s/.*signal:.*\-\([0-9]\{2\}\)\ .*[\[].*dBm/\1/;s/.*tx\ bitrate:[\ \t]\+\([0-9\.]\+\)\ MBit.*/\1/;s/.*rx\ bitrate:[\ \t]\+\([0-9\.]\+\)\ MBit.*/\1/'))
                total=${#data[@]}
                for (( i=0; i<$total; i+=4 ));
                do
                        identifier=${data[$i]^^}
                        MACHOSTLIST=($identifier $MACHOSTLIST)
                        MACSTATIONSIGNALMAP[$identifier]=${data[$i + 1]}
                        MACSTATIONSENDMAP[$identifier]=${data[$i + 2]}
                        MACSTATIONRECEIVEMAP[$identifier]=${data[$i + 3]}
                done
}

for d in $(seq 90 190); do
                check_host $d &
done

for a in ${INTERFACES[@]}; do
                check_station $a
done
echo "waiting for processes to finish ..."
wait
echo "terminating EOF write buffer"
echo 'quit' >> /dev/shm/memorymap
chmod ago-w /dev/shm/memorymap

while read line;
do
        if [[ "$line" == 'quit' ]]; then
                break
        fi
        echo "$line"
        IPHOST[$(echo $line | cut -d' ' -f 1)]="10.0.0.$(echo $line | cut -d' ' -f 2)"
        IPDNSMAP[$(echo $line | cut -d' ' -f 1)]=$(echo $line | cut -d' ' -f 3)
done < /dev/shm/memorymap

echo "removing pipe"
rm -rf /dev/shm/memorymap

echo "loop through results"
for i in $MACHOSTLIST; do

  echo "$i : ${IPHOST[$i]}: ${IPDNSMAP[$i]} - Send:  ${MACSTATIONSENDMAP[$i]} - Receive: ${MACSTATIONRECEIVEMAP[$i]} - quality: ${MACSTATIONSIGNALMAP[$i]} / 70"

done

Data written to a fifo does not go anywhere, until another process reads from it, and viceversa. So, the "echo 'qi' > /dev/shm/memorymap" locks until the script reaches the "read line < /dev/shm/memorymap" command. I see you are using a normal file now.

I do not think there are atomic writes happening in your script, so the contents of the "memorymap" can get garbled.

Too bad ... any idea to obtain thread-safety?

I found this article on Stackoverflowv
https://stackoverflow.com/questions/1154446/is-file-append-atomic-in-unix/1154599#1154599
So, for a short write of 64 characters, this should be fine, right? This should even work with UTF-16

Yes, looks like this should be fine.

Don't you trust it? I mean, I'd like to know if I'm learning it the wrong way... It's still reversible right now, but it may not be that easy in future versions

EDIT to give a feature whishlist:

  • use ARP cache to offload arping requests, and perform initial ipv6 mapping (ip neigh)
  • use NDP to further discover ipv6 (though not sure how to do that)

I haven't experimented or read about that to be one hundred percent sure that it will not fail... However, that explanation makes sense, and this is not a mission critical script, after all.

All right, thank you for the assistance, let's mark this topic solved then

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.