Add IP address scopes.
This commit is contained in:
parent
e60eced8b0
commit
26abbcd64f
7 changed files with 188 additions and 18 deletions
75
lib/enumerable/ip/prefix.ex
Normal file
75
lib/enumerable/ip/prefix.ex
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
defimpl Enumerable, for: IP.Prefix do
|
||||||
|
alias IP.{Prefix, Address}
|
||||||
|
use Bitwise
|
||||||
|
|
||||||
|
@moduledoc """
|
||||||
|
Implements `Enumerable` for `IP.Prefix`, allowing consumers to iterate
|
||||||
|
through all addresses in a prefix.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the number of addresses within the `prefix`.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> "192.0.2.128/25"
|
||||||
|
...> |> IP.Prefix.from_string!()
|
||||||
|
...> |> Enum.count()
|
||||||
|
128
|
||||||
|
|
||||||
|
iex> "2001:db8::/121"
|
||||||
|
...> |> IP.Prefix.from_string!()
|
||||||
|
...> |> Enum.count()
|
||||||
|
128
|
||||||
|
"""
|
||||||
|
@spec count(Prefix.t) :: {:ok, non_neg_integer} | {:error, module}
|
||||||
|
def count(prefix), do: {:ok, Prefix.space(prefix)}
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns whether an `address` is contained by the `prefix`.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> IP.Prefix.from_string!("192.0.2.128/25")
|
||||||
|
...> |> Enum.member?(IP.Address.from_string!("192.0.2.250"))
|
||||||
|
true
|
||||||
|
"""
|
||||||
|
@spec member?(Prefix.t, Address.t) :: {:ok, boolean} | {:error, module}
|
||||||
|
def member?(prefix, %Address{} = address), do: {:ok, Prefix.contains?(prefix, address)}
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Allows the reduction of `prefix` into a colection of addresses.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> IP.Prefix.from_string!("192.0.2.128/29")
|
||||||
|
...> |> Stream.filter(fn a -> rem(IP.Address.to_integer(a), 2) == 0 end)
|
||||||
|
...> |> Enum.map(fn a -> IP.Address.to_string(a) end)
|
||||||
|
["192.0.2.130", "192.0.2.132", "192.0.2.134"]
|
||||||
|
"""
|
||||||
|
@spec reduce(Prefix.t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
|
||||||
|
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
|
||||||
|
def reduce({prefix, pos, last}, {:suspend, acc}, fun), do: {:suspended, acc, &reduce({prefix, pos, last}, &1, fun)}
|
||||||
|
|
||||||
|
def reduce(%Prefix{} = prefix, {:cont, acc}, fun) do
|
||||||
|
first = prefix
|
||||||
|
|> Prefix.first()
|
||||||
|
|> Address.to_integer()
|
||||||
|
|
||||||
|
last = prefix
|
||||||
|
|> Prefix.last()
|
||||||
|
|> Address.to_integer()
|
||||||
|
|
||||||
|
reduce({prefix, first, last}, {:cont, acc}, fun)
|
||||||
|
end
|
||||||
|
|
||||||
|
def reduce({%Prefix{address: %Address{version: version}} = prefix, pos, last}, {:cont, acc}, fun) do
|
||||||
|
case pos do
|
||||||
|
^last -> {:done, acc}
|
||||||
|
pos ->
|
||||||
|
pos = pos + 1
|
||||||
|
next = Address.from_integer!(pos, version)
|
||||||
|
reduce({prefix, pos, last}, fun.(next, acc), fun)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -28,12 +28,12 @@ defmodule IP.Prefix do
|
||||||
#IP.Prefix<2001:db8::/64>
|
#IP.Prefix<2001:db8::/64>
|
||||||
"""
|
"""
|
||||||
@spec new(Address.t, ipv4_prefix_length | ipv6_prefix_length) :: t
|
@spec new(Address.t, ipv4_prefix_length | ipv6_prefix_length) :: t
|
||||||
def new(%Address{address: address, version: 4}, length) when length > 0 and length <= 32 do
|
def new(%Address{address: address, version: 4}, length) when length >= 0 and length <= 32 do
|
||||||
mask = calculate_mask_from_length(length, 32)
|
mask = calculate_mask_from_length(length, 32)
|
||||||
%Prefix{address: Address.from_integer!(address, 4), mask: mask}
|
%Prefix{address: Address.from_integer!(address, 4), mask: mask}
|
||||||
end
|
end
|
||||||
|
|
||||||
def new(%Address{address: address, version: 6}, length) when length > 0 and length <= 128 do
|
def new(%Address{address: address, version: 6}, length) when length >= 0 and length <= 128 do
|
||||||
mask = calculate_mask_from_length(length, 128)
|
mask = calculate_mask_from_length(length, 128)
|
||||||
%Prefix{address: Address.from_integer!(address, 6), mask: mask}
|
%Prefix{address: Address.from_integer!(address, 6), mask: mask}
|
||||||
end
|
end
|
||||||
|
@ -188,13 +188,12 @@ defmodule IP.Prefix do
|
||||||
|
|
||||||
iex> IP.Prefix.from_string!("192.0.2.0/24")
|
iex> IP.Prefix.from_string!("192.0.2.0/24")
|
||||||
...> |> IP.Prefix.subnet_mask()
|
...> |> IP.Prefix.subnet_mask()
|
||||||
"255.255.255.0"
|
#IP.Address<255.255.255.0>
|
||||||
"""
|
"""
|
||||||
@spec subnet_mask(t) :: binary
|
@spec subnet_mask(t) :: binary
|
||||||
def subnet_mask(%Prefix{mask: mask, address: %Address{version: 4}}) do
|
def subnet_mask(%Prefix{mask: mask, address: %Address{version: 4}}) do
|
||||||
mask
|
mask
|
||||||
|> Address.from_integer!(4)
|
|> Address.from_integer!(4)
|
||||||
|> Address.to_string()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -204,7 +203,7 @@ defmodule IP.Prefix do
|
||||||
|
|
||||||
iex> IP.Prefix.from_string!("192.0.2.0/24")
|
iex> IP.Prefix.from_string!("192.0.2.0/24")
|
||||||
...> |> IP.Prefix.wildcard_mask()
|
...> |> IP.Prefix.wildcard_mask()
|
||||||
"0.0.0.255"
|
#IP.Address<0.0.0.255>
|
||||||
"""
|
"""
|
||||||
@spec wildcard_mask(t) :: binary
|
@spec wildcard_mask(t) :: binary
|
||||||
def wildcard_mask(%Prefix{mask: mask, address: %Address{version: 4}}) do
|
def wildcard_mask(%Prefix{mask: mask, address: %Address{version: 4}}) do
|
||||||
|
@ -212,7 +211,6 @@ defmodule IP.Prefix do
|
||||||
|> bnot()
|
|> bnot()
|
||||||
|> band(@ipv4_mask)
|
|> band(@ipv4_mask)
|
||||||
|> Address.from_integer!(4)
|
|> Address.from_integer!(4)
|
||||||
|> Address.to_string()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -358,13 +356,15 @@ defmodule IP.Prefix do
|
||||||
18446744073709551616
|
18446744073709551616
|
||||||
"""
|
"""
|
||||||
@spec space(t) :: non_neg_integer
|
@spec space(t) :: non_neg_integer
|
||||||
def space(%Prefix{} = prefix) do
|
def space(%Prefix{address: %Address{address: address, version: 4}, mask: mask}) do
|
||||||
first = prefix
|
first = address &&& mask
|
||||||
|> Prefix.first()
|
last = first + (~~~mask &&& @ipv4_mask)
|
||||||
|> Address.to_integer()
|
last - first + 1
|
||||||
last = prefix
|
end
|
||||||
|> Prefix.last()
|
|
||||||
|> Address.to_integer()
|
def space(%Prefix{address: %Address{address: address, version: 6}, mask: mask}) do
|
||||||
|
first = address &&& mask
|
||||||
|
last = first + (~~~mask &&& @ipv6_mask)
|
||||||
last - first + 1
|
last - first + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,9 @@ defmodule IP.Prefix.Helpers do
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def calculate_mask_from_length(length, mask_length) do
|
def calculate_mask_from_length(length, mask_length) do
|
||||||
pad = mask_length - length
|
pad = mask_length - length - 1
|
||||||
0..(length - 1)
|
mask = n_times_reduce(length, 0, fn (i, mask) -> mask + (1 <<< i) end)
|
||||||
|> Enum.reduce(0, fn (i, mask) -> mask + (1 <<< i + pad) end)
|
mask <<< pad
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
@ -20,4 +20,10 @@ defmodule IP.Prefix.Helpers do
|
||||||
end)
|
end)
|
||||||
|> Enum.count()
|
|> Enum.count()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp n_times_reduce(0, acc, _fun), do: acc
|
||||||
|
defp n_times_reduce(n, acc, fun) do
|
||||||
|
acc = fun.(n, acc)
|
||||||
|
n_times_reduce(n - 1, acc, fun)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -85,8 +85,8 @@ defmodule IP.Prefix.Parser do
|
||||||
case Address.from_string(mask, 4) do
|
case Address.from_string(mask, 4) do
|
||||||
{:ok, address} ->
|
{:ok, address} ->
|
||||||
mask = address
|
mask = address
|
||||||
|> Address.to_integer()
|
|> Address.to_integer()
|
||||||
|> calculate_length_from_mask()
|
|> calculate_length_from_mask()
|
||||||
{:ok, mask}
|
{:ok, mask}
|
||||||
_ ->
|
_ ->
|
||||||
{:ok, String.to_integer(mask)}
|
{:ok, String.to_integer(mask)}
|
||||||
|
|
81
lib/ip/scope.ex
Normal file
81
lib/ip/scope.ex
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
defmodule IP.Scope do
|
||||||
|
alias IP.{Prefix, Address}
|
||||||
|
use Bitwise
|
||||||
|
require IP.Prefix
|
||||||
|
|
||||||
|
@moduledoc """
|
||||||
|
Implements scope lookup for all (currently) known scopes.
|
||||||
|
|
||||||
|
Please open a pull-request if this needs changing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@v4_scopes [
|
||||||
|
{"0.0.0.0/8", "CURRENT NETWORK"},
|
||||||
|
{"10.0.0.0/8", "RFC1918 PRIVATE"},
|
||||||
|
{"127.0.0.0/8", "LOOPBACK"},
|
||||||
|
{"168.254.0.0/16", "AUTOCONF PRIVATE"},
|
||||||
|
{"172.16.0.0/12", "RFC1918 PRIVATE"},
|
||||||
|
{"192.0.0.0/24", "RESERVED (IANA)"},
|
||||||
|
{"192.0.2.0/24", "DOCUMENTATION"},
|
||||||
|
{"192.88.99.0/24", "6to4 ANYCAST"},
|
||||||
|
{"192.168.0.0/16", "RFC1918 PRIVATE"},
|
||||||
|
{"198.18.0.0/15", "NETWORK BENCHMARK TESTS"},
|
||||||
|
{"198.51.100.0/24", "DOCUMENTATION"},
|
||||||
|
{"203.0.113.0/24", "DOCUMENTATION"},
|
||||||
|
{"239.0.0.0/8", "LOCAL MULTICAST"},
|
||||||
|
{"224.0.0.0/4", "GLOBAL MULTICAST"},
|
||||||
|
{"240.0.0.0/4", "RESERVED"},
|
||||||
|
{"255.255.255.255/32", "GLOBAL BROADCAST"},
|
||||||
|
{"0.0.0.0/0", "GLOBAL UNICAST"}
|
||||||
|
]
|
||||||
|
|
||||||
|
@v6_scopes [
|
||||||
|
{"2001:10::/28", "ORCHID"},
|
||||||
|
{"2001:db8::/32", "DOCUMENTATION"},
|
||||||
|
{"2000::/3", "GLOBAL UNICAST"},
|
||||||
|
{"::/128", "UNSPECIFIED ADDRESS"},
|
||||||
|
{"::1/128", "LINK LOCAL LOOPBACK"},
|
||||||
|
{"::ffff:0:0/96", "IPv4 MAPPED"},
|
||||||
|
{"::/96", "IPv4 TRANSITION (deprecated)"},
|
||||||
|
{"fc00::/7", "UNIQUE LOCAL UNICAST"},
|
||||||
|
{"fec0::/10", "SITE LOCAL (deprecated)"},
|
||||||
|
{"fe80::/10", "LINK LOCAL UNICAST"},
|
||||||
|
{"ff00::/8", "MULTICAST"},
|
||||||
|
{"::/0", "RESERVED"}
|
||||||
|
]
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Return the scope of `address``
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> IP.Address.from_string!("192.0.2.0")
|
||||||
|
...> |> IP.Scope.address_scope()
|
||||||
|
"DOCUMENTATION"
|
||||||
|
|
||||||
|
iex> IP.Address.from_string!("2001:db8::")
|
||||||
|
...> |> IP.Scope.address_scope()
|
||||||
|
"DOCUMENTATION"
|
||||||
|
"""
|
||||||
|
@spec address_scope(Address.t) :: binary
|
||||||
|
|
||||||
|
Enum.each(@v4_scopes, fn {prefix, description} ->
|
||||||
|
%Prefix{address: %Address{address: addr0}, mask: mask} = Prefix.from_string!(prefix)
|
||||||
|
def address_scope(%Address{address: addr1})
|
||||||
|
when (unquote(addr0) &&& unquote(mask)) <= addr1
|
||||||
|
and (unquote(addr0) &&& unquote(mask)) + (~~~unquote(mask) &&& 0xffffffff) >= addr1
|
||||||
|
do
|
||||||
|
unquote(description)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
Enum.each(@v6_scopes, fn {prefix, description} ->
|
||||||
|
%Prefix{address: %Address{address: addr0}, mask: mask} = Prefix.from_string!(prefix)
|
||||||
|
def address_scope(%Address{address: addr1})
|
||||||
|
when (unquote(addr0) &&& unquote(mask)) <= addr1
|
||||||
|
and (unquote(addr0) &&& unquote(mask)) + (~~~unquote(mask) &&& 0xffffffffffffffffffffffffffffffff) >= addr1
|
||||||
|
do
|
||||||
|
unquote(description)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
4
test/enumerable/ip/prefix_test.exs
Normal file
4
test/enumerable/ip/prefix_test.exs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
defmodule EnumerableIPPrefixTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
doctest Enumerable.IP.Prefix
|
||||||
|
end
|
4
test/ip/scope_test.exs
Normal file
4
test/ip/scope_test.exs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
defmodule IPScopeTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
doctest IP.Scope
|
||||||
|
end
|
Loading…
Reference in a new issue