├── .gitignore ├── GeoIPData ├── GeoIP.conf.sample └── readme.txt ├── README.md └── ip2geo.py /.gitignore: -------------------------------------------------------------------------------- 1 | GeoIPData/GeoIP.conf 2 | GeoIPData/*.mmdb 3 | GeoIPData/.geoipupdate.lock 4 | -------------------------------------------------------------------------------- /GeoIPData/GeoIP.conf.sample: -------------------------------------------------------------------------------- 1 | # GeoIP.conf file for `geoipupdate` program, for versions >= 3.1.1. 2 | # Used to update GeoIP databases from https://www.maxmind.com. 3 | # For more information about this config file, visit the docs at 4 | # https://dev.maxmind.com/geoip/geoipupdate/. 5 | 6 | # `AccountID` is from your MaxMind account. 7 | AccountID 0 8 | 9 | # `LicenseKey` is from your MaxMind account 10 | LicenseKey 0000000000 11 | 12 | # `EditionIDs` is from your MaxMind account. 13 | EditionIDs GeoLite2-ASN GeoLite2-City 14 | 15 | # `DatabaseDirectory` is the location to store databases in 16 | DatabaseDirectory ./ 17 | -------------------------------------------------------------------------------- /GeoIPData/readme.txt: -------------------------------------------------------------------------------- 1 | This directory is the default location for the GeoIP databases 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ip2geo 2 | 3 | This script reads IP addresses from STDIN and uses the MaxMind GeoIP databases to output various data points for each source IP. The script uses the GeoCityLite and ASN databases for enrichment. The user can specify various fields for output in a format string. You can use this script to download the GeoIP database files if needed as well. 4 | 5 | ## Usage 6 | 7 | Look up a single IP address and display results with default output format string: 8 | 9 | echo 192.30.252.122 | ./ip2geo.py 10 | "192.30.252.122","37.7697","-122.3933","36459","GitHub, Inc." 11 | 12 | Generate custom output format for all IPs in a file (one per line): 13 | 14 | cat file_of_ips.txt | ./ip2geo.py -f '%ip %asnum %cn' 15 | 157.166.226.26 5662 United States 16 | 192.30.252.122 36459 United States 17 | 66.35.59.202 22625 United States 18 | 58.162.89.137 1221 Australia 19 | 20 | Look up a single IP address and display results in JSON: 21 | 22 | echo 192.30.252.122 | ./ip2geo.py -j | jq '.' 23 | { 24 | "ipaddress": "192.30.252.122", 25 | "city": null, 26 | "region_code": null, 27 | "country_name": "United States", 28 | "postal_code": null, 29 | "country_code": "US", 30 | "continent": "North America", 31 | "metro_code": null, 32 | "time_zone": "America/Chicago", 33 | "latitude": 37.751, 34 | "longitude": -97.822, 35 | "asnum": 36459, 36 | "asname": "GitHub, Inc." 37 | } 38 | 39 | Download (or update) GeoIP database files: 40 | 41 | ./ip2geo.py -d 42 | Downloading GeoIP database files. 43 | 44 | Display usage (including list of all format string tags): 45 | 46 | ./ip2geo.py -h 47 | usage: ip2geo.py [-h] [-g GEOIPCONF] [-j] [-d] [-f FORMAT] 48 | 49 | Perform GeoIP lookups on IP addresses, displaying output in normalized format. 50 | 51 | optional arguments: 52 | -h, --help show this help message and exit 53 | -g GEOIPCONF, --geoipconf GEOIPCONF 54 | Path to GeoIP.conf file used by geoipupdate utility; Default: ./GeoIPData/GeoIP.conf 55 | -j, --json Provide each output record in JSON format. Ignores -f; Default: False 56 | -d, --download Download latest GeoIP databases to DatabaseDirectory specified in GeoIP.conf and exit. 57 | -f FORMAT, --format FORMAT 58 | Output format string; Default: '"%ip","%lat","%lon","%asnum","%asname"'. 59 | Possible values: 60 | %ip : IP Address 61 | %ci : City 62 | %rc : Region Code (State) 63 | %cn : Country Name 64 | %pc : Postal (ZIP) Code 65 | %cc : ISO Country Code 66 | %co : Continent 67 | %mc : Metro Code 68 | %tz : Time Zone Name 69 | %lat : Latitude 70 | %lon : Longitude 71 | %asnum : AS Number 72 | %asname : AS Name 73 | -------------------------------------------------------------------------------- /ip2geo.py: -------------------------------------------------------------------------------- 1 | #!env python3 2 | # ip2geo.py 3 | # version 2.0.1 4 | # by Phil Hagen 5 | # 6 | # This script reads IP addresses from STDIN and performs GeoIP database lookups 7 | # on each to add typical geo fields as well as ASNs. The results are output 8 | # in a customizable format, which defaults to CSV containing the IP, lat/long, 9 | # Autonomous System (AS) Number, and AS name. 10 | 11 | import sys 12 | from argparse import ArgumentParser, RawTextHelpFormatter 13 | import os 14 | import shutil 15 | import re 16 | import platform 17 | from subprocess import Popen, PIPE 18 | import json 19 | 20 | try: 21 | import geoip2.database 22 | except ImportError: 23 | sys.stderr.write('ERROR: No geoip2 library available - exiting.\n') 24 | sys.stderr.write(' Try installing with "pip install geoip2" or similar (may require admin privileges).\n') 25 | exit(2) 26 | 27 | format_map = { 28 | '%ip': ['ipaddress', 'IP Address'], 29 | '%ci': ['city', 'City'], 30 | '%rc': ['region_code', 'Region Code (State)'], 31 | '%cn': ['country_name', 'Country Name'], 32 | '%pc': ['postal_code', 'Postal (ZIP) Code'], 33 | '%cc': ['country_code', 'ISO Country Code'], 34 | '%co': ['continent', 'Continent'], 35 | '%mc': ['metro_code', 'Metro Code'], 36 | '%tz': ['time_zone', 'Time Zone Name'], 37 | '%lat': ['latitude', 'Latitude'], 38 | '%lon': ['longitude', 'Longitude'], 39 | '%asnum': ['asnum', 'AS Number'], 40 | '%asname': ['asname', 'AS Name'], 41 | } 42 | format_map_help_string = '' 43 | for tag in format_map.keys(): 44 | format_map_help_string += '%10s : %s\n' % (tag.replace('%','%%'), format_map[tag][1]) 45 | 46 | null_record = { 47 | 'city': None, 48 | 'region_code': None, 49 | 'country_name': None, 50 | 'postal_code': None, 51 | 'country_code': None, 52 | 'continent': None, 53 | 'metro_code': None, 54 | 'time_zone': None, 55 | 'latitude': None, 56 | 'longitude': None, 57 | } 58 | 59 | output_re = re.compile('|'.join(format_map.keys())) 60 | 61 | default_geoipconf = './GeoIPData/GeoIP.conf' 62 | default_format_string = '"%ip","%lat","%lon","%asnum","%asname"' 63 | 64 | # handle command line options 65 | p = ArgumentParser(description='Perform GeoIP lookups on IP addresses, displaying output in normalized format.', formatter_class=RawTextHelpFormatter) 66 | p.add_argument('-g', '--geoipconf', dest='geoipconf', help='Path to GeoIP.conf file used by geoipupdate utility; Default: %s' % (default_geoipconf), default=default_geoipconf) 67 | p.add_argument('-j', '--json', dest='json', help='Provide each output record in JSON format. Ignores -f; Default: False', action='store_true', default=False) 68 | p.add_argument('-d', '--download', dest='download', help='Download latest GeoIP databases to DatabaseDirectory specified in GeoIP.conf and exit.', action='store_true', default=False) 69 | p.add_argument('-f', '--format', dest='format', help='Output format string; Default: \'%s\'.\nPossible values:\n%s' % (default_format_string.replace('%','%%'), format_map_help_string), default=default_format_string) 70 | 71 | args = p.parse_args() 72 | 73 | def geoip_download(storagedir): 74 | conffile = os.path.abspath(args.geoipconf) 75 | db_download = Popen(['geoipupdate', '-f', conffile], cwd=storagedir, stdout=PIPE, stderr=PIPE) 76 | 77 | print('Downloading GeoIP database files.') 78 | 79 | db_download.communicate() 80 | if db_download.returncode != 0: 81 | sys.stderr.write('ERROR: Error downloading/updating GeoIP databases - exiting.') 82 | exit(5) 83 | 84 | def check_geoip_files(storagedir): 85 | try: 86 | gi1 = geoip2.database.Reader(os.path.join(storagedir, 'GeoLite2-City.mmdb')) 87 | gi2 = geoip2.database.Reader(os.path.join(storagedir, 'GeoLite2-ASN.mmdb')) 88 | except IOError: 89 | sys.stderr.write('ERROR: Required GeoIP files not present. Use "-d" flag to put them into %s.\n' % (storagedir)) 90 | sys.exit(2) 91 | 92 | return (gi1, gi2) 93 | 94 | ############################# 95 | if not shutil.which('geoipupdate'): 96 | sys.stderr.write('ERROR: Could not locate the geoipupdate utility in the path - exiting.\n') 97 | sys.stderr.write(' Try installing with apt, yum, brew, or similar.\n') 98 | exit(3) 99 | 100 | if not os.path.isfile(args.geoipconf): 101 | sys.stderr.write('ERROR: GeoIP configuration file %s does not exist - exiting.\n' % args.geoipconf) 102 | sys.stderr.write(' Refer to the included GeoIPData/GeoIP.conf.sample file.\n') 103 | sys.stderr.write(' Note that this requires a MaxMind account to update the database.\n') 104 | exit(4) 105 | 106 | # set up the storage directory, ending with an absolute path 107 | geoconf_fh = open(args.geoipconf) 108 | try: 109 | databasedir = re.search(r'^DatabaseDirectory\s+(\S+)', geoconf_fh.read(), re.MULTILINE).group(1) 110 | storagedir = os.path.abspath(os.path.join(os.path.dirname(args.geoipconf), databasedir)) 111 | except AttributeError: 112 | # this is the default, from the geoipupdate package 113 | if platform.system == 'Windows': 114 | storagedir = os.environ['SYSTEMDRIVE'] + '\\ProgramData\\MaxMind\\GeoIPUpdate\\GeoIP' 115 | else: 116 | storagedir = '/usr/local/share/GeoIP' 117 | geoconf_fh.close() 118 | 119 | # if we're here to download, get after it 120 | if args.download: 121 | geoip_download(storagedir) 122 | exit(0) 123 | 124 | (geocity, geoasn) = check_geoip_files(storagedir) 125 | 126 | # loop over input data 127 | for line in sys.stdin: 128 | line = line.strip() 129 | 130 | # make sure line only has an IP address via regexp 131 | if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', line): 132 | 133 | output_record = null_record 134 | 135 | # do geo lookups on IP 136 | try: 137 | ipdata = geocity.city(line) 138 | 139 | output_record = { 140 | 'ipaddress': line, 141 | 'city': ipdata.city.name, 142 | 'region_code': ipdata.subdivisions.most_specific.iso_code, 143 | 'country_name': ipdata.country.name, 144 | 'postal_code': ipdata.postal.code, 145 | 'country_code': ipdata.country.iso_code, 146 | 'continent': ipdata.continent.name, 147 | 'metro_code': ipdata.location.metro_code, 148 | 'time_zone': ipdata.location.time_zone, 149 | 'latitude': ipdata.location.latitude, 150 | 'longitude': ipdata.location.longitude, 151 | } 152 | 153 | except geoip2.errors.AddressNotFoundError: 154 | output_record = null_record 155 | output_record['ipaddress'] = line 156 | 157 | try: 158 | asn = geoasn.asn(line) 159 | output_record['asnum'] = asn.autonomous_system_number 160 | output_record['asname'] = asn.autonomous_system_organization 161 | 162 | except geoip2.errors.AddressNotFoundError: 163 | output_record['asnum'] = 0 164 | output_record['asname'] = 'None' 165 | 166 | if args.json: 167 | print(json.dumps(output_record)) 168 | 169 | else: 170 | # print out fields in CSV format 171 | output_record = {k: str(v) for k,v in output_record.items()} 172 | 173 | output_string = output_re.sub(lambda x: output_record[format_map[x.group()][0]], args.format) 174 | print(output_string) 175 | --------------------------------------------------------------------------------