Mô đun:IP

-- IP library
-- This library contains classes for working with IP addresses and IP ranges.

-- Load modules
require('strict')
local bit32 = require('bit32')
local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType
local checkTypeMulti = libraryUtil.checkTypeMulti
local makeCheckSelfFunction = libraryUtil.makeCheckSelfFunction

-- Constants
local V4 = 'IPv4'
local V6 = 'IPv6'

--------------------------------------------------------------------------------
-- Helper functions
--------------------------------------------------------------------------------

local function makeValidationFunction(className, isObjectFunc)
-- Make a function for validating a specific object.
return function (methodName, argIdx, arg)
if not isObjectFunc(arg) then
error(string.format(
"bad argument %d to '%s' (not a valid %s object)",
argIdx, methodName, className
), 3)
end
end
end

--------------------------------------------------------------------------------
-- Collection class
-- This is a table used to hold items.
--------------------------------------------------------------------------------

local Collection = {}
Collection.__index = Collection

function Collection:add(item)
if item ~= nil then
self.n = self.n + 1
self[self.n] = item
end
end

function Collection:join(sep)
return table.concat(self, sep)
end

function Collection:remove(pos)
if self.n > 0 and (pos == nil or (0 0 and s:match('^0') then
-- A redundant leading zero is for an IP in octal.
return nil
end
octets[i] = num
else
return nil
end
else
return nil
end
end
local parts = Collection.new()
for i = 1, 3, 2 do
parts:add(octets[i] 256 + octets[i+1])
end
return setmetatable(parts, RawIP)
end
return nil
end

function RawIP.newFromIPv6(ipStr)
-- Return a RawIP object if ipStr is a valid IPv6 string. Otherwise,
-- return nil.
ipStr = ipStr:match('^%s(.-)%s$')
local _, n = ipStr:gsub(':', ':')
if n = 0x10000 then
carry = previous and 0x10000 or 1
sum = sum - 0x10000
else
carry = previous and 0xffff or 0
end
result[i] = sum
end
return setmetatable(result, RawIP)
end

function RawIP:getPrefix(bitLength)
-- Return a RawIP object for the prefix of the current IP Address with a
-- bit length of bitLength.
local result = Collection.new()
result.n = self.n
for i = 1, self.n do
if bitLength > 0 then
if bitLength >= 16 then
result[i] = self[i]
bitLength = bitLength - 16
else
result[i] = bit32.replace(self[i], 0, 0, 16 - bitLength)
bitLength = 0
end
else
result[i] = 0
end
end
return setmetatable(result, RawIP)
end

function RawIP:getHighestHost(bitLength)
-- Return a RawIP object for the highest IP with the prefix of length
-- bitLength. In other words, the network (the most-significant bits)
-- is the same as the current IP's, but the host bits (the
-- least-significant bits) are all set to 1.
local bits = self.n 16
local width
if bitLength = bits then
width = 0
else
width = bits - bitLength
end
local result = Collection.new()
result.n = self.n
for i = self.n, 1, -1 do
if width > 0 then
if width >= 16 then
result[i] = 0xffff
width = width - 16
else
result[i] = bit32.replace(self[i], 0xffff, 0, width)
width = 0
end
else
result[i] = self[i]
end
end
return setmetatable(result, RawIP)
end

function RawIP:_makeIPv6String()
-- Return an IPv6 string representation of the object. Behavior is
-- undefined if the current object is IPv4.
local z1, z2 -- indices of run of zeroes to be displayed as "::"
local zstart, zcount
for i = 1, 9 do
-- Find left-most occurrence of longest run of two or more zeroes.
if i 1 then
if not z1 or zcount > z2 - z1 + 1 then
z1 = zstart
z2 = zstart + zcount - 1
end
end
zstart = nil
zcount = nil
end
end
local parts = Collection.new()
for i = 1, 8 do
if z1 and z1 data.rawIP:getBitLength()
or bitLength ~= math.floor(bitLength)
then
error(string.format(
"bad argument 1 to 'getSubnet' (must be an integer between 0 and %d)",
data.rawIP:getBitLength()
), 2)
end
return makeSubnetFromRaw(data.rawIP, bitLength)
end

function obj:getNextIP()
checkSelf(self, 'getNextIP')
return makeIPAddressFromRaw(data.rawIP:getAdjacent())
end

function obj:getPreviousIP()
checkSelf(self, 'getPreviousIP')
return makeIPAddressFromRaw(data.rawIP:getAdjacent(true))
end

-- Metamethods
return setmetatable(obj, {
__eq = ipEquals,
__lt = ipLessThan,
__concat = concatIP,
__tostring = ipToString,
__index = function (self, key)
-- If any code knows the unique data key, allow it to access
-- the data table.
if key == dataKey then
return data
end
end,
__metatable = false, -- don't allow access to the metatable
})
end

makeIPAddress = function (ip)
local rawIP = RawIP.newFromIP(ip)
if not rawIP then
error(string.format("'%s' is an invalid IP address", ip), 3)
end
return makeIPAddressFromRaw(rawIP)
end

function IPAddress.new(ip)
checkType('IPAddress.new', 1, ip, 'string')
return makeIPAddress(ip)
end
end

--------------------------------------------------------------------------------
-- Subnet class
-- Represents a block of IPv4 or IPv6 addresses.
--------------------------------------------------------------------------------

local Subnet = {}

do
-- uniqueKey is a unique, private key used to test whether a given object
-- is a Subnet object.
local uniqueKey = {}

-- Metatable
local mt = {
__index = function (self, key)
if key == uniqueKey then
return true
end
end,
__eq = function (self, obj)
return self:getCIDR() == obj:getCIDR()
end,
__concat = function (self, obj)
return tostring(self) .. tostring(obj)
end,
__tostring = function (self)
return self:getCIDR()
end,
__metatable = false
}

-- Private static methods
local function isSubnetObject(val)
-- Return true if val is a Subnet object, and false otherwise.
return type(val) == 'table' and val[uniqueKey] ~= nil
end

-- Function to validate subnet objects.
-- Params:
-- methodName (string) - the name of the method being validated
-- argIdx (number) - the position of the argument in the argument list
-- arg - the argument to be validated
validateSubnet = makeValidationFunction('Subnet', isSubnetObject)

-- Constructors
makeSubnetFromRaw = function (rawIP, bitLength)
-- Set up structure
local obj = setmetatable({}, mt)
local data = {
rawIP = rawIP,
bitLength = bitLength,
}

-- A function to check whether methods are called with a valid self
-- parameter.
local checkSelf = makeCheckSelfFunction(
'IP',
'subnet',
obj,
'Subnet object'
)

-- Public methods
function obj:getPrefix()
checkSelf(self, 'getPrefix')
if not data.prefix then
data.prefix = makeIPAddressFromRaw(
data.rawIP:getPrefix(data.bitLength)
)
end
return data.prefix
end

function obj:getHighestIP()
checkSelf(self, 'getHighestIP')
if not data.highestIP then
data.highestIP = makeIPAddressFromRaw(
data.rawIP:getHighestHost(data.bitLength)
)
end
return data.highestIP
end

function obj:getBitLength()
checkSelf(self, 'getBitLength')
return data.bitLength
end

function obj:getCIDR()
checkSelf(self, 'getCIDR')
return string.format(
'%s/%d',
tostring(self:getPrefix()), self:getBitLength()
)
end

function obj:getVersion()
checkSelf(self, 'getVersion')
return data.rawIP:getVersion()
end

function obj:isIPv4()
checkSelf(self, 'isIPv4')
return data.rawIP:isIPv4()
end

function obj:isIPv6()
checkSelf(self, 'isIPv6')
return data.rawIP:isIPv6()
end

function obj:containsIP(ip)
checkSelf(self, 'containsIP')
local tp = type(ip)
if tp == 'string' then
ip = makeIPAddress(ip)
elseif tp 'table' then
validateIPAddress('containsIP', 1, ip)
else
checkTypeMulti('containsIP', 1, ip, {'string', 'table'})
end
if self:getVersion() ip:getVersion() then
return self:getPrefix() = self:getPrefix() and
subnet:getPrefix() ip2 then
error('The first IP must be less than or equal to the second', 2)
end
end
Collection.add(self, {ip1, ip2 or ip1})
end

function Ranges:merge()
self:sort(
function (lhs, rhs)
-- Sort by second value, then first.
if lhs[2] == rhs[2] then
return lhs[1] 1 do
for i = pos - 1, 1, -1 do
local ip1 = self[i][2]
local ip2 = ip1:getNextIP()
if ip2 ip2 then
break
end
ip1 = self[i][1]
ip2 = self[pos][1]
self[i] = {ip1 > ip2 and ip2 or ip1, self[pos][2]}
self:remove(pos)
pos = pos - 1
if pos ?[%]_]', ' ')
for position, hit in line:gmatch('()(%S+)') do
local ip = hit:match('^([:%x]+)/?%d$')
if ip then
local _, n = ip:gsub(':', ':')
if n >= 2 then
self:_store(hit, position == 1)
end
end
end
end
return self
end
end

return {
IPAddress = IPAddress,
Subnet = Subnet,
IPv4Collection = IPv4Collection,
IPv6Collection = IPv6Collection,
}