Using Hazel, Python, Lingon, and ARP to Determine if a Device is on your Network

A friend of mine (he has a blog over here) gave me an idea for a program. He wanted to know when his kids got home (and when they left).  His idea was to have an application that would interrogate his wireless network to see if a specific client was associated with his Access Point.  I started down the path of using SNMP to query the Apple AirPort Extreme he owns, but quickly gave that up.  The main reason I gave up on that solution (which would have been perfect) is that Apple, in their infinite post-Jobs wisdom) has removed support for SNMP configurations from AirPort Utility 6.0 and has eliminated SNMP from their newer AirPort models.  So, dead end there.

So, I turned to the built-in support that I knew would be present:  arp.  The Address Resolution Protocol (ARP) is built into every operating system and allows you to map Ethernet MAC addresses into Internet Protocol (IP) addresses and even into human readable names (if you have reverse Domain Name Service (DNS) defined for those IP addresses).

You can see a list of all of the devices that your computer knows about by running this command:

1 arp -a
That command should produce some output that looks like this:
1 2 3 4 5 airport (192.168.1.80) at 80:ee:73:3e:2f:d on en0 ifscope [ethernet] my-ipad (192.168.1.97) at e8:6:88:8b:bd:bf on en0 ifscope [ethernet] my-iphone (192.168.1.99) at (incomplete) on en0 ifscope [ethernet] ? (192.68.1.237) at 0:e:58:7c:8e:bd on en0 ifscope [ethernet] ? (192.68.1.240) at 0:4:a3:40:62:66 on en0 ifscope [ethernet]
Those entries are broken into 4 pieces of information that you may want to capture:
  1. Name (my-ipad):  Based on reverse DNS, you’ll see a human-readable name here, if not, you’ll just see a ?
  2. IP Address (192.168.1.97):  The IP address that belongs to the MAC address
  3. MAC Address (e8:6:88:8b:bd:bf):  The hexadecimal Ethernet hardware address of the Device
  4. Interface (en0):  The physical network interface that the MAC was last seen on

Now that you know what you have to work with, let’s determine our desired outcome.

Desired Outcome

We want an automatic solution that identifies if a MAC address is currently visible on our network.  We’re going to use what we learned about the Address Resolution Protocol above to see the devices join and leave our network.  We’re then going to use a Python script that runs that arp command, processes the output, and “flags” our visible devices.  Finally, we’ll use Hazel to monitor the “flags” and take whatever appropriate action we deem necessary.

Python Script

I put together two different phython scripts.  The first script I wrote automatically named the “flags” based on the name shown in the arp command.  However, I quickly realized that most folks don’t have reverse DNS service for their internal network, so all of their names will just appear as question marks (?).  To combat that problem, I adapted the python script to allow you to pre-configure both the Name of the device and the MAC address of the device in a dictionary at the beginning of the script.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #!/usr/bin/env python import os,glob,subprocess,errno,sys # Enter device MAC addresses (lowercase) to watch myKids = {'d0:23:db:37:cf:f7': 'Kid1', 'fc:2a:54:0:5:75': 'Kid2'} present_files = [] allDevices = [] device = {} # Remove previously present devices list_of_files = os.listdir("/tmp") for ifile in list_of_files: if ifile.endswith(".present"): os.remove(os.path.join("/tmp", ifile)) # Execute arp command to find all currently known devices proc = subprocess.Popen('arp -a | cut -d" " -f1,4', shell=True, stdout=subprocess.PIPE) # Build array of dictionary entries for all devices found for line in proc.stdout: item = line.split() device["Name"] = item[0] device["MAC"] = item[1] allDevices.append(device.copy()) # Wait for subprocess to exit proc.wait() # Search Array of Dictionaries for items in myKids # Print name of device if found for kid in myKids.iterkeys(): for device in allDevices: if device.get('MAC') == kid: filename = "/Users/robpickering/Desktop/" + myKids.get(kid) + ".present" f = open(filename,'w') f.close() # Exit code based on success of original subprocess sys.exit(proc.returncode)
The script is fairly well documented, but you will have to make a couple of edits to make it work in your environment:
  1. Edit the MAC addresses and Names in the myKids dictionary at the beginning of the script.  Add more by separating the entries with commas.  Replace any spaces in the name you want to use with hyphens (-), as spaces will probably cause issues later.
  2. Edit the directory path for the “flag” which is actually just an empty file placed in the directory, found as the filename variable in the script.

Lingon

How do you get the script to run continuously?  On most Unix-based platforms you’d use a cron entry.  OS X calls these types of scripts LaunchAgents.  Lingon X is a great tool for seeing all of your LaunchAgents (cron entries) and LaunchDaemons (automatically starting processes).  You’ll have to purchase the version from their website to get full functionality (though there is a version in the Mac App Store, it is not the current version).

To create a LaunchAgent using Lingon X, just click the + sign to add an entry that looks like this:

LingonFindDevice

That will configure your system to run the Python script (saved here in /Users/robpickering/Scripts/devicePresent.py) every 5 minutes.  That in turn will create files in /Users/robpickering/Desktop that end in “.present”.

If you don’t have (or want to buy) Lingon, then you can just install the following plist file in your ~/Library/LaunchAgents directory:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Labelcom.robpickering.deviceProgramArguments/Users/robpickering/Scripts/devicePresent.pyRunAtLoadStartInterval300
If you didn’t use Lingon and just copied the above script into your LaunchAgents directory, you now need to make sure the OS knows about it, run this:
1 launchctl load ~/Library/LaunchAgents/com.robpickering.devicepresent.plist
Your script should now be running every 5 minutes and placing “.present” files on your Desktop (or wherever you told it to place them).

Hazel

Now you have to decide what you want to do when you find a .present file (or it disappears).  For the purposes of this article, I’m just going to display a notification on my Desktop.  Here is the Hazel rule you can use:

Hazel Notification

You will now receive a Desktop Notification whenever a device you’ve configured in the shell script appears in your MAC Address Table.

hazel howto osx programming python terminal
Tweet Post Share Update Email RSS

Leader, Mentor, Challenger, Educator, Network Engineer, System Administrator, Developer, Hacker, Writer, Diver, and Technology Explorer.