2 * Copyright (c) 2020 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 #import "SCTestUtils.h"
27 #include <net/ethernet.h>
28 #include <IOKit/IOKitLib.h>
29 #include <IOKit/IOBSD.h>
30 #include <IOKit/network/IONetworkController.h>
31 #include <IOKit/network/IOUserEthernetController.h>
32 #include <IOKit/storage/IOStorageDeviceCharacteristics.h>
33 #include <IOKit/usb/USB.h>
34 #include "plugin_shared.h"
36 #define INTERFACES_KEY @"State:/Network/Interface"
37 #define PLUGIN_INTERFACE_NAMER_KEY @"Plugin:InterfaceNamer"
39 #define WAIT_TIME (30 * NSEC_PER_SEC)
42 @interface SCTestInterfaceNamer : SCTest
43 @property NSArray *interfaces_all;
44 @property NSArray *interfaces_preconfigured;
45 @property dispatch_queue_t queue;
46 @property dispatch_semaphore_t sem_interfaces_all;
47 @property dispatch_semaphore_t sem_interfaces_preconfigured;
48 @property SCDynamicStoreRef store;
51 @implementation SCTestInterfaceNamer
55 return @"InterfaceNamer";
58 + (NSString *)commandDescription
60 return @"Tests the InterfaceNamer.bundle code paths";
64 storeCallback(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
69 SCTestInterfaceNamer *test = (__bridge SCTestInterfaceNamer *)info;
71 if ([(__bridge NSArray *)changedKeys containsObject:INTERFACES_KEY]) {
72 // copy list of interfaces
73 dict = (__bridge_transfer NSDictionary *)SCDynamicStoreCopyValue(store, (CFStringRef)INTERFACES_KEY);
74 interfaces = [dict objectForKey:(__bridge NSString *)kSCPropNetInterfaces];
75 if (!_SC_CFEqual((__bridge CFArrayRef)interfaces, (__bridge CFArrayRef)test.interfaces_all)) {
76 test.interfaces_all = interfaces;
77 dispatch_semaphore_signal(test.sem_interfaces_all);
81 if ([(__bridge NSArray *)changedKeys containsObject:PLUGIN_INTERFACE_NAMER_KEY]) {
82 // copy list of [pre-configured] interfaces
83 dict = (__bridge_transfer NSDictionary *)SCDynamicStoreCopyValue(store, (CFStringRef)PLUGIN_INTERFACE_NAMER_KEY);
84 interfaces = [dict objectForKey:@"_PreConfigured_"];
85 if (!_SC_CFEqual((__bridge CFArrayRef)interfaces, (__bridge CFArrayRef)test.interfaces_preconfigured)) {
86 test.interfaces_preconfigured = interfaces;
87 dispatch_semaphore_signal(test.sem_interfaces_preconfigured);
95 - (instancetype)initWithOptions:(NSDictionary *)options
97 self = [super initWithOptions:options];
105 [self watchForChanges:FALSE];
110 [self cleanupAndExitWithErrorCode:0];
113 - (void)cleanupAndExitWithErrorCode:(int)error
115 [super cleanupAndExitWithErrorCode:error];
129 BOOL allUnitTestsPassed = YES;
130 allUnitTestsPassed &= [self unitTestInsertRemoveOneInterface];
131 allUnitTestsPassed &= [self unitTestInsertRemoveMultipleInterfaces];
132 allUnitTestsPassed &= [self unitTestCheckIOKitQuiet];
133 allUnitTestsPassed &= [self unitTestCheckEN0];
135 if(![self tearDown]) {
139 return allUnitTestsPassed;
147 - (void)watchForChanges:(BOOL)enable
150 SCDynamicStoreContext context = {0, NULL, CFRetain, CFRelease, NULL};
153 self.queue = dispatch_queue_create("SCTestInterfaceNamer callback queue", NULL);
155 self.sem_interfaces_all = dispatch_semaphore_create(0);
157 self.sem_interfaces_preconfigured = dispatch_semaphore_create(0);
159 context.info = (__bridge void * _Nullable)self;
160 self.store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("SCTest"), storeCallback, &context);
162 ok = SCDynamicStoreSetNotificationKeys(self.store,
163 (__bridge CFArrayRef)@[INTERFACES_KEY,
164 PLUGIN_INTERFACE_NAMER_KEY],
168 ok = SCDynamicStoreSetDispatchQueue(self.store, self.queue);
171 if (self.store != NULL) {
172 (void)SCDynamicStoreSetDispatchQueue(self.store, NULL);
173 CFRelease(self.store);
178 self.sem_interfaces_all = NULL;
179 self.sem_interfaces_preconfigured = NULL;
183 - (BOOL)isPreconfiguredInterface:(NSString *)bsdName
185 return [self.interfaces_preconfigured containsObject:bsdName];;
189 #pragma mark IOEthernetController support
192 copy_interface_name(IOEthernetControllerRef controller)
195 io_object_t interface;
197 interface = IOEthernetControllerGetIONetworkInterfaceObject(controller);
198 if (interface == MACH_PORT_NULL) {
199 SCTestLog("*** could not get interface for controller");
203 bsdName = IORegistryEntryCreateCFProperty(interface, CFSTR(kIOBSDNameKey), NULL, kNilOptions);
204 if (bsdName == NULL) {
205 SCTestLog("*** IOEthernetController with no BSD interface name");
213 copy_interface_mac(IOEthernetControllerRef controller)
215 CFDataRef macAddress;
216 io_object_t interface;
218 interface = IOEthernetControllerGetIONetworkInterfaceObject(controller);
219 if (interface == MACH_PORT_NULL) {
220 SCTestLog("*** could not get interface for controller");
224 macAddress = IORegistryEntrySearchCFProperty(interface,
226 CFSTR(kIOMACAddress),
228 kIORegistryIterateRecursively | kIORegistryIterateParents);
229 if (macAddress == NULL) {
230 SCTestLog("*** IOEthernetController with no BSD interface name");
237 static IOEthernetControllerRef
238 create_hidden_interface(u_char ea_unique)
240 IOEthernetControllerRef controller;
242 struct ether_addr ea = { .octet = { 0x02, 'F', 'A', 'K', 'E', ea_unique } };
243 CFMutableDictionaryRef merge;
245 CFMutableDictionaryRef props;
246 const int usb_vid_apple = kIOUSBAppleVendorID;
248 props = CFDictionaryCreateMutable(NULL, 0,
249 &kCFTypeDictionaryKeyCallBacks,
250 &kCFTypeDictionaryValueCallBacks);
252 data = CFDataCreate(NULL, ea.octet, ETHER_ADDR_LEN);
253 CFDictionarySetValue(props, kIOEthernetHardwareAddress, data);
256 merge = CFDictionaryCreateMutable(NULL, 0,
257 &kCFTypeDictionaryKeyCallBacks,
258 &kCFTypeDictionaryValueCallBacks);
259 CFDictionarySetValue(merge, CFSTR(kIOPropertyProductNameKey), CFSTR("Hidden Ethernet"));
260 CFDictionarySetValue(merge, kIOUserEthernetInterfaceRole, CFSTR("hidden-ethernet"));
261 CFDictionarySetValue(merge, kSCNetworkInterfaceHiddenConfigurationKey, kCFBooleanTrue);
262 num = CFNumberCreate(NULL, kCFNumberIntType, &usb_vid_apple);
263 CFDictionarySetValue(merge, CFSTR(kUSBVendorID), num);
265 CFDictionarySetValue(props, kIOUserEthernetInterfaceMergeProperties, merge);
268 controller = IOEthernetControllerCreate(NULL, props);
270 if (controller == NULL) {
271 SCTestLog("*** could not create ethernet controller for \"%s\"", ether_ntoa(&ea));
278 - (BOOL)interfaceAdd:(u_char)ea_unique controller:(IOEthernetControllerRef *)newController
288 *newController = create_hidden_interface(ea_unique);
289 if (*newController == NULL) {
290 SCTestLog("*** could not create controller");
294 // wait for the [BSD] interface to show up
295 status = dispatch_semaphore_wait(self.sem_interfaces_all, dispatch_time(DISPATCH_TIME_NOW, WAIT_TIME));
298 SCTestLog("*** no KernelEventMonitor change posted, interface not? created");
302 bsdName = (__bridge_transfer NSString *)copy_interface_name(*newController);
303 if (bsdName == NULL) {
307 macAddress = (__bridge_transfer NSData *)copy_interface_mac(*newController);
308 if (macAddress == NULL) {
312 SCTestLog(" Interface \"%@\" added, mac=%s",
314 ether_ntoa((struct ether_addr *)macAddress.bytes));
316 // check if pre-configured
317 status = dispatch_semaphore_wait(self.sem_interfaces_preconfigured, dispatch_time(DISPATCH_TIME_NOW, WAIT_TIME));
320 SCTestLog("*** no InterfaceNamer change posted, not? preconfigured");
324 if (![self isPreconfiguredInterface:bsdName]) {
325 SCTestLog("*** Interface \"%@\" is not pre-configured", bsdName);
329 SCTestLog(" Interface \"%@\" is pre-configured", bsdName);
335 if (*newController != NULL) {
336 CFRelease(*newController);
337 *newController = NULL;
344 - (BOOL)interfaceRemove:(IOEthernetControllerRef)controller
349 NSString *bsdName = (__bridge_transfer NSString *)copy_interface_name(controller);
352 // remove the interface
353 CFRelease(controller);
355 // wait for the [BSD] interface to go away
356 status = dispatch_semaphore_wait(self.sem_interfaces_all, dispatch_time(DISPATCH_TIME_NOW, WAIT_TIME));
359 SCTestLog("*** no KernelEventMonitor change posted, interface not? removed");
363 SCTestLog(" Interface \"%@\" removed", bsdName);
365 // check if [still] pre-configured
366 status = dispatch_semaphore_wait(self.sem_interfaces_preconfigured, dispatch_time(DISPATCH_TIME_NOW, WAIT_TIME));
369 SCTestLog("*** no InterfaceNamer change posted, still? preconfigured");
373 if ([self isPreconfiguredInterface:bsdName]) {
374 SCTestLog("*** interface \"%@\" is still pre-configured", bsdName);
385 #pragma mark unitTestInsertRemoveOneInterface
387 #define N_INTERFACES 5
389 - (BOOL)unitTestInsertRemoveOneInterface
391 NSString *bsdName1 = nil;
393 SCTestInterfaceNamer *test;
395 test = [[SCTestInterfaceNamer alloc] initWithOptions:self.options];
396 [test watchForChanges:TRUE];
398 for (size_t i = 0; i < N_INTERFACES; i++) {
400 IOEthernetControllerRef controller;
402 SCTestLog("Interface #%zd", i + 1);
405 ok = [test interfaceAdd:i controller:&controller];
410 // check the assigned interface name
411 bsdName = (__bridge_transfer NSString *)copy_interface_name(controller);
414 } else if ([bsdName isNotEqualTo:bsdName1]) {
415 SCTestLog("*** interface name not re-assigned, expected \"%@\", assigned \"%@\"", bsdName1, bsdName);
419 // remove the interface
420 ok = [test interfaceRemove:controller];
427 SCTestLog("Successfully completed unitTestInsertRemoveOneInterface unit test");
434 #pragma mark unitTestInsertRemoveMultipleInterfaces
436 #define N_INTERFACES_SLOT_X (N_INTERFACES / 2)
438 - (BOOL)unitTestInsertRemoveMultipleInterfaces
441 IOEthernetControllerRef controller;
444 IOEthernetControllerRef controller;
445 } interfaces[N_INTERFACES] = { { } };
447 SCTestInterfaceNamer *test;
449 test = [[SCTestInterfaceNamer alloc] initWithOptions:self.options];
450 [test watchForChanges:TRUE];
452 SCTestLog("Adding %d interfaces", N_INTERFACES);
454 for (size_t i = 0; i < N_INTERFACES; i++) {
456 ok = [test interfaceAdd:i controller:&controller];
461 interfaces[i].controller = controller;
462 interfaces[i].bsdName = (__bridge_transfer NSString *)copy_interface_name(controller);
466 SCTestLog("Removing interfaces");
468 for (size_t i = 0; i < N_INTERFACES; i++) {
469 // remove the interface
470 ok = [test interfaceRemove:interfaces[i].controller];
471 interfaces[i].controller = NULL;
479 SCTestLog("Re-adding %d interfaces", N_INTERFACES);
481 for (size_t i = 0; i < N_INTERFACES; i++) {
483 ok = [test interfaceAdd:i controller:&controller];
488 interfaces[i].controller = controller;
490 bsdName = (__bridge_transfer NSString *)copy_interface_name(controller);
491 ok = [bsdName isEqualTo:interfaces[i].bsdName];
493 SCTestLog("*** interface %zd not assigned the same name, expected \"%@\", assigned \"%@\"",
495 interfaces[i].bsdName,
500 interfaces[i].bsdName = bsdName;
505 SCTestLog("Removing one interface");
507 ok = [test interfaceRemove:interfaces[N_INTERFACES_SLOT_X].controller];
508 interfaces[N_INTERFACES_SLOT_X].controller = NULL;
512 SCTestLog("Adding a new interface");
515 // add a new interface (should re-use the first available name)
516 ok = [test interfaceAdd:N_INTERFACES controller:&controller];
521 interfaces[N_INTERFACES_SLOT_X].controller = controller;
523 bsdName = (__bridge_transfer NSString *)copy_interface_name(controller);
524 ok = [bsdName isEqualTo:interfaces[N_INTERFACES_SLOT_X].bsdName];
526 SCTestLog("*** interface %d not assigned an old name, expected \"%@\", assigned \"%@\"",
528 interfaces[N_INTERFACES_SLOT_X].bsdName,
533 interfaces[N_INTERFACES_SLOT_X].bsdName = bsdName;
538 SCTestLog("Removing interfaces (again)");
540 for (size_t i = 0; i < N_INTERFACES; i++) {
541 // remove the interface
542 ok = [test interfaceRemove:interfaces[i].controller];
543 interfaces[i].controller = NULL;
551 for (size_t i = 0; i < N_INTERFACES; i++) {
552 if (interfaces[i].controller != NULL) {
553 CFRelease(interfaces[i].controller);
561 #pragma mark unitTestCheckIOKitQuiet
563 - (BOOL)unitTestCheckIOKitQuiet
565 CFDictionaryRef dict;
566 const char *err = NULL;
567 Boolean hasNamer = FALSE;
568 Boolean hasQuiet = FALSE;
569 Boolean hasTimeout = FALSE;
570 CFArrayRef interfaces;
574 SCTestLog("Checking IOKit quiet");
576 // first off, we need to wait for *QUIET* or *TIMEOUT*
577 interfaces = SCNetworkInterfaceCopyAll();
578 if (interfaces != NULL) {
579 CFRelease(interfaces);
582 // now, we check the configd/InterfaceNamer status
583 key = SCDynamicStoreKeyCreate(NULL, CFSTR("%@" "InterfaceNamer"), kSCDynamicStoreDomainPlugin);
584 dict = SCDynamicStoreCopyValue(NULL, key);
588 hasQuiet = CFDictionaryContainsKey(dict, kInterfaceNamerKey_Quiet);
589 hasTimeout = CFDictionaryContainsKey(dict, kInterfaceNamerKey_Timeout);
592 SCTestLog("*** configd/InterfaceNamer status not available");
597 err = "*** configd/InterfaceNamer quiet after timeout";
604 err = "*** configd/InterfaceNamer timeout, not quiet";
607 mach_timespec_t waitTime = { 60, 0 };
610 * Here, we're in limbo.
611 * 1. InterfaceNamer has not reported that the IORegistry
613 * 2. InterfaceNamer was happy yet quiet, but has not
614 * reported the timeout
616 * This likely means that we detected the previousl named
617 * interfaces and released any waiting processes. But, we
618 * don't know if the IORegistry actually quiesced.
620 * So, let's just check/wait.
622 ret = IOKitWaitQuiet(kIOMasterPortDefault, &waitTime);
623 if (ret == kIOReturnSuccess) {
625 SCTestLog("*** configd/InterfaceNamer released before quiet");
628 err = "*** configd/InterfaceNamer did not report quiet status";
631 err = "*** IOKit not quiet";
637 SCTestLog("IOKit quiesced");
638 } else if (err != NULL) {
639 SCTestLog("%s", err);
646 #pragma mark unitTestCheckEN0
648 - (BOOL)unitTestCheckEN0
650 CFStringRef en0 = CFSTR("en0");
651 Boolean en0Found = FALSE;
653 CFArrayRef interfaces;
656 SCTestLog("Checking interfaces");
658 // for debugging, provide a way to use an alternate interface name
659 if_name = getenv("EN0");
660 if (if_name != NULL) {
661 en0 = CFStringCreateWithCString(NULL, if_name, kCFStringEncodingUTF8);
666 interfaces = SCNetworkInterfaceCopyAll();
667 if (interfaces != NULL) {
670 n = CFArrayGetCount(interfaces);
671 for (CFIndex i = 0; i < n; i++) {
673 SCNetworkInterfaceRef interface;
675 interface = CFArrayGetValueAtIndex(interfaces, i);
676 bsdName = SCNetworkInterfaceGetBSDName(interface);
677 if (_SC_CFEqual(bsdName, en0)) {
678 CFStringRef interfaceType;
682 if (!_SCNetworkInterfaceIsBuiltin(interface)) {
683 SCTestLog("*** Network interface \"%@\" not built-in", en0);
687 interfaceType = SCNetworkInterfaceGetInterfaceType(interface);
688 if (CFEqual(interfaceType, kSCNetworkInterfaceTypeEthernet)) {
689 if (!_SCNetworkInterfaceIsThunderbolt(interface) &&
690 !_SCNetworkInterfaceIsApplePreconfigured(interface)) {
691 // if Ethernet (and not Thunderbolt, Bridge, ...)
695 } else if (CFEqual(interfaceType, kSCNetworkInterfaceTypeIEEE80211)) {
701 SCTestLog("*** Network interface \"%@\" not Ethernet or Wi-Fi", en0);
706 CFRelease(interfaces);
710 SCTestLog("*** Network interface \"%@\" not found", en0);
714 SCTestLog("Verified \"%@\"", en0);