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
Merged-In: Id4dde4a4103fd93bfbbacc52d0e5ade56ae67a6a
This commit is contained in:
Irina Dumitrescu
2018-12-05 16:19:47 +00:00
parent bb5952107d
commit de132bb6d6
4 changed files with 175 additions and 65 deletions

View File

@@ -39,12 +39,12 @@ import java.util.Locale;
*/ */
public class ProxyInfo implements Parcelable { public class ProxyInfo implements Parcelable {
private String mHost; private final String mHost;
private int mPort; private final int mPort;
private String mExclusionList; private final String mExclusionList;
private String[] mParsedExclusionList; private final String[] mParsedExclusionList;
private final Uri mPacFileUrl;
private Uri mPacFileUrl;
/** /**
*@hide *@hide
*/ */
@@ -96,7 +96,8 @@ public class ProxyInfo implements Parcelable {
public ProxyInfo(String host, int port, String exclList) { public ProxyInfo(String host, int port, String exclList) {
mHost = host; mHost = host;
mPort = port; mPort = port;
setExclusionList(exclList); mExclusionList = exclList;
mParsedExclusionList = parseExclusionList(mExclusionList);
mPacFileUrl = Uri.EMPTY; mPacFileUrl = Uri.EMPTY;
} }
@@ -107,7 +108,8 @@ public class ProxyInfo implements Parcelable {
public ProxyInfo(Uri pacFileUrl) { public ProxyInfo(Uri pacFileUrl) {
mHost = LOCAL_HOST; mHost = LOCAL_HOST;
mPort = LOCAL_PORT; mPort = LOCAL_PORT;
setExclusionList(LOCAL_EXCL_LIST); mExclusionList = LOCAL_EXCL_LIST;
mParsedExclusionList = parseExclusionList(mExclusionList);
if (pacFileUrl == null) { if (pacFileUrl == null) {
throw new NullPointerException(); throw new NullPointerException();
} }
@@ -121,7 +123,8 @@ public class ProxyInfo implements Parcelable {
public ProxyInfo(String pacFileUrl) { public ProxyInfo(String pacFileUrl) {
mHost = LOCAL_HOST; mHost = LOCAL_HOST;
mPort = LOCAL_PORT; mPort = LOCAL_PORT;
setExclusionList(LOCAL_EXCL_LIST); mExclusionList = LOCAL_EXCL_LIST;
mParsedExclusionList = parseExclusionList(mExclusionList);
mPacFileUrl = Uri.parse(pacFileUrl); mPacFileUrl = Uri.parse(pacFileUrl);
} }
@@ -132,13 +135,22 @@ public class ProxyInfo implements Parcelable {
public ProxyInfo(Uri pacFileUrl, int localProxyPort) { public ProxyInfo(Uri pacFileUrl, int localProxyPort) {
mHost = LOCAL_HOST; mHost = LOCAL_HOST;
mPort = localProxyPort; mPort = localProxyPort;
setExclusionList(LOCAL_EXCL_LIST); mExclusionList = LOCAL_EXCL_LIST;
mParsedExclusionList = parseExclusionList(mExclusionList);
if (pacFileUrl == null) { if (pacFileUrl == null) {
throw new NullPointerException(); throw new NullPointerException();
} }
mPacFileUrl = pacFileUrl; 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) { private ProxyInfo(String host, int port, String exclList, String[] parsedExclList) {
mHost = host; mHost = host;
mPort = port; mPort = port;
@@ -159,6 +171,10 @@ public class ProxyInfo implements Parcelable {
mExclusionList = source.getExclusionListAsString(); mExclusionList = source.getExclusionListAsString();
mParsedExclusionList = source.mParsedExclusionList; mParsedExclusionList = source.mParsedExclusionList;
} else { } else {
mHost = null;
mPort = 0;
mExclusionList = null;
mParsedExclusionList = null;
mPacFileUrl = Uri.EMPTY; mPacFileUrl = Uri.EMPTY;
} }
} }
@@ -214,24 +230,14 @@ public class ProxyInfo implements Parcelable {
return mExclusionList; 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 * @hide
*/ */
public boolean isValid() { public boolean isValid() {
if (!Uri.EMPTY.equals(mPacFileUrl)) return true; if (!Uri.EMPTY.equals(mPacFileUrl)) return true;
return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost, return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost,
mPort == 0 ? "" : Integer.toString(mPort), mPort == 0 ? "" : Integer.toString(mPort),
mExclusionList == null ? "" : mExclusionList); mExclusionList == null ? "" : mExclusionList);
} }
/** /**
@@ -262,7 +268,7 @@ public class ProxyInfo implements Parcelable {
sb.append("] "); sb.append("] ");
sb.append(Integer.toString(mPort)); sb.append(Integer.toString(mPort));
if (mExclusionList != null) { if (mExclusionList != null) {
sb.append(" xl=").append(mExclusionList); sb.append(" xl=").append(mExclusionList);
} }
} else { } else {
sb.append("[ProxyProperties.mHost == null]"); sb.append("[ProxyProperties.mHost == null]");
@@ -308,8 +314,8 @@ public class ProxyInfo implements Parcelable {
*/ */
public int hashCode() { public int hashCode() {
return ((null == mHost) ? 0 : mHost.hashCode()) return ((null == mHost) ? 0 : mHost.hashCode())
+ ((null == mExclusionList) ? 0 : mExclusionList.hashCode()) + ((null == mExclusionList) ? 0 : mExclusionList.hashCode())
+ mPort; + mPort;
} }
/** /**
@@ -352,8 +358,7 @@ public class ProxyInfo implements Parcelable {
} }
String exclList = in.readString(); String exclList = in.readString();
String[] parsedExclList = in.readStringArray(); String[] parsedExclList = in.readStringArray();
ProxyInfo proxyProperties = ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList);
new ProxyInfo(host, port, exclList, parsedExclList);
return proxyProperties; return proxyProperties;
} }

View File

@@ -506,7 +506,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
// A helper object to track the current default HTTP proxy. ConnectivityService needs to tell // A helper object to track the current default HTTP proxy. ConnectivityService needs to tell
// the world when it changes. // the world when it changes.
private final ProxyTracker mProxyTracker; @VisibleForTesting
protected final ProxyTracker mProxyTracker;
final private SettingsObserver mSettingsObserver; final private SettingsObserver mSettingsObserver;
@@ -815,7 +816,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
mPolicyManagerInternal = checkNotNull( mPolicyManagerInternal = checkNotNull(
LocalServices.getService(NetworkPolicyManagerInternal.class), LocalServices.getService(NetworkPolicyManagerInternal.class),
"missing NetworkPolicyManagerInternal"); "missing NetworkPolicyManagerInternal");
mProxyTracker = new ProxyTracker(context, mHandler, EVENT_PROXY_HAS_CHANGED); mProxyTracker = makeProxyTracker();
mNetd = NetdService.getInstance(); mNetd = NetdService.getInstance();
mKeyStore = KeyStore.getInstance(); mKeyStore = KeyStore.getInstance();
@@ -981,6 +982,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
deps); deps);
} }
@VisibleForTesting
protected ProxyTracker makeProxyTracker() {
return new ProxyTracker(mContext, mHandler, EVENT_PROXY_HAS_CHANGED);
}
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) { private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
final NetworkCapabilities netCap = new NetworkCapabilities(); final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET); netCap.addCapability(NET_CAPABILITY_INTERNET);
@@ -3685,20 +3691,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 @Override
public ProxyInfo getProxyForNetwork(Network network) { public ProxyInfo getProxyForNetwork(Network network) {
if (network == null) return mProxyTracker.getDefaultProxy();
final ProxyInfo globalProxy = mProxyTracker.getGlobalProxy(); final ProxyInfo globalProxy = mProxyTracker.getGlobalProxy();
if (globalProxy != null) return globalProxy; if (globalProxy != null) return globalProxy;
if (!NetworkUtils.queryUserAccess(Binder.getCallingUid(), network.netId)) return null; if (network == null) {
// Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which // Get the network associated with the calling UID.
// caller may not have. 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); final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null) return null; if (nai == null) return null;
synchronized (nai) { synchronized (nai) {
final ProxyInfo proxyInfo = nai.linkProperties.getHttpProxy(); final ProxyInfo linkHttpProxy = nai.linkProperties.getHttpProxy();
if (proxyInfo == null) return null; return linkHttpProxy == null ? null : new ProxyInfo(linkHttpProxy);
return new ProxyInfo(proxyInfo);
} }
} }
@@ -3722,11 +3754,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
mProxyTracker.setDefaultProxy(proxy); mProxyTracker.setDefaultProxy(proxy);
} }
// If the proxy has changed from oldLp to newLp, resend proxy broadcast with default proxy. // If the proxy has changed from oldLp to newLp, resend proxy broadcast. This method gets called
// This method gets called when any network changes proxy, but the broadcast only ever contains // when any network changes proxy.
// the default proxy (even if it hasn't changed). // TODO: Remove usage of broadcast extras as they are deprecated and not applicable in a
// TODO: Deprecate the broadcast extras as they aren't necessarily applicable in a multi-network // multi-network world where an app might be bound to a non-default network.
// world where an app might be bound to a non-default network.
private void updateProxy(LinkProperties newLp, LinkProperties oldLp) { private void updateProxy(LinkProperties newLp, LinkProperties oldLp) {
ProxyInfo newProxyInfo = newLp == null ? null : newLp.getHttpProxy(); ProxyInfo newProxyInfo = newLp == null ? null : newLp.getHttpProxy();
ProxyInfo oldProxyInfo = oldLp == null ? null : oldLp.getHttpProxy(); ProxyInfo oldProxyInfo = oldLp == null ? null : oldLp.getHttpProxy();
@@ -5893,12 +5924,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
scheduleUnvalidatedPrompt(networkAgent); 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 // Whether a particular NetworkRequest listen should cause signal strength thresholds to
// be communicated to a particular NetworkAgent depends only on the network's immutable, // 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 // capabilities, so it only needs to be done once on initial connect, not every time the
@@ -5917,10 +5942,16 @@ public class ConnectivityService extends IConnectivityManager.Stub
} else if (state == NetworkInfo.State.DISCONNECTED) { } else if (state == NetworkInfo.State.DISCONNECTED) {
networkAgent.asyncChannel.disconnect(); networkAgent.asyncChannel.disconnect();
if (networkAgent.isVPN()) { if (networkAgent.isVPN()) {
mProxyTracker.setDefaultProxyEnabled(true);
updateUids(networkAgent, networkAgent.networkCapabilities, null); updateUids(networkAgent, networkAgent.networkCapabilities, null);
} }
disconnectAndDestroyNetwork(networkAgent); 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) || } else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) ||
state == NetworkInfo.State.SUSPENDED) { state == NetworkInfo.State.SUSPENDED) {
// going into or coming out of SUSPEND: re-score and notify // going into or coming out of SUSPEND: re-score and notify

View File

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

View File

@@ -121,6 +121,7 @@ import android.net.NetworkRequest;
import android.net.NetworkSpecifier; import android.net.NetworkSpecifier;
import android.net.NetworkStack; import android.net.NetworkStack;
import android.net.NetworkUtils; import android.net.NetworkUtils;
import android.net.ProxyInfo;
import android.net.RouteInfo; import android.net.RouteInfo;
import android.net.UidRange; import android.net.UidRange;
import android.net.metrics.IpConnectivityLog; import android.net.metrics.IpConnectivityLog;
@@ -158,6 +159,7 @@ import com.android.server.connectivity.DefaultNetworkMetrics;
import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.Nat464Xlat; import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn; import com.android.server.connectivity.Vpn;
import com.android.server.net.NetworkPinner; import com.android.server.net.NetworkPinner;
@@ -1001,6 +1003,11 @@ public class ConnectivityServiceTest {
return mock(Tethering.class); return mock(Tethering.class);
} }
@Override
protected ProxyTracker makeProxyTracker() {
return mock(ProxyTracker.class);
}
@Override @Override
protected int reserveNetId() { protected int reserveNetId() {
while (true) { while (true) {
@@ -1023,6 +1030,11 @@ public class ConnectivityServiceTest {
} }
} }
@Override
protected boolean queryUserAccess(int uid, int netId) {
return true;
}
public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) { public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) {
return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd; return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd;
} }
@@ -4914,4 +4926,84 @@ public class ConnectivityServiceTest {
mCellNetworkAgent.sendLinkProperties(lp); mCellNetworkAgent.sendLinkProperties(lp);
verifyTcpBufferSizeChange(TEST_TCP_BUFFER_SIZES); 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<UidRange> 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));
}
} }