Subversion Repositories uuid_mac_utils

Rev

Rev 16 | Rev 18 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 16 Rev 17
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-04-29
6
 * Version 2023-05-01
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 19... Line 19...
19
 */
19
 */
20
 
20
 
21
const IEEE_MAC_REGISTRY = __DIR__ . '/../web-data';
21
const IEEE_MAC_REGISTRY = __DIR__ . '/../web-data';
22
 
22
 
23
/**
23
/**
-
 
24
 * Checks if a MAC, EUI, or IPv6-LinkLocal address is valid
24
 * @param string $mac
25
 * @param string $mac MAC, EUI, or IPv6-LinkLocal Address
25
 * @return bool
26
 * @return bool True if it is valid
26
 */
27
 */
27
function mac_valid(string $mac): bool {
28
function mac_valid(string $mac): bool {
28
        $tmp = ipv6linklocal_to_eui64($mac);
29
        $tmp = ipv6linklocal_to_mac48($mac);
29
        if ($tmp !== false) $mac = $tmp;
30
        if ($tmp !== false) $mac = $tmp;
30
 
31
 
31
        $mac = str_replace(array('-', ':'), '', $mac);
32
        $mac = str_replace(array('-', ':'), '', $mac);
32
        $mac = strtoupper($mac);
33
        $mac = strtoupper($mac);
33
 
34
 
Line 37... Line 38...
37
 
38
 
38
        return ($mac === '');
39
        return ($mac === '');
39
}
40
}
40
 
41
 
41
/**
42
/**
-
 
43
 * Returns the amount of bits of a MAC or EUI
42
 * @param string $mac
44
 * @param string $mac
43
 * @param string $delimiter
45
 * @return false|int
-
 
46
 */
-
 
47
function eui_bits(string $mac) {
-
 
48
        if (!mac_valid($mac)) return false;
-
 
49
        $mac = mac_canonize($mac, '');
44
 * @return string|false
50
        return (int)(strlen($mac)*4);
-
 
51
}
-
 
52
 
-
 
53
/**
-
 
54
 * Canonizes a MAC, EUI, or IPv6-LinkLocal address
-
 
55
 * @param string $mac MAC, EUI, or IPv6-LinkLocal Address
-
 
56
 * @param string $delimiter Desired delimiter for inserting between each octet
-
 
57
 * @return string|false The canonized address (Note: IPv6-Linklocal becomes EUI-64)
45
 */
58
 */
46
function mac_canonize(string $mac, string $delimiter="-") {
59
function mac_canonize(string $mac, string $delimiter="-") {
47
        if (!mac_valid($mac)) return false;
60
        if (!mac_valid($mac)) return false;
48
 
61
 
49
        $tmp = ipv6linklocal_to_eui64($mac);
62
        $tmp = ipv6linklocal_to_mac48($mac);
50
        if ($tmp !== false) $mac = $tmp;
63
        if ($tmp !== false) $mac = $tmp;
51
 
64
 
52
        $mac = strtoupper($mac);
65
        $mac = strtoupper($mac);
53
        $mac = preg_replace('@[^0-9A-F]@', '', $mac);
66
        $mac = preg_replace('@[^0-9A-F]@', '', $mac);
54
        if ((strlen($mac) != 12) && (strlen($mac) != 16)) return false;
67
        if ((strlen($mac) != 12) && (strlen($mac) != 16)) return false;
Line 110... Line 123...
110
 
123
 
111
        return false;
124
        return false;
112
}
125
}
113
 
126
 
114
/**
127
/**
-
 
128
 * Try to Decapsulate EUI-64 into MAC-48 or EUI-48
115
 * @param string $eui64
129
 * @param string $eui64
116
 * @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.
130
 * @return false|string If EUI-64 can be converted into EUI-48, returns EUI-48, otherwise returns EUI-64. On invalid input, return false.
117
 */
131
 */
