2 * Copyright (c) 2018 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
24 #import <Foundation/Foundation.h>
25 #import <EventFactory/EventFactory.h>
28 #import "StateDumpParser.h"
30 #define TokenFlagsDescription "flagsDescription"
31 #define TokenAddress "address"
32 #define TokenReachabilityDescription "reachabilityDescription"
33 #define TokenRank "rank"
34 #define TokenOrder "order"
35 #define TokenDomain "domain"
36 #define TokenSearchDomains "searchDomains"
37 #define TokenNameServers "nameServers"
39 #define ResolverSearchDomainsKey @"searchDomains"
40 #define ResolverNameServersKey @"nameServers"
41 #define ResolverInterfaceNameKey @"interfaceName"
42 #define ResolverFlagsDescriptionKey @"flagsDescription"
43 #define ResolverReachabilityDescriptionKey @"reachabilityDescription"
44 #define ResolverMatchDomainsKey @"matchDomains"
46 @interface StateDumpParser ()
47 @property (readonly, nonatomic) NSRegularExpression *nwiRegex;
48 @property (readonly, nonatomic) NSRegularExpression *dnsRegex;
49 @property (readonly, nonatomic) NSRegularExpression *nameserverRegex;
50 @property (readonly, nonatomic) NSRegularExpression *searchDomainRegex;
53 @implementation StateDumpParser
57 NSError *regexError = nil;
59 _nwiRegex = [[NSRegularExpression alloc] initWithPattern:@"\\s+(?<"TokenInterfaceName">\\w+) : flags\\s+: \\w+ \\(.+\\)\\n"
60 "\\s+address\\s+: (?<"TokenAddress">\\S+)\\n"
61 "(\\s+VPN server\\s+: \\S+\\n)?"
62 "\\s+reach\\s+: \\w+ \\(.+\\)\\n"
63 "\\s+rank\\s+: \\w+ \\((?<"TokenRank">\\w+), (?<"TokenOrder">\\w+)\\)"
66 if (_nwiRegex == nil || regexError != nil) {
67 specs_log_err("Failed to create NWI regex: %@", regexError);
72 _dnsRegex = [[NSRegularExpression alloc] initWithPattern:@"resolver #\\d+\\n"
73 "( domain : (?<"TokenDomain">\\S+)\\n)?"
74 "(?<"TokenSearchDomains">(?: search domain\\[\\d+\\] : \\S+\\n)*)"
75 "(?<"TokenNameServers">(?: nameserver\\[\\d+\\] : \\S+\\n)*)"
76 "( if_index : \\d+ \\((?<"TokenInterfaceName">\\w+)\\)\\n)"
77 "( flags : \\w+ \\((?<"TokenFlagsDescription">.+)\\)\\n)"
78 "( reach : \\w+ \\((?<"TokenReachabilityDescription">.+)\\)\\n)"
81 if (_dnsRegex == nil || regexError != nil) {
82 specs_log_err("Failed to create DNS configuration regex: %@", regexError);
87 _nameserverRegex = [[NSRegularExpression alloc] initWithPattern:@" nameserver\\[\\d+\\] : (?<"TokenAddress">\\S+)\\n"
90 if (_nameserverRegex == nil || regexError != nil) {
91 specs_log_err("Failed to create the nameserver regex: %@", regexError);
96 _searchDomainRegex = [[NSRegularExpression alloc] initWithPattern:@" search domain\\[\\d+\\] : (?<"TokenDomain">\\S+)\\n"
99 if (_searchDomainRegex == nil || regexError != nil) {
100 specs_log_err("Failed to create the search domain regex: %@", regexError);
104 NSArray<EFLogEventMatch *> *matches = @[
105 [[EFLogEventMatch alloc] initWithPattern:@"^Network information"
106 multipleNewEventHandler:
107 ^NSArray<EFEvent *> *(__unused NSTextCheckingResult *matchResult, EFLogEvent *logEvent) {
108 NSMutableDictionary<NSString *, EFNetworkControlPathEvent *> *newEvents = nil;
109 NSArray<NSTextCheckingResult *> *matches = [self.nwiRegex matchesInString:logEvent.eventMessage options:0 range:NSMakeRange(0, logEvent.eventMessage.length)];
110 BOOL primaryV4 = YES;
111 BOOL primaryV6 = YES;
113 for (NSString *interfaceName in SCLogParser.interfaceMap.allKeys) {
114 SCLogParser.interfaceMap[interfaceName] = @[ ];
117 for (NSTextCheckingResult *match in matches) {
118 NSString *interfaceName = [logEvent substringForCaptureGroup:@TokenInterfaceName inMatchResult:match];
119 if (interfaceName == nil) {
122 EFNetworkControlPathEvent *event = newEvents[interfaceName];
124 event = [self createInterfaceEventWithLogEvent:logEvent matchResult:match];
125 if (newEvents == nil) {
126 newEvents = [[NSMutableDictionary alloc] init];
128 newEvents[event.interfaceBSDName] = event;
129 event.primaryStateIPv4 = EFPrimaryStateNotPrimary;
130 event.primaryStateIPv6 = EFPrimaryStateNotPrimary;
132 NSString *addressString = [logEvent substringForCaptureGroup:@TokenAddress inMatchResult:match];
133 if (addressString.length > 0) {
134 if (primaryV4 || primaryV6) {
135 sa_family_t addressFamily = [self getAddressFamilyOfAddress:addressString];
136 if (primaryV4 && addressFamily == AF_INET) {
137 event.primaryStateIPv4 = EFPrimaryStatePrimary;
139 } else if (primaryV6 && addressFamily == AF_INET6) {
140 event.primaryStateIPv6 = EFPrimaryStatePrimary;
144 [self addAddress:addressString toInterfaceEvent:event];
146 NSString *rankString = [logEvent substringForCaptureGroup:@TokenRank inMatchResult:match];
147 if (rankString.length > 0) {
148 event.rank = rankString;
150 NSString *orderString = [logEvent substringForCaptureGroup:@TokenOrder inMatchResult:match];
151 if (orderString.length > 0) {
152 if ([orderString isEqualToString:@"Last"]) {
155 event.order = orderString.integerValue;
159 return newEvents.allValues;
161 [[EFLogEventMatch alloc] initWithPattern:@"^DNS Configuration"
162 multipleNewEventHandler:
163 ^NSArray<EFEvent *> *(__unused NSTextCheckingResult *matchResult, EFLogEvent *logEvent) {
164 NSMutableArray<EFNetworkControlPathEvent *> *newEvents = nil;
165 NSArray<NSTextCheckingResult *> *matches = [self.dnsRegex matchesInString:logEvent.eventMessage options:0 range:NSMakeRange(0, logEvent.eventMessage.length)];
166 NSMutableDictionary<NSString *, NSDictionary<NSString *, NSObject *> *> *interfaceDNSConfigurations = nil;
167 NSMutableArray<NSDictionary<NSString *, NSObject *> *> *orderedDNSConfigurations = nil;
169 for (NSTextCheckingResult *match in matches) {
170 NSMutableDictionary<NSString *, NSObject *> *dnsConfiguration = [[NSMutableDictionary alloc] init];
171 NSString *matchDomain = [logEvent substringForCaptureGroup:@TokenDomain inMatchResult:match];
173 if (matchDomain.length > 0) {
174 NSArray<NSString *> *domains = (NSArray<NSString *> *)dnsConfiguration[ResolverMatchDomainsKey];
175 dnsConfiguration[ResolverMatchDomainsKey] = [self addUniqueString:matchDomain toArray:domains];
177 NSString *searchDomainsString = [logEvent substringForCaptureGroup:@TokenSearchDomains inMatchResult:match];
178 if (searchDomainsString.length > 0) {
179 [self addSubstringsFromString:searchDomainsString
180 forCaptureGroup:@TokenDomain
181 inRegex:self.searchDomainRegex
182 toArrayAtKey:ResolverSearchDomainsKey
183 inDictionary:dnsConfiguration];
185 NSString *nameServersString = [logEvent substringForCaptureGroup:@TokenNameServers inMatchResult:match];
186 if (nameServersString.length > 0) {
187 [self addSubstringsFromString:nameServersString
188 forCaptureGroup:@TokenAddress
189 inRegex:self.nameserverRegex
190 toArrayAtKey:ResolverNameServersKey
191 inDictionary:dnsConfiguration];
193 NSString *flagsDescription = [logEvent substringForCaptureGroup:@TokenFlagsDescription inMatchResult:match];
194 if (flagsDescription.length > 0) {
195 dnsConfiguration[ResolverFlagsDescriptionKey] = flagsDescription;
196 if ([flagsDescription containsString:@"Scoped"]) {
200 NSString *reachabilityDescription = [logEvent substringForCaptureGroup:@TokenReachabilityDescription inMatchResult:match];
201 if (reachabilityDescription.length > 0) {
202 dnsConfiguration[ResolverReachabilityDescriptionKey] = reachabilityDescription;
205 NSString *interfaceName = [logEvent substringForCaptureGroup:@TokenInterfaceName inMatchResult:match];
206 if (interfaceName != nil) {
207 dnsConfiguration[ResolverInterfaceNameKey] = interfaceName;
210 NSDictionary<NSString *, NSObject *> *newConfiguration = nil;
211 if (interfaceName != nil && (scoped || matchDomain.length > 0)) {
212 NSDictionary<NSString *, NSObject *> *existingConfiguration = interfaceDNSConfigurations[interfaceName];
213 if (existingConfiguration != nil) {
214 if (matchDomain.length > 0) {
215 NSArray<NSString *> *matchDomains = (NSArray<NSString *> *)existingConfiguration[ResolverMatchDomainsKey];
216 dnsConfiguration[ResolverMatchDomainsKey] = [self addUniqueString:matchDomain toArray:matchDomains];
218 dnsConfiguration[ResolverMatchDomainsKey] = existingConfiguration[ResolverMatchDomainsKey];
221 newConfiguration = [[NSDictionary alloc] initWithDictionary:dnsConfiguration];
222 if (interfaceDNSConfigurations == nil) {
223 interfaceDNSConfigurations = [[NSMutableDictionary alloc] init];
225 interfaceDNSConfigurations[interfaceName] = newConfiguration;
227 newConfiguration = [[NSDictionary alloc] initWithDictionary:dnsConfiguration];
231 if (orderedDNSConfigurations == nil) {
232 orderedDNSConfigurations = [[NSMutableArray alloc] init];
234 NSUInteger existingIndex = [orderedDNSConfigurations indexOfObjectPassingTest:
235 ^BOOL(NSDictionary<NSString *,NSObject *> *obj, __unused NSUInteger idx, __unused BOOL *stop) {
236 NSString *existingInterfaceName = (NSString *)obj[ResolverInterfaceNameKey];
237 return (existingInterfaceName != nil && [interfaceName isEqualToString:existingInterfaceName]);
239 if (existingIndex == NSNotFound) {
240 [orderedDNSConfigurations addObject:newConfiguration];
242 orderedDNSConfigurations[existingIndex] = newConfiguration;
247 for (NSString *interfaceName in interfaceDNSConfigurations) {
248 NSDictionary<NSString *, NSObject *> *dnsConfiguration = interfaceDNSConfigurations[interfaceName];
249 if (dnsConfiguration == nil || ![NSJSONSerialization isValidJSONObject:dnsConfiguration]) {
250 specs_log_err("DNS configuration is not valid JSON: %@", dnsConfiguration);
253 NSError *jsonError = nil;
254 NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dnsConfiguration options:NSJSONWritingPrettyPrinted error:&jsonError];
255 if (jsonData == nil) {
256 specs_log_err("Failed to generate JSON from %@: %@", dnsConfiguration, jsonError);
259 EFNetworkControlPathEvent *newEvent = [self createInterfaceEventWithLogEvent:logEvent interfaceName:interfaceName];
260 newEvent.dnsConfiguration = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
262 if (newEvents == nil) {
263 newEvents = [[NSMutableArray alloc] init];
265 [newEvents addObject:newEvent];
267 if (orderedDNSConfigurations.count > 0) {
268 NSError *jsonError = nil;
269 NSData *jsonData = [NSJSONSerialization dataWithJSONObject:orderedDNSConfigurations options:NSJSONWritingPrettyPrinted error:&jsonError];
270 if (jsonData != nil) {
271 NSData *subsystemIdentifier = [self createSubsystemIdentifier];
272 EFNetworkControlPathEvent *newEvent = [[EFNetworkControlPathEvent alloc] initWithLogEvent:logEvent subsystemIdentifier:subsystemIdentifier];
273 newEvent.interfaceBSDName = @"system";
274 newEvent.interfaceDescription = @"System Configuration";
275 newEvent.dnsConfiguration = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
277 if (newEvents == nil) {
278 newEvents = [[NSMutableArray alloc] init];
280 [newEvents addObject:newEvent];
283 for (NSString *interfaceName in SCLogParser.interfaceMap) {
284 if (interfaceDNSConfigurations[interfaceName] == nil) {
285 EFNetworkControlPathEvent *newEvent = [self createInterfaceEventWithLogEvent:logEvent interfaceName:interfaceName];
286 newEvent.dnsConfiguration = EFNetworkControlPathEvent.configurationNotSet;
287 if (newEvents == nil) {
288 newEvents = [[NSMutableArray alloc] init];
290 [newEvents addObject:newEvent];
296 EFLogEventParser *parser = [[EFLogEventParser alloc] initWithMatches:matches];
297 return [super initWithCategory:@"StateDump" eventParser:parser];
300 - (void)addSubstringsFromString:(NSString *)string forCaptureGroup:(NSString *)groupName inRegex:(NSRegularExpression *)regex toArrayAtKey:(NSString *)configKey inDictionary:(NSMutableDictionary<NSString *, NSObject *> *)dictionary
302 NSArray<NSTextCheckingResult *> *matches = [regex matchesInString:string options:0 range:NSMakeRange(0, string.length)];
303 for (NSTextCheckingResult *match in matches) {
304 NSRange groupRange = [match rangeWithName:groupName];
305 if (!NSEqualRanges(groupRange, NSMakeRange(NSNotFound, 0))) {
306 NSString *substring = [string substringWithRange:groupRange];
307 if (substring.length > 0) {
308 NSArray<NSString *> *existingList = (NSArray<NSString *> *)dictionary[configKey];
309 dictionary[configKey] = [self addUniqueString:substring toArray:existingList];