From f7f833e28987fcf31012345fe80c78406456236a Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Wed, 14 Jul 2021 03:37:50 +0200 Subject: [PATCH 1/5] Add the possibility to return continent code and network in CIDR notation in addition to the country code. Needs error handling. --- geoip-lookup.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/geoip-lookup.py b/geoip-lookup.py index ccf4fc5..33b1378 100755 --- a/geoip-lookup.py +++ b/geoip-lookup.py @@ -38,7 +38,7 @@ def get_county_code(ipaddress, dbfile): raise LookupException("No address given") if not dbfile: raise LookupException("No db file given") - + reader = None try: reader = geoip2.database.Reader(dbfile) @@ -53,7 +53,7 @@ def get_county_code(ipaddress, dbfile): if not country: raise LookupException("Unsupported DB type: " + dbtype) - + return country.iso_code except FileNotFoundError as e: raise LookupException(e.args) @@ -67,6 +67,18 @@ def get_county_code(ipaddress, dbfile): if reader: reader.close() +def get_details(ipaddress, dbfile): + reader = None + try: + reader = geoip2.database.Reader(dbfile) + res = reader.city(ipaddress) + country = res.country.iso_code + continent = res.continent.code + network = res.traits.network + return "%s|%s|%s" % (country, continent, network) + finally: + if reader: + reader.close() def parse_command_line(argv): """ @@ -78,10 +90,11 @@ def parse_command_line(argv): dbfile = None parser = argparse.ArgumentParser(description='Get the country code from an IP address') parser.add_argument('-f', dest='dbfile', required=True, help="Path to the GeoIP2 database file") + parser.add_argument('--details', dest='detail', action='store_const', const=True, default=False, help="Verbose output: Print continent code and network along with country code") parser.add_argument('address', help="The IP address to check") args = parser.parse_args() - return args.dbfile, args.address + return args.dbfile, args.address, args.detail def main(argv): """ @@ -91,8 +104,12 @@ def main(argv): :return: """ try: - (dbfile, ipaddress) = parse_command_line(argv) - code = get_county_code(ipaddress, dbfile) + (dbfile, ipaddress, detail) = parse_command_line(argv) + if detail: + code = get_details(ipaddress, dbfile) + else: + code = get_county_code(ipaddress, dbfile) + print(code) except LookupException as e: print(e.args, file=sys.stderr) From 0062ae0b2b8be9724278a24e516d282c0d42fbd5 Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Thu, 15 Jul 2021 21:51:14 +0200 Subject: [PATCH 2/5] Try some refactoring. Needs testing. --- geoip-lookup.py | 52 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/geoip-lookup.py b/geoip-lookup.py index 33b1378..aabc3ad 100755 --- a/geoip-lookup.py +++ b/geoip-lookup.py @@ -26,7 +26,6 @@ class LookupException(Exception): """ pass - def get_county_code(ipaddress, dbfile): """ Determine the country code that the given ipaddress comes from. @@ -38,7 +37,7 @@ def get_county_code(ipaddress, dbfile): raise LookupException("No address given") if not dbfile: raise LookupException("No db file given") - + reader = None try: reader = geoip2.database.Reader(dbfile) @@ -50,10 +49,9 @@ def get_county_code(ipaddress, dbfile): country = reader.country(ipaddress).country # ASN is not supported # elif dbfile == 'GeoLite2-ASN' or dbtype == 'GeoIP2-ASN': - - if not country: + else: raise LookupException("Unsupported DB type: " + dbtype) - + return country.iso_code except FileNotFoundError as e: raise LookupException(e.args) @@ -68,14 +66,38 @@ def get_county_code(ipaddress, dbfile): reader.close() def get_details(ipaddress, dbfile): + """ + Determine the country code, continent code and the network that the given ipaddress comes from. + :param ipaddress: The IP address to look up + :param dbfile: The path to the GeoIP2/GeoLite2 database file (Country or City database) + :return: A string consisting of the ISO country code (2 letters), ISO continent code (2 letters) and network (CIDR notation), concatenated by "," + """ + if not ipaddress: + raise LookupException("No address given") + if not dbfile: + raise LookupException("No db file given") + reader = None try: reader = geoip2.database.Reader(dbfile) - res = reader.city(ipaddress) - country = res.country.iso_code - continent = res.continent.code - network = res.traits.network - return "%s|%s|%s" % (country, continent, network) + dbtype = reader.metadata().database_type + result = None + country = None + continent = None + network = None + if dbtype == 'GeoLite2-City' or dbtype == 'GeoIP2-City': + result = reader.city(ipaddress) + elif dbfile == 'GeoLite2-Country' or dbtype == 'GeoIP2-Country': + result = reader.country(ipaddress) + # ASN is not supported + # elif dbfile == 'GeoLite2-ASN' or dbtype == 'GeoIP2-ASN': + else: + raise LookupException("Unsupported DB type: " + dbtype) + + country = result.country.iso_code + continent = result.continent.code + network = result.traits.network + return "%s,%s,%s" % (country, continent, network) finally: if reader: reader.close() @@ -90,17 +112,18 @@ def parse_command_line(argv): dbfile = None parser = argparse.ArgumentParser(description='Get the country code from an IP address') parser.add_argument('-f', dest='dbfile', required=True, help="Path to the GeoIP2 database file") - parser.add_argument('--details', dest='detail', action='store_const', const=True, default=False, help="Verbose output: Print continent code and network along with country code") + parser.add_argument('-d', dest='detail', action='store_const', const=True, default=False, help="Verbose output: Print continent code and network along with country code") + parser.add_argument('--detail', dest='detail', action='store_const', const=True, default=False, help="Verbose output: Print continent code and network along with country code") parser.add_argument('address', help="The IP address to check") args = parser.parse_args() - + return args.dbfile, args.address, args.detail def main(argv): """ Read the database file and the IP address from the command line and print the corresponding ISO country code on stdout. - :param argv: Format: "-f /path/to/database.mmdb ip.v4.add.ress" + :param argv: Format: "-f /path/to/database.mmdb [--detail|-d] ip.v4.add.ress" :return: """ try: @@ -109,13 +132,12 @@ def main(argv): code = get_details(ipaddress, dbfile) else: code = get_county_code(ipaddress, dbfile) - + print(code) except LookupException as e: print(e.args, file=sys.stderr) print("Unknown") - if __name__ == '__main__': try: main(sys.argv[1:]) From 5a7dfe7e3cb34cdba1959bd3a0576d82e821223e Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Thu, 15 Jul 2021 21:55:10 +0200 Subject: [PATCH 3/5] Fix stupid typo that crashes Country DB lookups. --- geoip-lookup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geoip-lookup.py b/geoip-lookup.py index aabc3ad..ea7a270 100755 --- a/geoip-lookup.py +++ b/geoip-lookup.py @@ -45,7 +45,7 @@ def get_county_code(ipaddress, dbfile): country = None if dbtype == 'GeoLite2-City' or dbtype == 'GeoIP2-City': country = reader.city(ipaddress).country - elif dbfile == 'GeoLite2-Country' or dbtype == 'GeoIP2-Country': + elif dbtype == 'GeoLite2-Country' or dbtype == 'GeoIP2-Country': country = reader.country(ipaddress).country # ASN is not supported # elif dbfile == 'GeoLite2-ASN' or dbtype == 'GeoIP2-ASN': @@ -87,7 +87,7 @@ def get_details(ipaddress, dbfile): network = None if dbtype == 'GeoLite2-City' or dbtype == 'GeoIP2-City': result = reader.city(ipaddress) - elif dbfile == 'GeoLite2-Country' or dbtype == 'GeoIP2-Country': + elif dbtype == 'GeoLite2-Country' or dbtype == 'GeoIP2-Country': result = reader.country(ipaddress) # ASN is not supported # elif dbfile == 'GeoLite2-ASN' or dbtype == 'GeoIP2-ASN': From 4147d0360732d309ac7d588040477da666f82afe Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Thu, 15 Jul 2021 21:56:32 +0200 Subject: [PATCH 4/5] Fix error handling. Well, actually ... implement it :) --- geoip-lookup.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/geoip-lookup.py b/geoip-lookup.py index ea7a270..3bf3474 100755 --- a/geoip-lookup.py +++ b/geoip-lookup.py @@ -82,9 +82,6 @@ def get_details(ipaddress, dbfile): reader = geoip2.database.Reader(dbfile) dbtype = reader.metadata().database_type result = None - country = None - continent = None - network = None if dbtype == 'GeoLite2-City' or dbtype == 'GeoIP2-City': result = reader.city(ipaddress) elif dbtype == 'GeoLite2-Country' or dbtype == 'GeoIP2-Country': @@ -98,6 +95,14 @@ def get_details(ipaddress, dbfile): continent = result.continent.code network = result.traits.network return "%s,%s,%s" % (country, continent, network) + except FileNotFoundError as e: + raise LookupException(e.args) + except maxminddb.errors.InvalidDatabaseError as e: + raise LookupException(e.args) + except geoip2.errors.AddressNotFoundError as e: + raise LookupException(e.args) + except ValueError as e: + raise LookupException(e.args) finally: if reader: reader.close() From 75b8e2fb6cfbf0165a01ce0b8d60153910d9add3 Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Thu, 15 Jul 2021 22:02:57 +0200 Subject: [PATCH 5/5] Refactoring. --- geoip-lookup.py | 80 ++++++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 47 deletions(-) diff --git a/geoip-lookup.py b/geoip-lookup.py index 3bf3474..28ff122 100755 --- a/geoip-lookup.py +++ b/geoip-lookup.py @@ -26,51 +26,12 @@ class LookupException(Exception): """ pass -def get_county_code(ipaddress, dbfile): +def get_geoip_result(ipaddress, dbfile): """ - Determine the country code that the given ipaddress comes from. + Perform a lookup of the given ipaddress in the database. :param ipaddress: The IP address to look up :param dbfile: The path to the GeoIP2/GeoLite2 database file (Country or City database) - :return: The ISO country code (2 letters) - """ - if not ipaddress: - raise LookupException("No address given") - if not dbfile: - raise LookupException("No db file given") - - reader = None - try: - reader = geoip2.database.Reader(dbfile) - dbtype = reader.metadata().database_type - country = None - if dbtype == 'GeoLite2-City' or dbtype == 'GeoIP2-City': - country = reader.city(ipaddress).country - elif dbtype == 'GeoLite2-Country' or dbtype == 'GeoIP2-Country': - country = reader.country(ipaddress).country - # ASN is not supported - # elif dbfile == 'GeoLite2-ASN' or dbtype == 'GeoIP2-ASN': - else: - raise LookupException("Unsupported DB type: " + dbtype) - - return country.iso_code - except FileNotFoundError as e: - raise LookupException(e.args) - except maxminddb.errors.InvalidDatabaseError as e: - raise LookupException(e.args) - except geoip2.errors.AddressNotFoundError as e: - raise LookupException(e.args) - except ValueError as e: - raise LookupException(e.args) - finally: - if reader: - reader.close() - -def get_details(ipaddress, dbfile): - """ - Determine the country code, continent code and the network that the given ipaddress comes from. - :param ipaddress: The IP address to look up - :param dbfile: The path to the GeoIP2/GeoLite2 database file (Country or City database) - :return: A string consisting of the ISO country code (2 letters), ISO continent code (2 letters) and network (CIDR notation), concatenated by "," + :return: The lookup result """ if not ipaddress: raise LookupException("No address given") @@ -91,10 +52,7 @@ def get_details(ipaddress, dbfile): else: raise LookupException("Unsupported DB type: " + dbtype) - country = result.country.iso_code - continent = result.continent.code - network = result.traits.network - return "%s,%s,%s" % (country, continent, network) + return result except FileNotFoundError as e: raise LookupException(e.args) except maxminddb.errors.InvalidDatabaseError as e: @@ -107,6 +65,31 @@ def get_details(ipaddress, dbfile): if reader: reader.close() +def get_county_code(ipaddress, dbfile): + """ + Determine the country code that the given ipaddress comes from. + :param ipaddress: The IP address to look up + :param dbfile: The path to the GeoIP2/GeoLite2 database file (Country or City database) + :return: The ISO country code (2 letters) + """ + result = get_geoip_result(ipaddress, dbfile) + + return result.country.iso_code + +def get_details(ipaddress, dbfile): + """ + Determine the country code, continent code and the network that the given ipaddress comes from. + :param ipaddress: The IP address to look up + :param dbfile: The path to the GeoIP2/GeoLite2 database file (Country or City database) + :return: A string consisting of the ISO country code (2 letters), ISO continent code (2 letters) and network (CIDR notation), concatenated by "," + """ + result = get_geoip_result(ipaddress, dbfile) + country = result.country.iso_code + continent = result.continent.code + network = result.traits.network + + return "%s,%s,%s" % (country, continent, network) + def parse_command_line(argv): """ Parse the command line. First, the database file must be specified ("-f /path/to/db/file.mmdb"), then the IP address @@ -141,7 +124,10 @@ def main(argv): print(code) except LookupException as e: print(e.args, file=sys.stderr) - print("Unknown") + if detail: + print("Unknown,,") + else: + print("Unknown") if __name__ == '__main__': try: