Merge changes Iaa78a7ed,I6497b7ef into pi-dev

* changes:
  Support strict mode private DNS on VPNs that provide Internet.
  Add test coverage for strict mode private DNS.
This commit is contained in:
Lorenzo Colitti
2019-12-11 23:55:10 +00:00
committed by Android (Google) Code Review
5 changed files with 177 additions and 32 deletions

View File

@@ -2349,9 +2349,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
} }
private boolean networkRequiresValidation(NetworkAgentInfo nai) { private boolean networkRequiresPrivateDnsValidation(NetworkAgentInfo nai) {
return NetworkMonitor.isValidationRequired( return nai.networkMonitor.isPrivateDnsValidationRequired();
mDefaultRequest.networkCapabilities, nai.networkCapabilities);
} }
private void handleFreshlyValidatedNetwork(NetworkAgentInfo nai) { private void handleFreshlyValidatedNetwork(NetworkAgentInfo nai) {
@@ -2369,16 +2368,14 @@ 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));
} }
} }
} }
private void handlePerNetworkPrivateDnsConfig(NetworkAgentInfo nai, PrivateDnsConfig cfg) { private void handlePerNetworkPrivateDnsConfig(NetworkAgentInfo nai, PrivateDnsConfig cfg) {
// Private DNS only ever applies to networks that might provide if (!networkRequiresPrivateDnsValidation(nai)) return;
// Internet access and therefore also require validation.
if (!networkRequiresValidation(nai)) return;
// Notify the NetworkMonitor thread in case it needs to cancel or // Notify the NetworkMonitor thread in case it needs to cancel or
// schedule DNS resolutions. If a DNS resolution is required the // schedule DNS resolutions. If a DNS resolution is required the

View File

@@ -45,7 +45,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;

View File

@@ -432,11 +432,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;

View File

@@ -243,12 +243,6 @@ public class NetworkMonitor extends StateMachine {
private String mPrivateDnsProviderHostname = ""; private String mPrivateDnsProviderHostname = "";
public static boolean isValidationRequired(
NetworkCapabilities dfltNetCap, NetworkCapabilities nc) {
// TODO: Consider requiring validation for DUN networks.
return dfltNetCap.satisfiedByNetworkCapabilities(nc);
}
private final Context mContext; private final Context mContext;
private final Handler mConnectivityServiceHandler; private final Handler mConnectivityServiceHandler;
private final NetworkAgentInfo mNetworkAgentInfo; private final NetworkAgentInfo mNetworkAgentInfo;
@@ -381,11 +375,19 @@ public class NetworkMonitor extends StateMachine {
return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION; return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION;
} }
private boolean isValidationRequired() { @VisibleForTesting
return isValidationRequired( public boolean isValidationRequired() {
mDefaultRequest.networkCapabilities, mNetworkAgentInfo.networkCapabilities); // TODO: Consider requiring validation for DUN networks.
return mDefaultRequest.networkCapabilities.satisfiedByNetworkCapabilities(
mNetworkAgentInfo.networkCapabilities);
} }
public boolean isPrivateDnsValidationRequired() {
// VPNs become the default network for applications even if they do not provide the INTERNET
// capability (e.g., split tunnels; See b/119216095).
// Ensure private DNS works on such VPNs as well.
return isValidationRequired() || mNetworkAgentInfo.isVPN();
}
private void notifyNetworkTestResultInvalid(Object obj) { private void notifyNetworkTestResultInvalid(Object obj) {
mConnectivityServiceHandler.sendMessage(obtainMessage( mConnectivityServiceHandler.sendMessage(obtainMessage(
@@ -455,7 +457,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
@@ -621,9 +623,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;
} }
mAttempts++; mAttempts++;
@@ -796,7 +809,7 @@ public class NetworkMonitor extends StateMachine {
try { try {
// Do a blocking DNS resolution using the network-assigned nameservers. // Do a blocking DNS resolution using the network-assigned nameservers.
// Do not set AI_ADDRCONFIG in ai_flags so we get all address families in advance. // Do not set AI_ADDRCONFIG in ai_flags so we get all address families in advance.
final InetAddress[] ips = ResolvUtil.blockingResolveAllLocally( final InetAddress[] ips = resolveAllLocally(
mNetwork, mPrivateDnsProviderHostname, 0 /* aiFlags */); mNetwork, mPrivateDnsProviderHostname, 0 /* aiFlags */);
mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips); mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips);
} catch (UnknownHostException uhe) { } catch (UnknownHostException uhe) {
@@ -830,7 +843,7 @@ public class NetworkMonitor extends StateMachine {
final String host = UUID.randomUUID().toString().substring(0, 8) + final String host = UUID.randomUUID().toString().substring(0, 8) +
ONE_TIME_HOSTNAME_SUFFIX; ONE_TIME_HOSTNAME_SUFFIX;
try { try {
final InetAddress[] ips = mNetworkAgentInfo.network().getAllByName(host); final InetAddress[] ips = getAllByName(mNetworkAgentInfo.network(), host);
return (ips != null && ips.length > 0); return (ips != null && ips.length > 0);
} catch (UnknownHostException uhe) {} } catch (UnknownHostException uhe) {}
return false; return false;
@@ -1046,7 +1059,7 @@ public class NetworkMonitor extends StateMachine {
int result; int result;
String connectInfo; String connectInfo;
try { try {
InetAddress[] addresses = mNetwork.getAllByName(host); InetAddress[] addresses = getAllByName(mNetwork, host);
StringBuffer buffer = new StringBuffer(); StringBuffer buffer = new StringBuffer();
for (InetAddress address : addresses) { for (InetAddress address : addresses) {
buffer.append(',').append(address.getHostAddress()); buffer.append(',').append(address.getHostAddress());
@@ -1233,6 +1246,18 @@ public class NetworkMonitor extends StateMachine {
} }
} }
@VisibleForTesting
protected InetAddress[] getAllByName(Network network, String host) throws UnknownHostException {
return network.getAllByName(host);
}
@VisibleForTesting
protected InetAddress[] resolveAllLocally(Network network, String hostname, int flags)
throws UnknownHostException {
// We cannot use this in OneAddressPerFamilyNetwork#getAllByName because that's static.
return ResolvUtil.blockingResolveAllLocally(network, hostname, flags);
}
private URL makeURL(String url) { private URL makeURL(String url) {
if (url != null) { if (url != null) {
try { try {

View File

@@ -26,6 +26,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.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
@@ -165,6 +166,7 @@ import org.mockito.MockitoAnnotations;
import org.mockito.Spy; import org.mockito.Spy;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@@ -387,7 +389,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);
@@ -455,6 +457,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);
} }
@@ -869,6 +875,7 @@ public class ConnectivityServiceTest {
// HTTP response code fed back to NetworkMonitor for Internet connectivity probe. // HTTP response code fed back to NetworkMonitor for Internet connectivity probe.
public int gen204ProbeResult = 500; public int gen204ProbeResult = 500;
public String gen204ProbeRedirectUrl = null; public String gen204ProbeRedirectUrl = null;
public volatile InetAddress[] dnsLookupResults = null;
public WrappedNetworkMonitor(Context context, Handler handler, public WrappedNetworkMonitor(Context context, Handler handler,
NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest, NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest,
@@ -883,6 +890,25 @@ public class ConnectivityServiceTest {
if (!mIsCaptivePortalCheckEnabled) { return new CaptivePortalProbeResult(204); } if (!mIsCaptivePortalCheckEnabled) { return new CaptivePortalProbeResult(204); }
return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl, null); return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl, null);
} }
private InetAddress[] fakeDnsLookup() throws UnknownHostException {
if (dnsLookupResults == null) {
throw new UnknownHostException();
}
return dnsLookupResults;
}
@Override
protected InetAddress[] getAllByName(Network network, String hostname)
throws UnknownHostException {
return fakeDnsLookup();
}
@Override
protected InetAddress[] resolveAllLocally(Network network, String hostname, int flags)
throws UnknownHostException {
return fakeDnsLookup();
}
} }
private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker { private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker {
@@ -1086,6 +1112,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;
} }
@@ -4021,7 +4049,7 @@ public class ConnectivityServiceTest {
cellLp.addDnsServer(InetAddress.getByName("192.0.2.1")); cellLp.addDnsServer(InetAddress.getByName("192.0.2.1"));
mCellNetworkAgent.sendLinkProperties(cellLp); mCellNetworkAgent.sendLinkProperties(cellLp);
mCellNetworkAgent.connect(false); mCellNetworkAgent.connect(true);
waitForIdle(); waitForIdle();
verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork( verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
anyInt(), mStringArrayCaptor.capture(), any(), any(), anyInt(), mStringArrayCaptor.capture(), any(), any(),
@@ -4039,9 +4067,10 @@ public class ConnectivityServiceTest {
mCellNetworkAgent); mCellNetworkAgent);
CallbackInfo cbi = cellNetworkCallback.expectCallback( CallbackInfo cbi = cellNetworkCallback.expectCallback(
CallbackState.LINK_PROPERTIES, mCellNetworkAgent); CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive()); assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com"); setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork( verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork(
@@ -4066,14 +4095,45 @@ public class ConnectivityServiceTest {
reset(mNetworkManagementService); reset(mNetworkManagementService);
cellNetworkCallback.assertNoCallback(); cellNetworkCallback.assertNoCallback();
// Strict mode.
mCellNetworkAgent.getWrappedNetworkMonitor().dnsLookupResults = new InetAddress[] {
InetAddress.getByName("2001:db8::66"),
InetAddress.getByName("192.0.2.44")
};
setPrivateDnsSettings(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, "strict.example.com"); setPrivateDnsSettings(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, "strict.example.com");
// Can't test dns configuration for strict mode without properly mocking
// out the DNS lookups, but can test that LinkProperties is updated. // Expect a callback saying that private DNS is now in strict mode.
cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
mCellNetworkAgent); mCellNetworkAgent);
LinkProperties lp = (LinkProperties) cbi.arg;
assertTrue(lp.isPrivateDnsActive());
assertEquals("strict.example.com", lp.getPrivateDnsServerName());
cellNetworkCallback.assertNoCallback(); cellNetworkCallback.assertNoCallback();
assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive());
assertEquals("strict.example.com", ((LinkProperties)cbi.arg).getPrivateDnsServerName()); // When the validation callback arrives, LinkProperties are updated.
// We need to wait for this callback because the test thread races with the NetworkMonitor
// thread, and if the test thread wins the race, then the times(2) verify call below will
// fail.
mService.mNetdEventCallback.onPrivateDnsValidationEvent(
mCellNetworkAgent.getNetwork().netId, "2001:db8::66", "strict.example.com", true);
cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
mCellNetworkAgent);
lp = (LinkProperties) cbi.arg;
assertTrue(lp.isPrivateDnsActive());
assertEquals(1, lp.getValidatedPrivateDnsServers().size());
// setDnsConfigurationForNetwork is called twice: once when private DNS is set to strict
// mode and once when the hostname resolves.
verify(mNetworkManagementService, times(2)).setDnsConfigurationForNetwork(
anyInt(), mStringArrayCaptor.capture(), any(), any(),
eq("strict.example.com"), tlsServers.capture());
assertEquals(2, mStringArrayCaptor.getValue().length);
assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(),
new String[]{"2001:db8::1", "192.0.2.1"}));
assertEquals(2, tlsServers.getValue().length);
assertTrue(ArrayUtils.containsAll(tlsServers.getValue(),
new String[]{"2001:db8::66", "192.0.2.44"}));
reset(mNetworkManagementService);
// Send the same LinkProperties and expect getting the same result including private dns. // Send the same LinkProperties and expect getting the same result including private dns.
// b/118518971 // b/118518971
@@ -4395,6 +4455,69 @@ public class ConnectivityServiceTest {
mCm.unregisterNetworkCallback(defaultCallback); mCm.unregisterNetworkCallback(defaultCallback);
} }
@Test
public void testVpnUnvalidated() throws Exception {
final TestNetworkCallback callback = new TestNetworkCallback();
mCm.registerDefaultNetworkCallback(callback);
// Enable private DNS.
setPrivateDnsSettings(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, "strict.example.com");
// Bring up Ethernet.
mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
mEthernetNetworkAgent.getWrappedNetworkMonitor().dnsLookupResults =
new InetAddress[]{ InetAddress.getByName("2001:db8::1") };
mEthernetNetworkAgent.connect(true);
callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
callback.assertNoCallback();
// Bring up a VPN that has the INTERNET capability but does not validate.
final int uid = Process.myUid();
final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
vpnNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 500;
vpnNetworkAgent.getWrappedNetworkMonitor().dnsLookupResults = null;
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(vpnNetworkAgent.getWrappedNetworkMonitor().isValidationRequired());
assertTrue(vpnNetworkAgent.getWrappedNetworkMonitor().isPrivateDnsValidationRequired());
// Pretend that the strict mode private DNS hostname now resolves. Even though the
// connectivity probe still returns 500, the network validates because the connectivity
// probe is not used on VPNs.
vpnNetworkAgent.getWrappedNetworkMonitor().dnsLookupResults =
new InetAddress[]{ InetAddress.getByName("2001:db8::1") };
mCm.reportNetworkConnectivity(vpnNetworkAgent.getNetwork(), true);
// 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();