Merge "Support strict mode private DNS on VPNs that provide Internet." into qt-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
5c31d4ff79
@@ -517,6 +517,9 @@ public class NetworkMonitor extends StateMachine {
|
|||||||
return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities);
|
return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isPrivateDnsValidationRequired() {
|
||||||
|
return NetworkMonitorUtils.isPrivateDnsValidationRequired(mNetworkCapabilities);
|
||||||
|
}
|
||||||
|
|
||||||
private void notifyNetworkTested(int result, @Nullable String redirectUrl) {
|
private void notifyNetworkTested(int result, @Nullable String redirectUrl) {
|
||||||
try {
|
try {
|
||||||
@@ -604,7 +607,7 @@ public class NetworkMonitor extends StateMachine {
|
|||||||
return HANDLED;
|
return HANDLED;
|
||||||
case CMD_PRIVATE_DNS_SETTINGS_CHANGED: {
|
case CMD_PRIVATE_DNS_SETTINGS_CHANGED: {
|
||||||
final PrivateDnsConfig cfg = (PrivateDnsConfig) message.obj;
|
final PrivateDnsConfig cfg = (PrivateDnsConfig) message.obj;
|
||||||
if (!isValidationRequired() || cfg == null || !cfg.inStrictMode()) {
|
if (!isPrivateDnsValidationRequired() || cfg == null || !cfg.inStrictMode()) {
|
||||||
// No DNS resolution required.
|
// No DNS resolution required.
|
||||||
//
|
//
|
||||||
// We don't force any validation in opportunistic mode
|
// We don't force any validation in opportunistic mode
|
||||||
@@ -840,9 +843,20 @@ public class NetworkMonitor extends StateMachine {
|
|||||||
// the network so don't bother validating here. Furthermore sending HTTP
|
// the network so don't bother validating here. Furthermore sending HTTP
|
||||||
// packets over the network may be undesirable, for example an extremely
|
// packets over the network may be undesirable, for example an extremely
|
||||||
// expensive metered network, or unwanted leaking of the User Agent string.
|
// expensive metered network, or unwanted leaking of the User Agent string.
|
||||||
|
//
|
||||||
|
// On networks that need to support private DNS in strict mode (e.g., VPNs, but
|
||||||
|
// not networks that don't provide Internet access), we still need to perform
|
||||||
|
// private DNS server resolution.
|
||||||
if (!isValidationRequired()) {
|
if (!isValidationRequired()) {
|
||||||
validationLog("Network would not satisfy default request, not validating");
|
if (isPrivateDnsValidationRequired()) {
|
||||||
|
validationLog("Network would not satisfy default request, "
|
||||||
|
+ "resolving private DNS");
|
||||||
|
transitionTo(mEvaluatingPrivateDnsState);
|
||||||
|
} else {
|
||||||
|
validationLog("Network would not satisfy default request, "
|
||||||
|
+ "not validating");
|
||||||
transitionTo(mValidatedState);
|
transitionTo(mValidatedState);
|
||||||
|
}
|
||||||
return HANDLED;
|
return HANDLED;
|
||||||
}
|
}
|
||||||
mEvaluateAttempts++;
|
mEvaluateAttempts++;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
|
|||||||
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
|
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
|
||||||
import static android.net.NetworkPolicyManager.RULE_NONE;
|
import static android.net.NetworkPolicyManager.RULE_NONE;
|
||||||
import static android.net.NetworkPolicyManager.uidRulesToString;
|
import static android.net.NetworkPolicyManager.uidRulesToString;
|
||||||
import static android.net.shared.NetworkMonitorUtils.isValidationRequired;
|
import static android.net.shared.NetworkMonitorUtils.isPrivateDnsValidationRequired;
|
||||||
import static android.os.Process.INVALID_UID;
|
import static android.os.Process.INVALID_UID;
|
||||||
import static android.system.OsConstants.IPPROTO_TCP;
|
import static android.system.OsConstants.IPPROTO_TCP;
|
||||||
import static android.system.OsConstants.IPPROTO_UDP;
|
import static android.system.OsConstants.IPPROTO_UDP;
|
||||||
@@ -2826,8 +2826,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean networkRequiresValidation(NetworkAgentInfo nai) {
|
private boolean networkRequiresPrivateDnsValidation(NetworkAgentInfo nai) {
|
||||||
return isValidationRequired(nai.networkCapabilities);
|
return isPrivateDnsValidationRequired(nai.networkCapabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleFreshlyValidatedNetwork(NetworkAgentInfo nai) {
|
private void handleFreshlyValidatedNetwork(NetworkAgentInfo nai) {
|
||||||
@@ -2845,7 +2845,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
|
|
||||||
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
|
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
|
||||||
handlePerNetworkPrivateDnsConfig(nai, cfg);
|
handlePerNetworkPrivateDnsConfig(nai, cfg);
|
||||||
if (networkRequiresValidation(nai)) {
|
if (networkRequiresPrivateDnsValidation(nai)) {
|
||||||
handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
|
handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2854,7 +2854,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
private void handlePerNetworkPrivateDnsConfig(NetworkAgentInfo nai, PrivateDnsConfig cfg) {
|
private void handlePerNetworkPrivateDnsConfig(NetworkAgentInfo nai, PrivateDnsConfig cfg) {
|
||||||
// Private DNS only ever applies to networks that might provide
|
// Private DNS only ever applies to networks that might provide
|
||||||
// Internet access and therefore also require validation.
|
// Internet access and therefore also require validation.
|
||||||
if (!networkRequiresValidation(nai)) return;
|
if (!networkRequiresPrivateDnsValidation(nai)) return;
|
||||||
|
|
||||||
// Notify the NetworkAgentInfo/NetworkMonitor in case NetworkMonitor needs to cancel or
|
// Notify the NetworkAgentInfo/NetworkMonitor in case NetworkMonitor needs to cancel or
|
||||||
// schedule DNS resolutions. If a DNS resolution is required the
|
// schedule DNS resolutions. If a DNS resolution is required the
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class ConnectivityConstants {
|
|||||||
//
|
//
|
||||||
// This ensures that a) the explicitly selected network is never trumped by anything else, and
|
// This ensures that a) the explicitly selected network is never trumped by anything else, and
|
||||||
// b) the explicitly selected network is never torn down.
|
// b) the explicitly selected network is never torn down.
|
||||||
public static final int MAXIMUM_NETWORK_SCORE = 100;
|
public static final int EXPLICITLY_SELECTED_NETWORK_SCORE = 100;
|
||||||
// VPNs typically have priority over other networks. Give them a score that will
|
// VPNs typically have priority over other networks. Give them a score that will
|
||||||
// let them win every single time.
|
// let them win every single time.
|
||||||
public static final int VPN_DEFAULT_SCORE = 101;
|
public static final int VPN_DEFAULT_SCORE = 101;
|
||||||
|
|||||||
@@ -483,11 +483,11 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
|
|||||||
// down an explicitly selected network before the user gets a chance to prefer it when
|
// down an explicitly selected network before the user gets a chance to prefer it when
|
||||||
// a higher-scoring network (e.g., Ethernet) is available.
|
// a higher-scoring network (e.g., Ethernet) is available.
|
||||||
if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
|
if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
|
||||||
return ConnectivityConstants.MAXIMUM_NETWORK_SCORE;
|
return ConnectivityConstants.EXPLICITLY_SELECTED_NETWORK_SCORE;
|
||||||
}
|
}
|
||||||
|
|
||||||
int score = currentScore;
|
int score = currentScore;
|
||||||
if (!lastValidated && !pretendValidated && !ignoreWifiUnvalidationPenalty()) {
|
if (!lastValidated && !pretendValidated && !ignoreWifiUnvalidationPenalty() && !isVPN()) {
|
||||||
score -= ConnectivityConstants.UNVALIDATED_SCORE_PENALTY;
|
score -= ConnectivityConstants.UNVALIDATED_SCORE_PENALTY;
|
||||||
}
|
}
|
||||||
if (score < 0) score = 0;
|
if (score < 0) score = 0;
|
||||||
|
|||||||
@@ -43,16 +43,23 @@ public class NetworkMonitorUtils {
|
|||||||
"android.permission.ACCESS_NETWORK_CONDITIONS";
|
"android.permission.ACCESS_NETWORK_CONDITIONS";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether validation is required for a network.
|
* Return whether validation is required for private DNS in strict mode.
|
||||||
* @param dfltNetCap Default requested network capabilities.
|
|
||||||
* @param nc Network capabilities of the network to test.
|
* @param nc Network capabilities of the network to test.
|
||||||
*/
|
*/
|
||||||
public static boolean isValidationRequired(NetworkCapabilities nc) {
|
public static boolean isPrivateDnsValidationRequired(NetworkCapabilities nc) {
|
||||||
// TODO: Consider requiring validation for DUN networks.
|
// TODO: Consider requiring validation for DUN networks.
|
||||||
return nc != null
|
return nc != null
|
||||||
&& nc.hasCapability(NET_CAPABILITY_INTERNET)
|
&& nc.hasCapability(NET_CAPABILITY_INTERNET)
|
||||||
&& nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
|
&& nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
|
||||||
&& nc.hasCapability(NET_CAPABILITY_TRUSTED)
|
&& nc.hasCapability(NET_CAPABILITY_TRUSTED);
|
||||||
&& nc.hasCapability(NET_CAPABILITY_NOT_VPN);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether validation is required for a network.
|
||||||
|
* @param nc Network capabilities of the network to test.
|
||||||
|
*/
|
||||||
|
public static boolean isValidationRequired(NetworkCapabilities nc) {
|
||||||
|
// TODO: Consider requiring validation for DUN networks.
|
||||||
|
return isPrivateDnsValidationRequired(nc) && nc.hasCapability(NET_CAPABILITY_NOT_VPN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import static android.net.ConnectivityManager.TYPE_MOBILE;
|
|||||||
import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
|
import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
|
||||||
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
|
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
|
||||||
import static android.net.ConnectivityManager.TYPE_NONE;
|
import static android.net.ConnectivityManager.TYPE_NONE;
|
||||||
|
import static android.net.ConnectivityManager.TYPE_VPN;
|
||||||
import static android.net.ConnectivityManager.TYPE_WIFI;
|
import static android.net.ConnectivityManager.TYPE_WIFI;
|
||||||
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
|
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
|
||||||
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
|
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
|
||||||
@@ -489,7 +490,7 @@ public class ConnectivityServiceTest {
|
|||||||
|
|
||||||
MockNetworkAgent(int transport, LinkProperties linkProperties) {
|
MockNetworkAgent(int transport, LinkProperties linkProperties) {
|
||||||
final int type = transportToLegacyType(transport);
|
final int type = transportToLegacyType(transport);
|
||||||
final String typeName = ConnectivityManager.getNetworkTypeName(transport);
|
final String typeName = ConnectivityManager.getNetworkTypeName(type);
|
||||||
mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock");
|
mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock");
|
||||||
mNetworkCapabilities = new NetworkCapabilities();
|
mNetworkCapabilities = new NetworkCapabilities();
|
||||||
mNetworkCapabilities.addTransportType(transport);
|
mNetworkCapabilities.addTransportType(transport);
|
||||||
@@ -619,6 +620,10 @@ public class ConnectivityServiceTest {
|
|||||||
mNetworkAgent.sendNetworkScore(mScore);
|
mNetworkAgent.sendNetworkScore(mScore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getScore() {
|
||||||
|
return mScore;
|
||||||
|
}
|
||||||
|
|
||||||
public void explicitlySelected(boolean acceptUnvalidated) {
|
public void explicitlySelected(boolean acceptUnvalidated) {
|
||||||
mNetworkAgent.explicitlySelected(acceptUnvalidated);
|
mNetworkAgent.explicitlySelected(acceptUnvalidated);
|
||||||
}
|
}
|
||||||
@@ -1330,6 +1335,8 @@ public class ConnectivityServiceTest {
|
|||||||
return TYPE_WIFI;
|
return TYPE_WIFI;
|
||||||
case TRANSPORT_CELLULAR:
|
case TRANSPORT_CELLULAR:
|
||||||
return TYPE_MOBILE;
|
return TYPE_MOBILE;
|
||||||
|
case TRANSPORT_VPN:
|
||||||
|
return TYPE_VPN;
|
||||||
default:
|
default:
|
||||||
return TYPE_NONE;
|
return TYPE_NONE;
|
||||||
}
|
}
|
||||||
@@ -5367,6 +5374,58 @@ public class ConnectivityServiceTest {
|
|||||||
mCm.unregisterNetworkCallback(defaultCallback);
|
mCm.unregisterNetworkCallback(defaultCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVpnUnvalidated() throws Exception {
|
||||||
|
final TestNetworkCallback callback = new TestNetworkCallback();
|
||||||
|
mCm.registerDefaultNetworkCallback(callback);
|
||||||
|
|
||||||
|
// Bring up Ethernet.
|
||||||
|
mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
|
||||||
|
mEthernetNetworkAgent.connect(true);
|
||||||
|
callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
|
||||||
|
callback.assertNoCallback();
|
||||||
|
|
||||||
|
// Bring up a VPN that has the INTERNET capability, initially unvalidated.
|
||||||
|
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.setNetworkAgent(vpnNetworkAgent);
|
||||||
|
mMockVpn.setUids(ranges);
|
||||||
|
vpnNetworkAgent.connect(false /* validated */, true /* hasInternet */);
|
||||||
|
mMockVpn.connect();
|
||||||
|
|
||||||
|
// Even though the VPN is unvalidated, it becomes the default network for our app.
|
||||||
|
callback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
|
||||||
|
// TODO: this looks like a spurious callback.
|
||||||
|
callback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
|
||||||
|
callback.assertNoCallback();
|
||||||
|
|
||||||
|
assertTrue(vpnNetworkAgent.getScore() > mEthernetNetworkAgent.getScore());
|
||||||
|
assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, vpnNetworkAgent.getScore());
|
||||||
|
assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
|
||||||
|
|
||||||
|
NetworkCapabilities nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
|
||||||
|
assertFalse(nc.hasCapability(NET_CAPABILITY_VALIDATED));
|
||||||
|
assertTrue(nc.hasCapability(NET_CAPABILITY_INTERNET));
|
||||||
|
|
||||||
|
assertFalse(NetworkMonitorUtils.isValidationRequired(vpnNetworkAgent.mNetworkCapabilities));
|
||||||
|
assertTrue(NetworkMonitorUtils.isPrivateDnsValidationRequired(
|
||||||
|
vpnNetworkAgent.mNetworkCapabilities));
|
||||||
|
|
||||||
|
// Pretend that the VPN network validates.
|
||||||
|
vpnNetworkAgent.setNetworkValid();
|
||||||
|
vpnNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
|
||||||
|
// Expect to see the validated capability, but no other changes, because the VPN is already
|
||||||
|
// the default network for the app.
|
||||||
|
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, vpnNetworkAgent);
|
||||||
|
callback.assertNoCallback();
|
||||||
|
|
||||||
|
vpnNetworkAgent.disconnect();
|
||||||
|
callback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
|
||||||
|
callback.expectAvailableCallbacksValidated(mEthernetNetworkAgent);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVpnSetUnderlyingNetworks() {
|
public void testVpnSetUnderlyingNetworks() {
|
||||||
final int uid = Process.myUid();
|
final int uid = Process.myUid();
|
||||||
|
|||||||
Reference in New Issue
Block a user