IOS Checking Active internet connection even connected via WIFI without internet - ios

Please help me out how to check internet connection in iPhone.
I tried by checking the Apple's reachability framework, but it is not returning if connected via WIFI without internet.
What I am getting is reachable via wifi.
The other option I tried is making a synchronous request with google.com etc and checking the response code.
Is there any other way to check so that I can avoid making a url request and checking the response code.
Thanks in advance.

Check if wifi reachable (using Apple's reachability), and then make request to ensure host is reachable.
// wifi checking code here
//...
[[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:#"http://yourServer.com/someLightFile.html"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
if (!error) {
// Your host is reachable
}
}] resume];

Related

Check if Internet access is available

In my app I want to make a network call only if I can access the internet.
Note: I'm connected to a WiFi spot that doesn't have Internet
I want to test if Internet is available. I tried using Reachability as described here and I also tried a simpler solution as described here
The problem is that with Reachability is that it returns that the Internet is reachable when it's not. The other solution takes too much time to give a response. I tried to set timeout intervals for the request and the session, but it's getting ignored.
How can I test for internet reachability in case I'm connected to a wifi with no Internet?
Here some code that I use:
- (void) postAsyncTaskWithUrl:(NSString*)urlString
andType:(NSString*)requestType
andToken:(NSString*)token
andPropertiesObject:(id)propObject
urlEncoded:(BOOL)isUrlEncoded
withSuccess:(nullable void(^)(id _Nullable))success
andFailure:(nullable void(^)(id _Nullable))failure
{
internetReachableFoo = [Reachability reachabilityWithHostname:#"www.google.com"];
__weak typeof(self) weakSelf = self;
// Internet is reachable
internetReachableFoo.reachableBlock = ^(Reachability*reach)
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:urlSt];
[request setTimeoutInterval:20]; //Ignored ... WHY?
NSURLSessionConfiguration *sessionConfigurations = [NSURLSessionConfiguration defaultSessionConfiguration];
[sessionConfigurations setTimeoutIntervalForRequest:20]; //Ignored ... WHY?
[sessionConfigurations setTimeoutIntervalForResource:20]; //Ignored ... WHY?
// NSURLSession *session = [NSURLSession sharedSession];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfigurations];
[[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}
}
}
Despite this being something that was historically done in the past, I don't believe that it's worth trying to check if the network is reachable, as it isn't something that's reliable or something that should be done before making a request in almost all cases now.
According to Apple's official documentation:
Always attempt to make a connection. Do not attempt to guess whether
network service is available, and do not cache that determination
Similar blog post from Jared Sinclair on this topic, using SCNetworkReachability (which is what the Reachability library uses under the hood):
SCNetworkReachability should only be used to influence what you do about a network request that has already failed, not an initial request that has yet to be attempted. Use a negative status to determine whether or not you attempt an automatic retry, or to tweak the user-facing language of an alert. Use a positive status to consider retrying an earlier failed request. Never prevent a user-initiated request from being attempted just because SCNetworkReachability thinks there’s not a reachable network
Borrowing from a recent Reddit thread:
Reachability is one or "these topics" in iOS world. What does it mean
that the Internet is reachable? Even if you check network interfaces,
you may not be able to reach apple.com or google.com. Even if you
reach Google, you may not be able to reach your on-prem server or
cloud. If you check whether you can reach your server of interest, why
not to send a request straight away?
TL;DR: You probably want an NSTimer.
In my app I want to make a network call only if I can access the internet.
This is not a meaningful statement. There is no test you can perform that reliably means you can "access the internet" (despite the existence of NSURLErrorNotConnectedToInternet, which doesn't actually mean what it says). For example, your code is trying to treat "www.google.com" as "the internet." But it is completely possible to have a network that permits packets to go to www.google.com, but nowhere else. Or to permit ICMP packets, but not HTTP packets. Or to permit HTTP packets, but not on certain ports. Such network configurations are not just possible, they're common, and in various ways they all can look like "not on the internet."
The only thing you can answer is "can I send a packet to a specific host and receive a packet in return?" It's not even possible to know whether you can just send a packet. If you don't get something in return, you don't know if your packet was delivered or not.
Reachability answers the question "if I tried to send a packet, would my local network stack even try?" It is basically the question of whether there is a first-hop available in the routing table. It doesn't mean you can actually connect; it can only tell you that it absolutely won't.
The timeouts you're using are based on idle-time on a session. But you never start the session, so it never idles. The timeout you're looking for is the TCP timeout, which I expect to be 4 minutes. NSURLSession can't configure it.
If you want a wall-clock "start-to-finish" timeout, then you need to run it yourself with a Timer.
If you try to load http://captive.apple.com it will return hardcoded string “Success” as a body if internet works or will redirect / fail. You can check for response starts to be 200 and body to be “success”. That’s what Apple is using to automatically show the credentials window when connected to airport like WiFi’s
Saying that
a) there’s no foolproof solution
b) you will be wasting user’s data (might be ok in your use case?)

NSURLSession Redirection handler different behaviour on WatchOS

