aboutsummaryrefslogblamecommitdiffstats
path: root/README.rst
blob: b227b1a1f03ebef6694a02dd1cdb29b4e37e1b1d (plain) (tree)
1
2
3
4
5
6



                                         

                                                                          










                                                                           
                                                                



            
                                   

                                             




































                                                                            









                                                                             





                                                       
                                      









                                                                             







                                                     



                    



























                                                         





























































                                                                     






















                                                                              












                                                                                    
                                                         

                                                             


                                                                                     



             
           




                                                             






                                                                                    
                                                                    


                                                                                     

















































                                                                       



                                                                          
 
                  
                  










                                                                            























                                                                          

                                                                 
































































                                                                                                                                                                                              
                                                                                     









                                                        




                                                                          




                                                      




                                                                      


























                                                                               
















                                                         






                                             
 







                                                       


    



                        


              
                     
 



                          


                
                   
 







                                                                


                   
                        
 

















                                                          


                                                       


                     

                                                                  
 

  
                                                                             
                                                             
                                                                             

                                                             




                
  
 
                             
                           
 















                                                                                       

                                                                    


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