Submit Hint Search The Forums LinksStatsPollsHeadlinesRSS
14,000 hints and counting!

Getting port mappings and DHCP usage from an Airport Device Network
If you're like me, I'm frustrated by the way the Airport Utility displays NAT port mappings and DHCP assignments. I have a lot of ports mapped, and quite a few devices on my network and it's painful to remember which ports are mapped to which devices. I've searched high and low to find something to help and come up blank.

I wrote the following bash script which relies on the built-in utility PListBuddy to extract the relevant values from the property list that can be exported from the Airport Utility. To use it, open Airport Utility and select the device that has the mappings.

Press the 'Manual Setup' button (or Cmd+L) and the Utility will load the information from the device. Next choose Export Configuration File... from the file menu and save it to disk. Now run the bash script below and it will extract the values and send them to stdout in tab delimited form. You can then open the result in a spreadsheet or editor of your choice and see all your port mappings, DHCP assignments, known MAC addresses, current leases, and a snapshot of your network's performance.

It's isn't particularly pretty as I'm not a skilled bash writer, and someone with AppleScript expertise could do a much finer job. I'm always open to suggestions on how to do this. The hardest part is that there are no associative arrays in the version of bash that comes with OS X, so I had to do a lot of hashing and evals to create variables. Hope this proves useful and all comments and improvements are welcome.

Here's the script:
#!/bin/bash

##############################################################################
# Airport Configuration Reader
#
# This script processes the xml exported by the Airport Utility to produce a
# tab or comma delimited file containing the DHCP assignments, NAT port 
# mappings, and a snapshot of the outstanding leases and performance of the
# wireless clients at the time the file was exported.  The file is suitable
# for opening in excel or a text editor, where it can be formatted at will.
#
# It relies on the presence of the utility PlistBuddy, normally shipped with
# OS X, and displays an error message if it cannot find it.
#
# To use it, run the Airport Utility, select Manual Setup (command-L) and then
# File | Export Configuration to save the file on your disk.  Then run this
# script against it.
#
# This has only been really tested on my Airport Extreme, which is a dual N.
# Unfortunately your mileage may vary as I don't have any other devices...
#
# Questions, bugs, suggestions, improvements: dan at gerrity dot org.
#
# $Source: /Users/dan/bin/RCS/apt2tsv-v $
# $Date: 2011-12-26 17:27:21-08 $
# $Revision: 1.12 $
#
###############################################################################

###############################################################################
# Variable and default definitions
#
# Default text item delimiter is a tab, the -c switch can be used for comma
# delimited.  Note that string quoting is not provided so if names have quotes
# in them, the comma delimited version may not format exactly right.

declare -a res maps leases perf
me=$(basename $0)
delimiter="\t"
kma="${HOME}/.knownmacaddresses.$(date "+%Y-%m-%d_%H.%M")"
rev="$(echo '$Revision: 1.12 $' | sed -e 's/\$//g' -e 's/ $//' -e 's/R/r/')"


###############################################################################
# Functions
###############################################################################

function usage() {
    [[ ${1} ]] && echo -e "Configuration file \"${1}\" could not be found.\n" 1>&2
    echo -e "${me} ${rev}\n" 1>&2
    echo -e "Usage: ${me} [-c] configuration[.baseconfig]\n" 1>&2
    echo -e "Option -c creates comma delimited files instead of tab delimited.\n" 1>&2
    echo "Using the AirPort Utility, select the device for which you want information, and" 1>&2
    echo "then press the \"Manual Setup\" button.  Once the configuration is loaded, use" 1>&2
    echo "the File | Export Configuration File... to save the configuration to disk." 1>&2
    echo -e "Use that file as the argument to the script.\n" 1>&2
    exit 1
}

function checkForPlistBuddy() {
    plb=$(which PlistBuddy)
    [[ (! ${plb}) && (-e /usr/libexec/PlistBuddy) ]] && plb="/usr/libexec/PlistBuddy"
    [[ ${plb} ]] && return
    echo "PlistBuddy is an application provided by Apple that processes plist files."
    echo "${me} relies on PListBuddy and it is neither in your path nor in the normal location"
    echo "/usr/libexec/PlistBuddy."
    exit 2
}

