/** * LookupService.java * * Copyright (C) 2003 MaxMind LLC. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.maxmind.geoip; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.net.InetAddress; import java.net.Inet6Address; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Hashtable; import java.util.StringTokenizer; import javax.naming.NamingException; import javax.naming.directory.Attributes; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; /** * Provides a lookup service for information based on an IP address. The location of * a database file is supplied when creating a lookup service instance. The edition of * the database determines what information is available about an IP address. See the * DatabaseInfo class for further details.
* * The following code snippet demonstrates looking up the country that an IP * address is from: *
* // First, create a LookupService instance with the location of the database. * LookupService lookupService = new LookupService("c:\\geoip.dat"); * // Assume we have a String ipAddress (in dot-decimal form). * Country country = lookupService.getCountry(ipAddress); * System.out.println("The country is: " + country.getName()); * System.out.println("The country code is: " + country.getCode()); ** * In general, a single LookupService instance should be created and then reused * repeatedly.
* * Tip: Those deploying the GeoIP API as part of a web application may find it * difficult to pass in a File to create the lookup service, as the location of the * database may vary per deployment or may even be part of the web-application. In this * case, the database should be added to the classpath of the web-app. For example, by * putting it into the WEB-INF/classes directory of the web application. The following code * snippet demonstrates how to create a LookupService using a database that can be found * on the classpath: * *
* String fileName = getClass().getResource("/GeoIP.dat").toExternalForm().substring(6); * LookupService lookupService = new LookupService(fileName);* * @author Matt Tucker (matt@jivesoftware.com) */ public class LookupService { /** * Database file. */ private RandomAccessFile file = null; private File databaseFile = null; /** * Information about the database. */ private DatabaseInfo databaseInfo = null; /** * The database type. Default is the country edition. */ byte databaseType = DatabaseInfo.COUNTRY_EDITION; int databaseSegments[]; int recordLength; String licenseKey; int dnsService = 0; int dboptions; byte dbbuffer[]; byte index_cache[]; long mtime; int last_netmask; private final static int US_OFFSET = 1; private final static int CANADA_OFFSET = 677; private final static int WORLD_OFFSET = 1353; private final static int FIPS_RANGE = 360; private final static int COUNTRY_BEGIN = 16776960; private final static int STATE_BEGIN_REV0 = 16700000; private final static int STATE_BEGIN_REV1 = 16000000; private final static int STRUCTURE_INFO_MAX_SIZE = 20; private final static int DATABASE_INFO_MAX_SIZE = 100; public final static int GEOIP_STANDARD = 0; public final static int GEOIP_MEMORY_CACHE = 1; public final static int GEOIP_CHECK_CACHE = 2; public final static int GEOIP_INDEX_CACHE = 4; public final static int GEOIP_UNKNOWN_SPEED = 0; public final static int GEOIP_DIALUP_SPEED = 1; public final static int GEOIP_CABLEDSL_SPEED = 2; public final static int GEOIP_CORPORATE_SPEED = 3; private final static int SEGMENT_RECORD_LENGTH = 3; private final static int STANDARD_RECORD_LENGTH = 3; private final static int ORG_RECORD_LENGTH = 4; private final static int MAX_RECORD_LENGTH = 4; private final static int MAX_ORG_RECORD_LENGTH = 300; private final static int FULL_RECORD_LENGTH = 60; private final Country UNKNOWN_COUNTRY = new Country("--", "N/A"); private static final HashMap hashmapcountryCodetoindex = new HashMap(512); private static final HashMap hashmapcountryNametoindex = new HashMap(512); private static final String[] countryCode = { "--","AP","EU","AD","AE","AF","AG","AI","AL","AM","AN","AO","AQ","AR", "AS","AT","AU","AW","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ", "BM","BN","BO","BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD","CF", "CG","CH","CI","CK","CL","CM","CN","CO","CR","CU","CV","CX","CY","CZ", "DE","DJ","DK","DM","DO","DZ","EC","EE","EG","EH","ER","ES","ET","FI", "FJ","FK","FM","FO","FR","FX","GA","GB","GD","GE","GF","GH","GI","GL", "GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR", "HT","HU","ID","IE","IL","IN","IO","IQ","IR","IS","IT","JM","JO","JP", "KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ","LA","LB","LC", "LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","MG","MH","MK", "ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY", "MZ","NA","NC","NE","NF","NG","NI","NL","NO","NP","NR","NU","NZ","OM", "PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS","PT","PW","PY", "QA","RE","RO","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI","SJ", "SK","SL","SM","SN","SO","SR","ST","SV","SY","SZ","TC","TD","TF","TG", "TH","TJ","TK","TM","TN","TO","TL","TR","TT","TV","TW","TZ","UA","UG", "UM","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","YE", "YT","RS","ZA","ZM","ME","ZW","A1","A2","O1","AX","GG","IM","JE","BL", "MF"}; private static final String[] countryName = { "N/A","Asia/Pacific Region","Europe","Andorra","United Arab Emirates", "Afghanistan","Antigua and Barbuda","Anguilla","Albania","Armenia", "Netherlands Antilles","Angola","Antarctica","Argentina","American Samoa", "Austria","Australia","Aruba","Azerbaijan","Bosnia and Herzegovina", "Barbados","Bangladesh","Belgium","Burkina Faso","Bulgaria","Bahrain", "Burundi","Benin","Bermuda","Brunei Darussalam","Bolivia","Brazil","Bahamas", "Bhutan","Bouvet Island","Botswana","Belarus","Belize","Canada", "Cocos (Keeling) Islands","Congo, The Democratic Republic of the", "Central African Republic","Congo","Switzerland","Cote D'Ivoire", "Cook Islands","Chile","Cameroon","China","Colombia","Costa Rica","Cuba", "Cape Verde","Christmas Island","Cyprus","Czech Republic","Germany", "Djibouti","Denmark","Dominica","Dominican Republic","Algeria","Ecuador", "Estonia","Egypt","Western Sahara","Eritrea","Spain","Ethiopia","Finland", "Fiji","Falkland Islands (Malvinas)","Micronesia, Federated States of", "Faroe Islands","France","France, Metropolitan","Gabon","United Kingdom", "Grenada","Georgia","French Guiana","Ghana","Gibraltar","Greenland","Gambia", "Guinea","Guadeloupe","Equatorial Guinea","Greece", "South Georgia and the South Sandwich Islands","Guatemala","Guam", "Guinea-Bissau","Guyana","Hong Kong","Heard Island and McDonald Islands", "Honduras","Croatia","Haiti","Hungary","Indonesia","Ireland","Israel","India", "British Indian Ocean Territory","Iraq","Iran, Islamic Republic of", "Iceland","Italy","Jamaica","Jordan","Japan","Kenya","Kyrgyzstan","Cambodia", "Kiribati","Comoros","Saint Kitts and Nevis", "Korea, Democratic People's Republic of","Korea, Republic of","Kuwait", "Cayman Islands","Kazakhstan","Lao People's Democratic Republic","Lebanon", "Saint Lucia","Liechtenstein","Sri Lanka","Liberia","Lesotho","Lithuania", "Luxembourg","Latvia","Libyan Arab Jamahiriya","Morocco","Monaco", "Moldova, Republic of","Madagascar","Marshall Islands", "Macedonia","Mali","Myanmar","Mongolia", "Macau","Northern Mariana Islands","Martinique","Mauritania","Montserrat", "Malta","Mauritius","Maldives","Malawi","Mexico","Malaysia","Mozambique", "Namibia","New Caledonia","Niger","Norfolk Island","Nigeria","Nicaragua", "Netherlands","Norway","Nepal","Nauru","Niue","New Zealand","Oman","Panama", "Peru","French Polynesia","Papua New Guinea","Philippines","Pakistan", "Poland","Saint Pierre and Miquelon","Pitcairn Islands","Puerto Rico","" + "Palestinian Territory","Portugal","Palau","Paraguay","Qatar", "Reunion","Romania","Russian Federation","Rwanda","Saudi Arabia", "Solomon Islands","Seychelles","Sudan","Sweden","Singapore","Saint Helena", "Slovenia","Svalbard and Jan Mayen","Slovakia","Sierra Leone","San Marino", "Senegal","Somalia","Suriname","Sao Tome and Principe","El Salvador", "Syrian Arab Republic","Swaziland","Turks and Caicos Islands","Chad", "French Southern Territories","Togo","Thailand","Tajikistan","Tokelau", "Turkmenistan","Tunisia","Tonga","Timor-Leste","Turkey","Trinidad and Tobago", "Tuvalu","Taiwan","Tanzania, United Republic of","Ukraine","Uganda", "United States Minor Outlying Islands","United States","Uruguay","Uzbekistan", "Holy See (Vatican City State)","Saint Vincent and the Grenadines", "Venezuela","Virgin Islands, British","Virgin Islands, U.S.","Vietnam", "Vanuatu","Wallis and Futuna","Samoa","Yemen","Mayotte","Serbia", "South Africa","Zambia","Montenegro","Zimbabwe","Anonymous Proxy", "Satellite Provider","Other","Aland Islands","Guernsey","Isle of Man","Jersey", "Saint Barthelemy","Saint Martin"}; /* init the hashmap once at startup time */ static { int i; if(countryCode.length!=countryName.length) throw new AssertionError("countryCode.length!=countryName.length"); // distributed service only for (i = 0; i < countryCode.length ;i++){ hashmapcountryCodetoindex.put(countryCode[i],Integer.valueOf(i)); hashmapcountryNametoindex.put(countryName[i],Integer.valueOf(i)); } }; /** * Create a new distributed lookup service using the license key * * @param databaseFile String representation of the database file. * @param licenseKey license key provided by Maxmind to access distributed service */ public LookupService(String databaseFile,String licenseKey) throws IOException { this(new File(databaseFile)); this.licenseKey = licenseKey; dnsService = 1; } /** * Create a new distributed lookup service using the license key * * @param databaseFile the database file. * @param licenseKey license key provided by Maxmind to access distributed service */ public LookupService(File databaseFile,String licenseKey) throws IOException { this(databaseFile); this.licenseKey = licenseKey; dnsService = 1; } /** * Create a new distributed lookup service using the license key * * @param options Resevered for future use * @param licenseKey license key provided by Maxmind to access distributed service */ public LookupService(int options,String licenseKey) throws IOException { this.licenseKey = licenseKey; dnsService = 1; init(); } /** * Create a new lookup service using the specified database file. * * @param databaseFile String representation of the database file. * @throws java.io.IOException if an error occured creating the lookup service * from the database file. */ public LookupService(String databaseFile) throws IOException { this(new File(databaseFile)); } /** * Create a new lookup service using the specified database file. * * @param databaseFile the database file. * @throws java.io.IOException if an error occured creating the lookup service * from the database file. */ public LookupService(File databaseFile) throws IOException { this.databaseFile = databaseFile; this.file = new RandomAccessFile(databaseFile, "r"); init(); } /** * Create a new lookup service using the specified database file. * * @param databaseFile String representation of the database file. * @param options database flags to use when opening the database * GEOIP_STANDARD read database from disk * GEOIP_MEMORY_CACHE cache the database in RAM and read it from RAM * @throws java.io.IOException if an error occured creating the lookup service * from the database file. */ public LookupService(String databaseFile, int options) throws IOException{ this(new File(databaseFile),options); } /** * Create a new lookup service using the specified database file. * * @param databaseFile the database file. * @param options database flags to use when opening the database * GEOIP_STANDARD read database from disk * GEOIP_MEMORY_CACHE cache the database in RAM and read it from RAM * @throws java.io.IOException if an error occured creating the lookup service * from the database file. */ public LookupService(File databaseFile, int options) throws IOException{ this.databaseFile = databaseFile; this.file = new RandomAccessFile(databaseFile, "r"); dboptions = options; init(); } /** * Reads meta-data from the database file. * * @throws java.io.IOException if an error occurs reading from the database file. */ private void init() throws IOException { int i, j; byte [] delim = new byte[3]; byte [] buf = new byte[SEGMENT_RECORD_LENGTH]; if (file == null) { return; } if ((dboptions & GEOIP_CHECK_CACHE) != 0) { mtime = databaseFile.lastModified(); } file.seek(file.length() - 3); for (i = 0; i < STRUCTURE_INFO_MAX_SIZE; i++) { file.readFully(delim); if (delim[0] == -1 && delim[1] == -1 && delim[2] == -1) { databaseType = file.readByte(); if (databaseType >= 106) { // Backward compatibility with databases from April 2003 and earlier databaseType -= 105; } // Determine the database type. if (databaseType == DatabaseInfo.REGION_EDITION_REV0) { databaseSegments = new int[1]; databaseSegments[0] = STATE_BEGIN_REV0; recordLength = STANDARD_RECORD_LENGTH; }else if (databaseType == DatabaseInfo.REGION_EDITION_REV1){ databaseSegments = new int[1]; databaseSegments[0] = STATE_BEGIN_REV1; recordLength = STANDARD_RECORD_LENGTH; } else if (databaseType == DatabaseInfo.CITY_EDITION_REV0 || databaseType == DatabaseInfo.CITY_EDITION_REV1 || databaseType == DatabaseInfo.ORG_EDITION || databaseType == DatabaseInfo.ISP_EDITION || databaseType == DatabaseInfo.ASNUM_EDITION) { databaseSegments = new int[1]; databaseSegments[0] = 0; if (databaseType == DatabaseInfo.CITY_EDITION_REV0 || databaseType == DatabaseInfo.CITY_EDITION_REV1 || databaseType == DatabaseInfo.ASNUM_EDITION) { recordLength = STANDARD_RECORD_LENGTH; } else { recordLength = ORG_RECORD_LENGTH; } file.readFully(buf); for (j = 0; j < SEGMENT_RECORD_LENGTH; j++) { databaseSegments[0] += (unsignedByteToInt(buf[j]) << (j * 8)); } } break; } else { file.seek(file.getFilePointer() - 4); } } if ((databaseType == DatabaseInfo.COUNTRY_EDITION) || (databaseType == DatabaseInfo.COUNTRY_EDITION_V6) || (databaseType == DatabaseInfo.PROXY_EDITION) || (databaseType == DatabaseInfo.NETSPEED_EDITION)) { databaseSegments = new int[1]; databaseSegments[0] = COUNTRY_BEGIN; recordLength = STANDARD_RECORD_LENGTH; } if ((dboptions & GEOIP_MEMORY_CACHE) == 1) { int l = (int) file.length(); dbbuffer = new byte[l]; file.seek(0); file.readFully(dbbuffer,0,l); databaseInfo = this.getDatabaseInfo(); file.close(); } if ((dboptions & GEOIP_INDEX_CACHE) != 0) { int l = databaseSegments[0] * recordLength * 2; index_cache = new byte[l]; if (index_cache != null){ file.seek(0); file.readFully(index_cache,0,l); } } else { index_cache = null; } } /** * Closes the lookup service. */ public void close() { try { if (file != null){ file.close(); } file = null; } catch (Exception e) { } } /** * Returns the country the IP address is in. * * @param ipAddress String version of an IPv6 address, i.e. "::127.0.0.1" * @return the country the IP address is from. */ public Country getCountryV6(String ipAddress) { InetAddress addr; try { addr = Inet6Address.getByName(ipAddress); } catch (UnknownHostException e) { return UNKNOWN_COUNTRY; } return getCountryV6(addr); } /** * Returns the country the IP address is in. * * @param ipAddress String version of an IP address, i.e. "127.0.0.1" * @return the country the IP address is from. */ public Country getCountry(String ipAddress) { InetAddress addr; try { addr = InetAddress.getByName(ipAddress); } catch (UnknownHostException e) { return UNKNOWN_COUNTRY; } return getCountry(bytesToLong(addr.getAddress())); } /** * Returns the country the IP address is in. * * @param ipAddress the IP address. * @return the country the IP address is from. */ public synchronized Country getCountry(InetAddress ipAddress) { return getCountry(bytesToLong(ipAddress.getAddress())); } /** * Returns the country the IP address is in. * * @param addr the IP address as Inet6Address. * @return the country the IP address is from. */ public Country getCountryV6(InetAddress addr) { if (file == null && (dboptions & GEOIP_MEMORY_CACHE) == 0) { throw new IllegalStateException("Database has been closed."); } int ret = seekCountryV6(addr) - COUNTRY_BEGIN; if (ret == 0) { return UNKNOWN_COUNTRY; } else { return new Country(countryCode[ret], countryName[ret]); } } /** * Returns the country the IP address is in. * * @param ipAddress the IP address in long format. * @return the country the IP address is from. */ public Country getCountry(long ipAddress) { if (file == null && (dboptions & GEOIP_MEMORY_CACHE) == 0) { throw new IllegalStateException("Database has been closed."); } int ret = seekCountry(ipAddress) - COUNTRY_BEGIN; if (ret == 0) { return UNKNOWN_COUNTRY; } else { return new Country(countryCode[ret], countryName[ret]); } } public int getID(String ipAddress) { InetAddress addr; try { addr = InetAddress.getByName(ipAddress); } catch (UnknownHostException e) { return 0; } return getID(bytesToLong(addr.getAddress())); } public int getID(InetAddress ipAddress) { return getID(bytesToLong(ipAddress.getAddress())); } public synchronized int getID(long ipAddress) { if (file == null && (dboptions & GEOIP_MEMORY_CACHE) == 0) { throw new IllegalStateException("Database has been closed."); } int ret = seekCountry(ipAddress) - databaseSegments[0]; return ret; } public int last_netmask() { return this.last_netmask; } public void netmask(int nm){ this.last_netmask = nm; } /** * Returns information about the database. * * @return database info. */ public synchronized DatabaseInfo getDatabaseInfo() { if (databaseInfo != null) { return databaseInfo; } try { _check_mtime(); boolean hasStructureInfo = false; byte [] delim = new byte[3]; // Advance to part of file where database info is stored. file.seek(file.length() - 3); for (int i=0; i