X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fcore%2Fluasrc%2Fip.lua;h=60ca090135962eb24daf7c68ab3982475d2333b7;hp=855bc4a816258ca57e7c21d71436b6ed4bdfec93;hb=481cd6feb7583e42fda746d369ab6744af705fbb;hpb=9587d9db023db097fbf53e391cf8afbbe9d240fd diff --git a/libs/core/luasrc/ip.lua b/libs/core/luasrc/ip.lua index 855bc4a81..60ca09013 100644 --- a/libs/core/luasrc/ip.lua +++ b/libs/core/luasrc/ip.lua @@ -14,15 +14,23 @@ $Id$ ]]-- +--- LuCI IP calculation library. module( "luci.ip", package.seeall ) -require("bit") -require("luci.util") +require "nixio" +local bit = nixio.bit +local util = require "luci.util" -LITTLE_ENDIAN = not luci.util.bigendian() +--- Boolean; true if system is little endian +LITTLE_ENDIAN = not util.bigendian() + +--- Boolean; true if system is big endian BIG_ENDIAN = not LITTLE_ENDIAN +--- Specifier for IPv4 address family FAMILY_INET4 = 0x04 + +--- Specifier for IPv6 address family FAMILY_INET6 = 0x06 @@ -30,6 +38,7 @@ local function __bless(x) return setmetatable( x, { __index = luci.ip.cidr, __add = luci.ip.cidr.add, + __sub = luci.ip.cidr.sub, __lt = luci.ip.cidr.lower, __eq = luci.ip.cidr.equal, __le = @@ -39,23 +48,54 @@ local function __bless(x) } ) end +local function __array16( x, family ) + local list + + if type(x) == "number" then + list = { bit.rshift(x, 16), bit.band(x, 0xFFFF) } + + elseif type(x) == "string" then + if x:find(":") then x = IPv6(x) else x = IPv4(x) end + if x then + assert( x[1] == family, "Can't mix IPv4 and IPv6 addresses" ) + list = { unpack(x[2]) } + end + + elseif type(x) == "table" and type(x[2]) == "table" then + assert( x[1] == family, "Can't mix IPv4 and IPv6 addresses" ) + list = { unpack(x[2]) } + + elseif type(x) == "table" then + list = { unpack(x) } + end + + assert( list, "Invalid operand" ) + + return list +end + local function __mask16(bits) - return bit.lshift( - bit.rshift( 0xFFFF, 16 - bits % 16 ), - 16 - bits % 16 - ) + return bit.lshift( bit.rshift( 0xFFFF, 16 - bits % 16 ), 16 - bits % 16 ) end -local function __length(family) - if family == FAMILY_INET4 then - return 32 - else - return 128 - end +local function __not16(bits) + return bit.band( bit.bnot( __mask16(bits) ), 0xFFFF ) +end + +local function __maxlen(family) + return ( family == FAMILY_INET4 ) and 32 or 128 +end + +local function __sublen(family) + return ( family == FAMILY_INET4 ) and 30 or 127 end --- htons(), htonl(), ntohs(), ntohl() +--- Convert given short value to network byte order on little endian hosts +-- @param x Unsigned integer value between 0x0000 and 0xFFFF +-- @return Byte-swapped value +-- @see htonl +-- @see ntohs function htons(x) if LITTLE_ENDIAN then return bit.bor( @@ -67,6 +107,11 @@ function htons(x) end end +--- Convert given long value to network byte order on little endian hosts +-- @param x Unsigned integer value between 0x00000000 and 0xFFFFFFFF +-- @return Byte-swapped value +-- @see htons +-- @see ntohl function htonl(x) if LITTLE_ENDIAN then return bit.bor( @@ -78,10 +123,35 @@ function htonl(x) end end +--- Convert given short value to host byte order on little endian hosts +-- @class function +-- @name ntohs +-- @param x Unsigned integer value between 0x0000 and 0xFFFF +-- @return Byte-swapped value +-- @see htonl +-- @see ntohs ntohs = htons + +--- Convert given short value to host byte order on little endian hosts +-- @class function +-- @name ntohl +-- @param x Unsigned integer value between 0x00000000 and 0xFFFFFFFF +-- @return Byte-swapped value +-- @see htons +-- @see ntohl ntohl = htonl +--- Parse given IPv4 address in dotted quad or CIDR notation. If an optional +-- netmask is given as second argument and the IP address is encoded in CIDR +-- notation then the netmask parameter takes precedence. If neither a CIDR +-- encoded prefix nor a netmask parameter is given, then a prefix length of +-- 32 bit is assumed. +-- @param address IPv4 address in dotted quad or CIDR notation +-- @param netmask IPv4 netmask in dotted quad notation (optional) +-- @return luci.ip.cidr instance or nil if given address was invalid +-- @see IPv6 +-- @see Hex function IPv4(address, netmask) address = address or "0.0.0.0/0" @@ -89,11 +159,12 @@ function IPv4(address, netmask) local data = {} local prefix = address:match("/(.+)") + address = address:gsub("/.+","") + address = address:gsub("^%[(.*)%]$", "%1"):upper():gsub("^::FFFF:", "") if netmask then prefix = obj:prefix(netmask) elseif prefix then - address = address:gsub("/.+","") prefix = tonumber(prefix) if not prefix or prefix < 0 or prefix > 32 then return nil end else @@ -119,6 +190,16 @@ function IPv4(address, netmask) end end +--- Parse given IPv6 address in full, compressed, mixed or CIDR notation. +-- If an optional netmask is given as second argument and the IP address is +-- encoded in CIDR notation then the netmask parameter takes precedence. +-- If neither a CIDR encoded prefix nor a netmask parameter is given, then a +-- prefix length of 128 bit is assumed. +-- @param address IPv6 address in full/compressed/mixed or CIDR notation +-- @param netmask IPv6 netmask in full/compressed/mixed notation (optional) +-- @return luci.ip.cidr instance or nil if given address was invalid +-- @see IPv4 +-- @see Hex function IPv6(address, netmask) address = address or "::/0" @@ -126,11 +207,12 @@ function IPv6(address, netmask) local data = {} local prefix = address:match("/(.+)") + address = address:gsub("/.+","") + address = address:gsub("^%[(.*)%]$", "%1") if netmask then prefix = obj:prefix(netmask) elseif prefix then - address = address:gsub("/.+","") prefix = tonumber(prefix) if not prefix or prefix < 0 or prefix > 128 then return nil end else @@ -138,7 +220,7 @@ function IPv6(address, netmask) end local borderl = address:sub(1, 1) == ":" and 2 or 1 - local borderh, zeroh, chunk, block + local borderh, zeroh, chunk, block, i if #address > 45 then return nil end @@ -147,8 +229,8 @@ function IPv6(address, netmask) if not borderh then break end block = tonumber(address:sub(borderl, borderh - 1), 16) - if block and block <= 65535 then - table.insert(data, block) + if block and block <= 0xFFFF then + data[#data+1] = block else if zeroh or borderh - borderl > 1 then return nil end zeroh = #data + 1 @@ -160,9 +242,9 @@ function IPv6(address, netmask) chunk = address:sub(borderl) if #chunk > 0 and #chunk <= 4 then block = tonumber(chunk, 16) - if not block or block > 65535 then return nil end + if not block or block > 0xFFFF then return nil end - table.insert(data, block) + data[#data+1] = block elseif #chunk > 4 then if #data == 7 or #chunk > 15 then return nil end borderl = 1 @@ -175,7 +257,7 @@ function IPv6(address, netmask) if not block or block > 255 then return nil end if i == 1 or i == 3 then - table.insert(data, block * 256) + data[#data+1] = block * 256 else data[#data] = data[#data] + block end @@ -198,14 +280,24 @@ function IPv6(address, netmask) end end +--- Transform given hex-encoded value to luci.ip.cidr instance of specified +-- address family. +-- @param hex String containing hex encoded value +-- @param prefix Prefix length of CIDR instance (optional, default is 32/128) +-- @param family Address family, either luci.ip.FAMILY_INET4 or FAMILY_INET6 +-- @param swap Bool indicating whether to swap byteorder on low endian host +-- @return luci.ip.cidr instance or nil if given value was invalid +-- @see IPv4 +-- @see IPv6 function Hex( hex, prefix, family, swap ) family = ( family ~= nil ) and family or FAMILY_INET4 swap = ( swap == nil ) and true or swap - prefix = prefix or __length(family) + prefix = prefix or __maxlen(family) - local len = __length(family) + local len = __maxlen(family) local tmp = "" local data = { } + local i for i = 1, (len/4) - #hex do tmp = tmp .. '0' end @@ -220,26 +312,69 @@ function Hex( hex, prefix, family, swap ) for i = 1, ( len / 4 ), 4 do local n = tonumber( hex:sub( i, i+3 ), 16 ) if n then - table.insert( data, n ) + data[#data+1] = n else return nil end end - return __bless({ family, data, len }) + return __bless({ family, data, prefix }) end -cidr = luci.util.class() +--- LuCI IP Library / CIDR instances +-- @class module +-- @cstyle instance +-- @name luci.ip.cidr +cidr = util.class() +--- Test whether the instance is a IPv4 address. +-- @return Boolean indicating a IPv4 address type +-- @see cidr.is6 function cidr.is4( self ) return self[1] == FAMILY_INET4 end +--- Test whether this instance is an IPv4 RFC1918 private address +-- @return Boolean indicating whether this instance is an RFC1918 address +function cidr.is4rfc1918( self ) + if self[1] == FAMILY_INET4 then + return ((self[2][1] >= 0x0A00) and (self[2][1] <= 0x0AFF)) or + ((self[2][1] >= 0xAC10) and (self[2][1] <= 0xAC1F)) or + (self[2][1] == 0xC0A8) + end + return false +end + +--- Test whether this instance is an IPv4 link-local address (Zeroconf) +-- @return Boolean indicating whether this instance is IPv4 link-local +function cidr.is4linklocal( self ) + if self[1] == FAMILY_INET4 then + return (self[2][1] == 0xA9FE) + end + return false +end + +--- Test whether the instance is a IPv6 address. +-- @return Boolean indicating a IPv6 address type +-- @see cidr.is4 function cidr.is6( self ) return self[1] == FAMILY_INET6 end +--- Test whether this instance is an IPv6 link-local address +-- @return Boolean indicating whether this instance is IPv6 link-local +function cidr.is6linklocal( self ) + if self[1] == FAMILY_INET6 then + return (self[2][1] >= 0xFE80) and (self[2][1] <= 0xFEBF) + end + return false +end + +--- Return a corresponding string representation of the instance. +-- If the prefix length is lower then the maximum possible prefix length for the +-- corresponding address type then the address is returned in CIDR notation, +-- otherwise the prefix will be left out. function cidr.string( self ) local str if self:is4() then @@ -260,8 +395,16 @@ function cidr.string( self ) return str end +--- Test whether the value of the instance is lower then the given address. +-- This function will throw an exception if the given address has a different +-- family than this instance. +-- @param addr A luci.ip.cidr instance to compare against +-- @return Boolean indicating whether this instance is lower +-- @see cidr.higher +-- @see cidr.equal function cidr.lower( self, addr ) assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" ) + local i for i = 1, #self[2] do if self[2][i] ~= addr[2][i] then return self[2][i] < addr[2][i] @@ -270,8 +413,16 @@ function cidr.lower( self, addr ) return false end +--- Test whether the value of the instance is higher then the given address. +-- This function will throw an exception if the given address has a different +-- family than this instance. +-- @param addr A luci.ip.cidr instance to compare against +-- @return Boolean indicating whether this instance is higher +-- @see cidr.lower +-- @see cidr.equal function cidr.higher( self, addr ) assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" ) + local i for i = 1, #self[2] do if self[2][i] ~= addr[2][i] then return self[2][i] > addr[2][i] @@ -280,8 +431,16 @@ function cidr.higher( self, addr ) return false end +--- Test whether the value of the instance is equal to the given address. +-- This function will throw an exception if the given address is a different +-- family than this instance. +-- @param addr A luci.ip.cidr instance to compare against +-- @return Boolean indicating whether this instance is equal +-- @see cidr.lower +-- @see cidr.higher function cidr.equal( self, addr ) assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" ) + local i for i = 1, #self[2] do if self[2][i] ~= addr[2][i] then return false @@ -290,31 +449,33 @@ function cidr.equal( self, addr ) return true end +--- Return the prefix length of this CIDR instance. +-- @param mask Override instance prefix with given netmask (optional) +-- @return Prefix length in bit function cidr.prefix( self, mask ) local prefix = self[3] if mask then prefix = 0 + local stop = false - local obj = self:is4() and IPv4(mask) or IPv6(mask) + local obj = type(mask) ~= "table" + and ( self:is4() and IPv4(mask) or IPv6(mask) ) or mask - if not obj then - return nil - end + if not obj then return nil end - for i, block in ipairs(obj[2]) do - local pos = bit.lshift(1, 15) - for i=15, 0, -1 do - if bit.band(block, pos) == pos then - if not stop then - prefix = prefix + 1 - else - return nil - end - else - stop = true + local _, word + for _, word in ipairs(obj[2]) do + if word == 0xFFFF then + prefix = prefix + 16 + else + local bitmask = bit.lshift(1, 15) + while bit.band(word, bitmask) == bitmask do + prefix = prefix + 1 + bitmask = bit.lshift(1, 15 - (prefix % 16)) end - pos = bit.rshift(pos, 1) + + break end end end @@ -322,69 +483,191 @@ function cidr.prefix( self, mask ) return prefix end -function cidr.network( self ) +--- Return a corresponding CIDR representing the network address of this +-- instance. +-- @param bits Override prefix length of this instance (optional) +-- @return CIDR instance containing the network address +-- @see cidr.host +-- @see cidr.broadcast +-- @see cidr.mask +function cidr.network( self, bits ) local data = { } + bits = bits or self[3] - for i = 1, math.floor( self[3] / 16 ) do - table.insert( data, self[2][i] ) + local i + for i = 1, math.floor( bits / 16 ) do + data[#data+1] = self[2][i] end - table.insert( data, bit.band( self[2][1+#data], __mask16(self[3]) ) ) + if #data < #self[2] then + data[#data+1] = bit.band( self[2][1+#data], __mask16(bits) ) - for i = #data, #self[2] do - table.insert( data, 0 ) + for i = #data + 1, #self[2] do + data[#data+1] = 0 + end end - return __bless({ self[1], data, __length(self[1]) }) + return __bless({ self[1], data, __maxlen(self[1]) }) end +--- Return a corresponding CIDR representing the host address of this +-- instance. This is intended to extract the host address from larger subnet. +-- @return CIDR instance containing the network address +-- @see cidr.network +-- @see cidr.broadcast +-- @see cidr.mask function cidr.host( self ) - return __bless({ self[1], data, __length(self[1]) }) + return __bless({ self[1], self[2], __maxlen(self[1]) }) end +--- Return a corresponding CIDR representing the netmask of this instance. +-- @param bits Override prefix length of this instance (optional) +-- @return CIDR instance containing the netmask +-- @see cidr.network +-- @see cidr.host +-- @see cidr.broadcast function cidr.mask( self, bits ) local data = { } bits = bits or self[3] for i = 1, math.floor( bits / 16 ) do - table.insert( data, 0xFFFF ) + data[#data+1] = 0xFFFF end - table.insert( data, __mask16(bits) ) + if #data < #self[2] then + data[#data+1] = __mask16(bits) - for i = #data + 1, #self[2] do - table.insert( data, 0 ) + for i = #data + 1, #self[2] do + data[#data+1] = 0 + end end - return __bless({ self[1], data, __length(self[1]) }) + return __bless({ self[1], data, __maxlen(self[1]) }) +end + +--- Return CIDR containing the broadcast address of this instance. +-- @return CIDR instance containing the netmask, always nil for IPv6 +-- @see cidr.network +-- @see cidr.host +-- @see cidr.mask +function cidr.broadcast( self ) + -- IPv6 has no broadcast addresses (XXX: assert() instead?) + if self[1] == FAMILY_INET4 then + local data = { unpack(self[2]) } + local offset = math.floor( self[3] / 16 ) + 1 + + if offset <= #data then + data[offset] = bit.bor( data[offset], __not16(self[3]) ) + for i = offset + 1, #data do data[i] = 0xFFFF end + + return __bless({ self[1], data, __maxlen(self[1]) }) + end + end end +--- Test whether this instance fully contains the given CIDR instance. +-- @param addr CIDR instance to test against +-- @return Boolean indicating whether this instance contains the given CIDR function cidr.contains( self, addr ) assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" ) - local mask1 = self:mask() - local mask2 = addr:mask() - if mask1 <= mask2 then - return self:mask(addr:prefix()) == mask2 + + if self:prefix() <= addr:prefix() then + return self:network() == addr:network(self:prefix()) end return false end -function cidr.add( self, amount ) - local shorts = { bit.rshift(amount, 16), bit.band(amount, 0xFFFF) } +--- Add specified amount of hosts to this instance. +-- @param amount Number of hosts to add to this instance +-- @param inplace Boolen indicating whether to alter values inplace (optional) +-- @return CIDR representing the new address or nil on overflow error +-- @see cidr.sub +function cidr.add( self, amount, inplace ) + local pos local data = { unpack(self[2]) } + local shorts = __array16( amount, self[1] ) for pos = #data, 1, -1 do local add = ( #shorts > 0 ) and table.remove( shorts, #shorts ) or 0 if ( data[pos] + add ) > 0xFFFF then data[pos] = ( data[pos] + add ) % 0xFFFF - if pos > 2 then + if pos > 1 then data[pos-1] = data[pos-1] + ( add - data[pos] ) + else + return nil end else data[pos] = data[pos] + add end end - return __bless({ self[1], data, self[3] }) + if inplace then + self[2] = data + return self + else + return __bless({ self[1], data, self[3] }) + end +end + +--- Substract specified amount of hosts from this instance. +-- @param amount Number of hosts to substract from this instance +-- @param inplace Boolen indicating whether to alter values inplace (optional) +-- @return CIDR representing the new address or nil on underflow error +-- @see cidr.add +function cidr.sub( self, amount, inplace ) + local pos + local data = { unpack(self[2]) } + local shorts = __array16( amount, self[1] ) + + for pos = #data, 1, -1 do + local sub = ( #shorts > 0 ) and table.remove( shorts, #shorts ) or 0 + if ( data[pos] - sub ) < 0 then + data[pos] = ( sub - data[pos] ) % 0xFFFF + if pos > 1 then + data[pos-1] = data[pos-1] - ( sub + data[pos] ) + else + return nil + end + else + data[pos] = data[pos] - sub + end + end + + if inplace then + self[2] = data + return self + else + return __bless({ self[1], data, self[3] }) + end +end + +--- Return CIDR containing the lowest available host address within this subnet. +-- @return CIDR containing the host address, nil if subnet is too small +-- @see cidr.maxhost +function cidr.minhost( self ) + if self[3] <= __sublen(self[1]) then + -- 1st is Network Address in IPv4 and Subnet-Router Anycast Adresse in IPv6 + return self:network():add(1, true) + end +end + +--- Return CIDR containing the highest available host address within the subnet. +-- @return CIDR containing the host address, nil if subnet is too small +-- @see cidr.minhost +function cidr.maxhost( self ) + if self[3] <= __sublen(self[1]) then + local i + local data = { unpack(self[2]) } + local offset = math.floor( self[3] / 16 ) + 1 + + data[offset] = bit.bor( data[offset], __not16(self[3]) ) + for i = offset + 1, #data do data[i] = 0xFFFF end + data = __bless({ self[1], data, __maxlen(self[1]) }) + + -- Last address in reserved for Broadcast Address in IPv4 + if data[1] == FAMILY_INET4 then data:sub(1, true) end + + return data + end end