Current section

Files

Jump to
cidr lib cidr.ex
Raw

lib/cidr.ex

defmodule CIDR do
use Bitwise
@moduledoc """
Classless Inter-Domain Routing (CIDR)
"""
defstruct ip: { 0, 0, 0, 0 }, mask: 32
def set_mask(cidr, mask) when mask in 0..32 do
%CIDR{ cidr | mask: mask }
end
def is_cidr(cidr) when is_map(cidr), do: cidr.__struct__ == CIDR
def is_cidr(string) when is_bitstring(string), do: string |> parse |> is_cidr
def is_cidr(_), do: false
@doc """
Checks if an IP address is in the provided CIDR.
"""
def match(%CIDR{ ip: { a0, b0, c0, d0 }, mask: mask } = _cidr, { a1, b1, c1, d1 } = _ip) do
cidr_value = (a0 <<< 24) ||| (b0 <<< 16) ||| (c0 <<< 8) ||| d0
ip_value = (a1 <<< 24) ||| (b1 <<< 16) ||| (c1 <<< 8) ||| d1
(cidr_value >>> (32 - mask)) == (ip_value >>> (32 - mask))
end
def match(_address, _mask), do: false
def parse(string) when string |> is_bitstring do
[address | mask] = string |> String.split("/")
parse(address |> String.to_char_list |> :inet.parse_address, mask)
end
def parse(_string), do: false
def parse({:error, _}, _), do: :error
def parse({:ok, address}, []), do: %CIDR{ ip: address, mask: address |> mask_by_ip }
def parse({:ok, address}, [mask]), do: parse({:ok, address}, mask |> int)
def parse({:ok, _address}, mask) when (mask < 0) or (mask > 32), do: :error
def parse({:ok, address}, mask), do: %CIDR{ ip: address, mask: mask }
def parse(_, _), do: :error
@doc """
Returns the number of hosts covered.
"""
def hosts(cidr) do
1 <<< (32 - cidr.mask)
end
@doc """
Returns the lowest IP address covered.
"""
def min(%CIDR{ ip: { a, b, c, d }, mask: mask }) do
s = (32 - mask)
x = (((a <<< 24) ||| (b <<< 16) ||| (c <<< 8) ||| d) >>> s) <<< s
a1 = ((x >>> 24) &&& 0xFF)
b1 = ((x >>> 16) &&& 0xFF)
c1 = ((x >>> 8) &&& 0xFF)
d1 = ((x >>> 0) &&& 0xFF)
{ a1, b1, c1, d1 }
end
@doc """
Returns the highest IP address covered.
"""
def max(%CIDR{ ip: { a, b, c, d }, mask: mask }) do
s = (32 - mask)
x = (((a <<< 24) ||| (b <<< 16) ||| (c <<< 8) ||| d) >>> s) <<< s
y = x ||| ((1 <<< s) - 1)
a1 = ((y >>> 24) &&& 0xFF)
b1 = ((y >>> 16) &&& 0xFF)
c1 = ((y >>> 8) &&& 0xFF)
d1 = ((y >>> 0) &&& 0xFF)
{ a1, b1, c1, d1 }
end
def is_ipv6({a, b, c, d, e, f, g, h}) when
a in 0..65535 and
b in 0..65535 and
c in 0..65535 and
d in 0..65535 and
e in 0..65535 and
f in 0..65535 and
g in 0..65535 and
h in 0..65535, do: true
def is_ipv6(_), do: false
def is_ipv4({a, b, c, d}) when
a in 0..255 and
b in 0..255 and
c in 0..255 and
d in 0..255, do: true
def is_ipv4(_), do: false
def mask_by_ip(address) do
cond do
address |> is_ipv4 -> 32
address |> is_ipv6 -> 64
true -> -1
end
end
defp int(x) do
case x |> Integer.parse do
:error -> -1
{a,_} -> a
end
end
end