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