Pfx (Pfx v0.1.1) View Source

Functions to make working with prefixes easier, especially IPv4 and IPv6 prefixes.

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 else entirely.

A Pfx struct can be created from:

  1. a bitstring/0 and a non_neg_integer/0 for the maximum length,
  2. a Pfx.ip_address/0,
  3. a Pfx.ip_prefix/0, or
  4. a binary/0 denoting an IP prefix in CIDR-notation.

The first option allows for the creation of any sort of prefix, the latter three yield either an IPv4 or IPv6 prefix.

Several functions, like Pfx.unique_local?/1 are more IP oriented, and are included along with the more generic Pfx functions (like Pfx.cut/3) in order to have one module to rule them all. Functions generally accept all four representations and yield their result in the same fashion, if possible:

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

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:

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. 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, a binary is interpreted as a string in CIDR-notation for some IPv4/IPv6 address or prefix.

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 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.

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 leaving.

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.

Ayway, enough downplay, here are some more examples.

Examples

# IANA's OUI range 00-00-5e-xx-xx-xx
iex> new(<<0x00, 0x00, 0x5e>>, 48)
%Pfx{bits: <<0, 0, 94>>, maxlen: 48}

# IANA's assignment for the VRRP MAC address range 00-00-5e-00-01-{VRID}
iex> vrrp_mac_range = new(<<0x00, 0x00, 0x5e, 0x00, 0x01>>, 48)
%Pfx{bits: <<0, 0, 94, 0, 1>>, maxlen: 48}
iex>
iex> vrrp_mac = new(<<0x00, 0x00, 0x5e, 0x00, 0x01, 0x0f>>, 48)
%Pfx{bits: <<0, 0, 94, 0, 1, 15>>, maxlen: 48}
iex>
iex> member?(vrrp_mac, vrrp_mac_range)
true
iex> cut(vrrp_mac, 47, -8) |> cast()
15

# IPv4 examples
iex> new(<<10, 10, 10>>, 32)
%Pfx{bits: <<10, 10, 10>>, maxlen: 32}

iex> new("10.10.10.0/24")
%Pfx{bits: <<10, 10, 10>>, maxlen: 32}

iex> new({10, 10, 10, 10})
%Pfx{bits: <<10, 10, 10, 10>>, maxlen: 32}

iex> new({{10, 10, 10, 10}, 24})
%Pfx{bits: <<10, 10, 10>>, maxlen: 32}

# IPv6 examples
iex> new(<<44252::16, 6518::16>>, 128)
%Pfx{bits: <<0xACDC::16, 0x1976::16>>, maxlen: 128}

iex> new("acdc:1976::/32")
%Pfx{bits: <<44252::16, 6518::16>>, maxlen: 128}

iex> new({{44252, 6518, 0, 0, 0, 0, 0, 0}, 32})
%Pfx{bits: <<0xACDC::16, 0x1976::16>>, maxlen: 128}

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 MAC address string and
  • maxlen: 128 as an IPv6 CIDR 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.

# a subnet
iex> "#{new(<<10, 11, 12>>, 32)}"
"10.11.12.0/24"

# an address
iex> "#{new(<<10, 11, 12, 13>>, 32)}"
"10.11.12.13"

# an ipv6 prefix
iex> "#{new(<<0xACDC::16, 0x1976::16>>, 128)}"
"acdc:1976:0:0:0:0:0:0/32"

# a MAC address
iex> "#{new(<<0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6>>, 48)}"
"A1:B2:C3:D4:E5:F6"

# just 8-bit numbers and mask length
iex> "#{new(<<1, 2, 3, 4, 5>>, 64)}"
"1.2.3.4.5.0.0.0/40"

# an ip4 address formatted as a string of bits
iex> new(<<1, 2, 3, 4>>, 32) |> format(width: 1, unit: 8)
"00000001.00000010.00000011.00000100"

A Pfx.t/0 struct is also enumerable:

iex> pfx = new("10.10.10.0/30")
iex> for ip <- pfx do "#{ip}" end
[
  "10.10.10.0",
  "10.10.10.1",
  "10.10.10.2",
  "10.10.10.3"
]

Functions are sometimes IP specific, like:

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("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"
}

But most of the times, functions have generic names, since they apply to all sorts of prefixes, e.g.

iex> partition(%Pfx{bits: <<10, 10, 10>>, maxlen: 32}, 26)
[
  %Pfx{bits: <<10, 10, 10, 0::size(2)>>, maxlen: 32},
  %Pfx{bits: <<10, 10, 10, 1::size(2)>>, maxlen: 32},
  %Pfx{bits: <<10, 10, 10, 2::size(2)>>, maxlen: 32},
  %Pfx{bits: <<10, 10, 10, 3::size(2)>>, maxlen: 32}
]

The Pfx.new/1 and Pfx.new/2 always return a Pfx.t/0 struct, but most other functions will return their results in the same representation they were given. So the above could also be done as:

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"
]

# or
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}
]

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.

t()

A prefix struct with fields: bits and maxlen.

IP Functions

Return a reverse DNS name (pointer) for given pfx.

Return a map with link-local address components for given pfx.

Returns true if pfx is a link-local prefix, false otherwise

Returns a map with multicast address components for given pfx.

Returns true is pfx is a multicast prefix, false otherwise

Returns true if pfx is matched by the Well-Known Prefixes defined in rfc6053 and rfc8215, false otherwise.

Returns the embedded IPv4 address of a nat64 pfx

Return an IPv4 embedded IPv6 address for given pfx6 and pfx4.

Returns a map with the teredo address components of pfx or nil.

Returns true if prefix is a teredo address, false otherwise

Returns true if pfx is designated as "private-use".

Guards

Guard that ensures both prefixes are valid and comparable (same maxlen).

Guard that ensures a given pfx is actually valid.

Functions

A bitwise AND of two prefix/0's.

Return pfx prefix's bit-value at given position.

Return the concatenation of 1 or more series of bits of the given pfx.

Return a series of bits for given pfx, starting bit position & length.

A bitwise NOT of the pfx.bits.

A bitwise OR of two prefixes.

Returns the broadcast prefix (full address) for given pfx.

Rotate the pfx.bits by n positions.

Set all pfx.bits to either 0 or 1.

Arithmetic shift left the pfx.bits by n positions.

Arithmetic shift right the pfx.bits by n positions.

A bitwise XOR of two t:prefix's.

Cast a prefix/0 to an integer.

Compare function for sorting.

Contrast two Pfx prefixes

Cut out a series of bits and turn it into its own Pfx.

Transform a Pfx prefix into {digits, len} format.

Turn a pfx.bits string into a list of {number, width}-fields.

Generic formatter to turn a Pfx into a string, using several options

Return the nth host in given pfx.

Returns a list of address prefixes for given pfx.

Returns the inverted mask for given pfx.

Return the mask as a Pfx for given pfx.

Return the nth-member of a given pfx.

Return the nth subprefix for a given pfx, using width bits.

Returns true is prefix pfx1 is a member of prefix pfx2

Returns the neighboring prefix such that both can be combined in a supernet.

Returns the this-network prefix (full address) for given pfx.

Creates a new prefix from address tuples or binaries.

Creates a new Pfx.t/0-prefix.

Left pad the pfx.bits to its full length using 0-bits.

Left pad the pfx.bits to its full length using either 0 or 1-bits.

Left pad the pfx.bits with n bits of either 0 or 1's.

Right pad the pfx.bits to its full length using 0-bits.

Right pad the pfx.bits to its full length using either 0 or 1-bits.

Right pad the pfx.bits with n bits of either 0 or 1's.

Partition a Pfx prefix into a list of new prefixes, each bitlen long.

Returns another Pfx at distance offset.

Returns the number of full addresses represented by given pfx.

Return the Pfx prefix represented by the digits, actual length and a given field width.

Returns 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 IP Functions

Specs

dns_ptr(prefix()) :: String.t()

Return 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"

# https://www.youtube.com/watch?v=VD7BV-z5GsE
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

multicast(prefix()) :: map() | nil

Returns a map with multicast address components for given pfx.

Returns nil if pfx is not a multicast address.

Examples

iex> multicast("ff02::1")
%{
  preamble: 255,
  flags: {0, 0, 0, 0},
  scope: 2,
  groupID: <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1>>,
  address: "ff02::1"
}

iex> multicast("224.0.0.1")
%{
  address: "224.0.0.1",
  digits: {224, 0, 0, 1},
  groupID: <<0, 0, 0, 1::size(4)>>
}

Specs

multicast?(prefix()) :: boolean()

Returns true is pfx is a multicast prefix, false otherwise

Examples

iex> multicast?(%Pfx{bits: <<224, 0, 0, 1>>, maxlen: 32})
true

iex> multicast?({{224, 0, 0, 1}, 32})
true

iex> multicast?({224, 0, 0, 1})
true

iex> multicast?("224.0.0.1")
true

iex> multicast?("ff02::1")
true

iex> multicast?("1.1.1.1")
false

# bad prefix
iex> multicast?("224.0.0.256")
false

Specs

nat64?(prefix()) :: boolean()

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.

Example

iex> nat64?(%Pfx{bits: <<0x64::16, 0xff9b::16, 0::64, 0x1010::16, 0x1010::16>>, maxlen: 128})
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?("64:ff9b::10.10.10.10")
true

iex> nat64?("64:ff9b:1::10.10.10.10")
true

# bad prefix
iex> nat64?("64:ff9b:1::10.10.10.256")
false
Link to this function

nat64_decode(pfx, len \\ 96)

View Source

Specs

nat64_decode(prefix(), integer()) :: String.t()

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("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) error nat64_decode, "len 90 not in: 96, 64, 56, 48, 40, 32"
Link to this function

nat64_encode(pfx6, pfx4)

View Source

Specs

nat64_encode(prefix(), prefix()) :: prefix()

Return 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

# from rfc6052, section 2.2

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:0:0:0:0"

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}

# other examples
iex> nat64_encode("2001:db8:100::/40", "192.0.2.33")
"2001:db8:1c0:2:21:0:0:0"

iex> nat64_encode("2001:db8:122::/48", "192.0.2.33")
"2001:db8:122:c000:2:2100:0:0"

iex> nat64_encode("2001:db8:122:300::/56", "192.0.2.33")
"2001:db8:122:3c0:0:221:0:0"

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:0:0:c000:221"

Specs

teredo(prefix()) :: map() | nil

Returns a map with the teredo address components of pfx or nil.

Returns nil if pfx is not a teredo address.

Examples

# example from https://en.wikipedia.org/wiki/Teredo_tunneling#IPv6_addressing
iex> teredo("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({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("1.1.1.1")
nil

Specs

teredo?(prefix()) :: boolean()

Returns true if prefix is a teredo address, false otherwise

See rfc4380.

Example

iex> teredo?("2001:0000:4136:e378:8000:63bf:3fff:fdd2")
true

iex> teredo?("1.1.1.1")
false

iex> teredo?(42)
false

Specs

unique_local?(prefix()) :: boolean()

Returns true if pfx is designated as "private-use".

For IPv4 this includes the rfc1918 prefixes 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16. For IPv6 this includes the rfc4193 prefix fc00::/7.

Examples

iex> unique_local?(%Pfx{bits: <<172, 31, 255, 255>>, maxlen: 32})
true

iex> unique_local?({{172, 31, 255, 255}, 32})
true

iex> unique_local?({172, 31, 255, 255})
true

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

# bad prefix
iex> unique_local?("10.255.255.256")
false

Link to this section Guards

Link to this macro

is_comparable(x, y)

View Source (macro)

Guard that ensures both prefixes are valid and comparable (same maxlen).

Guard that ensures a given pfx is actually valid.

  • it is a Pfx.t/0 struct,
  • pfx.maxlen is a t:non-neg-integer/0,
  • pfx.maxlen is >= 0, and
  • bit_size(pfx.bits) <= pfx.maxlen

Link to this section Functions

Specs

band(prefix(), prefix()) :: prefix()

A bitwise AND of two prefix/0's.

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>
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"

Specs

bit(prefix(), integer()) :: 0 | 1

Return pfx prefix's bit-value at given position.

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. A actual bit position in the range of bit_size(pfx.bits)..pfx.maxlen - 1 always yields 0.

Examples

iex> bit(%Pfx{bits: <<1, 2>>, maxlen: 32}, 14)
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

# 'missing' bits inside the prefix are deemed to be `0`
iex> bit("1.2.0.0/16", 24)
0

iex> bit("1.2.0.0/16", -8) # same bit, 32 - 8 = 24
0

# errors out on invalid positions
iex> bit("255.255.255.255", 33)
** (ArgumentError) invalid bit position: 33

iex> bit("10.10.0.0/16", -33)
** (ArgumentError) invalid bit position: -33

Specs

bits(prefix(), [{integer(), integer()}]) :: bitstring()

Return the concatenation of 1 or more series of bits of the given pfx.

Example

iex> bits(%Pfx{bits: <<1, 2, 3, 4>>, maxlen: 32}, [{0,8}, {-1, -8}])
<<1, 4>>

iex> bits({{1, 2, 3, 4}, 32}, [{0,8}, {-1, -8}])
<<1, 4>>

iex> bits({1, 2, 3, 4}, [{0, 8}, {-1, -8}])
<<1, 4>>

iex> bits("1.2.3.4", [{0, 8}, {-1, -8}])
<<1, 4>>

iex> bits("1.2.3.4/24", [{0, 8}, {-1, -8}])
<<1, 0>>
Link to this function

bits(prefix, position, length)

View Source

Specs

bits(prefix(), integer(), integer()) :: bitstring()

Return a series of bits for given pfx, starting bit position & length.

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

# 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>>

# 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>>

# 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

bnot(prefix()) :: prefix()

A bitwise NOT of the pfx.bits.

Results are returned in the same representation as given pfx.

Examples

iex> new(<<255, 255, 0, 0>>, 32) |> bnot()
%Pfx{bits: <<0, 0, 255, 255>>, maxlen: 32}

iex> new(<<255, 0>>, 32) |> bnot()
%Pfx{bits: <<0, 255>>, maxlen: 32}

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}

