Packages

IP -> Country Code converted with built-in database.

Current section

Files

Jump to
ip2country lib ip2country.ex
Raw

lib/ip2country.ex

defmodule IP2Country do
@moduledoc """
IP2Country is a simple geolocating module (IP -> Country Code) with built-in database.
Database is provided by https://db-ip.com, and it is compiled into the module. Compilation time is about
20 seconds on 2-core machine. Searching time is about 4µs.
To update the database, download it from https://db-ip.com/db/download/country, unzip and replace the file
located in `lib/db`.
"""
@db "db/dbip-country.csv"
@external_resource @db
import IP2Country.Converters
IO.puts "Compiling DBIP database, please be patient"
dbip_list = File.stream!(Path.join([__DIR__, @db]), [], :line)
|> Stream.filter(&String.contains?(&1, ".")) # check if it is a line with IPv4
|> Stream.map(&decode_line/1)
|> Stream.map(fn [ip1, ip2, country] -> {ip2integer(ip1), ip2integer(ip2), String.to_atom(country)} end)
|> Enum.sort()
|> List.to_tuple
@dbip_list dbip_list
@doc """
Retrieves country code based on the IP address.
Expects string with a proper IP as an argument. Currently works only with IPv4.
Returns atom with the Country Code, uppercased, or `:ZZ` for private addresses.
## Examples
iex> IP2Country.whereis("51.254.116.38")
:FR
iex> IP2Country.whereis("176.9.20.99")
:DE
iex> IP2Country.whereis("83.144.118.166")
:PL
iex> IP2Country.whereis("0.0.0.0")
:ZZ
iex> IP2Country.whereis("255.255.255.255")
:ZZ
iex> IP2Country.whereis("2.17.212.254")
:PL
iex> IP2Country.whereis("2.17.212.255")
:PL
iex> IP2Country.whereis("2.17.213.0")
:SE
iex> IP2Country.whereis("2.17.213.1")
:SE
"""
@spec whereis(String.t) :: atom
def whereis(ip) when is_binary(ip) do
ipint = ip2integer(ip)
# {_, country} = Enum.find(@dbip_list, fn {ip2, _} -> ipint <= ip2 end)
t = @dbip_list
{:ok, country} = bsearch(ipint, t, 0, tuple_size(t))
country
end
def whereis({a, b, c, d}) do
whereis("#{a}.#{b}.#{c}.#{d}")
end
# bsearch borrowed from microcrawler:
# https://github.com/ApolloCrawler/microcrawler-webapp/blob/master/lib/microcrawler_webapp/ip_info.ex
defp bsearch(ip, ranges, low, high) do
mid = div(low + high, 2)
{start, stop, code} = elem(ranges, mid)
case {ip >= start, ip <= stop} do
{true, true} -> {:ok, code}
{true, false} -> bsearch(ip, ranges, mid + 1, high)
{false, true} -> bsearch(ip, ranges, low, mid - 1)
end
end
end