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
Change-Id: Ic4322af3cb49149f2d975cb31f54b2ac7927f907
This commit is contained in:
Erik Kline
2018-03-21 07:18:33 -07:00
parent 4acfd60844
commit 9a62f0108e
3 changed files with 209 additions and 111 deletions

View File

@@ -902,7 +902,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
// Used only for testing.
// 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).
// 2. Give FakeSettingsProvider an alternative notification mechanism and have the test use it
// by subclassing SettingsObserver.
@@ -911,6 +911,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
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() {
final boolean enable = toBool(Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1));
@@ -940,8 +946,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
private void registerPrivateDnsSettingsCallbacks() {
for (Uri u : DnsManager.getPrivateDnsSettingsUris()) {
mSettingsObserver.observe(u, EVENT_PRIVATE_DNS_SETTINGS_CHANGED);
for (Uri uri : DnsManager.getPrivateDnsSettingsUris()) {
mSettingsObserver.observe(uri, EVENT_PRIVATE_DNS_SETTINGS_CHANGED);
}
}
@@ -994,8 +1000,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (network == null) {
return null;
}
return getNetworkAgentInfoForNetId(network.netId);
}
private NetworkAgentInfo getNetworkAgentInfoForNetId(int netId) {
synchronized (mNetworkForNetId) {
return mNetworkForNetId.get(network.netId);
return mNetworkForNetId.get(netId);
}
}
@@ -1135,9 +1145,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
NetworkAgentInfo nai;
if (vpnNetId != NETID_UNSET) {
synchronized (mNetworkForNetId) {
nai = mNetworkForNetId.get(vpnNetId);
}
nai = getNetworkAgentInfoForNetId(vpnNetId);
if (nai != null) return nai.network;
}
nai = getDefaultNetwork();
@@ -2113,41 +2121,21 @@ public class ConnectivityService extends IConnectivityManager.Stub
default:
return false;
case NetworkMonitor.EVENT_NETWORK_TESTED: {
final NetworkAgentInfo nai;
synchronized (mNetworkForNetId) {
nai = mNetworkForNetId.get(msg.arg2);
}
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
if (nai == null) break;
final boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
final boolean wasValidated = nai.lastValidated;
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 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) {
final String logMsg = !TextUtils.isEmpty(redirectUrl)
? " with redirect to " + redirectUrl
: "";
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 (wasDefault) {
metricsLogger().defaultNetworkMetrics().logDefaultNetworkValidity(
@@ -2176,10 +2164,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
final int netId = msg.arg2;
final boolean visible = toBool(msg.arg1);
final NetworkAgentInfo nai;
synchronized (mNetworkForNetId) {
nai = mNetworkForNetId.get(netId);
}
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
// If captive portal status has changed, update capabilities or disconnect.
if (nai != null && (visible != nai.lastCaptivePortalDetected)) {
final int oldScore = nai.getCurrentScore();
@@ -2210,18 +2195,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
break;
}
case NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
final NetworkAgentInfo nai;
synchronized (mNetworkForNetId) {
nai = mNetworkForNetId.get(msg.arg2);
}
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
if (nai == null) break;
final PrivateDnsConfig cfg = (PrivateDnsConfig) msg.obj;
final boolean reevaluationRequired = updatePrivateDns(nai, cfg);
if (nai.lastValidated && reevaluationRequired) {
nai.networkMonitor.sendMessage(
NetworkMonitor.CMD_FORCE_REEVALUATION, Process.SYSTEM_UID);
}
updatePrivateDns(nai, (PrivateDnsConfig) msg.obj);
break;
}
}
@@ -2259,61 +2236,38 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
private boolean networkRequiresValidation(NetworkAgentInfo nai) {
return NetworkMonitor.isValidationRequired(
mDefaultRequest.networkCapabilities, nai.networkCapabilities);
}
private void handlePrivateDnsSettingsChanged() {
final PrivateDnsConfig cfg = mDnsManager.getPrivateDnsConfig();
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
// Private DNS only ever applies to networks that might provide
// 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);
}
handlePerNetworkPrivateDnsConfig(nai, cfg);
}
}
private boolean updatePrivateDns(NetworkAgentInfo nai, PrivateDnsConfig newCfg) {
final boolean reevaluationRequired = true;
final boolean dontReevaluate = false;
private void handlePerNetworkPrivateDnsConfig(NetworkAgentInfo nai, PrivateDnsConfig cfg) {
// Private DNS only ever applies to networks that might provide
// 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);
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) {
@@ -3252,7 +3206,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) {
return;
}
nai.networkMonitor.sendMessage(NetworkMonitor.CMD_FORCE_REEVALUATION, uid);
nai.networkMonitor.forceReevaluation(uid);
}
private ProxyInfo getDefaultProxy() {
@@ -4871,7 +4825,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
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
return;
}
@@ -5451,6 +5405,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
Slog.wtf(TAG, networkAgent.name() + " connected with null LinkProperties");
}
handlePerNetworkPrivateDnsConfig(networkAgent, mDnsManager.getPrivateDnsConfig());
updateLinkProperties(networkAgent, null);
networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);

