Skip to content

Instantly share code, notes, and snippets.

@bmatthewshea
Last active December 4, 2023 20:59
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save bmatthewshea/a214d710382b36612f6a06ee8ab5d3bf to your computer and use it in GitHub Desktop.
Save bmatthewshea/a214d710382b36612f6a06ee8ab5d3bf to your computer and use it in GitHub Desktop.
GeoIP Lookup scripts for use with new Maxmind MMDB database files
#!/bin/bash
#
# By: Brady Shea - 10FEB2020 - Last update 04DEC2023
#
# Usage (ip4 only):
# geoip2lookup IP_ADDRESS
#
# ** Install GeoIP Tool and Updater **
#
# sudo add-apt-repository ppa:maxmind/ppa
# sudo apt install libmaxminddb0 libmaxminddb-dev mmdb-bin geoipupdate
#
# * Review: https://dev.maxmind.com/geoip/geoip2/geolite2/ and
# sign up for a free license.
# * Edit your "/etc/GeoIP.conf":
# * Make sure you have the proper downloads location set:
# "EditionIDs GeoLite2-Country GeoLite2-City GeoLite2-ASN"
# * Make sure you have added the 'AccountID' and 'LicenseKey'
#
# Then finally execute:
#
# sudo geoipupdate
#
# Make sure you see new files in "/usr/share/GeoIP" or "/var/lib/GeoIP".
# If not, check the 'DatabaseDirectory' directive in your config/.conf above.
# A cron job will also be created to periodocally run the updater.
#
# Notes:
# * The valid_ip() function is taken from:
# https://www.linuxjournal.com/content/validating-ip-address-bash-script
# * Updated Legacy files can be found here: https://www.miyuru.lk/geoiplegacy
# - this script uses mmdb (only), though.
# * The country file is not currently used in this script. It can be removed
# from your downloads in the .conf file above if needed.
#
# Defaults
geo_file_prefix="/usr/share/GeoIP"
test_file1="GeoLite2-City.mmdb"
test_file2="GeoLite2-ASN.mmdb"
#####################################################################
# Functions
#####################################################################
function process_quoted () {
# Process all given tokens with spaces as one func argument= $*
grab_quoted=`sed -E 's/.*"(.*)".*/\1/' <<< "$*"`
}
function process_unquoted () {
grab_first_word=${1% *}
}
function valid_ip()
{
local ip=$1
local stat=1
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
OIFS=$IFS
IFS='.'
ip=($ip)
IFS=$OIFS
[[ ${ip[0]} -le 255 && ${ip[1]} -le 255 && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]
stat=$?
fi
return $stat
}
#####################################################################
# Validate
#####################################################################
# No argument given?
if [ -z "$1" ]; then
printf "\nUsage:\n\n geoip2lookup IP4_ADDRESS\n\n"
exit 1
fi
# Check ip validity.
if ! valid_ip "$1"; then
printf "\nThis is not a valid IP address. Exiting.\n\n"
exit 1
fi
# Check for GeoIP file location:
# Ubuntu 20 = /usr/share/ || Ubuntu 22 = /var/lib/
if [[ -f "/var/lib/GeoIP/${test_file1}" ]]; then
geo_file_prefix="/var/lib/GeoIP"
fi
# Make sure both geoip files exist at given location:
if ! [[ -f "${geo_file_prefix}/${test_file1}" || -f "${geo_file_prefix}/${test_file2}" ]]; then
printf "\nOne or more GeoIP file(s) are missing. Looking in: \"${geo_file_prefix}/\". Exiting.\n"
exit 1
fi
#####################################################################
# Execute
#####################################################################
printf "\nLooking up IP in the installed GeoIP database..\n"
#file_country="/usr/share/GeoIP/GeoLite2-Country.mmdb"
file_city="${geo_file_prefix}/${test_file1}"
file_asn="${geo_file_prefix}/${test_file2}"
city_string=`mmdblookup --file ${file_city} --ip "$1" city names en 2>/dev/null`
subdivision_string=`mmdblookup --file ${file_city} --ip "$1" subdivisions 0 names en 2>/dev/null`
country_string=`mmdblookup --file ${file_city} --ip "$1" country names en 2>/dev/null`
continent_string=`mmdblookup --file ${file_city} --ip "$1" continent names en 2>/dev/null`
location_lat_string=`mmdblookup --file ${file_city} --ip "$1" location latitude 2>/dev/null`
location_lon_string=`mmdblookup --file ${file_city} --ip "$1" location longitude 2>/dev/null`
location_metrocode_string=`mmdblookup --file ${file_city} --ip "$1" location metro_code 2>/dev/null`
location_timezone_string=`mmdblookup --file ${file_city} --ip "$1" location time_zone 2>/dev/null`
postal_string=`mmdblookup --file ${file_city} --ip "$1" postal code 2>/dev/null`
asn_number_string=`mmdblookup --file ${file_asn} --ip "$1" autonomous_system_number 2>/dev/null`
asn_name_string=`mmdblookup --file ${file_asn} --ip "$1" autonomous_system_organization 2>/dev/null`
process_quoted $city_string && city=${grab_quoted}
process_quoted $subdivision_string && subdivisions=${grab_quoted}
process_quoted $country_string && country=${grab_quoted}
process_quoted $continent_string && continent=${grab_quoted}
process_unquoted $location_lat_string && location_lat=${grab_first_word}
process_unquoted $location_lon_string && location_lon=${grab_first_word}
process_unquoted $location_metrocode_string && location_code=${grab_first_word}
process_quoted $location_timezone_string && location_tz=${grab_quoted}
process_quoted $postal_string && postal=${grab_quoted}
process_unquoted $asn_number_string && asn_number=${grab_first_word}
process_quoted $asn_name_string && asn_name=${grab_quoted}
printf "
City: ${city}
Territory: ${subdivisions}
Country: ${country}
Continent: ${continent}
Location (approx): ${location_lat},${location_lon}
Metro Code: ${location_code}
Timezone: ${location_tz}
Postal Code: ${postal}
ASN Number: ${asn_number}
ASN Organization: ${asn_name}
\n"
#!/usr/bin/python3
# mostly from https://github.com/maxmind/GeoIP2-python
import sys
import geoip2.database
# ^ (pip3 install geoip2)
ip = sys.argv[1]
print(ip + ":\n")
reader = geoip2.database.Reader('/usr/share/GeoIP/GeoLite2-City.mmdb')
response = reader.city(ip)
# Use these as needed
city = response.city.name
state = response.subdivisions.most_specific.name
country = response.country.name
postal = response.postal.code
llat = response.location.latitude
llon = response.location.longitude
netsize = response.traits.network
iso = response.country.iso_code
# example lookups created:
city_lookup = [city, state, country, postal]
location_lookup = [str(llat), str(llon)]
try: # "city_lookup"
print("\n".join(city_lookup))
except TypeError:
print ("(no city info found)")
except:
print ("Unknown Error on city lookup.")
try: # "location_lookup"
print("Coordinate Location:", ",".join(location_lookup))
except TypeError:
print ("No location data found.")
except:
print ("Unknown Error.")
@eggbean
Copy link

eggbean commented Nov 1, 2021

Has the database format changed since? I have the files in /usr/share/GeoIP, but I get a blank output:

$ ./geoip2lookup 1.1.1.1

Looking up IP in the installed GeoIP database..

  City:
  Territory:
  Country:
  Continent:
  Location (approx):      ,
  Metro Code:
  Timezone:
  Postal Code:
  ASN Number:
  ASN Organization:

@bmatthewshea
Copy link
Author

bmatthewshea commented Nov 1, 2021

@eggbean
This script already uses the 'new format' (.mmdb file)

I just ran it after running sudo geoipupdate-

SHEA22-2021-11-01_174956

And yeah- seems to still be working for me under Ubuntu20.

Did you follow the instructions in the script comments / get a license code /etc?

@bmatthewshea
Copy link
Author

added a python script

@eggbean
Copy link

eggbean commented Nov 2, 2021

Yes, I already had geoipupdate working but then realised that geoiplookup didn't work with .mmdb files, which led me to find your script.

jason@hydra:~/.dotfiles/bin/scripts$ sudo geoipupdate -v
geoipupdate version 4.6.0
Using config file /etc/GeoIP.conf
Using database directory /usr/share/GeoIP
Performing get filename request to https://updates.maxmind.com/app/update_getfilename?product_id=GeoLite2-ASN
Acquired lock file lock (/usr/share/GeoIP/.geoipupdate.lock)
Calculated MD5 sum for /usr/share/GeoIP/GeoLite2-ASN.mmdb: db83a6da755e231eb1f79858310350dc
Performing update request to https://updates.maxmind.com/geoip/databases/GeoLite2-ASN/update?db_md5=db83a6da755e231eb1f79858310350dc
No new updates available for GeoLite2-ASN
Performing get filename request to https://updates.maxmind.com/app/update_getfilename?product_id=GeoLite2-City
Acquired lock file lock (/usr/share/GeoIP/.geoipupdate.lock)
Calculated MD5 sum for /usr/share/GeoIP/GeoLite2-City.mmdb: dde2ccb737811d3409f34ad8eaad2fe6
Performing update request to https://updates.maxmind.com/geoip/databases/GeoLite2-City/update?db_md5=dde2ccb737811d3409f34ad8eaad2fe6
No new updates available for GeoLite2-City
Performing get filename request to https://updates.maxmind.com/app/update_getfilename?product_id=GeoLite2-Country
Acquired lock file lock (/usr/share/GeoIP/.geoipupdate.lock)
Calculated MD5 sum for /usr/share/GeoIP/GeoLite2-Country.mmdb: ecafa725dfea7428de5158891186449f
Performing update request to https://updates.maxmind.com/geoip/databases/GeoLite2-Country/update?db_md5=ecafa725dfea7428de5158891186449f
No new updates available for GeoLite2-Country
jason@hydra:~/.dotfiles/bin/scripts$ ls -l /usr/share/GeoIP
.rw-r--r-- 1 2.1M root root 19 Nov  2020 GeoIP.dat
.rw-r--r-- 1 8.1M root root 19 Nov  2020 GeoIPv6.dat
.rw-r--r-- 1 7.5M root root 26 Oct 14:01 GeoLite2-ASN.mmdb
.rw-r--r-- 1  73M root root 26 Oct 14:11 GeoLite2-City.mmdb
.rw-r--r-- 1 6.2M root root 26 Oct 14:05 GeoLite2-Country.mmdb
jason@hydra:~/.dotfiles/bin/scripts$ ./geoip2lookup 1.1.1.1

Looking up IP in the installed GeoIP database..

  City:
  Territory:
  Country:
  Continent:
  Location (approx):      ,
  Metro Code:
  Timezone:
  Postal Code:
  ASN Number:
  ASN Organization:

Strange..

@eggbean
Copy link

eggbean commented Nov 2, 2021

The python script worked though, cheers.

@bmatthewshea
Copy link
Author

bmatthewshea commented Nov 2, 2021

@eggbean
Yes, strange error using BASH. If me I would investigate permissions.. is the only thing that comes to mind. (meaning the .mmdb file is not readable by script user). It could of course be something else, but lacking any ideas at moment. Try running BASH script with sudo?


Glad Python one worked for you.
It should be simple to make it look like BASH one if needed. (It already uses same 'city' database file - no ASN added, though.)

Here is a formatted object which may help you understand the fields you can pull for Python script. (IP = 193.113.57.20)

SHEA22-2021-11-02_102544

@vadirajks
Copy link

thanks nice script :)
another way by using "mmdbinspect "
https://gist.github.com/vadirajks/8b9149d975eef25f6d9b6749df379224

