Jump to content

Module:IP

Permanently protected module
From Wikipedia, the free encyclopedia

-- 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,}
close