Module:IP
Το Module:IP είναι μια βιβλιοθήκη για εργασία με διευθύνσεις IP και υποδίκτυα. Μπορεί να χειριστεί και IPv4 και IPv6. Η βιβλιοθήκη εξάγει τέσσερις κλάσεις, IPAddress, Subnet, IPv4Collection, και IPv6Collection.
Φόρτωση της βιβλιοθήκης
Επεξεργασίαlocal IP = require('Module:IP')
local IPAddress = IP.IPAddress
local Subnet = IP.Subnet
IPAddress
ΕπεξεργασίαΗ κλάση IPAddress χρησιμοποιείται για εργασία με μεμονωμένες διευθύνσεις IP. Για να δημιουργήσετε ένα νέο αντικείμενο διεύθυνσης IP:
local ipAddress = IPAddress.new(ipString)
Η μεταβλητή ipString μπορεί να είναι έγκυρη διεύθυνση IPv4 ή IPv6.
Παραδείγματα:
local ipv4Address = IPAddress.new('1.2.3.4')
local ipv6Address = IPAddress.new('2001:db8::ff00:12:3456')
Τα αντικείμενα IPAddress μπορούν να συγκριθούν με σχεσιακούς τελεστές:
-- Ισότητα
IPAddress.new('1.2.3.4') == IPAddress.new('1.2.3.4') -- true
IPAddress.new('1.2.3.4') == IPAddress.new('1.2.3.5') -- false
-- Μικρότερη από / Μεγαλύτερη από
IPAddress.new('1.2.3.4') < IPAddress.new('1.2.3.5') -- true
IPAddress.new('1.2.3.4') > IPAddress.new('1.2.3.5') -- false
IPAddress.new('1.2.3.4') <= IPAddress.new('1.2.3.5') -- true
IPAddress.new('1.2.3.4') <= IPAddress.new('1.2.3.4') -- true
Μπορείτε να χρησιμοποιήσετε tostring σε αυτά (αυτό ισοδυναμεί με τη χρήση του getIP):
tostring(IPAddress.new('1.2.3.4')) -- "1.2.3.4"
tostring(IPAddress.new('2001:db8::ff00:12:3456')) -- "2001:db8::ff00:12:3456"
-- Οι διευρυμένες διευθύνσεις IPv6 έχουν συντομογραφία:
tostring(IPAddress.new('2001:db8:0:0:0:0:0:0')) -- "2001:db8::"
Μπορείτε επίσης να τα συνδυάσετε:
IPAddress.new('1.2.3.4') .. ' foo' -- "1.2.3.4 foo"
IPAddress.new('1.2.3.4') .. IPAddress.new('5.6.7.8') -- "1.2.3.45.6.7.8"
Τα αντικείμενα IPAddress έχουν διάφορες μεθόδους, οι οποίες περιγράφονται παρακάτω.
getIP
ΕπεξεργασίαipAddress:getIP()
Επιστρέφει μια παράσταση συμβολοσειράς της διεύθυνσης IP. Οι διευθύνσεις IPv6 συντομεύονται εάν είναι δυνατόν.
Παραδείγματα:
IPAddress.new('1.2.3.4'):getIP() -- "1.2.3.4"
IPAddress.new('2001:db8::ff00:12:3456'):getIP() -- "2001:db8::ff00:12:3456"
IPAddress.new('2001:db8:0:0:0:0:0:0'):getIP() -- "2001:db8::"
getVersion
ΕπεξεργασίαipAddress:getVersion()
Επιστρέφει την έκδοση του πρωτοκόλλου IP που χρησιμοποιείται. Αυτό είναι «IPv4» για διευθύνσεις IPv4 και «IPv6» για διευθύνσεις IPv6.
Παραδείγματα:
IPAddress.new('1.2.3.4'):getVersion() -- "IPv4"
IPAddress.new('2001:db8::ff00:12:3456'):getVersion() -- "IPv6"
isIPv4
ΕπεξεργασίαipAddress:isIPv4()
Επιστρέφει true εάν η διεύθυνση IP είναι διεύθυνση IPv4, και false διαφορετικά.
Παραδείγματα:
IPAddress.new('1.2.3.4'):isIPv4() -- true
IPAddress.new('2001:db8::ff00:12:3456'):isIPv4() -- false
isIPv6
ΕπεξεργασίαipAddress:isIPv6()
Επιστρέφει true εάν η διεύθυνση IP είναι διεύθυνση IPv6, και false διαφορετικά.
Παραδείγματα:
IPAddress.new('1.2.3.4'):isIPv6() -- false
IPAddress.new('2001:db8::ff00:12:3456'):isIPv6() -- true
isInSubnet
ΕπεξεργασίαipAddress:isInSubnet(subnet)
Επιστρέφει true εάν η διεύθυνση IP βρίσκεται στο υποδίκτυο subnet και false διαφορετικά. Το subnet μπορεί να είναι Subnet object ή CIDR συμβολοσειρά.
Παραδείγματα:
IPAddress.new('1.2.3.4'):isInSubnet('1.2.3.0/24') -- true
IPAddress.new('1.2.3.4'):isInSubnet('1.2.4.0/24') -- false
IPAddress.new('1.2.3.4'):isInSubnet(Subnet.new('1.2.3.0/24')) -- true
IPAddress.new('2001:db8::ff00:12:3456'):isInSubnet('2001:db8::ff00:12:0/112') -- true
getSubnet
ΕπεξεργασίαipAddress:getSubnet(bitLength)
Επιστρέφει ένα αντικείμενο υποδικτύου για το υποδίκτυο με ένα bit μήκος bitLength που περιέχει την τρέχουσα IP. Η παράμετρος bitLength πρέπει να είναι ακέραιος μεταξύ 0 και 32 για διευθύνσεις IPv4 ή ακέραιος μεταξύ 0 και 128 για διευθύνσεις IPv6.
Παραδείγματα:
IPAddress.new('1.2.3.4'):getSubnet(24) -- Equivalent to Subnet.new('1.2.3.0/24')
getNextIP
ΕπεξεργασίαipAddress:getNextIP()
Επιστρέφει ένα νέο αντικείμενο IPAddress ισοδύναμο με την τρέχουσα διεύθυνση IP προσαυξημένη κατά ένα. Η διεύθυνση IPv4 "255.255.255.255" μετατρέπεται σε "0.0.0.0" και η διεύθυνση IPv6 "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" μετατρέπεται σε "::".
Παραδείγματα:
IPAddress.new('1.2.3.4'):getNextIP() -- Equivalent to IPAddress.new('1.2.3.5')
IPAddress.new('2001:db8::ff00:12:3456'):getNextIP() -- Equivalent to IPAddress.new('2001:db8::ff00:12:3457')
IPAddress.new('255.255.255.255'):getNextIP() -- Equivalent to IPAddress.new('0.0.0.0')
getPreviousIP
ΕπεξεργασίαipAddress:getPreviousIP()
Επιστρέφει ένα νέο αντικείμενο IPAddress ισοδύναμο με την τρέχουσα διεύθυνση IP μειωμένη κατά ένα. Η διεύθυνση IPv4 "0.0.0.0" μετατρέπεται σε "255.255.255.255" και η διεύθυνση IPv6 "::" μετατρέπεται σε "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff".
Παραδείγματα:
IPAddress.new('1.2.3.4'):getPreviousIP() -- Equivalent to IPAddress.new('1.2.3.3')
IPAddress.new('2001:db8::ff00:12:3456'):getPreviousIP() -- Equivalent to IPAddress.new('2001:db8::ff00:12:3455')
IPAddress.new('0.0.0.0'):getPreviousIP() -- Equivalent to IPAddress.new('255.255.255.255')
Subnet
ΕπεξεργασίαΗ κλάση Subnet χρησιμοποιείται για εργασία με υποδίκτυα διευθύνσεων IPv4 ή IPv6. Για να δημιουργήσετε ένα νέο αντικείμενο υποδικτύου:
local subnet = Subnet.new(cidrString)
cidrStringπρέπει να είναι έγκυρη συμβολοσειρά IPv4 ή IPv6 CIDR.
Τα αντικείμενα υποδικτύου μπορούν να συγκριθούν ως προς την ισότητα:
Subnet.new('1.2.3.0/24') == Subnet.new('1.2.3.0/24') -- true
Subnet.new('1.2.3.0/24') == Subnet.new('1.2.3.0/25') -- false
Subnet.new('1.2.3.0/24') == Subnet.new('2001:db8::ff00:12:0/112') -- false
Subnet.new('2001:db8::ff00:12:0/112') == Subnet.new('2001:db8::ff00:12:0/112') -- true
Subnet.new('2001:db8:0:0:0:0:0:0/112') == Subnet.new('2001:db8::/112') -- true
Μπορείτε να χρησιμοποιήσετε tostring σε αυτά (αυτό ισοδυναμεί με getCIDR):
tostring(Subnet.new('1.2.3.0/24')) -- "1.2.3.0/24"
tostring(Subnet.new('2001:db8::ff00:12:0/112')) -- "2001:db8::ff00:12:0/112"
tostring(Subnet.new('2001:db8:0:0:0:0:0:0/112')) -- "2001:db8::/112"
Μπορείτε επίσης να τα συνδυάσετε:
Subnet.new('1.2.3.0/24') .. ' foo' -- "1.2.3.0/24 foo"
Subnet.new('1.2.3.0/24') .. Subnet.new('4.5.6.0/24') -- "1.2.3.0/244.5.6.0/24"
Τα αντικείμενα υποδικτύου έχουν διάφορες μεθόδους, οι οποίες περιγράφονται παρακάτω.
getPrefix
Επεξεργασίαsubnet:getPrefix()
Επιστρέφει ένα αντικείμενο IPAddress για τη χαμηλότερη διεύθυνση IP στο υποδίκτυο.
Παραδείγματα:
Subnet.new('1.2.3.0/24'):getPrefix() -- Equivalent to IPAddress.new('1.2.3.0')
Subnet.new('2001:db8::ff00:12:0/112'):getPrefix() -- Equivalent to IPAddress.new('2001:db8::ff00:12:0')
getHighestIP
Επεξεργασίαsubnet:getHighestIP()
Επιστρέφει ένα αντικείμενο IPAddress για την υψηλότερη διεύθυνση IP στο υποδίκτυο.
Παραδείγματα:
Subnet.new('1.2.3.0/24'):getHighestIP() -- Equivalent to IPAddress.new('1.2.3.255')
Subnet.new('2001:db8::ff00:12:0/112'):getHighestIP() -- Equivalent to IPAddress.new('2001:db8::ff00:12:ffff')
getBitLength
Επεξεργασίαsubnet:getBitLength()
Επιστρέφει το μήκος bit του υποδικτύου. Αυτός είναι ένας ακέραιος αριθμός μεταξύ 0 και 32 για διευθύνσεις IPv4 ή ακέραιος μεταξύ 0 και 128 για διευθύνσεις IPv6.
Παραδείγματα:
Subnet.new('1.2.3.0/24'):getBitLength() -- 24
Subnet.new('2001:db8::ff00:12:0/112'):getBitLength() -- 112
getCIDR
Επεξεργασίαsubnet:getCIDR()
Επιστρέφει μια συμβολοσειρά αναπαράστασης CIDR του υποδικτύου.
Παραδείγματα:
Subnet.new('1.2.3.0/24'):getCIDR() -- "1.2.3.0/24"
Subnet.new('2001:db8::ff00:12:0/112'):getCIDR() -- "2001:db8::ff00:12:0/112"
Subnet.new('2001:db8:0:0:0:0:0:0/112'):getCIDR() -- "2001:db8::/112"
getVersion
Επεξεργασίαsubnet:getVersion()
Επιστρέφει την έκδοση του πρωτοκόλλου IP που χρησιμοποιείται. Αυτό είναι "IPv4" για διευθύνσεις IPv4 και "IPv6" για διευθύνσεις IPv6.
Παραδείγματα:
Subnet.new('1.2.3.0/24'):getVersion() -- "IPv4"
Subnet.new('2001:db8::ff00:12:0/112'):getVersion() -- "IPv6"
isIPv4
Επεξεργασίαsubnet:isIPv4()
Επιστρέφει true εάν το υποδίκτυο χρησιμοποιεί IPv4 και false διαφορετικά.
Παραδείγματα:
Subnet.new('1.2.3.0/24'):isIPv4() -- true
Subnet.new('2001:db8::ff00:12:0/112'):isIPv4() -- false
isIPv6
Επεξεργασίαsubnet:isIPv6()
Επιστρέφει true εάν το υποδίκτυο χρησιμοποιεί IPv6 και false διαφορετικά.
Παραδείγματα:
Subnet.new('1.2.3.0/24'):isIPv6() -- false
Subnet.new('2001:db8::ff00:12:0/112'):isIPv6() -- true
containsIP
Επεξεργασίαsubnet:containsIP(ip)
Επιστρέφει true εάν το υποδίκτυο περιέχει τη διεύθυνση IP ip, και false διαφορετικά. Το ip μπορεί να είναι μια συμβολοσειρά διεύθυνσης IP ή ένα αντικείμενο διεύθυνσης IP.
Παραδείγματα:
Subnet.new('1.2.3.0/24'):containsIP('1.2.3.4') -- true
Subnet.new('1.2.3.0/24'):containsIP('1.2.4.4') -- false
Subnet.new('1.2.3.0/24'):containsIP(IPAddress.new('1.2.3.4')) -- true
Subnet.new('2001:db8::ff00:12:0/112'):containsIP('2001:db8::ff00:12:3456') -- true
overlapsSubnet
Επεξεργασίαsubnet:overlapsSubnet(subnet)
Επιστρέφει true εάν το τρέχον υποδίκτυο επικαλύπτεται με το subnet, και false διαφορετικά. Το subnet μπορεί να είναι μια συμβολοσειρά CIDR ή ένα αντικείμενο υποδικτύου.
Παραδείγματα:
Subnet.new('1.2.3.0/24'):overlapsSubnet('1.2.0.0/16') -- true
Subnet.new('1.2.3.0/24'):overlapsSubnet('1.2.12.0/22') -- false
Subnet.new('1.2.3.0/24'):overlapsSubnet(Subnet.new('1.2.0.0/16')) -- true
Subnet.new('2001:db8::ff00:12:0/112'):overlapsSubnet('2001:db8::ff00:0:0/96') -- true
walk
Επεξεργασίαsubnet:walk()
Η μέθοδος walk περιδιαβαίνει (iterates) σε όλα τα αντικείμενα διεύθυνσης IP στο υποδίκτυο.
Παραδείγματα:
for ipAddress in Subnet.new('192.168.0.0/30'):walk() do
mw.log(tostring(ipAddress))
end
-- 192.168.0.0
-- 192.168.0.1
-- 192.168.0.2
-- 192.168.0.3
IPv4Collection
ΕπεξεργασίαΗ κλάση IPv4Collection χρησιμοποιείται για εργασία με πολλές διαφορετικές διευθύνσεις IPv4 και υποδίκτυα IPv4. Για να δημιουργήσετε ένα νέο αντικείμενο IPv4Collection:
local collection = IPv4Collection.new()
Τα αντικείμενα IPv4Collection έχουν διάφορες μεθόδους, οι οποίες περιγράφονται παρακάτω.
getVersion
Επεξεργασίαcollection:getVersion()
Επιστρέφει τη συμβολοσειρά "IPv4".
addIP
Επεξεργασίαcollection:addIP(ip)
Προσθέτει μια IP στη συλλογή. Η IP μπορεί να είναι είτε μια συμβολοσειρά είτε ένα αντικείμενο IPAddress.
Παραδείγματα:
collection:addIP('1.2.3.4')
collection:addIP(IPAddress.new('1.2.3.4'))
Αυτή η μέθοδος μπορεί να είναι αλυσιδωτή:
collection:addIP('1.2.3.4'):addIP('5.6.7.8')
addSubnet
Επεξεργασίαcollection:addSubnet(subnet)
Προσθέτει ένα υποδίκτυο στη συλλογή. Το υποδίκτυο μπορεί να είναι είτε συμβολοσειρά CIDR ή ένα αντικείμενο Subnet.
Παραδείγματα:
collection:addSubnet('1.2.3.0/24')
collection:addSubnet(Subnet.new('1.2.3.0/24'))
Αυτή η μέθοδος μπορεί να είναι αλυσιδωτή:
collection:addSubnet('1.2.0.0/24'):addSubnet('1.2.1.0/24')
addFromString
Επεξεργασίαcollection:addFromString(str)
Εξάγει τυχόν διευθύνσεις IPv4 και IPv4 υποδίκτυα CIDR από το str και τα προσθέτει στη συλλογή. Οποιοδήποτε κείμενο δεν είναι διεύθυνση IPv4 ή υποδίκτυο CIDR αγνοείται.
Παραδείγματα:
collection:addFromString('Add some IPs and subnets: 1.2.3.4 1.2.3.5 2001:0::f foo 1.2.4.0/24')
Αυτή η μέθοδος μπορεί να είναι αλυσιδωτή:
collection:addFromString('foo 1.2.3.4'):addFromString('bar 5.6.7.8')
containsIP
Επεξεργασίαcollection:containsIP(ip)
Επιστρέφει true εάν η συλλογή περιέχει την καθορισμένη IP· διαφορετικά επιστρέφει false. Η παράμετρος ip μπορεί να είναι μια συμβολοσειρά ή ένα αντικείμενο IPAddress.
Παραδείγματα:
collection:containsIP('1.2.3.4')
collection:containsIP(IPAddress.new('1.2.3.4'))
getRanges
Επεξεργασίαcollection:getRanges()
Επιστρέφει έναν ταξινομημένο πίνακα ζευγών IP ισοδύναμου με τη συλλογή. Κάθε ζεύγος IP είναι ένας πίνακας που αντιπροσωπεύει ένα συνεχόμενο εύρος διευθύνσεων IP από pair[1] έως pair[2] συμπεριλαμβανομένου. Το pair[1] και το pair[2] είναι αντικείμενα IPAddress.
Παραδείγματα:
collection:addSubnet('1.2.0.0/24')
collection:addSubnet('1.2.1.0/24')
collection:addSubnet('1.2.10.0/24')
mw.logObject(collection:getRanges())
-- Logs the following:
-- table#1 {
-- table#2 {
-- 1.2.0.0,
-- 1.2.1.255,
-- },
-- table#3 {
-- 1.2.10.0,
-- 1.2.10.255,
-- },
-- }
overlapsSubnet
Επεξεργασίαcollection:overlapsSubnet(subnet)
Επιστρέφει true, obj εάν το subnet επικαλύπτει αυτήν τη συλλογή, όπου το obj είναι το πρώτο αντικείμενο IPAddress ή Subnet που επικαλύπτει το υποδίκτυο. Διαφορετικά, επιστρέφει false. Το subnet μπορεί να είναι μια συμβολοσειρά CIDR ή ένα αντικείμενο Subnet .
Παραδείγματα:
collection:addIP('1.2.3.4')
collection:overlapsSubnet('1.2.3.0/24') -- true, IPAddress.new('1.2.3.4')
collection:overlapsSubnet('1.2.4.0/24') -- false
IPv6Collection
ΕπεξεργασίαΗ κλάση IPv6Collection χρησιμοποιείται για εργασία με πολλές διαφορετικές διευθύνσεις IPv6 και υποδίκτυα IPv6. Τα αντικείμενα IPv6Collection είναι άμεσα ανάλογα με τα αντικείμενα IPv4Collection: περιέχουν τις ίδιες μεθόδους και λειτουργούν με τον ίδιο τρόπο, αλλά όλες οι διευθύνσεις IP και τα υποδίκτυα που προστίθενται σε αυτό πρέπει να είναι IPv6, όχι IPv4.
Για να δημιουργήσετε ένα νέο αντικείμενο IPv6Collection:
local collection = IPv6Collection.new()
-- 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 < pos and pos <= self.n)) then
self.n = self.n - 1
return table.remove(self, pos)
end
end
function Collection:sort(comp)
table.sort(self, comp)
end
function Collection:deobjectify()
-- Turns the collection into a plain array without any special properties
-- or methods.
self.n = nil
setmetatable(self, nil)
end
function Collection.new()
return setmetatable({n = 0}, Collection)
end
--------------------------------------------------------------------------------
-- RawIP class
-- Numeric representation of an IPv4 or IPv6 address. Used internally.
-- A RawIP object is constructed by adding data to a Collection object and
-- then giving it a new metatable. This is to avoid the memory overhead of
-- copying the data to a new table.
--------------------------------------------------------------------------------
local RawIP = {}
RawIP.__index = RawIP
-- Constructors
function RawIP.newFromIPv4(ipStr)
-- Return a RawIP object if ipStr is a valid IPv4 string. Otherwise,
-- return nil.
-- This representation is for compatibility with IPv6 addresses.
local octets = Collection.new()
local s = ipStr:match('^%s*(.-)%s*$') .. '.'
for item in s:gmatch('(.-)%.') do
octets:add(item)
end
if octets.n == 4 then
for i, s in ipairs(octets) do
if s:match('^%d+$') then
local num = tonumber(s)
if 0 <= num and num <= 255 then
if num > 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 < 7 then
ipStr = ipStr:gsub('::', string.rep(':', 9 - n))
end
local parts = Collection.new()
for item in (ipStr .. ':'):gmatch('(.-):') do
parts:add(item)
end
if parts.n == 8 then
for i, s in ipairs(parts) do
if s == '' then
parts[i] = 0
else
if s:match('^%x+$') then
local num = tonumber(s, 16)
if num and 0 <= num and num <= 65535 then
parts[i] = num
else
return nil
end
else
return nil
end
end
end
return setmetatable(parts, RawIP)
end
return nil
end
function RawIP.newFromIP(ipStr)
-- Return a new RawIP object from either an IPv4 string or an IPv6
-- string. If ipStr is not a valid IPv4 or IPv6 string, then return
-- nil.
return RawIP.newFromIPv4(ipStr) or RawIP.newFromIPv6(ipStr)
end
-- Methods
function RawIP:getVersion()
-- Return a string with the version of the IP protocol we are using.
return self.n == 2 and V4 or V6
end
function RawIP:isIPv4()
-- Return true if this is an IPv4 representation, and false otherwise.
return self.n == 2
end
function RawIP:isIPv6()
-- Return true if this is an IPv6 representation, and false otherwise.
return self.n == 8
end
function RawIP:getBitLength()
-- Return the bit length of the IP address.
return self.n * 16
end
function RawIP:getAdjacent(previous)
-- Return a RawIP object for an adjacent IP address. If previous is true
-- then the previous IP is returned; otherwise the next IP is returned.
-- Will wraparound:
-- next 255.255.255.255 → 0.0.0.0
-- ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff → ::
-- previous 0.0.0.0 → 255.255.255.255
-- :: → ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
local result = Collection.new()
result.n = self.n
local carry = previous and 0xffff or 1
for i = self.n, 1, -1 do
local sum = self[i] + carry
if sum >= 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 <= 0 then
width = bits
elseif 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 < 9 and self[i] == 0 then
if zstart then
zcount = zcount + 1
else
zstart = i
zcount = 1
end
else
if zcount and zcount > 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 <= i and i <= z2 then
if i == z1 then
if z1 == 1 or z2 == 8 then
if z1 == 1 and z2 == 8 then
return '::'
end
parts:add(':')
else
parts:add('')
end
end
else
parts:add(string.format('%x', self[i]))
end
end
return parts:join(':')
end
function RawIP:_makeIPv4String()
-- Return an IPv4 string representation of the object. Behavior is
-- undefined if the current object is IPv6.
local parts = Collection.new()
for i = 1, 2 do
local w = self[i]
parts:add(math.floor(w / 256))
parts:add(w % 256)
end
return parts:join('.')
end
function RawIP:__tostring()
-- Return a string equivalent to given IP address (IPv4 or IPv6).
if self.n == 2 then
return self:_makeIPv4String()
else
return self:_makeIPv6String()
end
end
function RawIP:__lt(obj)
if self.n == obj.n then
for i = 1, self.n do
if self[i] ~= obj[i] then
return self[i] < obj[i]
end
end
return false
end
return self.n < obj.n
end
function RawIP:__eq(obj)
if self.n == obj.n then
for i = 1, self.n do
if self[i] ~= obj[i] then
return false
end
end
return true
end
return false
end
--------------------------------------------------------------------------------
-- Initialize private methods available to IPAddress and Subnet
--------------------------------------------------------------------------------
-- Both IPAddress and Subnet need access to each others' private constructor
-- functions. IPAddress must be able to make Subnet objects from CIDR strings
-- and from RawIP objects, and Subnet must be able to make IPAddress objects
-- from IP strings and from RawIP objects. These constructors must all be
-- private to ensure correct error levels and to stop other modules from having
-- to worry about RawIP objects. Because they are private, they must be
-- initialized here.
local makeIPAddress, makeIPAddressFromRaw, makeSubnet, makeSubnetFromRaw
-- Objects need to be able to validate other objects that they are passed
-- as input, so initialize those functions here as well.
local validateCollection, validateIPAddress, validateSubnet
--------------------------------------------------------------------------------
-- IPAddress class
-- Represents a single IPv4 or IPv6 address.
--------------------------------------------------------------------------------
local IPAddress = {}
do
-- dataKey is a unique key to access objects' internal data. This is needed
-- to access the RawIP objects contained in other IPAddress objects so that
-- they can be compared with the current object's RawIP object. This data
-- is not available to other classes or other modules.
local dataKey = {}
-- Private static methods
local function isIPAddressObject(val)
return type(val) == 'table' and val[dataKey] ~= nil
end
validateIPAddress = makeValidationFunction('IPAddress', isIPAddressObject)
-- Metamethods that don't need upvalues
local function ipEquals(ip1, ip2)
return ip1[dataKey].rawIP == ip2[dataKey].rawIP
end
local function ipLessThan(ip1, ip2)
return ip1[dataKey].rawIP < ip2[dataKey].rawIP
end
local function concatIP(ip, val)
return tostring(ip) .. tostring(val)
end
local function ipToString(ip)
return ip:getIP()
end
-- Constructors
makeIPAddressFromRaw = function (rawIP)
-- Constructs a new IPAddress object from a rawIP object. This function
-- is for internal use; it is called by IPAddress.new and from other
-- IPAddress methods, and should be available to the Subnet class, but
-- should not be available to other modules.
assert(type(rawIP) == 'table', 'rawIP was type ' .. type(rawIP) .. '; expected type table')
-- Set up structure
local obj = {}
local data = {}
data.rawIP = rawIP
-- A function to check whether methods are called with a valid self
-- parameter.
local checkSelf = makeCheckSelfFunction(
'IP',
'ipAddress',
obj,
'IPAddress object'
)
-- Public methods
function obj:getIP()
checkSelf(self, 'getIP')
return tostring(data.rawIP)
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:isInCollection(collection)
checkSelf(self, 'isInCollection')
validateCollection('isInCollection', 1, collection)
return collection:containsIP(self)
end
function obj:isInSubnet(subnet)
checkSelf(self, 'isInSubnet')
local tp = type(subnet)
if tp == 'string' then
subnet = makeSubnet(subnet)
elseif tp == 'table' then
validateSubnet('isInSubnet', 1, subnet)
else
checkTypeMulti('isInSubnet', 1, subnet, {'string', 'table'})
end
return subnet:containsIP(self)
end
function obj:getSubnet(bitLength)
checkSelf(self, 'getSubnet')
checkType('getSubnet', 1, bitLength, 'number')
if bitLength < 0
or bitLength > 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() <= ip and ip <= self:getHighestIP()
end
return false
end
function obj:overlapsCollection(collection)
checkSelf(self, 'overlapsCollection')
validateCollection('overlapsCollection', 1, collection)
return collection:overlapsSubnet(self)
end
function obj:overlapsSubnet(subnet)
checkSelf(self, 'overlapsSubnet')
local tp = type(subnet)
if tp == 'string' then
subnet = makeSubnet(subnet)
elseif tp == 'table' then
validateSubnet('overlapsSubnet', 1, subnet)
else
checkTypeMulti('overlapsSubnet', 1, subnet, {'string', 'table'})
end
if self:getVersion() == subnet:getVersion() then
return (
subnet:getHighestIP() >= self:getPrefix() and
subnet:getPrefix() <= self:getHighestIP()
)
end
return false
end
function obj:walk()
checkSelf(self, 'walk')
local started
local current = self:getPrefix()
local highest = self:getHighestIP()
return function ()
if not started then
started = true
return current
end
if current < highest then
current = current:getNextIP()
return current
end
end
end
return obj
end
makeSubnet = function (cidr)
-- Return a Subnet object from a CIDR string. If the CIDR string is
-- invalid, throw an error.
local lhs, rhs = cidr:match('^%s*(.-)/(%d+)%s*$')
if lhs then
local bits = lhs:find(':', 1, true) and 128 or 32
local n = tonumber(rhs)
if n and n <= bits and (n == 0 or not rhs:find('^0')) then
-- The right-hand side is a number between 0 and 32 (for IPv4)
-- or 0 and 128 (for IPv6) and doesn't have any leading zeroes.
local base = RawIP.newFromIP(lhs)
if base then
-- The left-hand side is a valid IP address.
local prefix = base:getPrefix(n)
if base == prefix then
-- The left-hand side is the lowest IP in the subnet.
return makeSubnetFromRaw(prefix, n)
end
end
end
end
error(string.format("'%s' is an invalid CIDR string", cidr), 3)
end
function Subnet.new(cidr)
checkType('Subnet.new', 1, cidr, 'string')
return makeSubnet(cidr)
end
end
--------------------------------------------------------------------------------
-- Ranges class
-- Holds a list of IPAdress pairs representing contiguous IP ranges.
--------------------------------------------------------------------------------
local Ranges = Collection.new()
Ranges.__index = Ranges
function Ranges.new()
return setmetatable({}, Ranges)
end
function Ranges:add(ip1, ip2)
validateIPAddress('add', 1, ip1)
if ip2 ~= nil then
validateIPAddress('add', 2, ip2)
if ip1 > 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] < rhs[1]
end
return lhs[2] < rhs[2]
end
)
local pos = self.n
while pos > 1 do
for i = pos - 1, 1, -1 do
local ip1 = self[i][2]
local ip2 = ip1:getNextIP()
if ip2 < ip1 then
ip2 = ip1 -- don't wrap around
end
if self[pos][1] > 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 <= 1 then
break
end
end
pos = pos - 1
end
end
--------------------------------------------------------------------------------
-- IPCollection class
-- Holds a list of IP addresses/subnets. Used internally.
-- Each address/subnet has the same version (either IPv4 or IPv6).
--------------------------------------------------------------------------------
local IPCollection = {}
IPCollection.__index = IPCollection
function IPCollection.new(version)
assert(
version == V4 or version == V6,
'IPCollection.new called with an invalid version'
)
local obj = {
version = version, -- V4 or V6
addresses = Collection.new(), -- valid IP addresses
subnets = Collection.new(), -- valid subnets
omitted = Collection.new(), -- not-quite valid strings
}
return obj
end
function IPCollection:getVersion()
-- Return a string with the IP version of addresses in this collection.
return self.version
end
function IPCollection:_store(hit, stripColons)
local maker, location
if hit:find('/', 1, true) then
maker = Subnet.new
location = self.subnets
else
maker = IPAddress.new
location = self.addresses
end
local success, obj = pcall(maker, hit)
if success then
location:add(obj)
else
if stripColons then
local colons, hit = hit:match('^(:*)(.*)')
if colons ~= '' then
self:_store(hit)
return
end
end
self.omitted:add(hit)
end
end
function IPCollection:_assertVersion(version, msg)
if self.version ~= version then
error(msg, 3)
end
end
function IPCollection:addIP(ip)
local tp = type(ip)
if tp == 'string' then
ip = makeIPAddress(ip)
elseif tp == 'table' then
validateIPAddress('addIP', 1, ip)
else
checkTypeMulti('addIP', 1, ip, {'string', 'table'})
end
self:_assertVersion(ip:getVersion(), 'addIP called with incorrect IP version')
self.addresses:add(ip)
return self
end
function IPCollection:addSubnet(subnet)
local tp = type(subnet)
if tp == 'string' then
subnet = makeSubnet(subnet)
elseif tp == 'table' then
validateSubnet('addSubnet', 1, subnet)
else
checkTypeMulti('addSubnet', 1, subnet, {'string', 'table'})
end
self:_assertVersion(subnet:getVersion(), 'addSubnet called with incorrect subnet version')
self.subnets:add(subnet)
return self
end
function IPCollection:containsIP(ip)
-- Return true, obj if ip is in this collection,
-- where obj is the first IPAddress or Subnet with the ip.
-- Otherwise, return false.
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
for _, item in ipairs(self.addresses) do
if item == ip then
return true, item
end
end
for _, item in ipairs(self.subnets) do
if item:containsIP(ip) then
return true, item
end
end
end
return false
end
function IPCollection:getRanges()
-- Return a sorted table of IP pairs equivalent to the collection.
-- Each IP pair is a table representing a contiguous range of
-- IP addresses from pair[1] to pair[2] inclusive (IPAddress objects).
local ranges = Ranges.new()
for _, item in ipairs(self.addresses) do
ranges:add(item)
end
for _, item in ipairs(self.subnets) do
ranges:add(item:getPrefix(), item:getHighestIP())
end
ranges:merge()
ranges:deobjectify()
return ranges
end
function IPCollection:overlapsSubnet(subnet)
-- Return true, obj if subnet overlaps this collection,
-- where obj is the first IPAddress or Subnet overlapping the subnet.
-- Otherwise, return false.
local tp = type(subnet)
if tp == 'string' then
subnet = makeSubnet(subnet)
elseif tp == 'table' then
validateSubnet('overlapsSubnet', 1, subnet)
else
checkTypeMulti('overlapsSubnet', 1, subnet, {'string', 'table'})
end
if self:getVersion() == subnet:getVersion() then
for _, item in ipairs(self.addresses) do
if subnet:containsIP(item) then
return true, item
end
end
for _, item in ipairs(self.subnets) do
if subnet:overlapsSubnet(item) then
return true, item
end
end
end
return false
end
--------------------------------------------------------------------------------
-- IPv4Collection class
-- Holds a list of IPv4 addresses/subnets.
--------------------------------------------------------------------------------
local IPv4Collection = setmetatable({}, IPCollection)
IPv4Collection.__index = IPv4Collection
function IPv4Collection.new()
return setmetatable(IPCollection.new(V4), IPv4Collection)
end
function IPv4Collection:addFromString(text)
-- Extract any IPv4 addresses or CIDR subnets from given text.
checkType('addFromString', 1, text, 'string')
text = text:gsub('[:!"#&\'()+,%-;<=>?[%]_{|}]', ' ')
for hit in text:gmatch('%S+') do
if hit:match('^%d+%.%d+[%.%d/]+$') then
local _, n = hit:gsub('%.', '.')
if n >= 3 then
self:_store(hit)
end
end
end
return self
end
--------------------------------------------------------------------------------
-- IPv6Collection class
-- Holds a list of IPv6 addresses/subnets.
--------------------------------------------------------------------------------
local IPv6Collection = setmetatable({}, IPCollection)
IPv6Collection.__index = IPv6Collection
do
-- Private static methods
local function isCollectionObject(val)
-- Return true if val is probably derived from an IPCollection object,
-- otherwise return false.
if type(val) == 'table' then
local mt = getmetatable(val)
if mt == IPv4Collection or mt == IPv6Collection then
return true
end
end
return false
end
validateCollection = makeValidationFunction('IPCollection', isCollectionObject)
function IPv6Collection.new()
return setmetatable(IPCollection.new(V6), IPv6Collection)
end
function IPv6Collection:addFromString(text)
-- Extract any IPv6 addresses or CIDR subnets from given text.
-- Want to accept all valid IPv6 despite the fact that addresses used
-- are unlikely to start with ':'.
-- Also want to be able to parse arbitrary wikitext which might use
-- colons for indenting.
-- Therefore, if an address at the start of a line is valid, use it;
-- otherwise strip any leading colons and try again.
checkType('addFromString', 1, text, 'string')
for line in string.gmatch(text .. '\n', '[\t ]*(.-)[\t\r ]*\n') do
line = line:gsub('[!"#&\'()+,%-;<=>?[%]_{|}]', ' ')
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,
}