web-dev-qa-db-fra.com

Comment vérifier l'accès Internet illimité? (détection de portail captif)

Je dois détecter de manière fiable si un périphérique dispose d'un accès Internet complet, c'est-à-dire que l'utilisateur n'est pas confiné à un portail captif (également appelé - mur de jardin ), c'est-à-dire un sous-réseau limité qui oblige les utilisateurs à soumettre leurs informations d'identification sur un formulaire afin d'obtenir un accès complet. 

Mon application automatise le processus d'authentification. Il est donc important de savoir qu'un accès complet à Internet n'est pas disponible avant de démarrer l'activité de connexion. 

La question est non sur la façon de vérifier que l’interface réseau est active et connectée. Il s'agit de s'assurer que le périphérique dispose d'un accès Internet illimité, par opposition à un segment intranet en mode bac à sable.

Toutes les approches que j'ai essayées jusqu'à présent échouent, car la connexion à un hôte connu ne générerait pas d'exception, mais renverrait un code de réponse HTTP 200 valide, car toutes les demandes seraient acheminées vers la page de connexion.

Voici toutes les approches que j'ai essayées, mais elles renvoient toutes true au lieu de false pour les raisons expliquées ci-dessus:

1:

InetAddress.getByName(Host).isReachable(TIMEOUT_IN_MILLISECONDS);
isConnected = true; <exception not thrown>

2:

Socket socket = new Socket();
SocketAddress sockaddr = new InetSocketAddress(InetAddress.getByName(Host), 80);
socket.connect(sockaddr, pingTimeout);
isConnected = socket.isConnected();

3:

URL url = new URL(hostUrl));
URLConnection urlConn = url.openConnection();
HttpURLConnection httpConn = (HttpURLConnection) urlConn;
httpConn.setAllowUserInteraction(false);
httpConn.setRequestMethod("GET");
httpConn.connect();
responseCode = httpConn.getResponseCode();
isConnected = responseCode == HttpURLConnection.HTTP_OK;

Alors, comment puis-je m'assurer que je suis connecté à un hôte réel au lieu de la page de redirection de connexion? Évidemment, je pourrais vérifier le corps de la réponse réelle à partir de l'hôte 'ping' que j'utilise, mais cela ne semble pas être une solution appropriée.

35
ccpizza

Pour référence, voici la méthode 'officielle' de la base de code d'AOSP Android 4.0.1: WifiWatchdogStateMachine.isWalledGardenConnection () . J'inclus le code ci-dessous juste au cas où le lien se briserait à l'avenir.

private static final String mWalledGardenUrl = "http://clients3.google.com/generate_204";
private static final int WALLED_GARDEN_SOCKET_TIMEOUT_MS = 10000;

private boolean isWalledGardenConnection() {
    HttpURLConnection urlConnection = null;
    try {
        URL url = new URL(mWalledGardenUrl); // "http://clients3.google.com/generate_204"
        urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setInstanceFollowRedirects(false);
        urlConnection.setConnectTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS);
        urlConnection.setReadTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS);
        urlConnection.setUseCaches(false);
        urlConnection.getInputStream();
        // We got a valid response, but not from the real google
        return urlConnection.getResponseCode() != 204;
    } catch (IOException e) {
        if (DBG) {
            log("Walled garden check - probably not a portal: exception "
                    + e);
        }
        return false;
    } finally {
        if (urlConnection != null) {
            urlConnection.disconnect();
        }
    }
}

Cette approche repose sur une URL spécifique, mWalledGardenUrl = "http://clients3.google.com/generate_204" renvoyant toujours un code de réponse 204. Cela fonctionnera même si le DNS a été perturbé car dans ce cas, un code 200 sera renvoyé à la place du 204 attendu. J'ai vu des portails captifs usurper des requêtes à cette URL afin d'empêcher le message Internet non accessible sur les appareils Android.

Google propose une variante de ce thème: extraire http://www.google.com/blank.html renverra un code 200 avec un corps de réponse de longueur nulle. Donc, si vous obtenez un corps non vide, ce serait une autre façon de comprendre que vous êtes derrière un jardin clos.

Apple a ses propres URL pour détecter les portails captifs: lorsque le réseau est opérationnel IOS et que les périphériques MacOS se connectent à une URL du type http://www.Apple.com/library/test/success.html , http://attwifi.Apple.com/library/test/success.html ou http://captive.Apple.com/hotspot-detect.html qui doit renvoyer un code d'état HTTP de 200 et un corps contenant Success