@bmatthewshea
Copy link
Author

Hi @vadirajks -

Firstly, thanks for informing me on that file. Hadn't heard of it.

Second, I tried it and correct me if I am wrong, but the output appears to be identical to "mmdblookup".

mmdblookup should come with the Linux package ''mmdb-bin", or similar name depending on flavor of Linux. Or, if you look at Deb/Ubuntu install instructions here: https://github.com/maxmind/libmaxminddb (Notice the mmdb-bin package mentioned.)

I believe mmdbinspect is just a "side build" of the one that already lives in full build (called mmdblookup) in repo libmaxminddb above? :

As mentioned on Debian/Ubuntu flavors there is a package you can install called mmdb-bin:

$ dpkg -L mmdb-bin
/.
/usr
/usr/bin
/usr/bin/mmdblookup     <---
/usr/share
/usr/share/doc
/usr/share/doc/mmdb-bin
/usr/share/doc/mmdb-bin/changelog.Debian.gz
/usr/share/doc/mmdb-bin/changelog.gz
/usr/share/doc/mmdb-bin/copyright
/usr/share/man
/usr/share/man/man1
/usr/share/man/man1/mmdblookup.1.gz

( Or see: https://packages.ubuntu.com/focal/amd64/mmdb-bin/filelist )

Typical output from mmdblookup:

$ /usr/bin/mmdblookup -f /usr/share/GeoIP/GeoLite2-City.mmdb -i 32.32.32.32

  {
    "continent":
      {
        "code":
          "NA" <utf8_string>
        "geoname_id":
          6255149 <uint32>
        "names":
          {

..... (removed most of it) ......

}

The point is that mmdbinspect had the exact same output as above.

You may want to check and see if mmdblookup is already installed on your system (or see if you have a 'mmdb-bin' package on your Linux distribution? If so, it would appear you do not need mmdbinspect.)

And the small script on this page was written to simply shorten the output and make it more readable than the output those 'builtin' utilities produce (full json). The script(s) here also show (hopefully) how to pull the needed data you want out - and nothing extra.

@vadirajks
Copy link

sorry, for reference https://github.com/maxmind/mmdbinspect and by using "mmdbinspect", it is easily to parse json without any problem(like jq command as mentioned in my link....etc) but that is not the case with mmdblookup(JSON-ish format).
yes, you are correct both provide same result.
/usr/bin/mmdblookup -f /usr/share/GeoIP/GeoLite2-City.mmdb -i 32.32.32.32 | jq . (don't work)
/usr/bin/mmdbinspect -f /usr/share/GeoIP/GeoLite2-City.mmdb -i 32.32.32.32| jq . (works)

@bmatthewshea
Copy link
Author

bmatthewshea commented May 18, 2022

@vadirajks
Ahh yes & thanks. Yeah I see output is different. I just wasn't looking very closely. haha

The full thread below explained mmdblookup was just a developmental tool and never intended to output parsable json. Dumb reason IMO.

And this specific comment also explains why they made mmdbinspect (Also begs the question: Why not just re-write mmdblookup a bit?? Couldn't they have simply given it a new flag/parameter to produce proper json output (--json perhaps? Guess not.)). :-(

Anyway see here-
maxmind/libmaxminddb#135 (comment)

@kotenok2000
Copy link

updater puts database in /var/lib/GeoIP/ on 22.04 when not using ppa:maxmind/ppa

@bmatthewshea
Copy link
Author

bmatthewshea commented Dec 4, 2023

@kotenok2000
Thanks for the input. I have put a file check in BASH version and updated it here. I haven't had time to update python script, yet (or ever..lol).

If you are able to test bash version on a system that uses /var/lib/, let me know if it works. Only re-tested on a 20 box. Still works there. And you should be using the maxmind ppa if you use this script as noted in script setup comments. If location changes when using ppa in future, let me know! It should run a check either way now, though, at least for /var/lib/GeoIP..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment