Pfx (Pfx v0.14.1) 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 prefix
  • Pfx.new/2, which can create any type of prefix
  • Pfx.from_mac/1, which creates only EUI48 or EUI64 prefixes
  • Pfx.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:

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:

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, and
  • maxlen: 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.

t()

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

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

Link to this macro

is_comparable(x, y)

View Source (macro)

Guard that ensures both prefixes are valid Pfx structs and have the same maxlen.

Guard that ensures a given pfx is actually valid.

Link to this section IP Functions

Specs

broadcast(prefix()) :: prefix()

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

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

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

eui64_decode(prefix()) :: prefix()

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

eui64_encode(prefix()) :: prefix()

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"
Link to this function

iana_special(prefix, property \\ nil)

View Source

Specs

iana_special(:ip4 | :ip6 | prefix(), atom() | nil) :: nil | map() | term()

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 not prefix is a valid source address
  • :destination, a semi-boolean: whether or not prefix is a valid destination address
  • :forward, a semi-boolean: whether or not prefix can be forwarded (by a router)
  • :global, a semi-boolean: whether or not prefix is reachable on the Internet
  • :reserved, a semi-boolean: whether or not prefix is reserved-by-protocol
  • :name: a string: the description of the entry in the special purpose address registry
  • spec: a list of strings: naming the rfc's and errata related to given prefix
  • allocation: a string denoting yyyy-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), and
  • 2001::/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(:ip4) |> length()
25
iex> iana_special(:ip6) |> length()
20

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

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

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

multicast_decode(prefix()) :: map() | nil

Returns a map with multicast address components for given pfx.

Address components are parsed according to:

  • rfc1112 - Host Extensions for IP Multicasting
  • rfc4291 - IP Version 6 Addressing Architecture

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

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.

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
Link to this function

nat64_decode(pfx, len \\ 96)

View Source

Specs

nat64_decode(prefix(), integer()) :: prefix()

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
Link to this function

nat64_encode(pfx6, pfx4)

View Source

Specs

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

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

network(prefix()) :: prefix()

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

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

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

teredo_decode(prefix()) :: map() | nil

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:

  1. the teredo service prefix of 2000:0::/32
  2. IPv4 address of the teredo server
  3. flags (16 bits) that document type of address and NAT
  4. Port (16 bits), the obfuscated "mapped UDP port" at the client
  5. 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
Link to this function

teredo_encode(client, server, port, flags)

View Source

Specs

teredo_encode(prefix(), prefix(), integer(), tuple()) :: prefix()

Encodes given server, client, port and flags as an IPv6 teredo address.

The client and server must be full IPv4 adresses, 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

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

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

This includes the rfc1918 prefixes:

  • 10.0.0.0/8,
  • 172.16.0.0/12, and
  • 192.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

address(prefix()) :: prefix()

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

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

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

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

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

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

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>>
Link to this function

bits(prefix, position, length)

View Source

Specs

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

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

bnot(prefix()) :: prefix()

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

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

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

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

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

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

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

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

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

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

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

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

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
Link to this function

compare(prefix1, prefix2)

View Source

Specs

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

Compares prefix1 to prefix2 for sorting purposes.

The result is one of:

  • :eq prefix1 is equal to prefix2
  • :lt prefix1 has a smaller maxlen, more bits or is left of prefix2
  • :gt prefix1 has a larger maxlen, 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 donot 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

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

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

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

first(prefix()) :: prefix()

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

flip(prefix(), integer()) :: prefix()

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

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

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 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."
Link to this function

from_hex(string, punctuation \\ ':-.')

View Source

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

from_mac(t() | binary() | tuple()) :: t()

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

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

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

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

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}
]
Link to this function

insert(pfx, bits, position)

View Source

Specs

insert(prefix(), bitstring(), integer()) :: prefix()

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}

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

inv_mask(prefix()) :: prefix()

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

last(prefix()) :: prefix()

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

marshall(t(), prefix()) :: prefix()

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

mask(prefix()) :: prefix()

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}
Link to this function

mask(prefix, mask, opts \\ [])

View Source

Specs

mask(prefix(), prefix(), Keyword.t()) :: prefix()

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, if true the mask is inverted before applying it (default: false)
  • :trim, if false the result is not trimmed to the size of mask (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

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

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

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

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

minimize([prefix()]) :: [prefix()]

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 mimicing, 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

neighbor(prefix()) :: prefix()

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

new(prefix()) :: t()

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 to length.
  • 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

padl(prefix()) :: prefix()

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

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

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

padr(prefix()) :: prefix()

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

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

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

parse(prefix()) :: {:ok, t()} | {:error, :einvalid}

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

parse(prefix(), any()) :: {:ok, t()} | any()

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}
]
Link to this function

partition_range(prefix, nhosts)

View Source

Specs

partition_range(prefix(), prefix() | integer()) :: [prefix()]

Returns a list of prefixes that cover the given range of address space.

The (inclusive) range can be specified either by:

  • start, stop prefixes, or
  • start, 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 addres,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
Link to this function

remove(pfx, position, length)

View Source

Specs

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

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

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

sigil_p(prefix(), [integer()]) :: t() | binary() | tuple()

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 any
  • m returns the mask for given prefix
  • f returns the first full address for prefix
  • l returns the last full address for prefix
  • n returns the neighbor for prefix
  • p returns the direct parent for prefix (i.e. drops 1 lsb bit)
  • t returns the trimmed result for prefix (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 string
  • T 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
Link to this function

sigil_p(prefix, mask, opts)

View Source

Specs

sigil_p(prefix(), prefix(), [integer()]) :: t() | binary() | tuple()

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
Link to this function

to_tuple(prefix, opts \\ [])

View Source

Specs

to_tuple(prefix(), Keyword.t()) :: tuple()

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

trim(prefix()) :: prefix()

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()

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

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

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