2 * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * https://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #import <TargetConditionals.h>
19 // _createDispatchData in NSData_Private.h requires either
20 // DEPLOYMENT_TARGET_EMBEDDED or DEPLOYMENT_TARGET_MACOSX.
21 // We define them here, rather than in our project file.
24 // DEPLOYMENT_TARGET_EMBEDDED covers both embedded and simulator
25 # ifndef DEPLOYMENT_TARGET_EMBEDDED
26 # define DEPLOYMENT_TARGET_EMBEDDED 1
27 # endif // DEPLOYMENT_TARGET_EMBEDDED
28 #else // TARGET_OS_IPHONE
29 # ifndef DEPLOYMENT_TARGET_MACOSX
30 # define DEPLOYMENT_TARGET_MACOSX 1
31 # endif // DEPLOYMENT_TARGET_MACOSX
32 #endif // TARGET_OS_IPHONE
34 #import <Foundation/NSData_Private.h>
35 #import <CFNetwork/CFNSURLConnection.h>
36 #import <nw/private.h>
38 #import "mdns_symptoms.h"
39 #import "DNSMessage.h"
40 #import "HTTPUtilities.h"
41 #import <CoreFoundation/CFXPCBridge.h>
42 #import "DNSHeuristics.h"
43 #import <Foundation/Foundation.h>
47 shared_session(dispatch_queue_t queue)
49 static NSURLSession *session = nil;
50 static dispatch_once_t onceToken;
51 dispatch_once(&onceToken, ^{
53 // Disable AppSSO (process-wide) before any use of URLSession
54 [NSURLSession _disableAppSSO];
56 // Create (and configure) the NSURLSessionConfiguration
57 NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
59 // Disable HTTP Cookies
60 configuration.HTTPCookieStorage = nil;
63 configuration.URLCache = nil;
65 // Disable Credential Storage
66 configuration.URLCredentialStorage = nil;
68 if (@available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *)) {
69 // Disable ATS (process-wide) before any use of URLSession
70 [NSURLSession _disableATS];
72 // Disable reachability lookups
73 configuration._allowsReachabilityCheck = NO;
76 configuration._suppressedAutoAddedHTTPHeaders = [NSSet setWithObjects:@"User-Agent", nil];
77 configuration._allowsTLSSessionTickets = YES;
78 configuration._allowsTCPFastOpen = YES;
80 NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
81 operationQueue.underlyingQueue = queue;
82 session = [NSURLSession sessionWithConfiguration:configuration
84 delegateQueue:operationQueue];
91 create_base64_string(dispatch_data_t message)
94 NSString *base64String = [((NSData *)message) base64EncodedStringWithOptions:0];
95 base64String = [base64String stringByReplacingOccurrencesOfString:@"/"
97 base64String = [base64String stringByReplacingOccurrencesOfString:@"+"
99 return (__bridge_retained CFStringRef)base64String;
104 http_set_resolver_queue(dispatch_queue_t queue)
108 (void)shared_session(queue);
113 http_task_create_dns_query(nw_endpoint_t endpoint, const char *urlString, dispatch_data_t message, uint16_t query_type, bool use_post, http_task_dns_query_response_handler_t response_handler)
116 NSURLSession *session = shared_session(nil);
117 NSMutableURLRequest *request = nil;
119 request = [[NSMutableURLRequest alloc] initWithURL:(NSURL *)[[NSURL alloc] initWithString:(NSString *)@(urlString)]];
120 request.HTTPMethod = @"POST";
121 request.HTTPBody = (NSData *)message;
123 NSString *base64String = [((NSData *)message) base64EncodedStringWithOptions:0];
124 base64String = [base64String stringByReplacingOccurrencesOfString:@"/"
126 base64String = [base64String stringByReplacingOccurrencesOfString:@"+"
128 base64String = [base64String stringByReplacingOccurrencesOfString:@"="
130 NSString *urlWithQuery = [NSString stringWithFormat:@"%s?dns=%@", urlString, base64String];
131 request = [[NSMutableURLRequest alloc] initWithURL:(NSURL *)[[NSURL alloc] initWithString:urlWithQuery]];
132 request.HTTPMethod = @"GET";
134 [request setValue:@"application/dns-message" forHTTPHeaderField:@"accept"];
135 [request setValue:@"application/dns-message" forHTTPHeaderField:@"content-type"];
137 __block nw_activity_t activity = nil;
138 switch (query_type) {
139 case kDNSRecordType_A: {
140 activity = nw_activity_create(kDNSActivityDomain, kDNSActivityLabelUnicastAQuery);
143 case kDNSRecordType_AAAA: {
144 activity = nw_activity_create(kDNSActivityDomain, kDNSActivityLabelUnicastAAAAQuery);
148 // Don't mark any activity for non-address queries.
153 if (activity != nil) {
154 nw_activity_activate(activity);
157 NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
158 completionHandler:^(NSData *data,
159 __unused NSURLResponse *response,
161 if (activity != nil) {
163 nw_activity_complete_with_reason(activity, nw_activity_completion_reason_failure);
165 nw_activity_complete_with_reason(activity, nw_activity_completion_reason_success);
170 // If we did not receive a response from the server, report a failure.
171 // Any HTTP response, even one with a 50x failure, will yield a nil error.
172 // Any other error, such as a TCP-level or TLS-level failure, will manifest
173 // in a non-nil error. We only care about non-HTTP errors here.
175 // Some NSURLSession errors are also ignored here, such as when a task
176 // is cancelled (a common occurrence due to the client behavior) or the
177 // connection failed due to no network.
178 const bool errorIsWhitelisted = ([error.domain isEqualToString:NSURLErrorDomain] &&
179 (error.code == NSURLErrorCancelled ||
180 error.code == NSURLErrorNotConnectedToInternet));
181 const bool errorIsTimeout = ([error.domain isEqualToString:NSURLErrorDomain] &&
182 error.code == NSURLErrorTimedOut);
183 if (!errorIsWhitelisted) {
184 dns_heuristics_report_resolution_failure([request URL], errorIsTimeout);
187 dns_heuristics_report_resolution_success();
190 dispatch_data_t dispatch_data = [data _createDispatchData];
191 response_handler(dispatch_data, (__bridge CFErrorRef)error);
194 if (@available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *)) {
195 dataTask._hostOverride = endpoint;
198 if (dataTask && activity != nil) {
199 dataTask._nw_activity = activity;
202 return (__bridge_retained void *)dataTask;
207 http_task_create_pvd_query(dispatch_queue_t queue, const char *host, const char *path, void (^response_handler)(xpc_object_t json_object))
210 NSURLSession *session = shared_session(nil);
211 NSString *pvdURL = [NSString stringWithFormat:@"https://%s/.well-known/pvd%s", host, path];
212 NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:(NSURL *)[[NSURL alloc] initWithString:pvdURL]];
213 request.HTTPMethod = @"GET";
214 [request setValue:@"application/pvd+json" forHTTPHeaderField:@"accept"];
215 [request setValue:@"application/pvd+json" forHTTPHeaderField:@"content-type"];
217 __block nw_activity_t activity = nw_activity_create(kDNSActivityDomain, kDNSActivityLabelProvisioningRequest);
218 if (activity != nil) {
219 nw_activity_activate(activity);
222 NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
223 completionHandler:^(NSData *data,
224 __unused NSURLResponse *response,
225 __unused NSError *error) {
226 dispatch_async(queue, ^{
228 nw_activity_complete_with_reason(activity, nw_activity_completion_reason_failure);
229 response_handler(nil);
231 NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
232 if ([dictionary isKindOfClass:[NSDictionary class]]) {
233 xpc_object_t xpc_dictionary = _CFXPCCreateXPCObjectFromCFObject((__bridge CFDictionaryRef)dictionary);
235 // Convert "expires" to "seconds-remaining"
236 NSString *expires = dictionary[@"expires"];
237 NSNumber *secondsRemaining = dictionary[@"seconds-remaining"];
239 if (xpc_dictionary != nil &&
240 [expires isKindOfClass:[NSString class]] && secondsRemaining == nil) {
241 NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
242 [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
243 [dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
244 [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"];
245 [dateFormatter setFormatterBehavior:NSDateFormatterBehaviorDefault];
247 NSDate *date = [dateFormatter dateFromString:expires];
249 NSTimeInterval secondsFromNowFloat = date.timeIntervalSinceNow;
250 uint64_t secondsFromNow = (uint64_t)secondsFromNowFloat;
252 xpc_dictionary_set_uint64(xpc_dictionary, "seconds-remaining", secondsFromNow);
253 } else if (xpc_dictionary != nil && secondsRemaining != nil) {
254 uint64_t secondsFromNow = (uint64_t)secondsRemaining.unsignedLongLongValue;
255 xpc_dictionary_set_uint64(xpc_dictionary, "seconds-remaining", secondsFromNow);
258 nw_activity_complete_with_reason(activity, nw_activity_completion_reason_success);
259 response_handler(xpc_dictionary);
261 nw_activity_complete_with_reason(activity, nw_activity_completion_reason_failure);
262 response_handler(nil);
268 if (dataTask && activity != nil) {
269 dataTask._nw_activity = nw_activity_create(kDNSActivityDomain, kDNSActivityLabelProvisioningRequest);
272 return (__bridge_retained void *)dataTask;
277 http_task_start(void *task)
280 NSURLSessionDataTask *dataTask = (__bridge NSURLSessionDataTask *)task;
286 http_task_cancel(void *task)
289 NSURLSessionDataTask *dataTask = (__bridge_transfer NSURLSessionDataTask *)task;