Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

android proxy: add support for PAC proxies #2591

Merged
merged 6 commits into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
import android.net.ConnectivityManager;
import android.net.Proxy;
import android.net.ProxyInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class AndroidProxyMonitor extends BroadcastReceiver {
private static volatile AndroidProxyMonitor instance = null;
class AndroidProxyMonitor extends BroadcastReceiver {
static volatile AndroidProxyMonitor instance = null;
private ConnectivityManager connectivityManager;
private EnvoyEngine envoyEngine;

public static void load(Context context, EnvoyEngine envoyEngine) {
static void load(Context context, EnvoyEngine envoyEngine) {
if (instance != null) {
return;
}
Expand All @@ -34,7 +36,6 @@ private AndroidProxyMonitor(Context context, EnvoyEngine envoyEngine) {
this.connectivityManager =
(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
registerReceiver(context);
this.handleProxyChange();
}

private void registerReceiver(Context context) {
Expand All @@ -45,15 +46,41 @@ private void registerReceiver(Context context) {

@Override
public void onReceive(Context context, Intent intent) {
handleProxyChange();
handleProxyChange(intent);
}

private void handleProxyChange() {
ProxyInfo info = connectivityManager.getDefaultProxy();
private void handleProxyChange(final Intent intent) {
ProxyInfo info = this.extractProxyInfo(intent);

if (info == null) {
envoyEngine.setProxySettings("", 0);
} else {
envoyEngine.setProxySettings(info.getHost(), info.getPort());
}
}

private ProxyInfo extractProxyInfo(final Intent intent) {
ProxyInfo info = connectivityManager.getDefaultProxy();

// If proxy is configured using the PAC file use the
Augustyniak marked this conversation as resolved.
Show resolved Hide resolved
// Android's injected localhost HTTP proxy.
//
// Android's injected localhost proxy can be accessed using a proxy host
// equal to `localhost` and a proxy port retrieved from intent's 'extras'.
// We cannot take a proxy port from the ProxyInfo object that's exposed by
// the connectivity manager as it's always equal to -1 for cases when PAC
// proxy is configured.
//
// See https://github.com/envoyproxy/envoy-mobile/issues/2531 for more details.
if (info.getPacFileUrl() != null && info.getPacFileUrl() != Uri.EMPTY) {
jpsim marked this conversation as resolved.
Show resolved Hide resolved
Bundle extras = intent.getExtras();
if (extras == null) {
return null;
}

info = (ProxyInfo)extras.get("android.intent.extra.PROXY_INFO");
}

return info;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package test.kotlin.integration.proxying

import android.content.Intent
import android.content.Context
import android.net.ConnectivityManager
import android.net.Proxy
import android.net.ProxyInfo
import androidx.test.core.app.ApplicationProvider

Expand Down Expand Up @@ -47,17 +49,16 @@ class PerformHTTPRequestUsingProxy {
fun `performs an HTTP request through a proxy`() {
val port = (10001..11000).random()

val mockContext = Mockito.mock(Context::class.java)
Mockito.`when`(mockContext.getApplicationContext()).thenReturn(mockContext)
val mockConnectivityManager = Mockito.mock(ConnectivityManager::class.java)
Mockito.`when`(mockContext.getSystemService(Mockito.anyString())).thenReturn(mockConnectivityManager)
Mockito.`when`(mockConnectivityManager.getDefaultProxy()).thenReturn(ProxyInfo.buildDirectProxy("127.0.0.1", port))
val context = Mockito.spy(ApplicationProvider.getApplicationContext<Context>())
val connectivityManager: ConnectivityManager = Mockito.mock(ConnectivityManager::class.java)
Mockito.doReturn(connectivityManager).`when`(context).getSystemService(Context.CONNECTIVITY_SERVICE)
Mockito.`when`(connectivityManager.getDefaultProxy()).thenReturn(ProxyInfo.buildDirectProxy("127.0.0.1", port))

val onProxyEngineRunningLatch = CountDownLatch(1)
val onEngineRunningLatch = CountDownLatch(1)
val onRespondeHeadersLatch = CountDownLatch(1)

val proxyEngineBuilder = Proxy(ApplicationProvider.getApplicationContext(), port)
val proxyEngineBuilder = Proxy(context, port)
.http()
val proxyEngine = proxyEngineBuilder
.addLogLevel(LogLevel.DEBUG)
Expand All @@ -67,7 +68,9 @@ class PerformHTTPRequestUsingProxy {
onProxyEngineRunningLatch.await(10, TimeUnit.SECONDS)
assertThat(onProxyEngineRunningLatch.count).isEqualTo(0)

val builder = AndroidEngineBuilder(mockContext)
context.sendStickyBroadcast(Intent(Proxy.PROXY_CHANGE_ACTION))

val builder = AndroidEngineBuilder(context)
val engine = builder
.addLogLevel(LogLevel.DEBUG)
.enableProxying(true)
Expand Down
18 changes: 10 additions & 8 deletions test/kotlin/integration/proxying/PerformHTTPSRequestBadHostname.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package test.kotlin.integration.proxying


import android.content.Intent
import android.content.Context
import android.net.ConnectivityManager
import android.net.Proxy
import android.net.ProxyInfo
import androidx.test.core.app.ApplicationProvider

Expand Down Expand Up @@ -49,17 +50,16 @@ class PerformHTTPSRequestBadHostname {
fun `attempts an HTTPs request through a proxy using an async DNS resolution that fails`() {
val port = (10001..11000).random()

val mockContext = Mockito.mock(Context::class.java)
Mockito.`when`(mockContext.getApplicationContext()).thenReturn(mockContext)
val mockConnectivityManager = Mockito.mock(ConnectivityManager::class.java)
Mockito.`when`(mockContext.getSystemService(Mockito.anyString())).thenReturn(mockConnectivityManager)
Mockito.`when`(mockConnectivityManager.getDefaultProxy()).thenReturn(ProxyInfo.buildDirectProxy("loopback", port))
val context = Mockito.spy(ApplicationProvider.getApplicationContext<Context>())
val connectivityManager: ConnectivityManager = Mockito.mock(ConnectivityManager::class.java)
Mockito.doReturn(connectivityManager).`when`(context).getSystemService(Context.CONNECTIVITY_SERVICE)
Mockito.`when`(connectivityManager.getDefaultProxy()).thenReturn(ProxyInfo.buildDirectProxy("127.0.0.1", port))

val onEngineRunningLatch = CountDownLatch(1)
val onProxyEngineRunningLatch = CountDownLatch(1)
val onErrorLatch = CountDownLatch(1)

val proxyEngineBuilder = Proxy(ApplicationProvider.getApplicationContext(), port).https()
val proxyEngineBuilder = Proxy(context, port).https()
val proxyEngine = proxyEngineBuilder
.addLogLevel(LogLevel.DEBUG)
.addDNSQueryTimeoutSeconds(2)
Expand All @@ -69,7 +69,9 @@ class PerformHTTPSRequestBadHostname {
onProxyEngineRunningLatch.await(10, TimeUnit.SECONDS)
assertThat(onProxyEngineRunningLatch.count).isEqualTo(0)

val builder = AndroidEngineBuilder(mockContext)
context.sendStickyBroadcast(Intent(Proxy.PROXY_CHANGE_ACTION))

val builder = AndroidEngineBuilder(context)
val engine = builder
.addLogLevel(LogLevel.DEBUG)
.enableProxying(true)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package test.kotlin.integration.proxying


import android.content.Intent
import android.content.Context
import android.net.ConnectivityManager
import android.net.Proxy
import android.net.ProxyInfo
import androidx.test.core.app.ApplicationProvider

Expand Down Expand Up @@ -49,11 +50,10 @@ class PerformHTTPSRequestUsingAsyncProxyTest {
fun `performs an HTTPs request through a proxy using async DNS resolution`() {
val port = (10001..11000).random()

val mockContext = Mockito.mock(Context::class.java)
Mockito.`when`(mockContext.getApplicationContext()).thenReturn(mockContext)
val mockConnectivityManager = Mockito.mock(ConnectivityManager::class.java)
Mockito.`when`(mockContext.getSystemService(Mockito.anyString())).thenReturn(mockConnectivityManager)
Mockito.`when`(mockConnectivityManager.getDefaultProxy()).thenReturn(ProxyInfo.buildDirectProxy("localhost", port))
val context = Mockito.spy(ApplicationProvider.getApplicationContext<Context>())
val connectivityManager: ConnectivityManager = Mockito.mock(ConnectivityManager::class.java)
Mockito.doReturn(connectivityManager).`when`(context).getSystemService(Context.CONNECTIVITY_SERVICE)
Mockito.`when`(connectivityManager.getDefaultProxy()).thenReturn(ProxyInfo.buildDirectProxy("127.0.0.1", port))

val onEngineRunningLatch = CountDownLatch(1)
val onProxyEngineRunningLatch = CountDownLatch(1)
Expand All @@ -68,7 +68,9 @@ class PerformHTTPSRequestUsingAsyncProxyTest {
onProxyEngineRunningLatch.await(10, TimeUnit.SECONDS)
assertThat(onProxyEngineRunningLatch.count).isEqualTo(0)

val builder = AndroidEngineBuilder(mockContext)
context.sendStickyBroadcast(Intent(Proxy.PROXY_CHANGE_ACTION))

val builder = AndroidEngineBuilder(context)
val engine = builder
.addLogLevel(LogLevel.DEBUG)
.enableProxying(true)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package test.kotlin.integration.proxying


import android.content.Intent
import android.content.Context
import android.net.ConnectivityManager
import android.net.Proxy
import android.net.ProxyInfo
import androidx.test.core.app.ApplicationProvider

Expand Down Expand Up @@ -49,17 +50,16 @@ class PerformHTTPSRequestUsingProxy {
fun `performs an HTTPs request through a proxy`() {
val port = (10001..11000).random()

val mockContext = Mockito.mock(Context::class.java)
Mockito.`when`(mockContext.getApplicationContext()).thenReturn(mockContext)
val mockConnectivityManager = Mockito.mock(ConnectivityManager::class.java)
Mockito.`when`(mockContext.getSystemService(Mockito.anyString())).thenReturn(mockConnectivityManager)
Mockito.`when`(mockConnectivityManager.getDefaultProxy()).thenReturn(ProxyInfo.buildDirectProxy("127.0.0.1", port))
val context = Mockito.spy(ApplicationProvider.getApplicationContext<Context>())
val connectivityManager: ConnectivityManager = Mockito.mock(ConnectivityManager::class.java)
Mockito.doReturn(connectivityManager).`when`(context).getSystemService(Context.CONNECTIVITY_SERVICE)
Mockito.`when`(connectivityManager.getDefaultProxy()).thenReturn(ProxyInfo.buildDirectProxy("127.0.0.1", port))

val onEngineRunningLatch = CountDownLatch(1)
val onProxyEngineRunningLatch = CountDownLatch(1)
val onRespondeHeadersLatch = CountDownLatch(1)

val proxyEngineBuilder = Proxy(ApplicationProvider.getApplicationContext(), port).https()
val proxyEngineBuilder = Proxy(context, port).https()
val proxyEngine = proxyEngineBuilder
.addLogLevel(LogLevel.DEBUG)
.setOnEngineRunning { onProxyEngineRunningLatch.countDown() }
Expand All @@ -68,7 +68,9 @@ class PerformHTTPSRequestUsingProxy {
onProxyEngineRunningLatch.await(10, TimeUnit.SECONDS)
assertThat(onProxyEngineRunningLatch.count).isEqualTo(0)

val builder = AndroidEngineBuilder(mockContext)
context.sendStickyBroadcast(Intent(Proxy.PROXY_CHANGE_ACTION))

val builder = AndroidEngineBuilder(context)
val engine = builder
.addLogLevel(LogLevel.DEBUG)
.enableProxying(true)
Expand Down