X-Git-Url: https://git.archive.openwrt.org/?a=blobdiff_plain;f=libs%2Fcore%2Fluasrc%2Fip.lua;h=3b0ca6799b51afa7897311ec5d21c67411c9d585;hb=470184bd571baeaf15711bfe937b0b46022a07ac;hp=1245dc3df4ea2c855dcb07e6a1bc7562ede8036d;hpb=74824c32bf6dcf7a1cbd288fa5790fc9769c411a;p=project%2Fluci.git diff --git a/libs/core/luasrc/ip.lua b/libs/core/luasrc/ip.lua index 1245dc3df..3b0ca6799 100644 --- a/libs/core/luasrc/ip.lua +++ b/libs/core/luasrc/ip.lua @@ -17,13 +17,20 @@ $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 @@ -41,21 +48,49 @@ 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 + + --- 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 @@ -125,6 +160,7 @@ 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) @@ -172,6 +208,7 @@ function IPv6(address, netmask) local data = {} local prefix = address:match("/(.+)") address = address:gsub("/.+","") + address = address:gsub("^%[(.*)%]$", "%1") if netmask then prefix = obj:prefix(netmask) @@ -193,7 +230,7 @@ function IPv6(address, netmask) block = tonumber(address:sub(borderl, borderh - 1), 16) if block and block <= 0xFFFF then - table.insert(data, block) + data[#data+1] = block else if zeroh or borderh - borderl > 1 then return nil end zeroh = #data + 1 @@ -207,7 +244,7 @@ function IPv6(address, netmask) block = tonumber(chunk, 16) 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 @@ -220,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 @@ -255,9 +292,9 @@ end 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 = { } @@ -274,31 +311,32 @@ 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 --- LuCI IP Library / CIDR instances -- @class module +-- @cstyle instance -- @name luci.ip.cidr -cidr = luci.util.class() +cidr = util.class() --- Test whether the instance is a IPv4 address. -- @return Boolean indicating a IPv4 address type --- @see is6 +-- @see cidr.is6 function cidr.is4( self ) return self[1] == FAMILY_INET4 end --- Test whether the instance is a IPv6 address. -- @return Boolean indicating a IPv6 address type --- @see is4 +-- @see cidr.is4 function cidr.is6( self ) return self[1] == FAMILY_INET6 end @@ -332,8 +370,8 @@ end -- family than this instance. -- @param addr A luci.ip.cidr instance to compare against -- @return Boolean indicating whether this instance is lower --- @see higher --- @see equal +-- @see cidr.higher +-- @see cidr.equal function cidr.lower( self, addr ) assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" ) for i = 1, #self[2] do @@ -349,8 +387,8 @@ end -- family than this instance. -- @param addr A luci.ip.cidr instance to compare against -- @return Boolean indicating whether this instance is higher --- @see lower --- @see equal +-- @see cidr.lower +-- @see cidr.equal function cidr.higher( self, addr ) assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" ) for i = 1, #self[2] do @@ -361,13 +399,13 @@ function cidr.higher( self, addr ) return false end ---- Test whether the value of the instance is uequal to the given address. +--- 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 lower --- @see higher +-- @see cidr.lower +-- @see cidr.higher function cidr.equal( self, addr ) assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" ) for i = 1, #self[2] do @@ -379,32 +417,31 @@ function cidr.equal( self, addr ) end --- Return the prefix length of this CIDR instance. --- @return Prefix length in bit +-- @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 + 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 @@ -416,58 +453,81 @@ end -- instance. -- @param bits Override prefix length of this instance (optional) -- @return CIDR instance containing the network address --- @see host --- @see mask +-- @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( bits / 16 ) do - table.insert( data, self[2][i] ) + data[#data+1] = self[2][i] end if #data < #self[2] then - table.insert( data, bit.band( self[2][1+#data], __mask16(bits) ) ) + data[#data+1] = bit.band( self[2][1+#data], __mask16(bits) ) for i = #data + 1, #self[2] do - table.insert( data, 0 ) + 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 network --- @see mask +-- @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 network --- @see host +-- @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 if #data < #self[2] then - table.insert( data, __mask16(bits) ) + data[#data+1] = __mask16(bits) for i = #data + 1, #self[2] do - table.insert( data, 0 ) + 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. @@ -485,11 +545,12 @@ end --- 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 sub -function cidr.add( self, amount ) - local shorts = { bit.rshift(amount, 16), bit.band(amount, 0xFFFF) } +-- @see cidr.sub +function cidr.add( self, amount, inplace ) 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 @@ -505,16 +566,22 @@ function cidr.add( self, amount ) 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 add -function cidr.sub( self, amount ) - local shorts = { bit.rshift(amount, 16), bit.band(amount, 0xFFFF) } +-- @see cidr.add +function cidr.sub( self, amount, inplace ) 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 @@ -530,5 +597,39 @@ function cidr.sub( self, amount ) 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 + +--- 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 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