]>
Commit | Line | Data |
---|---|---|
942cecd7 | 1 | /* |
c956c85e | 2 | * Copyright (c) 2016-2020 Apple Inc. All rights reserved. |
942cecd7 A |
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> | |
afb19109 | 38 | #include <os/overflow.h> |
942cecd7 A |
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 | ||
afb19109 | 45 | #define SC_LOG_HANDLE __log_QoSMarking |
942cecd7 A |
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 | |
f715d946 | 76 | __log_QoSMarking(void) |
942cecd7 A |
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 | ||
afb19109 | 136 | memset(&ifr, 0, sizeof(ifr)); |
942cecd7 A |
137 | strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); |
138 | if (ioctl(s, SIOCGIFTYPE, (caddr_t)&ifr) == -1) { | |
1ef45fa4 A |
139 | SC_log(LOG_NOTICE, "%s: ioctl(SIOCGIFTYPE) failed: %s", |
140 | ifname, | |
141 | strerror(errno)); | |
942cecd7 A |
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 | ||
1ef45fa4 A |
147 | #if !TARGET_OS_IPHONE |
148 | if (ifr.ifr_type.ift_family == IFRTYPE_FAMILY_ETHERNET) { | |
149 | return true; | |
150 | } | |
151 | #else // !TARGET_OS_IPHONE | |
942cecd7 A |
152 | if ((ifr.ifr_type.ift_family == IFRTYPE_FAMILY_ETHERNET) && |
153 | (ifr.ifr_type.ift_subfamily == IFRTYPE_SUBFAMILY_WIFI)) { | |
154 | return true; | |
155 | } | |
1ef45fa4 | 156 | #endif // !TARGET_OS_IPHONE |
942cecd7 A |
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 | ||
afb19109 | 168 | memset(&ifr, 0, sizeof(ifr)); |
942cecd7 A |
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 | { | |
1ef45fa4 | 239 | return [[NEPolicySession alloc] init]; |
942cecd7 A |
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 | { | |
afb19109 | 306 | uint64_t bytes = 0; |
942cecd7 A |
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 | ||
afb19109 A |
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 | ||
942cecd7 A |
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 | |
afb19109 | 372 | memset(&hostinfo, 0, sizeof(hostinfo)); |
942cecd7 A |
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) { | |
1ef45fa4 | 586 | #pragma unused(index) |
942cecd7 A |
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 | ||
c956c85e A |
751 | // overall policy change |
752 | BOOL notify = false; | |
753 | ||
942cecd7 A |
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 | } | |
c956c85e A |
817 | |
818 | notify = true; | |
942cecd7 A |
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 | } | |
c956c85e A |
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 | } | |
942cecd7 A |
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 | { | |
1ef45fa4 | 1068 | #pragma unused(arg) |
942cecd7 A |
1069 | CFDictionaryRef changes; |
1070 | CFIndex n; | |
1071 | static CFStringRef prefix = NULL; | |
1072 | ||
942cecd7 A |
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 | ||
afb19109 A |
1134 | static Boolean |
1135 | haveNetworkExtensionFramework() | |
1136 | { | |
1137 | Boolean haveFramework; | |
1138 | ||
1139 | haveFramework = ([NEPolicy class] != nil); | |
1140 | return haveFramework; | |
1141 | } | |
1142 | ||
1143 | ||
942cecd7 A |
1144 | __private_extern__ |
1145 | void | |
1146 | load_QoSMarking(CFBundleRef bundle, Boolean bundleVerbose) | |
1147 | { | |
1ef45fa4 | 1148 | #pragma unused(bundleVerbose) |
942cecd7 A |
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 | ||
afb19109 A |
1160 | if (!haveNetworkExtensionFramework()) { |
1161 | return; | |
1162 | } | |
1163 | ||
942cecd7 A |
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 | { | |
59647b27 | 1244 | _sc_log = kSCLogDestinationFile; |
942cecd7 A |
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 |