function getConfigFile() {
    filepath="${1}:./${1}:./${1}.baseconfig"
    filepath="${filepath}:${HOME}/${1}:${HOME}/${1}.baseconfig"
    filepath="${filepath}:${HOME}/Downloads/${1}:${HOME}/Downloads/${1}.baseconfig"
    filepath="${filepath}:${HOME}/Desktop/${1}:${HOME}/Desktop/${1}.baseconfig"
    oIFS="${IFS}"
    IFS=":"
    for fpn in ${filepath}; do
	if [[ -e "${fpn}" ]]; then cf="${fpn}"; break; fi
    done
    IFS="${oIFS}"
    [[ ! -e "${cf}" ]] && usage "${cf}"
}

# This function makes a poor-man's lookup table (no associative arrays in bundled bash) between
# MAC address, IP address, and host name.  
# Takes an environment variable name prefix, an address, and a description
function mungAddr() {
    munged="${1}$(echo ${2} | sed -e 's/\./x/g' -e 's/:/x/g')"
    shift; shift
    eval "${munged}=\"$*\""
}

# Creates a 12 digit number for the IP address that is sortable
function ipSort() {
    printf "%03s%03s%03s%03s" $(echo ${1} | sed 's/\./ /g')
}

# Gets some name and model parameters
function setName () {
    n1=$(${plb} -c "print auNN" "${cf}")
    [[ ${n1} ]] && name="${n1}"
    n2=$(${plb} -c "print syDN" "${cf}")
    n3=$(${plb} -c "print syAM" "${cf}")
    if [[ (${n1}) && (${n2}) ]]; then
	[[ "${n1}" != "${n2}" ]] && name="${n1}/${n2}"
    else
	[[ (${n1}) || (${n2}) ]] && name="${n1}${n2}"
    fi
    [[ (${name}) && (${n3}) ]] && name="${name}, ${n3}"
}

# Reads the DRes key to obtain dhcp reservations, also links machine name to IP and MAC addresses
function getReservations() {
    resTitle="|Description|IP Address|MAC Address|Type"
    numres=$(${plb} -c "print DRes:dhcpReservations:" "${cf}" | grep Dict | wc | awk '{print $1}')
    for (( i=0; i<${numres}; i++ )); do
	eval $(${plb} -c "print DRes:dhcpReservations:${i}" "${cf}" | grep "=" | \
	    sed -e 's/type/etype/' -e  's/[[:space:]]*\(.*\) = \(.*\)$/\1="\2"/')
	macAddress=$(echo ${macAddress} | tr "[A-F]" "[a-f]")
	mungAddr "i2h" "${ipv4Address}" "${description}"
	mungAddr "m2h" "${macAddress}"  "${description}"
	mungAddr "m2i" "${macAddress}"  "${ipv4Address}"
	res[ i ]="$(ipSort ${ipv4Address})||${description}|${ipv4Address}|${macAddress}|${etype}"
    done
}

# Reads the NAT address translations
function getMaps() {
    mapTitle="|Description|Destination|Host|TCP Public|UDP Public|TCP Private|UDP Private|"
    mapTitle="${mapTitle}Service Type|Service Name|Advertise|Enabled"
    nummaps=$(${plb} -c "print fire:entries:" "${cf}" | grep Dict | wc | awk '{print $1}')
    for (( i=0; i<${nummaps}; i++ )); do
	dest=$(${plb} -c "print fire:entries:${i}:hosts:0" "${cf}")
	hname="i2h$(echo ${dest} | sed 's/\./x/g')"
	[[ "${!hname}" == "" ]] && host="** NO DHCP **" || host="${!hname}"
	eval $(${plb} -c "print fire:entries:${i}" "${cf}" | grep "=" | grep -v hosts | \
	    sed -e 's/[[:space:]]*\(.*\) = \(.*\)$/\1="\2"/' -e 's/true/yes/g' -e 's/false/no/g')
	maps[ i ]="$(ipSort ${dest})||${description}|${dest}|${host}|${tcpPublicPorts}|${udpPublicPorts}|${tcpPrivatePorts}|${udpPrivatePorts}|${serviceType}|${serviceName}|${advertiseService}|${entryEnabled}"
    done
}

