@meaton I have more information. As a last resort, we tried adding a mechanism to kill the system extension when the issue occurs, as it appeared to fix the issue in previous tests. It doesn't fix the issue, but it does change its root cause. Elsewhere in our application, we periodically submit a NSURLSessionTask for an HTTP POST request. When connectivity is lost, the request fails with the following error:
Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={_kCFStreamErrorCodeKey=-2103, _NSURLErrorFailingURLSessionTaskErrorKey=LocalUploadTask <03BFA491-37B2-419C-B69A-C3217558760B>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalUploadTask <03BFA491-37B2-419C-B69A-C3217558760B>.<1>"
), NSLocalizedDescription=The request timed out., NSErrorFailingURLStringKey=<URL>, NSErrorFailingURLKey=<URL>, _kCFStreamErrorDomainKey=4}
Our watchdog mechanism notices that the network extension isn't being notified of new network flows anymore (we have a separate mechanism that periodically starts a new UDP flow, to ensure a constant stream of calls to -[NEFilterDataProvider handleNewFlow:]. If we stop receiving calls to that method, it must be some kind of system issue/bug), and kills the network extension process with a KILL signal (don't judge us, we really need this to work). This does not restore connectivity; in fact, the aforementioned NSURLSessionTask starts failing with a different error:
Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo={_kCFStreamErrorCodeKey=50, NSUnderlyingError=0x60000355c180 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_NSURLErrorNWPathKey=unsatisfied (Path was denied by NECP policy), interface: en1[802.11], ipv4, dns, _kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalUploadTask <F7A60E10-A65C-4D1F-9954-8A0325B2CB88>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalUploadTask <F7A60E10-A65C-4D1F-9954-8A0325B2CB88>.<1>"
), NSLocalizedDescription=The Internet connection appears to be offline., NSErrorFailingURLStringKey=<URL>, NSErrorFailingURLKey=<URL>, _kCFStreamErrorDomainKey=1}
Note that the failure is in DNS specifically (interface: en1[802.11], ipv4, dns).
Needless to say, we don't explicitly invoke any NECP policies. This is how we create and submit the POST request:
void sendHttpRequest(NSURL *url,
const char *jwt,
NSString *userAgent,
NSData *data,
void (^completionHandler)(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error))
{
NSURLSessionConfiguration *const configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.TLSMinimumSupportedProtocolVersion = tls_protocol_version_TLSv12;
configuration.timeoutIntervalForResource = 30;
configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalAndRemoteCacheData;
configuration.HTTPCookieStorage = nil;
configuration.URLCredentialStorage = nil;
configuration.URLCache = nil;
NSURLSession *const session = [NSURLSession sessionWithConfiguration:configuration];
NSMutableURLRequest *const request = [NSMutableURLRequest requestWithURL:url];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:[NSString stringWithFormat:@"Bearer %s", jwt] forHTTPHeaderField:@"Authorization"];
[request setValue:userAgent forHTTPHeaderField:@"User-Agent"];
request.HTTPMethod = @"POST";
NSURLSessionTask *const task = [session uploadTaskWithRequest:request fromData:data completionHandler:completionHandler];
[task resume];
}