Запрос на дооснащение, выбрасывающий UnknownHostException за VPN - PullRequest
0 голосов
/ 14 ноября 2018

Итак, у меня возникла проблема, которая выглядит так, будто она вышла прямо из Сумеречной зоны.

Проблема

Мне нужно попасть в конечную точку REST API изБэкэнд, дело в том, что для достижения этой конечной точки мне нужно пройти через VPN.В противном случае хост недоступен.На рабочем столе все работает нормально, я открываю Postman, нажимаю конечную точку GET и получаю ответ.Однако, когда я пытаюсь подключиться к той же конечной точке через устройство Android, Retrofit генерирует исключение UnknownHostException.

Context

URL-адрес конечной точки выглядит примерно так: https://api.something.something.net/.Я использую инъекцию зависимостей в Dagger, поэтому у меня есть NetworkModule, который выглядит следующим образом:

...
NetworkModule("https://api.something.something.net/")
...
@Module
class NetworkModule(
    private val baseHost: String
) {
...
    @Provides
    @Named("authInterceptor")
    fun providesAuthInterceptor(
        @Named("authToken") authToken: String
    ): Interceptor {
        return Interceptor { chain ->
            var request = chain.request()

            request = request.newBuilder()
                .addHeader("Content-Type", "application/json")
                .addHeader("Authorization", "Bearer $authToken")
                .build()
            val response = chain.proceed(request)
        }
    }
...
    @Provides
    @Singleton
    fun provideOkHttpClient(
        curlInterceptor: CurlInterceptor,
        @Named("authInterceptor") authInterceptor: Interceptor
    ): OkHttpClient {
        val builder = OkHttpClient.Builder()
        builder.addInterceptor(authInterceptor)
        builder.addInterceptor(curlInterceptor)
        return builder.build()
    }
...
    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient, gson: Gson): Retrofit {
        return Retrofit.Builder()
                .baseUrl(baseHost)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(okHttpClient)
                .build()
    }
}

Затем у меня есть несколько репозиториев, которые делают запрос через Retrofit:

class MyApiRepositoryImpl(
    val myRetrofitApi: MyRetrofitApi,
    val uiScheduler: Scheduler,
    val backgroundScheduler: Scheduler
) : MyApiRepository {

    override fun getSomethingFromTheApi(): Observable<DataResource<List<ApiSomethingResponse>>> {
        return myRetrofitApi.getResponseFromEndpoint()
            .map {
                if (it.isSuccessful) {
                    DataResource(it.body()?.list!!, ResourceType.NETWORK_SUCCESS)
                } else {
                    throw RuntimeException("Network request failed code ${it.code()}")
                }
            }
            .subscribeOn(backgroundScheduler)
            .observeOn(uiScheduler)
            .toObservable()
    }
}

И это интерфейс API Retrofit:

interface MyRetrofitApi {

    @GET("/v1/something/")
    fun getResponseFromEndpoint(): Single<Response<ApiSomethingResponse>>
}

Итак, когда я вызываю этот метод репозитория из моего Interactor / UseCases, он прыгает прямо через onError и показывает UnknownHostException.

То, что я пробовал до сих пор

  • Я переключил Retrofit на Volley, а затем на Ion, просто чтобы быть уверенным, что это не было связано с остальным клиентом.Я получил одно и то же исключение во всех случаях:

java.net.UnknownHostException: Unable to resolve host "api.something.something.net": No address associated with hostname

com.android.volley.NoConnectionError: java.net.UnknownHostException: Unable to resolve host "api.something.something.net": No address associated with hostname

Я перепробовал все возможные конфигурации с Retrofit и OkHttpClient:

  • На OkHttpClient я попытался установить followSslRedirects в true и false.followRedirects на истину и ложь.Установите hostnameVerifier, чтобы разрешить пропуск любого имени хоста.Установите SSLSocketFactory, чтобы разрешить прохождение любых неподписанных сертификатов.

  • В моем манифесте я установил для android:networkSecurityConfig значение:

