Move the logic of (re)evaluation of Private DNS

Moves this out of ConnectivityService and into each NetworkMonitor
(where it's more self-contained).

Test: as follows
    - builds, flashes, boots
    - runtest frameworks-net passes
    - manual testing with working and non-working hostnames behaves
      somewhat (but not entirely) as expected, and not always quickly
Bug: 64133961
Bug: 72345192
Bug: 73872000
Bug: 77140445
Merged-In: I5dc90ecfe6f6f10967b7501645ad8e030cb38982
Merged-In: Ida4967d22f0781524f0f269e30e653b8ec867258
Change-Id: Ic4322af3cb49149f2d975cb31f54b2ac7927f907
(cherry picked from commit 736353a584aa89a29e737e21e29c49fad0d38a63)
This commit is contained in:
Erik Kline
2018-03-21 07:18:33 -07:00
parent 3d735d02d6
commit 471ce709e8
3 changed files with 210 additions and 112 deletions

View File

@@ -934,7 +934,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
// Used only for testing. // Used only for testing.
// TODO: Delete this and either: // TODO: Delete this and either:
// 1. Give Fake SettingsProvider the ability to send settings change notifications (requires // 1. Give FakeSettingsProvider the ability to send settings change notifications (requires
// changing ContentResolver to make registerContentObserver non-final). // changing ContentResolver to make registerContentObserver non-final).
// 2. Give FakeSettingsProvider an alternative notification mechanism and have the test use it // 2. Give FakeSettingsProvider an alternative notification mechanism and have the test use it
// by subclassing SettingsObserver. // by subclassing SettingsObserver.
@@ -943,6 +943,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
mHandler.sendEmptyMessage(EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON); mHandler.sendEmptyMessage(EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON);
} }
// See FakeSettingsProvider comment above.
@VisibleForTesting
void updatePrivateDnsSettings() {
mHandler.sendEmptyMessage(EVENT_PRIVATE_DNS_SETTINGS_CHANGED);
}
private void handleMobileDataAlwaysOn() { private void handleMobileDataAlwaysOn() {
final boolean enable = toBool(Settings.Global.getInt( final boolean enable = toBool(Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1)); mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1));
@@ -972,8 +978,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
private void registerPrivateDnsSettingsCallbacks() { private void registerPrivateDnsSettingsCallbacks() {
for (Uri u : DnsManager.getPrivateDnsSettingsUris()) { for (Uri uri : DnsManager.getPrivateDnsSettingsUris()) {
mSettingsObserver.observe(u, EVENT_PRIVATE_DNS_SETTINGS_CHANGED); mSettingsObserver.observe(uri, EVENT_PRIVATE_DNS_SETTINGS_CHANGED);
} }
} }
@@ -1026,8 +1032,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (network == null) { if (network == null) {
return null; return null;
} }
return getNetworkAgentInfoForNetId(network.netId);
}
private NetworkAgentInfo getNetworkAgentInfoForNetId(int netId) {
synchronized (mNetworkForNetId) { synchronized (mNetworkForNetId) {
return mNetworkForNetId.get(network.netId); return mNetworkForNetId.get(netId);
} }
} }
@@ -1167,9 +1177,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
NetworkAgentInfo nai; NetworkAgentInfo nai;
if (vpnNetId != NETID_UNSET) { if (vpnNetId != NETID_UNSET) {
synchronized (mNetworkForNetId) { nai = getNetworkAgentInfoForNetId(vpnNetId);
nai = mNetworkForNetId.get(vpnNetId);
}
if (nai != null) return nai.network; if (nai != null) return nai.network;
} }
nai = getDefaultNetwork(); nai = getDefaultNetwork();
@@ -2155,41 +2163,21 @@ public class ConnectivityService extends IConnectivityManager.Stub
default: default:
return false; return false;
case NetworkMonitor.EVENT_NETWORK_TESTED: { case NetworkMonitor.EVENT_NETWORK_TESTED: {
final NetworkAgentInfo nai; final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
synchronized (mNetworkForNetId) {
nai = mNetworkForNetId.get(msg.arg2);
}
if (nai == null) break; if (nai == null) break;
final boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID); final boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
final boolean wasValidated = nai.lastValidated; final boolean wasValidated = nai.lastValidated;
final boolean wasDefault = isDefaultNetwork(nai); final boolean wasDefault = isDefaultNetwork(nai);
final PrivateDnsConfig privateDnsCfg = (msg.obj instanceof PrivateDnsConfig)
? (PrivateDnsConfig) msg.obj : null;
final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : ""; final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : "";
final boolean reevaluationRequired;
final String logMsg;
if (valid) {
reevaluationRequired = updatePrivateDns(nai, privateDnsCfg);
logMsg = (DBG && (privateDnsCfg != null))
? " with " + privateDnsCfg.toString() : "";
} else {
reevaluationRequired = false;
logMsg = (DBG && !TextUtils.isEmpty(redirectUrl))
? " with redirect to " + redirectUrl : "";
}
if (DBG) { if (DBG) {
final String logMsg = !TextUtils.isEmpty(redirectUrl)
? " with redirect to " + redirectUrl
: "";
log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg); log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg);
} }
// If there is a change in Private DNS configuration,
// trigger reevaluation of the network to test it.
if (reevaluationRequired) {
nai.networkMonitor.sendMessage(
NetworkMonitor.CMD_FORCE_REEVALUATION, Process.SYSTEM_UID);
break;
}
if (valid != nai.lastValidated) { if (valid != nai.lastValidated) {
if (wasDefault) { if (wasDefault) {
metricsLogger().defaultNetworkMetrics().logDefaultNetworkValidity( metricsLogger().defaultNetworkMetrics().logDefaultNetworkValidity(
@@ -2218,10 +2206,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: { case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
final int netId = msg.arg2; final int netId = msg.arg2;
final boolean visible = toBool(msg.arg1); final boolean visible = toBool(msg.arg1);
final NetworkAgentInfo nai; final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
synchronized (mNetworkForNetId) {
nai = mNetworkForNetId.get(netId);
}
// If captive portal status has changed, update capabilities or disconnect. // If captive portal status has changed, update capabilities or disconnect.
if (nai != null && (visible != nai.lastCaptivePortalDetected)) { if (nai != null && (visible != nai.lastCaptivePortalDetected)) {
final int oldScore = nai.getCurrentScore(); final int oldScore = nai.getCurrentScore();
@@ -2252,18 +2237,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
break; break;
} }
case NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED: { case NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
final NetworkAgentInfo nai; final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
synchronized (mNetworkForNetId) {
nai = mNetworkForNetId.get(msg.arg2);
}
if (nai == null) break; if (nai == null) break;
final PrivateDnsConfig cfg = (PrivateDnsConfig) msg.obj; updatePrivateDns(nai, (PrivateDnsConfig) msg.obj);
final boolean reevaluationRequired = updatePrivateDns(nai, cfg);
if (nai.lastValidated && reevaluationRequired) {
nai.networkMonitor.sendMessage(
NetworkMonitor.CMD_FORCE_REEVALUATION, Process.SYSTEM_UID);
}
break; break;
} }
} }
@@ -2301,61 +2278,38 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
} }
private boolean networkRequiresValidation(NetworkAgentInfo nai) {
return NetworkMonitor.isValidationRequired(
mDefaultRequest.networkCapabilities, nai.networkCapabilities);
}
private void handlePrivateDnsSettingsChanged() { private void handlePrivateDnsSettingsChanged() {
final PrivateDnsConfig cfg = mDnsManager.getPrivateDnsConfig(); final PrivateDnsConfig cfg = mDnsManager.getPrivateDnsConfig();
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
// Private DNS only ever applies to networks that might provide handlePerNetworkPrivateDnsConfig(nai, cfg);
// Internet access and therefore also require validation.
if (!NetworkMonitor.isValidationRequired(
mDefaultRequest.networkCapabilities, nai.networkCapabilities)) {
continue;
}
// Notify the NetworkMonitor thread in case it needs to cancel or
// schedule DNS resolutions. If a DNS resolution is required the
// result will be sent back to us.
nai.networkMonitor.notifyPrivateDnsSettingsChanged(cfg);
if (!cfg.inStrictMode()) {
// No strict mode hostname DNS resolution needed, so just update
// DNS settings directly. In opportunistic and "off" modes this
// just reprograms netd with the network-supplied DNS servers
// (and of course the boolean of whether or not to attempt TLS).
//
// TODO: Consider code flow parity with strict mode, i.e. having
// NetworkMonitor relay the PrivateDnsConfig back to us and then
// performing this call at that time.
updatePrivateDns(nai, cfg);
}
} }
} }
private boolean updatePrivateDns(NetworkAgentInfo nai, PrivateDnsConfig newCfg) { private void handlePerNetworkPrivateDnsConfig(NetworkAgentInfo nai, PrivateDnsConfig cfg) {
final boolean reevaluationRequired = true; // Private DNS only ever applies to networks that might provide
final boolean dontReevaluate = false; // Internet access and therefore also require validation.
if (!networkRequiresValidation(nai)) return;
final PrivateDnsConfig oldCfg = mDnsManager.updatePrivateDns(nai.network, newCfg); // Notify the NetworkMonitor thread in case it needs to cancel or
// schedule DNS resolutions. If a DNS resolution is required the
// result will be sent back to us.
nai.networkMonitor.notifyPrivateDnsSettingsChanged(cfg);
// With Private DNS bypass support, we can proceed to update the
// Private DNS config immediately, even if we're in strict mode
// and have not yet resolved the provider name into a set of IPs.
updatePrivateDns(nai, cfg);
}
private void updatePrivateDns(NetworkAgentInfo nai, PrivateDnsConfig newCfg) {
mDnsManager.updatePrivateDns(nai.network, newCfg);
updateDnses(nai.linkProperties, null, nai.network.netId); updateDnses(nai.linkProperties, null, nai.network.netId);
if (newCfg == null) {
if (oldCfg == null) return dontReevaluate;
return oldCfg.useTls ? reevaluationRequired : dontReevaluate;
}
if (oldCfg == null) {
return newCfg.useTls ? reevaluationRequired : dontReevaluate;
}
if (oldCfg.useTls != newCfg.useTls) {
return reevaluationRequired;
}
if (newCfg.inStrictMode() && !Objects.equals(oldCfg.hostname, newCfg.hostname)) {
return reevaluationRequired;
}
return dontReevaluate;
} }
private void updateLingerState(NetworkAgentInfo nai, long now) { private void updateLingerState(NetworkAgentInfo nai, long now) {
@@ -3300,7 +3254,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) { if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) {
return; return;
} }
nai.networkMonitor.sendMessage(NetworkMonitor.CMD_FORCE_REEVALUATION, uid); nai.networkMonitor.forceReevaluation(uid);
} }
private ProxyInfo getDefaultProxy() { private ProxyInfo getDefaultProxy() {
@@ -4919,7 +4873,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) { public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) {
if (mNetworkForNetId.get(nai.network.netId) != nai) { if (getNetworkAgentInfoForNetId(nai.network.netId) != nai) {
// Ignore updates for disconnected networks // Ignore updates for disconnected networks
return; return;
} }
@@ -5495,6 +5449,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) { if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) {
networkAgent.everConnected = true; networkAgent.everConnected = true;
handlePerNetworkPrivateDnsConfig(networkAgent, mDnsManager.getPrivateDnsConfig());
updateLinkProperties(networkAgent, null); updateLinkProperties(networkAgent, null);
notifyIfacesChangedForNetworkStats(); notifyIfacesChangedForNetworkStats();
@@ -5911,4 +5866,4 @@ public class ConnectivityService extends IConnectivityManager.Stub
pw.println(" Get airplane mode."); pw.println(" Get airplane mode.");
} }
} }
} }

