Package netaddr :: Package eui |
[hide private]
[frames] | no frames]

# Source Code for Package netaddr.eui

      1  #!/usr/bin/env python
      2  #-----------------------------------------------------------------------------
      3  #   Copyright (c) 2008-2009, David P. D. Moss. All rights reserved.
      4  #
      5  #   Released under the BSD license. See the LICENSE file for details.
      6  #-----------------------------------------------------------------------------
      7  """
      8  Provides access to public network address information published by the IEEE.
      9
     10  More details can be found at the following URLs :-
     11
     12  Institute of Electrical and Electronics Engineers (IEEE)
     13
     14      - http://www.ieee.org/
     15      - http://standards.ieee.org/regauth/oui/
     16  """
     17
     18  import sys as _sys
     19  import os as _os
     20  import os.path as _path
     21  import csv as _csv
     22
     23  import pprint as _pprint
     24
     25  from netaddr.core import Subscriber, Publisher
     26
     27  #-----------------------------------------------------------------------------
     28  #   Constants.
     29  #-----------------------------------------------------------------------------
     30
     31  #: Path to local copy of IEEE OUI Registry data file.
     32  IEEE_OUI_REGISTRY = _path.join(_path.dirname(__file__), 'oui.txt')
     33  #: Path to netaddr OUI index file.
     34  IEEE_OUI_METADATA = _path.join(_path.dirname(__file__), 'oui.idx')
     35
     36  #: OUI index lookup dictionary.
     37  IEEE_OUI_INDEX = {}
     38
     39  #: Path to local copy of IEEE IAB Registry data file.
     40  IEEE_IAB_REGISTRY = _path.join(_path.dirname(__file__), 'iab.txt')
     41
     42  #: Path to netaddr IAB index file.
     43  IEEE_IAB_METADATA = _path.join(_path.dirname(__file__), 'iab.idx')
     44
     45  #: IAB index lookup dictionary.
     46  IEEE_IAB_INDEX = {}
     47
     48 -class NotRegisteredError(Exception):
     49      """
     50      An Exception indicating that an OUI or IAB was not found in the IEEE
     51      Registry.
     52      """
     53      pass
     54
     55  #-----------------------------------------------------------------------------
     56 -class FileIndexer(Subscriber):
     57      """Stores index data found by a parser in a CSV file"""
     58 -    def __init__(self, filename):
     59          """
     60          Constructor.
     61
     62          filename - location of CSV index file.
     63          """
     64          self.fh = open(filename, 'w')
     65          self.writer = _csv.writer(self.fh, lineterminator="\n")
     66
     67 -    def update(self, data):
     68          """
     69          Write received record to a CSV file
     70
     71          @param data: record containing offset record information.
     72          """
     73          self.writer.writerow(data)
     74
     75  #-----------------------------------------------------------------------------
     76 -class OUIIndexParser(Publisher):
     77      """
     78      A parser that processes OUI (Organisationally Unique Identifier)
     79      registration file data published by the IEEE.
     80
     81      It sends out notifications to registered subscribers for each record it
     82      encounters, passing on the record's position relative to file start
     83      (offset) and the size of the record (in bytes).
     84
     85      The file is available online here :-
     86
     87      http://standards.ieee.org/regauth/oui/oui.txt
     88
     89      Sample record::
     90
     91          00-CA-FE   (hex)        ACME CORPORATION
     92          00CAFE     (base 16)        ACME CORPORATION
     93                          1 MAIN STREET
     94                          SPRINGFIELD
     95                          UNITED STATES
     96      """
     97 -    def __init__(self, filename):
     98          """
     99          Constructor.
    100
    101          filename - location of file containing OUI records.
    102          """
    103          super(OUIIndexParser, self).__init__()
    104          self.fh = open(filename, 'rb')
    105
    106 -    def parse(self):
    107          """Parse an OUI registration file for records notifying subscribers"""
    108          skip_header = True
    109          record = None
    110          size = 0
    111
    112          while True:
    113              line = self.fh.readline() # unbuffered to obtain correct offsets
    114
    115              if not line:
    116                  break   # EOF, we're done
    117
    118              if skip_header and '(hex)' in line:
    119                  skip_header = False
    120
    121              if skip_header:
    122                  #   ignoring header section
    123                  continue
    124
    125              if '(hex)' in line:
    126                  #   record start
    127                  if record is not None:
    128                      #   a complete record.
    129                      record.append(size)
    130                      self.notify(record)
    131
    132                  size = len(line)
    133                  offset = (self.fh.tell() - len(line))
    134                  oui = line.split()[0]
    135                  index = int(oui.replace('-', ''), 16)
    136                  record = [index, offset]
    137              else:
    138                  #   within record
    139                  size += len(line)
    140
    141          #   process final record on loop exit
    142          record.append(size)
    143          self.notify(record)
    144
    145  #-----------------------------------------------------------------------------
    146 -class IABIndexParser(Publisher):
    147      """
    148      A parser that processes IAB (Individual Address Block) registration file
    149      data published by the IEEE.
    150
    151      It sends out notifications to registered Subscriber objects for each
    152      record it encounters, passing on the record's position relative to file
    153      start (offset) and the size of the record (in bytes).
    154
    155      The file is available online here :-
    156
    157      http://standards.ieee.org/regauth/oui/iab.txt
    158
    159      Sample record::
    160
    161          00-50-C2   (hex)        ACME CORPORATION
    162          ABC000-ABCFFF     (base 16)        ACME CORPORATION
    163                          1 MAIN STREET
    164                          SPRINGFIELD
    165                          UNITED STATES
    166      """
    167 -    def __init__(self, filename):
    168          """
    169          Constructor.
    170
    171          filename - location of file containing IAB records.
    172          """
    173          super(IABIndexParser, self).__init__()
    174          self.fh = open(filename, 'rb')
    175
    176 -    def parse(self):
    177          """Parse an IAB registration file for records notifying subscribers"""
    178          skip_header = True
    179          record = None
    180          size = 0
    181          while True:
    182              line = self.fh.readline()   # unbuffered
    183
    184              if not line:
    185                  break   # EOF, we're done
    186
    187              if skip_header and '(hex)' in line:
    188                  skip_header = False
    189
    190              if skip_header:
    191                  #   ignoring header section
    192                  continue
    193
    194              if '(hex)' in line:
    195                  #   record start
    196                  if record is not None:
    197                      record.append(size)
    198                      self.notify(record)
    199
    200                  offset = (self.fh.tell() - len(line))
    201                  iab_prefix = line.split()[0]
    202                  index = iab_prefix
    203                  record = [index, offset]
    204                  size = len(line)
    205              elif '(base 16)' in line:
    206                  #   within record
    207                  size += len(line)
    208                  prefix = record[0].replace('-', '')
    209                  suffix = line.split()[0]
    210                  suffix = suffix.split('-')[0]
    211                  record[0] = (int(prefix + suffix, 16)) >> 12
    212              else:
    213                  #   within record
    214                  size += len(line)
    215
    216          #   process final record on loop exit
    217          record.append(size)
    218          self.notify(record)
    219
    220  #-----------------------------------------------------------------------------
    221 -class OUI(object):
    222      """
    223      Represents an individual IEEE OUI (Organisationally Unique Identifier)
    224      identifier.
    225
    226      For online details see - http://standards.ieee.org/regauth/oui/
    227      """
    228 -    def __init__(self, oui):
    229          """
    230          Constructor
    231
    232          @param oui: an OUI string C{XX-XX-XX} or an unsigned integer.
    233              Also accepts and parses full MAC/EUI-48 address strings (but not
    234              MAC/EUI-48 integers)!
    235          """
    236          self.value = None
    237          self.records = []
    238
    239          if isinstance(oui, str):
    240  #FIXME: Improve string parsing here. Accept full MAC/EUI-48 addressse as
    241  #FIXME: well as XX-XX-XX and just take /16 (see IAB for details)!
    242              self.value = int(oui.replace('-', ''), 16)
    243          elif isinstance(oui, (int, long)):
    244              if 0 <= oui <= 0xffffff:
    245                  self.value = oui
    246              else:
    247                  raise ValueError('OUI int outside expected range: %r' % oui)
    248          else:
    249              raise TypeError('unexpected OUI format: %r' % oui)
    250
    251          #   Discover offsets.
    252          if self.value in IEEE_OUI_INDEX:
    253              fh = open(IEEE_OUI_REGISTRY, 'rb')
    254              for (offset, size) in IEEE_OUI_INDEX[self.value]:
    255                  fh.seek(offset)
    256                  data = fh.read(size)
    257                  self._parse_data(data, offset, size)
    258              fh.close()
    259          else:
    260              raise NotRegisteredError('OUI %r not registered!' % oui)
    261
    262 -    def _parse_data(self, data, offset, size):
    263          """Returns a dict record from raw OUI record data"""
    264          record = {
    265              'idx': 0,
    266              'oui': '',
    267              'org': '',
    268              'address' : [],
    269              'offset': offset,
    270              'size': size,
    271          }
    272
    273          for line in data.split("\n"):
    274              line = line.strip()
    275              if line == '':
    276                  continue
    277
    278              if '(hex)' in line:
    279                  record['idx'] = self.value
    280                  record['org'] = ' '.join(line.split()[2:])
    281                  record['oui'] = str(self)
    282              elif '(base 16)' in line:
    283                  continue
    284              else:
    285                  record['address'].append(line)
    286
    287          self.records.append(record)
    288
    289 -    def org_count(self):
    290          """@return: number of organisations with this OUI"""
    291          return len(self.records)
    292
    293 -    def address(self, index=0):
    294          """
    295          @param index: the index of record (multiple registrations)
    296              (Default: 0 - first registration)
    297
    298          @return: registered address of organisation linked to OUI
    299          """
    300          return self.records[index]['address']
    301
    302 -    def org(self, index=0):
    303          """
    304          @param index: the index of record (multiple registrations)
    305              (Default: 0 - first registration)
    306
    307          @return: the name of organisation linked to OUI
    308          """
    309          return self.records[index]['org']
    310
    311      organisation = org
    312
    313 -    def __int__(self):
    314          """@return: integer representation of this OUI"""
    315          return self.value
    316
    317 -    def __hex__(self):
    318          """
    319          @return: hexadecimal string representation of this OUI (in network byte
    320          order).
    321          """
    322          return hex(self.value).rstrip('L').lower()
    323
    324 -    def __str__(self):
    325          """@return: string representation of this OUI"""
    326          int_val = self.value
    327          words = []
    328          for _ in range(3):
    329              word = int_val & 0xff
    330              words.append('%02x' % word)
    331              int_val >>= 8
    332          return '-'.join(reversed(words)).upper()
    333
    334 -    def registration(self):
    335          """@return: registration details for this IAB"""
    336          return self.records
    337
    338 -    def __repr__(self):
    339          """@return: executable Python string to recreate equivalent object."""
    340          return '%s(%r)' % (self.__class__.__name__, str(self))
    341
    342  #-----------------------------------------------------------------------------
    343 -class IAB(object):
    344      """
    345      Represents an individual IEEE IAB (Individual Address Block) identifier.
    346
    347      For online details see - http://standards.ieee.org/regauth/oui/
    348      """
    349      @staticmethod
    350 -    def split_iab_mac(eui_int, strict=False):
    351          """
    352          @param eui_int: a MAC IAB as an unsigned integer.
    353
    354          @param strict: If True, raises a ValueError if the last 12 bits of
    355              IAB MAC/EUI-48 address are non-zero, ignores them otherwise.
    356              (Default: False)
    357          """
    358          if 0x50c2000 <= eui_int <= 0x50c2fff:
    359              return eui_int, 0
    360
    361          user_mask = 2 ** 12 - 1
    362          iab_mask = (2 ** 48 - 1) ^ user_mask
    363          iab_bits = eui_int >> 12
    364          user_bits = (eui_int | iab_mask) - iab_mask
    365
    366          if 0x50c2000 <= iab_bits <= 0x50c2fff:
    367              if strict and user_bits != 0:
    368                  raise ValueError('%r is not a strict IAB!' % hex(user_bits))
    369          else:
    370              raise ValueError('%r is not an IAB address!' % hex(eui_int))
    371
    372          return iab_bits, user_bits
    373
    374 -    def __init__(self, iab, strict=False):
    375          """
    376          Constructor
    377
    378          @param iab: an IAB string C{00-50-C2-XX-X0-00} or an unsigned integer.
    379              This address looks like an EUI-48 but it should not have any
    380              non-zero bits in the last 3 bytes.
    381
    382          @param strict: If True, raises a ValueError if the last 12 bits of
    383              IAB MAC/EUI-48 address are non-zero, ignores them otherwise.
    384              (Default: False)
    385          """
    386          self.value = None
    387          self.record = {
    388              'idx': 0,
    389              'iab': '',
    390              'org': '',
    391              'address' : [],
    392              'offset': 0,
    393              'size': 0,
    394          }
    395
    396          if isinstance(iab, str):
    397  #FIXME: Improve string parsing here !!! '00-50-C2' is actually invalid.
    398  #FIXME: Should be '00-50-C2-00-00-00' (i.e. a full MAC/EUI-48)
    399              int_val = int(iab.replace('-', ''), 16)
    400              (iab_int, user_int) = IAB.split_iab_mac(int_val, strict)
    401              self.value = iab_int
    402          elif isinstance(iab, (int, long)):
    403              (iab_int, user_int) = IAB.split_iab_mac(iab, strict)
    404              self.value = iab_int
    405          else:
    406              raise TypeError('unexpected IAB format: %r!' % iab)
    407
    408          #   Discover offsets.
    409          if self.value in IEEE_IAB_INDEX:
    410              fh = open(IEEE_IAB_REGISTRY, 'rb')
    411              (offset, size) = IEEE_IAB_INDEX[self.value][0]
    412              self.record['offset'] = offset
    413              self.record['size'] = size
    414              fh.seek(offset)
    415              data = fh.read(size)
    416              self._parse_data(data, offset, size)
    417              fh.close()
    418          else:
    419              raise NotRegisteredError('IAB %r not unregistered!' % iab)
    420
    421 -    def _parse_data(self, data, offset, size):
    422          """Returns a dict record from raw IAB record data"""
    423          for line in data.split("\n"):
    424              line = line.strip()
    425              if line == '':
    426                  continue
    427
    428              if '(hex)' in line:
    429                  self.record['idx'] = self.value
    430                  self.record['org'] = ' '.join(line.split()[2:])
    431                  self.record['iab'] = str(self)
    432              elif '(base 16)' in line:
    433                  continue
    434              else:
    435                  self.record['address'].append(line)
    436
    437 -    def address(self):
    438          """@return: registered address of organisation"""
    439          return self.record['address']
    440
    441 -    def org(self):
    442          """@return: the name of organisation"""
    443          return self.record['org']
    444
    445      organisation = org
    446
    447 -    def __int__(self):
    448          """@return: integer representation of this IAB"""
    449          return self.value
    450
    451 -    def __hex__(self):
    452          """
    453          @return: hexadecimal string representation of this IAB (in network
    454              byte order)
    455          """
    456          return hex(self.value).rstrip('L').lower()
    457
    458 -    def __str__(self):
    459          """@return: string representation of this IAB"""
    460          int_val = self.value << 12
    461          words = []
    462          for _ in range(6):
    463              word = int_val & 0xff
    464              words.append('%02x' % word)
    465              int_val >>= 8
    466          return '-'.join(reversed(words)).upper()
    467
    468 -    def registration(self):
    469          """@return: registration details for this IAB"""
    470          return self.record
    471
    472 -    def __repr__(self):
    473          """@return: executable Python string to recreate equivalent object."""
    474          return '%s(%r)' % (self.__class__.__name__, str(self))
    475
    476  #-----------------------------------------------------------------------------
    477 -def create_ieee_indices():
    478      """Create indices for OUI and IAB file based lookups"""
    479      oui_parser = OUIIndexParser(IEEE_OUI_REGISTRY)
    480      oui_parser.attach(FileIndexer(IEEE_OUI_METADATA))
    481      oui_parser.parse()
    482
    483      iab_parser = IABIndexParser(IEEE_IAB_REGISTRY)
    484      iab_parser.attach(FileIndexer(IEEE_IAB_METADATA))
    485      iab_parser.parse()
    486
    487  #-----------------------------------------------------------------------------
    488 -def load_ieee_indices():
    489      """Load OUI and IAB indices into memory"""
    490      for row in _csv.reader(open(IEEE_OUI_METADATA)):
    491          (key, offset, size) = [int(_) for _ in row]
    492          IEEE_OUI_INDEX.setdefault(key, [])
    493          IEEE_OUI_INDEX[key].append((offset, size))
    494
    495      for row in _csv.reader(open(IEEE_IAB_METADATA)):
    496          (key, offset, size) = [int(_) for _ in row]
    497          IEEE_IAB_INDEX.setdefault(key, [])
    498          IEEE_IAB_INDEX[key].append((offset, size))
    499
    500  #-----------------------------------------------------------------------------
    501 -def get_latest_files():
    502      """Download the latest files from the IEEE"""
    503      import urllib2
    504
    505      urls = [
    506          'http://standards.ieee.org/regauth/oui/oui.txt',
    507          'http://standards.ieee.org/regauth/oui/iab.txt',
    508      ]
    509
    510      for url in urls:
    511          print 'downloading latest copy of %s' % url
    512          request = urllib2.Request(url)
    513          response = urllib2.urlopen(request)
    514          save_path = _path.dirname(__file__)
    515          filename = _path.join(save_path, _os.path.basename(response.geturl()))
    516          fh = open(filename, 'wb')
    517          fh.write(response.read())
    518          fh.close()
    519
    520  #-----------------------------------------------------------------------------
    521  if __name__ == '__main__':
    522      #   Generate indices when module is executed as a script.
    523      get_latest_files()
    524      create_ieee_indices()
    525
    526
    527  #   On module load read indices in memory to enable lookups.
    528  load_ieee_indices()
    529