Pfx (Pfx v0.14.2) View Source
Functions to make working with prefixes easier, especially IP prefixes (IPv4 and IPv6).
Pfx
defines a prefix as a struct with a number of bits
and a maximum
maxlen
length. Hence a Pfx
struct represents some domain-specific value,
like an IPv4/6 address or network, a MAC address, a MAC OUI range or something
completely different.
A Pfx
struct can be created with:
Pfx.new/1
, which creates an IPv4, IPv6, EUI48 or an EUI64 prefix~p()
, which also creates an IPv4, IPv6, EUI48 or an EUI64 prefixPfx.new/2
, which can create any type of prefixPfx.from_mac/1
, which creates only EUI48 or EUI64 prefixesPfx.from_hex/2
, which creates any kind of prefix from a hexadecimal string
For example:
# IPv4
iex> new("1.1.1.1/24")
%Pfx{bits: <<1, 1, 1>>, maxlen: 32}
# IPv6
iex> ~p"acdc::/16"
%Pfx{bits: <<172, 220>>, maxlen: 128}
# EUI48
iex> new("aa-bb-cc-dd-ee-ff/40")
%Pfx{bits: <<0xaa, 0xbb, 0xcc, 0xdd, 0xee>>, maxlen: 48}
# EUI64
iex> ~p"11-22-33-44-55-66-77-88/24"
%Pfx{bits: <<0x11, 0x22, 0x33>>, maxlen: 64}
# EUI64, not IPv6
iex> from_mac("11:22:33:44:55:66:77:88/24")
%Pfx{bits: <<0x11, 0x22, 0x33>>, maxlen: 64}
# Other
iex> new(<<1, 1>>, 16)
%Pfx{bits: <<1, 1>>, maxlen: 16}
A prefix can be expressed as:
- a
Pfx.t/0
, e.g.%Pfx{bits: <<1, 1, 1>>, maxlen: 24}
- a
Pfx.ip_address/0
, e.g.{1, 1, 1, 1}
- a
Pfx.ip_prefix/0
, e.g.{{1, 2, 3, 4, 5, 6, 7, 0}, 120}
- a
binary/0
(either an IPv4 CIDR, IPv6, EUI48 or EUI64 string), e.g. "1.1.1.0/24"
Functions generally accept these representations and yield their result in the
same fashion. Pfx has several IP functions, like
Pfx.unique_local?/1
, which are IP oriented. They are included along with the
more generic functions, like Pfx.cut/3
, in order to
have one module to rule them all.
# check a local snapshot of IANA's IPv4 special-purpose address registry
iex> iana_special("192.168.0.128", :global)
false
iex> iana_special("192.168.0.128", :spec)
["rfc1918"]
iex> minimize(["10.10.10.0", "10.10.10.1", "10.10.10.2", "10.10.10.3"])
["10.10.10.0/30"]
iex> hosts("10.10.10.0/30")
["10.10.10.0", "10.10.10.1", "10.10.10.2", "10.10.10.3"]
iex> hosts({{10, 10, 10, 0}, 30})
[
{{10, 10, 10, 0}, 32},
{{10, 10, 10, 1}, 32},
{{10, 10, 10, 2}, 32},
{{10, 10, 10, 3}, 32}
]
iex> hosts(%Pfx{bits: <<10, 10, 10, 0::6>>, maxlen: 32})
[
%Pfx{bits: <<10, 10, 10, 0>>, maxlen: 32},
%Pfx{bits: <<10, 10, 10, 1>>, maxlen: 32},
%Pfx{bits: <<10, 10, 10, 2>>, maxlen: 32},
%Pfx{bits: <<10, 10, 10, 3>>, maxlen: 32},
]
# adopt representation of first argument
iex> band({10, 10, 10, 1}, "255.255.255.0")
{10, 10, 10, 0}
iex> multicast?("ff00::1")
true
# MAC OUI prefix
iex> keep("aa:bb:cc:dd:ee:ff", 24)
"AA-BB-CC-00-00-00/24"
# get IPv4 from a IPv4 compatible IPv6 address
iex> cut("::1.2.3.4", -1, -32)
"1.2.3.4"
# partition a range of address space
iex> partition_range("10.10.10.0", "10.10.10.33")
["10.10.10.0/27", "10.10.10.32/31"]
Validity
The Pfx.new/2
function will silently clip the provided bits
-string to
maxlen
-bits when needed, since a Pfx
struct named pfx
is valid, iff:
bit_size(pfx.bits)
<=pfx.maxlen
, and wherepfx.maxlen
is anon_neg_integer/0
Keep that in mind when instantiating directly or updating a Pfx
, otherwise
functions will choke on it.
Same goes for Pfx.ip_address/0
representations, which must be a valid
:inet.ip_address()
, representing either an IPv4 or IPv6 address through a
tuple of four 8
-bit wide numbers or eight 16
-bit wide numbers.
If used as the first element in a Pfx.ip_prefix/0
tuple, the second element
is interpreted as the mask, used to clip the bitstring when creating the Pfx
struct. For example: {{1, 1, 1, 0}, 24}
is the same as 1.1.1.0/24
. IPv4
masks must be in range 0..32
and IPv6 masks in range 0..128
. The resulting
Pfx
will have its maxlen
set to 32
for IPv4 tuples and 128
for IPv6
tuples.
Last but not least, binaries are interpreted as either an IPv4 in CIDR-notation, an IPv6 address/prefix, an EUI-48 or an EUI-64 formatted string.
# IPv4
iex> new("1.2.3.4")
%Pfx{bits: <<1, 2, 3, 4>>, maxlen: 32}
iex> new({1, 2, 3, 4})
%Pfx{bits: <<1, 2, 3, 4>>, maxlen: 32}
iex> new("1.2.3.0/24")
%Pfx{bits: <<1, 2, 3>>, maxlen: 32}
iex> new({{1, 2, 3, 0}, 24})
%Pfx{bits: <<1, 2, 3>>, maxlen: 32}
# IPv6
iex> new("acdc:1975::")
%Pfx{bits: <<0xACDC::16, 0x1975::16, 0::96>>, maxlen: 128}
iex> new({44252, 6517, 0, 0, 0, 0, 0, 0})
%Pfx{bits: <<0xACDC::16, 0x1975::16, 0::96>>, maxlen: 128}
# EUI-48
iex> new("00-88-88-88-88-88")
%Pfx{bits: <<0, 0x88, 0x88, 0x88, 0x88, 0x88>>, maxlen: 48}
iex> new("0088.8888.8888")
%Pfx{bits: <<0, 0x88, 0x88, 0x88, 0x88, 0x88>>, maxlen: 48}
# EUI-64
iex> new("02-88-88-FF-FE-88-88-88")
%Pfx{bits: <<0x02, 0x88, 0x88, 0xFF, 0xFE, 0x88, 0x88, 0x88>>, maxlen: 64}
iex> new("0288.88FF.FE88.8888")
%Pfx{bits: <<0x02, 0x88, 0x88, 0xFF, 0xFE, 0x88, 0x88, 0x88>>, maxlen: 64}
Ancient tradition
Pfx.new/1
accepts CIDR-strings which are ultimately processed using erlang's
:inet.parse_address
which, at the time of writing, still honors the ancient
linux tradition of injecting zero's (rather than appending them) when presented
with less than four IPv4 digits in a CIDR string.
# "d" -> "0.0.0.d"
iex> new("10") |> format()
"0.0.0.10"
iex> new("10/8") |> format()
"0.0.0.0/8"
# "d1.d2" -> "d1.0.0.d2"
iex> new("10.10") |> format()
"10.0.0.10"
iex> new("10.10/16") |> format()
"10.0.0.0/16"
# "d1.d2.d3" -> "d1.d2.0.d3"
iex> new("10.10.10") |> format()
"10.10.0.10"
iex> new("10.10.10/24") |> format()
"10.10.0.0/24"
Bottom line: never go short, you may be unpleasantly surprised.
EUI-64's
Since a string is first parsed as an IP prefix, EUI-64's like
"11:22:33:44:55:66:77:88" will come out as an IPv6 prefix with their maxlen
property set to 128
. So, when parsing EUI's that might use ':'-s as
punctuation, use Pfx.from_mac/1
, which also supports the tuple formats. Like
Pfx.new/1
, this function always returns a Pfx.t/0
-struct.
# new/1 parses EUI-64's like these correctly:
iex> new("1122.3344.5566.7788")
%Pfx{bits: <<0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88>>, maxlen: 64}
iex> new("11-22-33-44-55-66-77-88")
%Pfx{bits: <<0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88>>, maxlen: 64}
# but new/1 turns this valid EUI-64 into IPv6 due to ':'-punctuation used:
iex> new("01:02:03:04:05:06:07:08")
%Pfx{bits: <<0x1::16, 0x2::16, 0x3::16, 0x4::16, 0x5::16, 0x6::16, 0x7::16, 0x8::16>>, maxlen: 128}
# in this case, use from_mac/1
iex> from_mac("01:02:03:04:05:06:07:08")
%Pfx{bits: <<0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8>>, maxlen: 64}
# and supports digit-styled EUI's
iex> from_mac({0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8})
%Pfx{bits: <<0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8>>, maxlen: 64}
# from {{digits}, len}, keeping first 3 bytes
iex> from_mac({{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, 24})
%Pfx{bits: <<0x1, 0x2, 0x3>>, maxlen: 64}
Iana Special-Purpose Address Registry
The Pfx module carries a snapshot of the
IPv4
and
IPv6
special-purpose address registries. This allows checking a prefix' properties
as set in those registries (if at all) using Pfx.iana_special/2
.
# get all properties
iex> iana_special("10.10.10.10")
%{
allocation: "1996-02",
destination: true,
forward: true,
global: false,
name: "private-use",
prefix: "10.0.0.0/8",
reserved: false,
source: true,
spec: ["rfc1918"],
termination: :na
}
# or just one
iex> iana_special("10.10.10.10", :global)
false
# get all not globally routed IPv4 prefixes
iex> iana_special(:ip4)
...> |> Enum.filter(fn {_, props} -> props.global != true end)
...> |> Enum.map(fn {_, props} -> props.prefix end)
["0.0.0.0/32", "192.0.0.8/32", "192.0.0.170/32", "192.0.0.171/32",
"255.255.255.255/32", "192.0.0.0/29", "192.0.0.0/24", "192.0.2.0/24",
"192.88.99.0/24", "198.51.100.0/24", "203.0.113.0/24", "169.254.0.0/16",
"192.168.0.0/16", "198.18.0.0/15", "172.16.0.0/12", "100.64.0.0/10",
"0.0.0.0/8", "10.0.0.0/8", "127.0.0.0/8", "240.0.0.0/4"]
Unfortunately, the way the registries are set up, boolean values actually can
have 3 values: true
, false
and :na
. Hence the props.global != true
.
Enumeration
A Pfx.t/0
implements the Enumerable
protocol:
iex> for ip <- %Pfx{bits: <<1, 2, 3, 0::6>>, maxlen: 32}, do: ip
[
%Pfx{bits: <<1, 2, 3, 0>>, maxlen: 32},
%Pfx{bits: <<1, 2, 3, 1>>, maxlen: 32},
%Pfx{bits: <<1, 2, 3, 2>>, maxlen: 32},
%Pfx{bits: <<1, 2, 3, 3>>, maxlen: 32},
]
String.Chars
Pfx.t/0
implements the String.Chars
protocol with some defaults for
prefixes that formats prefixes with:
maxlen: 32
as an IPv4 CIDR string,maxlen: 48
as a EUI-48 address string,maxlen: 64
as a EUI-64 address string, andmaxlen: 128
as an IPv6 string
Other maxlen
's will simply come out as a series of 8-bit numbers joined by "."
followed by /num_of_bits
. The latter is omitted if equal to pfx.bits
length.
If other formatting is required, use the Pfx.format/2
function, which takes
some options that help shape the string representation for a Pfx
struct.
iex> "#{%Pfx{bits: <<10, 11, 12>>, maxlen: 32}}"
"10.11.12.0/24"
iex> "#{new(<<44252::16, 6518::16>>, 128)}"
"acdc:1976::/32"
iex> "#{%Pfx{bits: <<0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6>>, maxlen: 48}}"
"A1-B2-C3-D4-E5-F6"
iex> "#{new(<<1, 2, 3, 4, 5>>, 64)}"
"01-02-03-04-05-00-00-00/40"
# the enumeration example earlier, could also read:
iex> for ip <- ~p"1.2.3.0/30", do: "#{ip}"
[
"1.2.3.0",
"1.2.3.1",
"1.2.3.2",
"1.2.3.3"
]
# or
iex> for ip <- ~p"1.2.3.0/30", do: to_tuple(ip, mask: false)
[
{1, 2, 3, 0},
{1, 2, 3, 1},
{1, 2, 3, 2},
{1, 2, 3, 3}
]
# format as a string of bits
iex> "10.10.10.10" |> format(width: 1, unit: 8)
"00001010.00001010.00001010.00001010"
Limitations
A lot of Pfx
-functions convert the Pfx.bits
bitstring to an integer using
Pfx.cast/1
, before performing some, often Bitwise
-related, calculation on
them. Luckily Elixir can handle pretty
large numbers which seem mostly limited by the available system memory.
Other functions, like Pfx.digits/2
return a tuple with numbers and are so
limited by the maximum number of elements in a tuple (~16M+).
So if you're taking this somewhere far, far away, heed these limitations before take off.
Also, everything is done in Elixir with no extra, external dependencies. Usually fast enough, but if you really feel the need for speed, you might want to look elsewhere.
Anyway, enough downplay, here are some more examples.
Examples
# An entry from IANA's IPv6 Special-Purpose Address Registry
iex> iana_special("fc00::")
%{
allocation: "2005-10",
destination: true,
forward: true,
global: false,
name: "unique-local",
prefix: "fc00::/7",
reserved: false,
source: true,
spec: ["rfc4193", "rfc8190"],
termination: :na
}
# IANA's OUI range 00-00-5e-xx-xx-xx
iex> new("00-00-5e-00-00-00/24")
%Pfx{bits: <<0, 0, 94>>, maxlen: 48}
# IANA's VRRP MAC address range 00-00-5e-00-01-{VRID}
iex> vrrp_mac_range = new("00-00-5e-00-01-00/40")
%Pfx{bits: <<0, 0, 94, 0, 1>>, maxlen: 48}
iex>
iex> mac = new("00-00-5e-00-01-0f")
%Pfx{bits: <<0, 0, 94, 0, 1, 15>>, maxlen: 48}
iex>
iex> mac in vrrp_mac_range
true
iex> cut(mac, -1, -8) |> cast()
15
iex> new("10.10.10.0/24")
%Pfx{bits: <<10, 10, 10>>, maxlen: 32}
iex> mask("10.10.10.0/25")
"255.255.255.128"
iex> inv_mask("10.10.10.0/25")
"0.0.0.127"
iex> dns_ptr("acdc:1975::b1ba:2021")
"1.2.0.2.a.b.1.b.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.5.7.9.1.c.d.c.a.ip6.arpa"
iex> teredo_decode("2001:0000:4136:e378:8000:63bf:3fff:fdd2")
%{
server: "65.54.227.120",
client: "192.0.2.45",
port: 40000,
flags: {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
prefix: "2001:0000:4136:e378:8000:63bf:3fff:fdd2"
}
iex> eui64_encode("0288.8888.8888")
"00-88-88-FF-FE-88-88-88"
iex> partition("10.10.10.0/24", 26)
[ "10.10.10.0/26",
"10.10.10.64/26",
"10.10.10.128/26",
"10.10.10.192/26"
]
iex> new(<<1, 2, 3, 4>>, 32)
...> |> format(width: 1, unit: 8)
"00000001.00000010.00000011.00000100"
iex> from_hex("123456789abcdef")
...> |> keep(20)
%Pfx{bits: <<0x12, 0x34, 0x5::4>>, maxlen: 60}
iex> brot("1.2.3.4", 8)
"4.1.2.3"
Link to this section Summary
Types
An :inet IPv4 or IPv6 address tuple
An IPv4 prefix ({t:inet.ip4_address/0
, 0..32}) or an IPv6 prefix ({t:inet.ip6_address/0
, 0..128}).
A prefix expressed as either a Pfx.t/0
struct, an IP address-tuple, an
address,length-tuple or a CIDR string.
A prefix struct with fields: bits
and maxlen
.
Guards
Guard that ensures both prefixes are valid Pfx structs and have the same maxlen.
Guard that ensures a given pfx
is actually valid.
IP Functions
Returns the broadcast prefix (full address) for given pfx
.
Returns a reverse DNS name (pointer) for given pfx
.
Decodes a modified EUI-64 back into the original EUI-48 address.
Creates a modified EUI-64 out of eui
(an EUI-48 address or an EUI-64).
Returns one or all the properties as per Iana's special purpose address
registry for given prefix
.
Returns a map with link-local address components for given pfx
.
Returns true if pfx
is a link-local prefix, false otherwise
Returns true is pfx
is a multicast prefix, false otherwise
Returns a map with multicast address components for given pfx
.
Returns the embedded IPv4 address of a nat64 pfx
Returns an IPv4 embedded IPv6 address for given pfx6
and pfx4
.
Returns the this-network prefix (full address) for given pfx
.
Returns true if given pfx
is a teredo address, false otherwise
Returns a map with the teredo address components of pfx
or nil.
Encodes given server
, client
, port
and flags
as an IPv6 teredo address.
Returns true if pfx
is designated as "private-use".
Functions
Returns the address portion of given prefix
without applying the mask (if
any).
Returns a bitwise AND of pfx1
and pfx2
.
Returns the bit-value at given position
in pfx
.
Returns the concatenation of 1 or more series of bits of the given pfx
.
Returns length
bits, starting at position
for given pfx
.
Returns a bitwise NOT of the bits in pfx
.
Returns a bitwise OR of pfx1
and pfx2
.
Rotates the bits of pfx
by n
positions.
Sets all bits of pfx
to either 0
or 1
.
Performs an arithmetic shift left of the bits in pfx
by n
positions.
Performs an arithmetic shift right the bits in pfx
by n
positions.
Returns a bitwise XOR of pfx1
and pfx2
.
Casts a pfx
to an integer.
Compares prefix1
to prefix2
for sorting purposes.
Contrasts pfx1
to pfx2
.
Cuts out a series of bits and turns it into its own Pfx
.
Returns a {{digit, ..}, length}
representation of given pfx
.
Drops count
lsb bits from given pfx
.
Returns a list of {number, width}
-fields for given pfx
.
Returns the first full length prefix from the set represented by pfx
.
Flips a single bit at position
in given pfx
.
Formats pfx
as a string, using several options
Creates a Pfx
struct for given hexadecimal string
.
Creates a Pfx
struct from a EUI48/64 strings or tuples.
Returns the nth
full length prefix in given pfx
.
Returns a list of address prefixes for given pfx
.
Inserts bits
into pfx
-s bitstring, starting at position
.
Returns the inverted mask for given pfx
.
Inverts a prefix, bit by bit, same as Pfx.bnot/1
.
Keeps count
msb bits of given pfx
.
Returns the last full length prefix from the set represented by pfx
.
Returns a representation of pfx
(a Pfx.t/0
-struct) in the form of original
.
Returns the mask for given pfx
.
Applies mask
to given prefix
.
Returns the nth
-member of a given pfx
.
Returns the nth
member in the set represented by pfx
, using width
bits.
Returns true is prefix pfx1
is a member of prefix pfx2
Returns a minimized list of prefixes covering the same address space.
Returns the neighboring prefix for pfx
, such that both can be combined in a
supernet.
Creates a new prefix from address tuples/binaries or raises ArgumentError
.
Creates a new Pfx.t/0
-prefix.
Pads the bits in pfx
on the left to its full length using 0
-bits.
Left pad the pfx.bits
to its full length using either 0
or 1
-bits.
Pads the bits in pfx
on the Left with n
bits of either 0
or 1
's.
Pads the bits in pfx
on the right to its full length using 0
-bits.
Pads the bits in pfx
on the right to its full length using either 0
or
1
-bits.
Pads the bits in pfx
on the right with n
bits of either 0
or 1
's.
Parses a prefix/0
and returns {:ok, Pfx.t}
or {:error, :einvalid}
Parses a prefix/0
and returns {:ok, Pfx.t}
or given default
on error.
Partitions pfx
into a list of new prefixes, each bitlen
long.
Returns a list of prefixes that cover the given range of address space.
Returns the length of the bitstring for given prefix
.
Removes length
bits from pfx
-s bitstring, starting at position
.
Returns another Pfx
at distance offset
from given pfx
.
Returns either a Pfx struct, a binary or a tuple for the given binary prefix
.
Returns either a Pfx struct, a binary or a tuple for given binary prefixes
.
Returns the number of full addresses as represented by pfx
.
Returns a prefix
in the form of a tuple, with or without mask.
Trims all trailing 0
's from given prefix
.
Returns the prefix type, one of :ip4
, :ip6
, :eui48
, eui64
or simply
its maxlen property.
Returns the prefix represented by the digits
, actual length
and a given
field width
.
Returns a boolean indicating whether pfx
is a valid prefix/0
or not.
Link to this section Types
Specs
ip_address() :: :inet.ip4_address() | :inet.ip6_address()
An :inet IPv4 or IPv6 address tuple
Specs
ip_prefix() :: {:inet.ip4_address(), 0..32} | {:inet.ip6_address(), 0..128}
An IPv4 prefix ({t:inet.ip4_address/0
, 0..32}) or an IPv6 prefix ({t:inet.ip6_address/0
, 0..128}).
Specs
prefix() :: t() | ip_address() | ip_prefix() | String.t()
A prefix expressed as either a Pfx.t/0
struct, an IP address-tuple, an
address,length-tuple or a CIDR string.
Specs
t() :: %Pfx{bits: bitstring(), maxlen: non_neg_integer()}
A prefix struct with fields: bits
and maxlen
.
Link to this section Guards
Guard that ensures both prefixes are valid Pfx structs and have the same maxlen.
Guard that ensures a given pfx
is actually valid.
- it is a
Pfx.t/0
struct, pfx.maxlen
is anon_neg_integer/0
,bit_size(pfx.bits) <= pfx.maxlen
Link to this section IP Functions
Specs
Returns the broadcast prefix (full address) for given pfx
.
Yields the same result as Pfx.last/1
, included for nostalgia.
Examples
iex> broadcast("10.10.0.0/16")
"10.10.255.255"
# a full address is its own broadcast address
iex> broadcast({10, 10, 10, 1})
{10, 10, 10, 1}
iex> broadcast({{10, 10, 10, 1}, 30})
{{10, 10, 10, 3}, 32}
iex> broadcast(%Pfx{bits: <<10, 10, 10>>, maxlen: 32})
%Pfx{bits: <<10, 10, 10, 255>>, maxlen: 32}
iex> broadcast(%Pfx{bits: <<0xacdc::16, 0x1976::16>>, maxlen: 128})
%Pfx{bits: <<0xACDC::16, 0x1976::16, -1::96>>, maxlen: 128}
iex> broadcast("acdc:1976::/112")
"acdc:1976::ffff"
Specs
Returns a reverse DNS name (pointer) for given pfx
.
The prefix will be padded right with 0
-bits to a multiple of 8 for IPv4 prefixes and
to a multiple of 4 for IPv6 prefixes. Note that this might give unexpected results.
So dns_ptr/1
works best if the prefix given is actually a multiple of 4 or 8.
Examples
iex> dns_ptr("10.10.0.0/16")
"10.10.in-addr.arpa"
# "1.2.3.0/23" actually encodes as %Pfx{bits: <<1, 2, 1::size(7)>>, maxlen: 32}
# and padding right with 0-bits to a /24 yields the 1.2.2.0/24 ...
iex> dns_ptr("1.2.3.0/23")
"2.2.1.in-addr.arpa"
iex> dns_ptr("acdc:1976::/32")
"6.7.9.1.c.d.c.a.ip6.arpa"
iex> dns_ptr("acdc:1975::b1ba:2021")
"1.2.0.2.a.b.1.b.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.5.7.9.1.c.d.c.a.ip6.arpa"
Specs
Decodes a modified EUI-64 back into the original EUI-48 address.
This function flips the 7-th bit and removes 16-bits from the middle.
Those 16-bits should be 0xFFFE
, but this is not checked or enforced.
Examples
iex> eui64_decode("0088.88FE.FF88.8888")
"02-88-88-88-88-88"
iex> eui64_decode("0288.88FF.FE88.8888")
"00-88-88-88-88-88"
iex> eui64_decode("02-88-88-FF-FE-88-88-88")
"00-88-88-88-88-88"
iex> eui64_decode({0x02, 0x88, 0x88, 0xFF, 0xFE, 0x88, 0x88, 0x88})
{0x00, 0x88, 0x88, 0x88, 0x88, 0x88}
iex> new("2001:db8:1:2:020c:29ff:fe0c:47d5")
...> |> cut(-1, -64)
...> |> eui64_decode()
...> |> format()
"00-0C-29-0C-47-D5"
Specs
Creates a modified EUI-64 out of eui
(an EUI-48 address or an EUI-64).
This flips the 7-th bit (U/L - universal/local) and inserts 0xFFFE
in the
middle.
The function assumes either an EUI-48 or EUI-64 address. In the latter case, it'll only flip the 7-th bit.
Examples
iex> eui64_encode("0088.8888.8888")
"02-88-88-FF-FE-88-88-88"
iex> eui64_encode("0288.8888.8888")
"00-88-88-FF-FE-88-88-88"
iex> eui64_encode({0x00, 0x88, 0x88, 0x88, 0x88, 0x88})
{0x02, 0x88, 0x88, 0xFF, 0xFE, 0x88, 0x88, 0x88}
# modified EUI-64 from an existing EUI-64, simply flip the 7th bit
iex> eui64_encode("01:23:45:67:89:AB:CD:EF")
"03-23-45-67-89-AB-CD-EF"
Specs
Returns one or all the properties as per Iana's special purpose address
registry for given prefix
.
See:
If given either :ip4
or :ip6
as the first argument, the property
argument is ignored and the associated list of prefixes and their properties
is returned. The list contains two-element tuples, the first element is a
Pfx.t/0
struct and the second element a map with the properties
associated with the prefix. The list is ordered on the first tuple element,
more to less specific.
Otherwise, prefix
is taken to be a prefix/0
and can be a full address.
If there is no match with any of the prefixes in the associated special purpose
address registry, nil
is returned.
If the property argument is omitted, it defaults to nil
and in case of a match
the property map is returned. If there is no match, nil is returned.
Otherwise, an individual property value is returned and property
can be one of:
:prefix
, a string: the owner prefix:source
, a semi-boolean: whether or notprefix
is a valid source address:destination
, a semi-boolean: whether or notprefix
is a valid destination address:forward
, a semi-boolean: whether or notprefix
can be forwarded (by a router):global
, a semi-boolean: whether or notprefix
is reachable on the Internet:reserved
, a semi-boolean: whether or notprefix
is reserved-by-protocol:name
: a string: the description of the entry in the special purpose address registryspec
: a list of strings: naming the rfc's and errata related to givenprefix
allocation
: a string denotingyyyy-mm
of the registry's entry
A semi-boolean, for lack of a better word, can have 3 values:
true
,false
, or:na
, which means not-applicable in the eyes of iana
The :na
case currently appears only for:
192.88.99.0/24
(deprecated 6to4),2002::/16
(6to4), and2001::/32
(teredo)
The :name
property value is somewhat normalized, but will be tricky to rely
on in code since that may change at the whim of the RFC editors.
Examples
iex> iana_special(:ip4) |> hd()
{%Pfx{bits: <<0, 0, 0, 0>>, maxlen: 32},
%{
allocation: "1981-09",
destination: false,
forward: false,
global: false,
name: "this-host-on-this-network",
prefix: "0.0.0.0/32",
reserved: true,
source: true,
spec: ["rfc1122"],
termination: :na
}}
iex> iana_special("10.10.10.10")
%{
allocation: "1996-02",
destination: true,
forward: true,
global: false,
name: "private-use",
prefix: "10.0.0.0/8",
reserved: false,
source: true,
spec: ["rfc1918"],
termination: :na
}
iex> iana_special("10.11.12.13", :global)
false
iex> iana_special("10.11.12.13", :name)
"private-use"
iex> iana_special("fc00::/7", :global)
false
iex> iana_special("fc00::/7", :name)
"unique-local"
iex> iana_special("2001::/32", :global)
:na
iex> iana_special("2001::/32", :name)
"teredo"
iex> iana_special("2001::/23", :name)
"ietf-protocol-assignments"
Specs
Returns a map with link-local address components for given pfx
.
Returns nil if pfx
is not link-local as per
rfc3927 or
rfc4291
Examples
iex> x = link_local("169.254.128.233")
iex> x
%{ digits: {169, 254, 128, 233},
prefix: "169.254.0.0/16",
ifaceID: 33001,
address: "169.254.128.233"
}
#
iex> host(x.prefix, x.ifaceID)
"169.254.128.233"
iex> y = link_local("fe80::acdc:1976")
iex> y
%{ preamble: 1018,
prefix: "fe80::/64",
ifaceID: 2900105590,
address: "fe80::acdc:1976"
}
#
iex> host(y.prefix, y.ifaceID)
"fe80::acdc:1976"
Specs
Returns true if pfx
is a link-local prefix, false otherwise
Link local prefixes include:
0.0.0.0/8
, rfc1122, 'this-network'255.255.255.255/32
, rfc1122, limited broadcast169.254.0.0/16
, rfc3927, link-local (see examples)fe80::/64
, rfc4291, link-local
Examples
# first 256 addresses are reserved
iex> link_local?("169.254.0.0")
false
# last 256 addresses are reserved
iex> link_local?("169.254.255.0")
false
# rest is considered link local
iex> link_local?("169.254.1.0")
true
iex> link_local?("169.254.254.255")
true
iex> link_local?("0.0.0.0")
true
iex> link_local?("0.255.255.255")
true
iex> link_local?({0, 255, 255, 255})
true
iex> link_local?("fe80::acdc:1975")
true
iex> link_local?("1.1.1.1")
false
# bad prefix
iex> link_local?("10.10.10.256")
false
Specs
Returns true is pfx
is a multicast prefix, false otherwise
Checks if pfx
given is a member of:
Examples
iex> multicast?("224.0.0.1")
true
iex> multicast?("ff02::1")
true
iex> multicast?({{224, 0, 0, 1}, 32})
true
iex> multicast?({224, 0, 0, 1})
true
iex> multicast?(%Pfx{bits: <<224, 0, 0, 1>>, maxlen: 32})
true
iex> multicast?("1.1.1.1")
false
# bad prefix
iex> multicast?("224.0.0.256")
false
Specs
Returns a map with multicast address components for given pfx
.
Address components are parsed according to:
Rfc specific fields are put in their own map under the rfc
-key.
- rfc6034 - Unicast-Prefix-Based IPv4 Multicast Addresses
- rfc3180 - GLOP Addressing in 233/8
- rfc3956 - Embedding the Rendezvous Point (RP) Address in an IPv6 Multicast Address
- rfc3306 - Unicast-Prefix-based IPv6 Multicast Addresses
Note that for unicast-prefix-based IPv4 multicast addresses, the unicast prefix is always taken to be 24 bits long. That should still allow for identification of the origin by looking up the assigned unicast address space that includes the /24.
Examples
iex> multicast_decode("234.192.0.2")
%{
multicast_address: "234.192.0.2",
protocol: :ipv4,
rfc: %{
group_id: 2,
multicast_prefix: "234.0.0.0/8",
rfc: 6034,
unicast_prefix: "192.0.2.0/24"
}
}
iex> Pfx.multicast_decode("FF32:0030:3FFE:FFFF:0001::/96")
%{
flags: {0, 0, 1, 1},
multicast_address: "ff32:30:3ffe:ffff:1::/96",
multicast_prefix: "ff30::/12",
protocol: :ipv6,
rfc: %{
group_id: 0,
unicast_prefix: "3ffe:ffff:1::/48",
plen: 48,
reserved: 0,
rfc: 3306
},
scope: 2
}
Specs
Returns true if pfx
is matched by the Well-Known Prefixes defined in
rfc6053 and
rfc8215, false otherwise.
Note that organisation specific prefixes might still be used for nat64.
Examples
iex> nat64?("64:ff9b::10.10.10.10")
true
iex> nat64?("64:ff9b:1::10.10.10.10")
true
iex> nat64?({{0x64, 0xff9b, 0, 0, 0, 0, 0x1010, 0x1010}, 128})
true
iex> nat64?({0x64, 0xff9b, 0, 0, 0, 0, 0x1010, 0x1010})
true
iex> nat64?(%Pfx{bits: <<0x64::16, 0xff9b::16, 0::64, 0x1010::16, 0x1010::16>>, maxlen: 128})
true
# illegal/bad prefix
iex> nat64?("64:ff9b:1::10.10.10.256")
false
Specs
Returns the embedded IPv4 address of a nat64 pfx
The pfx
prefix should be a full IPv6 address. The len
defaults to 96
,
but if specified it should be one of [96, 64, 56, 48, 40, 32].
Examples
iex> nat64_decode("64:ff9b::10.10.10.10")
"10.10.10.10"
iex> nat64_decode("64:ff9b:1:0a0a:000a:0a00::", 48)
"10.10.10.10"
# from rfc6052, section 2.4
iex> nat64_decode("2001:db8:c000:221::", 32)
"192.0.2.33"
iex> nat64_decode("2001:db8:1c0:2:21::", 40)
"192.0.2.33"
iex> nat64_decode("2001:db8:122:c000:2:2100::", 48)
"192.0.2.33"
iex> nat64_decode("2001:db8:122:3c0:0:221::", 56)
"192.0.2.33"
iex> nat64_decode("2001:db8:122:344:c0:2:2100::", 64)
"192.0.2.33"
iex> nat64_decode({0x2001, 0xdb8, 0x122, 0x344, 0xC0, 0x2, 0x2100, 0x0}, 64)
{192, 0, 2, 33}
iex> nat64_decode({{0x2001, 0xdb8, 0x122, 0x344, 0xC0, 0x2, 0x2100, 0x0}, 128}, 64)
{{192, 0, 2, 33}, 32}
iex> nat64_decode("2001:db8:122:344::192.0.2.33", 96)
"192.0.2.33"
iex> nat64_decode("2001:db8:122:344::192.0.2.33", 90)
** (ArgumentError) nat64 prefix length not in [96, 64, 56, 48, 40, 32], got 90
Specs
Returns an IPv4 embedded IPv6 address for given pfx6
and pfx4
.
The length of the pfx6.bits
should be one of [96, 64, 56, 48, 40, 32] as defined
in rfc6052. The pfx4
prefix should be a full address.
Examples
iex> nat64_encode("2001:db8:100::/40", "192.0.2.33")
"2001:db8:1c0:2:21::"
iex> nat64_encode("2001:db8:122::/48", "192.0.2.33")
"2001:db8:122:c000:2:2100::"
iex> nat64_encode("2001:db8:122:300::/56", "192.0.2.33")
"2001:db8:122:3c0:0:221::"
iex> nat64_encode("2001:db8:122:344::/64", "192.0.2.33")
"2001:db8:122:344:c0:2:2100:0"
iex> nat64_encode("2001:db8:122:344::/96", "192.0.2.33")
"2001:db8:122:344::c000:221"
iex> nat64_encode({{0x2001, 0xdb8, 0, 0, 0, 0, 0, 0}, 32}, "192.0.2.33")
{{0x2001, 0xdb8, 0xc000, 0x221, 0, 0, 0, 0}, 128}
iex> nat64_encode(%Pfx{bits: <<0x2001::16, 0xdb8::16>>, maxlen: 128}, "192.0.2.33")
%Pfx{bits: <<0x2001::16, 0xdb8::16, 0xc000::16, 0x221::16, 0::64>>, maxlen: 128}
iex> nat64_encode("2001:db8::/32", "192.0.2.33")
"2001:db8:c000:221::"
Specs
Returns the this-network prefix (full address) for given pfx
.
Yields the same result as Pfx.first/1
, included for nostalgia.
Examples
iex> network("10.10.10.1/24")
"10.10.10.0"
iex> network("acdc:1976::/32")
"acdc:1976::"
# a full address is its own this-network
iex> network({10, 10, 10, 1})
{10, 10, 10, 1}
iex> network({{10, 10, 10, 1}, 24})
{{10, 10, 10, 0}, 32}
iex> network(%Pfx{bits: <<10, 10, 10>>, maxlen: 32})
%Pfx{bits: <<10, 10, 10, 0>>, maxlen: 32}
iex> network(%Pfx{bits: <<0xacdc::16, 0x1976::16>>, maxlen: 128})
%Pfx{bits: <<0xACDC::16, 0x1976::16, 0::96>>, maxlen: 128}
Specs
Returns true if given pfx
is a teredo address, false otherwise
IPv6 address within the teredo service prefix of 2000:0::/32
More details in rfc4380.
Examples
iex> teredo?("2001:0000:4136:e378:8000:63bf:3fff:fdd2")
true
iex> teredo?("1.1.1.1")
false
iex> teredo?(42)
false
Specs
Returns a map with the teredo address components of pfx
or nil.
Returns nil if pfx
is not a
teredo address.
A teredo address consists of:
- the teredo service prefix of
2000:0::/32
- IPv4 address of the teredo server
- flags (16 bits) that document type of address and NAT
- Port (16 bits), the obfuscated "mapped UDP port" at the client
- IPv4 address (obfucated) of the teredo client.
More details in rfc4380.
Examples
# example from https://en.wikipedia.org/wiki/Teredo_tunneling#IPv6_addressing
iex> teredo_decode("2001:0000:4136:e378:8000:63bf:3fff:fdd2")
%{
server: "65.54.227.120",
client: "192.0.2.45",
port: 40000,
flags: {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
prefix: "2001:0000:4136:e378:8000:63bf:3fff:fdd2"
}
iex> teredo_decode({0x2001, 0, 0x4136, 0xe378, 0x8000, 0x63bf, 0x3fff, 0xfdd2})
%{
server: {65, 54, 227, 120},
client: {192, 0, 2, 45},
port: 40000,
flags: {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
prefix: {0x2001, 0x0, 0x4136, 0xe378, 0x8000, 0x63bf, 0x3fff, 0xfdd2}
}
iex> teredo_decode("1.1.1.1")
nil
Specs
Encodes given server
, client
, port
and flags
as an IPv6 teredo address.
The client
and server
must be full IPv4 addresses, while both port
and flags
are interpreted as 16-bit unsigned integers.
The result mirrors the representation format of the client
argument.
Examples
iex> flags = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
iex> teredo_encode("192.0.2.45", "65.54.227.120", 40000, flags)
"2001:0:4136:e378:8000:63bf:3fff:fdd2"
iex> flags = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
iex> teredo_encode({192, 0, 2, 45}, "65.54.227.120", 40000, flags)
{0x2001, 0, 0x4136, 0xe378, 0x8000, 0x63bf, 0x3fff, 0xfdd2}
iex> flags = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
iex> teredo_encode({{192, 0, 2, 45}, 32}, "65.54.227.120", 40000, flags)
{{0x2001, 0, 0x4136, 0xe378, 0x8000, 0x63bf, 0x3fff, 0xfdd2}, 128}
iex> flags = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
iex> teredo_encode(%Pfx{bits: <<192, 0, 2, 45>>, maxlen: 32}, "65.54.227.120", 40000, flags)
%Pfx{bits: <<0x2001::16, 0::16, 0x4136::16, 0xe378::16, 0x8000::16, 0x63bf::16, 0x3fff::16, 0xfdd2::16>>, maxlen: 128}
Specs
Returns true if pfx
is designated as "private-use".
This includes the rfc1918 prefixes:
10.0.0.0/8
,172.16.0.0/12
, and192.168.0.0/16
.
And the rfc4193 prefix
fc00::/7
.
Examples
iex> unique_local?("172.31.255.255")
true
iex> unique_local?("10.10.10.10")
true
iex> unique_local?("fc00:acdc::")
true
iex> unique_local?("172.32.0.0")
false
iex> unique_local?("10.255.255.255")
true
iex> unique_local?({{172, 31, 255, 255}, 32})
true
iex> unique_local?({172, 31, 255, 255})
true
iex> unique_local?(%Pfx{bits: <<172, 31, 255, 255>>, maxlen: 32})
true
# bad prefix
iex> unique_local?("10.255.255.256")
false
Link to this section Functions
Specs
Returns the address portion of given prefix
without applying the mask (if
any).
Note that this has no real effect on a Pfx.t/0
or an ip_address/0
since
there is no mask to ignore. Since no mask is applied, this always returns a
full prefix without any bits masked off. Raises ArgumentError
if given an
invalid prefix.
Examples
iex> address("1.2.3.4/16")
"1.2.3.4"
iex> address({{1, 2, 3, 4}, 16})
{1, 2, 3, 4}
iex> address({{0xacdc, 0x1976, 0, 0, 0, 0, 0, 1}, 64})
{0xacdc, 0x1976, 0, 0, 0, 0, 0, 1}
iex> pfx = "1.2.3.4/24"
iex> new(pfx).bits
<<1, 2, 3>>
iex> new(address(pfx)).bits
<<1, 2, 3, 4>>
# no real effect
iex> address("1.2.3.4")
"1.2.3.4"
iex> address({1, 2, 3, 4})
{1, 2, 3, 4}
iex> address({0xacdc, 0x1976, 0, 0, 0, 0, 0, 1})
{0xacdc, 0x1976, 0, 0, 0, 0, 0, 1}
iex> new("1.2.3.4/16") |> address()
%Pfx{bits: <<1, 2>>, maxlen: 32}
Specs
Returns a bitwise AND of pfx1
and pfx2
.
Both prefixes must have the same maxlen
. The resulting prefix will have
the same number of bits as the first argument.
Examples
iex> band("10.10.10.10", "255.255.0.0")
"10.10.0.0"
iex> band("10.10.10.0/24", "255.255.0.0")
"10.10.0.0/24"
iex> x = new(<<128, 129, 130, 131>>, 32)
iex> y = new(<<255, 255>>, 32)
iex> band(x, y)
%Pfx{bits: <<128, 129, 0, 0>>, maxlen: 32}
iex>
iex> band(y,x)
%Pfx{bits: <<128, 129>>, maxlen: 32}
# results adopt the format of the first argument
iex> band("1.2.3.4", {255, 255, 0, 0})
"1.2.0.0"
iex> band({1, 2, 3, 4}, "255.255.0.0")
{1, 2, 0, 0}
iex> band({{1, 2, 3, 4}, 24}, {255, 255, 0, 0})
{{1, 2, 0, 0}, 24}
# honoring the ancient tradition
iex> band("1.2.3.4", "255.255")
"1.0.0.4"
iex> band("10.10.0.0/16", "255.0.0.0/24")
"10.0.0.0/16"
Specs
Returns the bit-value at given position
in pfx
.
A bit position is a 0
-based index from the left with range 0..maxlen-1
.
A negative bit position is taken relative to Pfx.maxlen
. Bits that are
masked (bit position in the range of bit_size(pfx.bits) .. pfx.maxlen - 1
)
always yield 0
.
Examples
iex> bit("1.2.0.0", 14)
1
# same bit
iex> bit("1.2.0.0", -18)
1
iex> bit("1.2.0.0/16", 14)
1
iex> bit({1, 2, 0, 0}, 14)
1
iex> bit({{1, 2, 0, 0}, 16}, 14)
1
iex> bit(%Pfx{bits: <<1, 2>>, maxlen: 32}, 14)
1
# 'masked' bits are deemed to be `0`
iex> bit("1.2.0.0/16", 24)
0
# errors out on invalid positions
iex> bit("255.255.255.255", 33)
** (ArgumentError) invalid bit position, got 33
iex> bit("10.10.0.0/16", -33)
** (ArgumentError) invalid bit position, got -33
Specs
Returns the concatenation of 1 or more series of bits of the given pfx
.
Examples
iex> bits("1.2.3.4", [{0, 8}, {-1, -8}])
<<1, 4>>
iex> bits("1.2.3.0/24", [{0, 8}, {-1, -8}])
<<1, 0>>
iex> bits({1, 2, 3, 4}, [{0, 8}, {-1, -8}])
<<1, 4>>
iex> bits({{1, 2, 3, 0}, 24}, [{0,8}, {-1, -8}])
<<1, 0>>
iex> bits(%Pfx{bits: <<1, 2, 3, 4>>, maxlen: 32}, [{0,8}, {-1, -8}])
<<1, 4>>
Specs
Returns length
bits, starting at position
for given pfx
.
Negative position
's are relative to the end of the pfx.bits
bitstring,
while negative length
will collect bits going left instead of to the
right. Note that the bit at given position
is always included in the
result regardless of direction. Finally, a length
of 0
results in
an empty bitstring.
Examples
# last two bytes
iex> bits("128.0.128.1", 16, 16)
<<128, 1>>
iex> bits({128, 0, 128, 1}, 16, 16) # same
<<128, 1>>
iex> bits({128, 0, 128, 1}, 31, -16) # same
<<128, 1>>
iex> bits({{128, 0, 128, 1}, 32}, 31, -16) # same
<<128, 1>>
# first byte
iex> bits(%Pfx{bits: <<128, 0, 0, 1>>, maxlen: 32}, 0, 8)
<<128>>
# same as
iex> bits(%Pfx{bits: <<128, 0, 0, 1>>, maxlen: 32}, 7, -8)
<<128>>
# missing bits are filled in as `0`
iex> x = new(<<128>>, 32)
iex> bits(x, 0, 32)
<<128, 0, 0, 0>>
iex> x = new(<<128>>, 32)
iex> bits(x, 0, 16)
<<128, 0>>
iex> x = new(<<128>>, 32)
iex> bits(x, 15, -16)
<<128, 0>>
# the last 5 bits
iex> x = new(<<255>>, 32)
iex> bits(x, 7, -5)
<<0b11111::size(5)>>
Specs
Returns a bitwise NOT of the bits in pfx
.
This inverts the pfx
and returns it using the same representation as given.
Examples
iex> bnot("255.255.0.0")
"0.0.255.255"
iex> bnot({255, 255, 0, 0})
{0, 0, 255, 255}
iex> bnot({{255, 255, 0, 0}, 32})
{{0, 0, 255, 255}, 32}
iex> new(<<255, 255, 0, 0>>, 32) |> bnot()
%Pfx{bits: <<0, 0, 255, 255>>, maxlen: 32}
iex> bnot("5323:e689::/32")
"acdc:1976::/32"
Specs
Returns a bitwise OR of pfx1
and pfx2
.
Both prefixes must have the same maxlen
. The result will have the same
number of bits as its first argument.
Examples
iex> bor("1.2.3.4", "0.0.255.0")
"1.2.255.4"
iex> bor({1, 2, 3, 4}, "0.0.255.0")
{1, 2, 255, 4}
iex> bor({{1, 2, 3, 4}, 16}, {0, 255, 255, 0})
{{1, 255, 0, 0}, 16}
# same sized `bits`
iex> x = new(<<10, 11, 12, 13>>, 32)
iex> y = new(<<0, 0, 255, 255>>, 32)
iex> bor(x, y)
%Pfx{bits: <<10, 11, 255, 255>>, maxlen: 32}
# same `maxlen` but differently sized `bits`: missing bits are considered to be `0`
iex> bor("10.11.12.13", "255.255.0.0/16")
"255.255.12.13"
# result has same number of bits as the first prefix
iex> bor("10.10.0.0/16", "255.255.255.255")
"255.255.0.0/16"
Specs
Rotates the bits of pfx
by n
positions.
Positive n
rotates right, negative rotates left. The length of the
resulting bits stays the same.
Examples
iex> brot("1.2.3.4", 8)
"4.1.2.3"
iex> brot("1.2.3.4", -8)
"2.3.4.1"
iex> brot({1, 2, 3, 4}, 8)
{4, 1, 2, 3}
iex> brot({{1, 2, 3, 4}, 32}, -8)
{{2, 3, 4, 1}, 32}
# note: the `bits` <<1, 2>> get rotated (!)
iex> brot("1.2.0.0/16", 8)
"2.1.0.0/16"
iex> brot(%Pfx{bits: <<1, 2, 3, 4>>, maxlen: 32}, 8)
%Pfx{bits: <<4, 1, 2, 3>>, maxlen: 32}
Specs
Sets all bits of pfx
to either 0
or 1
.
If bit
is not provided, it defaults to 0
.
Examples
iex> bset("1.1.1.0/24")
"0.0.0.0/24"
iex> bset("1.1.1.0/24", 1)
"255.255.255.0/24"
iex> bset({{1, 1, 1, 0}, 24}, 1)
{{255, 255, 255, 0}, 24}
iex> bset(%Pfx{bits: <<1, 1, 1>>, maxlen: 32})
%Pfx{bits: <<0, 0, 0>>, maxlen: 32}
iex> bset(%Pfx{bits: <<1, 1, 1>>, maxlen: 32}, 1)
%Pfx{bits: <<255, 255, 255>>, maxlen: 32}
Specs
Performs an arithmetic shift left of the bits in pfx
by n
positions.
A positive n
shifts to the left, negative n
shifts to the right. The
length of the bits stays the same.
Examples
iex> bsl("1.2.3.4", 1)
"2.4.6.8"
iex> bsl("1.2.0.0/16", 2)
"4.8.0.0/16"
iex> bsl({1, 2, 3, 4}, 2)
{4, 8, 12, 16}
# note: the `bits` <<1, 2>> get shifted left 2 bits
iex> bsl({{1, 2, 0, 0}, 16}, 2)
{{4, 8, 0, 0}, 16}
iex> bsl(%Pfx{bits: <<1, 2>>, maxlen: 32}, 2)
%Pfx{bits: <<4, 8>>, maxlen: 32}
iex> bsl(%Pfx{bits: <<1, 2>>, maxlen: 32}, -2)
%Pfx{bits: <<0, 64>>, maxlen: 32}
Specs
Performs an arithmetic shift right the bits in pfx
by n
positions.
A negative n
actually shifts to the left. The length of the bits stays the
same.
Examples
iex> bsr("1.2.0.0/16", 2)
"0.64.0.0/16"
# no mask, so all 32 bits get shifted
iex> bsr({1, 2, 0, 0}, 2)
{0, 64, 128, 0}
iex> bsr({{1, 2, 0, 0}, 16}, 2)
{{0, 64, 0, 0}, 16}
iex> bsr(%Pfx{bits: <<1, 2>>, maxlen: 32}, 2)
%Pfx{bits: <<0, 64>>, maxlen: 32}
# now shift to the left
iex> bsr(%Pfx{bits: <<1, 2>>, maxlen: 32}, -2)
%Pfx{bits: <<4, 8>>, maxlen: 32}
Specs
Returns a bitwise XOR of pfx1
and pfx2
.
Both prefixes must have the same maxlen
. The result has the same number of
bits as the first argument.
Examples
iex> bxor("10.11.12.13", "255.255.0.0")
"245.244.12.13"
iex> bxor({10, 11, 12, 13}, {255, 255, 0, 0})
{245, 244, 12, 13}
# mix 'n match
iex> bxor({{10, 11, 12, 13}, 32}, "255.255.0.0")
{{245, 244, 12, 13}, 32}
iex> x = new(<<10, 11, 12, 13>>, 32)
iex> y = new(<<255, 255>>, 32)
iex> bxor(x, y)
%Pfx{bits: <<245, 244, 12, 13>>, maxlen: 32}
iex> bxor("255.255.0.0/16", "10.11.12.13")
"245.244.0.0/16"
Specs
cast(prefix()) :: non_neg_integer()
Casts a pfx
to an integer.
After right padding the given pfx
, its bits are interpreted as a number of
maxlen bits wide. Empty prefixes evaluate to 0
, since all 'missing' bits
are taken to be zero (even if pfx.maxlen
is 0
).
See cut/3
for how this capability might be useful.
Examples
iex> cast("255.255.0.0")
4294901760
iex> cast("255.255.0.0/16")
4294901760
iex> cast({255, 255, 0, 0})
4294901760
iex> cast({{255, 255, 0, 0}, 32})
4294901760
iex> cast(%Pfx{bits: <<255, 255>>, maxlen: 32})
4294901760
iex> new(<<4294901760::32>>, 32)
%Pfx{bits: <<255, 255, 0, 0>>, maxlen: 32}
# missing bits filled in as `0`s
iex> cast(%Pfx{bits: <<255>>, maxlen: 16})
65280
iex> cast(%Pfx{bits: <<-1::128>>, maxlen: 128})
340282366920938463463374607431768211455
iex> cast(%Pfx{bits: <<>>, maxlen: 8})
0
# a bit weird, but:
iex> cast(%Pfx{bits: <<>>, maxlen: 0})
0
Specs
Compares prefix1
to prefix2
for sorting purposes.
The result is one of:
:eq
prefix1 is equal to prefix2:lt
prefix1 has a smallermaxlen
, more bits or is left of prefix2:gt
prefix1 has a largermaxlen
, less bits or is right of prefix2
Note that if prefixes are not of the same type, they are compared solely on their
maxlen
property.
This function enables Enum.sort/2
to be handed the Pfx
module to determine
how to sort a list of prefixes. When building some sort of acl, use {:desc, Pfx}
to sort from less to more specific prefixes.
Examples
iex> compare("10.0.0.0/8", "11.0.0.0/8")
:lt
iex> compare("10.0.0.0/8", {{11, 0, 0, 0}, 8})
:lt
iex> compare({10, 0, 0, 0}, {{11, 0, 0, 0}, 16})
:lt
iex> compare(new(<<10>>, 32), new(<<11>>, 32))
:lt
iex> compare("10.10.10.10", "acdc::1")
:lt
# sort prefixes, ascending (more to less specific)
iex> ["10.11.0.0/16", "10.10.10.0/24", "10.10.0.0/16"]
...> |> Enum.sort(Pfx)
[
"10.10.10.0/24",
"10.10.0.0/16",
"10.11.0.0/16"
]
# sort prefixes, descending (less to more specific)
iex> ["10.11.0.0/16", "10.10.10.0/24", "10.10.0.0/16"]
...> |> Enum.sort({:desc, Pfx})
[
"10.11.0.0/16",
"10.10.0.0/16",
"10.10.10.0/24"
]
# regular sort
iex> ["10.11.0.0/16", "10.10.10.0/24", "10.10.0.0/16"]
...> |> Enum.sort()
[
"10.10.0.0/16",
"10.10.10.0/24",
"10.11.0.0/16"
]
iex> [new(<<10, 11>>, 32), new(<<10,10,10>>, 32), new(<<10,10>>, 32)]
...> |> Enum.sort(Pfx)
[
%Pfx{bits: <<10, 10, 10>>, maxlen: 32},
%Pfx{bits: <<10, 10>>, maxlen: 32},
%Pfx{bits: <<10, 11>>, maxlen: 32}
]
# mixed representations
iex> ["10.11.0.0/16", {{10, 10, 10, 0}, 24}, %Pfx{bits: <<10, 10>>, maxlen: 32}]
...> |> Enum.sort(Pfx)
[
{{10, 10, 10, 0}, 24},
%Pfx{bits: <<10, 10>>, maxlen: 32},
"10.11.0.0/16",
]
# mixed prefix types
iex> ["10.10.10.0/24", "10.10.0.0/16", "acdc::1", "acdc::/32"]
...> |> Enum.sort({:desc, Pfx})
["acdc::/32", "acdc::1", "10.10.0.0/16", "10.10.10.0/24"]
Specs
contrast(prefix(), prefix()) :: :equal | :more | :less | :left | :right | :disjoint | :left_nc | :right_nc | :incompatible | :einvalid
Contrasts pfx1
to pfx2
.
Contrasting two prefixes yields one of:
:equal
pfx1 is equal to pfx2:more
pfx1 is more specific and contained in pfx2:less
pfx1 is less specific and contains pfx2:left
pfx1 is left-adjacent to pfx2 such that it can be combined with pfx2:left_nc
pfx1 is left-adjacent to pfx2, but cannot be combined:right
pfx1 is right-adjacent to pfx2 such that is can be combined with pfx2:right_nc
pfx1 is right-adjacent to pfx2, but cannot be combined:disjoint
pfx1 has no match with pfx2:einvalid
either pfx1 or pfx2 was invalid:incompatible
pfx1 and pfx2 do not have the same maxlen
Examples
iex> contrast("10.10.0.0/16", "10.10.0.0/16")
:equal
iex> contrast("10.10.10.0/24", "10.10.0.0/16")
:more
iex> contrast("10.0.0.0/8", "10.255.255.0/24")
:less
# can be combined as 1.1.0.0/23
iex> contrast("1.1.0.0/24", "1.1.1.0/24")
:left
# can be combined as 1.1.0.0/23
iex> contrast("1.1.1.0/24", "1.1.0.0/24")
:right
# adjacent, but cannot be combined
iex> contrast("1.1.3.0/24", "1.1.4.0/25")
:left_nc
# adjacent, but cannot be combined
iex> contrast("1.1.4.0/25", "1.1.3.0/24")
:right_nc
# adjacent again, but cannot be combined
iex> contrast("1.1.1.0/24", "1.1.2.0/24")
:left_nc
# adjacent again, but cannot be combined
iex> contrast("1.1.2.0/24", "1.1.1.0/24")
:right_nc
iex> contrast("1.2.3.4/30", "1.2.3.12/30")
:disjoint
iex> contrast("10.10.0.0/16", %Pfx{bits: <<10,12>>, maxlen: 32})
:disjoint
iex> contrast("1.2.3.4", {1, 2, 3, 4})
:equal
iex> contrast("1.1.1.1", "acdc:1976::1")
:incompatible
iex> contrast("1.1.1.400", "1.1.1.1")
:einvalid
Specs
Cuts out a series of bits and turns it into its own Pfx
.
Extracts the bits and returns a new Pfx.t/0
with bits
set to the
bits extracted and maxlen
set to the length of the bits
-string.
Examples
iex> cut("::ffff:192.0.2.128", -1, -32)
"192.0.2.128"
iex> teredo = new("2001:0:4136:e378:8000:63bf:3fff:fdd2")
iex> # client
iex> cut(teredo, 96, 32) |> bnot() |> format()
"192.0.2.45"
iex> # udp port
iex> cut(teredo, 80, 16) |> bnot() |> cast()
40000
iex> # teredo server
iex> cut(teredo, 32, 32) |> format()
"65.54.227.120"
iex> # flags
iex> cut(teredo, 64, 16) |> digits(1) |> elem(0)
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
'Masked' bits are considered to be zero.
# extract 2nd and 3rd byte:
iex> %Pfx{bits: <<255, 255>>, maxlen: 32} |> cut(8, 16)
%Pfx{bits: <<255, 0>>, maxlen: 16}
Less useful, but cut will mirror the representation given:
iex> cut("10.11.12.13", 8, 16)
"11.12"
iex> cut({1, 2, 3, 4}, 16, 16)
{3, 4}
iex> cut({{1, 2, 0, 0}, 16}, 8, 16)
{{2, 0}, 16}
Extraction must stay within maxlen
of given pfx
.
# cannot exceed boundaries though:
iex> %Pfx{bits: <<255, 255>>, maxlen: 32} |> cut(8, 32)
** (ArgumentError) invalid index range, got {8, 32}
Specs
digits(prefix(), pos_integer()) :: {tuple(), pos_integer()}
Returns a {{digit, ..}, length}
representation of given pfx
.
The pfx
is padded to its maximum length using 0
's and the resulting
bits are grouped into digits, each width
-bits wide. The resulting length
denotes the prefix' original bit_size.
Note: works best if the prefix' maxlen
is a multiple of the width
used,
otherwise maxlen
cannot be inferred from this format by tuple_size(digits) * width
(e.g. by Pfx.undigits/2
)
Examples
iex> digits("10.11.12.0/24", 8)
{{10, 11, 12, 0}, 24}
# mask is applied first
iex> digits("10.11.12.13/24", 8)
{{10, 11, 12, 0}, 24}
iex> digits("acdc:1976::/32", 16)
{{44252, 6518, 0, 0, 0, 0, 0, 0}, 32}
iex> digits({{0xacdc, 0x1976, 0, 0, 0, 0, 0, 0}, 32}, 16)
{{44252, 6518, 0, 0, 0, 0, 0, 0}, 32}
iex> digits(%Pfx{bits: <<10, 11, 12>>, maxlen: 32}, 8)
{{10, 11, 12, 0}, 24}
iex> digits(%Pfx{bits: <<10, 11, 12, 1::1>>, maxlen: 32}, 8)
{{10, 11, 12, 128}, 25}
iex> digits(%Pfx{bits: <<0x12, 0x34, 0x56, 0x78>>, maxlen: 128}, 4)
{{1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 32}
Specs
drop(prefix(), non_neg_integer()) :: prefix()
Drops count
lsb bits from given pfx
.
If count
exceeds the actual number of bits in pfx
, simply drops all
bits.
Examples
iex> drop("1.2.3.0/31", 1)
"1.2.3.0/30"
iex> drop("1.2.3.2/31", 1)
"1.2.3.0/30"
iex> drop("1.2.3.128/25", 1)
"1.2.3.0/24"
iex> drop("1.1.1.130/31", 1)
"1.1.1.128/30"
# drops all
iex> drop("1.2.3.0/24", 512)
"0.0.0.0/0"
iex> drop("2001:0db8:85a3:0000:0000:8a2e:0370:7334", 64)
"2001:db8:85a3::/64"
iex> drop({1, 2, 3, 4}, 8)
{1, 2, 3, 0}
iex> drop({{1, 2, 3, 4}, 32}, 16)
{{1, 2, 0, 0}, 16}
iex> drop(%Pfx{bits: <<1, 2, 3, 4>>, maxlen: 32}, 16)
%Pfx{bits: <<1, 2>>, maxlen: 32}
Specs
fields(prefix(), non_neg_integer()) :: [{non_neg_integer(), non_neg_integer()}]
Returns a list of {number, width}
-fields for given pfx
.
If bit_size(pfx.bits)
is not a multiple of width
, the last
{number, width}
-tuple, will have a smaller width.
Examples
iex> fields("10.11.12.13", 8)
[{10, 8}, {11, 8}, {12, 8}, {13, 8}]
iex> fields({10, 11, 12, 13}, 8)
[{10, 8}, {11, 8}, {12, 8}, {13, 8}]
iex> fields({{10, 11, 12, 0}, 24}, 8)
[{10, 8}, {11, 8}, {12, 8}]
iex> fields(%Pfx{bits: <<10, 11, 12, 13>>, maxlen: 32}, 8)
[{10, 8}, {11, 8}, {12, 8}, {13, 8}]
# pfx.bits is not a multiple of 8, hence the {0, 1} at the end
iex> fields("10.11.12.0/25", 8)
[{10, 8}, {11, 8}, {12, 8}, {0, 1}]
iex> new(<<0xacdc::16>>, 128) |> fields(4)
[{10, 4}, {12, 4}, {13, 4}, {12, 4}]
# only 1 field with less bits than given width of 64
iex> new(<<255, 255>>, 32) |> fields(64)
[{65535, 16}]
Specs
Returns the first full length prefix from the set represented by pfx
.
Examples
iex> first("10.10.10.1/24")
"10.10.10.0"
iex> first("acdc:1976::/32")
"acdc:1976::"
# a full address is its own this-network
iex> first({10, 10, 10, 1})
{10, 10, 10, 1}
iex> first({{10, 10, 10, 1}, 24})
{{10, 10, 10, 0}, 32}
iex> first(%Pfx{bits: <<10, 10, 10>>, maxlen: 32})
%Pfx{bits: <<10, 10, 10, 0>>, maxlen: 32}
iex> first(%Pfx{bits: <<0xacdc::16, 0x1976::16>>, maxlen: 128})
%Pfx{bits: <<0xACDC::16, 0x1976::16, 0::96>>, maxlen: 128}
Specs
Flips a single bit at position
in given pfx
.
A negative position
is relative to the end of the pfx.bits
bitstring.
It is an error to point to a bit outside the range of available bits.
Examples
iex> flip("255.255.254.0", 23)
"255.255.255.0"
iex> flip("255.255.255.0", -9)
"255.255.254.0"
# flip the 7th bit
iex> flip("0088.8888.8888", 6)
"02-88-88-88-88-88"
iex> flip({1, 2, 3, 0}, 24)
{1, 2, 3, 128}
# flip last bit
iex> flip({{1, 2, 3, 128}, 25}, 24)
{{1, 2, 3, 0}, 25}
iex> flip({{1, 2, 3, 128}, 25}, -1)
{{1, 2, 3, 0}, 25}
iex> flip(%Pfx{bits: <<1, 2, 3, 1::1>>, maxlen: 32}, -1)
%Pfx{bits: <<1, 2, 3, 0::1>>, maxlen: 32}
Specs
Formats pfx
as a string, using several options:
:width
, field width (default 8):base
, howto turn a field into a string (default 10, use 16 for hex numbers):unit
, how many fields go into 1 section (default 1):ssep
, howto join the sections together (default "."):lsep
, howto join a mask if required (default "/"):mask
, whether to add a mask (default false):reverse
, whether to reverse fields before grouping/joining (default false):padding
, whether to pad out thepfx.bits
(default true)
The defaults are geared towards IPv4 prefixes, but the options should be able to accommodate other domains as well.
Notes:
- the prefix.bits-length is omitted if equal to the prefix.bits-size
- domain specific submodules probably implement their own formatter.
Examples
iex> format(%Pfx{bits: <<10, 11, 12>>, maxlen: 32})
"10.11.12.0/24"
iex> format({{10, 11, 12, 0}, 24})
"10.11.12.0/24"
iex> format({10, 11, 12, 0})
"10.11.12.0"
# non-sensical, but there you go
iex> format("10.11.12.0/24")
"10.11.12.0/24"
# bitstring, note that mask is applied when new creates the `pfx`
iex> format("1.2.3.4/24", width: 1, base: 2, unit: 8, mask: false)
"00000001.00000010.00000011.00000000"
# mask not appended as its redundant for a full-sized prefix
iex> format(%Pfx{bits: <<10, 11, 12, 13>>, maxlen: 32})
"10.11.12.13"
iex> pfx = new(<<0xacdc::16, 0x1976::16>>, 128)
iex> format(pfx, width: 16, base: 16, ssep: ":")
"acdc:1976:0:0:0:0:0:0/32"
#
# similar, but grouping 4 fields, each 4 bits wide, into a single section
#
iex> format(pfx, width: 4, base: 16, unit: 4, ssep: ":")
"acdc:1976:0000:0000:0000:0000:0000:0000/32"
#
# this time, omit the actual pfx length
#
iex> format(pfx, width: 16, base: 16, ssep: ":", mask: false)
"acdc:1976:0:0:0:0:0:0"
#
# ptr for IPv6 using the nibble format:
# - dot-separated reversal of all hex digits in the expanded address
#
iex> pfx
...> |> format(width: 4, base: 16, mask: false, reverse: true)
...> |> String.downcase()
...> |> (fn x -> "#{x}.ip6.arpa." end).()
"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.7.9.1.c.d.c.a.ip6.arpa."
# turn off padding to get reverse zone dns ptr record
iex> new(<<10, 11, 12>>, 32)
...> |> format(padding: false, reverse: true, mask: false)
...> |> (&"#{&1}.in-addr.arpa.").()
"12.11.10.in-addr.arpa."
Creates a Pfx
struct for given hexadecimal string
.
This always returns a Pfx.t/0
struct. A list of punctuation characters
can be supplied as a second argument and defaults to [?:, ?-, ?.]
. Note
that '/' should not be used as that separates the mask from the rest of the
binary. Punctuation characters are simply ignored and no positional checks
are performed.
Contrary to Pfx.from_mac/1
, this function turns a random hexadecimal into a
prefix. Do not use this to create a prefix out of an IPv6 address.
Examples
iex> from_hex("1-2:3.4:a-bcdef")
%Pfx{bits: <<0x12, 0x34, 0xAB, 0xCD, 0xEF>>, maxlen: 40}
iex> from_hex("1-2:3.4:a-bcdef/16")
%Pfx{bits: <<0x12, 0x34>>, maxlen: 40}
iex> from_hex("1|2|3|A|B|C|DEF", [?|])
%Pfx{bits: <<0x12, 0x3A, 0xBC, 0xDE, 0xF::4>>, maxlen: 36}
iex> from_hex("ABC")
%Pfx{bits: <<0xAB, 0xC::4>>, maxlen: 12}
# not for IPv6 addresses ..
iex> from_hex("2001::1")
%Pfx{bits: <<0x20, 0x01, 0x1::4>>, maxlen: 20}
Specs
Creates a Pfx
struct from a EUI48/64 strings or tuples.
Parsing strings is somewhat relaxed since punctuation characters are interchangeable as long as their positions are correct.
Note that new/1
tries to parse binaries as IP prefixes first and would turn
an EUI-64 using ":" for punctuation into an IPv6 address. Similarly, a
8-element tuple is seen as IPv6 address. Hence, if you really need to parse
EUI-64 binaries with ":", or have EUI-48/64 tuples, use this function.
from_mac/1
also accepts a Pfx
struct, but only if its maxlen is either
48
or 64
. If not, an ArgumentError
is raised.
Examples
iex> from_mac("11:22:33:44:55:66")
%Pfx{bits: <<0x11, 0x22, 0x33, 0x44, 0x55, 0x66>>, maxlen: 48}
iex> from_mac("11-22-33-44-55-66")
%Pfx{bits: <<0x11, 0x22, 0x33, 0x44, 0x55, 0x66>>, maxlen: 48}
iex> from_mac("1122.3344.5566")
%Pfx{bits: <<0x11, 0x22, 0x33, 0x44, 0x55, 0x66>>, maxlen: 48}
iex> from_mac({0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
%Pfx{bits: <<0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff>>, maxlen: 48}
# keep the OUI
iex> from_mac({{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, 24})
%Pfx{bits: <<0xaa, 0xbb, 0xcc>>, maxlen: 48}
iex> from_mac("11:22:33:44:55:66/24")
%Pfx{bits: <<0x11, 0x22, 0x33>>, maxlen: 48}
# a EUI-64
iex> from_mac("11-22-33-44-55-66-77-88")
%Pfx{bits: <<0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88>>, maxlen: 64}
iex> from_mac("11:22:33:44:55:66:77:88")
%Pfx{bits: <<0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88>>, maxlen: 64}
iex> from_mac("11:22:33:44:55:66:77:88/24")
%Pfx{bits: <<0x11, 0x22, 0x33>>, maxlen: 64}
iex> from_mac({0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88})
%Pfx{bits: <<0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88>>, maxlen: 64}
iex> from_mac({{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}, 24})
%Pfx{bits: <<0x11, 0x22, 0x33>>, maxlen: 64}
# note: from_mac reads nibbles so each address element must be 2 nibbles (!)
iex> from_mac("01:02:03:04:05:06:07:08")
%Pfx{bits: <<0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8>>, maxlen: 64}
# mix and match
# ":" and "-" are interchangeable
iex> from_mac("11:22-33:44-55:66")
%Pfx{bits: <<0x11, 0x22, 0x33, 0x44, 0x55, 0x66>>, maxlen: 48}
Specs
Returns the nth
full length prefix in given pfx
.
Note that offset nth
wraps around. See Pfx.member/2
.
Examples
iex> host("10.10.10.0/24", 128)
"10.10.10.128"
iex> host({{10, 10, 10, 0}, 24}, 128)
{{10, 10, 10, 128}, 32}
iex> host(%Pfx{bits: <<10, 10, 10>>, maxlen: 32}, 128)
%Pfx{bits: <<10, 10, 10, 128>>, maxlen: 32}
# wraps around
iex> host("10.10.10.0/24", 256)
"10.10.10.0"
Specs
Returns a list of address prefixes for given pfx
.
Examples
iex> hosts("10.10.10.0/30")
[
"10.10.10.0",
"10.10.10.1",
"10.10.10.2",
"10.10.10.3"
]
iex> hosts({{10, 10, 10, 0}, 30})
[
{{10, 10, 10, 0}, 32},
{{10, 10, 10, 1}, 32},
{{10, 10, 10, 2}, 32},
{{10, 10, 10, 3}, 32}
]
iex> hosts(%Pfx{bits: <<10, 10, 10, 0::6>>, maxlen: 32})
[
%Pfx{bits: <<10, 10, 10, 0>>, maxlen: 32},
%Pfx{bits: <<10, 10, 10, 1>>, maxlen: 32},
%Pfx{bits: <<10, 10, 10, 2>>, maxlen: 32},
%Pfx{bits: <<10, 10, 10, 3>>, maxlen: 32}
]
Specs
Inserts bits
into pfx
-s bitstring, starting at position
.
The resulting bitstring is silently clipped to the pfx.maxlen
.
Valid bit positions are bits_size(pfx.bits) .. min(pfx.maxlen-1, bit_size(pfx.bits))
. A position of 0
will prepend the bits
, while a
position of bit_size(pfx.bits)
will append the bits
as long as the prefix
is not a full length prefix already.
A negative position is taken relative to the end. Note that this cannot
be used for appending bits, since -1
refers to the last actual bit and
there is no such thing as -0
..
Examples
# prepend bits
iex> insert("0.0.0.0/0", <<255>>, 0)
"255.0.0.0/8"
# append bits
iex> insert("255.255.0.0/16", <<255>>, 16)
"255.255.255.0/24"
# cannot append to a full prefix, positions go from 0..31
iex> insert("1.2.3.4", <<255>>, 32)
** (ArgumentError) invalid bit position, got 32
# but inserting inside the bitstring, is ok
iex> insert("1.2.3.4", <<255>>, 16)
"1.2.255.3"
# turn EUI48 into a modified EUI64
iex> new("0088.8888.8888")
...> |> new(64)
...> |> flip(6)
...> |> insert(<<0xFF, 0xFE>>, 24)
%Pfx{bits: <<0x02, 0x88, 0x88, 0xFF, 0xFE, 0x88, 0x88, 0x88>>, maxlen: 64}
# silently clips to pfx's maxlen
iex> insert("1.2.3.0/24", <<255, 255, 255, 255>>, 0)
"255.255.255.255"
iex> insert("1.2.3.0/24", <<255, 255, 255, 255>>, 24)
"1.2.3.255"
Specs
Returns the inverted mask for given pfx
.
The result is always a full length prefix.
Examples
iex> inv_mask("10.10.10.0/25")
"0.0.0.127"
iex> inv_mask({10, 10, 10, 0})
{0, 0, 0, 0}
iex> inv_mask({{10, 10, 10, 0}, 25})
{{0, 0, 0, 127}, 32}
iex> inv_mask(%Pfx{bits: <<10, 10, 10, 0::1>>, maxlen: 32})
%Pfx{bits: <<0, 0, 0, 127>>, maxlen: 32}
Inverts a prefix, bit by bit, same as Pfx.bnot/1
.
This function inverts all bits in the prefix by simply calling Pfx.bnot/1
,
whose name is somewhat obscure. See Pfx.bnot/1
for documentation.
Note: using Pfx.bnot/1
directly, actually saves a function call.
Specs
keep(prefix(), non_neg_integer()) :: prefix()
Keeps count
msb bits of given pfx
.
If count
exceeds the actual number of bits in pfx.bits
, it keeps all
bits.
Examples
iex> keep("2001:0db8:85a3:0000:0000:8a2e:0370:7334", 64)
"2001:db8:85a3::/64"
iex> keep("1.2.3.0/31", 30)
"1.2.3.0/30"
iex> keep("1.2.3.2/31", 30)
"1.2.3.0/30"
iex> keep("1.2.3.128/25", 24)
"1.2.3.0/24"
iex> keep("1.2.3.0/24", 512)
"1.2.3.0/24"
iex> keep({1, 2, 3, 4}, 24)
{1, 2, 3, 0}
iex> keep({{1, 2, 3, 4}, 32}, 16)
{{1, 2, 0, 0}, 16}
iex> keep(%Pfx{bits: <<1, 2, 3, 4>>, maxlen: 32}, 16)
%Pfx{bits: <<1, 2>>, maxlen: 32}
Specs
Returns the last full length prefix from the set represented by pfx
.
Examples
iex> last("10.10.0.0/16")
"10.10.255.255"
# a full address is its own last address
iex> last({10, 10, 10, 1})
{10, 10, 10, 1}
iex> last({{10, 10, 10, 1}, 30})
{{10, 10, 10, 3}, 32}
iex> last(%Pfx{bits: <<10, 10, 10>>, maxlen: 32})
%Pfx{bits: <<10, 10, 10, 255>>, maxlen: 32}
iex> last(%Pfx{bits: <<0xacdc::16, 0x1976::16>>, maxlen: 128})
%Pfx{bits: <<0xACDC::16, 0x1976::16, -1::96>>, maxlen: 128}
iex> last("acdc:1976::/112")
"acdc:1976::ffff"
Specs
Returns a representation of pfx
(a Pfx.t/0
-struct) in the form of original
.
The exact original is not required, the pfx
is transformed by the shape of
the original
argument: string vs two-element tuple vs tuple. If none of
the three shapes match, the pfx
is returned unchanged.
This is used to allow results to be the same shape as their (first) argument
that needed to turn into a Pfx.t/0
for some calculation.
Note that when turning a prefix into a address-tuple, an address-tuple comes out
which is the first full length prefix in the set represented by pfx
.
Examples
# original is a string
iex> marshall(%Pfx{bits: <<1, 1, 1>>, maxlen: 32}, "any string really")
"1.1.1.0/24"
# original is any two-element tuple
iex> marshall(%Pfx{bits: <<1, 1, 1>>, maxlen: 32}, {0,0})
{{1, 1, 1, 0}, 24}
# original is any other tuple, actually turns prefix into this-network address
iex> marshall(%Pfx{bits: <<1, 1, 1>>, maxlen: 32}, {})
{1, 1, 1, 0}
# original is a Pfx struct
iex> marshall(%Pfx{bits: <<1, 1, 1>>, maxlen: 32}, %Pfx{bits: <<>>, maxlen: 0})
%Pfx{bits: <<1, 1, 1>>, maxlen: 32}
iex> marshall(new("1.1.1.1"), {})
{1, 1, 1, 1}
Specs
Returns the mask for given pfx
.
The result is always a full length prefix.
Examples
iex> mask("10.10.10.0/25")
"255.255.255.128"
iex> mask({10, 10, 10, 0})
{255, 255, 255, 255}
iex> mask({{10, 10, 10, 0}, 25})
{{255, 255, 255, 128}, 32}
iex> mask("acdc:1976::/32")
"ffff:ffff::"
# some prefix with some other maxlen
iex> mask(%Pfx{bits: <<10, 10, 0::1>>, maxlen: 20})
%Pfx{bits: <<255, 255, 8::4>>, maxlen: 20}
Specs
Applies mask
to given prefix
.
The mask
is applied through a bitwise AND after which the result is trimmed
to the size of mask
. Both prefix
and mask
do not need to be full
length prefixes.
Options include:
:inv_mask
, iftrue
themask
is inverted before applying it (default:false
):trim
, iffalse
the result is not trimmed to the size ofmask
(default:true
)
Note that both prefix
and mask
must be of the same type (same maxlen).
Examples
# trims result by default
iex> mask("1.1.1.1", "255.255.255.0")
"1.1.1.0/24"
iex> mask("1.1.1.1", "255.255.255.0") |> first()
"1.1.1.0"
# same as above
iex> mask("1.1.1.1", "255.255.255.0", trim: false)
"1.1.1.0"
# mirror representation
iex> mask({{1, 1, 1, 1}, 32}, {255, 255, 255, 0})
{{1, 1, 1, 0}, 24}
iex> mask("1.1.1.1", "255.0.0.255")
"1.0.0.1"
# mask need not be full length prefix
iex> mask("1.1.1.1", "255.255.0.0/16")
"1.1.0.0/16"
iex> mask("1.1.1.1", "255.255.0.0/16", trim: false)
"1.1.0.0"
# neither does prefix
iex> mask("1.1.1.0/24", "255.255.0.0/16")
"1.1.0.0/16"
# no trim, so prefix length stays 24 bits
iex> mask("1.1.1.0/24", "255.255.0.0/16", trim: false)
"1.1.0.0/24"
# inverted mask
iex> mask("10.16.0.0", "0.3.255.255", inv_mask: true)
...> |> (fn x -> {x, first(x), last(x)} end).()
{"10.16.0.0/14", "10.16.0.0", "10.19.255.255"}
Specs
Returns the nth
-member of a given pfx
.
A prefix represents a range of (possibly longer) prefixes which can be
seen as members of the prefix. So a prefix of n
-bits long represents:
- 1 prefix of
n
-bits long (i.e. itself), - 2 prefixes of
n+1
-bits long, - 4 prefixes of
n+2
-bits long - ..
- 2^w prefixes of
n+w
-bits long
where n+w
<= pfx.maxlen
.
Not specifying a width
assumes the maximum width available. If a width
is specified, the nth
-offset is added to the prefix as a number
width
-bits wide. This wraps around the available address space.
Examples
iex> member("10.10.10.0/24", 255)
"10.10.10.255"
# wraps around
iex> member("10.10.10.0/24", 256)
"10.10.10.0"
iex> member({{10, 10, 10, 0}, 24}, 255)
{{10, 10, 10, 255}, 32}
iex> member(%Pfx{bits: <<10, 10, 10>>, maxlen: 32}, 0)
%Pfx{bits: <<10, 10, 10, 0>>, maxlen: 32}
iex> member(%Pfx{bits: <<10, 10, 10>>, maxlen: 32}, 255)
%Pfx{bits: <<10, 10, 10, 255>>, maxlen: 32}
# wraps around
iex> member(%Pfx{bits: <<10, 10, 10>>, maxlen: 32}, 256)
%Pfx{bits: <<10, 10, 10, 0>>, maxlen: 32}
iex> member(%Pfx{bits: <<10, 10, 10>>, maxlen: 32}, -1)
%Pfx{bits: <<10, 10, 10, 255>>, maxlen: 32}
# a full prefix always returns itself
iex> member(%Pfx{bits: <<10, 10, 10, 10>>, maxlen: 32}, 0)
%Pfx{bits: <<10, 10, 10, 10>>, maxlen: 32}
iex> member(%Pfx{bits: <<10, 10, 10, 10>>, maxlen: 32}, 3)
%Pfx{bits: <<10, 10, 10, 10>>, maxlen: 32}
Specs
member(prefix(), integer(), pos_integer()) :: prefix()
Returns the nth
member in the set represented by pfx
, using width
bits.
Examples
iex> member("10.10.10.0/24", 1, 2)
"10.10.10.64/26"
iex> member("10.10.10.0/24", 2, 2)
"10.10.10.128/26"
iex> member({{10, 10, 10, 0}, 24}, 2, 2)
{{10, 10, 10, 128}, 26}
# the first member that is 2 bits longer
iex> member(%Pfx{bits: <<10, 10, 10>>, maxlen: 32}, 0, 2)
%Pfx{bits: <<10, 10, 10, 0::2>>, maxlen: 32}
# the second member that is 2 bits longer
iex> member(%Pfx{bits: <<10, 10, 10>>, maxlen: 32}, 1, 2)
%Pfx{bits: <<10, 10, 10, 1::2>>, maxlen: 32}
Specs
Returns true is prefix pfx1
is a member of prefix pfx2
If either prfx1
or pfx2
is invalid or they are of different types,
member? simply returns false.
Examples
iex> member?("10.10.10.10", "10.0.0.0/8")
true
iex> member?({10, 10, 10, 10}, "10.0.0.0/8")
true
iex> member?({{10, 10, 10, 10}, 24}, "10.0.0.0/8")
true
iex> member?({{11, 0, 0, 0}, 8}, {{10, 0, 0, 0}, 8})
false
iex> member?(%Pfx{bits: <<10, 10, 10, 10>>, maxlen: 32}, %Pfx{bits: <<10>>, maxlen: 32})
true
# bad prefix
iex> member?("10.10.10.10", "10.10.10.256/24")
false
# different types
iex> member?("10.10.10.10", "acdc::/32")
false
Specs
Returns a minimized list of prefixes covering the same address space.
The list of, possibly mixed types of prefixes, is first grouped by their maxlen
property,
then each sublist is minimized by:
- sorting such that, possibly, adjacent/overlapping prefixes are next to each other
- recursively combine neighboring prefixes into their parent prefix
The prefixes can be any format understood by Pfx.new/1
or be straight up
Pfx.t/0
structs, and the result mimics the format of the first prefix.
Notes:
- to use the list as an acl, apply
Enum.sort({:desc, Pfx})
afterwards - that uses
Pfx.compare/2
, so when using mixed prefix types, group_by maxlen first and sort the sub-lists
Examples
iex> acl = ["1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4", "1.1.1.5", "1.1.1.6/31"]
iex> minimize(acl) |> Enum.sort({:desc, Pfx})
["1.1.1.4/30", "1.1.1.2/31", "1.1.1.1"]
# list of 255 hosts (ip 1.1.1.128 is excluded)
iex> hosts = for ip <- hosts("1.1.1.0/24"), ip != "1.1.1.128", do: ip
iex> Enum.member?(hosts, "1.1.1.128")
false
iex> Enum.count(hosts)
255
iex> acl = minimize(hosts)
iex> Enum.sort(acl, {:desc, Pfx})
[
"1.1.1.0/25",
"1.1.1.192/26",
"1.1.1.160/27",
"1.1.1.144/28",
"1.1.1.136/29",
"1.1.1.132/30",
"1.1.1.130/31",
"1.1.1.129"
]
#
# reverse back to list of hosts
#
iex> acl_hosts = (for pfx <- acl, do: hosts(pfx)) |> List.flatten()
iex> Enum.count(acl_hosts)
255
iex> Enum.member?(acl_hosts, "1.1.1.128")
false
# minimize list of different types of prefixes
iex> list = ["1.1.0.0/24", "1.1.1.0/24", "acdc:0::/17", "acdc:8000::/17"]
iex> minimize(list)
["acdc::/16", "1.1.0.0/23"]
# mimics format of first prefix in the list
iex> minimize([{1, 2, 3, 4}, {{1, 2, 3, 0}, 25}, "1.2.3.128/26", "1.2.3.192/26"])
[{{1, 2, 3, 0}, 24}]
# mixed prefixes
iex> ["10.10.10.0/24", "10.10.11.0/24", "100.100.100.0/25", "100.100.100.128/25", "acdc::1"]
...> |> minimize()
["acdc::1", "100.100.100.0/24", "10.10.10.0/23"]
# minimize & sort mixed prefixes more to less specific (per type)
iex> ["10.10.10.0/24", "10.10.11.0/24", "100.100.100.0/25", "100.100.100.128/25", "acdc::1"]
...> |> minimize()
...> |> Enum.sort({:desc, Pfx})
["acdc::1", "10.10.10.0/23", "100.100.100.0/24"]
# to avoid excessive conversions due to mimicking, do:
iex> ["10.10.10.0/24", "10.10.11.0/24", "100.100.100.0/25", "100.100.100.128/25", "acdc::1"]
...> |> Enum.map(fn pfx -> new(pfx) end)
...> |> minimize()
...> |> Enum.sort({:desc, Pfx})
...> |> Enum.map(&format/1)
["acdc::1", "10.10.10.0/23", "100.100.100.0/24"]
Specs
Returns the neighboring prefix for pfx
, such that both can be combined in a
supernet.
Examples
iex> neighbor("1.1.1.128/25")
"1.1.1.0/25"
iex> neighbor("1.1.1.0/25")
"1.1.1.128/25"
iex> neighbor({1, 1, 1, 1})
{1, 1, 1, 0}
iex> neighbor({{1, 1, 1, 128}, 25})
{{1, 1, 1, 0}, 25}
iex> neighbor(%Pfx{bits: <<1, 1, 1, 1::1>>, maxlen: 32})
%Pfx{bits: <<1, 1, 1, 0::1>>, maxlen: 32}
Specs
Creates a new prefix from address tuples/binaries or raises ArgumentError
.
Use:
- a binary in CIDR-notation,
- a binary in EUI-48 or EUI-64 format (EUI-64 must be using hyphens !)
- an {
ip_address/0
,length
}-tuple to truncate the bits tolength
. - an ipv4 or ipv6
ip_address/0
tuple directly for a full address, or - a
Pfx.t/0
struct to create a new Pfx struct.
To avoid a possible ArgumentError
, use Pfx.parse/1
or Pfx.parse/2
instead.
To avoid applying the mask, use Pfx.address/1
then apply Pfx.new/1
if needed.
Binaries are processed by :inet.parse_address/1
, so be aware of IPv4 shorthand
notations that may yield surprising results, since digits are taken to be:
d1.d2.d3.d4
->d1.d2.d3.d4
(full address)d1.d2.d3
->d1.d2.0.d3
d1.d2
->d1.0.0.d2
d1
->0.0.0.d1
If :inet.parse_address/1
fails to create an IPv4 or IPv6 address, an
attempt is made to parse the binary as an EUI-48 or EUI-64 MAC address.
Parsing EUI's is somewhat relaxed, punctuation chars "-", ":", "." are
interchangeable, but their positions should be correct.
Note that EUI-64's that use ":"-punctuation are indistinguishable from IPv6,
e.g. "11:22:33:44:55:66:77:88". Use from_mac/1
when in doubt about
punctuations used while parsing MAC addresses.
Examples
# from CIDR strings
iex> new("10.10.0.0")
%Pfx{bits: <<10, 10, 0, 0>>, maxlen: 32}
iex> new("10.10.10.10/16")
%Pfx{bits: <<10, 10>>, maxlen: 32}
# ipv6 string
iex> new("acdc:1976::/32")
%Pfx{bits: <<0xacdc::16, 0x1976::16>>, maxlen: 128}
# from an {address-tuple, length}
iex> new({{0xacdc, 0x1976, 0, 0, 0, 0, 0, 0}, 32})
%Pfx{bits: <<0xacdc::16, 0x1976::16>>, maxlen: 128}
iex> new({{10, 10, 0, 0}, 16})
%Pfx{bits: <<10, 10>>, maxlen: 32}
# from an address-tuple
iex> new({10, 10, 0, 0})
%Pfx{bits: <<10, 10, 0, 0>>, maxlen: 32}
# from a struct
iex> new(%Pfx{bits: <<10, 10>>, maxlen: 32})
%Pfx{bits: <<10, 10>>, maxlen: 32}
# 10.10/16 is interpreted as 10.0.0.10/16 (!)
iex> new("10.10/16")
%Pfx{bits: <<10, 0>>, maxlen: 32}
# some EUI-48's
iex> new("aa:bb:cc:dd:ee:ff")
%Pfx{bits: <<0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff>>, maxlen: 48}
iex> new("aa-bb-cc-dd-ee-ff")
%Pfx{bits: <<0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff>>, maxlen: 48}
iex> new("aabb.ccdd.eeff")
%Pfx{bits: <<0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff>>, maxlen: 48}
# keep only OUI
iex> new("aa-bb-cc-dd-ee-ff/24")
%Pfx{bits: <<0xaa, 0xbb, 0xcc>>, maxlen: 48}
# some EUI-64's
iex> new("11-22-33-44-55-66-77-88")
%Pfx{bits: <<0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88>>, maxlen: 64}
iex> new("1122.3344.5566.7788")
%Pfx{bits: <<0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88>>, maxlen: 64}
# but note the maxlen here ...
iex> new("11:22:33:44:55:66:77:88")
%Pfx{bits: <<0x11::16, 0x22::16, 0x33::16, 0x44::16, 0x55::16, 0x66::16, 0x77::16, 0x88::16>>, maxlen: 128}
iex> try do
...> new("1.1.1.256")
...> rescue
...> x -> Exception.message(x)
...> end
"expected an ipv4/ipv6 CIDR or EUI-48/64 string, got \"1.1.1.256\""
Specs
new(t() | bitstring(), non_neg_integer()) :: t()
Creates a new Pfx.t/0
-prefix.
Create a new prefix from:
- from a bitstring and a maximum length, truncating the bits as needed,
- from a
Pfx.t/0
prefix and a new maxlen, again truncating as needed,
Examples
iex> new(<<10, 10>>, 32)
%Pfx{bits: <<10, 10>>, maxlen: 32}
iex> new(<<10, 10>>, 8)
%Pfx{bits: <<10>>, maxlen: 8}
# changing 'maxlen' usually changes the prefix' meaning
iex> new(%Pfx{bits: <<10, 10>>, maxlen: 32}, 128)
%Pfx{bits: <<10, 10>>, maxlen: 128}
Specs
Pads the bits in pfx
on the left to its full length using 0
-bits.
Examples
iex> padl("1.2.0.0/16")
"0.0.1.2"
iex> padl({{1, 2, 0, 0}, 16})
{{0, 0, 1, 2}, 32}
iex> padl(%Pfx{bits: <<1, 2>>, maxlen: 32})
%Pfx{bits: <<0, 0, 1, 2>>, maxlen: 32}
Specs
Left pad the pfx.bits
to its full length using either 0
or 1
-bits.
Examples
iex> padl("1.2.0.0/16", 1)
"255.255.1.2"
iex> padl({{1, 2, 0, 0}, 16}, 1)
{{255, 255, 1, 2}, 32}
iex> padl(%Pfx{bits: <<1, 2>>, maxlen: 32}, 1)
%Pfx{bits: <<255, 255, 1, 2>>, maxlen: 32}
Specs
padl(prefix(), 0 | 1, non_neg_integer()) :: prefix()
Pads the bits in pfx
on the Left with n
bits of either 0
or 1
's.
Examples
iex> padl("255.255.0.0/16", 0, 16)
"0.0.255.255"
iex> padl("255.255.0.0/16", 1, 16)
"255.255.255.255"
iex> padl({{255, 255, 0, 0}, 16}, 0, 16)
{{0, 0, 255, 255}, 32}
iex> padl(%Pfx{bits: <<255, 255>>, maxlen: 32}, 0, 16)
%Pfx{bits: <<0, 0, 255, 255>>, maxlen: 32}
Specs
Pads the bits in pfx
on the right to its full length using 0
-bits.
The result is always a full prefix with maxlen
bits.
Examples
# already a full address
iex> padr("1.2.3.4")
"1.2.3.4"
# mask applied first, then padded with zero's
iex> padr("1.2.3.4/16")
"1.2.0.0"
# mask applied first, than padded with zero's
iex> padr({{1, 2, 0, 0}, 16})
{{1, 2, 0, 0}, 32}
iex> padr(%Pfx{bits: <<1, 2>>, maxlen: 32})
%Pfx{bits: <<1, 2, 0, 0>>, maxlen: 32}
Specs
Pads the bits in pfx
on the right to its full length using either 0
or
1
-bits.
Examples
iex> padr("1.2.0.0/16", 1)
"1.2.255.255"
iex> padr({{1, 2, 0, 0}, 16}, 1)
{{1, 2, 255, 255}, 32}
# nothing to padr, already a full prefix
iex> padr("1.2.0.0", 1)
"1.2.0.0"
iex> padr(%Pfx{bits: <<1, 2>>, maxlen: 32}, 1)
%Pfx{bits: <<1, 2, 255, 255>>, maxlen: 32}
Specs
padr(prefix(), 0 | 1, non_neg_integer()) :: prefix()
Pads the bits in pfx
on the right with n
bits of either 0
or 1
's.
The result is clipped at maxlen
bits without warning.
Examples
# expand a /16 to a /24
iex> padr("255.255.0.0/16", 0, 8)
"255.255.0.0/24"
iex> padr("255.255.0.0/16", 1, 8)
"255.255.255.0/24"
iex> padr({{255, 255, 0, 0}, 16}, 1, 8)
{{255, 255, 255, 0}, 24}
# results are clipped to maxlen
iex> padr("1.2.0.0/16", 1, 512)
"1.2.255.255"
iex> padr(%Pfx{bits: <<255, 255>>, maxlen: 32}, 0, 8)
%Pfx{bits: <<255, 255, 0>>, maxlen: 32}
iex> padr(%Pfx{bits: <<255, 255>>, maxlen: 32}, 1, 8)
%Pfx{bits: <<255, 255, 255>>, maxlen: 32}
Specs
Parses a prefix/0
and returns {:ok, Pfx.t}
or {:error, :einvalid}
Examples
iex> parse("1.2.3.4/24")
{:ok, %Pfx{bits: <<1, 2, 3>>, maxlen: 32}}
iex> parse({{1,2,3,4}, 24})
{:ok, %Pfx{bits: <<1, 2, 3>>, maxlen: 32}}
iex> parse({1,2,3,4})
{:ok, %Pfx{bits: <<1, 2, 3, 4>>, maxlen: 32}}
iex> parse(%Pfx{bits: <<4, 3, 2, 1>>, maxlen: 32})
{:ok, %Pfx{bits: <<4, 3, 2, 1>>, maxlen: 32}}
iex> parse("1.2.3.4/33")
{:error, :einvalid}
iex> parse("acdc:1976::/32")
{:ok, %Pfx{bits: <<0xACDC::16, 0x1976::16>>, maxlen: 128}}
iex> parse("DEAD:BEER::/32")
{:error, :einvalid}
Specs
Parses a prefix/0
and returns {:ok, Pfx.t}
or given default
on error.
Same as Pfx.parse/1
, but returns given default on error.
Examples
iex> parse("0.0.0.0/32", :oops)
{:ok, %Pfx{bits: <<0, 0, 0, 0>>, maxlen: 32}}
iex> parse("0.0.0.0/33", :oops)
:oops
iex> pfx = "0.0.0.256/24"
iex> parse(pfx, {:error, pfx})
{:error, "0.0.0.256/24"}
iex> parse("11:22:33:44:55:GG", {:error, :bad_eui48})
{:error, :bad_eui48}
Specs
partition(prefix(), non_neg_integer()) :: [prefix()]
Partitions pfx
into a list of new prefixes, each bitlen
long.
Note that bitlen
must be in the range of bit_size(pfx.bits)..pfx.maxlen-1
.
Examples
# break out the /26's in a /24
iex> partition("10.11.12.0/24", 26)
[
"10.11.12.0/26",
"10.11.12.64/26",
"10.11.12.128/26",
"10.11.12.192/26"
]
iex> partition({{10, 11, 12, 0}, 24}, 26)
[
{{10, 11, 12, 0}, 26},
{{10, 11, 12, 64}, 26},
{{10, 11, 12, 128}, 26},
{{10, 11, 12, 192}, 26},
]
iex> partition(%Pfx{bits: <<10, 11, 12>>, maxlen: 32}, 26)
[
%Pfx{bits: <<10, 11, 12, 0::size(2)>>, maxlen: 32},
%Pfx{bits: <<10, 11, 12, 1::size(2)>>, maxlen: 32},
%Pfx{bits: <<10, 11, 12, 2::size(2)>>, maxlen: 32},
%Pfx{bits: <<10, 11, 12, 3::size(2)>>, maxlen: 32}
]
Specs
Returns a list of prefixes that cover the given range of address space.
The (inclusive) range can be specified either by:
start
,stop
prefixes, orstart
,nhosts
When using the start
,stop
-prefixes as a range, both prefixes need to be
of the same type, otherwise an argument error is raised. The second form of
start
, nhosts
requires that nhosts
does not exceed the addressable capacity
of given start
prefix's type.
If start
lies to the right of stop
, they are not reversed and the build
up of the list of prefixes will wrap around the available address space.
If nhosts
is negative, start
is effectively the last address.
Note: any mask information for start
or stop
prefixes are ignored, if
possible.
Examples
iex> partition_range("10.10.10.0", "10.10.10.130")
["10.10.10.0/25", "10.10.10.128/31", "10.10.10.130"]
iex> partition_range("10.10.10.0", 131)
["10.10.10.0/25", "10.10.10.128/31", "10.10.10.130"]
iex> partition_range("10.10.10.0", 131)
...> |> Enum.map(&size/1) |> Enum.sum()
131
iex(481)> partition_range("acdc::1976", "acdc::2021")
["acdc::1976/127", "acdc::1978/125",
"acdc::1980/121", "acdc::1a00/119",
"acdc::1c00/118", "acdc::2000/123",
"acdc::2020/127"]
When working with address tuples, the result will be in address,length-tuples otherwise prefix length information would be lost.
# 128 hosts, starting with "10.10.10.128"
iex> partition_range({10, 10, 10, 128}, 128)
[{{10, 10, 10, 128}, 25}]
# 1 host, starting with "10.10.10.10"
iex> partition_range({10, 10, 10, 10}, 1)
[{{10, 10, 10, 10}, 32}]
# 0 hosts always yield an empty list
iex> partition_range({10, 10, 10, 10}, 0)
[]
iex> partition_range("10.10.10.10", 0)
[]
# binary format may not show the /len, if it is a full prefix
iex> partition_range("10.10.10.10", 1)
["10.10.10.10"]
The range is inclusive, so both start
and stop
are always included. In fact,
start
is the first address of the first prefix in the list and stop
the last
address in the last prefix.
iex> partition_range("10.10.10.10", "10.10.10.10")
["10.10.10.10"]
iex> partition_range("10.10.10.0", 512)
["10.10.10.0/23"]
iex> partition_range("10.10.10.0", 131)
["10.10.10.0/25", "10.10.10.128/31", "10.10.10.130"]
# negative number means start is actually the last address in the range
iex> partition_range("10.10.10.130", -131)
["10.10.10.0/25", "10.10.10.128/31", "10.10.10.130"]
Any mask information, if present, is ignored for both start
and stop
. Note that
when using Pfx.t/0
structs, the mask will already have been applied in which case
the address will be the first address in the prefix.
iex> partition_range("10.10.10.8/24", 10)
["10.10.10.8/29", "10.10.10.16/31"]
iex> partition_range("10.10.10.10/24", "10.10.10.17/24")
["10.10.10.10/31", "10.10.10.12/30", "10.10.10.16/31"]
# new() has already applied the mask
iex> start = new("10.10.10.10/24")
iex> stop = new("10.10.10.17/24")
iex> partition_range(start, stop)
[%Pfx{bits: <<10, 10, 10, 0>>, maxlen: 32}]
# so the above is basically the same as:
iex> partition_range("10.10.10.0", "10.10.10.0")
["10.10.10.0"]
The list of prefixes is built up starting with start
and prefixes are added
until it represents the entire range, wrapping the available address space if
needed. So if start
actually lies to the right of stop
, or if nhosts
is large enough, wrapping may occur. To avoid wrapping use Pfx.compare/2
to check whether or not to swap start
and stop
.
# happily wraps around address space boundary (so beware)
iex> partition_range("0.0.0.0", -257)
["255.255.255.0/24", "0.0.0.0"]
iex> partition_range("255.255.255.0", 257)
["255.255.255.0/24", "0.0.0.0"]
iex> partition_range("255.255.255.255", "0.0.0.255")
["255.255.255.255", "0.0.0.0/24"]
iex> start = "10.10.255.255"
iex> stop = "10.10.0.0"
iex> case compare(start, stop) do
...> :gt -> partition_range(stop, start)
...> _ -> partition_range(start, stop)
...> end
["10.10.0.0/16"]
Finally, the list of prefixes will have varying prefix lengths that ramp up and down. So, when building some sort of access control list, sorting on prefix lengths (ascending, so less specifics come first) may be preferable.
iex> partition_range("10.10.10.10", "10.10.10.31")
["10.10.10.10/31", "10.10.10.12/30", "10.10.10.16/28"]
iex> partition_range("10.10.10.10", "10.10.10.31")
...> |> Enum.sort(&(pfxlen(&1) <= pfxlen(&2)))
["10.10.10.16/28", "10.10.10.12/30", "10.10.10.10/31"]
# the above actually converts between string and `t:Pfx.t/0` twice,
# to avoid that do something like this:
iex> partition_range(new("10.10.10.10"), new("10.10.10.31"))
...> |> Enum.sort(&(pfxlen(&1) <= pfxlen(&2)))
...> |> Enum.map(&format/1)
["10.10.10.16/28", "10.10.10.12/30", "10.10.10.10/31"]
Specs
pfxlen(prefix()) :: non_neg_integer()
Returns the length of the bitstring for given prefix
.
Examples
iex> pfxlen("10.10.10.0/24")
24
iex> pfxlen({{10, 10, 10, 0}, 25})
25
iex> pfxlen(%Pfx{bits: <<10, 10, 10, 0::1>>, maxlen: 32})
25
iex> pfxlen("10.10.10.10")
32
Specs
remove(prefix(), non_neg_integer(), non_neg_integer()) :: prefix()
Removes length
bits from pfx
-s bitstring, starting at position
.
A negative position
is relative to the end of the pfx.bits
-string.
Valid range for position
is -bit_size(pfx.bits) .. bit_size(pfx.bits)-1
.
If length
is positive, bits are removed to the right. If it is negative
bits are removed going to the left.
Notes:
length
is silently clipped to the maximum number of bits available to remove- removing bits from
pfx.bits
does not change itspfx.maxlen
Examples
# remove 2nd digit (2)
iex> remove("1.2.3.4", 8, 8)
"1.3.4.0/24"
# remove 25th bit
iex> remove("1.2.3.128/25", -1, 1)
"1.2.3.0/24"
# remove the FF.FE part
iex> remove("0288.88FF.FE88.8888", 24, 16)
"02-88-88-88-88-88-00-00/48"
# remove 2nd digit (2)
iex> remove({{1, 2, 3, 4}, 32}, 8, 8)
{{1, 3, 4, 0}, 24}
Specs
Returns another Pfx
at distance offset
from given pfx
.
This basically increases or decreases the number represented by the
pfx.bits
while keeping pfx.maxlen
the same.
Note that the length of pfx.bits
will not change and cycling through
all siblings will eventually wrap around.
Examples
iex> sibling("1.2.3.0/24", -1)
"1.2.2.0/24"
iex> sibling("0.0.0.0", -1)
"255.255.255.255"
iex> sibling({{1, 2, 3, 0}, 24}, 256)
{{1, 3, 3, 0}, 24}
iex> sibling(%Pfx{bits: <<10, 11>>, maxlen: 32}, 1)
%Pfx{bits: <<10, 12>>, maxlen: 32}
iex> sibling(%Pfx{bits: <<10, 11, 0>>, maxlen: 32}, 255)
%Pfx{bits: <<10, 11, 255>>, maxlen: 32}
# wraps around
iex> sibling(%Pfx{bits: <<10, 11, 0>>, maxlen: 32}, 256)
%Pfx{bits: <<10, 12, 0>>, maxlen: 32}
iex> new(<<0, 0, 0, 0>>, 32) |> sibling(-1)
%Pfx{bits: <<255, 255, 255, 255>>, maxlen: 32}
# zero bit-length stays zero bit-length
iex> sibling(%Pfx{bits: <<>>, maxlen: 0}, 1)
%Pfx{bits: <<>>, maxlen: 0}
Specs
Returns either a Pfx struct, a binary or a tuple for the given binary prefix
.
When Pfx is imported, use the sigil ~p
.
This normally returns the same result as Pfx.new/1
, unless modifiers are
used. The following modifiers are supported:
a
returns the address without applying the mask, if anym
returns the mask for givenprefix
f
returns the first full address forprefix
l
returns the last full address forprefix
n
returns the neighbor forprefix
p
returns the direct parent forprefix
(i.e. drops 1 lsb bit)t
returns the trimmed result forprefix
(i.e. drops all trailing zero's)
The modifiers are checked in the order listed and the first one seen gets
applied. The modifiers above can be used in conjunction with the i
modifier, which inverts the result after the regular modifier (if any) has
been applied.
The result's representation can also be modified by:
S
returns the result as a stringT
returns the result as an address,length- or just an address-tuple.
For modifiers that produce a full length prefix (like address, mask, first and last)
the T
modifier returns an address-tuple, otherwise it will be an
address,length-tuple.
When used as a sigil ~p(pfx)
, pfx is given as a string to Pfx.sigil_p/2
and can be:
- an IPv4 prefix string in CIDR notation
- an IPv6 prefix string
- an EUI48 prefix string
- an EUI64 prefix string
Examples
Most examples use the S
modifier for readability.
iex> ~p"1.1.1.1"
%Pfx{bits: <<1, 1, 1, 1>>, maxlen: 32}
# address,length-tuple format
iex> ~p"1.1.1.1"T
{{1, 1, 1, 1}, 32}
iex> ~p"aa-bb-cc-dd-ee-ff"
%Pfx{bits: <<170, 187, 204, 221, 238, 255>>, maxlen: 48}
# address
iex> ~p"1.1.1.1/24"aS
"1.1.1.1"
# address-tuple format
iex> ~p"1.1.1.1/24"aT
{1, 1, 1, 1}
# first address
iex> ~p"1.1.1.1/24"fS
"1.1.1.0"
# last address
iex> ~p"1.1.1.1/24"lS
"1.1.1.255"
# mask
iex> ~p"1.1.1.1/24"mS
"255.255.255.0"
# inverse mask
iex> ~p"1.1.1.1/24"imS
"0.0.0.255"
# just the inverse
iex> ~p"255.255.255.0"iS
"0.0.0.255"
# neighbor (such that it can be combined in a supernet)
iex> ~p"1.1.1.1"nS
"1.1.1.0"
# parent (the supernet containing the prefix given and its neighbor)
iex> ~p"1.1.1.1"pS
"1.1.1.0/31"
# trim an address to get a fitting prefix
iex> ~p"1.1.0.0"tS
"1.1.0.0/16"
# tuple format
iex> ~p"acdc:1976::b1ba/64"T
{{44252, 6518, 0, 0, 0, 0, 0, 0}, 64}
# address-tuple format
iex> ~p"acdc:1976::b1ba/64"aT
{44252, 6518, 0, 0, 0, 0, 0, 45498}
# enumerate a Pfx.t struct
iex> for x <- ~p"1.1.1.0/30", do: "#{x}"
["1.1.1.0", "1.1.1.1", "1.1.1.2", "1.1.1.3"]
iex> ~p"1.1.1.128" in ~p"1.1.1.0/24"
true
Specs
Returns either a Pfx struct, a binary or a tuple for given binary prefixes
.
If mask
is an empty string, both prefix
and modifiers
are simply handed off
to Pfx.sigil_p/2
.
However, if mask
is not an empty string, then it is used to mask the
address portion of prefix1
before handing the result to Pfx.sigil_p/2
along with the modifiers
. Note that, if given, mask
must be the same
type of prefix as prefix
.
This basically allows for piping any prefix representation into ~p()
with an
optional masking twist.
Examples
# address mimics its input format
iex> {{1, 1, 1, 1}, 30}
...> |> address()
{1, 1, 1, 1}
# so, to get a string representation
iex> {{1, 1, 1, 1}, 30}
...> |> address()
...> |> format()
"1.1.1.1"
# or do both in one
iex> {{1, 1, 1, 1}, 30}
...> |> ~p()aS
"1.1.1.1"
# same when modifying the mask
iex> {{1, 1, 1, 1}, 30}
...> |> mask("255.255.255.0")
...> |> format()
"1.1.1.0/24"
# same thing
iex> {{1, 1, 1, 1}, 30}
...> |> ~p(255.255.255.0)S
"1.1.1.0/24"
# count IP's at /24 level
iex> l = ["1.1.1.1", {1, 1, 1, 2}, {{1, 1, 1, 3}, 32}, new("1.1.1.4")]
iex> slash24 = fn x -> x |> ~p(255.255.255.0)S end
iex> incr = fn v -> v + 1 end
iex> Enum.reduce(l, %{}, fn x, acc -> Map.update(acc, slash24.(x), 1, incr) end)
%{"1.1.1.0/24" => 4}
# apply mask, get Pfx struct
iex> "acdc::1" |> ~p(ffff::)
%Pfx{bits: <<172, 220>>, maxlen: 128}
# apply mask, get string representation
iex> "acdc::1" |> ~p(ffff::)S
"acdc::/16"
# apply mask to an EUI48 address
iex> "aa:bb:cc:dd:ee:ff" |> ~p(ff:ff:ff:ff:ff:00)S
"AA-BB-CC-DD-EE-00/40"
Specs
size(prefix()) :: pos_integer()
Returns the number of full addresses as represented by pfx
.
size(pfx) == 2^(pfx.maxlen - bit_size(pfx.bits))
Examples
iex> size("1.1.1.0/23")
512
iex> size({1,1,1,1})
1
iex> size({{1, 1, 1, 0}, 16})
65536
iex> size(%Pfx{bits: <<1, 1, 1>>, maxlen: 32})
256
Specs
Returns a prefix
in the form of a tuple, with or without mask.
The options include:
:mask
whether to apply and include the mask (i.e. prefix length):width
how many bits in an address part (default depends)
The :width
defaults to 8
, except when maxlen is 128, in which case it is 16
.
These defaults are a good fit for IPv4, IPv6, EUI48 and EUI64 prefixes and produces
a tuple representation that can easily be converted back into a Pfx.t/0
struct
if the need arises.
Examples
iex> to_tuple("1.1.1.0/24")
{{1, 1, 1, 0}, 24}
# mask is applied by default
iex> to_tuple("1.1.1.12/24")
{{1, 1, 1, 0}, 24}
# unless :mask option is false
iex> to_tuple("1.1.1.12/24", mask: false)
{1, 1, 1, 12}
# converts other formats of IPv4/6 and EUI48/64 as well
iex> to_tuple({1, 1, 1, 12})
{{1, 1, 1, 12}, 32}
iex> to_tuple(%Pfx{bits: <<1, 1, 1, 12>>, maxlen: 32})
{{1, 1, 1, 12}, 32}
iex> to_tuple("acdc:1976::/32")
{{0xacdc, 0x1976, 0, 0, 0, 0, 0, 0}, 32}
# convert back
iex> to_tuple("acdc:1976::/32")
...> |> new()
%Pfx{bits: <<0xacdc::size(16), 0x1976::size(16)>>, maxlen: 128}
For other types of prefixes, use the :width
option to override how many bits
go into an address part. If the prefix has a maxlen that is not the number
of address digits * their width, maxlen will need to be known when converting
the tuple representation back into a Pfx.t/0
struct (since it cannot be
deduced).
# maxlen is 30 bits, not 8*4
iex> to_tuple(%Pfx{bits: <<0x12, 0x34, 0x56>>, maxlen: 30}, width: 4)
{{1, 2, 3, 4, 5, 6, 0, 0}, 24}
# conversion back into a Pfx struct requires `width` and `maxlen` to be known
iex> pfx = %Pfx{bits: <<0x12, 0x34, 0x56>>, maxlen: 30}
iex> {parts, pfxlen} = to_tuple(pfx, width: 4)
iex> bits = for x <- Tuple.to_list(parts), into: <<>>, do: <<x::size(4)>>
iex> new(bits, 30)
...> |> keep(pfxlen)
%Pfx{bits: <<0x12, 0x34, 0x56>>, maxlen: 30}
# or less convoluted
iex> pfx = %Pfx{bits: <<0x12, 0x34, 0x56>>, maxlen: 30}
iex> to_tuple(pfx, width: 4)
...> |> undigits(4)
...> |> new(30)
%Pfx{bits: <<0x12, 0x34, 0x56>>, maxlen: 30}
Specs
Trims all trailing 0
's from given prefix
.
Examples
iex> trim("255.255.255.0")
"255.255.255.0/24"
# perhaps more visible like this
iex> trim("255.255.255.0")
...> |> new()
%Pfx{bits: <<255, 255, 255>>, maxlen: 32}
iex> trim("10.10.128.0/30")
"10.10.128.0/17"
iex> trim({255, 255, 255, 0})
{255, 255, 255, 0}
iex> trim({{255, 255, 255, 0}, 32})
{{255, 255, 255, 0}, 24}
iex> trim("acdc:1976::ff00/128")
"acdc:1976::ff00/120"
Specs
type(prefix()) :: :ip4 | :ip6 | :eui48 | :eui64 | non_neg_integer() | :einvalid
Returns the prefix type, one of :ip4
, :ip6
, :eui48
, eui64
or simply
its maxlen property.
Examples
iex> type("1.2.3.4")
:ip4
iex> type("1.2.3.0/24")
:ip4
iex> type({1, 2, 3, 4})
:ip4
iex> type({{1, 2, 3, 4}, 24})
:ip4
iex> type(%Pfx{bits: <<1, 2, 3, 4>>, maxlen: 32})
:ip4
iex> type("acdc:1976::1")
:ip6
iex> type({1, 2, 3, 4, 5, 6, 7, 8})
:ip6
iex> type({{1, 2, 3,4 ,5 ,6, 7, 8}, 64})
:ip6
iex> type(%Pfx{bits: <<>>, maxlen: 128})
:ip6
iex> type("aa-bb-cc-dd-ee-ff")
:eui48
iex> type(%Pfx{bits: <<0xaa, 0xbb>>, maxlen: 48})
:eui48
iex> type("aa-bb-cc-ee-ff-00-00-00")
:eui64
iex> type(%Pfx{bits: <<0xaa, 0xbb, 0xcc>>, maxlen: 64})
:eui64
iex> type(%Pfx{bits: <<1, 2>>, maxlen: 256})
256
Specs
undigits( {tuple(), pos_integer()}, pos_integer() ) :: t()
Returns the prefix represented by the digits
, actual length
and a given
field width
.
The pfx.bits
are formed by first concatenating the digits
expressed as
bitstrings of width
-bits wide and then truncating to length
bits.
The pfx.maxlen
is inferred as tuple_size(digits) * width
.
Note: if a digit does not fit in width
-bits, only the width
-least
significant bits are preserved, which may yield surprising results.
Examples
# truncated to the first 24 bits and maxlen is 32 (4*8)
iex> undigits({{10, 11, 12, 0}, 24}, 8)
%Pfx{bits: <<10, 11, 12>>, maxlen: 32}
iex> undigits({{-1, -1, 0, 0}, 32}, 8) |> format()
"255.255.0.0"
# bits are truncated to empty bitstring (`length` is 0)
iex> undigits({{1,2,3,4}, 0}, 8)
%Pfx{bits: <<>>, maxlen: 32}
# 32 4-bit wide numbers turn into an IPv6 prefix, truncated to 32 bits
# and maxlen is set to 32 * 4 = 128
iex> undigits({{1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 32},4)
%Pfx{bits: <<0x12, 0x34, 0x56, 0x78>>, maxlen: 128}
Specs
Returns a boolean indicating whether pfx
is a valid prefix/0
or not.
Examples
iex> valid?("1.2.3.4")
true
iex> valid?("1.2.3.4/8")
true
iex> valid?({1, 2, 3, 4})
true
iex> valid?({{1, 2, 3, 4}, 24})
true
iex> valid?(%Pfx{bits: <<1,2,3,4>>, maxlen: 32})
true
# bits exceed maxlen
iex> valid?(%Pfx{bits: <<1,2,3,4>>, maxlen: 16})
false