Specs

bor(prefix(), prefix()) :: prefix()

A bitwise OR of two prefixes.

Both prefixes must have the same maxlen.

Examples

# 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> x = new(<<10, 11, 12, 13>>, 32)
iex> y = new(<<255, 255>>, 32)
iex> bor(x, y)
%Pfx{bits: <<255, 255, 12, 13>>, maxlen: 32}

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}

Specs

broadcast(prefix()) :: prefix()

Returns the broadcast prefix (full address) for given pfx.

The result is in the same format as pfx.

Examples

iex> broadcast(%Pfx{bits: <<10, 10, 10>>, maxlen: 32})
%Pfx{bits: <<10, 10, 10, 255>>, maxlen: 32}

iex> broadcast({{10, 10, 10, 1}, 30})
{{10, 10, 10, 3}, 32}

# a full address is its own broadcast address
iex> broadcast({10, 10, 10, 1})
{10, 10, 10, 1}

iex> broadcast("10.10.0.0/16")
"10.10.255.255"

iex> broadcast(%Pfx{bits: <<0xacdc::16, 0x1976::16>>, maxlen: 128})
%Pfx{bits: <<0xACDC::16, 0x1976::16, -1::96>>, maxlen: 128}

Specs

brot(prefix(), integer()) :: prefix()

Rotate the pfx.bits by n positions.

Positive n rotates right, negative rotates left.

Note that the length of the resulting pfx.bits stays the same.

Examples

iex> brot(%Pfx{bits: <<1, 2, 3, 4>>, maxlen: 32}, 8)
%Pfx{bits: <<4, 1, 2, 3>>, maxlen: 32}

iex> new(<<1, 2, 3, 4>>, 32) |> brot(-8)
%Pfx{bits: <<2, 3, 4, 1>>, maxlen: 32}

iex> brot("1.2.3.4", 8)
"4.1.2.3"

iex> brot({1, 2, 3, 4}, 8)
{4, 1, 2, 3}

iex> brot({{1, 2, 3, 4}, 32}, -8)
{{2, 3, 4, 1}, 32}

# remember, its <<1, 2>> that gets rotated (!)
iex> brot({{1, 2, 3, 4}, 16}, 8)
{{2, 1, 0, 0}, 16}

Specs

bset(prefix(), 0 | 1) :: prefix()

Set all pfx.bits to either 0 or 1.

Examples

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}

# defaults to `0`-bit
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}

Specs

bsl(prefix(), integer()) :: prefix()

Arithmetic shift left the pfx.bits by n positions.

A positive n shifts to the left, negative n shifts to the right. Note that the length of pfx.bits stays the same.

Examples

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}

# mask is applied when creating a `Pfx` out of "1.2.3.4/16"
iex> bsl("1.2.3.4/16", 2)
"4.8.0.0/16"

iex> bsl({1, 2, 3, 4}, 2)
{4, 8, 12, 16}

# remember, its <<1, 2>> that gets shifted left 2 bits
iex> bsl({{1, 2, 3, 4}, 16}, 2)
{{4, 8, 0, 0}, 16}

Specs

bsr(prefix(), integer()) :: prefix()

Arithmetic shift right the pfx.bits by n positions.

A negative n actually shifts to the left. Note that the pfx.bits stays stays the same.

Examples

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}