118
function eui64_to_eui48(string $eui64) {
132
function eui64_to_eui48(string $eui64) {
119
        if (!mac_valid($eui64)) return false;
133
        if (!mac_valid($eui64)) return false;
120
        $eui64 = mac_canonize($eui64, '');
134
        $eui64 = mac_canonize($eui64, '');
121
        if (eui_bits($eui64) == 48) return mac_canonize($eui64);
135
        if (eui_bits($eui64) == 48) return mac_canonize($eui64);
122
 
136
 
123
        if (substr($eui64, 6, 4) == 'FFFE') {
137
        if (substr($eui64, 6, 4) == 'FFFF') {
-
 
138
                // EUI-64 to MAC-48
-
 
139
                return mac_canonize(substr($eui64, 0, 6).substr($eui64, 10, 6));
-
 
140
        } else if (substr($eui64, 6, 4) == 'FFFE') {
-
 
141
                if ((hexdec($eui64[1])&2) == 2) {
-
 
142
                        // Modified EUI-64 to MAC/EUI-48
-
 
143
                        $eui64[1] = dechex(hexdec($eui64[1])&253); // remove bit
124
                return mac_canonize(substr($eui64, 0, 6).substr($eui64, 10, 6));
144
                        return mac_canonize(substr($eui64, 0, 6).substr($eui64, 10, 6));
125
        } else {
145
                } else {
-
 
146
                        // EUI-64 to EUI-48
-
 
147
                        return mac_canonize(substr($eui64, 0, 6).substr($eui64, 10, 6));
-
 
148
                }
-
 
149
        } else {
126
                return mac_canonize($eui64);
150
                return mac_canonize($eui64);
127
        }
151
        }
128
}
152
}
129
 
153
 
130
/**
154
/**
-
 
155
 * MAC-48 to EUI-64 Encapsulation
-
 
156
 * @param string $mac48 MAC-48 address
-
 
157
 * @return false|string EUI-64 address
-
 
158
 */
-
 
159
function mac48_to_eui64(string $mac48) {
-
 
160
        // Note: MAC-48 is used for network hardware; EUI-48 is used to identify other devices and software.
-
 
161
        //       MAC48-to-EUI64 Encapsulation uses 0xFFFF middle part
-
 
162
        if (!mac_valid($mac48)) return false;
-
 
163
        $mac48 = mac_canonize($mac48, '');
-
 
164
        if (eui_bits($mac48) == 64) return mac_canonize($mac48);
-
 
165
 
-
 
166
        $eui64 = substr($mac48, 0, 6).'FFFF'.substr($mac48, 6, 6);
-
 
167
        return mac_canonize($eui64);
-
 
168
}
-
 
169
 
-
 
170
/**
-
 
171
 * EUI-48 to EUI-64 Encapsulation
131
 * @param string $eui48
172
 * @param string $eui48 EUI-48 address
132
 * @return false|string
173
 * @return false|string EUI-64 address
133
 */
174
 */
134
function eui48_to_eui64(string $eui48) {
175
function eui48_to_eui64(string $eui48) {
-
 
176
        // Note: MAC-48 is used for network hardware; EUI-48 is used to identify other devices and software.
-
 
177
        //       EUI48-to-EUI64 Encapsulation uses 0xFFFF middle part
135
        if (!mac_valid($eui48)) return false;
178
        if (!mac_valid($eui48)) return false;
136
        $eui48 = mac_canonize($eui48, '');
179
        $eui48 = mac_canonize($eui48, '');
137
        if (eui_bits($eui48) == 64) return mac_canonize($eui48);
180
        if (eui_bits($eui48) == 64) return mac_canonize($eui48);
138
 
181
 
139
        $eui64 = substr($eui48, 0, 6).'FFFE'.substr($eui48, 6, 6);
182
        $eui64 = substr($eui48, 0, 6).'FFFE'.substr($eui48, 6, 6);
140
        return mac_canonize($eui64);
183
        return mac_canonize($eui64);
141
}
184
}
142
 
185
 
143
/**
186
/**
-
 
187
 * MAC/EUI-48 to Modified EUI-64 Encapsulation
144
 * @param string $ipv6
188
 * @param string $eui48 MAC-48 or EUI-48 address
145
 * @return false|string
189
 * @return false|string Modified EUI-64 address
-
 
190
 */
-
 
191
function maceui48_to_modeui64(string $eui48) {
-
 
192
        // Note: MAC-48 is used for network hardware; EUI-48 is used to identify other devices and software.
-
 
193
        //       EUI48-to-ModifiedEUI64 Encapsulation uses 0xFFFE middle part (SIC! This was a mistake by IETF, since it should actually be 0xFFFF!)
-
 
194
        if (!mac_valid($eui48)) return false;
-
 
195
        $eui48 = mac_canonize($eui48, '');
-
 
196
        if (eui_bits($eui48) == 64) return mac_canonize($eui48);
-
 
197
 
-
 
198
        $eui64 = substr($eui48, 0, 6).'FFFE'.substr($eui48, 6, 6);
-
 
199
 
-
 
200
        $eui64[1] = dechex(hexdec($eui64[1]) | 2); // flip seventh bit
-
 
201
 
-
 
202
        return mac_canonize($eui64);
-
 
203
}
-
 
