1 | # -*- coding: iso-8859-15 -*- |
---|
2 | """IP4 address range set implementation. |
---|
3 | |
---|
4 | Implements an IPv4-range type. |
---|
5 | |
---|
6 | Copyright (C) 2006, Heiko Wundram. |
---|
7 | Released under the MIT-license. |
---|
8 | """ |
---|
9 | |
---|
10 | # Version information |
---|
11 | # ------------------- |
---|
12 | |
---|
13 | __author__ = "Heiko Wundram <me@modelnine.org>" |
---|
14 | __version__ = "0.2" |
---|
15 | __revision__ = "3" |
---|
16 | __date__ = "2006-01-20" |
---|
17 | |
---|
18 | |
---|
19 | # Imports |
---|
20 | # ------- |
---|
21 | |
---|
22 | import intset |
---|
23 | import socket |
---|
24 | |
---|
25 | |
---|
26 | # IP4Range class |
---|
27 | # -------------- |
---|
28 | |
---|
29 | class IP4Range(intset.IntSet): |
---|
30 | """IP4 address range class with efficient storage of address ranges. |
---|
31 | Supports all set operations.""" |
---|
32 | |
---|
33 | _MINIP4 = 0 |
---|
34 | _MAXIP4 = (1<<32) - 1 |
---|
35 | _UNITYTRANS = "".join([chr(n) for n in range(256)]) |
---|
36 | _IPREMOVE = "0123456789." |
---|
37 | |
---|
38 | def __init__(self,*args): |
---|
39 | """Initialize an ip4range class. The constructor accepts an unlimited |
---|
40 | number of arguments that may either be tuples in the form (start,stop), |
---|
41 | integers, longs or strings, where start and stop in a tuple may |
---|
42 | also be of the form integer, long or string. |
---|
43 | |
---|
44 | Passing an integer or long means passing an IPv4-address that's already |
---|
45 | been converted to integer notation, whereas passing a string specifies |
---|
46 | an address where this conversion still has to be done. A string |
---|
47 | address may be in the following formats: |
---|
48 | |
---|
49 | - 1.2.3.4 - a plain address, interpreted as a single address |
---|
50 | - 1.2.3 - a set of addresses, interpreted as 1.2.3.0-1.2.3.255 |
---|
51 | - localhost - hostname to look up, interpreted as single address |
---|
52 | - 1.2.3<->5 - a set of addresses, interpreted as 1.2.3.0-1.2.5.255 |
---|
53 | - 1.2.0.0/16 - a set of addresses, interpreted as 1.2.0.0-1.2.255.255 |
---|
54 | |
---|
55 | Only the first three notations are valid if you use a string address in |
---|
56 | a tuple, whereby notation 2 is interpreted as 1.2.3.0 if specified as |
---|
57 | lower bound and 1.2.3.255 if specified as upper bound, not as a range |
---|
58 | of addresses. |
---|
59 | |
---|
60 | Specifying a range is done with the <-> operator. This is necessary |
---|
61 | because '-' might be present in a hostname. '<->' shouldn't be, ever. |
---|
62 | """ |
---|
63 | |
---|
64 | # Special case copy constructor. |
---|
65 | if len(args) == 1 and isinstance(args[0],IP4Range): |
---|
66 | super(IP4Range,self).__init__(args[0]) |
---|
67 | return |
---|
68 | |
---|
69 | # Convert arguments to tuple syntax. |
---|
70 | args = list(args) |
---|
71 | for i in range(len(args)): |
---|
72 | argval = args[i] |
---|
73 | if isinstance(argval,str): |
---|
74 | if "<->" in argval: |
---|
75 | # Type 4 address. |
---|
76 | args[i] = self._parseRange(*argval.split("<->",1)) |
---|
77 | continue |
---|
78 | elif "/" in argval: |
---|
79 | # Type 5 address. |
---|
80 | args[i] = self._parseMask(*argval.split("/",1)) |
---|
81 | else: |
---|
82 | # Type 1, 2 or 3. |
---|
83 | args[i] = self._parseAddrRange(argval) |
---|
84 | elif isinstance(argval,tuple): |
---|
85 | if len(tuple) <> 2: |
---|
86 | raise ValueError("Tuple is of invalid length.") |
---|
87 | addr1, addr2 = argval |
---|
88 | if isinstance(addr1,str): |
---|
89 | addr1 = self._parseAddrRange(addr1)[0] |
---|
90 | elif not isinstance(addr1,(int,long)): |
---|
91 | raise TypeError("Invalid argument.") |
---|
92 | if isinstance(addr2,str): |
---|
93 | addr2 = self._parseAddrRange(addr2)[1] |
---|
94 | elif not isinstance(addr2,(int,long)): |
---|
95 | raise TypeError("Invalid argument.") |
---|
96 | args[i] = (addr1,addr2) |
---|
97 | elif not isinstance(argval,(int,long)): |
---|
98 | raise TypeError("Invalid argument.") |
---|
99 | |
---|
100 | # Initialize the integer set. |
---|
101 | super(IP4Range,self).__init__(min=self._MINIP4,max=self._MAXIP4,*args) |
---|
102 | |
---|
103 | # Parsing functions |
---|
104 | # ----------------- |
---|
105 | |
---|
106 | def _parseRange(self,addr1,addr2): |
---|
107 | naddr1, naddr1len = _parseAddr(addr1) |
---|
108 | naddr2, naddr2len = _parseAddr(addr2) |
---|
109 | if naddr2len < naddr1len: |
---|
110 | naddr2 += naddr1&(((1<<((naddr1len-naddr2len)*8))-1)<< |
---|
111 | (naddr2len*8)) |
---|
112 | naddr2len = naddr1len |
---|
113 | elif naddr2len > naddr1len: |
---|
114 | raise ValueError("Range has more dots than address.") |
---|
115 | naddr1 <<= (4-naddr1len)*8 |
---|
116 | naddr2 <<= (4-naddr2len)*8 |
---|
117 | naddr2 += (1<<((4-naddr2len)*8))-1 |
---|
118 | return (naddr1,naddr2) |
---|
119 | |
---|
120 | def _parseMask(self,addr,mask): |
---|
121 | naddr, naddrlen = _parseAddr(addr) |
---|
122 | naddr <<= (4-naddrlen)*8 |
---|
123 | try: |
---|
124 | if not mask: |
---|
125 | masklen = 0 |
---|
126 | else: |
---|
127 | masklen = int(mask) |
---|
128 | if not 0 <= masklen <= 32: |
---|
129 | raise ValueError |
---|
130 | except ValueError: |
---|
131 | try: |
---|
132 | mask = _parseAddr(mask,False) |
---|
133 | except ValueError: |
---|
134 | raise ValueError("Mask isn't parseable.") |
---|
135 | remaining = 0 |
---|
136 | masklen = 0 |
---|
137 | if not mask: |
---|
138 | masklen = 0 |
---|
139 | else: |
---|
140 | while not (mask&1): |
---|
141 | remaining += 1 |
---|
142 | while (mask&1): |
---|
143 | mask >>= 1 |
---|
144 | masklen += 1 |
---|
145 | if remaining+masklen <> 32: |
---|
146 | raise ValueError("Mask isn't a proper host mask.") |
---|
147 | naddr1 = naddr & (((1<<masklen)-1)<<(32-masklen)) |
---|
148 | naddr2 = naddr1 + (1<<(32-masklen)) - 1 |
---|
149 | return (naddr1,naddr2) |
---|
150 | |
---|
151 | def _parseAddrRange(self,addr): |
---|
152 | naddr, naddrlen = _parseAddr(addr) |
---|
153 | naddr1 = naddr<<((4-naddrlen)*8) |
---|
154 | naddr2 = ( (naddr<<((4-naddrlen)*8)) + |
---|
155 | (1<<((4-naddrlen)*8)) - 1 ) |
---|
156 | return (naddr1,naddr2) |
---|
157 | |
---|
158 | # Utility functions |
---|
159 | # ----------------- |
---|
160 | |
---|
161 | def _int2ip(self,num): |
---|
162 | rv = [] |
---|
163 | for i in range(4): |
---|
164 | rv.append(str(num&255)) |
---|
165 | num >>= 8 |
---|
166 | return ".".join(reversed(rv)) |
---|
167 | |
---|
168 | # Iterating |
---|
169 | # --------- |
---|
170 | |
---|
171 | def iteraddresses(self): |
---|
172 | """Returns an iterator which iterates over ips in this iprange. An |
---|
173 | IP is returned in string form (e.g. '1.2.3.4').""" |
---|
174 | |
---|
175 | for v in super(IP4Range,self).__iter__(): |
---|
176 | yield self._int2ip(v) |
---|
177 | |
---|
178 | def iterranges(self): |
---|
179 | """Returns an iterator which iterates over ip-ip ranges which build |
---|
180 | this iprange if combined. An ip-ip pair is returned in string form |
---|
181 | (e.g. '1.2.3.4-2.3.4.5').""" |
---|
182 | |
---|
183 | for r in self._ranges: |
---|
184 | if r[1]-r[0] == 1: |
---|
185 | yield self._int2ip(r[0]) |
---|
186 | else: |
---|
187 | yield '%s-%s' % (self._int2ip(r[0]),self._int2ip(r[1]-1)) |
---|
188 | |
---|
189 | def itermasks(self): |
---|
190 | """Returns an iterator which iterates over ip/mask pairs which build |
---|
191 | this iprange if combined. An IP/Mask pair is returned in string form |
---|
192 | (e.g. '1.2.3.0/24').""" |
---|
193 | |
---|
194 | for r in self._ranges: |
---|
195 | for v in self._itermasks(r): |
---|
196 | yield v |
---|
197 | |
---|
198 | def _itermasks(self,r): |
---|
199 | ranges = [r] |
---|
200 | while ranges: |
---|
201 | cur = ranges.pop() |
---|
202 | curmask = 0 |
---|
203 | while True: |
---|
204 | curmasklen = 1<<(32-curmask) |
---|
205 | start = (cur[0]+curmasklen-1)&(((1<<curmask)-1)<<(32-curmask)) |
---|
206 | if start >= cur[0] and start+curmasklen <= cur[1]: |
---|
207 | break |
---|
208 | else: |
---|
209 | curmask += 1 |
---|
210 | yield "%s/%s" % (self._int2ip(start),curmask) |
---|
211 | if cur[0] < start: |
---|
212 | ranges.append((cur[0],start)) |
---|
213 | if cur[1] > start+curmasklen: |
---|
214 | ranges.append((start+curmasklen,cur[1])) |
---|
215 | |
---|
216 | __iter__ = iteraddresses |
---|
217 | |
---|
218 | # Printing |
---|
219 | # -------- |
---|
220 | |
---|
221 | def __repr__(self): |
---|
222 | """Returns a string which can be used to reconstruct this iprange.""" |
---|
223 | |
---|
224 | rv = [] |
---|
225 | for start, stop in self._ranges: |
---|
226 | if stop-start == 1: |
---|
227 | rv.append("%r" % (self._int2ip(start),)) |
---|
228 | else: |
---|
229 | rv.append("(%r,%r)" % (self._int2ip(start), |
---|
230 | self._int2ip(stop-1))) |
---|
231 | return "%s(%s)" % (self.__class__.__name__,",".join(rv)) |
---|
232 | |
---|
233 | def _parseAddr(addr,lookup=True): |
---|
234 | if lookup and addr.translate(IP4Range._UNITYTRANS, IP4Range._IPREMOVE): |
---|
235 | try: |
---|
236 | addr = socket.gethostbyname(addr) |
---|
237 | except socket.error: |
---|
238 | raise ValueError("Invalid Hostname as argument.") |
---|
239 | naddr = 0 |
---|
240 | for naddrpos, part in enumerate(addr.split(".")): |
---|
241 | if naddrpos >= 4: |
---|
242 | raise ValueError("Address contains more than four parts.") |
---|
243 | try: |
---|
244 | if not part: |
---|
245 | part = 0 |
---|
246 | else: |
---|
247 | part = int(part) |
---|
248 | if not 0 <= part < 256: |
---|
249 | raise ValueError |
---|
250 | except ValueError: |
---|
251 | raise ValueError("Address part out of range.") |
---|
252 | naddr <<= 8 |
---|
253 | naddr += part |
---|
254 | return naddr, naddrpos+1 |
---|
255 | |
---|
256 | def ip2int(addr, lookup=True): |
---|
257 | return _parseAddr(addr, lookup=lookup)[0] |
---|
258 | |
---|
259 | if __name__ == "__main__": |
---|
260 | # Little test script. |
---|
261 | x = IP4Range("172.22.162.250/24") |
---|
262 | y = IP4Range("172.22.162.250","172.22.163.250","172.22.163.253<->255") |
---|
263 | print x |
---|
264 | for val in x.itermasks(): |
---|
265 | print val |
---|
266 | for val in y.itermasks(): |
---|
267 | print val |
---|
268 | for val in (x|y).itermasks(): |
---|
269 | print val |
---|
270 | for val in (x^y).iterranges(): |
---|
271 | print val |
---|
272 | for val in x: |
---|
273 | print val |
---|