Keep a History of your External IP Addresses

Today I had a look on my 404 log to check if there were any broken links or orphaned slugs on my site. The log was more than a couple of days old and … rather long. Since I made some “experiments” with my site recently, it was likely that the majority of the 404s have been triggered by myself, that is, from my Mac, behind a dynamic IP address.

So, how to filter out these not-so-interesting entries? 

While it is no problem to get the current external (WAN) IP address, it is indeed a problem to identify the IP addresses attributed to the router over the last weeks. (Unless you have a static IP, of course.) As far as I know there just isn’t a log file that tracks the WAN IP history anywhere on my Mac.

So, what we want is a little script that runs in the background, tracks the external IP address and writes it to a log file.


The Script

You love AppleScript, don’t you? Very useful here, since it integrates so well with the OS services, for example the Notification Center.

You can copy the script text from below or download it. Either way, save it as TrackWAN-IP.scpt into the Scripts folder in your Home directory (~/Library/Scripts/).


## Name: TrackWAN-IP
## Version: 2015-02-13

### BEGIN Settings ###

-- Location of the log file (delimiter: ":")
property IPLogFile : ((path to home folder) & "Desktop:wan_ip.log") as text

-- Shell queries to retrieve WAN IP; the fastest / most reliable should be first
property IPinfoQueries : {"dig +short myip.opendns.com @resolver1.opendns.com", "curl ip-addr.es", "curl ifconfig.me", "curl -s http://checkip.dyndns.org/ | grep -o \"[[:digit:].]\\+\""}

-- The format of the timestamp in the log file (for a complete timestamp: "date +%FT%T%z")
property dateCmd : "date +%y%m%dT%H%M" --This is a minimal timestamp

### END Settings ###

global wanIP, hasChanged, hasError


### Script ###

getWANIP(IPinfoQueries)
-- Don’t write if IP hasn’t changed
checkLastEntry(IPLogFile, wanIP)
if hasChanged then writeLog(wanIP)


### Handlers ###

on getWANIP(theQueries)
    set wanIP to ""
    set hasError to 0
    set storedDelimiters to AppleScript's text item delimiters
    set AppleScript's text item delimiters to "."
    repeat with i in theQueries
        try
            set wanIP to do shell script i
        end try
        if (count of wanIP's text items) is equal to 4 then exit repeat
        set hasError to 1
    end repeat
    if (count of wanIP's text items) is not equal to 4 then set hasError to 2
    set AppleScript's text item delimiters to storedDelimiters
    errorCheck(hasError)
    return
end getWANIP

on writeLog(theIP)
    try
        close access file IPLogFile
    end try
    set the openIPLogFile to open for access file IPLogFile with write permission
    set timeStamp to do shell script dateCmd
    set newEntry to timeStamp & ": " & theIP & "\n"
    write newEntry to the openIPLogFile starting at eof
    close access the openIPLogFile
    if hasError = 0 then display notification "New IP address written to log file." with title "✔︎ TrackWAN-IP: New IP Detected" subtitle theIP
end writeLog

on errorCheck(errorType)
    tell application "System Events"
        if errorType = 0 then
            return
        else if errorType = 1 then
            display notification "IP address retrieved – but one of the sources didn’t work. Please check the URLs." with title "✘ TrackWAN-IP: Fallback URL Used"
        else if errorType = 2 then
            display alert "TrackWAN-IP: Error\n\nNo IP address retrieved. Check your network connection."
            -- Less obstrusive:
            --display notification "Check your network connection." with title "✘✘ TrackWAN-IP: Error" subtitle "No IP address retrieved."
        else
            display alert "TrackWAN-IP:\n\nFatal error occurred."
            error number -128
        end if
    end tell
    return
end errorCheck

on checkLastEntry(theFile, theEntry)
    set storedDelimiters to AppleScript's text item delimiters
    set AppleScript's text item delimiters to " "
    try
        set lastEntry to text item 2 of paragraph -2 of (read file theFile)
    on error
        --In case there is no previous entry
        set lastEntry to ""
    end try
    set AppleScript's text item delimiters to storedDelimiters
    if theEntry is equal to lastEntry then
        -- Give feedback that the LaunchAgent is still launching the script
        if hasError = 0 then display notification "IP address checked, no change." with title "✔︎ TrackWAN-IP: No IP Change"
        -- We could exit here with '-128' but this would register an error with launchd
        set hasChanged to false
    else
        set hasChanged to true
    end if
    return
end checkLastEntry

   

What the script basically does:

  1. It queries the external IP address of your connection.
  2. It writes the IP address to a log file on your Mac.

In addition it does some comfort stuff:

  • If the IP query server fails it falls back to another server. Currently it uses these services:
    1. opendns.com (default)
    2. ip-addr.es (fallback 1)
    3. ifconfig.me (fallback 2)
    4. dyndns.org (fallback 3)
  • It doesn’t write anything to the log file if the IP address hasn’t changed.
  • It gives you different notifications for these events:
    • It has detected an IP address change
    • It has launched and the IP hasn’t changed (you may disable this)
    • The default query server is down and it uses one of the fallback URLs
    • There is no connection at all

Configuration

As you may have seen, at the beginning of the script there are some properties you can/should configure:

  • Location and name of your log file.
  • The URLs of the servers to query. The default order works fine for me but that may be different at your location. Test the URLs individually in the Terminal and make sure that the fastest is on first position. The others serve only as fallback in case the first one is down.
  • You can set the timestamp format to your liking. I’ve preset a compact form, to keep file size as small as possible.

Scheduling the Script

No special software needed for this. We’ll just create a launch agent for the system’s launchd. It works like this:

  1. Copy the text below und save it as net.dflect.TrackWAN-IP.plist into the directory ~/Library/LaunchAgents/. (That’s in your Home folder.)

    Make sure that the line <string>/Users/[home directory]/Library/Scripts/TrackWAN-IP.scpt</string> corresponds to the actual location and name of the script file. (Of course, replace the [home directory] with the name of your Home directory.)

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>Disabled</key>
        <false/>
        <key>Label</key>
        <string>net.dflect.TrackWAN-IP</string>
        <key>ProgramArguments</key>
        <array>
            <string>/usr/bin/osascript</string>
            <string>/Users/[home directory]/Library/Scripts/TrackWAN-IP.scpt</string>
        </array>
        <key>RunAtLoad</key>
        <true/>
        <key>StartInterval</key>
        <integer>10800</integer>
    </dict>
    </plist>
  2. Set the StartInterval in the plist according to your needs (seconds). Depending on how frequently your external IP address changes, something between 1 hour (3600s) and 24 hours (86400s) should be fine. (You can always manually launch the script from the Script menu, too.)
  3. Load and start the the script with…
    launchctl load -w ~/Library/LaunchAgents/net.dflect.TrackWAN-IP.plist

    The script should run immediatly, and automatically load at login, too.1


By the way, if you don’t want to bother with launch agents in the Terminal there is a very nice GUI app for all this: LaunchControl. It lets you set all options, indicates errors and can even create the plist file for you. Highly recommended.

Here’s a screenshot of what it looks like in LaunchControl if the script is correctly registered with launchd:

Screenshot, LaunchControl


Sources, Credits, More Info

If you need IPv6 addresses: ip.appspot.com, icanhazip.com, ifcfg.me

Footnotes

  1. If you open an already scheduled script in the Script Editor you’ll probably have to reload the launch agent afterwards: unload it with lauchctl unload ~/Library/LaunchAgents/net.dflect.TrackWAN-IP.plist and load it again with the above command.