Initial revision
[python-rwhoisd.git] / rwhoisd / Cidr.py
1 import socket, types, copy, bisect, re
2
3 class Cidr:
4     """A class representing a CIDRized IPv4 network value.
5
6     Specifically, it is representing contiguous IPv4 network blocks
7     that can be expressed as a ip-address/network length pair."""
8
9     # FIXME: we should probably actually make this class immutable and
10     # add methods that generate copies of this class with different
11     # netlens or whatever.
12
13     ip4addr_re = re.compile("^\d{1,3}(\.\d{1,3}){0,3}(/\d{1,2})?$")
14     
15     def __init__(self, address, netlen = -1):
16         """This takes either a formatted string in CIDR notation:
17         (e.g., "127.0.0.1/32"), A tuple consisting of an formatting
18         string IPv4 address and a numeric network length, or the same
19         as two arguments."""
20
21         if not Cidr.ip4addr_re.search(address):
22             raise ValueError, repr(address) + \
23                   " is not a valid CIDR representation"
24         
25         if netlen < 0:
26             if type(address) == types.StringType:
27                 if "/" in address:
28                     self.addr, self.netlen = address.split("/", 1)
29                 else:
30                     self.addr, self.netlen = address, 32
31             elif type(address) == types.TupleType:
32                 self.addr, self.netlen = address
33             else:
34                 raise TypeError, "address must be a string or a tuple"
35         else:
36             self.addr = address
37             self.netlen = netlen
38
39         # convert string network lengths to integer
40         if type(self.netlen) == types.StringType:
41             self.netlen = int(self.netlen)
42
43         self.calc()
44
45     def __cmp__(self, other):
46         """One CIDR network block is less than another if the start
47         address is numerically less or if the block is larger.  That
48         is, supernets will sort before subnets.  This ordering allows
49         for an effienct search for subnets of a given network."""
50
51         # FIXME: have to convert to longs to overcome signedness problems.
52         #  There is probably a better way to do this.
53         res = (self.numaddr & 0xFFFFFFFFL) - (other.numaddr & 0xFFFFFFFFL)
54         if (res < 0 ): return -1
55         if (res > 0): return 1
56         res = self.netlen - other.netlen
57         return res
58
59     def __str__(self):
60         return self.addr + "/" + str(self.netlen)
61
62     def __repr__(self):
63         return "<" + str(self) + ">"
64
65     def calc(self):
66         """This method should be called after any change to the main
67         internal state: netlen or numaddr."""
68
69         # make sure the network length is valid
70         if self.netlen > 32 or self.netlen < 0:
71             raise TypeError, "network length must be between 0 and 32"
72
73         # convert the string ipv4 address to a 32bit number
74         self.numaddr = self._convert_ip4str(self.addr)
75         # calculate our netmask
76         self.mask = self._mask(self.netlen)
77         # force the cidr address into correct masked notation
78         self.numaddr &= self.mask
79
80         # convert the number back to a string to normalize the string
81         self.addr = self._convert_ip4addr(self.numaddr)
82
83     def is_supernet(self, other):
84         """returns True if the other Cidr object is a supernet (an
85         enclosing network block) of this one.  A Cidr object is a
86         supernet of itself."""
87         return other.numaddr & self.mask == self.numaddr
88
89     def is_subnet(self, other):
90         """returns True if the other Cidr object is a subnet (an
91         enclosednetwork block) of this one.  A Cidr object is a
92         subnet of itself."""
93         return self.numaddr & other.mask == other.numaddr
94
95     def netmask(self):
96         """return the netmask of this Cidr network"""
97         return self._convert_ip4addr(self.mask)
98     
99     def length(self):
100         """return the length (in number of addresses) of this network block"""
101         return 1 << (32 - self.netlen);
102
103     def end(self):
104         """return the last IP address in this network block"""
105         return self._convert_ip4addr(self.numaddr + self.length() - 1)
106         
107     def _convert_ip4str(self, addr):
108         p = 3; a = 0
109         for octet in addr.split(".", 3):
110             o = int(octet);
111             if (o & 0xFF != o):
112                 raise SyntaxWarning, "octet " + str(o) + " isn't in range"
113             a |= o << (p * 8)
114             p -= 1
115         return a
116
117     def _convert_ip4addr(self, numaddr):
118         res = str((numaddr & 0xFF000000) >> 24 & 0xFF) + "." + \
119               str((numaddr & 0x00FF0000) >> 16) + "." + \
120               str((numaddr & 0x0000FF00) >> 8) + "." + \
121               str(numaddr & 0x000000FF)
122         return res
123
124     def _mask(self, len):
125         return 0xFFFFFFFF << (32 - len)
126
127     def clone(self):
128         # we can get away with a shallow copy (so far)
129         return copy.copy(self)
130
131
132 def valid_cidr(address):
133     """Returns the converted Cidr object  if 'address' is valid
134     CIDR notation, False if not.  For the purposes of this module,
135     valid CIDR notation consists of 1 to 4 octets separated by '.'
136     characters, with an optional trailing "/netlen"."""
137
138     if isinstance(address, Cidr): return address
139     try:
140         c = Cidr(address)
141         return c
142     except:
143         return False
144
145
146 # test driver
147 if __name__ == "__main__":
148     a = Cidr("127.00.000.1/24")
149     b = Cidr("127.0.0.1", 32)
150     c = Cidr("24.232.119.192", 26)
151     d = Cidr("24.232.119.0", 24)
152     e = Cidr(("24.224.0.0", 11))
153     f = Cidr("216.168.111.0/27");
154     g = Cidr("127.0.0.2/31");
155     h = Cidr("127.0.0.16/32")
156     print f.addr
157     
158     try:
159         bad = Cidr("24.261.119.0", 32)
160     except Warning, x:
161         print "warning:", x
162     
163     print "cidr:", a, "num addresses:", a.length(), "ending address", \
164           a.end(), "netmask", a.netmask()
165     
166     clist = [a, b, c, d, e, f, g, h]
167     print "unsorted list of cidr objects:\n  ", clist
168
169     clist.sort()
170     print "sorted list of cidr object:\n  ", clist