1
2
3
4
5
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
29
30
31
32 IEEE_OUI_REGISTRY = _path.join(_path.dirname(__file__), 'oui.txt')
33
34 IEEE_OUI_METADATA = _path.join(_path.dirname(__file__), 'oui.idx')
35
36
37 IEEE_OUI_INDEX = {}
38
39
40 IEEE_IAB_REGISTRY = _path.join(_path.dirname(__file__), 'iab.txt')
41
42
43 IEEE_IAB_METADATA = _path.join(_path.dirname(__file__), 'iab.idx')
44
45
46 IEEE_IAB_INDEX = {}
47
49 """
50 An Exception indicating that an OUI or IAB was not found in the IEEE
51 Registry.
52 """
53 pass
54
55
57 """Stores index data found by a parser in a CSV file"""
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
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
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 """
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
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()
114
115 if not line:
116 break
117
118 if skip_header and '(hex)' in line:
119 skip_header = False
120
121 if skip_header:
122
123 continue
124
125 if '(hex)' in line:
126
127 if record is not None:
128
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
139 size += len(line)
140
141
142 record.append(size)
143 self.notify(record)
144
145
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 """
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
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()
183
184 if not line:
185 break
186
187 if skip_header and '(hex)' in line:
188 skip_header = False
189
190 if skip_header:
191
192 continue
193
194 if '(hex)' in line:
195
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
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
214 size += len(line)
215
216
217 record.append(size)
218 self.notify(record)
219
220
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 """
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
241
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
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
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
290 """@return: number of organisations with this OUI"""
291 return len(self.records)
292
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
314 """@return: integer representation of this OUI"""
315 return self.value
316
318 """
319 @return: hexadecimal string representation of this OUI (in network byte
320 order).
321 """
322 return hex(self.value).rstrip('L').lower()
323
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
335 """@return: registration details for this IAB"""
336 return self.records
337
339 """@return: executable Python string to recreate equivalent object."""
340 return '%s(%r)' % (self.__class__.__name__, str(self))
341
342
344 """
345 Represents an individual IEEE IAB (Individual Address Block) identifier.
346
347 For online details see - http://standards.ieee.org/regauth/oui/
348 """
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
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
399
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
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
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
439 """@return: registered address of organisation"""
440 return self.record['address']
441
443 """@return: the name of organisation"""
444 return self.record['org']
445
446 organisation = org
447
449 """@return: integer representation of this IAB"""
450 return self.value
451
453 """
454 @return: hexadecimal string representation of this IAB (in network
455 byte order)
456 """
457 return hex(self.value).rstrip('L').lower()
458
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
470 """@return: registration details for this IAB"""
471 return self.record
472
474 """@return: executable Python string to recreate equivalent object."""
475 return '%s(%r)' % (self.__class__.__name__, str(self))
476
477
487
488
500
501
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
524 get_latest_files()
525 create_ieee_indices()
526
527
528
529 load_ieee_indices()
530