Iptrie (Iptrie v0.3.0) View Source
IP lookup, with longest prefix match, for IPv4, IPv6 prefixes (and others).
Iptrie manages multiple Radix
trees, one for each type of
Pfx.t/0
prefix used as determined by their maxlen
property. That way,
IPv4 prefixes (maxlen: 32
) use a different radix tree as opposed to e.g. IPv6
(maxlen: 128
).
Iptrie has a bias towards IPv4 and IPv6 since it uses Pfx
to convert
arguments to a Pfx.t/0
struct. So, doing other types of prefixes will
require the actual Pfx.t/0
structs as arguments for the various Iptrie
functions.
Like Pfx
, Iptrie tries to mirror the representation of results to the
argument(s) given, if possible.
IPv4/IPv6
iex> ipt = new()
...> |> put("1.2.3.0/24", "v4")
...> |> put("128.0.0.0/8", "v4-128")
...> |> put("acdc:1975::/32", "T.N.T")
...> |> put("acdc:1978::/32", "Powerage")
...> |> put("0.0.0.0/0", "v4 default")
...> |> put("::/0", "no dynamite")
iex>
iex> lookup(ipt, "1.2.3.128")
{"1.2.3.0/24", "v4"}
iex> lookup(ipt, "acdc:1975::")
{"acdc:1975:0:0:0:0:0:0/32", "T.N.T"}
iex>
iex> # separate trees, separate default routes
iex> lookup(ipt, "10.11.12.13")
{"0.0.0.0/0", "v4 default"}
iex> lookup(ipt, "abba::")
{"0:0:0:0:0:0:0:0/0", "no dynamite"}
iex>
iex> # visualize the IPv4 & IPv6 radix trees
iex> kv32 = fn {k, v} -> "#{Pfx.new(k, 32)}<br/>#{v}" end
iex> radix(ipt, 32)
...> |> Radix.dot(label: "IPv4", kv_tostr: kv32)
...> |> (&File.write("img/ipv4.dot", &1)).()
iex> kv128 = fn {k, v} -> "#{Pfx.new(k, 128)}<br/>#{v}" end
iex> radix(ipt, 128)
...> |> Radix.dot(label: "IPv6", kv_tostr: kv128)
...> |> (&File.write("img/ipv6.dot", &1)).()
Where the radix trees for the IP prefixes look like:
Others
Iptrie can also be used to do longest prefix match lookup for other types of prefixes, like e.g. MAC addresses:
iex> ipt = new()
...> |> put(%Pfx{bits: <<0x00, 0x22, 0x72>>, maxlen: 48}, "American Micro-Fuel Device")
...> |> put(%Pfx{bits: <<0x00, 0xd0, 0xef>>, maxlen: 48}, "IGT")
...> |> put(%Pfx{bits: <<0x08, 0x61, 0x95>>, maxlen: 48}, "Rockwell Automation")
iex>
iex> lookup(ipt, %Pfx{bits: <<0x00, 0xd0, 0xef, 0xaa, 0xbb>>, maxlen: 48})
{%Pfx{bits: <<0x00, 0xd0, 0xef>>, maxlen: 48}, "IGT"}
iex>
iex> # longest match for partial prefix
iex> lookup(ipt, %Pfx{bits: <<0x08, 0x61, 0x95, 0x01>>, maxlen: 48}) |> elem(1)
"Rockwell Automation"
iex>
iex> kv48 = fn {_k, v} -> "#{v}" end
iex> radix(ipt, 48)
...> |> Radix.dot(label: "MAC OUI", kv_tostr: kv48)
...> |> (&File.write("img/mac.dot", &1)).()
Iptrie
does not automatically recognize MAC addresses in string format (like
00-D0-EF-AA-BB-CC-DD-EE
or 00:D0:EF:AA:BB:CC:DD:EE
), so the actual
Pfx.t/0
structs must be used in the various IPtrie functions.
Since prefixes are stored in specific radix trees based on the maxlen
of
given prefix, you could also mix IPv4, IPv6 and MAC prefixes and possibly
others, in a single Iptrie.
Link to this section Summary
Types
A prefix represented as an opague Pfx.t/0
struct, an
Pfx.ip_address/0
, Pfx.ip_prefix/0
or CIDR string.
Functions
Delete one or more prefix, value-pairs from the trie
using an exact match.
Fetches the prefix,value-pair for given prefix
from trie
(exact match).
Fetches the prefix,value-pair for given prefix
from trie
(exact match).
Returns a new Iptrie, keeping only the entries for which fun
returns truthy.
Finds a prefix,value-pair for given prefix
from trie
(longest match).
Finds a prefix,value-pair for given prefix
from trie
(longest match).
Return one or more prefix,value-pair(s) using an exact match for given prefix(es)
.
Return all prefixes stored in all available radix trees in trie
.
Return the prefixes stored in the radix tree(s) in trie
for given type
.
Return all the prefix,value-pairs whose prefix is a prefix for the given
search prefix
.
Return the prefix,value-pair, whose prefix is the longest match for given search prefix
.
Return all the prefix,value-pairs where the search prefix
is a prefix for
the stored prefix.
Create an new, empty Iptrie.
Populate the trie
with a list of {prefix,value}-pairs.
Puts value
under prefix
in the trie
.
Return the radix tree for given type
or a new empty tree.
Invoke fun
on all prefix,value-pairs in all radix trees in the trie
.
Invoke fun
on each prefix,value-pair in the radix tree for given type
Return all prefix,value-pairs from all available radix trees in trie
.
Returns the prefix,value-pairs from the radix trees in trie
for given
type
(s).
Lookup prefix
and update the matched entry, only if found.
Lookup prefix
and, if found, update its value or insert the default.
Return all the values stored in all radix trees in trie
.
Return the values stored in the radix trees in trie
for given type
.
Link to this section Types
Specs
prefix() :: Pfx.prefix()
A prefix represented as an opague Pfx.t/0
struct, an
Pfx.ip_address/0
, Pfx.ip_prefix/0
or CIDR string.
See: Pfx
.
Specs
t() :: %Iptrie{}
An Iptrie struct that contains a Radix
tree per type of Pfx.t/0
used.
A prefix' type is determined by its maxlen
property: IPv4 has maxlen: 32
, IPv6 has maxlen: 128
, MAC addresses have maxlen: 48
and so on.
Although Iptrie facilitates lpm lookups of any type of prefix, it has a bias towards IP prefixes. So, any binaries (strings) are interpreted as CIDR-strings and tuples of address digits and/or {address-digits, length) are interpreted as IPv4 or IPv6 representations.
Link to this section Functions
Specs
Delete one or more prefix, value-pairs from the trie
using an exact match.
The list of prefixes to delete may contain all types, so all sorts of prefixes can be deleted from multiple radix trees in one go.
Example
iex> ipt = new()
...> |> put("1.1.1.0/24", "one")
...> |> put("2.2.2.0/24", "two")
iex>
iex> lookup(ipt, "1.1.1.1")
{"1.1.1.0/24", "one"}
iex>
iex> Map.get(ipt, 32) |> Radix.keys()
[<<1, 1, 1>>, <<2, 2, 2>>]
iex>
iex> ipt = delete(ipt, "1.1.1.0/24")
iex>
iex> lookup(ipt, "1.1.1.1")
nil
iex>
iex> Map.get(ipt, 32) |> Radix.keys()
[<<2, 2, 2>>]
iex> ipt = new()
...> |> put("1.1.1.0/24", "one")
...> |> put("2.2.2.0/24", "two")
...> |> put("abba:1973::/32", "Ring Ring")
...> |> put("acdc:1975::/32", "T.N.T")
iex>
iex> ipt = delete(ipt, ["1.1.1.0/24", "abba:1973::/32"])
iex>
iex> Map.get(ipt, 32) |> Radix.keys()
[<<2, 2, 2>>]
iex>
iex> Map.get(ipt, 128) |> Radix.keys()
[<<0xacdc::16, 0x1975::16>>]
Specs
Fetches the prefix,value-pair for given prefix
from trie
(exact match).
In case of success, returns {:ok, {prefix, value}}.
If prefix
is not present, returns {:error, :notfound}
.
In case of encoding errors for prefix
, returns {:error, :notprefix}
Example
iex> ipt = new()
...> |> put("1.1.1.0/24", "one")
...> |> put("2.2.2.0/24", "two")
iex>
iex> fetch(ipt, "1.1.1.0/24")
{:ok, {"1.1.1.0/24", "one"}}
iex>
iex> fetch(ipt, "12.12.12.12")
{:error, :notfound}
iex>
iex> fetch(ipt, "13.13.13.333")
{:error, :notprefix}
Specs
Fetches the prefix,value-pair for given prefix
from trie
(exact match).
In case of success, returns {prefix, value}.
If prefix
is not present, raises a KeyError
.
If prefix
could not be encoded, raises an ArgumentError
.
Example
iex> ipt = new()
...> |> put("10.10.10.0/24", "ten")
...> |> put("11.11.11.0/24", "eleven")
iex>
iex> fetch!(ipt, "10.10.10.0/24")
{"10.10.10.0/24", "ten"}
iex>
iex> fetch!(ipt, "12.12.12.12")
** (KeyError) prefix "12.12.12.12" not found
iex> ipt = new()
iex> fetch!(ipt, "13.13.13.333")
** (ArgumentError) invalid prefix "13.13.13.333"
Specs
Returns a new Iptrie, keeping only the entries for which fun
returns truthy.
The signature for fun
is (key, maxlen, value -> boolean), where the (radix)
key is the original bitstring of the prefix of type maxlen, used to store some
value in that particular radix tree in given trie
.
Radix trees that are empty, are removed from the new Iptrie.
Note that, if need be, Pfx.new(key, maxlen)
reconstructs the original
prefix used to store the value in the trie
.
Example
iex> ipt = new()
...> |> put("acdc:1975::/32", "rock")
...> |> put("acdc:1976::/32", "rock")
...> |> put("abba:1975::/32", "pop")
...> |> put("abba:1976::/32", "pop")
...> |> put("1.1.1.0/24", "v4")
iex>
iex> filter(ipt, fn _bits, maxlen, _value -> maxlen == 32 end)
...> |> to_list()
[{%Pfx{bits: <<1, 1, 1>>, maxlen: 32}, "v4"}]
iex>
iex> filter(ipt, fn _bits, _max, value -> value == "rock" end)
...> |> to_list()
...> |> Enum.map(fn {pfx, value} -> {"#{pfx}", value} end)
[
{"acdc:1975:0:0:0:0:0:0/32", "rock"},
{"acdc:1976:0:0:0:0:0:0/32", "rock"}
]
Specs
Finds a prefix,value-pair for given prefix
from trie
(longest match).
In case of success, returns {:ok, {prefix, value}}.
If prefix
had no match, returns {:error, :notfound}
.
In case of encoding errors for prefix
, returns {:error, :notprefix}
Example
iex> ipt = new()
...> |> put("1.1.1.0/24", "one")
...> |> put("2.2.2.0/24", "two")
iex>
iex> find(ipt, "1.1.1.0/24")
{:ok, {"1.1.1.0/24", "one"}}
iex>
iex> find(ipt, "12.12.12.12")
{:error, :notfound}
iex>
iex> find(ipt, "13.13.13.333")
{:error, :notprefix}
Specs
Finds a prefix,value-pair for given prefix
from trie
(longest match).
In case of success, returns {prefix, value}.
If prefix
had no match, raises a KeyError
.
If prefix
could not be encoded, raises an ArgumentError
.
Example
iex> ipt = new()
...> |> put("10.10.10.0/24", "ten")
...> |> put("11.11.11.0/24", "eleven")
iex>
iex> find!(ipt, "10.10.10.0/24")
{"10.10.10.0/24", "ten"}
iex>
iex> find!(ipt, "12.12.12.12")
** (KeyError) prefix "12.12.12.12" not found
iex> ipt = new()
iex> find!(ipt, "13.13.13.333")
** (ArgumentError) invalid prefix "13.13.13.333"
Specs
Return one or more prefix,value-pair(s) using an exact match for given prefix(es)
.
Examples
iex> ipt = new([{"1.1.1.0/30", "A"}, {"1.1.1.0/31", "B"}, {"1.1.1.0", "C"}])
iex>
iex> get(ipt, "1.1.1.0/31")
{"1.1.1.0/31", "B"}
iex>
iex> # or get a list of entries
iex>
iex> get(ipt, ["1.1.1.0/30", "1.1.1.0"])
[{"1.1.1.0/30", "A"}, {"1.1.1.0", "C"}]
Specs
Return all prefixes stored in all available radix trees in trie
.
The prefixes are reconstructed as Pfx.t/0
by combining the stored bitstrings
with the Radix
-tree's type.e. maxlen property).
Example
iex> ipt = new()
...> |> put("1.1.1.0/24", 1)
...> |> put("2.2.2.0/24", 2)
...> |> put("acdc:1975::/32", 3)
...> |> put("acdc:2021::/32", 4)
iex>
iex> keys(ipt)
[
%Pfx{bits: <<1, 1, 1>>, maxlen: 32},
%Pfx{bits: <<2, 2, 2>>, maxlen: 32},
%Pfx{bits: <<0xacdc::16, 0x1975::16>>, maxlen: 128},
%Pfx{bits: <<0xacdc::16, 0x2021::16>>, maxlen: 128}
]
iex>
iex> radix(ipt, 32) |> Radix.keys()
[
<<1, 1, 1>>,
<<2, 2, 2>>
]
Specs
Return the prefixes stored in the radix tree(s) in trie
for given type
.
Where type
is a single maxlen or a list thereof.
Example
iex> ipt = new()
...> |> put("1.1.1.0/24", 1)
...> |> put("2.2.2.0/24", 2)
...> |> put("acdc:1975::/32", 3)
...> |> put("acdc:2021::/32", 4)
iex>
iex> keys(ipt, 32)
[
%Pfx{bits: <<1, 1, 1>>, maxlen: 32},
%Pfx{bits: <<2, 2, 2>>, maxlen: 32}
]
iex>
iex> keys(ipt, 128)
[
%Pfx{bits: <<0xacdc::16, 0x1975::16>>, maxlen: 128},
%Pfx{bits: <<0xacdc::16, 0x2021::16>>, maxlen: 128}
]
iex>
iex> keys(ipt, 48)
[]
iex>
iex> keys(ipt, [32, 48, 128])
[
%Pfx{bits: <<1, 1, 1>>, maxlen: 32},
%Pfx{bits: <<2, 2, 2>>, maxlen: 32},
%Pfx{bits: <<0xacdc::16, 0x1975::16>>, maxlen: 128},
%Pfx{bits: <<0xacdc::16, 0x2021::16>>, maxlen: 128}
]
Specs
Return all the prefix,value-pairs whose prefix is a prefix for the given
search prefix
.
This returns the less specific entries that enclose the given search
prefix
. Note that any bitstring is always a prefix of itself. So, if
present, the search key will be included in the result.
If prefix
is not present or not valid, or cannot be encoded as an Ipv4 op
IPv6 Pfx.t/0
, an empty list is returned.
Example
iex> ipt = new()
...> |> put("1.1.1.0/25", "A25-lower")
...> |> put("1.1.1.128/25", "A25-upper")
...> |> put("1.1.1.0/30", "A30")
...> |> put("1.1.2.0/24", "B24")
iex>
iex> less(ipt, "1.1.1.0/30")
[
{"1.1.1.0/30", "A30"},
{"1.1.1.0/25", "A25-lower"},
]
iex> less(ipt, "2.2.2.2")
[]
Specs
Return the prefix,value-pair, whose prefix is the longest match for given search prefix
.
Returns nil if there is no match for search prefix
.
Silently ignores any errors when encoding the given search prefix
by returning nil.
Example
Specs
Return all the prefix,value-pairs where the search prefix
is a prefix for
the stored prefix.
This returns the more specific entries that are enclosed by given search
prefix
. Note that any bitstring is always a prefix of itself. So, if
present, the search prefix
will be included in the result.
If prefix
is not valid, or cannot be encoded as an Ipv4 op IPv6 t:Pfx.t
, nil
is returned.
Example
iex> ipt = new()
...> |> put("1.1.1.0/25", "A25-lower")
...> |> put("1.1.1.128/25", "A25-upper")
...> |> put("1.1.1.0/30", "A30")
...> |> put("1.1.2.0/24", "B24")
iex>
iex> more(ipt, "1.1.1.0/24")
[
{"1.1.1.0/30", "A30"},
{"1.1.1.0/25", "A25-lower"},
{"1.1.1.128/25", "A25-upper"}
]
Specs
new() :: t()
Create an new, empty Iptrie.
Example
iex> Iptrie.new()
%Iptrie{}
Specs
Create a new Iptrie, populated via a list of {prefix/0
, any/0
}-pairs.
Example
iex> ipt = Iptrie.new([{"1.1.1.0/24", "net1"}, {"acdc:1975::/32", "TNT"}])
iex> Map.get(ipt, 32)
{0, [{<<1, 1, 1>>, "net1"}], nil}
iex> Map.get(ipt, 128)
{0, nil, [{<<172, 220, 25, 117>>, "TNT"}]}
Specs
Populate the trie
with a list of {prefix,value}-pairs.
This always uses an exact match for prefix, updating its value if it exists. Any errors are silently ignored as the trie is always returned.
Example
iex> ipt = new([{"1.1.1.0/24", 0}, {"1.1.1.1", 0}, {"1.1.1.1", "x"}])
iex>
iex> get(ipt, "1.1.1.1")
{"1.1.1.1", "x"}
Specs
Puts value
under prefix
in the trie
.
This always uses an exact match for prefix, replacing its value if it exists. Any errors are silently ignored as the tree is always returned.
Example
iex> ipt = new()
...> |> put("1.1.1.0/24", 0)
...> |> put("1.1.1.1", 1)
...> |> put("1.1.1.1", "x")
iex>
iex> get(ipt, "1.1.1.1")
{"1.1.1.1", "x"}
Specs
radix(t(), integer()) :: Radix.tree()
Return the radix tree for given type
or a new empty tree.
If there is no Radix
tree for given type
, an empty radix will be returned
without storing it in the trie
.
Example
iex> ipt = new()
...> |> put("1.1.1.0/24", 1)
...> |> put("2.2.2.0/24", 2)
...> |> put("acdc:1975::/32", 3)
...> |> put("acdc:2021::/32", 4)
iex>
iex> radix(ipt, 32)
{0, {6, [{<<1, 1, 1>>, 1}], [{<<2, 2, 2>>, 2}]}, nil}
iex>
iex> radix(ipt, 128)
{0, nil, {18, [{<<172, 220, 25, 117>>, 3}], [{<<172, 220, 32, 33>>, 4}]}}
iex> radix(ipt, 48)
{0, nil, nil}
Specs
Invoke fun
on all prefix,value-pairs in all radix trees in the trie
.
Example
iex> ipt = new()
...> |> put("1.1.1.0/24", 1)
...> |> put("2.2.2.0/24", 2)
...> |> put("acdc:1975::/32", 3)
...> |> put("acdc:2021::/32", 4)
iex>
iex> reduce(ipt, 0, fn _key, value, acc -> acc + value end)
10
iex>
iex> reduce(ipt, %{}, fn key, value, acc -> Map.put(acc, key, value) end)
%{<<1, 1, 1>> => 1, <<2, 2, 2>> => 2, <<172, 220, 25, 117>> => 3, <<172, 220, 32, 33>> => 4}
Specs
reduce( t(), non_neg_integer() | [non_neg_integer()], any(), (bitstring(), any(), any() -> any()) ) :: any()
Invoke fun
on each prefix,value-pair in the radix tree for given type
This simply wraps Radix.reduce/3
for the radix tree in trie
at given
type
. The function fun
is called with the radix key, value and acc
accumulator and should return an updated accumulator. The result is the last
acc
accumulator returned.
If type
is a list of type
's, the acc
accumulator is updated across all
radix trees of type
's. Probably not entirely usefull, but there you go.
Example
iex> ipt = new()
...> |> put("1.1.1.0/24", 1)
...> |> put("2.2.2.0/24", 2)
...> |> put("acdc:1975::/32", 3)
...> |> put("acdc:2021::/32", 4)
iex>
iex> reduce(ipt, 32, 0, fn _key, value, acc -> acc + value end)
3
iex> reduce(ipt, 48, 0, fn _key, value, acc -> acc + value end)
0
iex> reduce(ipt, 128, 0, fn _key, value, acc -> acc + value end)
7
iex>
iex> reduce(ipt, [32, 48, 128], 0, fn _key, value, acc -> acc + value end)
10
Specs
Return all prefix,value-pairs from all available radix trees in trie
.
Example
iex> ipt = new()
...> |> put("1.1.1.0/24", 1)
...> |> put("2.2.2.0/24", 2)
...> |> put("acdc:1975::/32", 3)
...> |> put("acdc:2021::/32", 4)
iex>
iex> to_list(ipt)
[
{%Pfx{bits: <<1, 1, 1>>, maxlen: 32}, 1},
{%Pfx{bits: <<2, 2, 2>>, maxlen: 32}, 2},
{%Pfx{bits: <<0xacdc::16, 0x1975::16>>, maxlen: 128}, 3},
{%Pfx{bits: <<0xacdc::16, 0x2021::16>>, maxlen: 128}, 4}
]
Specs
to_list(t(), non_neg_integer() | [non_neg_integer()]) :: [{prefix(), any()}]
Returns the prefix,value-pairs from the radix trees in trie
for given
type
(s).
If the radix tree for type
does not exist, an empty list is returned.
If type
is a list of types, a flat list of all prefix,value-pairs of all
radix trees of given types is returned.
Example
iex> ipt = new()
...> |> put("1.1.1.0/24", 1)
...> |> put("2.2.2.0/24", 2)
...> |> put("acdc:1975::/32", 3)
...> |> put("acdc:2021::/32", 4)
iex>
iex> to_list(ipt, 32)
[
{%Pfx{bits: <<1, 1, 1>>, maxlen: 32}, 1},
{%Pfx{bits: <<2, 2, 2>>, maxlen: 32}, 2}
]
iex> to_list(ipt, [32, 48, 128])
[
{%Pfx{bits: <<1, 1, 1>>, maxlen: 32}, 1},
{%Pfx{bits: <<2, 2, 2>>, maxlen: 32}, 2},
{%Pfx{bits: <<0xacdc::16, 0x1975::16>>, maxlen: 128}, 3},
{%Pfx{bits: <<0xacdc::16, 0x2021::16>>, maxlen: 128}, 4}
]
Specs
Lookup prefix
and update the matched entry, only if found.
Uses longest prefix match, so search prefix
is usually matched by some less
specific prefix. If matched, fun
is called on its value. If
prefix
had no longest prefix match, the trie
is returned unchanged.
Example
iex> ipt = new()
...> |> put("1.1.1.0/24", 0)
...> |> update("1.1.1.0", fn x -> x + 1 end)
...> |> update("1.1.1.1", fn x -> x + 1 end)
...> |> update("2.2.2.2", fn x -> x + 1 end)
iex> get(ipt, "1.1.1.0/24")
{"1.1.1.0/24", 2}
iex> lookup(ipt, "2.2.2.2")
nil
Specs
Lookup prefix
and, if found, update its value or insert the default.
Uses longest prefix match, so search prefix
is usually matched by some less
specific prefix. If matched, fun
is called on the entry's value. If
prefix
had no longest prefix match, the default is inserted and fun
is
not called.
Example
iex> ipt = new()
...> |> update("1.1.1.0/24", 0, fn x -> x + 1 end)
...> |> update("1.1.1.0", 0, fn x -> x + 1 end)
...> |> update("1.1.1.1", 0, fn x -> x + 1 end)
...> |> update("2.2.2.2", 0, fn x -> x + 1 end)
iex> lookup(ipt, "1.1.1.2")
{"1.1.1.0/24", 2}
iex>
iex> # probably not what you wanted:
iex>
iex> lookup(ipt, "2.2.2.2")
{"2.2.2.2", 0}
Specs
Return all the values stored in all radix trees in trie
.
Example
iex> ipt = new()
...> |> put("1.1.1.0/24", 1)
...> |> put("2.2.2.0/24", 2)
...> |> put("acdc:1975::/32", 3)
...> |> put("acdc:2021::/32", 4)
iex>
iex> values(ipt)
[1, 2, 3, 4]
Specs
Return the values stored in the radix trees in trie
for given type
.
Where type
is a either single maxlen or a list thereof.
Example
iex> ipt = new()
...> |> put("1.1.1.0/24", 1)
...> |> put("2.2.2.0/24", 2)
...> |> put("acdc:1975::/32", 3)
...> |> put("acdc:2021::/32", 4)
iex>
iex> values(ipt, 32)
[1, 2]
iex>
iex> values(ipt, 128)
[3, 4]
iex>
iex> values(ipt, 48)
[]
iex>
iex> values(ipt, [32, 48, 128])
[1, 2, 3, 4]