Subversion Repositories uuid_mac_utils

Compare Revisions

Regard whitespace Rev 16 → Rev 17

/trunk/includes/mac_utils.inc.php
3,7 → 3,7
/*
* MAC (EUI-48 and EUI-64) utils for PHP
* Copyright 2017 - 2023 Daniel Marschall, ViaThinkSoft
* Version 2023-04-29
* Version 2023-05-01
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
21,11 → 21,12
const IEEE_MAC_REGISTRY = __DIR__ . '/../web-data';
 
/**
* @param string $mac
* @return bool
* Checks if a MAC, EUI, or IPv6-LinkLocal address is valid
* @param string $mac MAC, EUI, or IPv6-LinkLocal Address
* @return bool True if it is valid
*/
function mac_valid(string $mac): bool {
$tmp = ipv6linklocal_to_eui64($mac);
$tmp = ipv6linklocal_to_mac48($mac);
if ($tmp !== false) $mac = $tmp;
 
$mac = str_replace(array('-', ':'), '', $mac);
39,14 → 40,26
}
 
/**
* Returns the amount of bits of a MAC or EUI
* @param string $mac
* @param string $delimiter
* @return string|false
* @return false|int
*/
function eui_bits(string $mac) {
if (!mac_valid($mac)) return false;
$mac = mac_canonize($mac, '');
return (int)(strlen($mac)*4);
}
 
/**
* Canonizes a MAC, EUI, or IPv6-LinkLocal address
* @param string $mac MAC, EUI, or IPv6-LinkLocal Address
* @param string $delimiter Desired delimiter for inserting between each octet
* @return string|false The canonized address (Note: IPv6-Linklocal becomes EUI-64)
*/
function mac_canonize(string $mac, string $delimiter="-") {
if (!mac_valid($mac)) return false;
 
$tmp = ipv6linklocal_to_eui64($mac);
$tmp = ipv6linklocal_to_mac48($mac);
if ($tmp !== false) $mac = $tmp;
 
$mac = strtoupper($mac);
112,8 → 125,9
}
 
