Module:IP
![]() | This module is rated as ready for general use. It has reached a mature form and is thought to be relatively bug-free and ready for use wherever appropriate. It is ready to mention on help pages and other Wikipedia resources as an option for new users to learn. To reduce server load and bad output, it should be improved by sandbox testing rather than repeated trial-and-error editing. |
![]() | This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
![]() | This module can only be edited by administrators because it is transcluded onto one or more cascade-protected pages. |
![]() | This Lua module is used in system messages, and on approximately 141,000 pages. Changes to it can cause immediate changes to the Wikipedia user interface. To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Please discuss changes on the talk page before implementing them. |
Module:IP is a library for working with IP addresses and subnets. It can handle both IPv4 and IPv6. The library exports four classes, IPAddress, Subnet, IPv4Collection, and IPv6Collection.
Loading the library
localIP=require('Module:IP')localIPAddress=IP.IPAddresslocalSubnet=IP.Subnet
IPAddress
The IPAddress class is used to work with single IP addresses. To create a new IPAddress object:
localipAddress=IPAddress.new(ipString)
The ipString variable can be a valid IPv4 or IPv6 address.
Examples:
localipv4Address=IPAddress.new('1.2.3.4')localipv6Address=IPAddress.new('2001:db8::ff00:12:3456')
If a non-IP string or an invalid IP address is passed to the function, that returns an error. If you want to check whether a given string is an IP address and continue the procedure in the caller module, use pcall.
localisIp,ip=pcall(IPAddress.new,'1.2.3.4')-- isIp: true, ip: IPAddress objectlocalisIp,ip=pcall(IPAddress.new,'Example')-- isIp: false, ip: nillocalisIp,ip=pcall(IPAddress.new,'1.2.3.256')-- isIp: false, ip: nil
IPAddress objects can be compared with relational operators:
-- EqualityIPAddress.new('1.2.3.4')==IPAddress.new('1.2.3.4')-- trueIPAddress.new('1.2.3.4')==IPAddress.new('1.2.3.5')-- false-- Less than / greater thanIPAddress.new('1.2.3.4')<IPAddress.new('1.2.3.5')-- trueIPAddress.new('1.2.3.4')>IPAddress.new('1.2.3.5')-- falseIPAddress.new('1.2.3.4')<=IPAddress.new('1.2.3.5')-- trueIPAddress.new('1.2.3.4')<=IPAddress.new('1.2.3.4')-- true
You can use tostring on them (this is equivalent to using 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"-- Expanded IPv6 addresses are abbreviated:tostring(IPAddress.new('2001:db8:0:0:0:0:0:0'))-- "2001:db8::"
You can also concatenate them:
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 objects have several methods, outlined below.
getIP
ipAddress:getIP()
Returns a string representation of the IP address. IPv6 addresses are abbreviated if possible.
Examples:
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()
Returns the version of the IP protocol being used. This is "IPv4" for IPv4 addresses, and "IPv6" for IPv6 addresses.
Examples:
IPAddress.new('1.2.3.4'):getVersion()-- "IPv4"IPAddress.new('2001:db8::ff00:12:3456'):getVersion()-- "IPv6"
isIPv4
ipAddress:isIPv4()
Returns true if the IP address is an IPv4 address, and false otherwise.
Examples:
IPAddress.new('1.2.3.4'):isIPv4()-- trueIPAddress.new('2001:db8::ff00:12:3456'):isIPv4()-- false
isIPv6
ipAddress:isIPv6()
Returns true if the IP address is an IPv6 address, and false otherwise.
Examples:
IPAddress.new('1.2.3.4'):isIPv6()-- falseIPAddress.new('2001:db8::ff00:12:3456'):isIPv6()-- true
isInSubnet
ipAddress:isInSubnet(subnet)
Returns true if the IP address is in the subnet subnet, and false otherwise. subnet may be a Subnet object or a CIDR string.
Examples:
IPAddress.new('1.2.3.4'):isInSubnet('1.2.3.0/24')-- trueIPAddress.new('1.2.3.4'):isInSubnet('1.2.4.0/24')-- falseIPAddress.new('1.2.3.4'):isInSubnet(Subnet.new('1.2.3.0/24'))-- trueIPAddress.new('2001:db8::ff00:12:3456'):isInSubnet('2001:db8::ff00:12:0/112')-- true
getSubnet
ipAddress:getSubnet(bitLength)
Returns a Subnet object for the subnet with a bit length of bitLength which contains the current IP. The bitLength parameter must be an integer between 0 and 32 for IPv4 addresses, or an integer between 0 and 128 for IPv6 addresses.
Examples:
IPAddress.new('1.2.3.4'):getSubnet(24)-- Equivalent to Subnet.new('1.2.3.0/24')
getNextIP
ipAddress:getNextIP()
Returns a new IPAddress object equivalent to the current IP address incremented by one. The IPv4 address "255.255.255.255" rolls around to "0.0.0.0", and the IPv6 address "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" rolls around to "::".
Examples:
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()
Returns a new IPAddress object equivalent to the current IP address decremented by one. The IPv4 address "0.0.0.0" rolls around to "255.255.255.255", and the IPv6 address "::" rolls around to "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff".
Examples:
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
The Subnet class is used to work with subnetworks of IPv4 or IPv6 addresses. To create a new Subnet object:
localsubnet=Subnet.new(cidrString)
cidrString must be a valid IPv4 or IPv6 CIDR string.
localcidr=Subnet.new('255.255.255.0/24')-- Subnet objectlocalcidr=Subnet.new('255.255.255.1/24')-- error
Subnet objects can be compared for equality:
Subnet.new('1.2.3.0/24')==Subnet.new('1.2.3.0/24')-- trueSubnet.new('1.2.3.0/24')==Subnet.new('1.2.3.0/25')-- falseSubnet.new('1.2.3.0/24')==Subnet.new('2001:db8::ff00:12:0/112')-- falseSubnet.new('2001:db8::ff00:12:0/112')==Subnet.new('2001:db8::ff00:12:0/112')-- trueSubnet.new('2001:db8:0:0:0:0:0:0/112')==Subnet.new('2001:db8::/112')-- true
You can use tostring on them (this is equivalent to 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"
You can also concatenate them:
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"
Subnet objects have several methods, outlined below.
getPrefix
subnet:getPrefix()
Returns an IPAddress object for the lowest IP address in the subnet.
Examples:
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()
Returns an IPAddress object for the highest IP address in the subnet.
Examples:
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()
Returns the bit length of the subnet. This is an integer between 0 and 32 for IPv4 addresses, or an integer between 0 and 128 for IPv6 addresses.
Examples:
Subnet.new('1.2.3.0/24'):getBitLength()-- 24Subnet.new('2001:db8::ff00:12:0/112'):getBitLength()-- 112
getCIDR
subnet:getCIDR()
Returns a CIDR string representation of the subnet.
Examples:
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()
Returns the version of the IP protocol being used. This is "IPv4" for IPv4 addresses, and "IPv6" for IPv6 addresses.
Examples:
Subnet.new('1.2.3.0/24'):getVersion()-- "IPv4"Subnet.new('2001:db8::ff00:12:0/112'):getVersion()-- "IPv6"
isIPv4
subnet:isIPv4()
Returns true if the subnet is using IPv4, and false otherwise.
Examples:
Subnet.new('1.2.3.0/24'):isIPv4()-- trueSubnet.new('2001:db8::ff00:12:0/112'):isIPv4()-- false
isIPv6
subnet:isIPv6()
Returns true if the subnet is using IPv6, and false otherwise.
Examples:
Subnet.new('1.2.3.0/24'):isIPv6()-- falseSubnet.new('2001:db8::ff00:12:0/112'):isIPv6()-- true
containsIP
subnet:containsIP(ip)
Returns true if the subnet contains the IP address ip, and false otherwise. ip can be an IP address string, or an IPAddress object.
Examples:
Subnet.new('1.2.3.0/24'):containsIP('1.2.3.4')-- trueSubnet.new('1.2.3.0/24'):containsIP('1.2.4.4')-- falseSubnet.new('1.2.3.0/24'):containsIP(IPAddress.new('1.2.3.4'))-- trueSubnet.new('2001:db8::ff00:12:0/112'):containsIP('2001:db8::ff00:12:3456')-- true
overlapsSubnet
subnet:overlapsSubnet(subnet)
Returns true if the current subnet overlaps with subnet, and false otherwise. subnet can be a CIDR string or a subnet object.
Examples:
Subnet.new('1.2.3.0/24'):overlapsSubnet('1.2.0.0/16')-- trueSubnet.new('1.2.3.0/24'):overlapsSubnet('1.2.12.0/22')-- falseSubnet.new('1.2.3.0/24'):overlapsSubnet(Subnet.new('1.2.0.0/16'))-- trueSubnet.new('2001:db8::ff00:12:0/112'):overlapsSubnet('2001:db8::ff00:0:0/96')-- true
walk
subnet:walk()
The walk method iterates over all of the IPAddress objects in the subnet.
Examples:
foripAddressinSubnet.new('192.168.0.0/30'):walk()domw.log(tostring(ipAddress))end-- 192.168.0.0-- 192.168.0.1-- 192.168.0.2-- 192.168.0.3
IPv4Collection
The IPv4Collection class is used to work with several different IPv4 addresses and IPv4 subnets. To create a new IPv4Collection object:
localcollection=IPv4Collection.new()
IPv4Collection objects have several methods, outlined below.
getVersion
collection:getVersion()
Returns the string "IPv4".
addIP
collection:addIP(ip)
Adds an IP to the collection. The IP can be either a string or an IPAddress object.
Examples:
collection:addIP('1.2.3.4')collection:addIP(IPAddress.new('1.2.3.4'))
This method is chainable:
collection:addIP('1.2.3.4'):addIP('5.6.7.8')
addSubnet
collection:addSubnet(subnet)
Adds a subnet to the collection. The subnet can be either a CIDR string or a Subnet object.
Examples:
collection:addSubnet('1.2.3.0/24')collection:addSubnet(Subnet.new('1.2.3.0/24'))
This method is chainable:
collection:addSubnet('1.2.0.0/24'):addSubnet('1.2.1.0/24')
addFromString
collection:addFromString(str)
Extracts any IPv4 addresses and IPv4 CIDR subnets from str and adds them to the collection. Any text that is not an IPv4 address or CIDR subnet is ignored.
Examples:
collection:addFromString('Add some IPs and subnets: 1.2.3.4 1.2.3.5 2001:0::f foo 1.2.4.0/24')
This method is chainable:
collection:addFromString('foo 1.2.3.4'):addFromString('bar 5.6.7.8')
containsIP
collection:containsIP(ip)
Returns true if the collection contains the specified IP; otherwise returns false. The ip parameter can be a string or an IPAddress object.
Examples:
collection:containsIP('1.2.3.4')collection:containsIP(IPAddress.new('1.2.3.4'))
getRanges
collection:getRanges()
Returns a sorted array of IP pairs equivalent to the collection. Each IP pair is an array representing a contiguous range of IP addresses from pair[1] to pair[2] inclusive. pair[1] and pair[2] are IPAddress objects.
Examples:
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)
Returns true, obj if subnet overlaps this collection, where obj is the first IPAddress or Subnet object overlapping the subnet. Otherwise, returns false. subnet can be a CIDR string or a Subnet object.
Examples:
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
The IPv6Collection class is used to work with several different IPv6 addresses and IPv6 subnets. IPv6Collection objects are directly analogous to IPv4Collection objects: they contain the same methods and work the same way, but all IP addresses and subnets added to it must be IPv6, not IPv4.
To create a new IPv6Collection object:
localcollection=IPv6Collection.new()
-- IP library-- This library contains classes for working with IP addresses and IP ranges.-- Load modulesrequire('strict')localbit32=require('bit32')locallibraryUtil=require('libraryUtil')localcheckType=libraryUtil.checkTypelocalcheckTypeMulti=libraryUtil.checkTypeMultilocalmakeCheckSelfFunction=libraryUtil.makeCheckSelfFunction-- ConstantslocalV4='IPv4'localV6='IPv6'---------------------------------------------------------------------------------- Helper functions--------------------------------------------------------------------------------localfunctionmakeValidationFunction(className,isObjectFunc)-- Make a function for validating a specific object.returnfunction(methodName,argIdx,arg)ifnotisObjectFunc(arg)thenerror(string.format("bad argument #%d to '%s' (not a valid %s object)",argIdx,methodName,className),3)endendend---------------------------------------------------------------------------------- Collection class-- This is a table used to hold items.--------------------------------------------------------------------------------localCollection={}Collection.__index=CollectionfunctionCollection:add(item)ifitem~=nilthenself.n=self.n+1self[self.n]=itemendendfunctionCollection:join(sep)returntable.concat(self,sep)endfunctionCollection:remove(pos)ifself.n>0and(pos==nilor(0<posandpos<=self.n))thenself.n=self.n-1returntable.remove(self,pos)endendfunctionCollection:sort(comp)table.sort(self,comp)endfunctionCollection:deobjectify()-- Turns the collection into a plain array without any special properties-- or methods.self.n=nilsetmetatable(self,nil)endfunctionCollection.new()returnsetmetatable({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.--------------------------------------------------------------------------------localRawIP={}RawIP.__index=RawIP-- ConstructorsfunctionRawIP.newFromIPv4(ipStr)-- Return a RawIP object if ipStr is a valid IPv4 string. Otherwise,-- return nil.-- This representation is for compatibility with IPv6 addresses.localoctets=Collection.new()locals=ipStr:match('^%s*(.-)%s*$')..'.'foritemins:gmatch('(.-)%.')dooctets:add(item)endifoctets.n==4thenfori,sinipairs(octets)doifs:match('^%d+$')thenlocalnum=tonumber(s)if0<=numandnum<=255thenifnum>0ands:match('^0')then-- A redundant leading zero is for an IP in octal.returnnilendoctets[i]=numelsereturnnilendelsereturnnilendendlocalparts=Collection.new()fori=1,3,2doparts:add(octets[i]*256+octets[i+1])endreturnsetmetatable(parts,RawIP)endreturnnilendfunctionRawIP.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(':',':')ifn<7thenipStr=ipStr:gsub('::',string.rep(':',9-n))endlocalparts=Collection.new()foritemin(ipStr..':'):gmatch('(.-):')doparts:add(item)endifparts.n==8thenfori,sinipairs(parts)doifs==''thenparts[i]=0elseifs:match('^%x+$')thenlocalnum=tonumber(s,16)ifnumand0<=numandnum<=65535thenparts[i]=numelsereturnnilendelsereturnnilendendendreturnsetmetatable(parts,RawIP)endreturnnilendfunctionRawIP.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.returnRawIP.newFromIPv4(ipStr)orRawIP.newFromIPv6(ipStr)end-- MethodsfunctionRawIP:getVersion()-- Return a string with the version of the IP protocol we are using.returnself.n==2andV4orV6endfunctionRawIP:isIPv4()-- Return true if this is an IPv4 representation, and false otherwise.returnself.n==2endfunctionRawIP:isIPv6()-- Return true if this is an IPv6 representation, and false otherwise.returnself.n==8endfunctionRawIP:getBitLength()-- Return the bit length of the IP address.returnself.n*16endfunctionRawIP: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:fffflocalresult=Collection.new()result.n=self.nlocalcarry=previousand0xffffor1fori=self.n,1,-1dolocalsum=self[i]+carryifsum>=0x10000thencarry=previousand0x10000or1sum=sum-0x10000elsecarry=previousand0xffffor0endresult[i]=sumendreturnsetmetatable(result,RawIP)endfunctionRawIP:getPrefix(bitLength)-- Return a RawIP object for the prefix of the current IP Address with a-- bit length of bitLength.localresult=Collection.new()result.n=self.nfori=1,self.ndoifbitLength>0thenifbitLength>=16thenresult[i]=self[i]bitLength=bitLength-16elseresult[i]=bit32.replace(self[i],0,0,16-bitLength)bitLength=0endelseresult[i]=0endendreturnsetmetatable(result,RawIP)endfunctionRawIP: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.localbits=self.n*16localwidthifbitLength<=0thenwidth=bitselseifbitLength>=bitsthenwidth=0elsewidth=bits-bitLengthendlocalresult=Collection.new()result.n=self.nfori=self.n,1,-1doifwidth>0thenifwidth>=16thenresult[i]=0xffffwidth=width-16elseresult[i]=bit32.replace(self[i],0xffff,0,width)width=0endelseresult[i]=self[i]endendreturnsetmetatable(result,RawIP)endfunctionRawIP:_makeIPv6String()-- Return an IPv6 string representation of the object. Behavior is-- undefined if the current object is IPv4.localz1,z2-- indices of run of zeroes to be displayed as "::"localzstart,zcountfori=1,9do-- Find left-most occurrence of longest run of two or more zeroes.ifi<9andself[i]==0thenifzstartthenzcount=zcount+1elsezstart=izcount=1endelseifzcountandzcount>1thenifnotz1orzcount>z2-z1+1thenz1=zstartz2=zstart+zcount-1endendzstart=nilzcount=nilendendlocalparts=Collection.new()fori=1,8doifz1andz1<=iandi<=z2thenifi==z1thenifz1==1orz2==8thenifz1==1andz2==8thenreturn'::'endparts:add(':')elseparts:add('')endendelseparts:add(string.format('%x',self[i]))endendreturnparts:join(':')endfunctionRawIP:_makeIPv4String()-- Return an IPv4 string representation of the object. Behavior is-- undefined if the current object is IPv6.localparts=Collection.new()fori=1,2dolocalw=self[i]parts:add(math.floor(w/256))parts:add(w%256)endreturnparts:join('.')endfunctionRawIP:__tostring()-- Return a string equivalent to given IP address (IPv4 or IPv6).ifself.n==2thenreturnself:_makeIPv4String()elsereturnself:_makeIPv6String()endendfunctionRawIP:__lt(obj)ifself.n==obj.nthenfori=1,self.ndoifself[i]~=obj[i]thenreturnself[i]<obj[i]endendreturnfalseendreturnself.n<obj.nendfunctionRawIP:__eq(obj)ifself.n==obj.nthenfori=1,self.ndoifself[i]~=obj[i]thenreturnfalseendendreturntrueendreturnfalseend---------------------------------------------------------------------------------- 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.localmakeIPAddress,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.localvalidateCollection,validateIPAddress,validateSubnet---------------------------------------------------------------------------------- IPAddress class-- Represents a single IPv4 or IPv6 address.--------------------------------------------------------------------------------localIPAddress={}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.localdataKey={}-- Private static methodslocalfunctionisIPAddressObject(val)returntype(val)=='table'andval[dataKey]~=nilendvalidateIPAddress=makeValidationFunction('IPAddress',isIPAddressObject)-- Metamethods that don't need upvalueslocalfunctionipEquals(ip1,ip2)returnip1[dataKey].rawIP==ip2[dataKey].rawIPendlocalfunctionipLessThan(ip1,ip2)returnip1[dataKey].rawIP<ip2[dataKey].rawIPendlocalfunctionconcatIP(ip,val)returntostring(ip)..tostring(val)endlocalfunctionipToString(ip)returnip:getIP()end-- ConstructorsmakeIPAddressFromRaw=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 structurelocalobj={}localdata={}data.rawIP=rawIP-- A function to check whether methods are called with a valid self-- parameter.localcheckSelf=makeCheckSelfFunction('IP','ipAddress',obj,'IPAddress object')-- Public methodsfunctionobj:getIP()checkSelf(self,'getIP')returntostring(data.rawIP)endfunctionobj:getVersion()checkSelf(self,'getVersion')returndata.rawIP:getVersion()endfunctionobj:isIPv4()checkSelf(self,'isIPv4')returndata.rawIP:isIPv4()endfunctionobj:isIPv6()checkSelf(self,'isIPv6')returndata.rawIP:isIPv6()endfunctionobj:isInCollection(collection)checkSelf(self,'isInCollection')validateCollection('isInCollection',1,collection)returncollection:containsIP(self)endfunctionobj:isInSubnet(subnet)checkSelf(self,'isInSubnet')localtp=type(subnet)iftp=='string'thensubnet=makeSubnet(subnet)elseiftp=='table'thenvalidateSubnet('isInSubnet',1,subnet)elsecheckTypeMulti('isInSubnet',1,subnet,{'string','table'})endreturnsubnet:containsIP(self)endfunctionobj:getSubnet(bitLength)checkSelf(self,'getSubnet')checkType('getSubnet',1,bitLength,'number')ifbitLength<0orbitLength>data.rawIP:getBitLength()orbitLength~=math.floor(bitLength)thenerror(string.format("bad argument #1 to 'getSubnet' (must be an integer between 0 and %d)",data.rawIP:getBitLength()),2)endreturnmakeSubnetFromRaw(data.rawIP,bitLength)endfunctionobj:getNextIP()checkSelf(self,'getNextIP')returnmakeIPAddressFromRaw(data.rawIP:getAdjacent())endfunctionobj:getPreviousIP()checkSelf(self,'getPreviousIP')returnmakeIPAddressFromRaw(data.rawIP:getAdjacent(true))end-- Metamethodsreturnsetmetatable(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.ifkey==dataKeythenreturndataendend,__metatable=false,-- don't allow access to the metatable})endmakeIPAddress=function(ip)localrawIP=RawIP.newFromIP(ip)ifnotrawIPthenerror(string.format("'%s' is an invalid IP address",ip),3)endreturnmakeIPAddressFromRaw(rawIP)endfunctionIPAddress.new(ip)checkType('IPAddress.new',1,ip,'string')returnmakeIPAddress(ip)endend---------------------------------------------------------------------------------- Subnet class-- Represents a block of IPv4 or IPv6 addresses.--------------------------------------------------------------------------------localSubnet={}do-- uniqueKey is a unique, private key used to test whether a given object-- is a Subnet object.localuniqueKey={}-- Metatablelocalmt={__index=function(self,key)ifkey==uniqueKeythenreturntrueendend,__eq=function(self,obj)returnself:getCIDR()==obj:getCIDR()end,__concat=function(self,obj)returntostring(self)..tostring(obj)end,__tostring=function(self)returnself:getCIDR()end,__metatable=false}-- Private static methodslocalfunctionisSubnetObject(val)-- Return true if val is a Subnet object, and false otherwise.returntype(val)=='table'andval[uniqueKey]~=nilend-- 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 validatedvalidateSubnet=makeValidationFunction('Subnet',isSubnetObject)-- ConstructorsmakeSubnetFromRaw=function(rawIP,bitLength)-- Set up structurelocalobj=setmetatable({},mt)localdata={rawIP=rawIP,bitLength=bitLength,}-- A function to check whether methods are called with a valid self-- parameter.localcheckSelf=makeCheckSelfFunction('IP','subnet',obj,'Subnet object')-- Public methodsfunctionobj:getPrefix()checkSelf(self,'getPrefix')ifnotdata.prefixthendata.prefix=makeIPAddressFromRaw(data.rawIP:getPrefix(data.bitLength))endreturndata.prefixendfunctionobj:getHighestIP()checkSelf(self,'getHighestIP')ifnotdata.highestIPthendata.highestIP=makeIPAddressFromRaw(data.rawIP:getHighestHost(data.bitLength))endreturndata.highestIPendfunctionobj:getBitLength()checkSelf(self,'getBitLength')returndata.bitLengthendfunctionobj:getCIDR()checkSelf(self,'getCIDR')returnstring.format('%s/%d',tostring(self:getPrefix()),self:getBitLength())endfunctionobj:getVersion()checkSelf(self,'getVersion')returndata.rawIP:getVersion()endfunctionobj:isIPv4()checkSelf(self,'isIPv4')returndata.rawIP:isIPv4()endfunctionobj:isIPv6()checkSelf(self,'isIPv6')returndata.rawIP:isIPv6()endfunctionobj:containsIP(ip)checkSelf(self,'containsIP')localtp=type(ip)iftp=='string'thenip=makeIPAddress(ip)elseiftp=='table'thenvalidateIPAddress('containsIP',1,ip)elsecheckTypeMulti('containsIP',1,ip,{'string','table'})endifself:getVersion()==ip:getVersion()thenreturnself:getPrefix()<=ipandip<=self:getHighestIP()endreturnfalseendfunctionobj:overlapsCollection(collection)checkSelf(self,'overlapsCollection')validateCollection('overlapsCollection',1,collection)returncollection:overlapsSubnet(self)endfunctionobj:overlapsSubnet(subnet)checkSelf(self,'overlapsSubnet')localtp=type(subnet)iftp=='string'thensubnet=makeSubnet(subnet)elseiftp=='table'thenvalidateSubnet('overlapsSubnet',1,subnet)elsecheckTypeMulti('overlapsSubnet',1,subnet,{'string','table'})endifself:getVersion()==subnet:getVersion()thenreturn(subnet:getHighestIP()>=self:getPrefix()andsubnet:getPrefix()<=self:getHighestIP())endreturnfalseendfunctionobj:walk()checkSelf(self,'walk')localstartedlocalcurrent=self:getPrefix()localhighest=self:getHighestIP()returnfunction()ifnotstartedthenstarted=truereturncurrentendifcurrent<highestthencurrent=current:getNextIP()returncurrentendendendreturnobjendmakeSubnet=function(cidr)-- Return a Subnet object from a CIDR string. If the CIDR string is-- invalid, throw an error.locallhs,rhs=cidr:match('^%s*(.-)/(%d+)%s*$')iflhsthenlocalbits=lhs:find(':',1,true)and128or32localn=tonumber(rhs)ifnandn<=bitsand(n==0ornotrhs: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.localbase=RawIP.newFromIP(lhs)ifbasethen-- The left-hand side is a valid IP address.localprefix=base:getPrefix(n)ifbase==prefixthen-- The left-hand side is the lowest IP in the subnet.returnmakeSubnetFromRaw(prefix,n)endendendenderror(string.format("'%s' is an invalid CIDR string",cidr),3)endfunctionSubnet.new(cidr)checkType('Subnet.new',1,cidr,'string')returnmakeSubnet(cidr)endend---------------------------------------------------------------------------------- Ranges class-- Holds a list of IPAdress pairs representing contiguous IP ranges.--------------------------------------------------------------------------------localRanges=Collection.new()Ranges.__index=RangesfunctionRanges.new()returnsetmetatable({},Ranges)endfunctionRanges:add(ip1,ip2)validateIPAddress('add',1,ip1)ifip2~=nilthenvalidateIPAddress('add',2,ip2)ifip1>ip2thenerror('The first IP must be less than or equal to the second',2)endendCollection.add(self,{ip1,ip2orip1})endfunctionRanges:merge()self:sort(function(lhs,rhs)-- Sort by second value, then first.iflhs[2]==rhs[2]thenreturnlhs[1]<rhs[1]endreturnlhs[2]<rhs[2]end)localpos=self.nwhilepos>1dofori=pos-1,1,-1dolocalip1=self[i][2]localip2=ip1:getNextIP()ifip2<ip1thenip2=ip1-- don't wrap aroundendifself[pos][1]>ip2thenbreakendip1=self[i][1]ip2=self[pos][1]self[i]={ip1>ip2andip2orip1,self[pos][2]}self:remove(pos)pos=pos-1ifpos<=1thenbreakendendpos=pos-1endend---------------------------------------------------------------------------------- IPCollection class-- Holds a list of IP addresses/subnets. Used internally.-- Each address/subnet has the same version (either IPv4 or IPv6).--------------------------------------------------------------------------------localIPCollection={}IPCollection.__index=IPCollectionfunctionIPCollection.new(version)assert(version==V4orversion==V6,'IPCollection.new called with an invalid version')localobj={version=version,-- V4 or V6addresses=Collection.new(),-- valid IP addressessubnets=Collection.new(),-- valid subnetsomitted=Collection.new(),-- not-quite valid strings}returnobjendfunctionIPCollection:getVersion()-- Return a string with the IP version of addresses in this collection.returnself.versionendfunctionIPCollection:_store(hit,stripColons)localmaker,locationifhit:find('/',1,true)thenmaker=Subnet.newlocation=self.subnetselsemaker=IPAddress.newlocation=self.addressesendlocalsuccess,obj=pcall(maker,hit)ifsuccessthenlocation:add(obj)elseifstripColonsthenlocalcolons,hit=hit:match('^(:*)(.*)')ifcolons~=''thenself:_store(hit)returnendendself.omitted:add(hit)endendfunctionIPCollection:_assertVersion(version,msg)ifself.version~=versionthenerror(msg,3)endendfunctionIPCollection:addIP(ip)localtp=type(ip)iftp=='string'thenip=makeIPAddress(ip)elseiftp=='table'thenvalidateIPAddress('addIP',1,ip)elsecheckTypeMulti('addIP',1,ip,{'string','table'})endself:_assertVersion(ip:getVersion(),'addIP called with incorrect IP version')self.addresses:add(ip)returnselfendfunctionIPCollection:addSubnet(subnet)localtp=type(subnet)iftp=='string'thensubnet=makeSubnet(subnet)elseiftp=='table'thenvalidateSubnet('addSubnet',1,subnet)elsecheckTypeMulti('addSubnet',1,subnet,{'string','table'})endself:_assertVersion(subnet:getVersion(),'addSubnet called with incorrect subnet version')self.subnets:add(subnet)returnselfendfunctionIPCollection: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.localtp=type(ip)iftp=='string'thenip=makeIPAddress(ip)elseiftp=='table'thenvalidateIPAddress('containsIP',1,ip)elsecheckTypeMulti('containsIP',1,ip,{'string','table'})endifself:getVersion()==ip:getVersion()thenfor_,iteminipairs(self.addresses)doifitem==ipthenreturntrue,itemendendfor_,iteminipairs(self.subnets)doifitem:containsIP(ip)thenreturntrue,itemendendendreturnfalseendfunctionIPCollection: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).localranges=Ranges.new()for_,iteminipairs(self.addresses)doranges:add(item)endfor_,iteminipairs(self.subnets)doranges:add(item:getPrefix(),item:getHighestIP())endranges:merge()ranges:deobjectify()returnrangesendfunctionIPCollection:overlapsSubnet(subnet)-- Return true, obj if subnet overlaps this collection,-- where obj is the first IPAddress or Subnet overlapping the subnet.-- Otherwise, return false.localtp=type(subnet)iftp=='string'thensubnet=makeSubnet(subnet)elseiftp=='table'thenvalidateSubnet('overlapsSubnet',1,subnet)elsecheckTypeMulti('overlapsSubnet',1,subnet,{'string','table'})endifself:getVersion()==subnet:getVersion()thenfor_,iteminipairs(self.addresses)doifsubnet:containsIP(item)thenreturntrue,itemendendfor_,iteminipairs(self.subnets)doifsubnet:overlapsSubnet(item)thenreturntrue,itemendendendreturnfalseend---------------------------------------------------------------------------------- IPv4Collection class-- Holds a list of IPv4 addresses/subnets.--------------------------------------------------------------------------------localIPv4Collection=setmetatable({},IPCollection)IPv4Collection.__index=IPv4CollectionfunctionIPv4Collection.new()returnsetmetatable(IPCollection.new(V4),IPv4Collection)endfunctionIPv4Collection:addFromString(text)-- Extract any IPv4 addresses or CIDR subnets from given text.checkType('addFromString',1,text,'string')text=text:gsub('[:!"#&\'()+,%-;<=>?[%]_{|}]',' ')forhitintext:gmatch('%S+')doifhit:match('^%d+%.%d+[%.%d/]+$')thenlocal_,n=hit:gsub('%.','.')ifn>=3thenself:_store(hit)endendendreturnselfend---------------------------------------------------------------------------------- IPv6Collection class-- Holds a list of IPv6 addresses/subnets.--------------------------------------------------------------------------------localIPv6Collection=setmetatable({},IPCollection)IPv6Collection.__index=IPv6Collectiondo-- Private static methodslocalfunctionisCollectionObject(val)-- Return true if val is probably derived from an IPCollection object,-- otherwise return false.iftype(val)=='table'thenlocalmt=getmetatable(val)ifmt==IPv4Collectionormt==IPv6CollectionthenreturntrueendendreturnfalseendvalidateCollection=makeValidationFunction('IPCollection',isCollectionObject)functionIPv6Collection.new()returnsetmetatable(IPCollection.new(V6),IPv6Collection)endfunctionIPv6Collection: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')forlineinstring.gmatch(text..'\n','[\t ]*(.-)[\t\r ]*\n')doline=line:gsub('[!"#&\'()+,%-;<=>?[%]_{|}]',' ')forposition,hitinline:gmatch('()(%S+)')dolocalip=hit:match('^([:%x]+)/?%d*$')ifipthenlocal_,n=ip:gsub(':',':')ifn>=2thenself:_store(hit,position==1)endendendendreturnselfendendreturn{IPAddress=IPAddress,Subnet=Subnet,IPv4Collection=IPv4Collection,IPv6Collection=IPv6Collection,}