Identify Country From IP Address
Many people sometimes would like to identify country from IP address when you check access log or something.
Most of the people google with keywords like "ip address country" or "whois ip" or something, and then use the internet service which they find.
In this post, I will show you program for identifying country from IP address.
I wrote the program in Java, but if you an average developer you can easily translate into the program language you prefer.
Using File Provided by RIR
IP address is allocated, registered and managed by regional internet registry (RIR).
There are five organization based on covering region:
- African Network Information Centre (AfriNIC): Africa
- American Registry for Internet Numbers (ARIN): the United States, Canada, several parts of the Caribbean region, and Antarctica.
- Asia-Pacific Network Information Centre (APNIC): Asia, Australia, New Zealand, and neighboring countries
- Latin America and Caribbean Network Information Centre (LACNIC): Latin America and parts of the Caribbean region
- Réseaux IP Européens Network Coordination Centre (RIPE NCC): Europe, Russia, the Middle East, and Central Asia
So we use this file, then we can identify country from ip address! bravo!
The RIR file format is quite simple, something like below:
arin|US|ipv4|130.62.0.0|65536|19880609|assigned|f8c82702dc77343cbc6d17c0cb9f76d1
The important parts are:
- 2nd: country
- 3rd: type
- 4th: start ip address
- 5th: number of ip addresses allocated from the start which is on the 4th column
- 7th: status
Then you can understand much more details rather than my too simplified explanation :P
- http://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest
- http://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest
- http://ftp.apnic.net/pub/stats/apnic/delegated-apnic-extended-latest
- http://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-extended-latest
- http://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-extended-latest
Java Code
The program can do below:
- Download RIR files
- Create IP address range cache file
- Check country based on the IP address range cache file
- I think you might be better to store the RIR file data or cache file data in database for actual application usage.
- you should merge some continuous IP ranges for more efficiency. For simplicity, I left it as it is.
The advantage of this program is only using Java Standard Development Kit. i.e. no external library required!
Core Code
package com.dukesoftware.utils.net.ipaddress; import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Pattern; public class IpAddressCountryIdentifier { public static void main(String[] args) throws URISyntaxException, IOException { IpAddressCountryIdentifier checker = new IpAddressCountryIdentifier("c:/temp"); // you should not call following methods every ip address check // updating these files once a day should be fine // checker.saveRIRFiles(); // checker.createIpAddressFileFromRIRFile(); checker.loadIpAddessToMemory(); System.out.println(checker .guessCountry("ip address which you would like to test here")); } private static final String[] URLS = new String[] { "http://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest", "http://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest", "http://ftp.apnic.net/pub/stats/apnic/delegated-apnic-extended-latest", "http://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-extended-latest", "http://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-extended-latest" }; private final static Map<String, String> FILES = new HashMap<String, String>() { { Pattern pattern = Pattern.compile("^.+/"); for (String URL : URLS) { String fileBody = pattern.matcher(URL).replaceAll(""); put(URL, fileBody + ".txt"); } } }; private final Cache cacheV4 = new Cache( ipAddressStr -> ipAddressStr.contains("."), IpAddressCountryIdentifier::dotIPv4_to_BigInteger); private final Cache cacheV6 = new Cache( ipAddressStr -> ipAddressStr.contains(":"), IpAddressCountryIdentifier::colonIpV6_to_BigInteger); private final String directory; public IpAddressCountryIdentifier(String directory) { this.directory = directory; } public void downloadRIRFilesFromNIC() throws IOException { for (Entry<String, String> entry : FILES.entrySet()) { Utils.saveToFile(entry.getKey(), new File(directory, entry.getValue()).getAbsolutePath()); } } public void createIpAddressCacheFileFromRIRFile() throws IOException { File ipv4File = new File(directory, "ipv4.txt"); File ipv6File = new File(directory, "ipv6.txt"); ipv4File.delete(); ipv6File.delete(); for (Entry<String, String> entry : FILES.entrySet()) { File file = new File(directory, entry.getValue()); try (BufferedWriter bwV4 = newWriter(ipv4File); BufferedWriter bwV6 = newWriter(ipv6File)) { Utils.processLine(file, line -> { if (line.startsWith("#")) return; String[] parts = line.split("\\|"); if (parts.length < 7) return; String status = parts[6]; if (!(status.equals("allocated") || status .equals("assigned"))) return; String country = parts[1]; String type = parts[2]; String start = parts[3]; long value = 0; try { value = Long.valueOf(parts[4]); } catch (NumberFormatException e) { return; } try { if (type.equals("ipv4")) { bwV4.write(cacheV4.toCacheString(country, start, value)); } else if (type.equals("ipv6")) { bwV6.write(cacheV6.toCacheString(country, start, value)); } } catch (IOException e) { throw new RuntimeException("IOError while reading and writing ip address file", e); } }); } } } public void loadIpAddessToMemory() throws IOException { cacheV4.clear(); cacheV6.clear(); File ipv4File = new File(directory, "ipv4.txt"); File ipv6File = new File(directory, "ipv6.txt"); Utils.processLine(ipv4File, cacheV4::put); Utils.processLine(ipv6File, cacheV6::put); } public String guessCountry(String ipAddress) { if (cacheV4.isAcceptableIPString(ipAddress)) { return cacheV4.guessCountry(ipAddress); } if (cacheV6.isAcceptableIPString(ipAddress)) { return cacheV6.guessCountry(ipAddress); } throw new IllegalStateException("Unacceptable ip address format:" + ipAddress); } private static BufferedWriter newWriter(File file) throws UnsupportedEncodingException, FileNotFoundException { return new BufferedWriter(new OutputStreamWriter(new FileOutputStream( file, true), "utf-8")); } public static BigInteger dotIPv4_to_BigInteger(String dottedIP) { String[] addrArray = dottedIP.split("\\."); BigInteger num = BigInteger.ZERO; BigInteger block = BigInteger.valueOf(256); for (int i = 0; i < addrArray.length; i++) { int power = 3-i; BigInteger value = BigInteger.valueOf(Integer.parseInt(addrArray[i]) % 256); value = value.multiply(block.pow(power)); num = num.add(value); } return num; } public static BigInteger colonIpV6_to_BigInteger(String colonedIP) { String[] addrArray = colonedIP.split(":", -1); BigInteger num = BigInteger.ZERO; BigInteger block = BigInteger.valueOf(65536); for (int i = 0; i < addrArray.length; i++) { if(!addrArray[i].equals("")) { int power = 8-i; BigInteger value = BigInteger.valueOf(Long.parseLong(addrArray[i], 16) % 65536L); value = value.multiply(block.pow(power)); num = num.add(value); } } return num; } }
Class for represents IP address ranges with country
package com.dukesoftware.utils.net.ipaddress; import java.math.BigInteger; final class IpRange { private final BigInteger start; private final BigInteger end; private final String country; public IpRange(BigInteger start, BigInteger end, String country) { this.start = start; this.end = end; this.country = country; } boolean inRange(BigInteger value) { return value.compareTo(start) >= 0 && value.compareTo(end) <= 0; } public String getCountry() { return country; } }
Class for managing IP Address range cache files
package com.dukesoftware.utils.net.ipaddress; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import java.util.function.Predicate; class Cache { private final List<IpRange> ranges = new ArrayList<>(); private final Predicate<String> ipStringAcceptor; private final Function<String, BigInteger> ipStrToBigInteger; public Cache(Predicate<String> ipStringAcceptor, Function<String, BigInteger> ipStrToBigInteger) { this.ipStringAcceptor = ipStringAcceptor; this.ipStrToBigInteger = ipStrToBigInteger; } void put(String line) { String[] parts = line.split("\\|"); put(parts[0], new BigInteger(parts[1]), new BigInteger(parts[2])); } private void put(String country, BigInteger start, BigInteger end) { ranges.add(new IpRange(start, end, country)); } String guessCountry(String ipAddress) { if (isAcceptableIPString(ipAddress)) { BigInteger value = ipStrToBigInteger.apply(ipAddress); return searchInRange(value, ranges); } throw new IllegalStateException("Unacceptable ip address format:" + ipAddress); } private static String searchInRange(BigInteger value, List<IpRange> ranges) { for (IpRange range : ranges) { if (range.inRange(value)) { return range.getCountry(); } } return ""; } public String toCacheString(String country, String start, long value) { BigInteger startValue = ipStrToBigInteger.apply(start); BigInteger endValue = startValue.add(BigInteger.valueOf(value - 1)); return country + "|" + startValue + "|" + endValue + "\n"; } boolean isAcceptableIPString(String ipAddress) { return this.ipStringAcceptor.test(ipAddress); } void clear() { ranges.clear(); } }
Class for some trivial utility methods
package com.dukesoftware.utils.net.ipaddress; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.function.Consumer; public class Utils { public static final int _1K_BYTES = 1024; public final static void saveToFile(String url, String path) throws IOException { HttpURLConnection urlCon = null; try { URL urlObj = new URL(url); urlCon = (HttpURLConnection) urlObj.openConnection(); urlCon.setRequestMethod("GET"); try (InputStream is = urlCon.getInputStream(); OutputStream os = new FileOutputStream(path)) { byte[] buffer = new byte[_1K_BYTES]; for (int bytes = 0; (bytes = is.read(buffer)) != -1;) { os.write(buffer, 0, bytes); } os.flush(); } } finally { if (urlCon != null) { urlCon.disconnect(); } } } public final static void processLine(File file, Consumer<String> lp) throws IOException{ try(FileReader in = new FileReader(file); BufferedReader br = new BufferedReader(in)){ String line; while ((line = br.readLine()) != null) { lp.accept(line); } } } }
コメント
Determining When an Object Is No Longer Used