# mask get applied when creating a `Pfx`
iex> bsr("1.2.3.4/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, 3, 4}, 16}, 2)
{{0, 64, 0, 0}, 16}

Specs

bxor(prefix(), prefix()) :: prefix()

A bitwise XOR of two t:prefix's.

Both prefixes must have the same maxlen.

Examples

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(%Pfx{bits: <<10, 11, 12, 13>>, maxlen: 32}, "255.255.0.0")
%Pfx{bits: <<245, 244, 12, 13>>, maxlen: 32}

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}

iex> bxor({{10, 11, 12, 13}, 32}, "255.255.0.0")
{{245, 244, 12, 13}, 32}

Specs

cast(prefix()) :: non_neg_integer()

Cast a prefix/0 to an integer.

After right padding the given pfx, the pfx.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 maxlen is 0).

See cut/3 for how this capability might be useful.

Examples

iex> cast(%Pfx{bits: <<255, 255>>, maxlen: 16})
65535

# 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

iex> cast(%Pfx{bits: <<255, 255, 0, 0>>, maxlen: 32})
4294901760

iex> cast({255, 255, 0, 0})
4294901760

iex> cast({{255, 255, 0, 0}, 32})
4294901760

iex> cast("255.255.0.0")
4294901760

Specs

compare(prefix(), prefix()) :: :eq | :lt | :gt

Compare function for sorting.

  • :eq prefix1 is equal to prefix2
  • :lt prefix1 has more bits or lies to the left of prefix2
  • :gt prefix1 has less bits or lies to the right of prefix2

The prefixes must have the same maxlen and are first compared by size (i.e. a shorter prefix is considered larger), and second on their bitstring value.

Examples

iex> compare(new(<<10>>, 32), new(<<11>>, 32))
:lt

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

# sort on `pfx.bits` size first, than on `pfx.bits` values
iex> l = [new(<<10, 11>>, 32), new(<<10,10,10>>, 32), new(<<10,10>>, 32)]
iex> Enum.sort(l, Pfx)
[
  %Pfx{bits: <<10, 10, 10>>, maxlen: 32},
  %Pfx{bits: <<10, 10>>, maxlen: 32},
  %Pfx{bits: <<10, 11>>, maxlen: 32}
]
#
# whereas regular sort does:
#
iex> Enum.sort(l)
[
  %Pfx{bits: <<10, 10>>, maxlen: 32},
  %Pfx{bits: <<10, 10, 10>>, maxlen: 32},
  %Pfx{bits: <<10, 11>>, maxlen: 32}
]

iex> l = ["10.11.0.0/16", "10.10.10.0/24", "10.10.0.0/16"]
iex> Enum.sort(l, Pfx)
[
  "10.10.10.0/24",
  "10.10.0.0/16",
  "10.11.0.0/16"
]

# not advisable, you mixed representations are possible as well
iex> l = ["10.11.0.0/16", {{10, 10, 10, 0}, 24}, %Pfx{bits: <<10, 10>>, maxlen: 32}]
iex> Enum.sort(l, Pfx)
[
  {{10, 10, 10, 0}, 24},
  %Pfx{bits: <<10, 10>>, maxlen: 32},
  "10.11.0.0/16",
]

# note: all prefixes must have the same `maxlen`
iex> compare(new(<<10>>, 32), new(<<10>>, 128))
** (ArgumentError) prefixes have different maxlen's: {%Pfx{bits: "\n", maxlen: 32}, %Pfx{bits: "\n", maxlen: 128}}

Specs

contrast(prefix(), prefix()) ::
  :equal | :more | :less | :left | :right | :disjoint

Contrast two Pfx prefixes

Contrasting two prefixes will yield one of:

  • :equal pfx1 is equal to pfx2
  • :more pfx1 is a more specific version of pfx2
  • :less pfx1 is a less specific version of pfx2
  • :left pfx1 is left-adjacent to pfx2
  • :right pfx1 is right-adjacent to pfx2
  • :disjoint pfx1 has no match with pfx2 whatsoever.

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

iex> contrast("1.2.3.0/24", "1.2.4.0/24")
:left

iex> contrast("1.2.3.4/30", "1.2.3.0/30")
:right

iex> contrast("10.10.0.0/16", "9.0.0.0/8")
:disjoint

iex> contrast("10.10.0.0/16", %Pfx{bits: <<10,12>>, maxlen: 32})
:disjoint

Specs

cut(prefix(), integer(), integer()) :: prefix()

Cut out a series of bits and turn it into its own Pfx.

