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