Pfx (Pfx v0.8.0) 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/2 using:

This allows for the creation of any sort of prefix.

Use Pfx.new/1 to create a prefix struct from:

This creates a IPv4, IPv6, EUI-48 or an EUI-64 prefix. Other means to create prefixes include Pfx.from_mac/1 and Pfx.from_hex/1.

Several functions, like Pfx.unique_local?/1 are more IP oriented, and are included along with the more generic Pfx functions (like Pfx.cut/3) in order to have one module to rule them all.

Functions generally accept either a Pfx.t/0, a Pfx.ip_address/0, a Pfx.ip_prefix/0 or a binary and yield their result in the same fashion:

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"

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

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 and
  • maxlen: 64 as a EUI-64 address string
  • 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:0:0:0:0:0:0/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 <- new("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 <- new("1.2.3.0/30"), do: digits(ip, 8) |> elem(0)
[
  {1, 2, 3, 0},
  {1, 2, 3, 1},
  {1, 2, 3, 2},
  {1, 2, 3, 3}
]

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

# 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> vrrp_mac = new("00-00-5e-00-01-0f")
%Pfx{bits: <<0, 0, 94, 0, 1, 15>>, maxlen: 48}
iex>
iex> member?(vrrp_mac, vrrp_mac_range)
true
iex> cut(vrrp_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"

Some functions have IP related names (like Pfx.teredo_decode/1, but most of the times functions have generic names, since they apply to all sorts of prefixes, e.g.

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.

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

Guards

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

Guard that ensures a given pfx is actually valid.

Functions

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 pfx1 to pfx2 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.

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 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 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 the number of full addresses as represented by pfx.

Trims all trailing 0's from given prefix.

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

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:0:0:0/96",
  multicast_prefix: "ff30:0:0:0:0:0:0:0/12",
  protocol: :ipv6,
  rfc: %{
    group_id: 0,
    unicast_prefix: "3ffe:ffff:1:0:0:0:0:0/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:0:0:0"

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

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

iex> nat64_encode("2001:db8:122:344::/64", "192.0.2.33")
"2001:db8:122:344:c0:2:2100:0"

iex> nat64_encode("2001:db8:122:344::/96", "192.0.2.33")
"2001:db8:122:344:0:0:c000:221"

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

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

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

Teredo address consist 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 Guards

Link to this macro

is_comparable(x, y)

View Source (macro)

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

Guard that ensures a given pfx is actually valid.

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

Link to this section Functions

Specs

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

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

iex> bit("10.10.0.0/16", -33)
** (ArgumentError) invalid bit position: -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.

Results are returned in the same representation as given pfx.

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

Specs

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

Compares pfx1 to pfx2 for sorting purposes.

The result is one of:

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

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

Examples

iex> compare("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

# sort on prefixes, first on bit_size than bits-values

iex> list = ["10.11.0.0/16", "10.10.10.0/24", "10.10.0.0/16"]
iex> Enum.sort(list, Pfx)
[
  "10.10.10.0/24",
  "10.10.0.0/16",
  "10.11.0.0/16"
]
# whereas regular sort does:
#
iex> Enum.sort(list)
[
  "10.10.0.0/16",
  "10.10.10.0/24",
  "10.11.0.0/16"
]

iex> list = [new(<<10, 11>>, 32), new(<<10,10,10>>, 32), new(<<10,10>>, 32)]
iex> Enum.sort(list, Pfx)
[
  %Pfx{bits: <<10, 10, 10>>, maxlen: 32},
  %Pfx{bits: <<10, 10>>, maxlen: 32},
  %Pfx{bits: <<10, 11>>, maxlen: 32}
]

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

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

Specs

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

Contrasts pfx1 to pfx2.

Contrasting two prefixes yields one of:

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

Examples

iex> contrast("10.10.0.0/16", "10.10.0.0/16")
:equal

iex> contrast("10.10.10.0/24", "10.10.0.0/16")
:more

iex> contrast("10.0.0.0/8", "10.255.255.0/24")
:less

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

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

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

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

Specs

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

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

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

# 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(t(), non_neg_integer()) :: t()

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(t(), bitstring(), integer()) :: t()

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

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

# 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()) :: t()

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

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(ip_address() | ip_prefix() | String.t()) :: 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.

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

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

remove(t(), non_neg_integer(), non_neg_integer()) :: t()

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

iex> remove("1.2.3.4", 8, 8)
"1.3.4.0/24"

iex> remove("1.2.3.128/25", -1, 1)
"1.2.3.0/24"

iex> remove("0288.88FF.FE88.8888", 24, 16)
"02-88-88-88-88-88-00-00/48"

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

size(prefix()) :: pos_integer()

Returns the number of full addresses as represented by pfx.

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

Examples

iex> size("1.1.1.0/23")
512

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

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

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

Specs

trim(prefix()) :: prefix()

Trims all trailing 0's from given prefix.

Examples

iex> trim("255.255.255.0")
"255.255.255.0/24"

iex> trim("10.10.128.0/30")
"10.10.128.0/17"

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