1 # This file is part of python-rwhoisd
3 # Copyright (C) 2003, 2008 David E. Blacka
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 import socket, types, copy, bisect, re, struct
23 """A class representing a generic CIDRized network value."""
26 return self.addr + "/" + str(self.netlen)
29 return "<" + str(self) + ">"
31 def __cmp__(self, other):
32 """One CIDR network block is less than another if the start
33 address is numerically less or if the block is larger. That
34 is, supernets will sort before subnets. This ordering allows
35 for an efficient search for subnets of a given network."""
37 res = self._base_mask(self.numaddr) - self._base_mask(other.numaddr)
38 if res == 0: res = self.netlen - other.netlen
44 """This method should be called after any change to the main
45 internal state: netlen or numaddr."""
47 # make sure the network length is valid
48 if not self._is_valid_netlen(netlen):
49 raise TypeError, "network length must be between 0 and %d" % (_max_netlen())
51 # convert the string ipv4 address to a 32bit number
52 self.numaddr = self._convert_ipstr(self.addr)
53 # calculate our netmask
54 self.mask = self._mask(self.netlen)
55 # force the cidr address into correct masked notation
56 self.numaddr &= self.mask
58 # convert the number back to a string to normalize the string
59 self.addr = self._convert_ipaddr(self.numaddr)
61 def is_supernet(self, other):
62 """returns True if the other Cidr object is a supernet (an
63 enclosing network block) of this one. A Cidr object is a
64 supernet of itself."""
65 return other.numaddr & self.mask == self.numaddr
67 def is_subnet(self, other):
68 """returns True if the other Cidr object is a subnet (an
69 enclosednetwork block) of this one. A Cidr object is a
71 return self.numaddr & other.mask == other.numaddr
74 """return the netmask of this Cidr network"""
75 return self._convert_ipaddr(self.mask)
78 """return the length (in number of addresses) of this network block"""
79 return netlen_to_length(self.netlen)
82 """return the last IP address in this network block"""
83 return self._convert_ipaddr(self.numaddr + self.length() - 1)
85 def to_netblock(self):
86 return (self.addr, self.end())
89 # we can get away with a shallow copy (so far)
90 return copy.copy(self)
93 """A class representing a CIDRized IPv4 network value.
95 Specifically, it is representing a contiguous IPv4 network block
96 that can be expressed as a ip-address/network-length pair."""
98 # FIXME: we should probably actually make this class immutable and
99 # add methods that generate copies of this class with different
100 # netlens or whatever.
102 ip4addr_re = re.compile("^\d{1,3}(\.\d{1,3}){0,3}(/\d{1,2})?$")
104 def __init__(self, address, netlen = -1):
105 """This takes either a formatted string in CIDR notation:
106 (e.g., "127.0.0.1/32"), A tuple consisting of an formatting
107 string IPv4 address and a numeric network length, or the same
110 # if we are handing a numerical address and netlen, convert
112 if isinstance(address, int) and netlen >= 0:
114 self.numaddr = address
115 self.addr = self._convert_ipaddr(self.numaddr);
119 if not CidrV4.ip4addr_re.search(address):
120 raise ValueError, repr(address) + \
121 " is not a valid CIDR representation"
124 if type(address) == types.StringType:
126 self.addr, self.netlen = address.split("/", 1)
128 self.addr, self.netlen = address, 32
129 elif type(address) == types.TupleType:
130 self.addr, self.netlen = address
132 raise TypeError, "address must be a string or a tuple"
137 # convert string network lengths to integer
138 if type(self.netlen) == types.StringType:
139 self.netlen = int(self.netlen)
143 def _base_mask(self, numaddr):
144 return numaddr & 0xFFFFFFFFL
146 def _max_netlen(self):
149 def _is_valid_netlen(self, netlen):
150 if self.netlen < 0: return False
151 if self.netlen > _max_netlen(): return False
154 def _convert_ipstr(self, addr):
155 return socket.inet_aton(addr)
157 def _convert_ipaddr(self, numaddr):
158 res = str((numaddr & 0xFF000000) >> 24 & 0xFF) + "." + \
159 str((numaddr & 0x00FF0000) >> 16) + "." + \
160 str((numaddr & 0x0000FF00) >> 8) + "." + \
161 str(numaddr & 0x000000FF)
164 def _mask(self, len):
165 return 0xFFFFFFFF << (32 - len)
168 """A class representing a CIDRized IPv6 network value.
170 Specifically, it is representing a contiguous IPv6 network block
171 that can be expressed as a ipv6-address/network-length pair."""
173 ip6addr_re = re.compile("^[\da-f]{1,4}(:[\da-f]{1,4}){0,7}(::[\da-f])?(/\d{1,3})?$", re.I)
174 ip6_base_mask = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL # 128-bits of all ones.
176 def __init__(self, address, netlen = -1):
178 if isinstance(address, long) and netlen >= 0:
180 self.numaddr = address
181 self.addr = self._convert_ipaddr(address)
185 if not CidrV6.ip6addr_re.search(address):
186 raise ValueError, repr(address) + \
187 "is not a valid CIDR representation"
190 if type(address) == types.StringType:
192 self.addr, self.netlen = address.split("/", 1)
194 self.addr, self.netlen = address, 128
195 elif type(address) == types.TupleType:
196 self.addr, self.netlen = address
198 raise TypeError, "address must be a string or a tuple"
203 if type(self.netlen) == type.StringType:
204 self.netlen = int(self.netlen)
208 def _base_mask(self, numaddr):
209 return numaddr & CidrV6.ip6_base_mask
211 def _convert_ipstr(self, addr):
212 packed_numaddr = socket.inet_pton(socket.AF_INET6, addr)
213 upper, lower = struct.unpack("!QQ", packed_numaddr);
214 numaddr = (upper << 64) | lower
216 def _convert_ipaddr(self, numaddr):
217 upper = (numaddr & (ip6_base_mask << 64)) >> 64;
218 lower = numaddr & (ip6_base_mask >> 64)
219 packed_numaddr = struct.pack("!QQ", upper, lower)
220 return socket.inet_ntop(socket.AF_INET6, packed_numaddr)
222 def _mask(self, len):
223 return ip6_base_mask << (128 - len)
226 def valid_cidr(address):
227 """Returns the converted Cidr object if 'address' is valid
228 CIDR notation, False if not. For the purposes of this module,
229 valid CIDR notation consists of 1 to 4 octets separated by '.'
230 characters, with an optional trailing "/netlen"."""
232 if isinstance(address, Cidr): return address
240 def netlen_to_length(netlen):
241 """Convert a network-length to the length of the block in ip
244 return 1 << (32 - netlen);
246 def netblock_to_cidr(start, end):
247 """Convert an arbitrary network block expressed as a start and end
248 address (inclusive) into a series of valid CIDR blocks."""
250 def largest_prefix(length):
251 # calculates the largest network length (smallest mask length)
252 # that can fit within the block length.
255 if v & 0x80000000: break
258 def netlen_to_mask(n):
259 # convert the network length into its netmask
260 return ~((1 << (32 - n)) - 1)
263 # convert the start and ending addresses of the netblock to Cidr
264 # object, mostly so we can get the numeric versions of their
266 cs = valid_cidr(start)
269 # if either the start or ending addresses aren't valid ipv4
274 # calculate the number of IP address in the netblock
275 block_len = ce.numaddr - cs.numaddr
277 # calcuate the largest CIDR block size that fits
278 netlen = largest_prefix(block_len + 1)
280 res = []; s = cs.numaddr
282 mask = netlen_to_mask(netlen)
283 # check to see if our current network length is valid
285 # if not, shrink the network block size
288 # otherwise, we have a valid CIDR block, so add it to the list
290 res.append(Cidr(s, netlen))
291 # and setup for the next round:
292 cur_len = netlen_to_length(netlen)
295 netlen = largest_prefix(block_len + 1)
299 if __name__ == "__main__":
300 a = Cidr("127.00.000.1/24")
301 b = Cidr("127.0.0.1", 32)
302 c = Cidr("24.232.119.192", 26)
303 d = Cidr("24.232.119.0", 24)
304 e = Cidr("24.224.0.0", 11)
305 f = Cidr("216.168.111.0/27");
306 g = Cidr("127.0.0.2/31");
307 h = Cidr("127.0.0.16/32")
311 bad = Cidr("24.261.119.0", 32)
315 print "cidr:", a, "num addresses:", a.length(), "ending address", \
316 a.end(), "netmask", a.netmask()
318 clist = [a, b, c, d, e, f, g, h]
319 print "unsorted list of cidr objects:\n ", clist
322 print "sorted list of cidr object:\n ", clist
324 netblocks = [ ("192.168.10.0", "192.168.10.255"),
325 ("192.168.10.0", "192.168.10.63"),
326 ("172.16.0.0", "172.16.127.255"),
327 ("24.33.41.22", "24.33.41.37"),
328 ("196.11.1.0", "196.11.30.255"),
329 ("192.247.1.0", "192.247.10.255")]
331 for start, end in netblocks:
332 print "netblock %s - %s:" % (start, end)
333 blocks = netblock_to_cidr(start, end)