Запрос на дооснащение, выбрасывающий UnknownHostException за VPN
Итак, у меня проблема, которая выглядит так, будто она вышла прямо из Сумеречной Зоны.
проблема
Я должен подключиться к конечной точке REST API из бэкэнда, дело в том, что для достижения этой конечной точки мне нужно пройти через VPN. В противном случае хост недоступен. На рабочем столе все работает нормально, я открываю Postman, нажимаю конечную точку GET и получаю ответ. Однако, когда я пытаюсь подключиться к той же конечной точке через устройство Android, Retrofit генерирует исключение UnknownHostException.
контекст
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>>
}
Поэтому, когда я вызываю этот метод Repository из моего 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
на истину и ложь.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 ответа
Коллега нашел проблему. В основном VPN блокирует любое приложение, кроме com.android.chrome
,
Я думаю, что эта проблема может быть связана с действительностью сертификата 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();