Package starcluster :: Module iptools
[hide private]
[frames] | no frames]

Source Code for Module starcluster.iptools

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  # 
  4  # Copyright (c) 2008-2010, Bryan Davis 
  5  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions are met: 
  9  #     - Redistributions of source code must retain the above copyright notice, 
 10  #     this list of conditions and the following disclaimer. 
 11  #     - Redistributions in binary form must reproduce the above copyright 
 12  #     notice, this list of conditions and the following disclaimer in the 
 13  #     documentation and/or other materials provided with the distribution. 
 14  # 
 15  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 16  # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 17  # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 18  # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 19  # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 20  # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 21  # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 22  # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 23  # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 24  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 25  # POSSIBILITY OF SUCH DAMAGE. 
 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  # sniff for python2.x / python3k compatibility "fixes' 
 70  try: 
 71      basestring = basestring 
 72  except NameError: 
 73      # 'basestring' is undefined, must be python3k 
 74      basestring = str 
 75   
 76   
 77  try: 
 78      next = next 
 79  except NameError: 
 80   
 81      # builtin next function doesn't exist 
82 - def next(iterable):
83 return iterable.next()
84 85 86 _DOTTED_QUAD_RE = re.compile(r'^(\d{1,3}\.){0,3}\d{1,3}$') 87 88
89 -def validate_ip(s):
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 #end validate_ip 124 125 _CIDR_RE = re.compile(r'^(\d{1,3}\.){0,3}\d{1,3}/\d{1,2}$') 126 127
128 -def validate_cidr(s):
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 #end validate_cidr 169 170
171 -def ip2long(ip):
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 # only a network quad 200 quads = quads + [0, 0, 0] 201 elif len(quads) < 4: 202 # partial form, last supplied quad is host address, rest is network 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 #end ip2long 211 212 _MAX_IP = 0xffffffff 213 _MIN_IP = 0x0 214 215
216 -def long2ip(l):
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 #end long2ip 261 262
263 -def ip2hex(addr):
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 #end ip2hex 286 287
288 -def hex2ip(hex_str):
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 #end hex2ip 310 311
312 -def cidr2block(cidr):
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 # convert dotted-quad ip to base network number 349 # can't use ip2long because partial addresses are treated as all network 350 # instead of network plus host (eg. '127.1' expands to '127.1.0.0') 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 # keep left most prefix bits of baseIp 357 shift = 32 - prefix 358 start = baseIp >> shift << shift 359 360 # expand right most 32 - prefix bits to 1 361 mask = (1 << shift) - 1 362 end = start | mask 363 return (long2ip(start), long2ip(end))
364 #end cidr2block 365 366
367 -class IpRange(object):
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 """
401 - def __init__(self, start, end=None):
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 # occurs when IpRangeList calls via map to pass start and end 411 start, end = start 412 413 elif validate_cidr(start): 414 # CIDR notation range 415 start, end = cidr2block(start) 416 417 else: 418 # degenerate range 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 #end __init__ 426
427 - def __repr__(self):
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 #end __repr__ 440
441 - def __contains__(self, item):
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 #end __contains__ 476
477 - def __iter__(self):
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 #end __iter__ 497 #end class IpRange 498 499
500 -class IpRangeList(object):
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 """
523 - def __init__(self, *args):
524 self.ips = tuple(map(IpRange, args))
525 #end __init__ 526
527 - def __repr__(self):
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 #end __repr__ 537
538 - def __contains__(self, item):
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 #end __contains__ 570
571 - def __iter__(self):
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 #end __iter__ 597 #end class IpRangeList 598 599
600 -def iptools_test():
601 import doctest 602 doctest.testmod()
603 #end iptools_test 604 605 if __name__ == '__main__': 606 iptools_test() 607 # vim: set sw=4 ts=4 sts=4 et : 608