Using bash to monitor devices entering/exiting a LAN

Someone asked me for help on a scripting problem, and it seemed both simple and interesting. They had a raspberry Pi set up to control some lights, and they wanted to turn lights on and off if a set of devices entered the house (and joined the network).

While there are many ways to detect devices, such as sniffing WiFi packets, etc, in this case I used ping to check for an IP address.

To be precise, they wanted to know about several devices in an IP address range, such as what might be dynamically assigned by a home router. I wanted to respond with a script that allowed someone to react differently – such as turning a light on or off, or perhaps play a sound or whatever.

This posed an interesting problem. I’ve used AWK for keeping track of IP addresses, but I wanted something that would remember state of the network. Also I didn’t want to call ping from within an AWK script because that gets complicated.

Generally,  I tend to have one program generate a stream of data, and a second respond to the data. But I didn’t see an easy way to have ping run continuously. It’s not designed to stream data and be easy to parse.

Also – calling ping a dozen times is both inefficient and can complicate parsing, A quick search of alternatives showed the fping command, which turns out to be perfect for our needs.

I decided to use bash‘s associative arrays combined with fping. But there were a couple of surprises I discovered.

For those new to scripting, an associative array uses a string as the index to the array. So I decided to use a data structure such as

ip[192.168.1.140]="alive"

Note that the index to the array ip is the IP address, and the value is the status.

One nice feature of fping is the easy of parsing the results. There is a special flag that tests if a device is alive or not. With this flag – we can ignore error messages.

Also – fping allows the use of a file to contain a list of IP addresses. Another process can generate and/or change this file. Therefore I used the following command to generate my data:

fping -A -f /tmp/ip 2>&-

The list of IP addresses is in /tmp/ip and the string “2>&-” tells the shell to discard STDERR

 

As it performs several pings in parallel, the order of the IP addresses is not predictable. However, an associative array addresses this.

Another bonus of using the fping command is the output is easy to parse – each output line contains the IP address as the first word, and the status as the third word:

192.168.1.145 is alive
192.168.1.142 is alive
192.168.1.140 is unreachable
192.168.1.141 is unreachable
192.168.1.143 is unreachable
192.168.1.144 is unreachable
192.168.1.146 is unreachable

Bash can parse this easily.

I did run into a problem that puzzled me at first. I generally use code such as

fping …. | while read arg1 arg2 arg3

But this didn’t work. I mean, it worked, but not fully.  I wanted to capture the status of the devices in the array, and I forgot that when you use the pipe command, a subshell is forked off to process it, and all of the variables in this loop[ that I “remembered” were forgotten at the end of the loop. Smack Forehead!

Instead, I piped the results into a temporary file, and then read the file in the same shell. My variables remembered their values.

In this script, I use an array ip2light to map an IP address to a light. I could easily have two arrays, called ipenter, and ipexit, and these could contain shell commands to execute.

A simple modification could allow you to play trumpets when a device joined your WiFi, and a sad trombone when it leaves. True – this is by IP address. A more complicated script could keep track of unique devices via the MAC address (using arp to map the MAC address to the IP address).

So here’s the script. I hope this helps

 

#!/bin/bash

TMPFILE=$(mktemp)
trap "/bin/rm $TMPFIE"  0 HUP INT TERM

# let's create 2 associative arrays - this one maps 
# an IP address to a light

declare -A ip2light

ip2light[192.168.1.140]="Light0"
ip2light[192.168.1.141]="Light1"
ip2light[192.168.1.142]="Light2"
ip2light[192.168.1.143]="Light3"
ip2light[192.168.1.144]="Light4"
ip2light[192.168.1.145]="Light5"
ip2light[192.168.1.146]="Light6"


#etc




# declare another array that keeps track of each IP address


declare -A ip




# This lets us know if the device is here.

# for debug reasons, I did this once, 
# and then while it was running, I edited the temp file to test
#  the loop


#fping -A -f /tmp/ip 2>&- >$TMPFILE
while true # do this forever
      do
          # doing an fping here in a loop causes it to constantly query the machines

          fping -A -f /tmp/ip 2>&- >$TMPFILE
          while read IP x status # each line has 3 arguments - I only care about the first and third
          do
              # $IP contains IP address
              # $status contains status - either  alive or unreachable
              was=${ip[$IP]}
              if [[ "$was" != "$status" ]] # Did a device arrive or leave? Did the status change
              then
                  printf "Status of %s changed. It was '%s' and is now '%s'\n" "$IP" "$was" "$status"
                  if [[ "$status" == "alive" ]]
                  then
                    printf "Because %s arrived, turn on %s\n" "$IP" "${ip2light[$IP]}"
                  elif [[ "$status" == "unreachable" ]]
                  then
                    printf "Because %s left, turn off %s\n" "$IP" "${ip2light[$IP]}"
                  fi

              fi
              declare  ip[$IP]="$status" # remember the status
          done <$TMPFILE
          echo sleep 5 seconds
          sleep 5
done
I hope this helps someone.

 

 

This entry was posted in Hardware Hacking, Linux, Shell Scripting and tagged , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.