Index: ipOps.lua
===================================================================
--- ipOps.lua (revision 29646)
+++ ipOps.lua (working copy)
@@ -3,17 +3,17 @@
--
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
-local bin = require "bin"
-local bit = require "bit"
-local stdnse = require "stdnse"
-local string = require "string"
-local table = require "table"
local type = type
local table = table
local string = string
local ipairs = ipairs
+local pairs = pairs
local tonumber = tonumber
+local tostring = tostring
+local unpack = unpack
+local stdnse = require "stdnse"
+local bit = require "bit"
_ENV = stdnse.module("ipOps", stdnse.seeall)
@@ -42,11 +42,13 @@
-- String non-routable address containing the supplied IP address.
isPrivate = function( ip )
local err
+ local sub = string.sub
+ local match = string.match
ip, err = expand_ip( ip )
if err then return nil, err end
- if ip:match( ":" ) then
+ if match(ip, ":") then
local is_private
local ipv6_private = { "::/127", "FC00::/7", "FE80::/10" }
@@ -61,19 +63,19 @@
end
end
- elseif ip:sub(1,3) == '10.' then
+ elseif sub(ip,1,3) == '10.' then
return true, '10/8'
- elseif ip:sub(1,4) == '127.' then
+ elseif sub(ip,1,4) == '127.' then
return true, '127/8'
- elseif ip:sub(1,8) == '169.254.' then
+ elseif sub(ip,1,8) == '169.254.' then
return true, '169.254/16'
- elseif ip:sub(1,4) == '172.' then
+ elseif sub(ip,1,4) == '172.' then
local p, e = ip_in_range(ip, '172.16/12')
if p == true then
@@ -82,33 +84,33 @@
return p, e
end
- elseif ip:sub(1,4) == '192.' then
+ elseif sub(ip,1,4) == '192.' then
- if ip:sub(5,8) == '168.' then
+ if sub(ip,5,8) == '168.' then
return true, '192.168/16'
- elseif ip:match('^192%.[0][0]?[0]?%.[0][0]?[0]?%.') then
+ elseif match(ip, '^192%.[0][0]?[0]?%.[0][0]?[0]?%.') then
return true, '192.0.0/24'
- elseif ip:match('^192%.[0][0]?[0]?%.[0]?[0]?2') then
+ elseif match(ip, '^192%.[0][0]?[0]?%.[0]?[0]?2') then
return true, '192.0.2/24'
end
- elseif ip:sub(1,4) == '198.' then
+ elseif sub(ip,1,4) == '198.' then
- if ip:match('^198%.[0]?18%.') or ip:match('^198%.[0]?19%.') then
+ if match(ip,'^198%.[0]?18%.') or match(ip,'^198%.[0]?19%.') then
return true, '198.18/15'
- elseif ip:match('^198%.[0]?51%.100%.') then
+ elseif match(ip,'^198%.[0]?51%.100%.') then
return true, '198.51.100/24'
end
- elseif ip:match('^203%.[0][0]?[0]?%.113%.') then
+ elseif match(ip,'^203%.[0][0]?[0]?%.113%.') then
return true, '203.0.113/24'
- elseif ip:match('^224%.[0][0]?[0]?%.[0][0]?[0]?%.') then
+ elseif match(ip,'^224%.[0][0]?[0]?%.[0][0]?[0]?%.') then
return true, '224.0.0/24'
- elseif ip:match('^24[0-9]%.') or ip:match('^25[0-5]%.') then
+ elseif match(ip,'^24[0-9]%.') or match(ip,'^25[0-5]%.') then
return true, '240.0.0/4'
@@ -127,7 +129,7 @@
--
-- Note: IPv6 addresses are not supported. Currently, numbers in NSE are
-- limited to 10^14, and consequently not all IPv6 addresses can be
--- represented. Consider using ip_to_str
for IPv6 addresses.
+-- represented.
-- @param ip String representing an IPv4 address. Shortened notation is
-- permitted.
-- @usage
@@ -152,12 +154,12 @@
end
---
--- Converts the supplied IPv4 address from a DWORD value into a dotted string.
+-- Converts the supplied IPv4 address from a DWORD value into a dotted string.
--
--- For example, the address (((a*256+b)*256+c)*256+d) becomes a.b.c.d.
+-- For example, the address (((a*256+b)*256+c)*256+d) becomes a.b.c.d.
--
---@param ip DWORD representing an IPv4 address.
---@return The string representing the address.
+--@param ip DWORD representing an IPv4 address.
+--@return The string representing the address.
fromdword = function( ip )
if type( ip ) ~= "number" then
stdnse.print_debug(1, "Error in ipOps.todword: Expected IPv4 address.")
@@ -212,8 +214,7 @@
---
--- Compares two IP addresses. When comparing addresses from different families,
--- IPv4 addresses will sort before IPv6 addresses.
+-- Compares two IP addresses (from the same address family).
-- @param left String representing an IPv4 or IPv6 address. Shortened
-- notation is permitted.
-- @param op A comparison operator which may be one of the following
@@ -233,36 +234,47 @@
return nil, "Error in ipOps.compare_ip: Expected IP address as a string."
end
+ if ( left:match( ":" ) and not right:match( ":" ) ) then right = expand_ip( right, 'inet6' )
+ elseif ( not left:match( ":" ) and right:match( ":" ) ) then left = expand_ip( left, 'inet6' )
+ end
+
+ if op == "lt" or op == "le" then
+ left, right = right, left
+ elseif op ~= "eq" and op ~= "ge" and op ~= "gt" then
+ return nil, "Error in ipOps.compare_ip: Invalid Operator."
+ end
+
local err ={}
- left, err[#err+1] = ip_to_str( left )
- right, err[#err+1] = ip_to_str( right )
+ left, err[#err+1] = ip_to_bin( left )
+ right, err[#err+1] = ip_to_bin( right )
if #err > 0 then
return nil, table.concat( err, " " )
end
- if #left > #right then
- left = bin.pack( "CA", 0x06, left )
- right = bin.pack( "CA", 0x04, right )
- elseif #right > #left then
- right = bin.pack( "CA", 0x06, right )
- left = bin.pack( "CA", 0x04, left )
+ if string.len( left ) ~= string.len( right ) then
+ -- shouldn't happen...
+ return nil, "Error in ipOps.compare_ip: Binary IP addresses were of different lengths."
end
- if ( op == "eq" ) then
- return ( left == right )
- elseif ( op == "ne" ) then
- return ( left ~= right )
- elseif ( op == "le" ) then
- return ( left <= right )
- elseif ( op == "ge" ) then
- return ( left >= right )
- elseif ( op == "lt" ) then
- return ( left < right )
- elseif ( op == "gt" ) then
- return ( left > right )
+ -- equal?
+ if ( op == "eq" or op == "le" or op == "ge" ) and left == right then
+ return true
+ elseif op == "eq" then
+ return false
end
- return nil, "Error in ipOps.compare_ip: Invalid Operator."
+ -- starting from the leftmost bit, subtract the bit in right from the bit in left
+ local compare
+ for i = 1, string.len( left ), 1 do
+ compare = tonumber( string.sub( left, i, i ) ) - tonumber( string.sub( right, i, i ) )
+ if compare == 1 then
+ return true
+ elseif compare == -1 then
+ return false
+ end
+ end
+ return false
+
end
@@ -288,8 +300,8 @@
if err then return nil, err end
ip, err = expand_ip( ip )
if err then return nil, err end
- if ( ip:match( ":" ) and not first:match( ":" ) ) or ( not ip:match( ":" ) and first:match( ":" ) ) then
- return nil, "Error in ipOps.ip_in_range: IP address is of a different address family to Range."
+ if ( first:match( ":" ) and not ip:match( ":" ) ) then ip = expand_ip( ip, 'inet6' )
+ elseif ( not first:match( ":" ) and ip:match( ":" ) ) then first = expand_ip( first, 'inet6' )
end
err = {}
@@ -318,15 +330,12 @@
-- the IPv4 portion is shortened and does not contain a dot, in which case the
-- address will be treated as IPv6.
-- @param ip String representing an IPv4 or IPv6 address in shortened or full notation.
--- @param family String representing the address family to expand to. Only
--- affects IPv4 addresses when "inet6" is provided, causing the function to
--- return an IPv4-mapped IPv6 address.
-- @usage
-- local ip = ipOps.expand_ip( "2001::" )
-- @return String representing a fully expanded IPv4 or IPv6 address (or
-- nil
in case of an error).
-- @return String error message in case of an error.
-expand_ip = function( ip, family )
+expand_ip = function( ip )
local err
if type( ip ) ~= "string" or ip == "" then
@@ -341,7 +350,7 @@
return nil, err4
end
local octets = {}
- for octet in string.gmatch( ip, "%d+" ) do
+ for octet in ip:gmatch( "%d+" ) do
if tonumber( octet, 10 ) > 255 then return nil, err4 end
octets[#octets+1] = octet
end
@@ -349,20 +358,9 @@
while #octets < 4 do
octets[#octets+1] = "0"
end
- if family == "inet6" then
- return ( table.concat( { 0,0,0,0,0,"ffff",
- stdnse.tohex( 256*octets[1]+octets[2] ),
- stdnse.tohex( 256*octets[3]+octets[4] )
- }, ":" ) )
- else
- return ( table.concat( octets, "." ) )
- end
+ return ( table.concat( octets, "." ) )
end
- if family ~= nil and family ~= "inet6" then
- return nil, "Error in ipOps.expand_ip: Cannot convert IPv6 address to IPv4"
- end
-
if ip:match( "[^%.:%x]" ) then
return nil, ( err4:gsub( "IPv4", "IPv6" ) )
end
@@ -372,7 +370,7 @@
-- get a table of each hexadectet
local hexadectets = {}
- for hdt in string.gmatch( ip, "[%.z%x]+" ) do
+ for hdt in ip:gmatch( "[%.z%x]+" ) do
hexadectets[#hexadectets+1] = hdt
end
@@ -445,6 +443,9 @@
first, prefix = range:match( "([%x%d:%.]+)/(%d+)" )
elseif range:match( "-" ) then
first, last = range:match( "([%x%d:%.]+)%s*%-%s*([%x%d:%.]+)" )
+ else
+ first = range
+ last = range
end
local err = {}
@@ -460,8 +461,8 @@
end
if first and last then
- if ( first:match( ":" ) and not last:match( ":" ) ) or ( not first:match( ":" ) and last:match( ":" ) ) then
- return nil, nil, "Error in ipOps.get_ips_from_range: First IP address is of a different address family to last IP address."
+ if ( first:match( ":" ) and not last:match( ":" ) ) then last = expand_ip( last, 'inet6' )
+ elseif ( not first:match( ":" ) and last:match( ":" ) ) then first = expand_ip( first, 'inet6' )
end
return first, last
else
@@ -473,6 +474,234 @@
---
+-- Converts a first and last address into a table of ranges.
+-- @param first String representing a IPv4 or IPv6 address.
+-- @param last String representing a IPv4 or IPv6 address.
+-- @usage
+-- ranges = ipOps.get_ranges_from_ips( "192.168.0.0", "192.168.255.255" )
+-- @return Table containing the list of ranges in CIDR format
+-- (or nil
in case of an error).
+-- @return String error message in case of an error.
+get_ranges_from_ips = function( first, last )
+
+ if first == last then return { first } end
+
+ local err = {}
+ first, err[#err+1] = expand_ip( first )
+ last, err[#err+1] = expand_ip( last )
+ if #err > 0 then
+ return nil, table.concat( err, " " )
+ end
+
+ if first == last then return { first } end
+
+ if compare_ip( first, "gt", last ) then first, last = last, first end
+
+ if ( first:match( ":" ) and not last:match( ":" ) ) then last = expand_ip( last, 'inet6' )
+ elseif ( not first:match( ":" ) and last:match( ":" ) ) then first = expand_ip( first, 'inet6' )
+ end
+
+ local maxPrefix = 32
+ if first:match( ":" ) then maxPrefix = 128 end
+
+ -- find the best net address
+ local netPrefix = maxPrefix
+ for cidr = maxPrefix-1, 1, -1 do
+ local netIP = get_net_ip(first, cidr)
+ if compare_ip( first, "eq", netIP ) then netPrefix = cidr else break end
+ end
+
+ -- find the best prefix
+ local prefix = maxPrefix
+ local lastIP
+ for cidr = netPrefix, maxPrefix, 1 do
+ lastIP = get_last_ip(first, cidr)
+ if compare_ip( last, "ge", lastIP ) then prefix = cidr break end -- (no else)
+ end
+
+ -- divide and conquer
+ local range = first .. '/' .. tostring(prefix)
+ if prefix == maxPrefix then range = first end
+ if compare_ip( last, "eq", lastIP ) then return { range } end
+
+ local ip_blocks = {}
+ ip_blocks, err = get_ranges_from_ips( get_next_ip(lastIP), last ) -- re-run to get the next set of IP blocks
+ if ip_blocks then
+ table.insert(ip_blocks, 1, range)
+ return ip_blocks
+ else
+ return nil, err
+ end
+
+end
+
+
+
+---
+-- Returns a table (possibly combined) with both ranges A and B.
+-- @param rangeA String representing a range of IPv4 or IPv6 addresses in either
+-- CIDR or first-last notation. Can also be a single IP address.
+-- @param rangeB String representing a range of IPv4 or IPv6 addresses in either
+-- CIDR or first-last notation. Can also be a single IP address.
+-- @usage
+-- ranges = ipOps.add_range_to_range( "192.168.0.0/24", "192.168.1.0/24" )
+-- @return Table containing the list of ranges, which could be the exact
+-- same addresses (if A is completely detached from B), or nil
in
+-- case of an error.
+-- @return String error message in case of an error.
+add_range_to_range = function( rangeA, rangeB )
+
+ -- ip to range shortcut
+ local isIPA, isIPB = not rangeA:match( "[/-]" ), not rangeB:match( "[/-]" )
+ if isIPA and isIPB then return { rangeA, rangeB } -- (kind of absurd, but...)
+ elseif isIPA then
+ local inrange, err = ip_in_range( rangeA, rangeB )
+ if err then return nil, err end
+
+ if inrange then return { rangeB }
+ else return { rangeA, rangeB }
+ end
+ elseif isIPB then
+ local inrange, err = ip_in_range( rangeB, rangeA )
+ if err then return nil, err end
+
+ if inrange then return { rangeA }
+ else return { rangeA, rangeB }
+ end
+ end
+
+ local firstA, lastA, firstB, lastB
+ local err = {}
+ firstA, lastA, err[#err+1] = get_ips_from_range( rangeA )
+ firstB, lastB, err[#err+1] = get_ips_from_range( rangeB )
+ if #err > 0 then
+ return nil, table.concat( err, " " )
+ end
+
+ local inrangeAF, inrangeAL, inrangeBF, inrangeBL, nextedgeA, nextedgeB
+ inrangeAF, err[#err+1] = ip_in_range( firstA, rangeB )
+ inrangeAL, err[#err+1] = ip_in_range( lastA, rangeB )
+ inrangeBF, err[#err+1] = ip_in_range( firstB, rangeA )
+ inrangeBL, err[#err+1] = ip_in_range( lastB, rangeA )
+ nextedgeA, err[#err+1] = compare_ip( get_next_ip(lastA), "eq", firstB )
+ nextedgeB, err[#err+1] = compare_ip( get_next_ip(lastB), "eq", firstA )
+ if #err > 0 then
+ return nil, table.concat( err, " " )
+ end
+
+ -- B fully contains A; B wins
+ if inrangeAF and inrangeAL then return { rangeB }
+ -- A fully contains B; A wins
+ elseif inrangeBF and inrangeBL then return { rangeA }
+ -- A/B don't even touch each other; separate ranges
+ elseif not inrangeBF and not inrangeBL then return { rangeA, rangeB }
+ -- A is on the left edge of B
+ elseif nextedgeA then return get_ranges_from_ips(firstA, lastB)
+ -- B is on the left edge of A
+ elseif nextedgeB then return get_ranges_from_ips(firstB, lastA)
+ -- Venn diagram; A on left side
+ elseif inrangeAL and inrangeBF then return get_ranges_from_ips(firstA, lastB)
+ -- Venn diagram; B on left side
+ elseif inrangeAF and inrangeBL then return get_ranges_from_ips(firstB, lastA)
+ end
+
+ return nil, nil -- we should never get here...
+end
+
+
+
+---
+-- Subtracts IPs in range B from range A (A minus B).
+-- @param rangeA String representing a range of IPv4 or IPv6 addresses in either
+-- CIDR or first-last notation. Can also be a single IP address.
+-- @param rangeB String representing a range of IPv4 or IPv6 addresses in either
+-- CIDR or first-last notation. Can also be a single IP address.
+-- @usage
+-- ranges = ipOps.subtract_range_from_range( "192.168.0.0/24", "192.168.128.0/22" )
+-- @return Table containing the list of ranges, which could be an empty
+-- table (if B wholely contains A), or nil
in case of an error.
+-- @return String error message in case of an error.
+subtract_range_from_range = function( rangeA, rangeB )
+
+ local firstA, lastA, firstB, lastB
+ local err = {}
+ firstA, lastA, err[#err+1] = get_ips_from_range( rangeA )
+ firstB, lastB, err[#err+1] = get_ips_from_range( rangeB )
+ if #err > 0 then
+ return nil, table.concat( err, " " )
+ end
+
+ local inrangeAF, inrangeAL, inrangeBF, inrangeBL, isedgeF, isedgeL
+ inrangeAF, err[#err+1] = ip_in_range( firstA, rangeB )
+ inrangeAL, err[#err+1] = ip_in_range( lastA, rangeB )
+ inrangeBF, err[#err+1] = ip_in_range( firstB, rangeA )
+ inrangeBL, err[#err+1] = ip_in_range( lastB, rangeA )
+ isedgeF, err[#err+1] = compare_ip( firstA, "eq", firstB )
+ isedgeL, err[#err+1] = compare_ip( lastA, "eq", lastB )
+ if #err > 0 then
+ return nil, table.concat( err, " " )
+ end
+
+ -- B fully contains A; B wins
+ if inrangeAF and inrangeAL then return { }
+ -- B isn't even in A; A wins
+ elseif not inrangeBF and not inrangeBL then return { rangeA }
+ -- Venn diagram; A on left side
+ elseif inrangeAL and inrangeBF then return get_ranges_from_ips(firstA, get_prev_ip(firstB))
+ -- Venn diagram; B on left side
+ elseif inrangeAF and inrangeBL then return get_ranges_from_ips(get_next_ip(lastB), lastA)
+ -- A fully contains B; B is a hole within A
+ elseif inrangeBF and inrangeBL then
+ elseif inrangeBF and inrangeBL then
+ local blocksL, blocksR
+ if not isedgeF then blocksL, err[#err+1] = get_ranges_from_ips(firstA, get_prev_ip(firstB)) end
+ if not isedgeL then blocksR, err[#err+1] = get_ranges_from_ips(get_next_ip(lastB), lastA) end
+ if #err > 0 then
+ return nil, table.concat( err, " " )
+ end
+
+ if isedgeF then return blocksL end
+ if isedgeL then return blocksR end
+ return { unpack(blocksL), unpack(blocksR) }
+ end
+
+ return nil, nil -- we should never get here...
+end
+
+---
+-- Calculates the network IP address given an IP address in
+-- the range and prefix length for that range.
+-- @param ip String representing an IPv4 or IPv6 address. Shortened
+-- notation is permitted.
+-- @param prefix Number or a string representing a decimal number corresponding
+-- to a prefix length.
+-- @usage
+-- net = ipOps.get_net_ip( "192.168.85.3", 26 )
+-- @return String representing the network IP address of the range denoted
+-- by the supplied parameters (or nil
in case of an error).
+-- @return String error message in case of an error.
+get_net_ip = function( ip, prefix )
+
+ local first, err = ip_to_bin( ip )
+ if err then return nil, err end
+
+ prefix = tonumber( prefix )
+ if not prefix or ( prefix < 0 ) or ( prefix > # first ) then
+ return nil, "Error in ipOps.get_net_ip: Invalid prefix length."
+ end
+
+ local netbits = first:sub(1, prefix)
+ local hostbits = ('0'):rep(#first - prefix)
+
+ local net
+ net, err = bin_to_ip( netbits .. hostbits )
+ if err then return nil, err end
+ return net
+end
+
+
+
+---
-- Calculates the last IP address of a range of addresses given an IP address in
-- the range and prefix length for that range.
-- @param ip String representing an IPv4 or IPv6 address. Shortened
@@ -490,54 +719,88 @@
if err then return nil, err end
prefix = tonumber( prefix )
- if not prefix or ( prefix < 0 ) or ( prefix > # first ) then
+ if not prefix or ( prefix < 0 ) or ( prefix > #first ) then
return nil, "Error in ipOps.get_last_ip: Invalid prefix length."
end
- local hostbits = string.sub( first, prefix + 1 )
- hostbits = string.gsub( hostbits, "0", "1" )
- local last = string.sub( first, 1, prefix ) .. hostbits
+ local hostbits = first:sub( prefix + 1 )
+ hostbits = hostbits:gsub( "0", "1" )
+
+ local last = first:sub( 1, prefix ) .. hostbits
last, err = bin_to_ip( last )
if err then return nil, err end
return last
end
+
+
---
--- Converts an IP address into an opaque string.
--- @param ip String representing an IPv4 or IPv6 address.
--- @param family (optional) Address family to convert to. "ipv6" converts IPv4
--- addresses to IPv4-mapped IPv6.
+-- Calculates the previous IP (IP-1) given an IP address.
+-- @param ip String representing an IPv4 or IPv6 address. Shortened
+-- notation is permitted.
-- @usage
--- opaque = ipOps.ip_to_str( "192.168.3.4" )
--- @return 4- or 16-byte string representing IP address (or nil
+-- next = ipOps.get_prev_ip( "192.168.85.3" )
+-- @return String representing the previous IP address (or nil
-- in case of an error).
--- @return String error message in case of an error
-ip_to_str = function( ip, family )
- local err
+-- @return String error message in case of an error.
+get_prev_ip = function( ip )
- ip, err = expand_ip( ip, family )
+ local bin_ip, err = ip_to_bin( ip )
if err then return nil, err end
- local t = {}
+ for p = #bin_ip, 1, -1 do
+ -- hit a one; bit can be subtracted and we can stop
+ if bin_ip:sub(p, p) == '1' then
+ bin_ip = bin_ip:replace_char('0', p)
+ break
+ -- hit a zero; bit will underflow to one and we need to continue
+ else
+ bin_ip = bin_ip:replace_char('1', p)
+ end
+ end
- if not ip:match( ":" ) then
- -- ipv4 string
- for octet in string.gmatch( ip, "%d+" ) do
- t[#t+1] = bin.pack( ">C", tonumber(octet) )
+ local prevIP
+ prevIP, err = bin_to_ip( bin_ip )
+ if err then return nil, err end
+ return prevIP
+end
+
+
+
+---
+-- Calculates the next IP (IP+1) given an IP address.
+-- @param ip String representing an IPv4 or IPv6 address. Shortened
+-- notation is permitted.
+-- @usage
+-- next = ipOps.get_next_ip( "192.168.85.3" )
+-- @return String representing the next IP address (or nil
+-- in case of an error).
+-- @return String error message in case of an error.
+get_next_ip = function( ip )
+
+ local bin_ip, err = ip_to_bin( ip )
+ if err then return nil, err end
+
+ for p = #bin_ip, 1, -1 do
+ -- hit a zero; will fill bit and we can stop
+ if bin_ip:sub(p, p) == '0' then
+ bin_ip = bin_ip:replace_char('1', p)
+ break
+ -- hit a one; bit will overflow to zero and we need to continue
+ else
+ bin_ip = bin_ip:replace_char('0', p)
end
- else
- -- ipv6 string
- for hdt in string.gmatch( ip, "%x+" ) do
- t[#t+1] = bin.pack( ">S", tonumber(hdt, 16) )
- end
end
-
- return table.concat( t )
+ local nextIP
+ nextIP, err = bin_to_ip( bin_ip )
+ if err then return nil, err end
+ return nextIP
end
+
---
-- Converts an IP address into a string representing the address as binary
-- digits.
@@ -572,7 +835,7 @@
-- padding
for i, v in ipairs( t ) do
- t[i] = mask:sub( 1, # mask - # v ) .. v
+ t[i] = mask:sub( 1, #mask - #v ) .. v
end
return hex_to_bin( table.concat( t ) )
@@ -597,9 +860,9 @@
end
local af
- if # binstring == 32 then
+ if string.len( binstring ) == 32 then
af = 4
- elseif # binstring == 128 then
+ elseif string.len( binstring ) == 128 then
af = 6
else
return nil, "Error in ipOps.bin_to_ip: Expected exactly 32 or 128 binary digits."
@@ -647,10 +910,63 @@
local t, mask, binchar = {}, "0000"
for hexchar in string.gmatch( hex, "%x" ) do
binchar = stdnse.tobinary( tonumber( hexchar, 16 ) )
- t[#t+1] = mask:sub( 1, # mask - # binchar ) .. binchar
+ t[#t+1] = mask:sub( 1, #mask - #binchar ) .. binchar
end
return table.concat( t )
end
+---
+-- Count the number of individual IPs in a set of ranges/IPs.
+-- @param ... Any number of strings representing a range or IP address.
+-- Non-IP/domain strings count as 1. Tables will be unpacked. Anything else
+-- will simply be skipped over.
+-- @usage
+-- bin_string = ipOps.count_ips( "192.168.2.0/24", "4.4.4.4", ips, ipOps.get_ranges_from_ips(first, last) )
+-- @return Number representing the total count of (valid) IPs
+count_ips = function( ... )
+ local ips = {...}
+
+ local count = 0
+ for _,range in ipairs(ips) do
+ if type(range) == "string" then
+ local ip, prefix
+ if range:match( "/" ) then
+ ip, prefix = range:match( "([%x%d:\.]+)/(%d+)" )
+ prefix = tonumber(prefix)
+
+ local maxPrefix = 32
+ if ip:match( ":" ) then maxPrefix = 128 end
+
+ if prefix ~= nil then count = count + 2 ^ (maxPrefix-prefix)
+ else count = count+1
+ end
+ elseif range:match( "-" ) then
+ -- let get_ranges_from_ips handle it
+ local first, last = range:match( "([%x%d:\.]+)%s*\-%s*([%x%d:\.]+)" )
+ local ranges = get_ranges_from_ips(first, last)
+
+ if ranges ~= nil then table.push( ips, ranges )
+ else count = count+1
+ end
+
+ else
+ count = count+1
+ end
+ elseif type(range) == "table" then
+ table.push( ips, range )
+ end
+ end
+
+ return count
+end
+
+string.replace_char = function(str, r, pos)
+ return str:sub(1, pos-1) .. r .. str:sub(pos+1)
+end
+
+table.push = function(tbl, itbl)
+ for _,i in pairs(itbl) do table.insert(tbl, i) end
+end
+
return _ENV;