djangoproject.com | python.org | nginx.org
version seven.
  http://demongin.org
demongin.org - Stupid Linux Tricks: Avoid Unnecessary System Calls with /proc/net/arp

Stupid Linux Tricks: Avoid Unnecessary System Calls with /proc/net/arp

How to use new-style arp tables to ping better.


Sunday, 2010-03-28 | AlmostEffortless, Careerism, Programming

/proc is, for anyone interested in what's happening under the hood, an endless source of awesomeness. I recently experienced a bit of /proc awesomeness when sitting down to refactor an old VPN monitoring project using python.

Long story short, OpenVPN (http://openvpn.net/) prints a log called "openvpn-status.log" at fixed intervals. Contained within this log are the MAC addresses and common names of the VPN clients currently connected to the VPN server. The goal of the project was to take that log, swap the MAC addresses for local IP addresses and reprint the log on an intranet webpage.

The old way of doing this involved pinging all of the IP addresses on our subnet within the range of addresses that we had set aside for VPN clients and then scraping the output from # /usr/bin/arp -a to assign MAC addresses to IP addresses. This was alright, but in my program it required making a system (or, more specifically, a subprocess) call with python. Not optimal.

The new way of doing this is via the new awesomeness: /proc/net/arp. The setup is the same: you ping all the IP addresses in the range alotted for VPN clients to fill the arp cache but then, instead of working with the output from the arp program, you simply create a file-like object /proc/net/arp and work with that much simpler, easy to manipulate output.

To illustrate the difference, allow me to cut and paste both outputs. /usr/bin/arp -a first:

virginia:/var/log/openvpn# arp -v -a
? (192.168.2.89) at <incomplete> on br0
? (192.168.2.85) at </incomplete><incomplete> on br0
? (192.168.2.82) at 00:FF:49:56:76:30 [ether] on br0
? (192.168.2.87) at </incomplete><incomplete> on br0
? (192.168.2.83) at 00:FF:2D:53:53:20 [ether] on br0</incomplete>
Now take a gander at what /proc/net/arp spits out:
virginia:/var/log/openvpn# cat /proc/net/arp
IP address       HW type     Flags       HW address            Mask     Device
192.168.2.85     0x1         0x0         00:00:00:00:00:00     *        br0
192.168.2.89     0x1         0x0         00:00:00:00:00:00     *        br0
192.168.2.82     0x1         0x2         00:FF:49:56:76:30     *        br0
192.168.2.87     0x1         0x0         00:00:00:00:00:00     *        br0
192.168.2.83     0x1         0x2         00:FF:2D:53:53:20     *        br0
Basically the same thing, but with /proc/net/arp you've got the option, if you're working with python, of simply creating a file-like object and working with that (instead of making a system call and having to strip off those parentheses):
f = file("/proc/net/arp")
From there, a quick list comprehension will get you all the data you need in a single, easy-to-use list:
output = [line.strip().split() for line in f.readlines()]
Two lines of code and you're ready to throw the data you need into a dictionary and do whatever it is you need to do.

Finally, the awesomeness isn't limited to python: when I'm trying to track down switches or other devices on the network that might not necessarily want to be found, all I've got to do to is rattle off the following bas one-liner and I've got all the data I need to start poking around:

cat /proc/net/arp |awk '{print $1 " " $4}'