Rev 19 | Rev 21 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 19 | Rev 20 | ||
---|---|---|---|
Line 1... | Line 1... | ||
1 | <?php |
1 | <?php |
2 | 2 | ||
3 | /* |
3 | /* |
4 | * MAC (EUI-48 and EUI-64) utils for PHP |
4 | * MAC (EUI-48 and EUI-64) utils for PHP |
5 | * Copyright 2017 - 2023 Daniel Marschall, ViaThinkSoft |
5 | * Copyright 2017 - 2023 Daniel Marschall, ViaThinkSoft |
6 | * Version 2023-05-01 |
6 | * Version 2023-05-03 |
7 | * |
7 | * |
8 | * Licensed under the Apache License, Version 2.0 (the "License"); |
8 | * Licensed under the Apache License, Version 2.0 (the "License"); |
9 | * you may not use this file except in compliance with the License. |
9 | * you may not use this file except in compliance with the License. |
10 | * You may obtain a copy of the License at |
10 | * You may obtain a copy of the License at |
11 | * |
11 | * |
Line 24... | Line 24... | ||
24 | // - https://en.m.wikipedia.org/wiki/Organizationally_unique_identifier |
24 | // - https://en.m.wikipedia.org/wiki/Organizationally_unique_identifier |
25 | 25 | ||
26 | const IEEE_MAC_REGISTRY = __DIR__ . '/../web-data'; |
26 | const IEEE_MAC_REGISTRY = __DIR__ . '/../web-data'; |
27 | 27 | ||
28 | /** |
28 | /** |
29 | * Checks if a MAC, EUI, ELI, or IPv6-Link-Local address is valid |
29 | * Checks if a MAC, EUI, ELI, AAI, SAI, or IPv6-Link-Local address is valid |
30 | * @param string $mac MAC, EUI, or IPv6-Link-Local Address |
30 | * @param string $mac MAC, EUI, or IPv6-Link-Local Address |
31 | * @return bool True if it is valid |
31 | * @return bool True if it is valid |
32 | */ |
32 | */ |
33 | function mac_valid(string $mac): bool { |
33 | function mac_valid(string $mac): bool { |
34 | $tmp = ipv6linklocal_to_mac48($mac); |
34 | $tmp = ipv6linklocal_to_mac48($mac); |
Line 43... | Line 43... | ||
43 | 43 | ||
44 | return ($mac === ''); |
44 | return ($mac === ''); |
45 | } |
45 | } |
46 | 46 | ||
47 | /** |
47 | /** |
48 | * Returns the amount of bits of a MAC, EUI, or ELI |
48 | * Returns the amount of bits of a MAC, EUI, ELI, AAI, or SAI |
49 | * @param string $mac |
49 | * @param string $mac |
50 | * @return false|int |
50 | * @return false|int |
51 | */ |
51 | */ |
52 | function eui_bits(string $mac) { |
52 | function eui_bits(string $mac) { |
53 | if (!mac_valid($mac)) return false; |
53 | if (!mac_valid($mac)) return false; |
54 | $mac = mac_canonize($mac, ''); |
54 | $mac = mac_canonize($mac, ''); |
55 | return (int)(strlen($mac)*4); |
55 | return (int)(strlen($mac)*4); |
56 | } |
56 | } |
57 | 57 | ||
58 | /** |
58 | /** |
59 | * Canonizes a MAC, EUI, ELI, or IPv6-Link-Local address |
59 | * Canonizes a MAC, EUI, ELI, AAI, SAI, or IPv6-Link-Local address |
60 | * @param string $mac MAC, EUI, ELI, or IPv6-Link-Local Address |
60 | * @param string $mac MAC, EUI, ELI, or IPv6-Link-Local Address |
61 | * @param string $delimiter Desired delimiter for inserting between each octet |
61 | * @param string $delimiter Desired delimiter for inserting between each octet |
62 | * @return string|false The canonized address (Note: IPv6-Link-Local becomes EUI-64) |
62 | * @return string|false The canonized address (Note: IPv6-Link-Local becomes EUI-64) |
63 | */ |
63 | */ |
64 | function mac_canonize(string $mac, string $delimiter="-") { |
64 | function mac_canonize(string $mac, string $delimiter="-") { |
Line 119... | Line 119... | ||
119 | if ($n == 0) continue; |
119 | if ($n == 0) continue; |
120 | else if ($n == 1) $out .= sprintf("%-32s %s\n", "Address of registrant:", $y); |
120 | else if ($n == 1) $out .= sprintf("%-32s %s\n", "Address of registrant:", $y); |
121 | else if ($n >= 2) $out .= sprintf("%-32s %s\n", "", $y); |
121 | else if ($n >= 2) $out .= sprintf("%-32s %s\n", "", $y); |
122 | } |
122 | } |
123 | 123 | ||
124 | // TODO: also print the date of last update of the OUI files |
- | |
125 | - | ||
126 | return $out; |
124 | return $out; |
127 | } |
125 | } |
128 | 126 | ||
129 | return false; |
127 | return false; |
130 | } |
128 | } |
Line 136... | Line 134... | ||
136 | */ |
134 | */ |
137 | function eui64_to_eui48(string $eui64) { |
135 | function eui64_to_eui48(string $eui64) { |
138 | if (!mac_valid($eui64)) return false; |
136 | if (!mac_valid($eui64)) return false; |
139 | $eui64 = mac_canonize($eui64, ''); |
137 | $eui64 = mac_canonize($eui64, ''); |
140 | if (eui_bits($eui64) == 48) return mac_canonize($eui64); |
138 | if (eui_bits($eui64) == 48) return mac_canonize($eui64); |
141 | if ($eui64[1] == 'A') return false; // do not allow ELI-64 |
139 | if (($eui64[1] != '0') && ($eui64[1] != '4') && ($eui64[1] != '8') && ($eui64[1] != 'C')) return false; // only allow EUI |
142 | 140 | ||
143 | if (substr($eui64, 6, 4) == 'FFFF') { |
141 | if (substr($eui64, 6, 4) == 'FFFF') { |
144 | // EUI-64 to MAC-48 |
142 | // EUI-64 to MAC-48 |
145 | return mac_canonize(substr($eui64, 0, 6).substr($eui64, 10, 6)); |
143 | return mac_canonize(substr($eui64, 0, 6).substr($eui64, 10, 6)); |
146 | } else if (substr($eui64, 6, 4) == 'FFFE') { |
144 | } else if (substr($eui64, 6, 4) == 'FFFE') { |
Line 166... | Line 164... | ||
166 | // Note: MAC-48 is used for network hardware; EUI-48 is used to identify other devices and software. |
164 | // Note: MAC-48 is used for network hardware; EUI-48 is used to identify other devices and software. |
167 | // MAC48-to-EUI64 Encapsulation uses 0xFFFF middle part |
165 | // MAC48-to-EUI64 Encapsulation uses 0xFFFF middle part |
168 | if (!mac_valid($mac48)) return false; |
166 | if (!mac_valid($mac48)) return false; |
169 | $mac48 = mac_canonize($mac48, ''); |
167 | $mac48 = mac_canonize($mac48, ''); |
170 | if (eui_bits($mac48) == 64) return mac_canonize($mac48); |
168 | if (eui_bits($mac48) == 64) return mac_canonize($mac48); |
171 | if ($mac48[1] == 'A') return false; // do not allow ELI-48 |
169 | if (($mac48[1] != '0') && ($mac48[1] != '4') && ($mac48[1] != '8') && ($mac48[1] != 'C')) return false; // only allow EUI |
172 | 170 | ||
173 | $eui64 = substr($mac48, 0, 6).'FFFF'.substr($mac48, 6, 6); |
171 | $eui64 = substr($mac48, 0, 6).'FFFF'.substr($mac48, 6, 6); |
174 | return mac_canonize($eui64); |
172 | return mac_canonize($eui64); |
175 | } |
173 | } |
176 | 174 | ||
Line 183... | Line 181... | ||
183 | // Note: MAC-48 is used for network hardware; EUI-48 is used to identify other devices and software. |
181 | // Note: MAC-48 is used for network hardware; EUI-48 is used to identify other devices and software. |
184 | // EUI48-to-EUI64 Encapsulation uses 0xFFFF middle part |
182 | // EUI48-to-EUI64 Encapsulation uses 0xFFFF middle part |
185 | if (!mac_valid($eui48)) return false; |
183 | if (!mac_valid($eui48)) return false; |
186 | $eui48 = mac_canonize($eui48, ''); |
184 | $eui48 = mac_canonize($eui48, ''); |
187 | if (eui_bits($eui48) == 64) return mac_canonize($eui48); |
185 | if (eui_bits($eui48) == 64) return mac_canonize($eui48); |
188 | if ($eui48[1] == 'A') return false; // do not allow ELI-48 |
186 | if (($eui48[1] != '0') && ($eui48[1] != '4') && ($eui48[1] != '8') && ($eui48[1] != 'C')) return false; // only allow EUI |
189 | 187 | ||
190 | $eui64 = substr($eui48, 0, 6).'FFFE'.substr($eui48, 6, 6); |
188 | $eui64 = substr($eui48, 0, 6).'FFFE'.substr($eui48, 6, 6); |
191 | return mac_canonize($eui64); |
189 | return mac_canonize($eui64); |
192 | } |
190 | } |
193 | 191 | ||
Line 200... | Line 198... | ||
200 | // Note: MAC-48 is used for network hardware; EUI-48 is used to identify other devices and software. |
198 | // Note: MAC-48 is used for network hardware; EUI-48 is used to identify other devices and software. |
201 | // EUI48-to-ModifiedEUI64 Encapsulation uses 0xFFFE middle part (SIC! This was a mistake by IETF, since it should actually be 0xFFFF!) |
199 | // EUI48-to-ModifiedEUI64 Encapsulation uses 0xFFFE middle part (SIC! This was a mistake by IETF, since it should actually be 0xFFFF!) |
202 | if (!mac_valid($eui48)) return false; |
200 | if (!mac_valid($eui48)) return false; |
203 | $eui48 = mac_canonize($eui48, ''); |
201 | $eui48 = mac_canonize($eui48, ''); |
204 | if (eui_bits($eui48) == 64) return mac_canonize($eui48); |
202 | if (eui_bits($eui48) == 64) return mac_canonize($eui48); |
205 | if ($eui48[1] == 'A') return false; // do not allow ELI-48 |
203 | if (($eui48[1] != '0') && ($eui48[1] != '4') && ($eui48[1] != '8') && ($eui48[1] != 'C')) return false; // only allow EUI |
206 | 204 | ||
207 | $eui64 = substr($eui48, 0, 6).'FFFE'.substr($eui48, 6, 6); |
205 | $eui64 = substr($eui48, 0, 6).'FFFE'.substr($eui48, 6, 6); |
208 | 206 | ||
209 | $eui64[1] = dechex(hexdec($eui64[1]) | 2); // flip seventh bit |
207 | $eui64[1] = dechex(hexdec($eui64[1]) | 2); // flip seventh bit |
210 | 208 | ||
Line 255... | Line 253... | ||
255 | $mac = str_pad($mac, 16, '0', STR_PAD_LEFT); |
253 | $mac = str_pad($mac, 16, '0', STR_PAD_LEFT); |
256 | return strtolower('fe80::'.substr($mac,0, 4).':'.substr($mac,4, 4).':'.substr($mac,8, 4).':'.substr($mac,12, 4)); |
254 | return strtolower('fe80::'.substr($mac,0, 4).':'.substr($mac,4, 4).':'.substr($mac,8, 4).':'.substr($mac,12, 4)); |
257 | } |
255 | } |
258 | 256 | ||
259 | /** |
257 | /** |
260 | * Prints information about an IPv6-Link-Local address, MAC, EUI, or ELI. |
- | |
261 | * @param string $mac IPv6-Link-Local address, MAC, EUI, or ELI |
258 | * @param string $mac |
262 | * @return void |
259 | * @return string |
263 | * @throws Exception |
260 | * @throws Exception |
264 | */ |
261 | */ |
265 | function decode_mac(string $mac) { |
262 | function mac_type(string $mac): string { |
266 | echo sprintf("%-32s %s\n", "Input:", $mac); |
- | |
267 | - | ||
268 | // Format MAC for machine readability |
263 | // Format MAC for machine readability |
269 | $mac = mac_canonize($mac, ''); |
264 | $mac = mac_canonize($mac, ''); |
270 | 265 | ||
- | 266 | /** |
|
- | 267 | * |
|
- | 268 | * ZYXM |
|
- | 269 | * 0 0000 EUI (OUI) |
|
- | 270 | * 1 0001 |
|
- | 271 | * 2 0010 AAI |
|
- | 272 | * 3 0011 |
|
- | 273 | * 4 0100 EUI (OUI) |
|
- | 274 | * 5 0101 |
|
- | 275 | * 6 0110 Reserved |
|
- | 276 | * 7 0111 |
|
- | 277 | * 8 1000 EUI (OUI) |
|
- | 278 | * 9 1001 |
|
- | 279 | * A 1010 ELI (CID) |
|
- | 280 | * B 1011 |
|
- | 281 | * C 1100 EUI (OUI) |
|
- | 282 | * D 1101 |
|
- | 283 | * E 1110 SAI |
|
- | 284 | * F 1111 |
|
- | 285 | * |
|
- | 286 | */ |
|
- | 287 | ||
271 | $type = ''; |
288 | $type = ''; |
272 | if ($mac[1] == 'A') { |
- | |
273 | // An ELI is based on a CID-24 |
- | |
274 | // A CID has ZYXM bits set to 1010 (0b1010 = 0xA) |
- | |
275 | // Since X=1 (U/L=1), the CID cannot be used to form a universal UAA MAC (only a local LAA MAC) |
- | |
276 | $type = 'ELI-'.eui_bits($mac);; |
- | |
277 | } else { |
- | |
278 | $tmp = ipv6linklocal_to_mac48($mac); |
289 | $tmp = ipv6linklocal_to_mac48($mac); |
279 | if ($tmp !== false) { |
290 | if ($tmp !== false) { |
280 | $mac = $tmp; |
291 | $mac = $tmp; |
281 | $type = 'IPv6-Link-Local'; |
292 | $type = 'IPv6-Link-Local'; |
282 | } |
293 | } |
283 | if (!mac_valid($mac)) throw new Exception("Invalid MAC address"); |
294 | if (!mac_valid($mac)) throw new Exception("Invalid MAC address"); |
284 | if ($tmp === false) { |
295 | if ($tmp === false) { |
- | 296 | if ($mac[1] == '2') { |
|
- | 297 | /* |
|
- | 298 | * AAI: Administratively Assigned Identifier |
|
- | 299 | * Administrators who wish to assign local MAC addresses in an |
|
- | 300 | * arbitrary fashion (for example, randomly) and yet maintain |
|
- | 301 | * compatibility with other assignment protocols operating under the |
|
- | 302 | * SLAP on the same LAN may assign a local MAC address as AAI. |
|
- | 303 | */ |
|
- | 304 | $type = 'AAI-' . eui_bits($mac).' (Administratively Assigned Identifier)'; |
|
- | 305 | } else if ($mac[1] == '6') { |
|
- | 306 | /* |
|
- | 307 | * Reserved |
|
- | 308 | * may be administratively used and assigned in accordance with the |
|
- | 309 | * considerations specified for AAI usage, without effect on SLAP |
|
- | 310 | * assignments. However, administrators should be cognizant of |
|
- | 311 | * possible future specifications… that would render administrative |
|
- | 312 | * assignment incompatible with the SLAP. |
|
- | 313 | */ |
|
- | 314 | $type = 'Reserved-' . eui_bits($mac); |
|
- | 315 | } else if ($mac[1] == 'A') { |
|
- | 316 | /* |
|
- | 317 | * ELI: Extended Local Identifier |
|
- | 318 | * An ELI is based on a 24 bit CID |
|
- | 319 | * A CID has ZYXM bits set to 1010 (0b1010 = 0xA) |
|
- | 320 | * Since X=1 (U/L=1), the CID cannot be used to form a universal UAA MAC (only a local LAA MAC) |
|
- | 321 | */ |
|
- | 322 | $type = 'ELI-' . eui_bits($mac).' (Extended Local Identifier)'; |
|
- | 323 | } else if ($mac[1] == 'E') { |
|
- | 324 | /* |
|
- | 325 | * SAI: Standard Assigned Identifier |
|
- | 326 | * Specification of the use of the SAI quadrant for SLAP address |
|
- | 327 | * assignments is reserved for the standard forthcoming from IEEE |
|
- | 328 | * P802.1CQ. |
|
- | 329 | * An SAI is assigned by a protocol specified in an IEEE 802 standard. |
|
- | 330 | * Multiple protocols for assigning SAI may be specified within various |
|
- | 331 | * IEEE 802 standards. Coexistence of such protocols may be supported |
|
- | 332 | * by restricting each to assignments within a subspace of SAI space. |
|
- | 333 | * In some cases, an SAI assignment protocol may assign the SAI to convey |
|
- | 334 | * specific information. Such information may be interpreted by receivers |
|
- | 335 | * and bridges that recognize the specific SAI assignment protocol, as |
|
- | 336 | * identified by the subspace of the SAI. The functionality of receivers |
|
- | 337 | * and bridges that do not recognize the protocol is not affected. |
|
- | 338 | */ |
|
- | 339 | $type = 'SAI-' . eui_bits($mac).' (Standard Assigned Identifier)'; |
|
- | 340 | } else if ((hexdec($mac[1])&1) == 1) { |
|
- | 341 | $type = 'Multicast MAC-'.eui_bits($mac); |
|
- | 342 | } else if (($mac[1] == '0') || ($mac[1] == '4') || ($mac[1] == '8') || ($mac[1] == 'C')) { |
|
- | 343 | /* |
|
- | 344 | * Extended Unique Identifier |
|
- | 345 | * Based on an OUI-24, OUI-28, or OUI-36 |
|
- | 346 | */ |
|
285 | if (eui_bits($mac) == 48) { |
347 | if (eui_bits($mac) == 48) { |
- | 348 | // The name "MAC-48" has been deprecated by IEEE |
|
286 | $type = 'MAC-48 (network hardware) or EUI-48 (other devices and software)'; |
349 | //$type = 'MAC-48 (network hardware) or EUI-48 (other devices and software)'; |
- | 350 | $type = 'EUI-48 (Extended Unique Identifier)'; |
|
287 | } else if (eui_bits($mac) == 64) { |
351 | } else if (eui_bits($mac) == 64) { |
288 | if (substr($mac,6,4) == 'FFFE') { |
352 | if (substr($mac, 6, 4) == 'FFFE') { |
289 | if ((hexdec($mac[1])&2) == 2) { |
353 | if ((hexdec($mac[1]) & 2) == 2) { |
290 | $type = 'EUI-64 (MAC/EUI-48 to Modified EUI-64 Encapsulation)'; |
354 | $type = 'EUI-64 (Extended Unique Identifier, MAC/EUI-48 to Modified EUI-64 Encapsulation)'; |
291 | } else { |
355 | } else { |
292 | $type = 'EUI-64 (EUI-48 to EUI-64 Encapsulation)'; |
356 | $type = 'EUI-64 (Extended Unique Identifier, EUI-48 to EUI-64 Encapsulation)'; |
293 | } |
357 | } |
294 | } else if (substr($mac,6,4) == 'FFFF') { |
358 | } else if (substr($mac, 6, 4) == 'FFFF') { |
295 | $type = 'EUI-64 (MAC-48 to EUI-64 Encapsulation)'; |
359 | $type = 'EUI-64 (Extended Unique Identifier, MAC-48 to EUI-64 Encapsulation)'; |
296 | } else { |
360 | } else { |
297 | $type = 'EUI-64 (Regular)'; |
361 | $type = 'EUI-64 (Extended Unique Identifier)'; |
298 | } |
362 | } |
299 | } else { |
363 | } else { |
300 | assert(false); /** @phpstan-ignore-line */ |
364 | assert(false); /** @phpstan-ignore-line */ |
301 | } |
365 | } |
302 | } |
366 | } |
303 | } |
367 | } |
- | 368 | return $type; |
|
- | 369 | } |
|
- | 370 | ||
- | 371 | /** |
|
- | 372 | * Prints information about an IPv6-Link-Local address, MAC, EUI, ELI, AAI, or SAI. |
|
- | 373 | * @param string $mac IPv6-Link-Local address, MAC, EUI, ELI, AAI, or SAI |
|
- | 374 | * @return void |
|
- | 375 | * @throws Exception |
|
- | 376 | */ |
|
- | 377 | function decode_mac(string $mac) { |
|
- | 378 | echo sprintf("%-32s %s\n", "Input:", $mac); |
|
- | 379 | ||
- | 380 | // Format MAC for machine readability |
|
- | 381 | $mac = mac_canonize($mac, ''); |
|
- | 382 | ||
- | 383 | $type = mac_type($mac); |
|
304 | echo sprintf("%-32s %s\n", "Type:", $type); |
384 | echo sprintf("%-32s %s\n", "Type:", $type); |
305 | 385 | ||
306 | echo "\n"; |
386 | echo "\n"; |
307 | 387 | ||
308 | // Show various representations |
388 | // Show various representations |
309 | if ($mac[1] == 'A') { |
389 | if ($mac[1] == 'A') { |
310 | // Note: There does not seem to exist an algorithm for converting ELI-48 <=> ELI-64 |
390 | // Note: There does not seem to exist an algorithm for encapsulating/converting ELI-48 <=> ELI-64 |
311 | echo sprintf("%-32s %s\n", "ELI-".eui_bits($mac).":", mac_canonize($mac)); |
391 | echo sprintf("%-32s %s\n", "ELI-".eui_bits($mac).":", mac_canonize($mac)); |
312 | $mac48 = eui64_to_eui48($mac); |
392 | $mac48 = eui64_to_eui48($mac); |
313 | echo sprintf("%-32s %s\n", "MAC-48 (Local):", (eui_bits($mac48) != 48) ? 'Not available' : $mac48); |
393 | echo sprintf("%-32s %s\n", "MAC-48 (Local):", (eui_bits($mac48) != 48) ? 'Not available' : $mac48); |
314 | } else { |
394 | } else if (($mac[1] == '0') || ($mac[1] == '4') || ($mac[1] == '8') || ($mac[1] == 'C')) { |
315 | $eui48 = eui64_to_eui48($mac); |
395 | $eui48 = eui64_to_eui48($mac); |
316 | echo sprintf("%-32s %s\n", "EUI-48 or MAC-48:", (eui_bits($eui48) != 48) ? 'Not available' : $eui48); |
396 | echo sprintf("%-32s %s\n", "EUI-48 or MAC-48:", (eui_bits($eui48) != 48) ? 'Not available' : $eui48); |
317 | if (eui_bits($mac) == 48) { |
397 | if (eui_bits($mac) == 48) { |
318 | $eui64 = mac48_to_eui64($mac); |
398 | $eui64 = mac48_to_eui64($mac); |
319 | echo sprintf("%-32s %s\n", "EUI-64:", ((eui_bits($eui64) != 64) ? 'Not available' : $eui64).' (MAC-48 to EUI-64 Encapsulation)'); |
399 | echo sprintf("%-32s %s\n", "EUI-64:", ((eui_bits($eui64) != 64) ? 'Not available' : $eui64).' (MAC-48 to EUI-64 Encapsulation)'); |