=========================================
``inet`` - an IP address mangling library
=========================================
``inet`` is meant to make it simple and fun to do IP address calculations,
subnet math and similar IP address related arithmetics.
::
local inet = require 'inet'
-- get first address of the 3rd /64 in a /56
inet('2001:db8::/56') / 64 * 3 + 1 -- returns inet('2001:db8:0:3::1/64')
-- get last /64 in a /56
inet('2001:db8::/56') * 1 / 64 * -1 -- returns inet('2001:db8:0:ff::/64')
All examples on this page, are tested when ``make test`` is run.
Dependencies
============
- Lua_ version 5.2, 5.3 or 5.4 beta
- LPeg_ - Parsing Expression Grammars For Lua
Install
=======
You can install lua-inet in several ways,
after all it is just a pure Lua library.
From git
--------
System-wide
~~~~~~~~~~~
::
$ sudo -i
# apt install lua-lpeg
# git clone https://git.2e8.dk/lua-inet /opt/lua-inet
# mkdir -p /usr/local/share/lua/5.3
# ln -s /opt/lua-inet/lua/inet /usr/local/share/lua/5.3/inet
Local user
~~~~~~~~~~
Clone this repo to somewhere, then include this repo's ``lua/`` in your
``package.path``, with something like this::
package.path = '/PATH/TO/lua/?.lua;/PATH/TO/lua/?/init.lua;'..package.path
See also the `LUA_INIT`_ environment variable.
LuaRocks
--------
If you want you can also use LuaRocks_::
$ luarocks install inet
API
===
``inet`` module
---------------
======================= =====================================================
API Description
======================= =====================================================
``inet(...)`` Parse address and build ``inet4`` or ``inet6`` table
``inet.is(foo)`` is ``foo`` an ``inet*`` table?
``inet.is4(foo)`` is ``foo`` an ``inet4`` table?
``inet.is6(foo)`` is ``foo`` an ``inet6`` table?
``inet.is_set(foo)`` is ``set`` table?
``inet.set()`` get new empty ``set`` instance.
``inet.mixed_networks`` IPv6 mixed notation ``set``
``inet.lpeg`` LPeg_ patterns
``inet.version`` API version (currently ``1``)
======================= =====================================================
IPv6 mixed notation configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``inet.mixed_networks`` can be used to configure which IPv6 networks
should use mixed notation, ie. last 32 bits formatted as IPv4,
as per `RFC 5952`_ section 5.
Initially the set contains these well-known networks:
::
inet.mixed_networks:list() -- returns {
inet('::ffff:0:0/96'), -- RFC 5156
inet('64:ff9b::/96'), -- RFC 6052
}
Common ``inet*`` API
--------------------
================== ======================================
Operator Description
================== ======================================
``+`` Addition
``-`` Subtract
``/`` Change mask (absolute)
``^`` Change mask (relative)
``*`` Move network
``<`` is less than
``<=`` is less than or equal
``==`` equals
``>=`` is greater or equal
``>`` is greater than
``~=`` not equals
``#`` number of network bits
``:contains()`` contains
``:network()`` extract network part of address
``tostring(net)`` convert to network
``:ipstring()`` ip as string without prefix
``:cidrstring()`` format CIDR notation
``:netmask()`` generate netmask as an address
``:hostmask()`` generate hostmask as an address
``:flip()`` flip the least significant network bit
``:bits()`` return the address bits in a table
``:subnets()`` return the amount of /n subnets
``:family()`` return the address family (number)
``:full_family()`` return the address family (string)
================== ======================================
Additional ``inet6`` methods
-----------------------------
inet6 has these additional methods:
================ =====================================
Operator Description
================ =====================================
``:ipstring4()`` string formatted in mixed notation
``:ipstring6()`` string formatted in standard notation
================ =====================================
``set`` API
-----------
================== =================================
API Description
================== =================================
``set:list()`` list networks in set
``set:add()`` add network to set
``set:remove()`` remove network from set
``set:contains()`` is network contained in set?
``set:flush()`` empty the set
================== =================================
Creating
--------
There is a multitude of different ways to create ``inet*`` instances.
::
-- IPv4
inet('192.0.2.0') -- returns inet('192.0.2.0/32')
inet('192.0.2.0', 24) -- returns inet('192.0.2.0/24')
inet({192,0,2,0}, 24) -- returns inet('192.0.2.0/24')
inet(3221225985, 32) -- returns inet('192.0.2.1')
-- IPv6
inet('2001:db8::') -- returns inet('2001:db8::/128')
inet('2001:db8::', 56) -- returns inet('2001:db8::/56')
-- its possible to wrap inet instances
inet(inet('192.0.2.0/24')) -- returns inet('192.0.2.0/24')
inet(inet('2001:db8::')) -- returns inet('2001:db8::')
-- when wrapped additional mask takes precedence
inet(inet('192.0.2.0/32'), 24) -- returns inet('192.0.2.0/24')
inet(inet('2001:db8::/128'), 64) -- returns inet('2001:db8::/64')
-- various error examples
inet('192.0.2.0/24', 32) -- returns nil, 'multiple masks supplied'
inet('2001:db8::/64', 56) -- returns nil, 'multiple masks supplied'
inet('foobar') -- returns nil, 'parse error'
inet('foo::bar') -- returns nil, 'parse error'
inet('192.0.2.0', 33) -- returns nil, 'invalid mask'
inet('2001:db8::', 129) -- returns nil, 'invalid mask'
Usable in LPeg patterns
~~~~~~~~~~~~~~~~~~~~~~~
Internally ``inet`` uses LPeg_ to parse IP adresses, but the
LPeg patterns are also available for embedding into your
own LPeg patterns.
::
local lpeg = require 'lpeg'
local P = lpeg.P
local http_host_v6 = P('[') * inet.lpeg.ipv6 * P(']')
local http_host = http_host_v6 + inet.lpeg.ipv4
local my_http_uri = P('https://') * http_host * P('/') * -1
my_http_uri:match('https://192.0.2.0/') -- returns inet('192.0.2.0')
my_http_uri:match('https://[2001:db8::1]/') -- returns inet('2001:db8::1')
inet.lpeg.ipv6:match('2001:db8::/64') -- returns inet('2001:db8::/64')
inet.lpeg.ipv4:match('192.0.2.0/24') -- returns inet('192.0.2.0/24')
inet.lpeg.ip:match('2001:db8::/64') -- returns inet('2001:db8::/64')
inet.lpeg.ip:match('192.0.2.0/24') -- returns inet('192.0.2.0/24')
Mangling
--------
All of the ``inet*`` mangling operators and methods returns a new instance, and does
not modify the original instance.
``foo + bar``
~~~~~~~~~~~~~
Addition
::
inet('192.0.2.0') + 24 -- returns inet('192.0.2.24')
inet('2001:db8::/64') + 5 -- returns inet('2001:db8::5/64')
-- mixed networks special:
inet('::ffff:0.0.0.0/96') + inet('192.0.2.24') -- returns inet('::ffff:192.0.2.24')
inet('192.0.2.24') + inet('::ffff:0.0.0.0/96') -- returns inet('::ffff:192.0.2.24')
``foo - bar``
~~~~~~~~~~~~~
Subtraction
::
inet('2001:db8::5/64') - 5 -- returns inet('2001:db8::/64')
inet('192.0.2.24') - inet('192.0.2.0') -- returns 24
inet('2001:db8::5/64') - inet('2001:db8::') -- returns 5
-- by calling the operator method directly additional debuging info are available:
inet('2001:db8::5/64') - inet('ffff::') -- returns nil
inet('2001:db8::5/64'):__sub(inet('ffff::'))
-- returns nil, 'out of range', { -57342, 3512, 0, 0, 0, 0, 0, 5 }
-- mixed networks special:
inet('::ffff:192.0.2.24') - inet('::ffff:0.0.0.0/96') -- returns inet('192.0.2.24')
``foo / bar``
~~~~~~~~~~~~~
Change mask (absolute)
::
inet('2001:db8::/32') / 64 -- returns inet('2001:db8::/64')
inet('2001:db8::1/32') / 64 -- returns inet('2001:db8::1/64')
``foo ^ bar``
~~~~~~~~~~~~~
Change mask (relative)
::
inet('2001:db8::/64') ^ -8 -- returns inet('2001:db8::/56')
inet('2001:db8::2/48') ^ 8 -- returns inet('2001:db8::2/56')
``foo * bar``
~~~~~~~~~~~~~
Move network
::
inet('2001:db8::/64') * 1 -- returns inet('2001:db8:0:1::/64')
inet('2001:db8:1::/64') * -16 -- returns inet('2001:db8:0:fff0::/64')
``foo:network()``
~~~~~~~~~~~~~~~~~
Reset the host bits.
::
inet('192.0.2.4/24'):network() -- returns inet('192.0.2.0/24')
``foo:netmask()``
~~~~~~~~~~~~~~~~~
Build an IP address mask with the netmask of ``foo``.
::
inet('192.0.2.0/24'):netmask() -- returns inet('255.255.255.0')
inet('2001:db8::/52'):netmask() -- returns inet('ffff:ffff:ffff:f000::')
inet('2001:db8::/56'):netmask() -- returns inet('ffff:ffff:ffff:ff00::')
inet('2001:db8::/64'):netmask() -- returns inet('ffff:ffff:ffff:ffff::')
``foo:hostmask()``
~~~~~~~~~~~~~~~~~~
Build an IP address mask with the netmask of ``foo``.
::
inet('192.0.2.0/24'):hostmask() -- returns inet('0.0.0.255')
inet('2001:db8::/64'):hostmask() -- returns inet('::ffff:ffff:ffff:ffff')
inet('2001:db8::/116'):hostmask() -- returns inet('::fff')
inet('2001:db8::/112'):hostmask() -- returns inet('::ffff')
``foo:flip()``
~~~~~~~~~~~~~~
Flip the least significant network bit, to find the complimentary network.
::
inet('192.0.2.0/26'):flip() -- returns inet('192.0.2.64/26')
inet('192.0.2.64/26'):flip() -- returns inet('192.0.2.0/26')
inet('192.0.2.0/25'):flip() -- returns inet('192.0.2.128/25')
Tests
-----
``<``, ``<=``, ``>=`` and ``>``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compares ``inet`` instances according to the sort order.
::
inet('192.0.2.0/26') < inet('192.0.2.64/26') -- returns true
inet('192.0.2.0/24') < inet('192.0.2.0/26') -- returns true
inet('192.0.2.0/26') < inet('192.0.2.1/26') -- returns true
inet('192.0.2.0/26') < inet('2001:db8::1/64') -- returns true
inet('2001:db8::1/64') < inet('192.0.2.0/26') -- returns false
``==`` and ``~=``
~~~~~~~~~~~~~~~~~
Checks if two ``inet`` instances are of the same family, address and mask, or not.
::
inet('192.0.2.0/24') == inet('192.0.2.0/24') -- returns true
inet('192.0.2.0/24') ~= inet('192.0.2.0/24') -- returns false
inet('192.0.2.0/24') == inet('192.0.2.0/26') -- returns false
inet('192.0.2.0/24') == inet('192.0.2.1/24') -- returns false
inet('192.0.2.0/24') == inet('2001:db8::') -- returns false
``#foo``
~~~~~~~~
Returns the amount of significant network bits.
::
#inet('192.0.2.0/24') -- returns 24
#inet('2001:db8::/48') -- returns 48
``foo:contains(bar)``
~~~~~~~~~~~~~~~~~~~~~~
``:contains()`` tests for subnet inclusion. It considers only the network parts of the two addresses, ignoring any host part, and determine whether one network part is a subnet of the other.
::
inet('192.0.2.0/24'):contains(inet('192.0.2.64/26')) -- returns true
inet('192.0.2.0/24'):contains(inet('192.0.2.0/26')) -- returns true
inet('192.0.2.0/24'):contains(inet('192.0.2.0/24')) -- returns false
inet('192.0.2.64/26'):contains(inet('192.0.2.0/24')) -- returns false
Text representation
-------------------
``inet6`` implements `RFC 5952`_ providing a standardized textual representation of IPv6 addresses.
``tostring(foo)``
~~~~~~~~~~~~~~~~~
String representation of ``foo``. If ``foo`` represents a host address, then just the address is returned, otherwise CIDR notation is used.
::
tostring(inet('192.0.2.0/24')) -- returns '192.0.2.0/24'
tostring(inet('192.0.2.0/32')) -- returns '192.0.2.0'
For IPv6, if the network is contained by ``inet.mixed_networks``, then mixed notation is used.
``foo:cidrstring(foo)``
~~~~~~~~~~~~~~~~~~~~~~~
Like ``tostring(foo)``, but always return the address in CIDR notation, as specified in `RFC 4632`_.
::
inet('192.0.2.0/32'):cidrstring() -- returns '192.0.2.0/32'
``foo:ipstring()``
~~~~~~~~~~~~~~~~~~
Like ``tostring(foo)``, but always returns the only the IP address, and not the mask.
::
inet('192.0.2.0/24'):ipstring() -- returns '192.0.2.0'
``foo:ipstring4()``
~~~~~~~~~~~~~~~~~~~
Like ``foo:ipstring()``, but always uses mixed notation.
::
inet('2001:db8::c000:218'):ipstring() -- returns '2001:db8::c000:218'
inet('2001:db8::c000:218'):ipstring4() -- returns '2001:db8::192.0.2.24'
``foo:ipstring6()``
~~~~~~~~~~~~~~~~~~~
Like ``tostring(foo)``, but never uses mixed notation.
::
inet('::ffff:192.0.2.24'):ipstring() -- returns '::ffff:192.0.2.24'
inet('::ffff:192.0.2.24'):ipstring6() -- returns '::ffff:c000:218'
``foo:bits(n)``
~~~~~~~~~~~~~~~
Return the IP as a table with elements of ``n`` bits each.
Valid values for ``n`` are ``1``, ``2``, ``4``, ``8``, ``16`` or ``32``.
::
inet('192.0.2.24'):bits(32) -- returns { 3221226008 }
inet('192.0.2.24'):bits(16) -- returns { 49152, 536 }
inet('192.0.2.24'):bits(8) -- returns { 192, 0, 2, 24 }
inet('192.0.2.24'):bits(4) -- returns { 12, 0, 0, 0, 0, 2, 1, 8 }
inet('192.0.2.24'):bits(1) -- returns
{ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0 }
inet('2001:db8::42/64'):bits(32) -- returns { 536939960, 0, 0, 66 }
inet('::ffff:192.0.2.24'):bits(32) -- returns { 0, 0, 65535, 3221226008 }
inet('2001:db8::42/64'):bits(16) -- returns { 8193, 3512, 0, 0, 0, 0, 0, 66 }
inet('2001:db8::42/64'):bits(8) -- returns
{ 32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66 }
inet('2001:db8::42/64'):bits(4) -- returns
{ 2, 0, 0, 1, 0, 13, 11, 8, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2 }
``foo:subnets(n)``
~~~~~~~~~~~~~~~~~~
::
inet('192.0.2.0/24'):subnets(26) -- returns 4
inet('192.0.2.0/25'):subnets(26) -- returns 2
inet('192.0.2.0/26'):subnets(26) -- returns 1
inet('192.0.2.0/27'):subnets(26) -- returns 0.5
inet('192.0.2.0/28'):subnets(26) -- returns 0.25
inet('2001:db8::/48'):subnets(56) -- returns 256
inet('2001:db8::/56'):subnets(64) -- returns 256
inet('2001:db8::/48'):subnets(64) -- returns 65536
inet('2001:db8::/64'):subnets(56) -- returns 0.00390625
``foo:family(n)``
~~~~~~~~~~~~~~~~~
::
inet('192.0.2.0/24'):family() -- returns 4
inet('2001:db8::/64'):family() -- returns 6
``foo:full_family(n)``
~~~~~~~~~~~~~~~~~~~~~~
::
inet('192.0.2.0/24'):full_family() -- returns 'ipv4'
inet('2001:db8::/64'):full_family() -- returns 'ipv6'
Sets
----
::
local foo = inet.set()
``set:list()``
~~~~~~~~~~~~~~
List networks in set.
::
foo:list() -- returns {}
``set:add(foo)``
~~~~~~~~~~~~~~~~
Add network to set.
::
foo:add(inet('2001:db8::/48')) -- returns true
foo:list() -- returns { inet('2001:db8::/48') }
foo:add(inet('2001:db8:1::/48')) -- returns true
foo:list() -- returns { inet('2001:db8::/47') }
foo:add(inet('192.0.2.0/24')) -- returns nil, 'invalid family'
``set:remove(foo)``
~~~~~~~~~~~~~~~~~~~
Remove network from set.
::
foo:remove(inet('2001:db8:1::/48')) -- returns true
foo:remove(inet('2001:db8:1::/48')) -- returns false
foo:list() -- returns { inet('2001:db8::/48') }
foo:remove(inet('2001:db8:0:4200::/56')) -- returns true
foo:list() -- returns {
inet('2001:db8::/50'),
inet('2001:db8:0:4000::/55'),
inet('2001:db8:0:4300::/56'),
inet('2001:db8:0:4400::/54'),
inet('2001:db8:0:4800::/53'),
inet('2001:db8:0:5000::/52'),
inet('2001:db8:0:6000::/51'),
inet('2001:db8:0:8000::/49'),
}
foo:add(inet('2001:db8:0:4200::/56')) -- returns true
foo:list() -- returns { inet('2001:db8::/48') }
``set:contains(foo)``
~~~~~~~~~~~~~~~~~~~~~
If the network is contained or equal to a network in the set, then
the matching network will be returned, otherwise false is.
::
foo:contains(inet('2001:db8::')) -- returns inet('2001:db8::/48')
foo:contains(inet('2001:db8::/32')) -- returns false
foo:contains(inet('2001:db8::/48')) -- returns inet('2001:db8::/48')
foo:contains(inet('2001:db8:1:2:3:4:5:6')) -- returns false
``set:flush()``
~~~~~~~~~~~~~~~
Empties the set.
::
foo:flush() -- returns true
foo:list() -- returns {}
History
=======
* ``inet`` was brewed in Labitat_ in late 2014.
* Since then it has been battle-tested in production at the danish ISP Fiberby_.
* In July 2019 ``inet`` was finally polished up and released to the world.
License
=======
This project is licensed under `GNU Lesser General Public License version 3`_ or later.
.. _Labitat: https://labitat.dk/
.. _Fiberby: https://peeringdb.com/asn/42541
.. _Lua: http://www.lua.org/
.. _LPeg: http://www.inf.puc-rio.br/~roberto/lpeg/
.. _LuaRocks: https://luarocks.org/modules/asbjorn/inet
.. _LUA_INIT: http://www.lua.org/manual/5.3/manual.html#pdf-LUA_INIT
.. _RFC 4632: https://tools.ietf.org/html/rfc4632
.. _RFC 5952: https://tools.ietf.org/html/rfc5952
.. _GNU Lesser General Public License version 3: https://www.gnu.org/licenses/lgpl-3.0.en.html