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

Source Code for Module starcluster.iptools

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