View File

@@ -61,6 +61,51 @@ import java.util.StringJoiner;
* This class it NOT designed for concurrent access. Furthermore, all non-static
* 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
*/
public class DnsManager {

View File

@@ -17,6 +17,9 @@
package com.android.server;
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_MOBILE;
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.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -181,6 +185,9 @@ public class ConnectivityServiceTest {
private static final int TIMEOUT_MS = 500;
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 WrappedConnectivityService mService;
private WrappedConnectivityManager mCm;
@@ -751,7 +758,7 @@ public class ConnectivityServiceTest {
// NetworkMonitor implementation allowing overriding of Internet connectivity probe result.
private class WrappedNetworkMonitor extends NetworkMonitor {
public Handler connectivityHandler;
public final Handler connectivityHandler;
// HTTP response code fed back to NetworkMonitor for Internet connectivity probe.
public int gen204ProbeResult = 500;
public String gen204ProbeRedirectUrl = null;
@@ -926,6 +933,7 @@ public class ConnectivityServiceTest {
// Ensure that the default setting for Captive Portals is used for most tests
setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
setMobileDataAlwaysOn(false);
setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
}
@After
@@ -2580,6 +2588,14 @@ public class ConnectivityServiceTest {
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) {
NetworkCapabilities nc = mCm.getNetworkCapabilities(network.getNetwork());
assertNotNull(nc);
@@ -3579,7 +3595,7 @@ public class ConnectivityServiceTest {
mCm.registerNetworkCallback(networkRequest, networkCallback);
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("wlan0");
lp.setInterfaceName(WIFI_IFNAME);
LinkAddress myIpv4Address = new LinkAddress("192.168.12.3/24");
RouteInfo myIpv4DefaultRoute = new RouteInfo((IpPrefix) null,
NetworkUtils.numericToInetAddress("192.168.12.1"), lp.getInterfaceName());
@@ -3668,52 +3684,63 @@ public class ConnectivityServiceTest {
@Test
public void testBasicDnsConfigurationPushed() throws Exception {
final String IFNAME = "test_rmnet_data0";
final String[] EMPTY_TLS_SERVERS = new String[0];
setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com");
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);
waitForIdle();
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();
cellLp.setInterfaceName(IFNAME);
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"), 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.addRoute(
new RouteInfo((IpPrefix) null, InetAddress.getByName("2001:db8:1::1"), IFNAME));
cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("2001:db8:1::1"),
MOBILE_IFNAME));
mCellNetworkAgent.sendLinkProperties(cellLp);
mCellNetworkAgent.connect(false);
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.
assertEmpty(mStringArrayCaptor.getValue());
verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), eq(EMPTY_STRING_ARRAY));
reset(mNetworkManagementService);
cellLp.addDnsServer(InetAddress.getByName("2001:db8::1"));
mCellNetworkAgent.sendLinkProperties(cellLp);
waitForIdle();
verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork(
verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
anyInt(), mStringArrayCaptor.capture(), any(), any(),
anyString(), eq(EMPTY_TLS_SERVERS));
eq(""), tlsServers.capture());
assertEquals(1, mStringArrayCaptor.getValue().length);
assertTrue(ArrayUtils.contains(mStringArrayCaptor.getValue(), "2001:db8::1"));
// Opportunistic mode.
assertTrue(ArrayUtils.contains(tlsServers.getValue(), "2001:db8::1"));
reset(mNetworkManagementService);
cellLp.addDnsServer(InetAddress.getByName("192.0.2.1"));
mCellNetworkAgent.sendLinkProperties(cellLp);
waitForIdle();
verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork(
verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
anyInt(), mStringArrayCaptor.capture(), any(), any(),
anyString(), eq(EMPTY_TLS_SERVERS));
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);
final String TLS_SPECIFIER = "tls.example.com";
@@ -3726,7 +3753,7 @@ public class ConnectivityServiceTest {
mCellNetworkAgent.getNetwork().netId,
new DnsManager.PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS)));
waitForIdle();
verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork(
verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
anyInt(), mStringArrayCaptor.capture(), any(), any(),
eq(TLS_SPECIFIER), eq(TLS_SERVERS));
assertEquals(2, mStringArrayCaptor.getValue().length);
@@ -3735,6 +3762,77 @@ public class ConnectivityServiceTest {
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,
Collection<LinkAddress> linkAddresses, Collection<RouteInfo> otherRoutes) {
assertTrue(callbackObj instanceof LinkProperties);