204
 
-
 
205
/**
-
 
206
 * Try to convert IPv6-LinkLocal address to MAC-48
-
 
207
 * @param string $ipv6 IPv6-LinkLocal address
-
 
208
 * @return false|string MAC-48 (or IPv6 if it was no LinkLocal address, or Modified EUI-64 if it decapsulation failed)
146
 */
209
 */
147
function ipv6linklocal_to_eui64(string $ipv6) {
210
function ipv6linklocal_to_mac48(string $ipv6) {
148
        // https://stackoverflow.com/questions/12095835/quick-way-of-expanding-ipv6-addresses-with-php (modified)
211
        // https://stackoverflow.com/questions/12095835/quick-way-of-expanding-ipv6-addresses-with-php (modified)
149
        $tmp = inet_pton($ipv6);
212
        $tmp = inet_pton($ipv6);
150
        if ($tmp === false) return false;
213
        if ($tmp === false) return false;
151
        $hex = unpack("H*hex", $tmp);
214
        $hex = unpack("H*hex", $tmp);
152
        $ipv6 = substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hex['hex']), 0, -1);
215
        $ipv6 = substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hex['hex']), 0, -1);
Line 156... Line 219...
156
        $cnt = 0;
219
        $cnt = 0;
157
        $mac = preg_replace('@^fe80:0000:0000:0000:@i', '', $ipv6, -1, $cnt);
220
        $mac = preg_replace('@^fe80:0000:0000:0000:@i', '', $ipv6, -1, $cnt);
158
        if ($cnt == 0) return false;
221
        if ($cnt == 0) return false;
159
 
222
 
160
        // Set LAA to UAA again
223
        // Set LAA to UAA again
-
 
224
        $mac_uaa_64 = $mac;
161
        $mac[1] = dechex(hexdec($mac[1]) & 253);
225
        $mac_uaa_64[1] = dechex(hexdec($mac_uaa_64[1]) & 253);
162
 
226
 
163
        return eui64_to_eui48($mac);
227
        $mac_uaa_48 = eui64_to_eui48($mac_uaa_64);
-
 
228
        if (eui_bits($mac_uaa_48) == 48) {
-
 
229
                return $mac_uaa_48; // Output MAC-48 (UAA)
-
 
230
        } else {
-
 
231
                return $mac; // Failed decapsulation; output Modified EUI-64 instead
164
}
232
        }
165
 
-
 
166
/**
-
 
167
 * @param string $mac
-
 
168
 * @return false|int
-
 
169
 */
-
 
170
function eui_bits(string $mac) {
-
 
171
        if (!mac_valid($mac)) return false;
-
 
172
        $mac = mac_canonize($mac, '');
-
 
173
        return (int)(strlen($mac)*4);
-
 
174
}
233
}
175
 
234
 
176
/**
235
/**
-
 
236
 * Converts MAC-48 or EUI-48 to IPv6-LinkLocal (based on Modified EUI-64)
177
 * @param string $mac
237
 * @param string $mac
178
 * @return false|string
238
 * @return false|string
179
 */
239
 */
180
function eui_to_ipv6linklocal(string $mac) {
240
function maceui_to_ipv6linklocal(string $mac) {
181
        if (!mac_valid($mac)) return false;
241
        if (!mac_valid($mac)) return false;
182
        if (eui_bits($mac) == 48) {
242
        if (eui_bits($mac) == 48) {
183
                $mac = eui48_to_eui64($mac);
243
                $mac = maceui48_to_modeui64($mac);
184
        }
244
        }
185
        $mac = mac_canonize($mac, '');
245
        $mac = mac_canonize($mac, '');
186
 
-
 
187
        $mac[1] = dechex(hexdec($mac[1]) | 2);
-
 
188
 
-
 
189
        $mac = str_pad($mac, 16, '0', STR_PAD_LEFT);
246
        $mac = str_pad($mac, 16, '0', STR_PAD_LEFT);
190
        return strtolower('fe80::'.substr($mac,0, 4).':'.substr($mac,4, 4).':'.substr($mac,8, 4).':'.substr($mac,12, 4));
247
        return strtolower('fe80::'.substr($mac,0, 4).':'.substr($mac,4, 4).':'.substr($mac,8, 4).':'.substr($mac,12, 4));
191
}
248
}
192
 
