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