Подтвердить что ты не робот

Как проверить неограниченный доступ в Интернет? (обнаружение портативного портала)

Мне нужно надежно определить, имеет ли устройство полный доступ к Интернету, т.е. что пользователь не ограничен узким порталом (также называемым огороженным садом), то есть ограниченной подсетью, которая заставляет пользователей отправлять свои учетные данные в форме для того, чтобы для получения полного доступа.

Мое приложение автоматизирует процесс аутентификации, и поэтому важно знать, что полный доступ в Интернет недоступен до начала действия входа в систему.

Вопрос не в том, как проверить, что сетевой интерфейс вставлен и подключен. Речь идет о том, чтобы обеспечить неограниченное доступ к Интернету, в отличие от изолированного сегмента интрасети.

Все подходы, которые я пробовал до сих пор, терпят неудачу, потому что подключение к любому известному хосту не приведет к исключению, а вернет правильный код ответа HTTP 200, потому что все запросы направляются на страницу входа.

Вот все подходы, которые я пробовал, но все они возвращают true вместо false по причинам, описанным выше:

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;

Итак, как я могу убедиться, что я подключился к реальному хосту вместо страницы перенаправления входа? Очевидно, что я могу проверить фактическое тело ответа на используемом мной хосте "ping", но это не похоже на правильное решение.

4b9b3361

Ответ 1

Для справки, вот "официальный" метод из базы данных AOSP для Android 4.0.1: WifiWatchdogStateMachine.isWalledGardenConnection(). Я включаю в себя код ниже, только в случае, если связь будет нарушена в будущем.

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();
        }
    }
}

Этот подход основан на определенном URL-адресе, mWalledGardenUrl = "http://clients3.google.com/generate_204" всегда возвращает код ответа 204. Это будет работать, даже если DNS помешал, так как в этом случае вместо ожидаемого 204 будет возвращен код 200. Я видел некоторые невольные порталы, подменяющие запросы на этот конкретный URL-адрес, чтобы предотвратить доступ к Интернету на устройствах Android.

В Google есть вариация этой темы: выборка http://www.google.com/blank.html вернет код 200 с телом ответа нулевой длины. Поэтому, если вы получите непустое тело, это будет другим способом выяснить, что вы находитесь за огороженным садом.

У Apple есть свои собственные URL-адреса для обнаружения портативных порталов: при подключении к сети IOS и устройства MacOS будут подключаться к URL-адресу, например http://www.apple.com/library/test/success.html, http://attwifi.apple.com/library/test/success.html или http://captive.apple.com/hotspot-detect.html, который должен вернуть код состояния HTTP 200 и тело, содержащее Success.

Примечание:     Такой подход не будет работать в районах с ограниченным доступом к Интернету, таких как Китай, где вся страна является огороженным садом и где большинство служб Google/Apple блокируются или фильтруются. Некоторые из них могут не блокироваться: http://www.google.cn/generate_204, http://g.cn/generate_204, http://gstatic.com/generate_204 или http://connectivitycheck.gstatic.com/generate_204 - все же все они принадлежат Google, поэтому не гарантируется работа.

Ответ 2

Другим возможным решением может быть подключение через HTTPS и проверка целевого сертификата. Не уверен, что стеновые сады фактически обслуживают страницу входа через HTTPS или просто отбрасывают соединения. В любом случае вы должны уметь видеть, что ваш пункт назначения не тот, который вы ожидали.

Конечно, у вас также есть накладные расходы на проверку TLS и сертификатов. Такова цена аутентифицированных подключений, к сожалению.

Ответ 3

Я считаю, что предотвращение перенаправления для вашего соединения будет работать.

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;

Если это не сработает, я думаю, что единственный способ сделать это - проверить тело ответа.

Ответ 4

Это было реализовано в версии Android 4.2.2+ - я нахожу их подход быстрым и интересным:

CaptivePortalTracker.java обнаруживает огороженный сад следующим образом - Попробуйте подключиться к www.google.com/generate_204. - Убедитесь, что ответ HTTP равен 204

Если проверка не удалась, мы находимся в огороженном саду.

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();
        }
    }
}

Ответ 5

если вы уже используете retrofit, вы можете сделать это с помощью retrofit. просто создайте страницу ping.html и отправьте запрос главы на него с помощью модификации и убедитесь, что ваш http-клиент настроен так: (followRedirects(false) часть - самая важная часть)

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

тогда создайте свой модификатор, как показано ниже:

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);
}

ваш InternetCheckApi.class будет выглядеть следующим образом:

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

то вы можете использовать его, как показано ниже:

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
     }
  }
);

обратите внимание, что ваш клиент проверки HTTP-клиента должен быть отделен от вашего основного http-клиента.

Ответ 6

Лучше всего это сделать здесь, как в AOSP: https://github.com/aosp-mirror/platform_frameworks_base/blob/6bebb8418ceecf44d2af40033870f3aabacfe36e/core/java/android/net/captiveportal/CaptivePortaljrobeRult

https://github.com/aosp-mirror/platform_frameworks_base/blob/e3a0f42e8e8678f6d90ddf104d485858fbb2e35b/services/core/java/com/android/server/connectivity/NetworkMonitor.java

private static final String GOOGLE_PING_URL = "http://google.com/generate_204";
private static final int SOCKET_TIMEOUT_MS = 10000;

public boolean isCaptivePortal () {

try {
            URL url = new URL(GOOGLE_PING_URL);
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
            urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
            urlConnection.setUseCaches(false);
            urlConnection.getInputStream();
            return (urlConnection.getResponseCode() != 204)
                    && (urlConnection.getResponseCode() >= 200)
                    && (urlConnection.getResponseCode() <= 399);
        } catch (Exception e) {
            // for any exception throw an exception saying check was unsuccesful
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
        }
}

Обратите внимание, что это, вероятно, не будет работать в прокси-сети и что-то более сложное, как в URL-адресе AOSP, необходимо сделать