# Reads the outstanding leases at the time the export was made.
function getLeases() {
    leaseTitle="|Host|IP Address|MAC Address|Lease Ends"
    numleases=$(${plb} -c "print dhSL:leases:" "${cf}" | grep Dict | wc | awk '{print $1}')
    for (( i=0; i<${numleases}; i++ )); do
	eval $(${plb} -c "print dhSL:leases:${i}" "${cf}" | grep "=" | \
	    sed 's/[[:space:]]*\(.*\) = \(.*\)$/\1="\2"/')
	macAddress=$(echo ${macAddress} | tr "[A-F]" "[a-f]")
	leases[ i ]="$(ipSort ${ipAddress})||${hostname} (lease)|${ipAddress}|${macAddress}|${leaseEnds}"
    done
}

function getPerformance() {
    perfTitle="|Description|IP Address|MAC Address|Signal|Noise|Rate|Mode"
    let perfEntries=0
    numRadios=$(${plb} -c "print raSL" "${cf}" | grep "wlan.*" | wc | awk '{print $1}')
#   for some reason the wlan entries are sparse, so just from from 0 to 9    
#   for (( rad=0; rad<${numRadios}; rad++ )); do
    for (( rad=0; rad<9; rad++ )); do
	numClients=$(${plb} -c "print raSL:wlan${rad}" "${cf}" 2> /dev/null | grep "macAddress" | wc | \
	    awk '{print $1}')
	for (( j=0; j<${numClients}; j++)); do
	    eval macAddress=\"$(${plb} -c "print raSL:wlan${rad}:${j}:macAddress" "${cf}" 2> /dev/null | \
		tr [A-F] [a-f])\"
	    for term in rssi noise txrate phy_mode; do
		eval ${term}=\"$(${plb} -c "print raSL:wlan${rad}:${j}:${term}" "${cf}" 2> /dev/null)\"
	    done
	    mip="m2i$(echo ${macAddress} | sed 's/:/x/g')"
	    [[ ${!mip} ]] && ip=${!mip} || ip="0"
	    mdesc="m2h$(echo ${macAddress} | sed 's/:/x/g')"
	    [[ ${!mdesc} ]] && desc=${!mdesc} || desc="unknown"
	    perf[ (( perfEntries++ )) ]="$(ipSort ${ip})||${desc}|${ip}|${macAddress}|${rssi}|${noise}|${txrate}|${phy_mode}"
	done
    done
}

function printResults() {
    echo "Airport Data taken from $(basename ${cf}) [${name}] on $(date)"; echo
    echo "DHCP RESERVATIONS IN $(basename ${cf}) [${name}]"
    echo ${resTitle} | tr "|" "${delimiter}"
    printf "%s\n" "${res[@]}" | sort | cut -f2- -d'|' | tr "|" "${delimiter}"; echo
    echo "DHCP LEASES IN $(basename ${cf}) [${name}]"
    echo ${leaseTitle} | tr "|" "${delimiter}"
    printf "%s\n" "${leases[@]}" | sort | cut -f2- -d'|' | tr "|" "${delimiter}"; echo
    echo "NAT PORT MAPPINGS IN $(basename ${cf}) [${name}]"
    echo ${mapTitle} | tr "|" "${delimiter}"
    printf "%s\n" "${maps[@]}" | sort |  cut -f2- -d'|' | tr "|" "${delimiter}"; echo
    echo "PERFORMANCE SNAPSHOT IN $(basename ${cf}) [${name}]"
    echo ${perfTitle} | tr "|" "${delimiter}"
    printf "%s\n" "${perf[@]}" | sort | cut -f2- -d'|' | tr "|" "${delimiter}"; echo
}

