starcluster.iptools

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