This basically uses &bits/3 to extract the bits and wraps it in a Pfx.t/0 with its maxlen set to the length of the bits extracted.

Examples

As per example on wikipedia for an IPv6 address 2001:0000:4136:e378:8000:63bf:3fff:fdd2 that refers to a Teredo client:

iex> teredo = new(<<0x2001::16, 0::16, 0x4136::16, 0xe378::16,
...>  0x8000::16, 0x63bf::16, 0x3fff::16, 0xfdd2::16>>, 128)
iex>
iex> # client
iex> cut(teredo, 96, 32) |> bnot() |> format()
"192.0.2.45"
iex>
iex>
iex> # udp port
iex> cut(teredo, 80, 16) |> bnot() |> cast()
40000
iex>
iex> # teredo server
iex> cut(teredo, 32, 32) |> format()
"65.54.227.120"
iex>
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}

"Missing" 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}

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: {8, 32}

Specs

digits(prefix(), pos_integer()) :: {tuple(), pos_integer()}

Transform a Pfx prefix into {digits, len} format.

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 len denotes the original pfx.bits bit_size.

Note: works best if pfx.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)

Examples

iex> digits(%Pfx{bits: <<10, 11, 12>>, maxlen: 32}, 8)
{{10, 11, 12, 0}, 24}

# not obvious that each number is 4 bits wide
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}

iex> digits(%Pfx{bits: <<10, 11, 12, 1::1>>, maxlen: 32}, 8)
{{10, 11, 12, 128}, 25}

iex> digits(%Pfx{bits: <<0xacdc::16, 0x1976::16>>, maxlen: 128}, 16)
{{44252, 6518, 0, 0, 0, 0, 0, 0}, 32}

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}

Specs

Turn a pfx.bits string into a list of {number, width}-fields.

If bit_size(pfx.bits) is not a multiple of width, the last {number, width}-tuple, will have a smaller width.

Examples

iex> fields(%Pfx{bits: <<10, 11, 12, 13>>, maxlen: 32}, 8)
[{10, 8}, {11, 8}, {12, 8}, {13, 8}]

# not a multiple of 8
iex> fields(%Pfx{bits: <<10, 11, 12, 0::1>>, maxlen: 32}, 8)
[{10, 8}, {11, 8}, {12, 8}, {0, 1}]

iex> new(<<0xacdc::16>>, 128) |> fields(4)
[{10, 4}, {12, 4}, {13, 4}, {12, 4}]

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}]

# only 1 field with less bits than given width of 64
iex> new(<<255, 255>>, 32) |> fields(64)
[{65535, 16}]

Specs

format(prefix(), Keyword.t()) :: String.t()

Generic formatter to turn a Pfx into 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 the pfx.bits (default true)

The defaults are geared towards IPv4 prefixes, but the options should be able to accomodate 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 acutal 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."

Specs

host(prefix(), integer()) :: prefix()

Return the nth host in given pfx.

The result is in the same format as pfx.

Note that offset nth wraps around. See Pfx.member/2.

Example

iex> host(%Pfx{bits: <<10, 10, 10>>, maxlen: 32}, 128)
%Pfx{bits: <<10, 10, 10, 128>>, maxlen: 32}

iex> host({{10, 10, 10, 0}, 24}, 128)
{{10, 10, 10, 128}, 32}

iex> host({10, 10, 10, 10}, 13)
{10, 10, 10, 10}

iex> host("10.10.10.0/24", 128)
"10.10.10.128"

Specs

hosts(prefix()) :: [prefix()]

Returns a list of address prefixes for given pfx.

The result is in the same format as pfx.

Examples

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}
]

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}
]

Specs

inv_mask(prefix()) :: prefix()

Returns the inverted mask for given pfx.

The result is in the same format as pfx.

Examples

iex> inv_mask(%Pfx{bits: <<10, 10, 10, 0::1>>, maxlen: 32})
%Pfx{bits: <<0, 0, 0, 127>>, maxlen: 32}

iex> inv_mask({{10, 10, 10, 0}, 25})
{{0, 0, 0, 127}, 32}

iex> inv_mask({10, 10, 10, 0})
{0, 0, 0, 0}

iex> inv_mask("10.10.10.0/25")
"0.0.0.127"

Specs

mask(prefix()) :: prefix()

Return the mask as a Pfx for given pfx.

The result is in the same format as pfx.

Examples