/**
* Try to Decapsulate EUI-64 into MAC-48 or EUI-48
* @param string $eui64
* @return false|string If EUI-64 can be converted into EUI-48 (if it has FFFE in the middle), returns EUI-48, otherwise returns EUI-64. On invalid input, return false.
* @return false|string If EUI-64 can be converted into EUI-48, returns EUI-48, otherwise returns EUI-64. On invalid input, return false.
*/
function eui64_to_eui48(string $eui64) {
if (!mac_valid($eui64)) return false;
120,18 → 134,47
$eui64 = mac_canonize($eui64, '');
if (eui_bits($eui64) == 48) return mac_canonize($eui64);
 
if (substr($eui64, 6, 4) == 'FFFE') {
if (substr($eui64, 6, 4) == 'FFFF') {
// EUI-64 to MAC-48
return mac_canonize(substr($eui64, 0, 6).substr($eui64, 10, 6));
} else if (substr($eui64, 6, 4) == 'FFFE') {
if ((hexdec($eui64[1])&2) == 2) {
// Modified EUI-64 to MAC/EUI-48
$eui64[1] = dechex(hexdec($eui64[1])&253); // remove bit
return mac_canonize(substr($eui64, 0, 6).substr($eui64, 10, 6));
} else {
// EUI-64 to EUI-48
return mac_canonize(substr($eui64, 0, 6).substr($eui64, 10, 6));
}
} else {
return mac_canonize($eui64);
}
}
 
/**
* @param string $eui48
* @return false|string
* MAC-48 to EUI-64 Encapsulation
* @param string $mac48 MAC-48 address
* @return false|string EUI-64 address
*/
function mac48_to_eui64(string $mac48) {
// Note: MAC-48 is used for network hardware; EUI-48 is used to identify other devices and software.
// MAC48-to-EUI64 Encapsulation uses 0xFFFF middle part
if (!mac_valid($mac48)) return false;
$mac48 = mac_canonize($mac48, '');
if (eui_bits($mac48) == 64) return mac_canonize($mac48);
 
$eui64 = substr($mac48, 0, 6).'FFFF'.substr($mac48, 6, 6);
return mac_canonize($eui64);
}
 
/**
* EUI-48 to EUI-64 Encapsulation
* @param string $eui48 EUI-48 address
* @return false|string EUI-64 address
*/
function eui48_to_eui64(string $eui48) {
// Note: MAC-48 is used for network hardware; EUI-48 is used to identify other devices and software.
// EUI48-to-EUI64 Encapsulation uses 0xFFFF middle part
if (!mac_valid($eui48)) return false;
$eui48 = mac_canonize($eui48, '');
if (eui_bits($eui48) == 64) return mac_canonize($eui48);
141,10 → 184,30
}
 
/**
* @param string $ipv6
* @return false|string
* MAC/EUI-48 to Modified EUI-64 Encapsulation
* @param string $eui48 MAC-48 or EUI-48 address
* @return false|string Modified EUI-64 address
*/
function ipv6linklocal_to_eui64(string $ipv6) {
function maceui48_to_modeui64(string $eui48) {
// Note: MAC-48 is used for network hardware; EUI-48 is used to identify other devices and software.
// EUI48-to-ModifiedEUI64 Encapsulation uses 0xFFFE middle part (SIC! This was a mistake by IETF, since it should actually be 0xFFFF!)
if (!mac_valid($eui48)) return false;
$eui48 = mac_canonize($eui48, '');
if (eui_bits($eui48) == 64) return mac_canonize($eui48);
 
$eui64 = substr($eui48, 0, 6).'FFFE'.substr($eui48, 6, 6);
 
$eui64[1] = dechex(hexdec($eui64[1]) | 2); // flip seventh bit
 
return mac_canonize($eui64);
}
 
/**
* Try to convert IPv6-LinkLocal address to MAC-48
* @param string $ipv6 IPv6-LinkLocal address
* @return false|string MAC-48 (or IPv6 if it was no LinkLocal address, or Modified EUI-64 if it decapsulation failed)
*/
function ipv6linklocal_to_mac48(string $ipv6) {
// https://stackoverflow.com/questions/12095835/quick-way-of-expanding-ipv6-addresses-with-php (modified)
$tmp = inet_pton($ipv6);
if ($tmp === false) return false;
158,40 → 221,35
if ($cnt == 0) return false;
 
// Set LAA to UAA again
$mac[1] = dechex(hexdec($mac[1]) & 253);
$mac_uaa_64 = $mac;
$mac_uaa_64[1] = dechex(hexdec($mac_uaa_64[1]) & 253);
 
return eui64_to_eui48($mac);
$mac_uaa_48 = eui64_to_eui48($mac_uaa_64);
if (eui_bits($mac_uaa_48) == 48) {
return $mac_uaa_48; // Output MAC-48 (UAA)
} else {
return $mac; // Failed decapsulation; output Modified EUI-64 instead
}
 
/**
* @param string $mac
* @return false|int
*/
function eui_bits(string $mac) {
if (!mac_valid($mac)) return false;
$mac = mac_canonize($mac, '');
return (int)(strlen($mac)*4);
}
 
/**
* Converts MAC-48 or EUI-48 to IPv6-LinkLocal (based on Modified EUI-64)
* @param string $mac
* @return false|string
*/
function eui_to_ipv6linklocal(string $mac) {
function maceui_to_ipv6linklocal(string $mac) {
if (!mac_valid($mac)) return false;
if (eui_bits($mac) == 48) {
$mac = eui48_to_eui64($mac);
$mac = maceui48_to_modeui64($mac);
}
$mac = mac_canonize($mac, '');
 
$mac[1] = dechex(hexdec($mac[1]) | 2);
 
$mac = str_pad($mac, 16, '0', STR_PAD_LEFT);
return strtolower('fe80::'.substr($mac,0, 4).':'.substr($mac,4, 4).':'.substr($mac,8, 4).':'.substr($mac,12, 4));
}
 
/**
* @param string $mac
* Prints information about an IPv6-LinkLocal address, MAC, or EUI.
* @param string $mac IPv6-LinkLocal address, MAC, or EUI
* @return void
* @throws Exception
*/
200,8 → 258,11
 
echo sprintf("%-32s %s\n", "Input:", $mac);
 
// Format MAC for machine readability
$mac = mac_canonize($mac, '');
 
$type = '';
$tmp = ipv6linklocal_to_eui64($mac);
$tmp = ipv6linklocal_to_mac48($mac);
if ($tmp !== false) {
$mac = $tmp;
$type = 'IPv6-Link-Local';
208,23 → 269,44
}
if (!mac_valid($mac)) throw new Exception("Invalid MAC address");
if ($tmp === false) {
// Size
$type = eui_bits($mac)==48 ? 'EUI-48 (6 Byte)' : 'EUI-64 (8 Byte)';
if (eui_bits($mac) == 48) {
$type = 'MAC-48 (network hardware) or EUI-48 (other devices and software)';
} else if (eui_bits($mac) == 64) {
if (substr($mac,6,4) == 'FFFE') {
if ((hexdec($mac[1])&2) == 2) {
$type = 'EUI-64 (MAC/EUI-48 to Modified EUI-64 Encapsulation)';
} else {
$type = 'EUI-64 (EUI-48 to EUI-64 Encapsulation)';
}
} else if (substr($mac,6,4) == 'FFFF') {
$type = 'EUI-64 (MAC-48 to EUI-64 Encapsulation)';
} else {
$type = 'EUI-64 (Regular)';
}
} else {
assert(false); /** @phpstan-ignore-line */
}
}
echo sprintf("%-32s %s\n", "Type:", $type);
 
echo "\n";
 
// Format MAC
$mac = mac_canonize($mac, '');
 
// Show various representations
$eui48 = eui64_to_eui48($mac);
echo sprintf("%-32s %s\n", "EUI-48:", (eui_bits($eui48) != 48) ? 'Not available' : $eui48);
if (eui_bits($mac) == 48) {
$eui64 = mac48_to_eui64($mac);
echo sprintf("%-32s %s\n", "EUI-64:", ((eui_bits($eui64) != 64) ? 'Not available' : $eui64).' (MAC-48 to EUI-64 Encapsulation)');
$eui64 = eui48_to_eui64($mac);
echo sprintf("%-32s %s\n", "EUI-64:", (eui_bits($eui64) != 64) ? 'Not available' : $eui64);
$ipv6 = eui_to_ipv6linklocal($mac);
echo sprintf("%-32s %s\n", "", ((eui_bits($eui64) != 64) ? 'Not available' : $eui64).' (EUI-48 to EUI-64 Encapsulation)');
$eui64 = maceui48_to_modeui64($mac);
echo sprintf("%-32s %s\n", "", ((eui_bits($eui64) != 64) ? 'Not available' : $eui64).' (MAC/EUI-48 to Modified EUI-64 Encapsulation)');
$ipv6 = maceui_to_ipv6linklocal($mac);
echo sprintf("%-32s %s\n", "IPv6 link local address:", $ipv6);
} else {
$eui64 = mac_canonize($mac);
echo sprintf("%-32s %s\n", "EUI-64:", $eui64);
}
 
// Vergabestelle
$ul = hexdec($mac[1]) & 2; // Bit #LSB+1 of Byte 1
237,15 → 319,15
echo sprintf("%-32s %s\n", "Transmission type (I/G flag):", $ig_);
 
// Query IEEE registries
// TODO: gilt OUI nur bei Individual UAA?
if (count(glob(IEEE_MAC_REGISTRY.'/*.txt')) > 0) {
// TODO: gilt OUI nur bei Individual UAA? For LAA, should we convert to UAA and then query the registry?
if (count(glob(IEEE_MAC_REGISTRY.DIRECTORY_SEPARATOR.'*.txt')) > 0) {
if (
# The IEEE Registration Authority distinguishes between IABs and OUI-36 values. Both are 36-bit values which may be used to generate EUI-48 values, but IABs may not be used to generate EUI-64 values.[6]
# Note: The Individual Address Block (IAB) is an inactive registry activity, which has been replaced by the MA-S registry product as of January 1, 2014.
($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . '/iab.txt', 'IAB', $mac)) ||
($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . '/oui36.txt', 'OUI-36 (MA-S)', $mac)) ||
($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . '/mam.txt', 'OUI-28 (MA-M)', $mac)) ||
($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . '/oui.txt', 'OUI-24 (MA-L)', $mac))
($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . DIRECTORY_SEPARATOR . 'iab.txt', 'IAB', $mac)) ||
($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . DIRECTORY_SEPARATOR . 'oui36.txt', 'OUI-36 (MA-S)', $mac)) ||
($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . DIRECTORY_SEPARATOR . 'mam.txt', 'OUI-28 (MA-M)', $mac)) ||
($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . DIRECTORY_SEPARATOR . 'oui.txt', 'OUI-24 (MA-L)', $mac))
) {
echo $x;
}