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.
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.
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
License
This project is licensed under GNU Lesser General Public License version 3 or later.