/* * Copyright (c) 2018 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #import #import #import #import "StateDumpParser.h" #define TokenFlagsDescription "flagsDescription" #define TokenAddress "address" #define TokenReachabilityDescription "reachabilityDescription" #define TokenRank "rank" #define TokenOrder "order" #define TokenDomain "domain" #define TokenSearchDomains "searchDomains" #define TokenNameServers "nameServers" #define ResolverSearchDomainsKey @"searchDomains" #define ResolverNameServersKey @"nameServers" #define ResolverInterfaceNameKey @"interfaceName" #define ResolverFlagsDescriptionKey @"flagsDescription" #define ResolverReachabilityDescriptionKey @"reachabilityDescription" #define ResolverMatchDomainsKey @"matchDomains" @interface StateDumpParser () @property (readonly, nonatomic) NSRegularExpression *nwiRegex; @property (readonly, nonatomic) NSRegularExpression *dnsRegex; @property (readonly, nonatomic) NSRegularExpression *nameserverRegex; @property (readonly, nonatomic) NSRegularExpression *searchDomainRegex; @end @implementation StateDumpParser - (instancetype)init { NSError *regexError = nil; _nwiRegex = [[NSRegularExpression alloc] initWithPattern:@"\\s+(?<"TokenInterfaceName">\\w+) : flags\\s+: \\w+ \\(.+\\)\\n" "\\s+address\\s+: (?<"TokenAddress">\\S+)\\n" "(\\s+VPN server\\s+: \\S+\\n)?" "\\s+reach\\s+: \\w+ \\(.+\\)\\n" "\\s+rank\\s+: \\w+ \\((?<"TokenRank">\\w+), (?<"TokenOrder">\\w+)\\)" options:0 error:®exError]; if (_nwiRegex == nil || regexError != nil) { specs_log_err("Failed to create NWI regex: %@", regexError); return nil; } regexError = nil; _dnsRegex = [[NSRegularExpression alloc] initWithPattern:@"resolver #\\d+\\n" "( domain : (?<"TokenDomain">\\S+)\\n)?" "(?<"TokenSearchDomains">(?: search domain\\[\\d+\\] : \\S+\\n)*)" "(?<"TokenNameServers">(?: nameserver\\[\\d+\\] : \\S+\\n)*)" "( if_index : \\d+ \\((?<"TokenInterfaceName">\\w+)\\)\\n)" "( flags : \\w+ \\((?<"TokenFlagsDescription">.+)\\)\\n)" "( reach : \\w+ \\((?<"TokenReachabilityDescription">.+)\\)\\n)" options:0 error:®exError]; if (_dnsRegex == nil || regexError != nil) { specs_log_err("Failed to create DNS configuration regex: %@", regexError); return nil; } regexError = nil; _nameserverRegex = [[NSRegularExpression alloc] initWithPattern:@" nameserver\\[\\d+\\] : (?<"TokenAddress">\\S+)\\n" options:0 error:®exError]; if (_nameserverRegex == nil || regexError != nil) { specs_log_err("Failed to create the nameserver regex: %@", regexError); return nil; } regexError = nil; _searchDomainRegex = [[NSRegularExpression alloc] initWithPattern:@" search domain\\[\\d+\\] : (?<"TokenDomain">\\S+)\\n" options:0 error:®exError]; if (_searchDomainRegex == nil || regexError != nil) { specs_log_err("Failed to create the search domain regex: %@", regexError); return nil; } NSArray *matches = @[ [[EFLogEventMatch alloc] initWithPattern:@"^Network information" multipleNewEventHandler: ^NSArray *(__unused NSTextCheckingResult *matchResult, EFLogEvent *logEvent) { NSMutableDictionary *newEvents = nil; NSArray *matches = [self.nwiRegex matchesInString:logEvent.eventMessage options:0 range:NSMakeRange(0, logEvent.eventMessage.length)]; BOOL primaryV4 = YES; BOOL primaryV6 = YES; for (NSString *interfaceName in SCLogParser.interfaceMap.allKeys) { SCLogParser.interfaceMap[interfaceName] = @[ ]; } for (NSTextCheckingResult *match in matches) { NSString *interfaceName = [logEvent substringForCaptureGroup:@TokenInterfaceName inMatchResult:match]; if (interfaceName == nil) { continue; } EFNetworkControlPathEvent *event = newEvents[interfaceName]; if (event == nil) { event = [self createInterfaceEventWithLogEvent:logEvent matchResult:match]; if (newEvents == nil) { newEvents = [[NSMutableDictionary alloc] init]; } newEvents[event.interfaceBSDName] = event; event.primaryStateIPv4 = EFPrimaryStateNotPrimary; event.primaryStateIPv6 = EFPrimaryStateNotPrimary; } NSString *addressString = [logEvent substringForCaptureGroup:@TokenAddress inMatchResult:match]; if (addressString.length > 0) { if (primaryV4 || primaryV6) { sa_family_t addressFamily = [self getAddressFamilyOfAddress:addressString]; if (primaryV4 && addressFamily == AF_INET) { event.primaryStateIPv4 = EFPrimaryStatePrimary; primaryV4 = NO; } else if (primaryV6 && addressFamily == AF_INET6) { event.primaryStateIPv6 = EFPrimaryStatePrimary; primaryV6 = NO; } } [self addAddress:addressString toInterfaceEvent:event]; } NSString *rankString = [logEvent substringForCaptureGroup:@TokenRank inMatchResult:match]; if (rankString.length > 0) { event.rank = rankString; } NSString *orderString = [logEvent substringForCaptureGroup:@TokenOrder inMatchResult:match]; if (orderString.length > 0) { if ([orderString isEqualToString:@"Last"]) { event.order = -1; } else { event.order = orderString.integerValue; } } } return newEvents.allValues; }], [[EFLogEventMatch alloc] initWithPattern:@"^DNS Configuration" multipleNewEventHandler: ^NSArray *(__unused NSTextCheckingResult *matchResult, EFLogEvent *logEvent) { NSMutableArray *newEvents = nil; NSArray *matches = [self.dnsRegex matchesInString:logEvent.eventMessage options:0 range:NSMakeRange(0, logEvent.eventMessage.length)]; NSMutableDictionary *> *interfaceDNSConfigurations = nil; NSMutableArray *> *orderedDNSConfigurations = nil; for (NSTextCheckingResult *match in matches) { NSMutableDictionary *dnsConfiguration = [[NSMutableDictionary alloc] init]; NSString *matchDomain = [logEvent substringForCaptureGroup:@TokenDomain inMatchResult:match]; BOOL scoped = NO; if (matchDomain.length > 0) { NSArray *domains = (NSArray *)dnsConfiguration[ResolverMatchDomainsKey]; dnsConfiguration[ResolverMatchDomainsKey] = [self addUniqueString:matchDomain toArray:domains]; } NSString *searchDomainsString = [logEvent substringForCaptureGroup:@TokenSearchDomains inMatchResult:match]; if (searchDomainsString.length > 0) { [self addSubstringsFromString:searchDomainsString forCaptureGroup:@TokenDomain inRegex:self.searchDomainRegex toArrayAtKey:ResolverSearchDomainsKey inDictionary:dnsConfiguration]; } NSString *nameServersString = [logEvent substringForCaptureGroup:@TokenNameServers inMatchResult:match]; if (nameServersString.length > 0) { [self addSubstringsFromString:nameServersString forCaptureGroup:@TokenAddress inRegex:self.nameserverRegex toArrayAtKey:ResolverNameServersKey inDictionary:dnsConfiguration]; } NSString *flagsDescription = [logEvent substringForCaptureGroup:@TokenFlagsDescription inMatchResult:match]; if (flagsDescription.length > 0) { dnsConfiguration[ResolverFlagsDescriptionKey] = flagsDescription; if ([flagsDescription containsString:@"Scoped"]) { scoped = YES; } } NSString *reachabilityDescription = [logEvent substringForCaptureGroup:@TokenReachabilityDescription inMatchResult:match]; if (reachabilityDescription.length > 0) { dnsConfiguration[ResolverReachabilityDescriptionKey] = reachabilityDescription; } NSString *interfaceName = [logEvent substringForCaptureGroup:@TokenInterfaceName inMatchResult:match]; if (interfaceName != nil) { dnsConfiguration[ResolverInterfaceNameKey] = interfaceName; } NSDictionary *newConfiguration = nil; if (interfaceName != nil && (scoped || matchDomain.length > 0)) { NSDictionary *existingConfiguration = interfaceDNSConfigurations[interfaceName]; if (existingConfiguration != nil) { if (matchDomain.length > 0) { NSArray *matchDomains = (NSArray *)existingConfiguration[ResolverMatchDomainsKey]; dnsConfiguration[ResolverMatchDomainsKey] = [self addUniqueString:matchDomain toArray:matchDomains]; } else { dnsConfiguration[ResolverMatchDomainsKey] = existingConfiguration[ResolverMatchDomainsKey]; } } newConfiguration = [[NSDictionary alloc] initWithDictionary:dnsConfiguration]; if (interfaceDNSConfigurations == nil) { interfaceDNSConfigurations = [[NSMutableDictionary alloc] init]; } interfaceDNSConfigurations[interfaceName] = newConfiguration; } else { newConfiguration = [[NSDictionary alloc] initWithDictionary:dnsConfiguration]; } if (!scoped) { if (orderedDNSConfigurations == nil) { orderedDNSConfigurations = [[NSMutableArray alloc] init]; } NSUInteger existingIndex = [orderedDNSConfigurations indexOfObjectPassingTest: ^BOOL(NSDictionary *obj, __unused NSUInteger idx, __unused BOOL *stop) { NSString *existingInterfaceName = (NSString *)obj[ResolverInterfaceNameKey]; return (existingInterfaceName != nil && [interfaceName isEqualToString:existingInterfaceName]); }]; if (existingIndex == NSNotFound) { [orderedDNSConfigurations addObject:newConfiguration]; } else { orderedDNSConfigurations[existingIndex] = newConfiguration; } } } for (NSString *interfaceName in interfaceDNSConfigurations) { NSDictionary *dnsConfiguration = interfaceDNSConfigurations[interfaceName]; if (dnsConfiguration == nil || ![NSJSONSerialization isValidJSONObject:dnsConfiguration]) { specs_log_err("DNS configuration is not valid JSON: %@", dnsConfiguration); continue; } NSError *jsonError = nil; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dnsConfiguration options:NSJSONWritingPrettyPrinted error:&jsonError]; if (jsonData == nil) { specs_log_err("Failed to generate JSON from %@: %@", dnsConfiguration, jsonError); continue; } EFNetworkControlPathEvent *newEvent = [self createInterfaceEventWithLogEvent:logEvent interfaceName:interfaceName]; newEvent.dnsConfiguration = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; if (newEvents == nil) { newEvents = [[NSMutableArray alloc] init]; } [newEvents addObject:newEvent]; } if (orderedDNSConfigurations.count > 0) { NSError *jsonError = nil; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:orderedDNSConfigurations options:NSJSONWritingPrettyPrinted error:&jsonError]; if (jsonData != nil) { NSData *subsystemIdentifier = [self createSubsystemIdentifier]; EFNetworkControlPathEvent *newEvent = [[EFNetworkControlPathEvent alloc] initWithLogEvent:logEvent subsystemIdentifier:subsystemIdentifier]; newEvent.interfaceBSDName = @"system"; newEvent.interfaceDescription = @"System Configuration"; newEvent.dnsConfiguration = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; if (newEvents == nil) { newEvents = [[NSMutableArray alloc] init]; } [newEvents addObject:newEvent]; } } for (NSString *interfaceName in SCLogParser.interfaceMap) { if (interfaceDNSConfigurations[interfaceName] == nil) { EFNetworkControlPathEvent *newEvent = [self createInterfaceEventWithLogEvent:logEvent interfaceName:interfaceName]; newEvent.dnsConfiguration = EFNetworkControlPathEvent.configurationNotSet; if (newEvents == nil) { newEvents = [[NSMutableArray alloc] init]; } [newEvents addObject:newEvent]; } } return newEvents; }], ]; EFLogEventParser *parser = [[EFLogEventParser alloc] initWithMatches:matches]; return [super initWithCategory:@"StateDump" eventParser:parser]; } - (void)addSubstringsFromString:(NSString *)string forCaptureGroup:(NSString *)groupName inRegex:(NSRegularExpression *)regex toArrayAtKey:(NSString *)configKey inDictionary:(NSMutableDictionary *)dictionary { NSArray *matches = [regex matchesInString:string options:0 range:NSMakeRange(0, string.length)]; for (NSTextCheckingResult *match in matches) { NSRange groupRange = [match rangeWithName:groupName]; if (!NSEqualRanges(groupRange, NSMakeRange(NSNotFound, 0))) { NSString *substring = [string substringWithRange:groupRange]; if (substring.length > 0) { NSArray *existingList = (NSArray *)dictionary[configKey]; dictionary[configKey] = [self addUniqueString:substring toArray:existingList]; } } } } @end