Anonymous avatar Anonymous committed 16ee6f7

Reorginized, rewriten with own simple DB

Comments (0)

Files changed (4)

+ip2cc.db
+.py[co]
+		  ip2cc: Lookup country country by IP address
+                  ===========================================
+
+(c) 2002 Denis S. Otkidach
+
+WHAT IS IT
+
+If you want to gather web statistics by countries (not by top-level
+domains) or implement targeting, here is solution: ip2cc.  This module
+allows to resolve country from IP address.
+
+USAGE
+
+ip2cc.py -update              - build/update database
+ip2cc.py <address>            - print country name for which
+                                <address> is registered
+For example:
+$ ./ip2cc.py python.org
+python.org (194.109.137.226) is located in NETHERLANDS
+$ ./ip2cc.py google.com.ru
+google.com.ru (216.239.33.100) is located in UNITED STATES
+
+Module can be used as CGI.
+
+LICENSE
+
+Python-style
+
+ACKNOWLEDGEMENTS
+
+Jason R. Mastaler
+Fredrik Lundh
+
+CHANGES
+
+0.2	- Adopted to new format of registrars.
+	- Added LACNIC to sources.
+	- Fixed contry code map and added -check option to simplify
+	  maintainance.
+
+0.1	Initial release
+#!/usr/local/bin/python -O
+# $Id$
+__version__ = '0.3'
+
+import re, struct
+
+
+is_IP = re.compile('^%s$' % '.'.join([r'((1?\d)?\d|2[0-4]\d|25[0-5])']*4)).match
+
+
+class CountryByIP:
+
+    def __init__(self, filename):
+        self.fp = open(filename, 'rb')
+
+    def __getitem__(self, ip):
+        offset = 0
+        fp = self.fp
+        for part in ip.split('.'):
+            start = offset+int(part)*4
+            fp.seek(start)
+            value = fp.read(4)
+            assert len(value)==4
+            if value[:2]=='\xFF\xFF':
+                if value[2:]=='\x00\x00':
+                    raise KeyError(ip)
+                else:
+                    return value[2:]
+            offset = struct.unpack('!I', value)[0]
+        raise RuntimeError('ip2cc database is briken') # must never reach here
+
+    
+# ISO 3166-1 A2 codes (latest change: Wednesday 10 October 2003)
+cc2name = {
+    'AD': 'ANDORRA',
+    'AE': 'UNITED ARAB EMIRATES',
+    'AF': 'AFGHANISTAN',
+    'AG': 'ANTIGUA AND BARBUDA',
+    'AI': 'ANGUILLA',
+    'AL': 'ALBANIA',
+    'AM': 'ARMENIA',
+    'AN': 'NETHERLANDS ANTILLES',
+    'AO': 'ANGOLA',
+    'AQ': 'ANTARCTICA',
+    'AR': 'ARGENTINA',
+    'AS': 'AMERICAN SAMOA',
+    'AT': 'AUSTRIA',
+    'AU': 'AUSTRALIA',
+    'AW': 'ARUBA',
+    'AZ': 'AZERBAIJAN',
+    'BA': 'BOSNIA AND HERZEGOVINA',
+    'BB': 'BARBADOS',
+    'BD': 'BANGLADESH',
+    'BE': 'BELGIUM',
+    'BF': 'BURKINA FASO',
+    'BG': 'BULGARIA',
+    'BH': 'BAHRAIN',
+    'BI': 'BURUNDI',
+    'BJ': 'BENIN',
+    'BM': 'BERMUDA',
+    'BN': 'BRUNEI DARUSSALAM',
+    'BO': 'BOLIVIA',
+    'BR': 'BRAZIL',
+    'BS': 'BAHAMAS',
+    'BT': 'BHUTAN',
+    'BV': 'BOUVET ISLAND',
+    'BW': 'BOTSWANA',
+    'BY': 'BELARUS',
+    'BZ': 'BELIZE',
+    'CA': 'CANADA',
+    'CC': 'COCOS (KEELING) ISLANDS',
+    'CD': 'CONGO, THE DEMOCRATIC REPUBLIC OF THE',
+    'CF': 'CENTRAL AFRICAN REPUBLIC',
+    'CG': 'CONGO',
+    'CH': 'SWITZERLAND',
+    'CI': "COTE D'IVOIRE",
+    'CK': 'COOK ISLANDS',
+    'CL': 'CHILE',
+    'CM': 'CAMEROON',
+    'CN': 'CHINA',
+    'CO': 'COLOMBIA',
+    'CR': 'COSTA RICA',
+    'CS': 'SERBIA AND MONTENEGRO',
+    'CU': 'CUBA',
+    'CV': 'CAPE VERDE',
+    'CX': 'CHRISTMAS ISLAND',
+    'CY': 'CYPRUS',
+    'CZ': 'CZECH REPUBLIC',
+    'DE': 'GERMANY',
+    'DJ': 'DJIBOUTI',
+    'DK': 'DENMARK',
+    'DM': 'DOMINICA',
+    'DO': 'DOMINICAN REPUBLIC',
+    'DZ': 'ALGERIA',
+    'EC': 'ECUADOR',
+    'EE': 'ESTONIA',
+    'EG': 'EGYPT',
+    'EH': 'WESTERN SAHARA',
+    'ER': 'ERITREA',
+    'ES': 'SPAIN',
+    'ET': 'ETHIOPIA',
+    'FI': 'FINLAND',
+    'FJ': 'FIJI',
+    'FK': 'FALKLAND ISLANDS (MALVINAS)',
+    'FM': 'MICRONESIA, FEDERATED STATES OF',
+    'FO': 'FAROE ISLANDS',
+    'FR': 'FRANCE',
+    'GA': 'GABON',
+    'GB': 'UNITED KINGDOM',
+    'GD': 'GRENADA',
+    'GE': 'GEORGIA',
+    'GF': 'FRENCH GUIANA',
+    'GH': 'GHANA',
+    'GI': 'GIBRALTAR',
+    'GL': 'GREENLAND',
+    'GM': 'GAMBIA',
+    'GN': 'GUINEA',
+    'GP': 'GUADELOUPE',
+    'GQ': 'EQUATORIAL GUINEA',
+    'GR': 'GREECE',
+    'GS': 'SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS',
+    'GT': 'GUATEMALA',
+    'GU': 'GUAM',
+    'GW': 'GUINEA-BISSAU',
+    'GY': 'GUYANA',
+    'HK': 'HONG KONG',
+    'HM': 'HEARD ISLAND AND MCDONALD ISLANDS',
+    'HN': 'HONDURAS',
+    'HR': 'CROATIA',
+    'HT': 'HAITI',
+    'HU': 'HUNGARY',
+    'ID': 'INDONESIA',
+    'IE': 'IRELAND',
+    'IL': 'ISRAEL',
+    'IN': 'INDIA',
+    'IO': 'BRITISH INDIAN OCEAN TERRITORY',
+    'IQ': 'IRAQ',
+    'IR': 'IRAN, ISLAMIC REPUBLIC OF',
+    'IS': 'ICELAND',
+    'IT': 'ITALY',
+    'JM': 'JAMAICA',
+    'JO': 'JORDAN',
+    'JP': 'JAPAN',
+    'KE': 'KENYA',
+    'KG': 'KYRGYZSTAN',
+    'KH': 'CAMBODIA',
+    'KI': 'KIRIBATI',
+    'KM': 'COMOROS',
+    'KN': 'SAINT KITTS AND NEVIS',
+    'KP': "KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF",
+    'KR': 'KOREA, REPUBLIC OF',
+    'KW': 'KUWAIT',
+    'KY': 'CAYMAN ISLANDS',
+    'KZ': 'KAZAKHSTAN',
+    'LA': "LAO PEOPLE'S DEMOCRATIC REPUBLIC",
+    'LB': 'LEBANON',
+    'LC': 'SAINT LUCIA',
+    'LI': 'LIECHTENSTEIN',
+    'LK': 'SRI LANKA',
+    'LR': 'LIBERIA',
+    'LS': 'LESOTHO',
+    'LT': 'LITHUANIA',
+    'LU': 'LUXEMBOURG',
+    'LV': 'LATVIA',
+    'LY': 'LIBYAN ARAB JAMAHIRIYA',
+    'MA': 'MOROCCO',
+    'MC': 'MONACO',
+    'MD': 'MOLDOVA, REPUBLIC OF',
+    'MG': 'MADAGASCAR',
+    'MH': 'MARSHALL ISLANDS',
+    'MK': 'MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF',
+    'ML': 'MALI',
+    'MM': 'MYANMAR',
+    'MN': 'MONGOLIA',
+    'MO': 'MACAO',
+    'MP': 'NORTHERN MARIANA ISLANDS',
+    'MQ': 'MARTINIQUE',
+    'MR': 'MAURITANIA',
+    'MS': 'MONTSERRAT',
+    'MT': 'MALTA',
+    'MU': 'MAURITIUS',
+    'MV': 'MALDIVES',
+    'MW': 'MALAWI',
+    'MX': 'MEXICO',
+    'MY': 'MALAYSIA',
+    'MZ': 'MOZAMBIQUE',
+    'NA': 'NAMIBIA',
+    'NC': 'NEW CALEDONIA',
+    'NE': 'NIGER',
+    'NF': 'NORFOLK ISLAND',
+    'NG': 'NIGERIA',
+    'NI': 'NICARAGUA',
+    'NL': 'NETHERLANDS',
+    'NO': 'NORWAY',
+    'NP': 'NEPAL',
+    'NR': 'NAURU',
+    'NU': 'NIUE',
+    'NZ': 'NEW ZEALAND',
+    'OM': 'OMAN',
+    'PA': 'PANAMA',
+    'PE': 'PERU',
+    'PF': 'FRENCH POLYNESIA',
+    'PG': 'PAPUA NEW GUINEA',
+    'PH': 'PHILIPPINES',
+    'PK': 'PAKISTAN',
+    'PL': 'POLAND',
+    'PM': 'SAINT PIERRE AND MIQUELON',
+    'PN': 'PITCAIRN',
+    'PR': 'PUERTO RICO',
+    #'PS': 'PALESTINIAN TERRITORY, OCCUPIED',
+    'PT': 'PORTUGAL',
+    'PW': 'PALAU',
+    'PY': 'PARAGUAY',
+    'QA': 'QATAR',
+    'RE': 'REUNION',
+    'RO': 'ROMANIA',
+    'RU': 'RUSSIAN FEDERATION',
+    'RW': 'RWANDA',
+    'SA': 'SAUDI ARABIA',
+    'SB': 'SOLOMON ISLANDS',
+    'SC': 'SEYCHELLES',
+    'SD': 'SUDAN',
+    'SE': 'SWEDEN',
+    'SG': 'SINGAPORE',
+    'SH': 'SAINT HELENA',
+    'SI': 'SLOVENIA',
+    'SJ': 'SVALBARD AND JAN MAYEN',
+    'SK': 'SLOVAKIA',
+    'SL': 'SIERRA LEONE',
+    'SM': 'SAN MARINO',
+    'SN': 'SENEGAL',
+    'SO': 'SOMALIA',
+    'SR': 'SURINAME',
+    'ST': 'SAO TOME AND PRINCIPE',
+    'SV': 'EL SALVADOR',
+    'SY': 'SYRIAN ARAB REPUBLIC',
+    'SZ': 'SWAZILAND',
+    'TC': 'TURKS AND CAICOS ISLANDS',
+    'TD': 'CHAD',
+    'TF': 'FRENCH SOUTHERN TERRITORIES',
+    'TG': 'TOGO',
+    'TH': 'THAILAND',
+    'TJ': 'TAJIKISTAN',
+    'TK': 'TOKELAU',
+    'TL': 'TIMOR-LESTE',
+    'TM': 'TURKMENISTAN',
+    'TN': 'TUNISIA',
+    'TO': 'TONGA',
+    'TR': 'TURKEY',
+    'TT': 'TRINIDAD AND TOBAGO',
+    'TV': 'TUVALU',
+    'TW': 'TAIWAN, PROVINCE OF CHINA',
+    'TZ': 'TANZANIA, UNITED REPUBLIC OF',
+    'UA': 'UKRAINE',
+    'UG': 'UGANDA',
+    'UM': 'UNITED STATES MINOR OUTLYING ISLANDS',
+    'US': 'UNITED STATES',
+    'UY': 'URUGUAY',
+    'UZ': 'UZBEKISTAN',
+    'VA': 'HOLY SEE (VATICAN CITY STATE)',
+    'VC': 'SAINT VINCENT AND THE GRENADINES',
+    'VE': 'VENEZUELA',
+    'VG': 'VIRGIN ISLANDS, BRITISH',
+    'VI': 'VIRGIN ISLANDS, U.S.',
+    'VN': 'VIET NAM',
+    'VU': 'VANUATU',
+    'WF': 'WALLIS AND FUTUNA',
+    'WS': 'SAMOA',
+    'YE': 'YEMEN',
+    'YT': 'MAYOTTE',
+    'ZA': 'SOUTH AFRICA',
+    'ZM': 'ZAMBIA',
+    'ZW': 'ZIMBABWE'
+}
+
+# Additional codes used by registrars
+cc2name.update({
+    'UK': cc2name['GB'],
+    'EU': 'EUROPEAN UNION',
+    'AP': 'ASSIGNED PORTABLE',
+    'YU': 'FORMER YUGOSLAVIA',
+})
+
+
+if __name__=='__main__':
+    import sys, os
+    db_file = os.path.splitext(sys.argv[0])[0]+'.db'
+    if os.environ.get('REQUEST_URI'):
+        import cgi
+        form = cgi.FieldStorage()
+        try:
+            addr = form['addr'].value
+        except (KeyError, AttributeError):
+            addr = ''
+        msg = ''
+
+        if addr:
+            if not is_IP(addr):
+                msg = '%s is not valid IP address' % cgi.escape(addr)
+            else:
+                db = CountryByIP(db_file)
+                try:
+                    cc = db[addr]
+                except KeyError:
+                    msg = 'Information for %s not found' % cgi.escape(addr)
+                else:
+                    msg = '%s is located in %s' % (cgi.escape(addr),
+                                                   cc2name.get(cc, cc))
+        script_name = os.environ['SCRIPT_NAME']
+        print '''\
+Content-Type: text/html
+
+<html>
+<head><title>Country By IP</title></head>
+<body>
+<h1>Country By IP</h1>
+<form action="%(script_name)s">
+<input type="text" name="addr" value="%(addr)s">
+</form>
+%(msg)s
+<hr>
+<a href="http://ppa.sf.net/#ip2cc">ip2cc %(__version__)s</a>
+</body>
+</html>''' % vars()
+
+    elif len(sys.argv)==2:
+        addr = sys.argv[1]
+        if is_IP(addr):
+            ip = addr_str = addr
+        else:
+            from socket import gethostbyname, gaierror
+            try:
+                ip = gethostbyname(addr)
+            except gaierror, exc:
+                sys.exit(exc)
+            else:
+                addr_str = '%s (%s)' % (addr, ip)
+        db = CountryByIP(db_file)
+        try:
+            cc = db[ip]
+        except KeyError:
+            sys.exit('Information for %s not found' % addr)
+        else:
+            print '%s is located in %s' % (addr_str, cc2name.get(cc, cc))
+    else:
+        sys.exit('Usage:\n\t%s <address>' % sys.argv[0])
+#!/usr/local/bin/python
+# $Id$
+
+from urllib import urlopen
+from socket import inet_aton, inet_ntoa
+import struct, sys, os
+from ip2cc import cc2name
+
+
+class CountryByIPTree:
+
+    # Change it to the closest mirror. Official are:
+    #   ftp://ftp.ripe.net/pub/stats/
+    #   ftp://ftp.arin.net/pub/stats/
+    #   ftp://ftp.apnic.net/pub/stats/
+    #   ftp://ftp.lacnic.net/pub/stats/
+    url_template = 'ftp://ftp.ripe.net/pub/stats/%s/delegated-%s-latest'
+    sources = {}
+    for name in ('arin', 'ripencc', 'apnic', 'lacnic'):
+        sources[name] = url_template % (name, name)
+
+    def __init__(self):
+        self._tree = ['\x00\x00']*256
+
+    def fetch(self):
+        for name, source in self.sources.items():
+            fp = urlopen(source)
+            for line in iter(fp.readline, ''):
+                parts = line.strip().split('|')
+                if len(parts)==7 and parts[2]=='ipv4' and \
+                        parts[6] in ('allocated', 'assigned') and \
+                        name==parts[0]:
+                    first = parts[3]
+                    first_int = struct.unpack('!I', inet_aton(first))[0]
+                    last_int = first_int+int(parts[4])-1
+                    last = inet_ntoa(struct.pack('!I', last_int))
+                    try:
+                        self.add(first, last, parts[1].upper())
+                    except ValueError:
+                        pass
+
+    def add(self, start, end, cc):
+        if not cc2name.has_key(cc):
+            sys.stderr.write('Warning: unknown country code %r\n' % (cc))
+        self._set_range(self._tree, [int(n) for n in start.split('.')],
+                        [int(n) for n in end.split('.')], cc)
+
+    def _get_sub_tree(self, tree, idx):
+        if tree[idx]=='\x00\x00':
+            tree[idx] = ['\x00\x00']*256
+        return tree[idx]
+    
+    def _set_range(self, tree, start, end, cc):
+        #print '   '*(4-len(start)), start, end, cc
+        assert isinstance(tree, list)
+        assert len(start)==len(end)
+        if start[0]==end[0]:
+            if start[1:]==[0]*(len(start)-1) and end[1:]==[255]*(len(end)-1):
+                tree[start[0]] = cc
+            else:
+                self._set_range(self._get_sub_tree(tree, start[0]),
+                                start[1:], end[1:], cc)
+        elif len(start)==1:
+            for idx in range(start[0], end[0]+1):
+                tree[idx] = cc
+        else:
+            if start[1:]==[0]*(len(start)-1):
+                tree[start[0]] = cc
+            else:
+                self._set_range(self._get_sub_tree(tree, start[0]),
+                                start[1:], [255]*(len(start)-1), cc)
+            for idx in range(start[0]+1, end[0]):
+                tree[idx] = cc
+            if end[1:]==[255]*(len(end)-1):
+                tree[end[0]] = cc
+            else:
+                self._set_range(self._get_sub_tree(tree, end[0]),
+                                [0]*(len(end)-1), end[1:], cc)
+
+    def optimize(self, tree=None):
+        if tree is None:
+            tree = self._tree
+        for idx in range(256):
+            node = tree[idx]
+            if isinstance(node, list):
+                assert len(node)==256
+                if node==[node[0]]*256:
+                    tree[idx] = node[0]
+                else:
+                    self.optimize(node)
+
+    def dump(self, offset=0, tree=None):
+        if tree is None:
+            tree = self._tree
+        assert isinstance(tree, list)
+        import struct
+        offsets = []
+        subtrees = []
+        end = offset+256*4
+        for node in tree:
+            if isinstance(node, str):
+                offsets.append('\xFF\xFF'+node)
+            else:
+                offsets.append(struct.pack('!I', end))
+                subtrees.append(self.dump(end, node))
+                end += len(subtrees[-1])
+        assert len(''.join(offsets))==256*4
+        result = ''.join(offsets+subtrees)
+        assert len(result)==end-offset
+        return result
+
+
+if __name__=='__main__':
+    tree = CountryByIPTree()
+    tree.fetch()
+    tree.optimize()
+    db_file = os.path.join(os.path.dirname(sys.argv[0]), 'ip2cc.db')
+    db = open(db_file+'.new', 'wb')
+    db.write(tree.dump())
+    db.close()
+    os.rename(db_file+'.new', db_file)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.