]> git.saurik.com Git - apple/configd.git/blob - Plugins/QoSMarking/qos-marking.m
configd-888.1.2.tar.gz
[apple/configd.git] / Plugins / QoSMarking / qos-marking.m
1 /*
2 * Copyright (c) 2016 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /*
25 * Modification History
26 *
27 * March 1, 2016 Allan Nathanson <ajn@apple.com>
28 * - initial revision
29 */
30
31
32 #include <mach/mach.h>
33 #include <mach-o/fat.h>
34 #include <mach-o/loader.h>
35 #include <net/if.h>
36 #include <net/if_types.h>
37 #include <net/necp.h>
38 #include <sys/ioctl.h>
39 #include <sys/kern_control.h>
40 #include <sys/socket.h>
41 #include <sys/sockio.h>
42 #include <sys/sys_domain.h>
43
44 #define SC_LOG_HANDLE __log_QoSMarking()
45 #include <SystemConfiguration/SystemConfiguration.h>
46 #include <SystemConfiguration/SCPrivate.h>
47 #include <SystemConfiguration/SCValidation.h>
48
49 #import <Foundation/Foundation.h>
50 #import <NetworkExtension/NEPolicySession.h>
51 #import <NEHelperClient.h>
52
53 // define the QoSMarking.bundle Info.plist key containing [application] bundleIDs to be white-listed
54 #define kQoSMarkingBundleIdentifiersAppleAudioVideoCallsKey CFSTR("QoSMarking_AppleAudioVideoCalls_BundleIDs")
55
56 // define the QoSMarking.bundle Info.plist key containing paths to be white-listed
57 #define kQoSMarkingExecutablePathsAppleAudioVideoCallsKey CFSTR("QoSMarking_AppleAudioVideoCalls_ExecutablePaths")
58
59 // define the starting "order" value for any QoS Marking NEPolicy rules
60 #define QOS_MARKING_PRIORITY_BLOCK_AV_APPS 1000
61 #define QOS_MARKING_PRIORITY_BLOCK_AV_PATHS 1500
62 #define QOS_MARKING_PRIORITY_BLOCK_APPS 2000
63
64
65 static CFStringRef interfacesKey = NULL;
66 static NSArray * qosMarkingAudioVideoCalls_bundleIDs = nil;
67 static NSArray * qosMarkingAudioVideoCalls_executablePaths = nil;
68
69
70 #pragma mark -
71 #pragma mark Logging
72
73
74 __private_extern__ os_log_t
75 __log_QoSMarking()
76 {
77 static os_log_t log = NULL;
78
79 if (log == NULL) {
80 log = os_log_create("com.apple.SystemConfiguration", "QoSMarking");
81 }
82
83 return log;
84 }
85
86
87 #pragma mark -
88 #pragma mark QoSMarking support (system)
89
90
91 static void
92 qosMarkingSetPolicyRestriction(const char *ctl, BOOL yn)
93 {
94 int restricted = yn ? 1 : 0;
95 int ret;
96
97 ret = sysctlbyname(ctl, NULL, 0, &restricted, sizeof(restricted));
98 if (ret != -1) {
99 SC_log(LOG_NOTICE, "QoS marking policy: sysctl %s=%d", ctl, restricted);
100 } else {
101 if (errno != ENOENT) {
102 SC_log(LOG_ERR, "sysctlbyname() failed: %s", strerror(errno));
103 }
104 }
105
106 return;
107 }
108
109
110 static void
111 qosMarkingSetHavePolicies(BOOL havePolicies)
112 {
113 qosMarkingSetPolicyRestriction("net.qos.policy.restricted", havePolicies);
114 return;
115 }
116
117
118 static void
119 qosMarkingSetRestrictAVApps(BOOL restrictApps)
120 {
121 qosMarkingSetPolicyRestriction("net.qos.policy.restrict_avapps", restrictApps);
122 return;
123 }
124
125
126 #pragma mark -
127 #pragma mark QoSMarking support (per-interface)
128
129
130 static BOOL
131 supportsQoSMarking(int s, const char *ifname)
132 {
133 struct ifreq ifr;
134
135 bzero(&ifr, sizeof(ifr));
136 strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
137 if (ioctl(s, SIOCGIFTYPE, (caddr_t)&ifr) == -1) {
138 SC_log(LOG_NOTICE, "%s: ioctl(SIOCGIFTYPE) failed: %m", ifname);
139 ifr.ifr_type.ift_type = 0;
140 ifr.ifr_type.ift_family = IFRTYPE_FAMILY_ANY;
141 ifr.ifr_type.ift_subfamily = IFRTYPE_SUBFAMILY_ANY;
142 }
143
144 if ((ifr.ifr_type.ift_family == IFRTYPE_FAMILY_ETHERNET) &&
145 (ifr.ifr_type.ift_subfamily == IFRTYPE_SUBFAMILY_WIFI)) {
146 return true;
147 }
148
149 return false;
150 }
151
152
153 static void
154 qosMarkingSetEnabled(int s, const char *ifname, BOOL enabled)
155 {
156 struct ifreq ifr;
157 int ret;
158
159 bzero(&ifr, sizeof(ifr));
160 strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
161 ifr.ifr_qosmarking_enabled = enabled ? 1 : 0;
162 ret = ioctl(s, SIOCSQOSMARKINGENABLED, &ifr);
163 if (ret == -1) {
164 SC_log(LOG_ERR, "%s: ioctl(SIOCSQOSMARKINGENABLED) failed: %s",
165 ifname,
166 strerror(errno));
167 }
168
169 return;
170 }
171
172
173 #pragma mark -
174 #pragma mark QoSMarking Policy support
175
176
177 @interface QoSMarkingController : NSObject
178
179 + (QoSMarkingController *)sharedController;
180 - (void)setInterfaces:(NSArray *)interfaces;
181 - (void)setPolicy:(NSDictionary *)policy forInterface:(NSString *)interface;
182
183 @end
184
185
186 @interface QoSMarkingController()
187
188 /*
189 * interfaces
190 * An array of network interface names on the system/device
191 */
192 @property (nonatomic) NSArray * interfaces;
193
194 /*
195 * policySessions
196 * A dictionary for the maintaining the QoS marking policies.
197 *
198 * Key : interface [name]
199 * Value : the NEPolicySession* for the interface
200 */
201 @property (nonatomic) NSMutableDictionary * policySessions;
202
203 /*
204 * requested
205 * A dictionary for the tracking the QoS marking policies.
206 *
207 * Key : interface [name]
208 * Value : the [requested] NSDictionary* "policy" for the interface
209 */
210 @property (nonatomic) NSMutableDictionary * requested;
211
212 /*
213 * enabled, enabledAV
214 * Dictionaries for tracking the "enabled" interfaces with QoS [AV]
215 * marking policies.
216 *
217 * Key : interface [name]
218 * Value : the enabled NSDictionary* "policy" for the interface
219 */
220 @property (nonatomic) NSMutableDictionary * enabled;
221 @property (nonatomic) NSMutableDictionary * enabledAV;
222
223 @end
224
225
226 @implementation QoSMarkingController
227
228 - (NEPolicySession *)createPolicySession
229 {
230 NEPolicySession *session = nil;
231 #if !TARGET_OS_IPHONE
232 /*
233 * Note: we cannot have entitlements on OSX so we open a kernel
234 * control socket and use it to create a policy session
235 */
236
237 struct sockaddr_ctl kernctl_addr;
238 struct ctl_info kernctl_info;
239 int sock;
240
241 // Create kernel control socket
242 sock = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
243 if (sock == -1) {
244 SC_log(LOG_ERR, "socket() failed: %s", strerror(errno));
245 return nil;
246 }
247
248 bzero(&kernctl_info, sizeof(kernctl_info));
249 strlcpy(kernctl_info.ctl_name, NECP_CONTROL_NAME, sizeof(kernctl_info.ctl_name));
250 if (ioctl(sock, CTLIOCGINFO, &kernctl_info)) {
251 SC_log(LOG_ERR, "ioctl() failed: %s", strerror(errno));
252 close(sock);
253 return nil;
254 }
255
256 bzero(&kernctl_addr, sizeof(kernctl_addr));
257 kernctl_addr.sc_len = sizeof(kernctl_addr);
258 kernctl_addr.sc_family = AF_SYSTEM;
259 kernctl_addr.ss_sysaddr = AF_SYS_CONTROL;
260 kernctl_addr.sc_id = kernctl_info.ctl_id;
261 kernctl_addr.sc_unit = 0;
262 if (connect(sock, (struct sockaddr *)&kernctl_addr, sizeof(kernctl_addr))) {
263 SC_log(LOG_ERR, "connect() failed: %s", strerror(errno));
264 close(sock);
265 return nil;
266 }
267
268 /* Create policy session */
269 session = [[NEPolicySession alloc] initWithSocket:sock];
270 if (session == nil) {
271 close(sock);
272 }
273
274 #else // !TARGET_OS_IPHONE
275 session = [[NEPolicySession alloc] init];
276 #endif // !TARGET_OS_IPHONE
277
278 return session;
279 }
280
281
282 #pragma mark -
283
284
285 - (BOOL)qosMarkingPolicyEnabled:(NSDictionary *)policy forKey:(NSString *)key
286 {
287 NSNumber * enabled;
288
289 enabled = policy[key];
290 if (enabled != nil) {
291 if (![enabled isKindOfClass:[NSNumber class]]) {
292 SC_log(LOG_ERR, "%@ not valid", key);
293 return false;
294 }
295 } else {
296 // assume "enabled" if no key
297 return true;
298 }
299
300 return enabled.boolValue;
301 }
302
303
304 - (BOOL)qosMarkingIsEnabled:(NSDictionary *)policy
305 {
306 return [self qosMarkingPolicyEnabled:policy
307 forKey:(NSString *)kSCPropNetQoSMarkingEnabled];
308 }
309
310
311 - (BOOL)qosMarkingIsAppleAudioVideoCallsEnabled:(NSDictionary *)policy
312 {
313 return [self qosMarkingPolicyEnabled:policy
314 forKey:(NSString *)kSCPropNetQoSMarkingAppleAudioVideoCalls];
315 }
316
317
318 - (NSArray *)qosMarkingWhitelistedAppIdentifiers:(NSDictionary *)policy
319 {
320 NSArray * appIDs;
321
322 appIDs = policy[(NSString *)kSCPropNetQoSMarkingWhitelistedAppIdentifiers];
323 if ((appIDs != nil) && ![appIDs isKindOfClass:[NSArray class]]) {
324 SC_log(LOG_ERR, "QoSMarkingWhitelistedAppIdentifier list not valid");
325 return nil;
326 }
327
328 for (NSString *appID in appIDs) {
329 if ((appID != nil) &&
330 (![appID isKindOfClass:[NSString class]] || (appID.length == 0))) {
331 SC_log(LOG_ERR, "QoSMarkingWhitelistedAppIdentifier not valid");
332 return nil;
333 }
334 }
335
336 return appIDs;
337 }
338
339
340 #pragma mark -
341
342
343 - (NSUUID *)copyUUIDForSingleArch:(int)fd
344 {
345 struct mach_header header;
346 NSUUID * uuid = nil;
347
348 if (read(fd, &header, sizeof(header)) != sizeof(header)) {
349 return nil;
350 }
351
352 // Go past the 64 bit header if we have a 64 arch
353 if (header.magic == MH_MAGIC_64) {
354 if (lseek(fd, sizeof(uint32_t), SEEK_CUR) == -1) {
355 SC_log(LOG_ERR, "could not lseek() past 64 bit header");
356 return nil;
357 }
358 }
359
360 // Find LC_UUID in the load commands
361 for (size_t i = 0; i < header.ncmds; i++) {
362 struct load_command lcmd;
363
364 if (read(fd, &lcmd, sizeof(lcmd)) != sizeof(lcmd)) {
365 SC_log(LOG_ERR, "could not read() load_command");
366 break;
367 }
368
369 if (lcmd.cmd == LC_UUID) {
370 struct uuid_command uuid_cmd;
371
372 if (read(fd, uuid_cmd.uuid, sizeof(uuid_t)) != sizeof(uuid_t)) {
373 SC_log(LOG_ERR, "could not read() uuid_command");
374 break;
375 }
376
377 uuid = [[NSUUID alloc] initWithUUIDBytes:uuid_cmd.uuid];
378 break;
379 } else {
380 if (lseek(fd, lcmd.cmdsize - sizeof(lcmd), SEEK_CUR) == -1) {
381 SC_log(LOG_ERR, "could not lseek() past load command");
382 return nil;
383 }
384 }
385 }
386
387 return uuid;
388 }
389
390 #define MAX_NFAT_ARCH 32
391
392 - (NSArray *)copyUUIDsForFatBinary:(int)fd
393 {
394 struct fat_arch * arches = NULL;
395 mach_msg_type_number_t count;
396 struct fat_header hdr;
397 struct host_basic_info hostinfo;
398 kern_return_t kr;
399 NSMutableArray * uuids = nil;
400
401 // For a fat architecture, we need find the section that is closet to the host cpu
402 bzero(&hostinfo, sizeof(hostinfo));
403 count = HOST_BASIC_INFO_COUNT;
404 kr = host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)&hostinfo, &count);
405 if (kr != KERN_SUCCESS) {
406 SC_log(LOG_ERR, "host_info() failed: %d", kr);
407 return nil;
408 }
409
410 if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
411 SC_log(LOG_ERR, "could not read() fat_header");
412 return nil;
413 }
414
415 // Fat header info are always big-endian
416 hdr.nfat_arch = OSSwapInt32(hdr.nfat_arch);
417 if (hdr.nfat_arch > MAX_NFAT_ARCH) {
418 SC_log(LOG_ERR, "fat_header.nfat_arch (%d) > %d", hdr.nfat_arch, MAX_NFAT_ARCH);
419 return nil;
420 }
421
422 arches = (struct fat_arch *)malloc(hdr.nfat_arch * sizeof(struct fat_arch));
423 if (arches == NULL) {
424 // if we could not allocate space for architectures
425 return nil;
426 }
427
428 uuids = [[NSMutableArray alloc] init];
429
430 for (size_t i = 0; i < hdr.nfat_arch; ++i) {
431 struct fat_arch arch;
432
433 if (read(fd, &arch, sizeof(arch)) != sizeof(arch)) {
434 SC_log(LOG_ERR, "could not read() fat_arch");
435 goto done;
436 }
437 arch.cputype = (int)OSSwapInt32(arch.cputype);
438 arch.offset = OSSwapInt32(arch.offset);
439 memcpy(&arches[i], &arch, sizeof(arch));
440 }
441
442 for (size_t i = 0; i < hdr.nfat_arch; ++i) {
443 struct fat_arch arch;
444 NSUUID * uuid;
445
446 memcpy(&arch, &arches[i], sizeof(arch));
447
448 if (arch.offset == 0) {
449 SC_log(LOG_ERR, "invalid offset for arch %d", arch.cputype);
450 goto done;
451 }
452
453 if (lseek(fd, arch.offset, SEEK_SET) == -1) {
454 SC_log(LOG_ERR, "could not lseek() to arch %d", arch.cputype);
455 goto done;
456 }
457
458 uuid = [self copyUUIDForSingleArch:fd];
459 if (uuid == nil) {
460 SC_log(LOG_ERR, "could not get uuid for arch %d", arch.cputype);
461 goto done;
462 }
463
464 if (arch.cputype == hostinfo.cpu_type) {
465 [uuids insertObject:uuid atIndex:0];
466 } else {
467 [uuids addObject:uuid];
468 }
469 }
470
471 done:
472
473 if (arches != NULL) {
474 free(arches);
475 }
476 if (uuids.count == 0) {
477 uuids = nil;
478 }
479 return uuids;
480 }
481
482 - (NSArray *)copyUUIDsForExecutable:(const char *)executablePath
483 {
484 int fd;
485 uint32_t magic;
486 NSArray * uuids = nil;
487
488 if (executablePath == NULL) {
489 return nil;
490 }
491
492 fd = open(executablePath, O_RDONLY);
493 if (fd == -1) {
494 if (errno != ENOENT) {
495 SC_log(LOG_ERR, "open(\"%s\", O_RDONLY) failed: %s", executablePath, strerror(errno));
496 }
497
498 return nil;
499 }
500
501 // Read the magic format to decide which path to take
502 if (read(fd, &magic, sizeof(magic)) != sizeof(magic)) {
503 SC_log(LOG_ERR, "read() no magic format: %s", executablePath);
504 goto done;
505 }
506
507 // Rewind to the beginning
508 lseek(fd, 0, SEEK_SET);
509 switch (magic) {
510 case FAT_CIGAM: {
511 uuids = [self copyUUIDsForFatBinary:fd];
512 break;
513 }
514
515 case MH_MAGIC:
516 case MH_MAGIC_64: {
517 NSUUID * uuid;
518
519 uuid = [self copyUUIDForSingleArch:fd];
520 if (uuid == nil) {
521 SC_log(LOG_ERR, "%s: failed to get UUID for single arch", __FUNCTION__);
522 goto done;
523 }
524
525 uuids = @[ uuid ];
526 break;
527 }
528
529 default: {
530 break;
531 }
532 }
533
534 done:
535
536 close(fd);
537 return uuids;
538 }
539
540
541 - (void)addWhitelistedPathPolicy:(NSString *)interface forPath:(NSString *)path order:(uint32_t)order
542 {
543 NEPolicyCondition * allInterfacesCondition;
544 NEPolicyResult * result;
545 NEPolicyRouteRule * routeRule;
546 NEPolicySession * session;
547 NSArray * uuids;
548
549 session = _policySessions[interface];
550 if (session == nil) {
551 SC_log(LOG_ERR, "QoS marking policy: %@: no session", interface);
552 return;
553 }
554
555 // create QoS route rule
556 routeRule = [NEPolicyRouteRule routeRuleWithAction:NEPolicyRouteRuleActionQoSMarking
557 forInterfaceName:interface];
558 result = [NEPolicyResult routeRules:@[ routeRule ]];
559
560 // create "all interfaces" condition
561 allInterfacesCondition = [NEPolicyCondition allInterfaces];
562
563 uuids = [self copyUUIDsForExecutable:[path UTF8String]];
564 if ((uuids == nil) || (uuids.count == 0)) {
565 SC_log(LOG_ERR, "QoS marking policy: %@: could not add path \"%@\"",
566 interface,
567 path);
568 return;
569 }
570
571 for (NSUUID *uuid in uuids) {
572 NEPolicy * policy;
573 NSUInteger policyID;
574 NEPolicyCondition * uuidCondition;
575
576 // create per-app bundleID-->UUID condition
577 uuidCondition = [NEPolicyCondition effectiveApplication:uuid];
578
579 // create and add policy
580 policy = [[NEPolicy alloc] initWithOrder:order
581 result:result
582 conditions:@[ uuidCondition, allInterfacesCondition ]];
583 policyID = [session addPolicy:policy];
584 if (policyID != 0) {
585 SC_log(LOG_NOTICE, "QoS marking policy: %@: %u: whitelist path \"%@\" (%@)",
586 interface,
587 order,
588 path,
589 uuid.UUIDString);
590
591 } else {
592 SC_log(LOG_ERR, "QoS marking policy: %@: could not add whitelist policy for path \"%@\" (%@)",
593 interface,
594 path,
595 uuid.UUIDString);
596 }
597 }
598
599
600 return;
601 }
602
603
604 #pragma mark -
605
606
607 - (NSArray *)copyUUIDsForUUIDMapping:(xpc_object_t)mapping
608 {
609 NSMutableArray * uuids = nil;
610
611 if ((mapping != NULL) &&
612 (xpc_get_type(mapping) == XPC_TYPE_ARRAY)) {
613 uuids = [NSMutableArray array];
614
615 xpc_array_apply(mapping, ^bool(size_t index, xpc_object_t value) {
616 if ((value != NULL) &&
617 (xpc_get_type(value) == XPC_TYPE_UUID)) {
618 NSUUID * uuid;
619
620 uuid = [[NSUUID alloc] initWithUUIDBytes:xpc_uuid_get_bytes(value)];
621 [uuids addObject:uuid];
622 }
623 return YES;
624 });
625
626 if (uuids.count == 0) {
627 uuids = nil;
628 }
629 }
630
631 return uuids;
632 }
633
634
635 - (NSArray *)copyUUIDsForBundleID:(NSString *)bundleID
636 {
637 NSArray * uuids;
638 xpc_object_t uuidsFromHelper;
639
640 uuidsFromHelper = NEHelperCacheCopyAppUUIDMapping([bundleID UTF8String], NULL);
641
642 uuids = [self copyUUIDsForUUIDMapping:uuidsFromHelper];
643 return uuids;
644 }
645
646
647 - (void)addWhitelistedAppIdentifierPolicy:(NSString *)interface forApp:(NSString *)appBundleID order:(uint32_t)order
648 {
649 NEPolicyCondition * allInterfacesCondition;
650 NEPolicyResult * result;
651 NEPolicyRouteRule * routeRule;
652 NEPolicySession * session;
653 NSArray * uuids;
654
655 if ([appBundleID hasPrefix:@"/"]) {
656 if (_SC_isAppleInternal()) {
657 // special case executable path handling (if internal)
658 [self addWhitelistedPathPolicy:interface forPath:appBundleID order:order];
659 }
660
661 return;
662 }
663
664 session = _policySessions[interface];
665 if (session == nil) {
666 SC_log(LOG_ERR, "QoS marking policy: %@: no session", interface);
667 return;
668 }
669
670 // create QoS route rule
671 routeRule = [NEPolicyRouteRule routeRuleWithAction:NEPolicyRouteRuleActionQoSMarking
672 forInterfaceName:interface];
673 result = [NEPolicyResult routeRules:@[ routeRule ]];
674
675 // create "all interfaces" condition
676 allInterfacesCondition = [NEPolicyCondition allInterfaces];
677
678 uuids = [self copyUUIDsForBundleID:appBundleID];
679 if ((uuids == nil) || (uuids.count == 0)) {
680 SC_log(LOG_ERR, "QoS marking policy: %@: could not add bundleID \"%@\"",
681 interface,
682 appBundleID);
683 return;
684 }
685
686 for (NSUUID *uuid in uuids) {
687 NEPolicy * policy;
688 NSUInteger policyID;
689 NEPolicyCondition * uuidCondition;
690
691 // create per-app bundleID-->UUID condition
692 uuidCondition = [NEPolicyCondition effectiveApplication:uuid];
693
694 // create and add policy
695 policy = [[NEPolicy alloc] initWithOrder:order
696 result:result
697 conditions:@[ uuidCondition, allInterfacesCondition ]];
698 policyID = [session addPolicy:policy];
699 if (policyID != 0) {
700 SC_log(LOG_NOTICE, "QoS marking policy: %@: %u: whitelist bundleID \"%@\" (%@)",
701 interface,
702 order,
703 appBundleID,
704 uuid.UUIDString);
705
706 } else {
707 SC_log(LOG_ERR, "QoS marking policy: %@: could not add whitelist policy for bundleID \"%@\" (%@)",
708 interface,
709 appBundleID,
710 uuid.UUIDString);
711 }
712 }
713
714 return;
715 }
716
717
718 #pragma mark -
719
720
721 - (instancetype)init
722 {
723 self = [super init];
724 if (self != nil) {
725 _interfaces = nil;
726 _policySessions = [NSMutableDictionary dictionary];
727 _requested = [NSMutableDictionary dictionary];
728 _enabled = [NSMutableDictionary dictionary];
729 _enabledAV = [NSMutableDictionary dictionary];
730 }
731
732 return self;
733 }
734
735
736 /*
737
738 Have QoS Whitelist AppleAVCalls | net.qos.policy. | Interface Interface Interface
739 Profile Enabled Apps(#) Enabled | restricted restrict_avapps | QoS Enabled NECP rules NECP AV rules
740 ======= ======= ========= ============ + ========== =============== + =========== ========== =============
741 1 [N] | 0 0 | [Y] [N] [N]
742 | |
743 2 [Y] [N] [0] [N] | 0 0 | [N] [N] [N]
744 3 [Y] [N] [0] [Y] | 0 0 | [N] [N] [N]
745 | |
746 4 [Y] [N] [> 0] [N] | 0 0 | [N] [N] [N]
747 5 [Y] [N] [> 0] [Y] | 0 0 | [N] [N] [N]
748 | |
749 6 [Y] [Y] [0] [N] | 1 1 | [Y] [N] [N]
750 7 [Y] [Y] [0] [Y] | 1 0 | [Y] [N] [Y]
751 | |
752 8 [Y] [Y] [> 0] [N] | 1 1 | [Y] [Y] [N]
753 9 [Y] [Y] [> 0] [Y] | 1 0 | [Y] [Y] [Y]
754
755 Notes (QoSMarking policy) :
756 * If "QoSEnabled" is not present, assume "Y"
757 * If "QoSMarkingAppleAudioVideoCalls" is not present, assume "Y"
758 * If "QoSMarkingWhitelistedAppIdentifiers" is not present (or empty), assume no whitelisted applications
759
760 Notes (sysctl) :
761 * net.qos.policy.restricted should be "1" when NECP policies are present
762 * net.qos.policy.restrict_avapps should be "1" when "QoSMarkingAppleAudioVideoCalls" is "N"
763
764 */
765
766 - (void)updatePolicy:(NSDictionary *)reqPolicy forInterface:(NSString *)interface
767 {
768 // currently enabled settings
769 NSDictionary * nowPolicy = _enabled[interface];
770 BOOL nowDisabled = false;
771 BOOL nowEnabled = false;
772 BOOL nowAppleAV = false;
773
774 // requested settings
775 BOOL reqDefault = false;
776 BOOL reqDisabled = false;
777 BOOL reqEnabled = false;
778 BOOL reqAppleAV = false;
779
780 if (nowPolicy != nil) {
781 if ([self qosMarkingIsEnabled:nowPolicy]) {
782 // if we have an enabled QoS marking policy
783 nowEnabled = true;
784 } else {
785 // if we have a disabled QoS marking policy
786 nowDisabled = true;
787 }
788
789 nowAppleAV = [self qosMarkingIsAppleAudioVideoCallsEnabled:nowPolicy];
790 }
791
792 if (reqPolicy != nil) {
793 if ([self qosMarkingIsEnabled:reqPolicy]) {
794 // if QoS marking policy requested
795 reqEnabled = true;
796 } else {
797 // if QoS marking policy present (but disabled)
798 reqDisabled = true;
799 }
800
801 reqAppleAV = [self qosMarkingIsAppleAudioVideoCallsEnabled:reqPolicy];
802 } else {
803 reqDefault = true;
804 }
805
806 if ((!nowEnabled && reqDefault ) ||
807 ( nowEnabled != reqEnabled ) ||
808 ( nowDisabled != reqDisabled) ||
809 ( nowEnabled && reqEnabled && ![nowPolicy isEqual:reqPolicy])) {
810 int s;
811
812 if (reqEnabled) {
813 // if we are transitioning to enabled or we have a policy
814 // change, ensure that we rebuild policies
815 nowPolicy = nil;
816 } else {
817 if ((nowPolicy != nil) && (reqPolicy == nil)) {
818 SC_log(LOG_NOTICE, "QoS marking policy: %@: remove", interface);
819 }
820
821 // if QoS marking was enabled (for this interface), close session
822 [_policySessions removeObjectForKey:interface];
823
824 // QoS marking policy for this interface is no longer enabled
825 [_enabled removeObjectForKey:interface];
826 [_enabledAV removeObjectForKey:interface];
827 }
828
829 // update QoSMarking enabled (per-interface)
830 s = socket(AF_INET, SOCK_DGRAM, 0);
831 if (s != -1) {
832 BOOL enable = reqEnabled || reqDefault;
833
834 SC_log(LOG_NOTICE, "QoS marking policy: %@: %s%s",
835 interface,
836 enable ? "enable" : "disable",
837 reqDefault ? " (default)" : "");
838 qosMarkingSetEnabled(s, interface.UTF8String, enable);
839 close(s);
840 } else {
841 SC_log(LOG_ERR, "socket() failed: %s", strerror(errno));
842 }
843 }
844
845 if (reqEnabled) {
846 NSArray * curAppIDs;
847 NSArray * reqAppIDs;
848 BOOL update = FALSE;
849
850 if (nowAppleAV != reqAppleAV) {
851 update = true;
852 }
853
854 curAppIDs = [self qosMarkingWhitelistedAppIdentifiers:nowPolicy];
855 reqAppIDs = [self qosMarkingWhitelistedAppIdentifiers:reqPolicy];
856 if (![curAppIDs isEqual:reqAppIDs]) {
857 update = true;
858 }
859
860 if (update) {
861 BOOL ok;
862 uint32_t order;
863 NEPolicySession * session;
864
865 // QoS marking being (or still) enabled for this interface
866 if (_enabled.count == 0) {
867 // if we now have a profile requiring us to check NECP policies
868 qosMarkingSetHavePolicies(true);
869 }
870
871 // the QoS marking policy for this interface is now enabled
872 _enabled[interface] = reqPolicy;
873
874 SC_log(LOG_NOTICE, "QoS marking policy: %@: %s",
875 interface,
876 nowEnabled ? "update" : "add");
877
878 // prepare [new] per-interface NECP session
879
880 session = _policySessions[interface];
881 if ((session == nil) && ((reqAppIDs.count > 0) || reqAppleAV)) {
882 // if we need to add NECP policies
883 session = [self createPolicySession];
884 if (session != nil) {
885 _policySessions[interface] = session;
886 } else {
887 SC_log(LOG_ERR, "%@: failed to create policy session", interface);
888 }
889 }
890
891 // zap any previously stored policies
892 if (session != nil) {
893 ok = [session removeAllPolicies];
894 if (!ok) {
895 SC_log(LOG_ERR, "%@: could not remove policies", interface);
896 }
897 }
898
899 // if needed, add policies for any whitelisted applications
900 if ((session != nil) && (reqAppIDs.count > 0)) {
901 order = QOS_MARKING_PRIORITY_BLOCK_APPS;
902 for (NSString *app in reqAppIDs) {
903 [self addWhitelistedAppIdentifierPolicy:interface forApp:app order:order++];
904 }
905 }
906
907 if (reqAppleAV) {
908 if (_enabledAV.count == 0) {
909 // if we are enabling the marking of Apple AV application
910 // then we do not want to restrict handling of traffic that
911 // cannot be handled by NECP
912 qosMarkingSetRestrictAVApps(false);
913 }
914
915 // the QoS [AV] marking policy for this interface is now enabled
916 _enabledAV[interface] = reqPolicy;
917
918 if (session != nil) {
919 // if needed, add Apple audio/video application policies
920
921 order = QOS_MARKING_PRIORITY_BLOCK_AV_APPS;
922 for (NSString *app in qosMarkingAudioVideoCalls_bundleIDs) {
923 [self addWhitelistedAppIdentifierPolicy:interface forApp:app order:order++];
924 }
925
926 order = QOS_MARKING_PRIORITY_BLOCK_AV_PATHS;
927 for (NSString *path in qosMarkingAudioVideoCalls_executablePaths) {
928 [self addWhitelistedPathPolicy:interface forPath:path order:order++];
929 }
930 }
931 } else {
932 // the QoS [AV] marking policy for this interface is no longer enabled
933 [_enabledAV removeObjectForKey:interface];
934
935 if (_enabledAV.count == 0) {
936 // if we do not (no longer want to) be marking AV then restrict
937 // handling traffic that cannot be handled by NECP
938 qosMarkingSetRestrictAVApps(true);
939 }
940 }
941
942 if (session != nil) {
943 ok = [session apply];
944 if (!ok) {
945 SC_log(LOG_ERR, "%@: could not apply new policies", interface);
946 }
947 }
948 }
949 }
950
951 // Restore "default" state if no policies
952 if (_enabled.count == 0) {
953 qosMarkingSetRestrictAVApps(false);
954 qosMarkingSetHavePolicies(false);
955 }
956 }
957
958
959 #pragma mark -
960 #pragma mark Update QoSMarking Policy Configuration per [SC] changes
961
962
963 + (QoSMarkingController *)sharedController
964 {
965 static QoSMarkingController * controller;
966 static dispatch_once_t once;
967
968 dispatch_once(&once, ^{
969 controller = [[QoSMarkingController alloc] init];
970 });
971
972 return controller;
973 }
974
975
976 - (void)setInterfaces:(NSArray *)newInterfaces
977 {
978 NSArray * curInterfaces;
979 int s;
980
981 s = socket(AF_INET, SOCK_DGRAM, 0);
982 if (s == -1) {
983 SC_log(LOG_ERR, "socket() failed: %s", strerror(errno));
984 return;
985 }
986
987 curInterfaces = _interfaces;
988 _interfaces = newInterfaces;
989
990 for (NSString *interface in newInterfaces) {
991 if (!supportsQoSMarking(s, interface.UTF8String)) {
992 // skip interfaces that do not support QoS marking
993 continue;
994 }
995
996 if (![curInterfaces containsObject:interface]) {
997 NSDictionary * policy;
998
999 // if new interface
1000 policy = _requested[interface];
1001 [_requested removeObjectForKey:interface]; // make this look like a fresh "add"
1002 [self setPolicy:policy forInterface:interface]; // and "set" the new policy
1003 }
1004 }
1005
1006 close(s);
1007 return;
1008 }
1009
1010
1011 - (void)setPolicy:(NSDictionary *)policy forInterface:(NSString *)interface
1012 {
1013 if (policy != nil) {
1014 if ([_interfaces containsObject:interface]) {
1015 // set (update) per-interface policy
1016 [self updatePolicy:policy forInterface:interface];
1017 }
1018
1019 // track policy for future changes
1020 [_requested setObject:policy forKey:interface];
1021 } else {
1022 // remove (update) per-interface policy
1023 [self updatePolicy:policy forInterface:interface];
1024
1025 // track policy for future changes
1026 [_requested removeObjectForKey:interface];
1027 }
1028
1029 return;
1030 }
1031
1032 @end
1033
1034
1035 #pragma mark -
1036 #pragma mark Update QoS Marking Policy Plugin
1037
1038
1039 /*
1040 * Function: parse_component
1041 * Purpose:
1042 * Given a string 'key' and a string prefix 'prefix',
1043 * return the next component in the slash '/' separated
1044 * key.
1045 *
1046 * Examples:
1047 * 1. key = "a/b/c" prefix = "a/"
1048 * returns "b"
1049 * 2. key = "a/b/c" prefix = "a/b/"
1050 * returns "c"
1051 */
1052 static CF_RETURNS_RETAINED CFStringRef
1053 parse_component(CFStringRef key, CFStringRef prefix)
1054 {
1055 CFMutableStringRef comp;
1056 CFRange range;
1057
1058 if (!CFStringHasPrefix(key, prefix)) {
1059 return NULL;
1060 }
1061 comp = CFStringCreateMutableCopy(NULL, 0, key);
1062 CFStringDelete(comp, CFRangeMake(0, CFStringGetLength(prefix)));
1063 range = CFStringFind(comp, CFSTR("/"), 0);
1064 if (range.location == kCFNotFound) {
1065 return comp;
1066 }
1067 range.length = CFStringGetLength(comp) - range.location;
1068 CFStringDelete(comp, range);
1069 return comp;
1070 }
1071
1072
1073
1074 static void
1075 qosMarkingConfigChangedCallback(SCDynamicStoreRef store, CFArrayRef changedKeys, void *arg)
1076 {
1077 os_activity_t activity;
1078 CFDictionaryRef changes;
1079 CFIndex n;
1080 static CFStringRef prefix = NULL;
1081
1082 activity = os_activity_create("processing QoS marking configuration changes",
1083 OS_ACTIVITY_CURRENT,
1084 OS_ACTIVITY_FLAG_DEFAULT);
1085 os_activity_scope(activity);
1086
1087 if (prefix == NULL) {
1088 prefix = SCDynamicStoreKeyCreate(NULL,
1089 CFSTR("%@/%@/%@/"),
1090 kSCDynamicStoreDomainSetup,
1091 kSCCompNetwork,
1092 kSCCompInterface);
1093 }
1094
1095 changes = SCDynamicStoreCopyMultiple(store, changedKeys, NULL);
1096
1097 n = CFArrayGetCount(changedKeys);
1098 for (CFIndex i = 0; i < n; i++) {
1099 CFStringRef key;
1100
1101 key = CFArrayGetValueAtIndex(changedKeys, i);
1102
1103 if (CFEqual(key, interfacesKey)) {
1104 CFDictionaryRef info;
1105
1106 info = (changes != NULL) ? CFDictionaryGetValue(changes, key) : NULL;
1107 if (isA_CFDictionary(info) != NULL) {
1108 CFArrayRef interfaces;
1109
1110 interfaces = CFDictionaryGetValue(info, kSCPropNetInterfaces);
1111 if (isA_CFArray(interfaces)) {
1112 @autoreleasepool {
1113 QoSMarkingController * controller;
1114
1115 controller = [QoSMarkingController sharedController];
1116 [controller setInterfaces:(__bridge NSArray *)interfaces];
1117 }
1118 }
1119 }
1120 } else {
1121 CFStringRef interface;
1122
1123 interface = parse_component(key, prefix);
1124 if (interface != NULL) {
1125 CFDictionaryRef policy;
1126
1127 policy = (changes != NULL) ? CFDictionaryGetValue(changes, key) : NULL;
1128 @autoreleasepool {
1129 QoSMarkingController * controller;
1130
1131 controller = [QoSMarkingController sharedController];
1132 [controller setPolicy:(__bridge NSDictionary *)policy
1133 forInterface:(__bridge NSString *)interface];
1134 }
1135 CFRelease(interface);
1136 }
1137 }
1138 }
1139
1140 if (changes != NULL) {
1141 CFRelease(changes);
1142 }
1143
1144 return;
1145 }
1146
1147
1148 __private_extern__
1149 void
1150 load_QoSMarking(CFBundleRef bundle, Boolean bundleVerbose)
1151 {
1152 CFDictionaryRef dict;
1153 CFStringRef key;
1154 CFMutableArrayRef keys;
1155 Boolean ok;
1156 CFMutableArrayRef patterns;
1157 CFRunLoopSourceRef rls;
1158 SCDynamicStoreRef store;
1159
1160 SC_log(LOG_DEBUG, "load() called");
1161 SC_log(LOG_DEBUG, " bundle ID = %@", CFBundleGetIdentifier(bundle));
1162
1163 // initialize a few globals
1164 interfacesKey = SCDynamicStoreKeyCreateNetworkInterface(NULL,
1165 kSCDynamicStoreDomainState);
1166
1167 dict = CFBundleGetInfoDictionary(bundle);
1168 if (isA_CFDictionary(dict)) {
1169 CFArrayRef bundleIDs;
1170 CFArrayRef paths;
1171
1172 bundleIDs = CFDictionaryGetValue(dict, kQoSMarkingBundleIdentifiersAppleAudioVideoCallsKey);
1173 bundleIDs = isA_CFArray(bundleIDs);
1174 qosMarkingAudioVideoCalls_bundleIDs = (__bridge NSArray *)bundleIDs;
1175
1176 paths = CFDictionaryGetValue(dict, kQoSMarkingExecutablePathsAppleAudioVideoCallsKey);
1177 paths = isA_CFArray(paths);
1178 qosMarkingAudioVideoCalls_executablePaths = (__bridge NSArray *)paths;
1179 }
1180
1181 // open a "configd" store to allow cache updates
1182 store = SCDynamicStoreCreate(NULL,
1183 CFSTR("QoS Marking Configuraton plug-in"),
1184 qosMarkingConfigChangedCallback,
1185 NULL);
1186 if (store == NULL) {
1187 SC_log(LOG_ERR, "SCDynamicStoreCreate() failed: %s", SCErrorString(SCError()));
1188 goto error;
1189 }
1190
1191 // establish notification keys and patterns
1192 keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
1193 patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
1194
1195 // ...watch for a change in the list of network interfaces
1196 CFArrayAppendValue(keys, interfacesKey);
1197
1198 // ...watch for (per-interface) QoS marking policy changes
1199 key = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
1200 kSCDynamicStoreDomainSetup,
1201 kSCCompAnyRegex,
1202 kSCEntNetQoSMarkingPolicy);
1203 CFArrayAppendValue(patterns, key);
1204 CFRelease(key);
1205
1206 // register the keys/patterns
1207 ok = SCDynamicStoreSetNotificationKeys(store, keys, patterns);
1208 CFRelease(keys);
1209 CFRelease(patterns);
1210 if (!ok) {
1211 SC_log(LOG_NOTICE, "SCDynamicStoreSetNotificationKeys() failed: %s",
1212 SCErrorString(SCError()));
1213 goto error;
1214 }
1215
1216 rls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
1217 if (rls == NULL) {
1218 SC_log(LOG_NOTICE, "SCDynamicStoreCreateRunLoopSource() failed: %s",
1219 SCErrorString(SCError()));
1220 goto error;
1221 }
1222
1223 CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
1224 CFRelease(rls);
1225
1226 error :
1227
1228 if (store != NULL) CFRelease(store);
1229 return;
1230 }
1231
1232
1233 #ifdef MAIN
1234
1235
1236 #pragma mark -
1237 #pragma mark Standalone test code
1238
1239
1240 int
1241 main(int argc, char **argv)
1242 {
1243 _sc_log = FALSE;
1244 _sc_verbose = (argc > 1) ? TRUE : FALSE;
1245
1246 load_QoSMarking(CFBundleGetMainBundle(), (argc > 1) ? TRUE : FALSE);
1247 CFRunLoopRun();
1248 // not reached
1249 exit(0);
1250 return 0;
1251 }
1252 #endif