]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSMacOSX/HTTPUtilities.m
mDNSResponder-1310.80.1.tar.gz
[apple/mdnsresponder.git] / mDNSMacOSX / HTTPUtilities.m
1 /*
2 * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
3 *
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
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17 #import <TargetConditionals.h>
18
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.
22
23 #if TARGET_OS_IPHONE
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
33
34 #import <Foundation/NSData_Private.h>
35 #import <CFNetwork/CFNSURLConnection.h>
36 #import <nw/private.h>
37
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>
44 #import <os/log.h>
45
46 static NSURLSession *
47 shared_session(dispatch_queue_t queue)
48 {
49 static NSURLSession *session = nil;
50 static dispatch_once_t onceToken;
51 dispatch_once(&onceToken, ^{
52 @autoreleasepool {
53 // Disable AppSSO (process-wide) before any use of URLSession
54 [NSURLSession _disableAppSSO];
55
56 // Create (and configure) the NSURLSessionConfiguration
57 NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
58
59 // Disable HTTP Cookies
60 configuration.HTTPCookieStorage = nil;
61
62 // Disable HTTP Cache
63 configuration.URLCache = nil;
64
65 // Disable Credential Storage
66 configuration.URLCredentialStorage = nil;
67
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];
71
72 // Disable reachability lookups
73 configuration._allowsReachabilityCheck = NO;
74 }
75
76 configuration._suppressedAutoAddedHTTPHeaders = [NSSet setWithObjects:@"User-Agent", nil];
77 configuration._allowsTLSSessionTickets = YES;
78 configuration._allowsTCPFastOpen = YES;
79
80 NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
81 operationQueue.underlyingQueue = queue;
82 session = [NSURLSession sessionWithConfiguration:configuration
83 delegate:nil
84 delegateQueue:operationQueue];
85 }
86 });
87 return session;
88 }
89
90 CFStringRef
91 create_base64_string(dispatch_data_t message)
92 {
93 @autoreleasepool {
94 NSString *base64String = [((NSData *)message) base64EncodedStringWithOptions:0];
95 base64String = [base64String stringByReplacingOccurrencesOfString:@"/"
96 withString:@"_"];
97 base64String = [base64String stringByReplacingOccurrencesOfString:@"+"
98 withString:@"-"];
99 return (__bridge_retained CFStringRef)base64String;
100 }
101 }
102
103 void
104 http_set_resolver_queue(dispatch_queue_t queue)
105 {
106 @autoreleasepool {
107 // Set up session
108 (void)shared_session(queue);
109 }
110 }
111
112 void *
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)
114 {
115 @autoreleasepool {
116 NSURLSession *session = shared_session(nil);
117 NSMutableURLRequest *request = nil;
118 if (use_post) {
119 request = [[NSMutableURLRequest alloc] initWithURL:(NSURL *)[[NSURL alloc] initWithString:(NSString *)@(urlString)]];
120 request.HTTPMethod = @"POST";
121 request.HTTPBody = (NSData *)message;
122 } else {
123 NSString *base64String = [((NSData *)message) base64EncodedStringWithOptions:0];
124 base64String = [base64String stringByReplacingOccurrencesOfString:@"/"
125 withString:@"_"];
126 base64String = [base64String stringByReplacingOccurrencesOfString:@"+"
127 withString:@"-"];
128 base64String = [base64String stringByReplacingOccurrencesOfString:@"="
129 withString:@""];
130 NSString *urlWithQuery = [NSString stringWithFormat:@"%s?dns=%@", urlString, base64String];
131 request = [[NSMutableURLRequest alloc] initWithURL:(NSURL *)[[NSURL alloc] initWithString:urlWithQuery]];
132 request.HTTPMethod = @"GET";
133 }
134 [request setValue:@"application/dns-message" forHTTPHeaderField:@"accept"];
135 [request setValue:@"application/dns-message" forHTTPHeaderField:@"content-type"];
136
137 __block nw_activity_t activity = nil;
138 switch (query_type) {
139 case kDNSRecordType_A: {
140 activity = nw_activity_create(kDNSActivityDomain, kDNSActivityLabelUnicastAQuery);
141 break;
142 }
143 case kDNSRecordType_AAAA: {
144 activity = nw_activity_create(kDNSActivityDomain, kDNSActivityLabelUnicastAAAAQuery);
145 break;
146 }
147 default: {
148 // Don't mark any activity for non-address queries.
149 break;
150 }
151 }
152
153 if (activity != nil) {
154 nw_activity_activate(activity);
155 }
156
157 NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
158 completionHandler:^(NSData *data,
159 __unused NSURLResponse *response,
160 NSError *error) {
161 if (activity != nil) {
162 if (error != nil) {
163 nw_activity_complete_with_reason(activity, nw_activity_completion_reason_failure);
164 } else {
165 nw_activity_complete_with_reason(activity, nw_activity_completion_reason_success);
166 }
167 }
168
169 if (error != nil) {
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.
174
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);
185 }
186 } else {
187 dns_heuristics_report_resolution_success();
188 }
189
190 dispatch_data_t dispatch_data = [data _createDispatchData];
191 response_handler(dispatch_data, (__bridge CFErrorRef)error);
192 }];
193
194 if (@available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *)) {
195 dataTask._hostOverride = endpoint;
196 }
197
198 if (dataTask && activity != nil) {
199 dataTask._nw_activity = activity;
200 }
201
202 return (__bridge_retained void *)dataTask;
203 }
204 }
205
206 void *
207 http_task_create_pvd_query(dispatch_queue_t queue, const char *host, const char *path, void (^response_handler)(xpc_object_t json_object))
208 {
209 @autoreleasepool {
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"];
216
217 __block nw_activity_t activity = nw_activity_create(kDNSActivityDomain, kDNSActivityLabelProvisioningRequest);
218 if (activity != nil) {
219 nw_activity_activate(activity);
220 }
221
222 NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
223 completionHandler:^(NSData *data,
224 __unused NSURLResponse *response,
225 __unused NSError *error) {
226 dispatch_async(queue, ^{
227 if (data == nil) {
228 nw_activity_complete_with_reason(activity, nw_activity_completion_reason_failure);
229 response_handler(nil);
230 } else {
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);
234
235 // Convert "expires" to "seconds-remaining"
236 NSString *expires = dictionary[@"expires"];
237 NSNumber *secondsRemaining = dictionary[@"seconds-remaining"];
238
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];
246
247 NSDate *date = [dateFormatter dateFromString:expires];
248
249 NSTimeInterval secondsFromNowFloat = date.timeIntervalSinceNow;
250 uint64_t secondsFromNow = (uint64_t)secondsFromNowFloat;
251
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);
256 }
257
258 nw_activity_complete_with_reason(activity, nw_activity_completion_reason_success);
259 response_handler(xpc_dictionary);
260 } else {
261 nw_activity_complete_with_reason(activity, nw_activity_completion_reason_failure);
262 response_handler(nil);
263 }
264 }
265 });
266 }];
267
268 if (dataTask && activity != nil) {
269 dataTask._nw_activity = nw_activity_create(kDNSActivityDomain, kDNSActivityLabelProvisioningRequest);
270 }
271
272 return (__bridge_retained void *)dataTask;
273 }
274 }
275
276 void
277 http_task_start(void *task)
278 {
279 @autoreleasepool {
280 NSURLSessionDataTask *dataTask = (__bridge NSURLSessionDataTask *)task;
281 [dataTask resume];
282 }
283 }
284
285 void
286 http_task_cancel(void *task)
287 {
288 @autoreleasepool {
289 NSURLSessionDataTask *dataTask = (__bridge_transfer NSURLSessionDataTask *)task;
290 [dataTask cancel];
291 dataTask = nil;
292 }
293 }