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 -    def split_iab_mac(eui_int, strict=False):
    350          """
    351          @param eui_int: a MAC IAB as an unsigned integer.
    352
    353          @param strict: If True, raises a ValueError if the last 12 bits of
    354              IAB MAC/EUI-48 address are non-zero, ignores them otherwise.
    355              (Default: False)
    356          """
    357          if 0x50c2000 <= eui_int <= 0x50c2fff:
    358              return eui_int, 0
    359
    360          user_mask = 2 ** 12 - 1
    361          iab_mask = (2 ** 48 - 1) ^ user_mask
    362          iab_bits = eui_int >> 12
    363          user_bits = (eui_int | iab_mask) - iab_mask
    364
    365          if 0x50c2000 <= iab_bits <= 0x50c2fff:
    366              if strict and user_bits != 0:
    367                  raise ValueError('%r is not a strict IAB!' % hex(user_bits))
    368          else:
    369              raise ValueError('%r is not an IAB address!' % hex(eui_int))
    370
    371          return iab_bits, user_bits
    372
    373      split_iab_mac = staticmethod(split_iab_mac)
    374
    375 -    def __init__(self, iab, strict=False):
    376          """
    377          Constructor
    378
    379          @param iab: an IAB string C{00-50-C2-XX-X0-00} or an unsigned integer.
    380              This address looks like an EUI-48 but it should not have any
    381              non-zero bits in the last 3 bytes.
    382
    383          @param strict: If True, raises a ValueError if the last 12 bits of
    384              IAB MAC/EUI-48 address are non-zero, ignores them otherwise.
    385              (Default: False)
    386          """
    387          self.value = None
    388          self.record = {
    389              'idx': 0,
    390              'iab': '',
    391              'org': '',
    392              'address' : [],
    393              'offset': 0,
    394              'size': 0,
    395          }
    396
    397          if isinstance(iab, str):
    398  #FIXME: Improve string parsing here !!! '00-50-C2' is actually invalid.
    399  #FIXME: Should be '00-50-C2-00-00-00' (i.e. a full MAC/EUI-48)
    400              int_val = int(iab.replace('-', ''), 16)
    401              (iab_int, user_int) = IAB.split_iab_mac(int_val, strict)
    402              self.value = iab_int
    403          elif isinstance(iab, (int, long)):
    404              (iab_int, user_int) = IAB.split_iab_mac(iab, strict)
    405              self.value = iab_int
    406          else:
    407              raise TypeError('unexpected IAB format: %r!' % iab)
    408
    409          #   Discover offsets.
    410          if self.value in IEEE_IAB_INDEX:
    411              fh = open(IEEE_IAB_REGISTRY, 'rb')
    412              (offset, size) = IEEE_IAB_INDEX[self.value][0]
    413              self.record['offset'] = offset
    414              self.record['size'] = size
    415              fh.seek(offset)
    416              data = fh.read(size)
    417              self._parse_data(data, offset, size)
    418              fh.close()
    419          else:
    420              raise NotRegisteredError('IAB %r not unregistered!' % iab)
    421
    422 -    def _parse_data(self, data, offset, size):
    423          """Returns a dict record from raw IAB record data"""
    424          for line in data.split("\n"):
    425              line = line.strip()
    426              if line == '':
    427                  continue
    428
    429              if '(hex)' in line:
    430                  self.record['idx'] = self.value
    431                  self.record['org'] = ' '.join(line.split()[2:])
    432                  self.record['iab'] = str(self)
    433              elif '(base 16)' in line:
    434                  continue
    435              else:
    436                  self.record['address'].append(line)
    437
    438 -    def address(self):
    439          """@return: registered address of organisation"""
    440          return self.record['address']
    441
    442 -    def org(self):
    443          """@return: the name of organisation"""
    444          return self.record['org']
    445
    446      organisation = org
    447
    448 -    def __int__(self):
    449          """@return: integer representation of this IAB"""
    450          return self.value
    451
    452 -    def __hex__(self):
    453          """
    454          @return: hexadecimal string representation of this IAB (in network
    455              byte order)
    456          """
    457          return hex(self.value).rstrip('L').lower()
    458
    459 -    def __str__(self):
    460          """@return: string representation of this IAB"""
    461          int_val = self.value << 12
    462          words = []
    463          for _ in range(6):
    464              word = int_val & 0xff
    465              words.append('%02x' % word)
    466              int_val >>= 8
    467          return '-'.join(reversed(words)).upper()
    468
    469 -    def registration(self):
    470          """@return: registration details for this IAB"""
    471          return self.record
    472
    473 -    def __repr__(self):
    474          """@return: executable Python string to recreate equivalent object."""
    475          return '%s(%r)' % (self.__class__.__name__, str(self))
    476
    477  #-----------------------------------------------------------------------------
    478 -def create_ieee_indices():
    479      """Create indices for OUI and IAB file based lookups"""
    480      oui_parser = OUIIndexParser(IEEE_OUI_REGISTRY)
    481      oui_parser.attach(FileIndexer(IEEE_OUI_METADATA))
    482      oui_parser.parse()
    483
    484      iab_parser = IABIndexParser(IEEE_IAB_REGISTRY)
    485      iab_parser.attach(FileIndexer(IEEE_IAB_METADATA))
    486      iab_parser.parse()
    487
    488  #-----------------------------------------------------------------------------
    489 -def load_ieee_indices():
    490      """Load OUI and IAB indices into memory"""
    491      for row in _csv.reader(open(IEEE_OUI_METADATA)):
    492          (key, offset, size) = [int(_) for _ in row]
    493          IEEE_OUI_INDEX.setdefault(key, [])
    494          IEEE_OUI_INDEX[key].append((offset, size))
    495
    496      for row in _csv.reader(open(IEEE_IAB_METADATA)):
    497          (key, offset, size) = [int(_) for _ in row]
    498          IEEE_IAB_INDEX.setdefault(key, [])
    499          IEEE_IAB_INDEX[key].append((offset, size))
    500
    501  #-----------------------------------------------------------------------------
    502 -def get_latest_files():
    503      """Download the latest files from the IEEE"""
    504      import urllib2
    505
    506      urls = [
    507          'http://standards.ieee.org/regauth/oui/oui.txt',
    508          'http://standards.ieee.org/regauth/oui/iab.txt',
    509      ]
    510
    511      for url in urls:
    512          print 'downloading latest copy of %s' % url
    513          request = urllib2.Request(url)
    514          response = urllib2.urlopen(request)
    515          save_path = _path.dirname(__file__)
    516          filename = _path.join(save_path, _os.path.basename(response.geturl()))
    517          fh = open(filename, 'wb')
    518          fh.write(response.read())
    519          fh.close()
    520
    521  #-----------------------------------------------------------------------------
    522  if __name__ == '__main__':
    523      #   Generate indices when module is executed as a script.
    524      get_latest_files()
    525      create_ieee_indices()
    526
    527
    528  #   On module load read indices in memory to enable lookups.
    529  load_ieee_indices()
    530