https://github.com/commonsguy/cwac-netsecurity/issues/5

  • Я тестировал приложение на своем Android-устройстве (Android Nougat), на эмуляторах с Nougat, Marshmellow и Oreo, а также на эмуляторе Genymotion с Nougat.

  • Я пытался попасть в произвольную общедоступную конечную точку (https://jsonplaceholder.typicode.com/todos/1), и она отлично работала.Так что это не проблема с подключением к Интернету.

  • В моем манифесте установлены три разрешения:

    android.permission.INTERNET 
    android.permission.ACCESS_NETWORK_STATE
    android.permission.ACCESS_WIFI_STATE
    

Это очень странно, потому что у меня есть набор Interceptor для преобразования всех запросов в запросы cURL, я скопировал и вставил тот же запрос, который не выполняется, в Postman и работает отлично.

  • На моем ноутбуке яиспользуя Cisco AnyConnect, на моем устройстве Android я использую приложение Cisco AnyConnect и AFAIK на эмуляторах и Genymotion, оно должно использовать ту же сеть, что и хост-компьютер.

Есть несколько веб-сайтов, которыевидны только через VPN и я вижу их на устройствах и на эмуляторах.Но URL-адрес конечной точки по-прежнему недоступен из приложения.

Пара странных вещей

Да, это становится страннее.Если я подключаюсь к конечной точке через браузер Chrome на своем устройстве или в эмуляторе, меня перенаправляют на страницу входа в систему, потому что я не отправляю токен авторизации.Теперь, если я проверю сетевые ответы через chrome: // inspect, я смогу получить IP-адрес хоста.Теперь, если я изменяю базовый URL-адрес NetworkModule по IP и добавляю эту строку в Interceptor авторизации:

@Provides
@Named("authInterceptor")
fun providesAuthInterceptor(
    @Named("authToken") authToken: String
): Interceptor {
    return Interceptor { chain ->
        var request = chain.request()

        request = request.newBuilder()
            .addHeader("Content-Type", "application/json")
            .addHeader("Authorization", "Bearer $authToken")
            .build()
        val response = chain.proceed(request)
        response.newBuilder().code(HttpURLConnection.HTTP_SEE_OTHER).build() // <--- This one
    }
}

Тогда я получаю:

11-13 13:56:01.084 4867-4867/com.something.myapp D/GetSomethingUseCase: javax.net.ssl.SSLPeerUnverifiedException: Hostname <BASE IP> not verified:
        certificate: sha256/someShaString
        DN: CN=something.server.net,OU=Title,O=Company Something\, LLC,L=Some City,ST=SomeState,C=SomeCountryCode
        subjectAltNames: [someServer1.com, someServer2.com, someServer3.com, someServer4.com, andSoOn.com]

Я неконечно, если это не связано или это действительно один шаг вперед, чтобы исправить это.

Любой совет или совет приветствуется.

Спасибо,

Ответы [ 2 ]

0 голосов
/ 17 ноября 2018

Сотрудник обнаружил проблему. В основном VPN блокирует любое приложение, кроме com.android.chrome.

https://www.cisco.com/c/en/us/td/docs/security/vpn_client/anyconnect/anyconnect40/administration/guide/Cisco_AnyConnect_Mobile_Administrator_Guide_4-0/mobile-anyconnect-mobile-devices.html#task_F445916BDC9649D49A98F98224D2EA7D

0 голосов
/ 14 ноября 2018

Я думаю, что эта проблема может быть связана с действительностью сертификата ssl. для решения этой проблемы вы должны установить setSSLSocketFactory в HttpClient.

  private SSLConnectionSocketFactory getSSLSocketFactory() {
  KeyStore trustStore;
  try {
    trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    trustStore.load(null, null);
    TrustStrategy trustStrategy = new TrustStrategy() {
        @Override
        public boolean isTrusted(X509Certificate[] chain, String authType) 
    throws CertificateException {
            return true;
        }

    };

    SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
    sslContextBuilder.loadTrustMaterial(trustStore, trustStrategy);
    sslContextBuilder.useTLS();
    SSLContext sslContext = sslContextBuilder.build();
    SSLConnectionSocketFactory sslSocketFactory = new 
    SSLConnectionSocketFactory(sslContext);
    return sslSocketFactory;
       } catch (GeneralSecurityException | IOException e) {
    System.err.println("SSL Error : " + e.getMessage());
      }
   return null;
  }

 HttpClientBuilder.create().setSSLSocketFactory(getSSLSocketFactory())
 .build();
...