function printKnownMacAddresses() {
    echo "MAC ADDRESSES"
    for (( i=0; i<${#res[@]}; i++ )); do
	echo "${res[ i ]}" | sed 's/.*\|\(.*\)\|.*\|\(.*\)\|.*/\2 \1/' >> "${kma}"
    done
    for (( i=0; i<${#perf[@]}; i++ )); do
	echo "${perf[ i ]}" | sed 's/^.*\|\(.*\)\|.*\|\(.*\)\|.*\|.*\|.*\|.*$/\2 \1/' >> "${kma}"
    done
    sort "${kma}" | uniq > "${kma}.tmp" && mv "${kma}.tmp" "${kma}"
    lastmac=""
    line=""
    cat "${kma}" | while read mac rol; do
	if [[ ${mac} == ${lastmac} ]]; then
            line="${line}/${rol}"
	else
            [[ ${line} ]] && echo "$(echo ${line} | tr "|" "${delimiter}")"
            line="${mac}|${rol}"
            lastmac=${mac}
	fi
    done
}

###############################################################################

[[ ! ${1} ]] && usage

if [[ "${1}" == "-c" ]]; then
    delimiter=","
    shift
fi

while [[ "${1}" ]]; do
    getConfigFile "${1}"
    checkForPlistBuddy
    setName
    getReservations
    getLeases
    getMaps
    getPerformance
    printResults
    shift
done
printKnownMacAddresses

[crarko adds: There are a couple of steps missing above. First, copy and paste the script to a text file and save it (I named mine 'airport_addr.sh'). I save it to my Desktop along with the exported Airport configuration file (named 'AC_Extreme.baseconfig' in my case). Then I opened Terminal and typed cd Desktop so the working directory is where the files are located. I made sure the script was executable by typing chmod +x airport_addr.sh.

To run the script I typed (use the actual names of your files instead of mine):

./airport1.sh AC_Extreme.baseconfig

The results displayed in the Terminal window. You'll probably want to redirect the output to a file to permanently capture the data, or you can just copy and paste the results from Terminal.]
    •    
  • Currently 2.25 / 5
  You rated: 5 / 5 (4 votes cast)
 
[5,374 views]  

Getting port mappings and DHCP usage from an Airport Device | 6 comments | Create New Account
Click here to return to the 'Getting port mappings and DHCP usage from an Airport Device' hint
The following comments are owned by whoever posted them. This site is not responsible for what they say.
Getting port mappings and DHCP usage from an Airport Device
Authored by: Jonas Lundberg on Dec 29, '11 08:09:40AM

It would be great to be able to get this information directly from the base station instead of using the Airport Utility. Anyone knows if it's possible to talk to the base station the same way that Airport Utility does?



[ Reply to This | # ]
Getting port mappings and DHCP usage from an Airport Device
Authored by: xz4gb8 on Dec 29, '11 11:23:06AM

This is quite useful. My techy clients love it. I'll probably try to encapsulate this script in an AppleScript that will, with one click, open the parsed conifguration in Excel.

Thank you.



[ Reply to This | # ]
Finding the configuration file
Authored by: dgerrity on Dec 29, '11 01:41:15PM

Sorry about not posting the instructions for making the script executable. If no path is specified the script will automatically search in your home folder, your downloads folder, and then your desktop for the file, which can be specified with or without the extension ".baseconfig".

Edited on Dec 29, '11 01:41:47PM by dgerrity



[ Reply to This | # ]
Getting port mappings and DHCP usage from an Airport Device
Authored by: alexiskai on Dec 30, '11 08:08:15AM
Per the comments, here's an improvement.

1. Download the script to your desktop and save it as something.sh, e.g. exportairport.sh
2. In Terminal, navigate to the desktop and run chmod u+x exportairport.sh
3. Now open the script in Terminal, i.e. vi exportairport.sh
4. After the first block of comments, ending with:
# $Revision: 1.12 $
#
###############################################################################
insert the following code (you can change airport.xls to whatever.xls if you like):
LOGFILE=airport.xls

exec 6>&1           # Link file descriptor #6 with stdout.
                    # Saves stdout.

exec > $LOGFILE
5. Now go down to the last line of the script; after the last line, insert the following code:
exec 1>&6 6>&-      # Restore stdout and close file descriptor #6.

exit 0
6. Save and exit.

One more tip is that you should save your Airport .baseconfig file with no spaces in the name. This script translates spaces to carriage returns and it looks awkward.

So now you can run ./exportairport.sh aebs.baseconfig and it will simply output airport.xls, which you can then double-click and open in Excel.

Edit: I opted not to have the script actually open Excel itself for several reasons, mostly that you might simply wish to retrieve the file over SFTP/AFP or you might not have Excel on the box in question, e.g. a file server.
Edited on Dec 30, '11 08:26:45AM by alexiskai


[ Reply to This | # ]
Getting port mappings and DHCP usage from an Airport Device
Authored by: audiophil on Jan 03, '12 09:03:30AM

Oooh, thank you! This could be quite handy.

When I need more functionality I typically use monowall/pfsense on a embedded appliance for dhcp/nat/firewall etc; I end up running airports in bridge/wap mode etc. This might make dealing with a few of my client networks a tad easier.



[ Reply to This | # ]
Getting port mappings and DHCP usage from an Airport Device
Authored by: mike_savory on Jan 04, '12 03:55:54PM

Also to get live information, you can enable SNMP on an Airport (I recommend not enabling SNMP over the WAN as this is then a security hole) on the Advanced Tab under Logging and Statistics. For this example I have set my community string to "secret", you should obviously use something more secure.

SNMP is a very old Internet Protocol for reading values and tables from a heirachical database (called a MIB) over IP. For a general description of SNMP and MIB's check out Wikipedia.

Luckily OSX already ships with a built in SNMP command-line tool and with a custom MIB for the Airport already installed by Apple. The MIB is located at
/usr/share/snmp/mibs/AIRPORT-BASESTATION-3-MIB.txt

Full details of the CLI tool can be found at http://www.net-snmp.org/
Lion ships with version 5.6, if you are a macports user (http://www.macports.org/) you can easily upgade to the latest version 5.7.1 using the command "sudo port install net-snmp".

Reading the MIB is a very geeky thing to do, and its sometimes just easier to walk the whole MIB to look for what you want. Here I'll just give you a few examples.

$ snmpwalk -v2c -csecret -M/usr/share/snmp/mibs -mALL 10.0.3.1 dhcpIpAddress
AIRPORT-BASESTATION-3-MIB::dhcpIpAddress."00:00:CD:1D:92:93" = IpAddress: 10.0.3.31
AIRPORT-BASESTATION-3-MIB::dhcpIpAddress."00:0C:29:66:31:CA" = IpAddress: 10.0.3.53
AIRPORT-BASESTATION-3-MIB::dhcpIpAddress."00:11:24:95:53:F8" = IpAddress: 10.0.3.26
AIRPORT-BASESTATION-3-MIB::dhcpIpAddress."00:11:24:EC:08:5B" = IpAddress: 10.0.3.13
AIRPORT-BASESTATION-3-MIB::dhcpIpAddress."00:17:F2:E6:75:75" = IpAddress: 10.0.3.36
AIRPORT-BASESTATION-3-MIB::dhcpIpAddress."00:23:6C:90:08:B9" = IpAddress: 10.0.3.10
AIRPORT-BASESTATION-3-MIB::dhcpIpAddress."00:24:36:F1:38:0C" = IpAddress: 10.0.3.20

So the command I used was "snmpwalk", this steps through a set of values and displays them. The parameter "-v2c" tells snmpwalk to use the 2c version of the protocol, The version 3 of the protocol allows encrypted and authenticated connections, but is more detailed to set up. Most devices will use version 2c. The "-csecret" parameter sets snmpwalk to use the string you set on your airport (note this is not really a password, as it is sent in the clear with every packet). The "-M/usr/share/snmp/mibs" parameter tells snmpwalk to look for MIB's in this directory, and the -mALL tels it to load all the MIB's it finds there. If you dont load the MIB's then snmpwalk will show you the OID number of the variable, rather than the clearer english version. 10.0.3.1 is the IP address of my Airport Extreme (yours will of course be different). And the final parameter tells snmpwalk to show me the dhcpIpAddress table (I found this name in the MIB).

To see everyting the special Apple MIB (called an Enterprise MIB) contains you should use the command

$ snmpwalk -v2c -csecret -M/usr/share/snmp/mibs -mALL 10.0.3.1 enterprises

To see all the standard MIB's supported try

$ snmpwalk -v2c -csecret -M/usr/share/snmp/mibs -mALL 10.0.3.1

In these standard MIB's are thinks like port speeds and port counters. This opens up opportunities for monitorng almost every aspect of the operation of you device, and this is not ust limited to Airort Extremes, most internet switches and routers (and even servers) can be monitored via SNMP.

Searching through here I was not able to find the static port-maps, but that is more of a configuration setting rather than running state information like DHCP leases.




[ Reply to This | # ]