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