7

In an Android app, I'm trying to test that the user has a working Internet connection. If you are interested, there is some background in a previous question Detecting limited network connectivity in Android?

The code is basically like:

try {
    HttpParams myParams = new BasicHttpParams();
    HttpConnectionParams.setConnectionTimeout(myParams, 10000);
    HttpConnectionParams.setSoTimeout(myParams, 10000);

    httpClient = new DefaultHttpClient(myParams);

    request = new HttpHead(url);
    response = httpClient.execute(request);
    statusCode = response.getStatusLine().getStatusCode();

    if (statusCode != 200)
    {       
        return false;
    }
    return true;

} catch(Exception e) {
    return false;
}

I can control the timeouts for Connection and Socket using HttpConnectionParams. But, if my device is connected to Wifi, but the wifi has no Internet access, the error I'm getting in the exception is:

libcore.io.GaiException: getaddrinfo failed: EAI_NODATA (No address associated with hostname)

Unable to resolve host "www.example.com": No address associated with hostname

Which looks like it timed out on a DNS lookup. Can I control the timeout of the DNS Lookup? httpClient.execute is taking about 45 seconds to fail with the exception noted above. I'd like it to give up sooner.

Community
  • 1
  • 1
Michael Levy
  • 13,097
  • 15
  • 66
  • 100

1 Answers1

13

I did a little homework and it seems that you cannot adjust the DNS lookup timeout. So, I figured a better approach would be to an explicit DNS lookup so I could control it (and hopefully have the result cached to speed the next attempt). So that led me to the simple:

InetAddress addr = InetAddress.getByName(hostname);

but this also had a 45 second timeout. Others had mentioned that there was no control of the timeout for getByName(). Finally, I stumbled on a simple solution, just launch the lookup in a separate thread and manage your own timeout. This blog post shows this quite well.

private static boolean testDNS(String hostname) {
  try
  {
    DNSResolver dnsRes = new DNSResolver(hostname);
    Thread t = new Thread(dnsRes);
    t.start();
    t.join(1000);
    InetAddress inetAddr = dnsRes.get();            
    return inetAddr != null;
  }
  catch(Exception e)
  { 
    return false;
  }
}

private static class DNSResolver implements Runnable {
    private String domain;
    private InetAddress inetAddr;

    public DNSResolver(String domain) {
        this.domain = domain;
    }

    public void run() {
        try {
            InetAddress addr = InetAddress.getByName(domain);
            set(addr);
        } catch (UnknownHostException e) {                
        }
    }

    public synchronized void set(InetAddress inetAddr) {
        this.inetAddr = inetAddr;
    }
    public synchronized InetAddress get() {
        return inetAddr;
    }
}

Using this I can first test if the device can resolve the host name, then if it successful to the full connectivity test.

Michael Levy
  • 13,097
  • 15
  • 66
  • 100
  • Awesome, thanks. Surprised that this is the only post about this very important use case. For example, someone using my app over hotspots (which can be overloaded, slow or just plain not connected to internet). – Dominic Cerisano Jan 29 '15 at 06:51
  • 1
    The situation that caused our problems we called the "CEO on Hotel WiFi problem". Our CEO would be in a hotel and his phone would attach to the hotel's wifi. But, he would forget to run the web browser and actually log in to the hotel's wifi to get full network access. He would be limited to the walled garden of the hotel's network and when he ran our App, it could not reach our servers. We put in this test to remind our CEO (and our users) when they don't really have working Internet access. – Michael Levy Jan 29 '15 at 23:16
  • I am using your approach in combination with a exec(ping) in order to get around long dns error delays when connected to wifi but not to internet. For those reading, remember that dns is cached, so you need the ping to check for internet (ICMP) connectivity after the first dns check. Could find no other way to avoid the long hardcoded (20sec) dns timeout. Note that InetAddr.isReachable() method performs a low level ICMP call that requires root access. exec(ping) does not. (8.8.8.8) – Dominic Cerisano Jan 31 '15 at 04:12
  • @DominicCerisano not all servers accept ICMP requests though – cbreezier Jan 21 '21 at 03:30
  • @cbreezier the example I gave (8.8.8.8) is a Google DNS server and has ICMP enabled. – Dominic Cerisano Jan 22 '21 at 04:08