From 18622d3d3529f0becfb339908e73fbdf347616e6 Mon Sep 17 00:00:00 2001 From: Irina Dumitrescu Date: Wed, 5 Dec 2018 16:19:47 +0000 Subject: [PATCH] Add API for proxy configuration over VPN. Test: runtest -x frameworks/base/tests/net/java/com/android/server/ConnectivityServiceTest.java && atest HostsideVpnTests Bug: 76001058 Change-Id: Id4dde4a4103fd93bfbbacc52d0e5ade56ae67a6a --- api/current.txt | 1 + core/java/android/app/ActivityThread.java | 14 +-- core/java/android/app/IApplicationThread.aidl | 3 +- core/java/android/net/ProxyInfo.java | 57 ++++++------ core/java/android/net/VpnService.java | 9 ++ .../com/android/internal/net/VpnConfig.java | 5 + .../com/android/internal/net/VpnInfo.java | 10 +- .../TransactionParcelTests.java | 3 +- .../android/server/ConnectivityService.java | 73 ++++++++++----- .../server/am/ActivityManagerService.java | 16 +--- .../com/android/server/am/ProcessList.java | 4 +- .../server/connectivity/ProxyTracker.java | 18 ---- .../com/android/server/connectivity/Vpn.java | 2 + .../server/ConnectivityServiceTest.java | 92 +++++++++++++++++++ 14 files changed, 206 insertions(+), 101 deletions(-) diff --git a/api/current.txt b/api/current.txt index 86e3021d18225..bcfed293e6c77 100644 --- a/api/current.txt +++ b/api/current.txt @@ -29138,6 +29138,7 @@ package android.net { method public android.os.ParcelFileDescriptor establish(); method public android.net.VpnService.Builder setBlocking(boolean); method public android.net.VpnService.Builder setConfigureIntent(android.app.PendingIntent); + method public android.net.VpnService.Builder setHttpProxy(android.net.ProxyInfo); method public android.net.VpnService.Builder setMtu(int); method public android.net.VpnService.Builder setSession(java.lang.String); method public android.net.VpnService.Builder setUnderlyingNetworks(android.net.Network[]); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 7767f0491a166..594de7e50e01a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -77,9 +77,7 @@ import android.graphics.ImageDecoder; import android.hardware.display.DisplayManagerGlobal; import android.net.ConnectivityManager; import android.net.IConnectivityManager; -import android.net.Network; import android.net.Proxy; -import android.net.ProxyInfo; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; @@ -1033,15 +1031,10 @@ public final class ActivityThread extends ClientTransactionHandler { NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged(); } - public void setHttpProxy(String host, String port, String exclList, Uri pacFileUrl) { + public void updateHttpProxy() { final ConnectivityManager cm = ConnectivityManager.from( getApplication() != null ? getApplication() : getSystemContext()); - final Network network = cm.getBoundNetworkForProcess(); - if (network != null) { - Proxy.setHttpProxySystemProperty(cm.getDefaultProxy()); - } else { - Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl); - } + Proxy.setHttpProxySystemProperty(cm.getDefaultProxy()); } public void processInBackground() { @@ -5955,8 +5948,7 @@ public final class ActivityThread extends ClientTransactionHandler { // crash if we can't get it. final IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); try { - final ProxyInfo proxyInfo = service.getProxyForNetwork(null); - Proxy.setHttpProxySystemProperty(proxyInfo); + Proxy.setHttpProxySystemProperty(service.getProxyForNetwork(null)); } catch (RemoteException e) { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); throw e.rethrowFromSystemServer(); diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index fcb6c14d052cb..c64fcf3e75263 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -100,8 +100,7 @@ oneway interface IApplicationThread { void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix, in String[] args); void clearDnsCache(); - void setHttpProxy(in String proxy, in String port, in String exclList, - in Uri pacFileUrl); + void updateHttpProxy(); void setCoreSettings(in Bundle coreSettings); void updatePackageCompatibilityInfo(in String pkg, in CompatibilityInfo info); void scheduleTrimMemory(int level); diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java index e926fda336f48..ef2269a145d07 100644 --- a/core/java/android/net/ProxyInfo.java +++ b/core/java/android/net/ProxyInfo.java @@ -39,12 +39,12 @@ import java.util.Locale; */ public class ProxyInfo implements Parcelable { - private String mHost; - private int mPort; - private String mExclusionList; - private String[] mParsedExclusionList; + private final String mHost; + private final int mPort; + private final String mExclusionList; + private final String[] mParsedExclusionList; + private final Uri mPacFileUrl; - private Uri mPacFileUrl; /** *@hide */ @@ -96,7 +96,8 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(String host, int port, String exclList) { mHost = host; mPort = port; - setExclusionList(exclList); + mExclusionList = exclList; + mParsedExclusionList = parseExclusionList(mExclusionList); mPacFileUrl = Uri.EMPTY; } @@ -107,7 +108,8 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(Uri pacFileUrl) { mHost = LOCAL_HOST; mPort = LOCAL_PORT; - setExclusionList(LOCAL_EXCL_LIST); + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); if (pacFileUrl == null) { throw new NullPointerException(); } @@ -121,7 +123,8 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(String pacFileUrl) { mHost = LOCAL_HOST; mPort = LOCAL_PORT; - setExclusionList(LOCAL_EXCL_LIST); + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); mPacFileUrl = Uri.parse(pacFileUrl); } @@ -132,13 +135,22 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(Uri pacFileUrl, int localProxyPort) { mHost = LOCAL_HOST; mPort = localProxyPort; - setExclusionList(LOCAL_EXCL_LIST); + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); if (pacFileUrl == null) { throw new NullPointerException(); } mPacFileUrl = pacFileUrl; } + private static String[] parseExclusionList(String exclusionList) { + if (exclusionList == null) { + return new String[0]; + } else { + return exclusionList.toLowerCase(Locale.ROOT).split(","); + } + } + private ProxyInfo(String host, int port, String exclList, String[] parsedExclList) { mHost = host; mPort = port; @@ -159,6 +171,10 @@ public class ProxyInfo implements Parcelable { mExclusionList = source.getExclusionListAsString(); mParsedExclusionList = source.mParsedExclusionList; } else { + mHost = null; + mPort = 0; + mExclusionList = null; + mParsedExclusionList = null; mPacFileUrl = Uri.EMPTY; } } @@ -214,24 +230,14 @@ public class ProxyInfo implements Parcelable { return mExclusionList; } - // comma separated - private void setExclusionList(String exclusionList) { - mExclusionList = exclusionList; - if (mExclusionList == null) { - mParsedExclusionList = new String[0]; - } else { - mParsedExclusionList = exclusionList.toLowerCase(Locale.ROOT).split(","); - } - } - /** * @hide */ public boolean isValid() { if (!Uri.EMPTY.equals(mPacFileUrl)) return true; return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost, - mPort == 0 ? "" : Integer.toString(mPort), - mExclusionList == null ? "" : mExclusionList); + mPort == 0 ? "" : Integer.toString(mPort), + mExclusionList == null ? "" : mExclusionList); } /** @@ -262,7 +268,7 @@ public class ProxyInfo implements Parcelable { sb.append("] "); sb.append(Integer.toString(mPort)); if (mExclusionList != null) { - sb.append(" xl=").append(mExclusionList); + sb.append(" xl=").append(mExclusionList); } } else { sb.append("[ProxyProperties.mHost == null]"); @@ -308,8 +314,8 @@ public class ProxyInfo implements Parcelable { */ public int hashCode() { return ((null == mHost) ? 0 : mHost.hashCode()) - + ((null == mExclusionList) ? 0 : mExclusionList.hashCode()) - + mPort; + + ((null == mExclusionList) ? 0 : mExclusionList.hashCode()) + + mPort; } /** @@ -352,8 +358,7 @@ public class ProxyInfo implements Parcelable { } String exclList = in.readString(); String[] parsedExclList = in.readStringArray(); - ProxyInfo proxyProperties = - new ProxyInfo(host, port, exclList, parsedExclList); + ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList); return proxyProperties; } diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index f0c0462cec186..7c53bb273574b 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -485,6 +485,15 @@ public class VpnService extends Service { return this; } + /** + * Sets an HTTP proxy for the VPN network. This proxy is only a recommendation + * and it is possible that some apps will ignore it. + */ + public Builder setHttpProxy(ProxyInfo proxyInfo) { + mConfig.proxyInfo = proxyInfo; + return this; + } + /** * Add a network address to the VPN interface. Both IPv4 and IPv6 * addresses are supported. At least one address must be set before diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index fd03b3f16348c..da8605e645b4e 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -28,6 +28,7 @@ import android.content.res.Resources; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.Network; +import android.net.ProxyInfo; import android.net.RouteInfo; import android.os.Parcel; import android.os.Parcelable; @@ -104,6 +105,7 @@ public class VpnConfig implements Parcelable { public boolean allowIPv4; public boolean allowIPv6; public Network[] underlyingNetworks; + public ProxyInfo proxyInfo; public void updateAllowedFamilies(InetAddress address) { if (address instanceof Inet4Address) { @@ -164,6 +166,7 @@ public class VpnConfig implements Parcelable { out.writeInt(allowIPv4 ? 1 : 0); out.writeInt(allowIPv6 ? 1 : 0); out.writeTypedArray(underlyingNetworks, flags); + out.writeParcelable(proxyInfo, flags); } public static final Parcelable.Creator CREATOR = @@ -189,6 +192,7 @@ public class VpnConfig implements Parcelable { config.allowIPv4 = in.readInt() != 0; config.allowIPv6 = in.readInt() != 0; config.underlyingNetworks = in.createTypedArray(Network.CREATOR); + config.proxyInfo = in.readParcelable(null); return config; } @@ -220,6 +224,7 @@ public class VpnConfig implements Parcelable { .append(", allowIPv4=").append(allowIPv4) .append(", allowIPv6=").append(allowIPv6) .append(", underlyingNetworks=").append(Arrays.toString(underlyingNetworks)) + .append(", proxyInfo=").append(proxyInfo.toString()) .append("}") .toString(); } diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/com/android/internal/net/VpnInfo.java index a676dacb0c490..b1a412871bd28 100644 --- a/core/java/com/android/internal/net/VpnInfo.java +++ b/core/java/com/android/internal/net/VpnInfo.java @@ -32,11 +32,11 @@ public class VpnInfo implements Parcelable { @Override public String toString() { - return "VpnInfo{" + - "ownerUid=" + ownerUid + - ", vpnIface='" + vpnIface + '\'' + - ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\'' + - '}'; + return "VpnInfo{" + + "ownerUid=" + ownerUid + + ", vpnIface='" + vpnIface + '\'' + + ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\'' + + '}'; } @Override diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index 2801f32430ef6..0066bd0590785 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -38,7 +38,6 @@ import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; -import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Debug; @@ -500,7 +499,7 @@ public class TransactionParcelTests { } @Override - public void setHttpProxy(String s, String s1, String s2, Uri uri) throws RemoteException { + public void updateHttpProxy() throws RemoteException { } @Override diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index d0666b98c0e05..9e29e9f98f773 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -506,7 +506,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // A helper object to track the current default HTTP proxy. ConnectivityService needs to tell // the world when it changes. - private final ProxyTracker mProxyTracker; + @VisibleForTesting + protected final ProxyTracker mProxyTracker; final private SettingsObserver mSettingsObserver; @@ -815,7 +816,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mPolicyManagerInternal = checkNotNull( LocalServices.getService(NetworkPolicyManagerInternal.class), "missing NetworkPolicyManagerInternal"); - mProxyTracker = new ProxyTracker(context, mHandler, EVENT_PROXY_HAS_CHANGED); + mProxyTracker = makeProxyTracker(); mNetd = NetdService.getInstance(); mKeyStore = KeyStore.getInstance(); @@ -981,6 +982,11 @@ public class ConnectivityService extends IConnectivityManager.Stub deps); } + @VisibleForTesting + protected ProxyTracker makeProxyTracker() { + return new ProxyTracker(mContext, mHandler, EVENT_PROXY_HAS_CHANGED); + } + private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); @@ -3674,20 +3680,46 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + /** + * Returns information about the proxy a certain network is using. If given a null network, it + * it will return the proxy for the bound network for the caller app or the default proxy if + * none. + * + * @param network the network we want to get the proxy information for. + * @return Proxy information if a network has a proxy configured, or otherwise null. + */ @Override public ProxyInfo getProxyForNetwork(Network network) { - if (network == null) return mProxyTracker.getDefaultProxy(); final ProxyInfo globalProxy = mProxyTracker.getGlobalProxy(); if (globalProxy != null) return globalProxy; - if (!NetworkUtils.queryUserAccess(Binder.getCallingUid(), network.netId)) return null; - // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which - // caller may not have. + if (network == null) { + // Get the network associated with the calling UID. + final Network activeNetwork = getActiveNetworkForUidInternal(Binder.getCallingUid(), + true); + if (activeNetwork == null) { + return null; + } + return getLinkPropertiesProxyInfo(activeNetwork); + } else if (queryUserAccess(Binder.getCallingUid(), network.netId)) { + // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which + // caller may not have. + return getLinkPropertiesProxyInfo(network); + } + // No proxy info available if the calling UID does not have network access. + return null; + } + + @VisibleForTesting + protected boolean queryUserAccess(int uid, int netId) { + return NetworkUtils.queryUserAccess(uid, netId); + } + + private ProxyInfo getLinkPropertiesProxyInfo(Network network) { final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); if (nai == null) return null; synchronized (nai) { - final ProxyInfo proxyInfo = nai.linkProperties.getHttpProxy(); - if (proxyInfo == null) return null; - return new ProxyInfo(proxyInfo); + final ProxyInfo linkHttpProxy = nai.linkProperties.getHttpProxy(); + return linkHttpProxy == null ? null : new ProxyInfo(linkHttpProxy); } } @@ -3711,11 +3743,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mProxyTracker.setDefaultProxy(proxy); } - // If the proxy has changed from oldLp to newLp, resend proxy broadcast with default proxy. - // This method gets called when any network changes proxy, but the broadcast only ever contains - // the default proxy (even if it hasn't changed). - // TODO: Deprecate the broadcast extras as they aren't necessarily applicable in a multi-network - // world where an app might be bound to a non-default network. + // If the proxy has changed from oldLp to newLp, resend proxy broadcast. This method gets called + // when any network changes proxy. + // TODO: Remove usage of broadcast extras as they are deprecated and not applicable in a + // multi-network world where an app might be bound to a non-default network. private void updateProxy(LinkProperties newLp, LinkProperties oldLp) { ProxyInfo newProxyInfo = newLp == null ? null : newLp.getHttpProxy(); ProxyInfo oldProxyInfo = oldLp == null ? null : oldLp.getHttpProxy(); @@ -5881,12 +5912,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } scheduleUnvalidatedPrompt(networkAgent); - if (networkAgent.isVPN()) { - // Temporarily disable the default proxy (not global). - mProxyTracker.setDefaultProxyEnabled(false); - // TODO: support proxy per network. - } - // Whether a particular NetworkRequest listen should cause signal strength thresholds to // be communicated to a particular NetworkAgent depends only on the network's immutable, // capabilities, so it only needs to be done once on initial connect, not every time the @@ -5905,10 +5930,16 @@ public class ConnectivityService extends IConnectivityManager.Stub } else if (state == NetworkInfo.State.DISCONNECTED) { networkAgent.asyncChannel.disconnect(); if (networkAgent.isVPN()) { - mProxyTracker.setDefaultProxyEnabled(true); updateUids(networkAgent, networkAgent.networkCapabilities, null); } disconnectAndDestroyNetwork(networkAgent); + if (networkAgent.isVPN()) { + // As the active or bound network changes for apps, broadcast the default proxy, as + // apps may need to update their proxy data. This is called after disconnecting from + // VPN to make sure we do not broadcast the old proxy data. + // TODO(b/122649188): send the broadcast only to VPN users. + mProxyTracker.sendProxyBroadcast(); + } } else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) || state == NetworkInfo.State.SUSPENDED) { // going into or coming out of SUSPEND: re-score and notify diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3b08a00a65d2c..e1ae6640add2d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1568,19 +1568,8 @@ public class ActivityManagerService extends IActivityManager.Stub } } break; case UPDATE_HTTP_PROXY_MSG: { - ProxyInfo proxy = (ProxyInfo)msg.obj; - String host = ""; - String port = ""; - String exclList = ""; - Uri pacFileUrl = Uri.EMPTY; - if (proxy != null) { - host = proxy.getHost(); - port = Integer.toString(proxy.getPort()); - exclList = proxy.getExclusionListAsString(); - pacFileUrl = proxy.getPacFileUrl(); - } synchronized (ActivityManagerService.this) { - mProcessList.setAllHttpProxyLocked(host, port, exclList, pacFileUrl); + mProcessList.setAllHttpProxyLocked(); } } break; case PROC_START_TIMEOUT_MSG: { @@ -14563,8 +14552,7 @@ public class ActivityManagerService extends IActivityManager.Stub mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG); break; case Proxy.PROXY_CHANGE_ACTION: - ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO); - mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy)); + mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG)); break; case android.hardware.Camera.ACTION_NEW_PICTURE: case android.hardware.Camera.ACTION_NEW_VIDEO: diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 5bc88450c3765..787c0ea048539 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2377,14 +2377,14 @@ public final class ProcessList { } @GuardedBy("mService") - void setAllHttpProxyLocked(String host, String port, String exclList, Uri pacFileUrl) { + void setAllHttpProxyLocked() { for (int i = mLruProcesses.size() - 1; i >= 0; i--) { ProcessRecord r = mLruProcesses.get(i); // Don't dispatch to isolated processes as they can't access // ConnectivityManager and don't have network privileges anyway. if (r.thread != null && !r.isolated) { try { - r.thread.setHttpProxy(host, port, exclList, pacFileUrl); + r.thread.updateHttpProxy(); } catch (RemoteException ex) { Slog.w(TAG, "Failed to update http proxy for: " + r.info.processName); diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java index fdddccd207146..a671287324af8 100644 --- a/services/core/java/com/android/server/connectivity/ProxyTracker.java +++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java @@ -309,22 +309,4 @@ public class ProxyTracker { } } } - - /** - * Enable or disable the default proxy. - * - * This sets the flag for enabling/disabling the default proxy and sends the broadcast - * if applicable. - * @param enabled whether the default proxy should be enabled. - */ - public void setDefaultProxyEnabled(final boolean enabled) { - synchronized (mProxyLock) { - if (mDefaultProxyEnabled != enabled) { - mDefaultProxyEnabled = enabled; - if (mGlobalProxy == null && mDefaultProxy != null) { - sendProxyBroadcast(); - } - } - } - } } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 602aedbc2d00e..e72ced2d493f5 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -788,6 +788,8 @@ public class Vpn { } } + lp.setHttpProxy(mConfig.proxyInfo); + if (!allowIPv4) { lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index bf3964416e116..ebca152176c1f 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -121,6 +121,7 @@ import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkUtils; +import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.StringNetworkSpecifier; import android.net.UidRange; @@ -158,6 +159,7 @@ import com.android.server.connectivity.DefaultNetworkMetrics; import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.Nat464Xlat; +import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; import com.android.server.net.NetworkPinner; @@ -1001,6 +1003,11 @@ public class ConnectivityServiceTest { return mock(Tethering.class); } + @Override + protected ProxyTracker makeProxyTracker() { + return mock(ProxyTracker.class); + } + @Override protected int reserveNetId() { while (true) { @@ -1023,6 +1030,11 @@ public class ConnectivityServiceTest { } } + @Override + protected boolean queryUserAccess(int uid, int netId) { + return true; + } + public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) { return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd; } @@ -4823,4 +4835,84 @@ public class ConnectivityServiceTest { mCellNetworkAgent.sendLinkProperties(lp); verifyTcpBufferSizeChange(TEST_TCP_BUFFER_SIZES); } + + @Test + public void testGetGlobalProxyForNetwork() { + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + final Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); + when(mService.mProxyTracker.getGlobalProxy()).thenReturn(testProxyInfo); + assertEquals(testProxyInfo, mService.getProxyForNetwork(wifiNetwork)); + } + + @Test + public void testGetProxyForActiveNetwork() { + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + assertNull(mService.getProxyForNetwork(null)); + + final LinkProperties testLinkProperties = new LinkProperties(); + testLinkProperties.setHttpProxy(testProxyInfo); + + mWiFiNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + + assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); + } + + @Test + public void testGetProxyForVPN() { + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + + // Set up a WiFi network with no proxy + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + assertNull(mService.getProxyForNetwork(null)); + + // Set up a VPN network with a proxy + final int uid = Process.myUid(); + final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); + final ArraySet ranges = new ArraySet<>(); + ranges.add(new UidRange(uid, uid)); + mMockVpn.setUids(ranges); + LinkProperties testLinkProperties = new LinkProperties(); + testLinkProperties.setHttpProxy(testProxyInfo); + vpnNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + + // Connect to VPN with proxy + mMockVpn.setNetworkAgent(vpnNetworkAgent); + vpnNetworkAgent.connect(true); + mMockVpn.connect(); + waitForIdle(); + + // Test that the VPN network returns a proxy, and the WiFi does not. + assertEquals(testProxyInfo, mService.getProxyForNetwork(vpnNetworkAgent.getNetwork())); + assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); + assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); + + // Test that the VPN network returns no proxy when it is set to null. + testLinkProperties.setHttpProxy(null); + vpnNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + assertNull(mService.getProxyForNetwork(vpnNetworkAgent.getNetwork())); + assertNull(mService.getProxyForNetwork(null)); + + // Set WiFi proxy and check that the vpn proxy is still null. + testLinkProperties.setHttpProxy(testProxyInfo); + mWiFiNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + assertNull(mService.getProxyForNetwork(null)); + + // Disconnect from VPN and check that the active network, which is now the WiFi, has the + // correct proxy setting. + vpnNetworkAgent.disconnect(); + waitForIdle(); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); + assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); + } }