Pfx (Pfx v0.5.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 from option 4 above 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. 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.

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

Decode a modified EUI-64 back into the original EUI-48 address.

Create a modified EUI-64 out of eui (an EUI-48 address or an EUI-64).

Return 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

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

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

Returns true if prefix is a teredo address, false otherwise

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

Encode 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

A bitwise AND of two prefix/0's.

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

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

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

A bitwise NOT of the pfx.bits.

A bitwise OR of two prefixes.

Rotate the pfx.bits by n positions.

Set all pfx.bits to either 0 or 1.

Arithmetic shift left the pfx.bits by n positions.

Arithmetic shift right the pfx.bits by n positions.

A bitwise XOR of two prefix/0's.

Cast a prefix/0 to an integer.

Compare function for sorting.

Contrast two Pfx prefixes

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

Transform a Pfx prefix into {{digit, ..}, length} format.

Drop count lsb bits from given pfx.

Turn a prefix into a list of {number, width}-fields.

Returns the first full length prefix from the set represented by pfx.

Flip a single bit at position in given pfx.bits

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

Create a Pfx.t/0 out of a hexadecimal string.

Create a Pfx struct from a EUI48/64 strings or tuples.

Return the nth host in given pfx.

Returns a list of address prefixes for given pfx.

Insert some bits into pfx-s bitstring.

Returns the inverted mask for given pfx.

Keep count msb bits of given pfx.

Returns the last full length prefix from the set represented by pfx.

Given a t.Pfx.t/0 prefix, try to represent it in its original form.

Return the mask for given pfx.

Return the nth-member of a given pfx.

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

Returns true is prefix pfx1 is a member of prefix pfx2

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

Creates a new prefix from address tuples or binaries.

Creates a new Pfx.t/0-prefix.

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

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

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

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

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

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

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

Remove length bits from given pfx-s bitstring, starting at position.

Returns another Pfx at distance offset.

Returns the number of full addresses represented by given pfx.

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

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

Link to this section Types

Specs

ip_address() :: :inet.ip4_address() | :inet.ip6_address()

An :inet IPv4 or IPv6 address (tuple)

Specs

ip_prefix() :: {:inet.ip4_address(), 0..32} | {:inet.ip6_address(), 0..128}

An IPv4 prefix ({t:inet.ip4_address/0, 0..32}) or an IPv6 prefix ({t:inet.ip6_address/0, 0..128}).

Specs

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

A prefix expressed as either a Pfx.t/0 struct, an IP address-tuple, an address,length-tuple or a CIDR string.

Specs

t() :: %Pfx{bits: bitstring(), maxlen: non_neg_integer()}

A prefix struct with fields: bits and maxlen.

Link to this section IP Functions

Specs

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

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

The prefix will be padded right with 0-bits to a multiple of 8 for IPv4 prefixes and to a multiple of 4 for IPv6 prefixes. Note that this might give unexpected results. So dns_ptr/1 works best if the prefix given is actually a multiple of 4 or 8.

Examples

iex> dns_ptr("10.10.0.0/16")
"10.10.in-addr.arpa"

# "1.2.3.0/23" actually encodes as %Pfx{bits: <<1, 2, 1::size(7)>>, maxlen: 32}
# and padding right with 0-bits to a /24 yields the 1.2.2.0/24 ...
iex> dns_ptr("1.2.3.0/23")
"2.2.1.in-addr.arpa"

iex> dns_ptr("acdc:1976::/32")
"6.7.9.1.c.d.c.a.ip6.arpa"

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

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

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

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

The length of the pfx6.bits should be one of [96, 64, 56, 48, 40, 32] as defined in rfc6052. The pfx4 prefix should be a full address.

Examples

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

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

A bitwise AND of two prefix/0's.

Both prefixes must have the same maxlen. The resulting prefix will have the same number of bits as the first argument.

Examples

iex> band("10.10.10.10", "255.255.0.0")
"10.10.0.0"

iex> band("10.10.10.0/24", "255.255.0.0")
"10.10.0.0/24"

iex> x = new(<<128, 129, 130, 131>>, 32)
iex> y = new(<<255, 255>>, 32)
iex>
iex> band(x, y)
%Pfx{bits: <<128, 129, 0, 0>>, maxlen: 32}
iex>
iex> band(y,x)
%Pfx{bits: <<128, 129>>, maxlen: 32}

# results adopt the format of the first argument
iex> band("1.2.3.4", {255, 255, 0, 0})
"1.2.0.0"

iex> band({1, 2, 3, 4}, "255.255.0.0")
{1, 2, 0, 0}

iex> band({{1, 2, 3, 4}, 24}, {255, 255, 0, 0})
{{1, 2, 0, 0}, 24}

# honoring the ancient tradition
iex> band("1.2.3.4", "255.255")
"1.0.0.4"

Specs

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

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

A bit position is a 0-based index from the left with range 0..maxlen-1.
A negative bit position is taken relative to Pfx.maxlen.
A bit position in the range of bit_size(pfx.bits) .. pfx.maxlen - 1 always yields 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()

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

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

Negative position's are relative to the end of the pfx.bits bitstring, while negative length will collect bits going left instead of to the right. Note that the bit at given position is always included in the result regardless of direction. Finally, a length of 0 results in an empty bitstring.

Examples

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

A bitwise NOT of the pfx.bits.

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

A bitwise OR of two prefixes.

Both prefixes must have the same maxlen.

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", new(<<255, 255>>, 32)) # "255.255.0.0/16"
"255.255.12.13"

Specs

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

Rotate the pfx.bits by n positions.

Positive n rotates right, negative rotates left.
Note that the length of the resulting pfx.bits stays the same.

Examples

iex> brot("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()

Set all pfx.bits to either 0 or 1.

Examples

# defaults to `0`-bit
iex> bset("1.1.1.0/24")
"0.0.0.0/24"

iex> bset("1.1.1.0/24", 1)
"255.255.255.0/24"

iex> bset({{1, 1, 1, 0}, 24}, 1)
{{255, 255, 255, 0}, 24}

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

Arithmetic shift left the pfx.bits by n positions.

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

Examples

iex> bsl("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()

Arithmetic shift right the pfx.bits by n positions.

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

Examples

iex> bsr("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()

A bitwise XOR of two prefix/0's.

Both prefixes must have the same maxlen.

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}

Specs

cast(prefix()) :: non_neg_integer()

Cast a prefix/0 to an integer.

After right padding the given pfx, the pfx.bits are interpreted as a number of maxlen bits wide. Empty prefixes evaluate to 0, since all 'missing' bits are taken to be zero (even if maxlen is 0).

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

Examples

iex> cast("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> %Pfx{bits: <<4294901760::32>>, maxlen: 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

Compare function for sorting.

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

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

Examples

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

Contrast two Pfx prefixes

Contrasting two prefixes will yield one of:

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

Examples

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

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

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

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

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

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

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

Specs

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

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

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>
iex> # client
iex> cut(teredo, 96, 32) |> bnot() |> format()
"192.0.2.45"
iex>
iex>
iex> # udp port
iex> cut(teredo, 80, 16) |> bnot() |> cast()
40000
iex>
iex> # teredo server
iex> cut(teredo, 32, 32) |> format()
"65.54.227.120"
iex>
iex> # flags
iex> cut(teredo, 64, 16) |> digits(1) |> elem(0)
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

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

Transform a Pfx prefix into {{digit, ..}, length} format.

The pfx is padded to its maximum length using 0's and the resulting bits are grouped into digits, each width-bits wide. The resulting 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)

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

Drop count lsb bits from given pfx.

If count exceeds the actual number of bits in pfx.bits, 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.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

Turn a prefix into a list of {number, width}-fields.

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

Examples

iex> fields("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()

Flip a single bit at position in given pfx.bits

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

# flip some bit
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()

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

  • :width, field width (default 8)
  • :base, howto turn a field into a string (default 10, use 16 for hex numbers)
  • :unit, how many fields go into 1 section (default 1)
  • :ssep, howto join the sections together (default ".")
  • :lsep, howto join a mask if required (default "/")
  • :mask, whether to add a mask (default false)
  • :reverse, whether to reverse fields before grouping/joining (default false)
  • :padding, whether to pad out the pfx.bits (default true)

The defaults are geared towards IPv4 prefixes, but the options should be able to accomodate other domains as well.

Notes:

  • the prefix.bits-length is omitted if equal to the prefix.bits-size
  • domain specific submodules probably implement their own formatter.

Examples

iex> format(%Pfx{bits: <<10, 11, 12>>, maxlen: 32})
"10.11.12.0/24"

iex> format({{10, 11, 12, 0}, 24})
"10.11.12.0/24"

iex> format({10, 11, 12, 0})
"10.11.12.0"

# non-sensical, but there you go
iex> format("10.11.12.0/24")
"10.11.12.0/24"

# bitstring, note that mask is applied when new creates the `pfx`
iex> format("1.2.3.4/24", width: 1, base: 2, unit: 8, mask: false)
"00000001.00000010.00000011.00000000"

# mask not appended as its redundant for a full-sized prefix
iex> format(%Pfx{bits: <<10, 11, 12, 13>>, maxlen: 32})
"10.11.12.13"

iex> pfx = new(<<0xacdc::16, 0x1976::16>>, 128)
iex> format(pfx, width: 16, base: 16, ssep: ":")
"acdc:1976:0:0:0:0:0:0/32"
#
# similar, but grouping 4 fields, each 4 bits wide, into a single section
#
iex> format(pfx, width: 4, base: 16, unit: 4, ssep: ":")
"acdc:1976:0000:0000:0000:0000:0000:0000/32"
#
# this time, omit the acutal pfx length
#
iex> format(pfx, width: 16, base: 16, ssep: ":", mask: false)
"acdc:1976:0:0:0:0:0:0"
#
# ptr for IPv6 using the nibble format:
# - dot-separated reversal of all hex digits in the expanded address
#
iex> pfx
...> |> format(width: 4, base: 16, mask: false, reverse: true)
...> |> String.downcase()
...> |> (fn x -> "#{x}.ip6.arpa." end).()
"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.7.9.1.c.d.c.a.ip6.arpa."

# turn off padding to get reverse zone dns ptr record
iex> new(<<10, 11, 12>>, 32)
...> |> format(padding: false, reverse: true, mask: false)
...> |> (&"#{&1}.in-addr.arpa.").()
"12.11.10.in-addr.arpa."
Link to this function

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

View Source

Create a Pfx.t/0 out of a hexadecimal string.

This always returns a Pfx.t 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 characeters are simply ignored and there are no positional checks 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()

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

Return the nth host 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()

Insert some bits into pfx-s bitstring.

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

Keep count msb bits of given pfx.

If count exceeds the actual number of bits in pfx.bits, simply 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()

Given a t.Pfx.t/0 prefix, try to represent it in its original form.

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, the first full length member comes out as an address.

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}

Specs

mask(prefix()) :: prefix()

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

# and now for something completely different:
iex> mask(%Pfx{bits: <<10, 10, 0::1>>, maxlen: 20})
%Pfx{bits: <<255, 255, 8::4>>, maxlen: 20}

Specs

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

Return the nth-member of a given pfx.

A prefix represents a range of (possibly longer) prefixes which can be seen as members of the prefix. So a prefix of n-bits long represents:

  • 1 prefix of n-bits long (i.e. itself),
  • 2 prefixes of n+1-bits long,
  • 4 prefixes of n+2-bits long
  • ..
  • 2^w prefixes of n+w-bits long

where n+w <= pfx.maxlen.

Not specifying a width assumes the maximum width available. If a width is specified, the nth-offset is added to the prefix as a number width-bits wide. This wraps around 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()

Return the nth subprefix for a given 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 sub-prefix that is 2 bits longer
iex> member(%Pfx{bits: <<10, 10, 10>>, maxlen: 32}, 0, 2)
%Pfx{bits: <<10, 10, 10, 0::2>>, maxlen: 32}

# the second sub-prefix that is 2 bits longer
iex> member(%Pfx{bits: <<10, 10, 10>>, maxlen: 32}, 1, 2)
%Pfx{bits: <<10, 10, 10, 1::2>>, maxlen: 32}

Specs

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

Returns true is prefix pfx1 is a member of prefix pfx2

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

Examples

iex> member?("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 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 or binaries.

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

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}

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}

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

Left pad the pfx.bits 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()

Left pad the pfx.bits 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()

Right pad the pfx.bits 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()

Right pad the pfx.bits 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()

Right pad the pfx.bits 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

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

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

Note that bitlen must be in the range of bit_size(pfx.bits)..pfx.maxlen-1.

Examples

# break out the /26's in a /24
iex> partition("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

remove(pfx, position, length)

View Source

Specs

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

Remove length bits from given 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.

Note:

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

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 represented by given 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

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

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

The pfx.bits are formed by first concatenating the digits expressed as bitstrings of width-bits wide and then truncating to the length-msb bits.

The pfx.maxlen is inferred as tuple_size(digits) * width.

Note: if a digit does not fit in width-bits, only the width-least significant bits are preserved, which may yield surprising results.

Examples

# truncated to the first 24 bits and maxlen is 32 (4*8)
iex> undigits({{10, 11, 12, 0}, 24}, 8)
%Pfx{bits: <<10, 11, 12>>, maxlen: 32}

iex> undigits({{-1, -1, 0, 0}, 32}, 8) |> format()
"255.255.0.0"

# bits are truncated to empty bitstring (`length` is 0)
iex> undigits({{1,2,3,4}, 0}, 8)
%Pfx{bits: <<>>, maxlen: 32}

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