I am working on updating an old WatchOS app to support WatchOS 4.0. The app makes some network requests, one of which responds with a 302 redirect status code and corresponding Location header. I do not want to follow the redirection, and the 302 response headers contains all the info I need for this particular request.
I am using NSURLSession and have implemented the delegate method - URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:.
The delegate method is called as expected, and inside it I call completionHandler(nil) to tell the OS not to follow the redirect. All is fine up to this point.
The NSURLSessionTask completion handler at this point typically gets called immediately after this with a request cancelled error, and that's fine too.
Here's the strange part. When the Apple Watch is paired with an iPhone, I get the behaviour described above. However when I put the iPhone into Aeroplane Mode to simulate the Watch being out of range, with the network requests being made directly over wifi or cellular, I get a different behaviour.
The redirection delegate method is still called, as expected, however the NSURLSessionTask completion handler does not get called immediately. Rather, it gets called some time later with a -1001 Request Timed Out error.
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// Odd behaviour described above here
}];
[task resume];
The delegate redirection method:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
completionHandler(nil);
}
This has been frustratingly difficult to debug to this point as I cannot debug through Xcode and have not been able to find a way to proxy the watch network requests.
Has anyone experienced this behaviour, or have any clues on why it's behaving differently? Is this a WatchOS defect?

NSURLSessionDownloadTask switching from http to https

I have an AFHTTPSessionManager created NSURLSessionDownloadTask being used to download a video within an app that sometimes when initialized with an http:80 url will convert it to https:443. This is happening before any connection attempt is being made (I added a custom HTTP protocol class via NSURLSessionConfiguration to the session in order to log when the connection is being made).
By the time the request makes it to the
-(NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
delegate method of my CustomHTTPProtocolDelegate class, the request has been changed to https.
App Transport Security is disabled (NSAllowsArbitraryLoads=true) and this behavior seems to be associated with a particular http-only server (other http-only server have no issue, and the connection is made as http on port 80).
Any idea of what could be going on? Anything else I could do to debug?
Here is how the download task is being created (including the debug custom protocol class):
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionConfiguration* config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
config.protocolClasses = #[[CustomHTTPProtocol class]];
AFHTTPSessionManager *session = [[AFHTTPSessionManager manager] initWithSessionConfiguration:config];
self.downloadTask = [session downloadTaskWithRequest:request progress:&progress destination:^NSURL *(NSURL *targetPath, NSURLResponse *response){
...
[UPDATE]
This issue is causing us a number of headaches, so to help facilitate troubleshooting, I created a small test project to help narrow in on the problem. My simple project does two things, loads a url into a UIWebView and downloads a file using NSURLSessionDownloadTask. The urls for these actions follow this pattern:
WebView URL: https://console.company.com/home.html
Download URL: http://data.company.com/file.txt
And those hostnames resolve to different IPs (different servers).
If I download the file before navigating the webview, then everything is fine, but if the webview loads its URL first, then the download URL will be switched to HTTPS automatically and the initial request for data will fail. One thought we had was that once iOS opens a TLS tunnel for the HTTPS connection that the webview is creating, that it tries to use that same tunnel for all subsequent *.company.com connections. Or at the very least, it assumes all *.company.com connections must also be TLS.
Figured it out. Both servers were sending a HSTS header for all subdomains. Because the networking layer under NSURLSession observes this header, the calls to the HTTP server were being upgraded to HTTPS prior to leaving the client.

NSURLSession didReceiveChallenge only called once

I'm using NSURLSession dataTaskWithRequest:completionHandler: to make some requests to my server. I'm using a self-signed certificate, so when the delegate's URLSession:didReceiveChallenge:completionHandler: gets called, I can do my internal checks to verify it everything is fine.
This all works great on the first request I send. After I call the completion handler with NSURLSessionAuthChallengeUseCredential, then the next requests never call URLSession:didReceiveChallenge:completionHandler:.
For reasons I won't go into in this post, I would really like URLSession:didReceiveChallenge:completionHandler: to be called each time, so that I can do my own checking. I'm assuming NSURLSession is somehow caching the certificate and keeping it in some sort of "valid certificates" list so it's not doing this check each time. I want to clear this cache so that I can make my own check each time. If I restart the App, then URLSession:didReceiveChallenge:completionHandler: does get called once more.
I have tried setting the Credential Storage to nil with no success:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.URLCredentialStorage = nil;
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
[[session dataTaskWithRequest:request
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
...
}] resume];
Any ideas how to accomplish this?
Thank you!
For someone who tried everything here can be problem in the backend. So this problem was fixed by disabling ssl session caching in nginx server.

NSURLConnection SendAsynchronousRequest not working without Wi-Fi

Requests don't seem to be sent when in 3G. The code is called and it returns to the callback with a -1001 error and the following message: "(The request timed out.)"
Protocol is HTTP, not HTTPs.
Code works fine in Wi-Fi.
Wireshark on the server does not show any incoming data when app is in 3G (but data is received when in Wi-Fi).
After network inspection in Instruments: request is shown but no data / packets out.
Code Below:
self.request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:self.route]];
[self.request setHTTPMethod:self.httpMethod];
self.request.timeoutInterval = XX.f;
[NSURLConnection sendAsynchronousRequest:self.request
queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// Stuff actually getting called but with -1001 error code (only when NOT in Wi-Fi)
}
It seems that OSX 10.10 Beta can corrupt the network privileges of an app when installing from XCode.
The issue can be worked-around by completely uninstalling the app and rebooting the phone (pressing home and power button simultaneously).
The error code you get (-1001) resolves to NSURLErrorTimedOut, which means your request timed out. I recommend you check your firewall (at the server-end). Is your server accessible from your phone? Is port 80 accessible from your phone? Note that the IP address of your phone when you're using WiFi differs from the IP address you get using 3G.

Resources