1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 """Utitlities for dealing with ip addresses.
27
28 Functions:
29 - validate_ip: Validate a dotted-quad ip address.
30 - ip2long: Convert a dotted-quad ip address to a network byte order 32-bit
31 integer.
32 - long2ip: Convert a network byte order 32-bit integer to a dotted quad ip
33 address.
34 - ip2hex: Convert a dotted-quad ip address to a hex encoded network byte
35 order 32-bit integer.
36 - hex2ip: Convert a hex encoded network byte order 32-bit integer to a
37 dotted-quad ip address.
38 - validate_cidr: Validate a CIDR notation ip address.
39 - cidr2block: Convert a CIDR notation ip address into a tuple containing
40 network block start and end addresses.
41
42 Objects:
43 - IpRange: Range of ip addresses providing ``in`` and iteration.
44 - IpRangeList: List of IpRange objects providing ``in`` and iteration.
45
46
47 The IpRangeList object can be used in a django settings file to allow CIDR
48 notation and/or (start, end) ranges to be used in the INTERNAL_IPS list.
49
50 Example:
51 INTERNAL_IPS = IpRangeList(
52 '127.0.0.1',
53 '192.168/16',
54 ('10.0.0.1', '10.0.0.19'),
55 )
56
57 """
58 __version__ = '0.5.0-dev'
59
60 __all__ = (
61 'validate_ip', 'ip2long', 'long2ip', 'ip2hex', 'hex2ip',
62 'validate_cidr', 'cidr2block',
63 'IpRange', 'IpRangeList',
64 )
65
66 import re
67
68
69
70 try:
71 basestring = basestring
72 except NameError:
73
74 basestring = str
75
76
77 try:
78 next = next
79 except NameError:
80
81
83 return iterable.next()
84
85
86 _DOTTED_QUAD_RE = re.compile(r'^(\d{1,3}\.){0,3}\d{1,3}$')
87
88
90 """Validate a dotted-quad ip address.
91
92 The string is considered a valid dotted-quad address if it consists of
93 one to four octets (0-255) seperated by periods (.).
94
95
96 >>> validate_ip('127.0.0.1')
97 True
98
99 >>> validate_ip('127.0')
100 True
101
102 >>> validate_ip('127.0.0.256')
103 False
104
105 >>> validate_ip(None)
106 Traceback (most recent call last):
107 ...
108 TypeError: expected string or buffer
109
110
111 Args:
112 s: String to validate as a dotted-quad ip address
113 Returns:
114 True if str is a valid dotted-quad ip address, False otherwise
115 """
116 if _DOTTED_QUAD_RE.match(s):
117 quads = s.split('.')
118 for q in quads:
119 if int(q) > 255:
120 return False
121 return True
122 return False
123
124
125 _CIDR_RE = re.compile(r'^(\d{1,3}\.){0,3}\d{1,3}/\d{1,2}$')
126
127
129 """Validate a CIDR notation ip address.
130
131 The string is considered a valid CIDR address if it consists of one to
132 four octets (0-255) seperated by periods (.) followed by a forward slash
133 (/) and a bit mask length (1-32).
134
135
136 >>> validate_cidr('127.0.0.1/32')
137 True
138
139 >>> validate_cidr('127.0/8')
140 True
141
142 >>> validate_cidr('127.0.0.256/32')
143 False
144
145 >>> validate_cidr('127.0.0.0')
146 False
147
148 >>> validate_cidr(None)
149 Traceback (most recent call last):
150 ...
151 TypeError: expected string or buffer
152
153
154 Args:
155 str: String to validate as a CIDR ip address
156 Returns:
157 True if str is a valid CIDR address, False otherwise
158 """
159 if _CIDR_RE.match(s):
160 ip, mask = s.split('/')
161 if validate_ip(ip):
162 if int(mask) > 32:
163 return False
164 else:
165 return False
166 return True
167 return False
168
169
170
172 """
173 Convert a dotted-quad ip address to a network byte order 32-bit integer.
174
175
176 >>> ip2long('127.0.0.1')
177 2130706433
178
179 >>> ip2long('127.1')
180 2130706433
181
182 >>> ip2long('127')
183 2130706432
184
185 >>> ip2long('127.0.0.256') is None
186 True
187
188
189 Args:
190 ip: Dotted-quad ip address (eg. '127.0.0.1')
191
192 Returns:
193 Network byte order 32-bit integer or None if ip is invalid
194 """
195 if not validate_ip(ip):
196 return None
197 quads = ip.split('.')
198 if len(quads) == 1:
199
200 quads = quads + [0, 0, 0]
201 elif len(quads) < 4:
202
203 host = quads[-1:]
204 quads = quads[:-1] + [0, ] * (4 - len(quads)) + host
205
206 lngip = 0
207 for q in quads:
208 lngip = (lngip << 8) | int(q)
209 return lngip
210
211
212 _MAX_IP = 0xffffffff
213 _MIN_IP = 0x0
214
215
217 """
218 Convert a network byte order 32-bit integer to a dotted quad ip address.
219
220
221 >>> long2ip(2130706433)
222 '127.0.0.1'
223
224 >>> long2ip(_MIN_IP)
225 '0.0.0.0'
226
227 >>> long2ip(_MAX_IP)
228 '255.255.255.255'
229
230 >>> long2ip(None) #doctest: +IGNORE_EXCEPTION_DETAIL
231 Traceback (most recent call last):
232 ...
233 TypeError: unsupported operand type(s) for >>: 'NoneType' and 'int'
234
235 >>> long2ip(-1) #doctest: +IGNORE_EXCEPTION_DETAIL
236 Traceback (most recent call last):
237 ...
238 TypeError: expected int between 0 and 4294967295 inclusive
239
240 >>> long2ip(374297346592387463875L) #doctest: +IGNORE_EXCEPTION_DETAIL
241 Traceback (most recent call last):
242 ...
243 TypeError: expected int between 0 and 4294967295 inclusive
244
245 >>> long2ip(_MAX_IP + 1) #doctest: +IGNORE_EXCEPTION_DETAIL
246 Traceback (most recent call last):
247 ...
248 TypeError: expected int between 0 and 4294967295 inclusive
249
250
251 Args:
252 l: Network byte order 32-bit integer
253 Returns:
254 Dotted-quad ip address (eg. '127.0.0.1')
255 """
256 if _MAX_IP < l or l < 0:
257 raise TypeError("expected int between 0 and %d inclusive" % _MAX_IP)
258 return '%d.%d.%d.%d' % (l >> 24 & 255, l >> 16 & 255,
259 l >> 8 & 255, l & 255)
260
261
262
264 """
265 Convert a dotted-quad ip address to a hex encoded number.
266
267 >>> ip2hex('0.0.0.1')
268 '00000001'
269 >>> ip2hex('127.0.0.1')
270 '7f000001'
271 >>> ip2hex('127.255.255.255')
272 '7fffffff'
273 >>> ip2hex('128.0.0.1')
274 '80000001'
275 >>> ip2hex('128.1')
276 '80000001'
277 >>> ip2hex('255.255.255.255')
278 'ffffffff'
279
280 """
281 netip = ip2long(addr)
282 if netip is None:
283 return None
284 return "%08x" % netip
285
286
287
289 """
290 Convert a hex encoded integer to a dotted-quad ip address.
291
292 >>> hex2ip('00000001')
293 '0.0.0.1'
294 >>> hex2ip('7f000001')
295 '127.0.0.1'
296 >>> hex2ip('7fffffff')
297 '127.255.255.255'
298 >>> hex2ip('80000001')
299 '128.0.0.1'
300 >>> hex2ip('ffffffff')
301 '255.255.255.255'
302
303 """
304 try:
305 netip = int(hex_str, 16)
306 except ValueError:
307 return None
308 return long2ip(netip)
309
310
311
313 """
314 Convert a CIDR notation ip address into a tuple containing the network
315 block start and end addresses.
316
317
318 >>> cidr2block('127.0.0.1/32')
319 ('127.0.0.1', '127.0.0.1')
320
321 >>> cidr2block('127/8')
322 ('127.0.0.0', '127.255.255.255')
323
324 >>> cidr2block('127.0.1/16')
325 ('127.0.0.0', '127.0.255.255')
326
327 >>> cidr2block('127.1/24')
328 ('127.1.0.0', '127.1.0.255')
329
330 >>> cidr2block('127.0.0.3/29')
331 ('127.0.0.0', '127.0.0.7')
332
333 >>> cidr2block('127/0')
334 ('0.0.0.0', '255.255.255.255')
335
336
337 Args:
338 cidr: CIDR notation ip address (eg. '127.0.0.1/8')
339 Returns:
340 Tuple of block (start, end) or None if invalid
341 """
342 if not validate_cidr(cidr):
343 return None
344
345 ip, prefix = cidr.split('/')
346 prefix = int(prefix)
347
348
349
350
351 quads = ip.split('.')
352 baseIp = 0
353 for i in range(4):
354 baseIp = (baseIp << 8) | int(len(quads) > i and quads[i] or 0)
355
356
357 shift = 32 - prefix
358 start = baseIp >> shift << shift
359
360
361 mask = (1 << shift) - 1
362 end = start | mask
363 return (long2ip(start), long2ip(end))
364
365
366
368 """
369 Range of ip addresses.
370
371 Converts a CIDR notation address, tuple of ip addresses or start and end
372 addresses into a smart object which can perform ``in`` and ``not in``
373 tests and iterate all of the addresses in the range.
374
375
376 >>> r = IpRange('127.0.0.1', '127.255.255.255')
377 >>> '127.127.127.127' in r
378 True
379
380 >>> '10.0.0.1' in r
381 False
382
383 >>> 2130706433 in r
384 True
385
386 >>> r = IpRange('127/24')
387 >>> print(r)
388 ('127.0.0.0', '127.0.0.255')
389
390 >>> r = IpRange('127/30')
391 >>> for ip in r:
392 ... print(ip)
393 127.0.0.0
394 127.0.0.1
395 127.0.0.2
396 127.0.0.3
397
398 >>> print(IpRange('127.0.0.255', '127.0.0.0'))
399 ('127.0.0.0', '127.0.0.255')
400 """
402 """
403 Args:
404 start: Ip address in dotted quad format or CIDR notation or tuple
405 of ip addresses in dotted quad format
406 end: Ip address in dotted quad format or None
407 """
408 if end is None:
409 if isinstance(start, tuple):
410
411 start, end = start
412
413 elif validate_cidr(start):
414
415 start, end = cidr2block(start)
416
417 else:
418
419 end = start
420
421 start = ip2long(start)
422 end = ip2long(end)
423 self.startIp = min(start, end)
424 self.endIp = max(start, end)
425
426
428 """
429 >>> print(IpRange('127.0.0.1'))
430 ('127.0.0.1', '127.0.0.1')
431
432 >>> print(IpRange('10/8'))
433 ('10.0.0.0', '10.255.255.255')
434
435 >>> print(IpRange('127.0.0.255', '127.0.0.0'))
436 ('127.0.0.0', '127.0.0.255')
437 """
438 return (long2ip(self.startIp), long2ip(self.endIp)).__repr__()
439
440
442 """
443 Implements membership test operators `in` and `not in` for the address
444 range.
445
446
447 >>> r = IpRange('127.0.0.1', '127.255.255.255')
448 >>> '127.127.127.127' in r
449 True
450
451 >>> '10.0.0.1' in r
452 False
453
454 >>> 2130706433 in r
455 True
456
457 >>> 'invalid' in r
458 Traceback (most recent call last):
459 ...
460 TypeError: expected dotted-quad ip address or 32-bit integer
461
462
463 Args:
464 item: Dotted-quad ip address
465 Returns:
466 True if address is in range, False otherwise
467 """
468 if isinstance(item, basestring):
469 item = ip2long(item)
470 if type(item) not in [type(1), type(_MAX_IP)]:
471 raise TypeError(
472 "expected dotted-quad ip address or 32-bit integer")
473
474 return self.startIp <= item <= self.endIp
475
476
478 """
479 Return an iterator over the range.
480
481
482 >>> iter = IpRange('127/31').__iter__()
483 >>> next(iter)
484 '127.0.0.0'
485 >>> next(iter)
486 '127.0.0.1'
487 >>> next(iter)
488 Traceback (most recent call last):
489 ...
490 StopIteration
491 """
492 i = self.startIp
493 while i <= self.endIp:
494 yield long2ip(i)
495 i += 1
496
497
498
499
501 """
502 List of IpRange objects.
503
504 Converts a list of dotted quad ip address and/or CIDR addresses into a
505 list of IpRange objects. This list can perform ``in`` and ``not in`` tests
506 and iterate all of the addresses in the range.
507
508 This can be used to convert django's conf.settings.INTERNAL_IPS list into
509 a smart object which allows CIDR notation.
510
511
512 >>> private_range = ('192.168.0.1','192.168.255.255')
513 >>> INTERNAL_IPS = IpRangeList('127.0.0.1','10/8', private_range)
514 >>> '127.0.0.1' in INTERNAL_IPS
515 True
516 >>> '10.10.10.10' in INTERNAL_IPS
517 True
518 >>> '192.168.192.168' in INTERNAL_IPS
519 True
520 >>> '172.16.0.1' in INTERNAL_IPS
521 False
522 """
525
526
528 """
529 >>> repr = IpRangeList('127.0.0.1', '10/8', '192.168/16').__repr__()
530 >>> repr = eval(repr)
531 >>> assert repr[0] == ('127.0.0.1', '127.0.0.1')
532 >>> assert repr[1] == ('10.0.0.0', '10.255.255.255')
533 >>> assert repr[2] == ('192.168.0.0', '192.168.255.255')
534 """
535 return self.ips.__repr__()
536
537
539 """
540 Implements membership test operators `in` and `not in` for the address
541 range.
542
543
544 >>> r = IpRangeList('127.0.0.1', '10/8', '192.168/16')
545 >>> '127.0.0.1' in r
546 True
547
548 >>> '10.0.0.1' in r
549 True
550
551 >>> 2130706433 in r
552 True
553
554 >>> 'invalid' in r
555 Traceback (most recent call last):
556 ...
557 TypeError: expected dotted-quad ip address or 32-bit integer
558
559
560 Args:
561 item: Dotted-quad ip address
562 Returns:
563 True if address is in range, False otherwise
564 """
565 for r in self.ips:
566 if item in r:
567 return True
568 return False
569
570
572 """
573 >>> iter = IpRangeList('127.0.0.1').__iter__()
574 >>> next(iter)
575 '127.0.0.1'
576 >>> next(iter)
577 Traceback (most recent call last):
578 ...
579 StopIteration
580
581 >>> iter = IpRangeList('127.0.0.1', '10/31').__iter__()
582 >>> next(iter)
583 '127.0.0.1'
584 >>> next(iter)
585 '10.0.0.0'
586 >>> next(iter)
587 '10.0.0.1'
588 >>> next(iter)
589 Traceback (most recent call last):
590 ...
591 StopIteration
592 """
593 for r in self.ips:
594 for ip in r:
595 yield ip
596
597
598
599
603
604
605 if __name__ == '__main__':
606 iptools_test()
607
608