IP Geo-Location Extension Attribute

curullij
Contributor

I wrote this extension attribute to do a Geo lookup on the external IP address of our laptops. It uses the API from IPStack and stores the last recorded IP address locally, only doing a new lookup if the IP address changes. This prevents extra lookups. You can also add multiple API keys and it will randomly select one for the request.

If it returns your organisations external IP then you can customise what the extension attribute response will be, in this case I added the school name to make it easier for our support staff to see the device was on prem.

You can also specify where you want the two text files written to. /Users/Shared/ is the default location.

My sed skills could do with a little polishing, but it gets the job done :)

#!/bin/sh
# Script to get Geo location data from dyndns.org and IP Stack
# Will only check if a new IP is recorded, will update manually if school IP is detected
# Written 01/04/2019
# Jacob Curulli - ILT Manager, Tranby College

# Edit these values below
# Create an array of API keys and randomly select one to use
declare -a API=('123' '123' '123')
index=$( jot -r 1  0 $((${#API[@]} - 1)) )
APIKey=${API[index]}
# School external facing IP address
schoolIP="127.0.0.1"
# School location information
schoolLocation="Tranby College, Perth, Australia : -31.9674, 115.8621"
# File location to save text files to
fileLocation="/Users/Shared/"

#Get current IP address my DynDNS
currentIP=`curl -L -s --max-time 10 http://checkip.dyndns.org | egrep -o -m 1 '([[:digit:]]{1,3}.){3}[[:digit:]]{1,3}'`

# Read in last recorded IP address
oldIP=`cat "$fileLocation".ip.txt`

#Read in last recorded location
oldLocation=`cat "$fileLocation".location.txt`

echo "Current IP is $currentIP"
echo "Selected API Key is $APIKey"
echo "Old IP is $oldIP"
echo "Old location is $oldLocation"

if [ "$currentIP" = "$oldIP" ];
then
echo "IP's are the same nothing to do"
elif [ "$currentIP" = "$schoolIP" ];
        then
        # Device is at school, No need to waste an IP Stack lookup
        echo "IP matches school IP, manually updating location"
        echo "Writing current IP to disk - $currentIP"
        echo $currentIP > "$fileLocation".ip.txt
        echo "Writing current location to disk - $myLocation"
        echo $myLocation > "$fileLocation".location.txt
        echo "<result>$schoolLocation</result>"
        else
        echo "There is an IP mismatch"
        # Fetch GEO location info from IPStack using IP address
        myLocationInfo=`curl -L -s --max-time 10 http://api.ipstack.com/$currentIP?access_key="$APIKey"&format=0`
        myCountryName=`echo $myLocationInfo | awk -F, '{print $6 }' | sed 's/.*://g' | sed 's/"//g'`
        myRegionCode=`echo $myLocationInfo | awk -F, '{print $7 }' | sed 's/.*://g' | sed 's/"//g'`
        myCity=`echo $myLocationInfo | awk -F, '{print $9 }' | sed 's/.*://g' | sed 's/"//g'`
        myLatitude=`echo $myLocationInfo | awk -F, '{print $11 }' | sed 's/.*://g'`
        myLongitude=`echo $myLocationInfo | awk -F, '{print $12 }' | sed 's/.*://g'`
        myLocation=`echo "$myCity, $myCountryName : $myLatitude, $myLongitude"`
        echo "Writing current IP to disk - $currentIP"
        echo $currentIP > "$fileLocation".ip.txt
        echo "Writing current location to disk - $myLocation"
        echo $myLocation > "$fileLocation".location.txt
        echo "<result>$myLocation</result>"
fi
7 REPLIES 7

ImAMacGuy
Valued Contributor II

interesting, thanks! Where did you get the geo location for the school? What about multiple locations - we are a company and have multiple offices across the world and it would be nice to have them listed.

mm2270
Legendary Contributor III

@jwojda I'm not sure what code/command @curullij is using, but I have this from an old script. It will give you an external IP for your location.

curl --connect-timeout 3 -s http://v4.ipv6-test.com/api/myip.php

ryan_ball
Valued Contributor

This a simple way as well:

curl https://ipinfo.io/ip

mm2270
Legendary Contributor III

@ryan.ball Nice! I'm keeping that one in my toolbox for later use. Nice and simple.

ryan_ball
Valued Contributor

@curullij Another way to do a portion of your script would be to resolve the IP of a hostname that is only available internally. For example, if your internal IPs are 10.X.X.X, you could do this:

#!/bin/bash

function internal_network_test () {
    local domain="contoso.com"
    local internalServer="internalserver.contoso.com"

    echo "Performing internal network tests..."
    # Test that a user's IP address is an internal IP address
    addresses=$(ifconfig -a inet 2>/dev/null | sed -n -e '/127.0.0.1/d' -e '/0.0.0.0/d' -e '/inet/p' | awk '{print $2}')
    for address in $addresses; do
        if [[ "$address" =~ (^127.)|(^172.1[6-9].)|(^172.2[0-9].)|(^172.3[0-1].)|(^192.168.) ]]; then
            echo "Internal network test (Device IP) failed."
            return 1
        fi
    done

    # Test that DNS lookups find internal addresses, not external
    addresses=$(nslookup "$domain" | grep -A 1 "Name:" | grep "Address:" | awk '{print $2}')
    for address in $addresses; do
        if [[ ! "$address" =~ (^10.) ]]; then
            echo "Internal network test (DNS Lookup) failed."
            return 1
        fi
    done

    # Test for internal connectivity via Ping
    if ! ping -c 2 -o "$internalServer" &> /dev/null; then
        echo "Internal network test (Ping) failed."
        return 1
    fi
}

if internal_network_test; then
    echo "Device is internal."
    # Do something here
else
    echo "Device is external."
    # Do something here
fi

exit 0

curullij
Contributor

@ryan.ball that is a great idea! I'll look at implementing that.

@mm2270 the command is currentIP=curl -L -s --max-time 10 http://checkip.dyndns.org | egrep -o -m 1 '([[:digit:]]{1,3}.){3}[[:digit:]]{1,3}'
However the command that Ryan posted is much simpler. That piece of code is a snippet from a really old project that has been reused several times. "if it aint broke...."

curullij
Contributor

@jwojda The script looks at the returned public IP address and then updates the location variable manually. I ran part of the script whilst at the school to get what the normal result would be.

If you had multiple locations then you could match each public IP to a manual location with a bunch of nested if statements. I'm sure there is a cleaner way to do it, perhaps with an array of IP's and then returning the array id and matching that to a second array with the location details for the public IP.