defmodule IpReserved do @moduledoc """ Documentation for `IpReserved`, a tool that checks if the given IP is private / reserved or "normal". Works with IPv4 and IPv6 addresses. """ @ip_v4_reserved_blocks [ # Current network "0.0.0.0/8", # rfc1918, Private network "10.0.0.0/8", # NAT "100.64.0.0/10", # Loopback addresses "127.0.0.0/8", # Link-local addresses "169.254.0.0/16", # rfc1918, Local communications for private network "172.16.0.0/12", "192.0.0.0/29", "192.0.0.170/31", # Documentation and examples "192.0.2.0/24", # rfc1918, Local communications for private network "192.168.0.0/16", # benchmark testing "198.18.0.0/15", # Documentation and examples "198.51.100.0/24", # Documentation and examples "203.0.113.0/24", # Future use "240.0.0.0/4", # limited broadcast "255.255.255.255/32", # other things "240.0.0.0/4" ] @ip_v6_reserved_blocks [ # loopback address to local host "::1/128", # unspecified "::/128", "::ffff:0:0/96", # Discard prefix "100::/64", "2001::/23", "2001:2::/48", # Documentation "2001:db8::/32", "2001:10::/28", # Unique local address "fc00::/7", # Link-local address "fe80::/10", # Other things "::/8", "100::/8", "200::/7", "400::/6", "800::/5", "1000::/4", "4000::/3", "6000::/3", "8000::/3", "A000::/3", "C000::/3", "E000::/4", "F000::/5", "F800::/6", "FE00::/9" ] @private_blocks @ip_v4_reserved_blocks ++ @ip_v6_reserved_blocks @type ipv4_address :: {0..255, 0..255, 0..255, 0..255} @type ipv6_address() :: {0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535} @type ip_address :: ipv4_address | ipv6_address @doc """ Returns whether an IP address is reserved or not. ## Examples iex> IpReserved.is_reserved?("192.168.0.1") true iex> IpReserved.is_reserved?({8, 8, 1, 1}) false iex> IpReserved.is_reserved?("2001:db8:0:85a3::ac1f:8001") true """ @spec is_reserved?(String.t()) :: boolean def is_reserved?(ip) when is_bitstring(ip) do ip |> InetCidr.parse_address!() |> is_reserved?() end @spec is_reserved?(ip_address()) :: boolean def is_reserved?(ip) when is_tuple(ip) do test_ip_against_block(ip, @private_blocks) end defp test_ip_against_block(ip, block) when is_tuple(ip) and is_list(block) do Enum.reduce_while(block, false, fn block, acc -> cidr = InetCidr.parse(block) if InetCidr.contains?(cidr, ip) do {:halt, true} else {:cont, acc} end end) end end