iex> mask(%Pfx{bits: <<10, 10, 10, 1::1>>, maxlen: 32})
%Pfx{bits: <<255, 255, 255, 128>>, maxlen: 32}

iex> mask({{10, 10, 10, 1}, 25})
{{255, 255, 255, 128}, 32}

iex> mask({10, 10, 10, 1})
{255, 255, 255, 255}

iex> mask("10.10.10.128/25")
"255.255.255.128"

Specs

member(prefix(), integer()) :: prefix()

Return 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 since <<16::4>> comes out as <<0::4>>.

Examples

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}

# other representations work too
iex> member("10.10.10.0/24", 255)
"10.10.10.255"

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}

Specs

member(prefix(), integer(), pos_integer()) :: t()

Return the nth subprefix for a given pfx, using width bits.

Examples

# the first sub-prefix 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 sub-prefix 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}

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}

Specs

member?(prefix(), prefix()) :: boolean()

Returns true is prefix pfx1 is a member of prefix pfx2

If either prfx1 or pfx2 is invalid, member? simply returns false

Examples

iex> member?(%Pfx{bits: <<10, 10, 10, 10>>, maxlen: 32}, %Pfx{bits: <<10>>, maxlen: 32})
true

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

# bad prefix
iex> member?("10.10.10.10", "10.10.10.256/24")
false

Specs

neighbor(prefix()) :: prefix()

Returns the neighboring prefix such that both can be combined in a supernet.

The result is in the same format as pfx.

Example

iex> neighbor(%Pfx{bits: <<1, 1, 1, 1::1>>, maxlen: 32})
%Pfx{bits: <<1, 1, 1, 0::1>>, maxlen: 32}

iex> neighbor({{1, 1, 1, 128}, 25})
{{1, 1, 1, 0}, 25}

iex> neighbor({1, 1, 1, 1})
{1, 1, 1, 0}

iex> neighbor("1.1.1.0/25")
"1.1.1.128/25"

iex> neighbor("1.1.1.128/25")
"1.1.1.0/25"

Specs

network(prefix()) :: prefix()

Returns the this-network prefix (full address) for given pfx.

The result is in the same format as pfx.

Examples

iex> network(%Pfx{bits: <<10, 10, 10>>, maxlen: 32})
%Pfx{bits: <<10, 10, 10, 0>>, maxlen: 32}

# mask is applied to address
iex> network({{10, 10, 10, 1}, 24})
{{10, 10, 10, 0}, 32}

# 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"

iex> network(%Pfx{bits: <<0xacdc::16, 0x1976::16>>, maxlen: 128})
%Pfx{bits: <<0xACDC::16, 0x1976::16, 0::96>>, maxlen: 128}

iex> network("acdc:1976::/32")
"acdc:1976:0:0:0:0:0:0"

Specs

new(ip_address() | ip_prefix() | String.t()) :: t()

Creates a new prefix from address tuples or binaries.

Use:

  • an ipv4 or ipv6 ip_address/0 tuple directly for a full address, or
  • a {ip_address/0, length}-tuple to truncate the bits to length.
  • a binary in CIDR-notation, like "acdc:1976::/32"

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

Examples

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})
%Pfx{bits: <<10, 10, 0, 0>>, maxlen: 32}

iex> new({{10, 10, 0, 0}, 16})
%Pfx{bits: <<10, 10>>, maxlen: 32}

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}

# 10.10/16 is interpreted as 10.0.0.10/16 (!)
iex> new("10.10/16")
%Pfx{bits: <<10, 0>>, maxlen: 32}

Specs

new(t() | bitstring(), non_neg_integer()) :: t()

Creates a new Pfx.t/0-prefix.

A prefix can be created 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,
  • from an ipv4 or ipv6 ip_address/0 tuple
  • from an {ip_address/0, length} tuple

The last form sets the maxlen according to the IP protocol version used, while the length parameter is used to truncate the bits for the prefix.

Examples

iex> new(<<10, 10>>, 32)
%Pfx{bits: <<10, 10>>, maxlen: 32}

iex> new(<<10, 10>>, 8)
%Pfx{bits: <<10>>, maxlen: 8}

# Create a new `Pfx` from an existing one, note:
# this changes the `Pfx`'s meaning
iex> new(<<10, 10>>, 32) |> new(128)
%Pfx{bits: <<10, 10>>, maxlen: 128}

Specs

padl(prefix()) :: prefix()

Left pad the pfx.bits to its full length using 0-bits.

Example

