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