View File

@@ -61,6 +61,51 @@ import java.util.StringJoiner;
* This class it NOT designed for concurrent access. Furthermore, all non-static * This class it NOT designed for concurrent access. Furthermore, all non-static
* methods MUST be called from ConnectivityService's thread. * methods MUST be called from ConnectivityService's thread.
* *
* [ Private DNS ]
* The code handling Private DNS is spread across several components, but this
* seems like the least bad place to collect all the observations.
*
* Private DNS handling and updating occurs in response to several different
* events. Each is described here with its corresponding intended handling.
*
* [A] Event: A new network comes up.
* Mechanics:
* [1] ConnectivityService gets notifications from NetworkAgents.
* [2] in updateNetworkInfo(), the first time the NetworkAgent goes into
* into CONNECTED state, the Private DNS configuration is retrieved,
* programmed, and strict mode hostname resolution (if applicable) is
* enqueued in NetworkAgent's NetworkMonitor, via a call to
* handlePerNetworkPrivateDnsConfig().
* [3] Re-resolution of strict mode hostnames that fail to return any
* IP addresses happens inside NetworkMonitor; it sends itself a
* delayed CMD_EVALUATE_PRIVATE_DNS message in a simple backoff
* schedule.
* [4] Successfully resolved hostnames are sent to ConnectivityService
* inside an EVENT_PRIVATE_DNS_CONFIG_RESOLVED message. The resolved
* IP addresses are programmed into netd via:
*
* updatePrivateDns() -> updateDnses()
*
* both of which make calls into DnsManager.
* [5] Upon a successful hostname resolution NetworkMonitor initiates a
* validation attempt in the form of a lookup for a one-time hostname
* that uses Private DNS.
*
* [B] Event: Private DNS settings are changed.
* Mechanics:
* [1] ConnectivityService gets notifications from its SettingsObserver.
* [2] handlePrivateDnsSettingsChanged() is called, which calls
* handlePerNetworkPrivateDnsConfig() and the process proceeds
* as if from A.3 above.
*
* [C] Event: An application calls ConnectivityManager#reportBadNetwork().
* Mechanics:
* [1] NetworkMonitor is notified and initiates a reevaluation, which
* always bypasses Private DNS.
* [2] Once completed, NetworkMonitor checks if strict mode is in operation
* and if so enqueues another evaluation of Private DNS, as if from
* step A.5 above.
*
* @hide * @hide
*/ */
public class DnsManager { public class DnsManager {

View File

@@ -17,6 +17,9 @@
package com.android.server; package com.android.server;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
@@ -70,6 +73,7 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -181,6 +185,9 @@ public class ConnectivityServiceTest {
private static final int TIMEOUT_MS = 500; private static final int TIMEOUT_MS = 500;
private static final int TEST_LINGER_DELAY_MS = 120; private static final int TEST_LINGER_DELAY_MS = 120;
private static final String MOBILE_IFNAME = "test_rmnet_data0";
private static final String WIFI_IFNAME = "test_wlan0";
private MockContext mServiceContext; private MockContext mServiceContext;
private WrappedConnectivityService mService; private WrappedConnectivityService mService;
private WrappedConnectivityManager mCm; private WrappedConnectivityManager mCm;
@@ -751,7 +758,7 @@ public class ConnectivityServiceTest {
// NetworkMonitor implementation allowing overriding of Internet connectivity probe result. // NetworkMonitor implementation allowing overriding of Internet connectivity probe result.
private class WrappedNetworkMonitor extends NetworkMonitor { private class WrappedNetworkMonitor extends NetworkMonitor {
public Handler connectivityHandler; public final Handler connectivityHandler;
// 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;
@@ -928,6 +935,7 @@ public class ConnectivityServiceTest {
// Ensure that the default setting for Captive Portals is used for most tests // Ensure that the default setting for Captive Portals is used for most tests
setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT); setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
setMobileDataAlwaysOn(false); setMobileDataAlwaysOn(false);
setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
} }
@After @After
@@ -2582,6 +2590,14 @@ public class ConnectivityServiceTest {
waitForIdle(); waitForIdle();
} }
private void setPrivateDnsSettings(String mode, String specifier) {
final ContentResolver cr = mServiceContext.getContentResolver();
Settings.Global.putString(cr, Settings.Global.PRIVATE_DNS_MODE, mode);
Settings.Global.putString(cr, Settings.Global.PRIVATE_DNS_SPECIFIER, specifier);
mService.updatePrivateDnsSettings();
waitForIdle();
}
private boolean isForegroundNetwork(MockNetworkAgent network) { private boolean isForegroundNetwork(MockNetworkAgent network) {
NetworkCapabilities nc = mCm.getNetworkCapabilities(network.getNetwork()); NetworkCapabilities nc = mCm.getNetworkCapabilities(network.getNetwork());
assertNotNull(nc); assertNotNull(nc);
@@ -3583,7 +3599,7 @@ public class ConnectivityServiceTest {
mCm.registerNetworkCallback(networkRequest, networkCallback); mCm.registerNetworkCallback(networkRequest, networkCallback);
LinkProperties lp = new LinkProperties(); LinkProperties lp = new LinkProperties();
lp.setInterfaceName("wlan0"); lp.setInterfaceName(WIFI_IFNAME);
LinkAddress myIpv4Address = new LinkAddress("192.168.12.3/24"); LinkAddress myIpv4Address = new LinkAddress("192.168.12.3/24");
RouteInfo myIpv4DefaultRoute = new RouteInfo((IpPrefix) null, RouteInfo myIpv4DefaultRoute = new RouteInfo((IpPrefix) null,
NetworkUtils.numericToInetAddress("192.168.12.1"), lp.getInterfaceName()); NetworkUtils.numericToInetAddress("192.168.12.1"), lp.getInterfaceName());
@@ -3672,52 +3688,63 @@ public class ConnectivityServiceTest {
@Test @Test
public void testBasicDnsConfigurationPushed() throws Exception { public void testBasicDnsConfigurationPushed() throws Exception {
final String IFNAME = "test_rmnet_data0"; setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com");
final String[] EMPTY_TLS_SERVERS = new String[0]; ArgumentCaptor<String[]> tlsServers = ArgumentCaptor.forClass(String[].class);
// Clear any interactions that occur as a result of CS starting up.
reset(mNetworkManagementService);
final String[] EMPTY_STRING_ARRAY = new String[0];
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
waitForIdle(); waitForIdle();
verify(mNetworkManagementService, never()).setDnsConfigurationForNetwork( verify(mNetworkManagementService, never()).setDnsConfigurationForNetwork(
anyInt(), any(), any(), any(), anyString(), eq(EMPTY_TLS_SERVERS)); anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), eq(EMPTY_STRING_ARRAY));
verifyNoMoreInteractions(mNetworkManagementService);
final LinkProperties cellLp = new LinkProperties(); final LinkProperties cellLp = new LinkProperties();
cellLp.setInterfaceName(IFNAME); cellLp.setInterfaceName(MOBILE_IFNAME);
// Add IPv4 and IPv6 default routes, because DNS-over-TLS code does // Add IPv4 and IPv6 default routes, because DNS-over-TLS code does
// "is-reachable" testing in order to not program netd with unreachable // "is-reachable" testing in order to not program netd with unreachable
// nameservers that it might try repeated to validate. // nameservers that it might try repeated to validate.
cellLp.addLinkAddress(new LinkAddress("192.0.2.4/24")); cellLp.addLinkAddress(new LinkAddress("192.0.2.4/24"));
cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("192.0.2.4"), IFNAME)); cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("192.0.2.4"),
MOBILE_IFNAME));
cellLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64")); cellLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64"));
cellLp.addRoute( cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("2001:db8:1::1"),
new RouteInfo((IpPrefix) null, InetAddress.getByName("2001:db8:1::1"), IFNAME)); MOBILE_IFNAME));
mCellNetworkAgent.sendLinkProperties(cellLp); mCellNetworkAgent.sendLinkProperties(cellLp);
mCellNetworkAgent.connect(false); mCellNetworkAgent.connect(false);
waitForIdle(); waitForIdle();
verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork(
anyInt(), mStringArrayCaptor.capture(), any(), any(),
anyString(), eq(EMPTY_TLS_SERVERS));
// CS tells netd about the empty DNS config for this network. // CS tells netd about the empty DNS config for this network.
assertEmpty(mStringArrayCaptor.getValue()); verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), eq(EMPTY_STRING_ARRAY));
reset(mNetworkManagementService); reset(mNetworkManagementService);
cellLp.addDnsServer(InetAddress.getByName("2001:db8::1")); cellLp.addDnsServer(InetAddress.getByName("2001:db8::1"));
mCellNetworkAgent.sendLinkProperties(cellLp); mCellNetworkAgent.sendLinkProperties(cellLp);
waitForIdle(); waitForIdle();
verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork( verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
anyInt(), mStringArrayCaptor.capture(), any(), any(), anyInt(), mStringArrayCaptor.capture(), any(), any(),
anyString(), eq(EMPTY_TLS_SERVERS)); eq(""), tlsServers.capture());
assertEquals(1, mStringArrayCaptor.getValue().length); assertEquals(1, mStringArrayCaptor.getValue().length);
assertTrue(ArrayUtils.contains(mStringArrayCaptor.getValue(), "2001:db8::1")); assertTrue(ArrayUtils.contains(mStringArrayCaptor.getValue(), "2001:db8::1"));
// Opportunistic mode.
assertTrue(ArrayUtils.contains(tlsServers.getValue(), "2001:db8::1"));
reset(mNetworkManagementService); reset(mNetworkManagementService);
cellLp.addDnsServer(InetAddress.getByName("192.0.2.1")); cellLp.addDnsServer(InetAddress.getByName("192.0.2.1"));
mCellNetworkAgent.sendLinkProperties(cellLp); mCellNetworkAgent.sendLinkProperties(cellLp);
waitForIdle(); waitForIdle();
verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork( verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
anyInt(), mStringArrayCaptor.capture(), any(), any(), anyInt(), mStringArrayCaptor.capture(), any(), any(),
anyString(), eq(EMPTY_TLS_SERVERS)); eq(""), tlsServers.capture());
assertEquals(2, mStringArrayCaptor.getValue().length); assertEquals(2, mStringArrayCaptor.getValue().length);
assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(), assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(),
new String[]{"2001:db8::1", "192.0.2.1"})); new String[]{"2001:db8::1", "192.0.2.1"}));
// Opportunistic mode.
assertEquals(2, tlsServers.getValue().length);
assertTrue(ArrayUtils.containsAll(tlsServers.getValue(),
new String[]{"2001:db8::1", "192.0.2.1"}));
reset(mNetworkManagementService); reset(mNetworkManagementService);
final String TLS_SPECIFIER = "tls.example.com"; final String TLS_SPECIFIER = "tls.example.com";
@@ -3730,7 +3757,7 @@ public class ConnectivityServiceTest {
mCellNetworkAgent.getNetwork().netId, mCellNetworkAgent.getNetwork().netId,
new DnsManager.PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS))); new DnsManager.PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS)));
waitForIdle(); waitForIdle();
verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork( verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
anyInt(), mStringArrayCaptor.capture(), any(), any(), anyInt(), mStringArrayCaptor.capture(), any(), any(),
eq(TLS_SPECIFIER), eq(TLS_SERVERS)); eq(TLS_SPECIFIER), eq(TLS_SERVERS));
assertEquals(2, mStringArrayCaptor.getValue().length); assertEquals(2, mStringArrayCaptor.getValue().length);
@@ -3739,6 +3766,77 @@ public class ConnectivityServiceTest {
reset(mNetworkManagementService); reset(mNetworkManagementService);
} }
@Test
public void testPrivateDnsSettingsChange() throws Exception {
final String[] EMPTY_STRING_ARRAY = new String[0];
ArgumentCaptor<String[]> tlsServers = ArgumentCaptor.forClass(String[].class);
// Clear any interactions that occur as a result of CS starting up.
reset(mNetworkManagementService);
// The default on Android is opportunistic mode ("Automatic").
setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com");
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
waitForIdle();
// CS tells netd about the empty DNS config for this network.
verify(mNetworkManagementService, never()).setDnsConfigurationForNetwork(
anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), eq(EMPTY_STRING_ARRAY));
verifyNoMoreInteractions(mNetworkManagementService);
final LinkProperties cellLp = new LinkProperties();
cellLp.setInterfaceName(MOBILE_IFNAME);
// Add IPv4 and IPv6 default routes, because DNS-over-TLS code does
// "is-reachable" testing in order to not program netd with unreachable
// nameservers that it might try repeated to validate.
cellLp.addLinkAddress(new LinkAddress("192.0.2.4/24"));
cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("192.0.2.4"),
MOBILE_IFNAME));
cellLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64"));
cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("2001:db8:1::1"),
MOBILE_IFNAME));
cellLp.addDnsServer(InetAddress.getByName("2001:db8::1"));
cellLp.addDnsServer(InetAddress.getByName("192.0.2.1"));
mCellNetworkAgent.sendLinkProperties(cellLp);
mCellNetworkAgent.connect(false);
waitForIdle();
verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
anyInt(), mStringArrayCaptor.capture(), any(), any(),
eq(""), tlsServers.capture());
assertEquals(2, mStringArrayCaptor.getValue().length);
assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(),
new String[]{"2001:db8::1", "192.0.2.1"}));
// Opportunistic mode.
assertEquals(2, tlsServers.getValue().length);
assertTrue(ArrayUtils.containsAll(tlsServers.getValue(),
new String[]{"2001:db8::1", "192.0.2.1"}));
reset(mNetworkManagementService);
setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork(
anyInt(), mStringArrayCaptor.capture(), any(), any(),
eq(""), eq(EMPTY_STRING_ARRAY));
assertEquals(2, mStringArrayCaptor.getValue().length);
assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(),
new String[]{"2001:db8::1", "192.0.2.1"}));
reset(mNetworkManagementService);
setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com");
verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
anyInt(), mStringArrayCaptor.capture(), any(), any(),
eq(""), 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::1", "192.0.2.1"}));
reset(mNetworkManagementService);
// Can't test strict mode without properly mocking out the DNS lookups.
}
private void checkDirectlyConnectedRoutes(Object callbackObj, private void checkDirectlyConnectedRoutes(Object callbackObj,
Collection<LinkAddress> linkAddresses, Collection<RouteInfo> otherRoutes) { Collection<LinkAddress> linkAddresses, Collection<RouteInfo> otherRoutes) {
assertTrue(callbackObj instanceof LinkProperties); assertTrue(callbackObj instanceof LinkProperties);