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 {
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;
}

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
// 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);
@@ -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
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);
}
}
@@ -3722,11 +3754,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();
@@ -5893,12 +5924,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
@@ -5917,10 +5942,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

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.NetworkStack;
import android.net.NetworkUtils;
import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.UidRange;
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.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;
}
@@ -4914,4 +4926,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<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));
}
}