249
 
193
/**
250
/**
-
 
251
 * Prints information about an IPv6-LinkLocal address, MAC, or EUI.
194
 * @param string $mac
252
 * @param string $mac IPv6-LinkLocal address, MAC, or EUI
195
 * @return void
253
 * @return void
196
 * @throws Exception
254
 * @throws Exception
197
 */
255
 */
198
function decode_mac(string $mac) {
256
function decode_mac(string $mac) {
199
        // Amazing website about MAC addresses: https://mac-address.alldatafeeds.com/faq#how-to-recognise-mac-address-application
257
        // Amazing website about MAC addresses: https://mac-address.alldatafeeds.com/faq#how-to-recognise-mac-address-application
200
 
258
 
201
        echo sprintf("%-32s %s\n", "Input:", $mac);
259
        echo sprintf("%-32s %s\n", "Input:", $mac);
202
 
260
 
-
 
261
        // Format MAC for machine readability
-
 
262
        $mac = mac_canonize($mac, '');
-
 
263
 
203
        $type = '';
264
        $type = '';
204
        $tmp = ipv6linklocal_to_eui64($mac);
265
        $tmp = ipv6linklocal_to_mac48($mac);
205
        if ($tmp !== false) {
266
        if ($tmp !== false) {
206
                $mac = $tmp;
267
                $mac = $tmp;
207
                $type = 'IPv6-Link-Local';
268
                $type = 'IPv6-Link-Local';
208
        }
269
        }
209
        if (!mac_valid($mac)) throw new Exception("Invalid MAC address");
270
        if (!mac_valid($mac)) throw new Exception("Invalid MAC address");
210
        if ($tmp === false) {
271
        if ($tmp === false) {
-
 
272
                if (eui_bits($mac) == 48) {
-
 
273
                        $type = 'MAC-48 (network hardware) or EUI-48 (other devices and software)';
-
 
274
                } else if (eui_bits($mac) == 64) {
-
 
275
                        if (substr($mac,6,4) == 'FFFE') {
-
 
276
                                if ((hexdec($mac[1])&2) == 2) {
-
 
277
                                        $type = 'EUI-64 (MAC/EUI-48 to Modified EUI-64 Encapsulation)';
211
                // Size
278
                                } else {
212
                $type = eui_bits($mac)==48 ? 'EUI-48 (6 Byte)' : 'EUI-64 (8 Byte)';
279
                                        $type = 'EUI-64 (EUI-48 to EUI-64 Encapsulation)';
-
 
280
                                }
-
 
281
                        } else if (substr($mac,6,4) == 'FFFF') {
-
 
282
                                $type = 'EUI-64 (MAC-48 to EUI-64 Encapsulation)';
-
 
283
                        } else {
-
 
284
                                $type = 'EUI-64 (Regular)';
-
 
285
                        }
-
 
286
                } else {
-
 
287
                        assert(false); /** @phpstan-ignore-line */
-
 
288
                }
213
        }
289
        }
214
        echo sprintf("%-32s %s\n", "Type:", $type);
290
        echo sprintf("%-32s %s\n", "Type:", $type);
215
 
291
 
216
        echo "\n";
292
        echo "\n";
217
 
293
 
218
        // Format MAC
-
 
219
        $mac = mac_canonize($mac, '');
-
 
220
 
-
 
221
        // Show various representations
294
        // Show various representations
222
        $eui48 = eui64_to_eui48($mac);
295
        $eui48 = eui64_to_eui48($mac);
223
        echo sprintf("%-32s %s\n", "EUI-48:", (eui_bits($eui48) != 48) ? 'Not available' : $eui48);
296
        echo sprintf("%-32s %s\n", "EUI-48:", (eui_bits($eui48) != 48) ? 'Not available' : $eui48);
-
 
297
        if (eui_bits($mac) == 48) {
-
 
298
                $eui64 = mac48_to_eui64($mac);
-
 
299
                echo sprintf("%-32s %s\n", "EUI-64:", ((eui_bits($eui64) != 64) ? 'Not available' : $eui64).' (MAC-48 to EUI-64 Encapsulation)');
224
        $eui64 = eui48_to_eui64($mac);
300
                $eui64 = eui48_to_eui64($mac);
225
        echo sprintf("%-32s %s\n", "EUI-64:", (eui_bits($eui64) != 64) ? 'Not available' : $eui64);
301
                echo sprintf("%-32s %s\n", "", ((eui_bits($eui64) != 64) ? 'Not available' : $eui64).' (EUI-48 to EUI-64 Encapsulation)');
-
 
302
                $eui64 = maceui48_to_modeui64($mac);
-
 
303
                echo sprintf("%-32s %s\n", "", ((eui_bits($eui64) != 64) ? 'Not available' : $eui64).' (MAC/EUI-48 to Modified EUI-64 Encapsulation)');
226
        $ipv6 = eui_to_ipv6linklocal($mac);
304
                $ipv6 = maceui_to_ipv6linklocal($mac);
227
        echo sprintf("%-32s %s\n", "IPv6 link local address:", $ipv6);
305
                echo sprintf("%-32s %s\n", "IPv6 link local address:", $ipv6);
-
 
306
        } else {
-
 
307
                $eui64 = mac_canonize($mac);
-
 
308
                echo sprintf("%-32s %s\n", "EUI-64:", $eui64);
-
 
309
        }
228
 
310
 
229
        // Vergabestelle
311
        // Vergabestelle
230
        $ul = hexdec($mac[1]) & 2; // Bit #LSB+1 of Byte 1
312
        $ul = hexdec($mac[1]) & 2; // Bit #LSB+1 of Byte 1
231
        $ul_ = ($ul == 0) ? '[0] Universally Administered Address (UAA)' : '[1] Locally Administered Address (LAA)';
313
        $ul_ = ($ul == 0) ? '[0] Universally Administered Address (UAA)' : '[1] Locally Administered Address (LAA)';
232
        echo sprintf("%-32s %s\n", "Administration type (U/L flag):", $ul_);
314
        echo sprintf("%-32s %s\n", "Administration type (U/L flag):", $ul_);
Line 235... Line 317...
235
        $ig = hexdec($mac[1]) & 1; // Bit #LSB+0 of Byte 1
317
        $ig = hexdec($mac[1]) & 1; // Bit #LSB+0 of Byte 1
236
        $ig_ = ($ig == 0) ? '[0] Individual (Unicast)' : '[1] Group (Multicast)';
318
        $ig_ = ($ig == 0) ? '[0] Individual (Unicast)' : '[1] Group (Multicast)';
237
        echo sprintf("%-32s %s\n", "Transmission type (I/G flag):", $ig_);
319
        echo sprintf("%-32s %s\n", "Transmission type (I/G flag):", $ig_);
238
 
320
 
239
        // Query IEEE registries
321
        // Query IEEE registries
240
        // TODO: gilt OUI nur bei Individual UAA?
322
        // TODO: gilt OUI nur bei Individual UAA? For LAA, should we convert to UAA and then query the registry?
241
        if (count(glob(IEEE_MAC_REGISTRY.'/*.txt')) > 0) {
323
        if (count(glob(IEEE_MAC_REGISTRY.DIRECTORY_SEPARATOR.'*.txt')) > 0) {
242
                if (
324
                if (
243
                        # 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]
325
                        # 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]
244
                        # 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.
326
                        # 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.
245
                        ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . '/iab.txt', 'IAB', $mac)) ||
327
                        ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . DIRECTORY_SEPARATOR . 'iab.txt', 'IAB', $mac)) ||
246
                        ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . '/oui36.txt', 'OUI-36 (MA-S)', $mac)) ||
328
                        ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . DIRECTORY_SEPARATOR . 'oui36.txt', 'OUI-36 (MA-S)', $mac)) ||
247
                        ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . '/mam.txt', 'OUI-28 (MA-M)', $mac)) ||
329
                        ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . DIRECTORY_SEPARATOR . 'mam.txt', 'OUI-28 (MA-M)', $mac)) ||
248
                        ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . '/oui.txt', 'OUI-24 (MA-L)', $mac))
330
                        ($x = _lookup_ieee_registry(IEEE_MAC_REGISTRY . DIRECTORY_SEPARATOR . 'oui.txt', 'OUI-24 (MA-L)', $mac))
249
                ) {
331
                ) {
250
                        echo $x;
332
                        echo $x;
251
                }
333
                }
252
        }
334
        }
253
 
335