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>
35 #define INTERFACES_KEY @"State:/Network/Interface"
36 #define PLUGIN_INTERFACE_NAMER_KEY @"Plugin:InterfaceNamer"
38 #define WAIT_TIME (30 * NSEC_PER_SEC)
41 @interface SCTestInterfaceNamer : SCTest
42 @property NSArray *interfaces_all;
43 @property NSArray *interfaces_preconfigured;
44 @property dispatch_queue_t queue;
45 @property dispatch_semaphore_t sem_interfaces_all;
46 @property dispatch_semaphore_t sem_interfaces_preconfigured;
47 @property SCDynamicStoreRef store;
50 @implementation SCTestInterfaceNamer
54 return @"InterfaceNamer";
57 + (NSString *)commandDescription
59 return @"Tests the InterfaceNamer.bundle code paths";
63 storeCallback(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
68 SCTestInterfaceNamer *test = (__bridge SCTestInterfaceNamer *)info;
70 if ([(__bridge NSArray *)changedKeys containsObject:INTERFACES_KEY]) {
71 // copy list of interfaces
72 dict = (__bridge_transfer NSDictionary *)SCDynamicStoreCopyValue(store, (CFStringRef)INTERFACES_KEY);
73 interfaces = [dict objectForKey:(__bridge NSString *)kSCPropNetInterfaces];
74 if (!_SC_CFEqual((__bridge CFArrayRef)interfaces, (__bridge CFArrayRef)test.interfaces_all)) {
75 test.interfaces_all = interfaces;
76 dispatch_semaphore_signal(test.sem_interfaces_all);
80 if ([(__bridge NSArray *)changedKeys containsObject:PLUGIN_INTERFACE_NAMER_KEY]) {
81 // copy list of [pre-configured] interfaces
82 dict = (__bridge_transfer NSDictionary *)SCDynamicStoreCopyValue(store, (CFStringRef)PLUGIN_INTERFACE_NAMER_KEY);
83 interfaces = [dict objectForKey:@"_PreConfigured_"];
84 if (!_SC_CFEqual((__bridge CFArrayRef)interfaces, (__bridge CFArrayRef)test.interfaces_preconfigured)) {
85 test.interfaces_preconfigured = interfaces;
86 dispatch_semaphore_signal(test.sem_interfaces_preconfigured);
94 - (instancetype)initWithOptions:(NSDictionary *)options
96 self = [super initWithOptions:options];
104 [self watchForChanges:FALSE];
109 [self cleanupAndExitWithErrorCode:0];
112 - (void)cleanupAndExitWithErrorCode:(int)error
114 [super cleanupAndExitWithErrorCode:error];
128 BOOL allUnitTestsPassed = YES;
129 allUnitTestsPassed &= [self unitTestInsertRemoveOneInterface];
131 if(![self tearDown]) {
135 return allUnitTestsPassed;
143 - (void)watchForChanges:(BOOL)enable
146 SCDynamicStoreContext context = {0, NULL, CFRetain, CFRelease, NULL};
149 self.queue = dispatch_queue_create("SCTestInterfaceNamer callback queue", NULL);
151 self.sem_interfaces_all = dispatch_semaphore_create(0);
153 self.sem_interfaces_preconfigured = dispatch_semaphore_create(0);
155 context.info = (__bridge void * _Nullable)self;
156 self.store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("SCTest"), storeCallback, &context);
158 ok = SCDynamicStoreSetNotificationKeys(self.store,
159 (__bridge CFArrayRef)@[INTERFACES_KEY,
160 PLUGIN_INTERFACE_NAMER_KEY],
164 ok = SCDynamicStoreSetDispatchQueue(self.store, self.queue);
167 if (self.store != NULL) {
168 (void)SCDynamicStoreSetDispatchQueue(self.store, NULL);
169 CFRelease(self.store);
174 self.sem_interfaces_all = NULL;
175 self.sem_interfaces_preconfigured = NULL;
179 - (BOOL)isPreconfiguredInterface:(NSString *)bsdName
181 return [self.interfaces_preconfigured containsObject:bsdName];;
185 #pragma mark IOEthernetController support
188 copy_interface_name(IOEthernetControllerRef controller)
191 io_object_t interface;
193 interface = IOEthernetControllerGetIONetworkInterfaceObject(controller);
194 if (interface == MACH_PORT_NULL) {
195 SCTestLog("*** could not get interface for controller");
199 bsdName = IORegistryEntryCreateCFProperty(interface, CFSTR(kIOBSDNameKey), NULL, kNilOptions);
200 if (bsdName == NULL) {
201 SCTestLog("*** IOEthernetController with no BSD interface name");
209 copy_interface_mac(IOEthernetControllerRef controller)
211 CFDataRef macAddress;
212 io_object_t interface;
214 interface = IOEthernetControllerGetIONetworkInterfaceObject(controller);
215 if (interface == MACH_PORT_NULL) {
216 SCTestLog("*** could not get interface for controller");
220 macAddress = IORegistryEntrySearchCFProperty(interface,
222 CFSTR(kIOMACAddress),
224 kIORegistryIterateRecursively | kIORegistryIterateParents);
225 if (macAddress == NULL) {
226 SCTestLog("*** IOEthernetController with no BSD interface name");
233 static IOEthernetControllerRef
234 create_hidden_interface(u_char ea_unique)
236 IOEthernetControllerRef controller;
238 struct ether_addr ea = { .octet = { 0x02, 'F', 'A', 'K', 'E', ea_unique } };
239 CFMutableDictionaryRef merge;
241 CFMutableDictionaryRef props;
242 const int usb_vid_apple = kIOUSBAppleVendorID;
244 props = CFDictionaryCreateMutable(NULL, 0,
245 &kCFTypeDictionaryKeyCallBacks,
246 &kCFTypeDictionaryValueCallBacks);
248 data = CFDataCreate(NULL, ea.octet, ETHER_ADDR_LEN);
249 CFDictionarySetValue(props, kIOEthernetHardwareAddress, data);
252 merge = CFDictionaryCreateMutable(NULL, 0,
253 &kCFTypeDictionaryKeyCallBacks,
254 &kCFTypeDictionaryValueCallBacks);
255 CFDictionarySetValue(merge, CFSTR(kIOPropertyProductNameKey), CFSTR("Hidden Ethernet"));
256 CFDictionarySetValue(merge, kIOUserEthernetInterfaceRole, CFSTR("hidden-ethernet"));
257 CFDictionarySetValue(merge, kSCNetworkInterfaceHiddenConfigurationKey, kCFBooleanTrue);
258 num = CFNumberCreate(NULL, kCFNumberIntType, &usb_vid_apple);
259 CFDictionarySetValue(merge, CFSTR(kUSBVendorID), num);
261 CFDictionarySetValue(props, kIOUserEthernetInterfaceMergeProperties, merge);
264 controller = IOEthernetControllerCreate(NULL, props);
266 if (controller == NULL) {
267 SCTestLog("*** could not create ethernet controller for \"%s\"", ether_ntoa(&ea));
274 - (BOOL)interfaceAdd:(u_char)ea_unique controller:(IOEthernetControllerRef *)newController
284 *newController = create_hidden_interface(ea_unique);
285 if (*newController == NULL) {
286 SCTestLog("*** could not create controller");
290 // wait for the [BSD] interface to show up
291 status = dispatch_semaphore_wait(self.sem_interfaces_all, dispatch_time(DISPATCH_TIME_NOW, WAIT_TIME));
294 SCTestLog("*** no KernelEventMonitor change posted, interface not? created");
298 bsdName = (__bridge_transfer NSString *)copy_interface_name(*newController);
299 if (bsdName == NULL) {
303 macAddress = (__bridge_transfer NSData *)copy_interface_mac(*newController);
304 if (macAddress == NULL) {
308 SCTestLog(" Interface \"%@\" added, mac=%s",
310 ether_ntoa((struct ether_addr *)macAddress.bytes));
312 // check if pre-configured
313 status = dispatch_semaphore_wait(self.sem_interfaces_preconfigured, dispatch_time(DISPATCH_TIME_NOW, WAIT_TIME));
316 SCTestLog("*** no InterfaceNamer change posted, not? preconfigured");
320 if (![self isPreconfiguredInterface:bsdName]) {
321 SCTestLog("*** Interface \"%@\" is not pre-configured", bsdName);
325 SCTestLog(" Interface \"%@\" is pre-configured", bsdName);
331 if (*newController != NULL) {
332 CFRelease(*newController);
333 *newController = NULL;
340 - (BOOL)interfaceRemove:(IOEthernetControllerRef)controller
345 NSString *bsdName = (__bridge_transfer NSString *)copy_interface_name(controller);
348 // remove the interface
349 CFRelease(controller);
351 // wait for the [BSD] interface to go away
352 status = dispatch_semaphore_wait(self.sem_interfaces_all, dispatch_time(DISPATCH_TIME_NOW, WAIT_TIME));
355 SCTestLog("*** no KernelEventMonitor change posted, interface not? removed");
359 SCTestLog(" Interface \"%@\" removed", bsdName);
361 // check if [still] pre-configured
362 status = dispatch_semaphore_wait(self.sem_interfaces_preconfigured, dispatch_time(DISPATCH_TIME_NOW, WAIT_TIME));
365 SCTestLog("*** no InterfaceNamer change posted, still? preconfigured");
369 if ([self isPreconfiguredInterface:bsdName]) {
370 SCTestLog("*** interface \"%@\" is still pre-configured", bsdName);
381 #pragma mark unitTestInsertRemoveOneInterface
383 #define N_INTERFACES 5
385 - (BOOL)unitTestInsertRemoveOneInterface
387 NSString *bsdName1 = nil;
389 SCTestInterfaceNamer *test;
391 test = [[SCTestInterfaceNamer alloc] initWithOptions:self.options];
392 [test watchForChanges:TRUE];
394 for (size_t i = 0; i < N_INTERFACES; i++) {
396 IOEthernetControllerRef controller;
398 SCTestLog("Interface #%zd", i + 1);
401 ok = [test interfaceAdd:i controller:&controller];
406 // check the assigned interface name
407 bsdName = (__bridge_transfer NSString *)copy_interface_name(controller);
410 } else if ([bsdName isNotEqualTo:bsdName1]) {
411 SCTestLog("*** interface name not re-assigned, expected \"%@\", assigned \"%@\"", bsdName1, bsdName);
415 // remove the interface
416 ok = [test interfaceRemove:controller];
423 SCTestLog("Successfully completed unitTestInsertRemoveOneInterface unit test");
430 #pragma mark unitTestInsertRemoveMultipleInterfaces
432 #define N_INTERFACES_SLOT_X (N_INTERFACES / 2)
434 - (BOOL)unitTestInsertRemoveMultipleInterfaces
437 IOEthernetControllerRef controller;
440 IOEthernetControllerRef controller;
441 } interfaces[N_INTERFACES] = { { } };
443 SCTestInterfaceNamer *test;
445 test = [[SCTestInterfaceNamer alloc] initWithOptions:self.options];
446 [test watchForChanges:TRUE];
448 SCTestLog("Adding %d interfaces", N_INTERFACES);
450 for (size_t i = 0; i < N_INTERFACES; i++) {
452 ok = [test interfaceAdd:i controller:&controller];
457 interfaces[i].controller = controller;
458 interfaces[i].bsdName = (__bridge_transfer NSString *)copy_interface_name(controller);
462 SCTestLog("Removing interfaces");
464 for (size_t i = 0; i < N_INTERFACES; i++) {
465 // remove the interface
466 ok = [test interfaceRemove:interfaces[i].controller];
467 interfaces[i].controller = NULL;
475 SCTestLog("Re-adding %d interfaces", N_INTERFACES);
477 for (size_t i = 0; i < N_INTERFACES; i++) {
479 ok = [test interfaceAdd:i controller:&controller];
484 interfaces[i].controller = controller;
486 bsdName = (__bridge_transfer NSString *)copy_interface_name(controller);
487 ok = [bsdName isEqualTo:interfaces[i].bsdName];
489 SCTestLog("*** interface %zd not assigned the same name, expected \"%@\", assigned \"%@\"",
491 interfaces[i].bsdName,
496 interfaces[i].bsdName = bsdName;
501 SCTestLog("Removing one interface");
503 ok = [test interfaceRemove:interfaces[N_INTERFACES_SLOT_X].controller];
504 interfaces[N_INTERFACES_SLOT_X].controller = NULL;
508 SCTestLog("Adding a new interface");
511 // add a new interface (should re-use the first available name)
512 ok = [test interfaceAdd:N_INTERFACES controller:&controller];
517 interfaces[N_INTERFACES_SLOT_X].controller = controller;
519 bsdName = (__bridge_transfer NSString *)copy_interface_name(controller);
520 ok = [bsdName isEqualTo:interfaces[N_INTERFACES_SLOT_X].bsdName];
522 SCTestLog("*** interface %d not assigned an old name, expected \"%@\", assigned \"%@\"",
524 interfaces[N_INTERFACES_SLOT_X].bsdName,
529 interfaces[N_INTERFACES_SLOT_X].bsdName = bsdName;
534 SCTestLog("Removing interfaces (again)");
536 for (size_t i = 0; i < N_INTERFACES; i++) {
537 // remove the interface
538 ok = [test interfaceRemove:interfaces[i].controller];
539 interfaces[i].controller = NULL;
547 for (size_t i = 0; i < N_INTERFACES; i++) {
548 if (interfaces[i].controller != NULL) {
549 CFRelease(interfaces[i].controller);