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

# Source Code for Package netaddr.eui

      1  #!/usr/bin/env python
      2  #-----------------------------------------------------------------------------
      3  #   Copyright (c) 2008, 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/iab/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 a network byte order 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          """@return: network byte order hexadecimal string of this OUI"""
    319          return hex(self.value).rstrip('L').lower()
    320
    321 -    def __str__(self):
    322          """@return: string representation of this OUI"""
    323          int_val = self.value
    324          words = []
    325          for _ in range(3):
    326              word = int_val & 0xff
    327              words.append('%02x' % word)
    328              int_val >>= 8
    329          words.reverse()
    330          return '-'.join(words).upper()
    331
    332 -    def registration(self):
    333          """@return: registration details for this IAB"""
    334          return self.records
    335
    336 -    def __repr__(self):
    337          """@return: executable Python string to recreate equivalent object."""
    338          return '%s(%r)' % (self.__class__.__name__, str(self))
    339
    340  #-----------------------------------------------------------------------------
    341 -class IAB(object):
    342      """
    343      Represents an individual IEEE IAB (Individual Address Block) identifier.
    344
    345      For online details see - http://standards.ieee.org/regauth/oui/
    346      """
    347 -    def split_iab_mac(eui_int, strict=False):
    348          """
    349          @param eui_int: a MAC IAB integer value in network byte order.
    350
    351          @param strict: If True, raises a ValueError if the last 12 bits of
    352              IAB MAC/EUI-48 address are non-zero, ignores them otherwise.
    353              (Default: False)
    354          """
    355          if 0x50c2000 <= eui_int <= 0x50c2fff:
    356              return eui_int, 0
    357
    358          user_mask = 2 ** 12 - 1
    359          iab_mask = (2 ** 48 - 1) ^ user_mask
    360          iab_bits = eui_int >> 12
    361          user_bits = (eui_int | iab_mask) - iab_mask
    362
    363          if 0x50c2000 <= iab_bits <= 0x50c2fff:
    364              if strict and user_bits != 0:
    365                  raise ValueError('%r is not a strict IAB!' % hex(user_bits))
    366          else:
    367              raise ValueError('%r is not an IAB address!' % hex(eui_int))
    368
    369          return iab_bits, user_bits
    370
    371      split_iab_mac = staticmethod(split_iab_mac)
    372
    373 -    def __init__(self, iab, strict=False):
    374          """
    375          Constructor
    376
    377          @param iab: an IAB string C{00-50-C2-XX-X0-00} or a network byte order
    378              integer. This address looks like an EUI-48 but it should not have
    379              any non-zero bits in the last 3 bytes.
    380
    381          @param strict: If True, raises a ValueError if the last 12 bits of
    382              IAB MAC/EUI-48 address are non-zero, ignores them otherwise.
    383              (Default: False)
    384          """
    385          self.value = None
    386          self.record = {
    387              'idx': 0,
    388              'iab': '',
    389              'org': '',
    390              'address' : [],
    391              'offset': 0,
    392              'size': 0,
    393          }
    394
    395          if isinstance(iab, str):
    396  #FIXME: Improve string parsing here !!! '00-50-C2' is actually invalid.
    397  #FIXME: Should be '00-50-C2-00-00-00' (i.e. a full MAC/EUI-48)
    398              int_val = int(iab.replace('-', ''), 16)
    399              (iab_int, user_int) = IAB.split_iab_mac(int_val, strict)
    400              self.value = iab_int
    401          elif isinstance(iab, (int, long)):
    402              (iab_int, user_int) = IAB.split_iab_mac(iab, strict)
    403              self.value = iab_int
    404          else:
    405              raise TypeError('unexpected IAB format: %r!' % iab)
    406
    407          #   Discover offsets.
    408          if self.value in IEEE_IAB_INDEX:
    409              fh = open(IEEE_IAB_REGISTRY, 'rb')
    410              (offset, size) = IEEE_IAB_INDEX[self.value][0]
    411              self.record['offset'] = offset
    412              self.record['size'] = size
    413              fh.seek(offset)
    414              data = fh.read(size)
    415              self._parse_data(data, offset, size)
    416              fh.close()
    417          else:
    418              raise NotRegisteredError('IAB %r not unregistered!' % iab)
    419
    420 -    def _parse_data(self, data, offset, size):
    421          """Returns a dict record from raw IAB record data"""
    422          for line in data.split("\n"):
    423              line = line.strip()
    424              if line == '':
    425                  continue
    426
    427              if '(hex)' in line:
    428                  self.record['idx'] = self.value
    429                  self.record['org'] = ' '.join(line.split()[2:])
    430                  self.record['iab'] = str(self)
    431              elif '(base 16)' in line:
    432                  continue
    433              else:
    434                  self.record['address'].append(line)
    435
    436 -    def address(self):
    437          """@return: registered address of organisation"""
    438          return self.record['address']
    439
    440 -    def org(self):
    441          """@return: the name of organisation"""
    442          return self.record['org']
    443
    444      organisation = org
    445
    446 -    def __int__(self):
    447          """@return: integer representation of this IAB"""
    448          return self.value
    449
    450 -    def __hex__(self):
    451          """@return: network byte order hexadecimal string of this IAB"""
    452          return hex(self.value).rstrip('L').lower()
    453
    454 -    def __str__(self):
    455          """@return: string representation of this IAB"""
    456          int_val = self.value << 12
    457          words = []
    458          for _ in range(6):
    459              word = int_val & 0xff
    460              words.append('%02x' % word)
    461              int_val >>= 8
    462          words.reverse()
    463          return '-'.join(words).upper()
    464
    465 -    def registration(self):
    466          """@return: registration details for this IAB"""
    467          return self.record
    468
    469 -    def __repr__(self):
    470          """@return: executable Python string to recreate equivalent object."""
    471          return '%s(%r)' % (self.__class__.__name__, str(self))
    472
    473  #-----------------------------------------------------------------------------
    474 -def create_ieee_indices():
    475      """Create indices for OUI and IAB file based lookups"""
    476      oui_parser = OUIIndexParser(IEEE_OUI_REGISTRY)
    477      oui_parser.attach(FileIndexer(IEEE_OUI_METADATA))
    478      oui_parser.parse()
    479
    480      iab_parser = IABIndexParser(IEEE_IAB_REGISTRY)
    481      iab_parser.attach(FileIndexer(IEEE_IAB_METADATA))
    482      iab_parser.parse()
    483
    484  #-----------------------------------------------------------------------------
    485 -def load_ieee_indices():
    486      """Load OUI and IAB indices into memory"""
    487      for row in _csv.reader(open(IEEE_OUI_METADATA)):
    488          (key, offset, size) = [int(_) for _ in row]
    489          IEEE_OUI_INDEX.setdefault(key, [])
    490          IEEE_OUI_INDEX[key].append((offset, size))
    491
    492      for row in _csv.reader(open(IEEE_IAB_METADATA)):
    493          (key, offset, size) = [int(_) for _ in row]
    494          IEEE_IAB_INDEX.setdefault(key, [])
    495          IEEE_IAB_INDEX[key].append((offset, size))
    496
    497  #-----------------------------------------------------------------------------
    498  if __name__ == '__main__':
    499      #   Generate indices when module is executed as a script.
    500      create_ieee_indices()
    501
    502  #   On module load read indices in memory to enable lookups.
    503  load_ieee_indices()
    504