iex> padl(%Pfx{bits: <<1, 2>>, maxlen: 32})
%Pfx{bits: <<0, 0, 1, 2>>, maxlen: 32}

iex> padl("1.2.0.0/16")
"0.0.1.2"

iex> padl({{1, 2, 0, 0}, 16})
{{0, 0, 1, 2}, 32}

Specs

padl(prefix(), 0 | 1) :: prefix()

Left pad the pfx.bits to its full length using either 0 or 1-bits.

Example

iex> padl(%Pfx{bits: <<1, 2>>, maxlen: 32}, 1)
%Pfx{bits: <<255, 255, 1, 2>>, maxlen: 32}

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}

Specs

padl(prefix(), 0 | 1, non_neg_integer()) :: prefix()

Left pad the pfx.bits with n bits of either 0 or 1's.

Example

iex> padl(%Pfx{bits: <<255, 255>>, maxlen: 32}, 0, 16)
%Pfx{bits: <<0, 0, 255, 255>>, maxlen: 32}

iex> padl("255.255.0.0/16", 0, 16)
"0.0.255.255"

iex> padl({{255, 255, 0, 0}, 16}, 0, 16)
{{0, 0, 255, 255}, 32}

Specs

padr(prefix()) :: prefix()

Right pad the pfx.bits to its full length using 0-bits.

The result is always a full prefix with maxlen bits.

Example

iex> padr(%Pfx{bits: <<1, 2>>, maxlen: 32})
%Pfx{bits: <<1, 2, 0, 0>>, maxlen: 32}

# already a full address
iex> padr("1.2.0.0")
"1.2.0.0"

# 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,3,4}, 16})
{{1, 2, 0, 0}, 32}

Specs

padr(prefix(), 0 | 1) :: prefix()

Right pad the pfx.bits to its full length using either 0 or 1-bits.

Example

iex> padr(%Pfx{bits: <<1, 2>>, maxlen: 32}, 1)
%Pfx{bits: <<1, 2, 255, 255>>, maxlen: 32}

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({1, 2, 0, 0}, 1)
{1, 2, 0, 0}

Specs

padr(prefix(), 0 | 1, non_neg_integer()) :: prefix()

Right pad the pfx.bits with n bits of either 0 or 1's.

The result is clipped at maxlen bits without warning.

Examples

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}

# results are clipped to maxlen
iex> new(<<1, 2>>, 32) |> padr(0, 512)
%Pfx{bits: <<1, 2, 0, 0>>, maxlen: 32}

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}

Specs

partition(prefix(), non_neg_integer()) :: [prefix()]

Partition a Pfx prefix 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(%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}
]

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},
]

Specs

sibling(prefix(), integer()) :: prefix()

Returns another Pfx at distance offset.

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

# next in line
iex> sibling(%Pfx{bits: <<10, 11>>, maxlen: 32}, 1)
%Pfx{bits: <<10, 12>>, maxlen: 32}

# the last shall be the first
iex> sibling(%Pfx{bits: <<10, 11, 0>>, maxlen: 32}, 255)
%Pfx{bits: <<10, 11, 255>>, maxlen: 32}

iex> sibling(%Pfx{bits: <<10, 11, 0>>, maxlen: 32}, 256)
%Pfx{bits: <<10, 12, 0>>, maxlen: 32}

# from one end to another
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}

iex> sibling("0.0.0.0", -1)
"255.255.255.255"

iex> sibling("1.2.3.0/24", -1)
"1.2.2.0/24"

iex> sibling({{1, 2, 3, 0}, 24}, 256)
{{1, 3, 3, 0}, 24}

Specs

size(prefix()) :: pos_integer()

Returns the number of full addresses represented by given pfx.

size(pfx) == 2^(pfx.maxlen - bit_size(pfx.bits))

Examples

iex> size(%Pfx{bits: <<1, 1, 1>>, maxlen: 32})
256

iex> size({{1, 1, 1, 0}, 16})
65536

iex> size({1,1,1,1})
1

iex> size("1.1.1.0/23")
512

Specs

undigits({tuple(), pos_integer()}, pos_integer()) :: t()

Return the Pfx 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 the length-msb 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}

Specs

valid?(prefix()) :: boolean()

Returns boolean indicating whether pfx is a valid prefix/0 or not.

Examples

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

iex> valid?({{1, 2, 3, 4}, 24})
true

iex> valid?({1, 2, 3, 4})
true

iex> valid?("1.2.3.4")
true

iex> valid?("1.2.3.4/8")
true