REMARQUE: Cette approche ne fonctionnera pas dans les zones à accès Internet restreint, telles que la Chine, où tout le pays est un {jardin muré}, et où la plupart des services Google/Apple sont bloqués ou filtrés. Certains d'entre eux pourraient ne pas être bloqués: http://www.google.cn/generate_204, http://g.cn/generate_204, http://gstatic.com/generate_204 ou http://connectivitycheck.gstatic.com/generate_204 - et pourtant, ils appartiennent tous à Google. Il n'est donc pas garanti qu'ils fonctionnent.

42
ccpizza

Une autre solution possible pourrait consister à se connecter via HTTPS et à inspecter le certificat cible. Vous ne savez pas si les jardins murés servent réellement la page de connexion via HTTPS ou abandonnent simplement les connexions. Dans les deux cas, vous devriez être en mesure de voir que votre destination n’est pas celle que vous attendiez.

Bien entendu, vous disposez également des frais généraux liés aux vérifications TLS et des certificats. Tel est le prix des connexions authentifiées, malheureusement.

5
Delyan

Je crois qu'empêcher le réacheminement de votre connexion fonctionnera.

URL url = new URL(hostUrl));
HttpURLConnection httpConn = (HttpURLConnection)url.openConnection();

/* This line prevents redirects */
httpConn.setInstanceFollowRedirects( false );

httpConn.setAllowUserInteraction( false );
httpConn.setRequestMethod( "GET" );
httpConn.connect();
responseCode = httpConn.getResponseCode();
isConnected = responseCode == HttpURLConnection.HTTP_OK;

Si cela ne fonctionne pas, alors je pense que la seule façon de le faire est de vérifier le corps de la réponse.

1
Ralgha

si vous utilisez déjà retrofit, vous pouvez le faire par retrofit. créez simplement une page ping.html et envoyez-lui une requête principale en utilisant retrofit et assurez-vous que votre client http est configuré comme ci-dessous: (followRedirects(false) partie est la partie la plus importante)

private OkHttpClient getCheckInternetOkHttpClient() {
    return new OkHttpClient.Builder()
            .readTimeout(2L, TimeUnit.SECONDS)
            .connectTimeout(2L, TimeUnit.SECONDS)
            .followRedirects(false)
            .build();
}

puis construisez votre rénovation comme ci-dessous:

private InternetCheckApi getCheckInternetRetrofitApi() {
    return (new Retrofit.Builder())
            .baseUrl("[base url of your ping.html page]")             
            .addConverterFactory(GsonConverterFactory.create(new Gson()))
            .client(getCheckInternetOkHttpClient())
            .build().create(InternetCheckApi.class);
}

votre InternetCheckApi.class serait comme:

public interface InternetCheckApi {
    @Headers({"Content-Typel: application/json"})
    @HEAD("ping.html")
    Call<Void> checkInternetConnectivity();
}

alors vous pouvez l'utiliser comme ci-dessous:

getCheckInternetOkHttpClient().checkInternetConnectivity().enqueue(new Callback<Void>() {
     public void onResponse(Call<Void> call, Response<Void> response) {
       if(response.code() == 200) {
        //internet is available
       } else {
         //internet is not available
       }
     }

     public void onFailure(Call<Void> call, Throwable t) {
        //internet is not available
     }
  }
);

notez que votre client http de vérification Internet doit être séparé de votre client http principal.

0
Amir Ziarati

Cela a été mis en œuvre sur la version Android 4.2.2+ - je trouve leur approche rapide et intéressante:

CaptivePortalTracker.Java détecte le jardin muré comme suit - Essayez de vous connecter à www.google.com/generate_204- Vérifiez que la réponse HTTP est bien 204.

Si le contrôle échoue, nous sommes dans un jardin clos.

private boolean isCaptivePortal(InetAddress server) {
    HttpURLConnection urlConnection = null;
    if (!mIsCaptivePortalCheckEnabled) return false;

    mUrl = "http://" + server.getHostAddress() + "/generate_204";
    if (DBG) log("Checking " + mUrl);
    try {
        URL url = new URL(mUrl);
        urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setInstanceFollowRedirects(false);
        urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
        urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
        urlConnection.setUseCaches(false);
        urlConnection.getInputStream();
        // we got a valid response, but not from the real google
        return urlConnection.getResponseCode() != 204;
    } catch (IOException e) {
        if (DBG) log("Probably not a portal: exception " + e);
        return false;
    } finally {
        if (urlConnection != null) {
            urlConnection.disconnect();
        }
    }
}
0
Xavier Stenuit