diff options
4 files changed, 532 insertions, 5 deletions
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 59cb13562764..6463bed639a9 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -52,6 +52,8 @@ import android.database.ContentObserver; import android.net.ConnectivityManager; import android.net.ConnectivityManager.PacketKeepalive; import android.net.IConnectivityManager; +import android.net.IIpConnectivityMetrics; +import android.net.INetdEventCallback; import android.net.INetworkManagementEventObserver; import android.net.INetworkPolicyListener; import android.net.INetworkPolicyManager; @@ -140,6 +142,7 @@ import com.android.server.am.BatteryStatsService; import com.android.server.connectivity.DataConnectionStats; import com.android.server.connectivity.DnsManager; import com.android.server.connectivity.DnsManager.PrivateDnsConfig; +import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate; import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.KeepaliveTracker; import com.android.server.connectivity.LingerMonitor; @@ -155,6 +158,7 @@ import com.android.server.connectivity.PermissionMonitor; import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; import com.android.server.connectivity.tethering.TetheringDependencies; +import com.android.server.net.BaseNetdEventCallback; import com.android.server.net.BaseNetworkObserver; import com.android.server.net.LockdownVpnTracker; import com.android.server.net.NetworkPolicyManagerInternal; @@ -256,6 +260,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private INetworkStatsService mStatsService; private INetworkPolicyManager mPolicyManager; private NetworkPolicyManagerInternal mPolicyManagerInternal; + private IIpConnectivityMetrics mIpConnectivityMetrics; private String mCurrentTcpBufferSizes; @@ -414,6 +419,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // Handle changes in Private DNS settings. private static final int EVENT_PRIVATE_DNS_SETTINGS_CHANGED = 37; + // Handle private DNS validation status updates. + private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38; + private static String eventName(int what) { return sMagicDecoderRing.get(what, Integer.toString(what)); } @@ -1553,6 +1561,41 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } + @VisibleForTesting + protected final INetdEventCallback mNetdEventCallback = new BaseNetdEventCallback() { + @Override + public void onPrivateDnsValidationEvent(int netId, String ipAddress, + String hostname, boolean validated) { + try { + mHandler.sendMessage(mHandler.obtainMessage( + EVENT_PRIVATE_DNS_VALIDATION_UPDATE, + new PrivateDnsValidationUpdate(netId, + InetAddress.parseNumericAddress(ipAddress), + hostname, validated))); + } catch (IllegalArgumentException e) { + loge("Error parsing ip address in validation event"); + } + } + }; + + @VisibleForTesting + protected void registerNetdEventCallback() { + mIpConnectivityMetrics = + (IIpConnectivityMetrics) IIpConnectivityMetrics.Stub.asInterface( + ServiceManager.getService(IpConnectivityLog.SERVICE_NAME)); + if (mIpConnectivityMetrics == null) { + Slog.wtf(TAG, "Missing IIpConnectivityMetrics"); + } + + try { + mIpConnectivityMetrics.addNetdEventCallback( + INetdEventCallback.CALLBACK_CALLER_CONNECTIVITY_SERVICE, + mNetdEventCallback); + } catch (Exception e) { + loge("Error registering netd callback: " + e); + } + } + private final INetworkPolicyListener mPolicyListener = new NetworkPolicyManager.Listener() { @Override public void onUidRulesChanged(int uid, int uidRules) { @@ -1738,6 +1781,7 @@ public class ConnectivityService extends IConnectivityManager.Stub void systemReady() { loadGlobalProxy(); + registerNetdEventCallback(); synchronized (this) { mSystemReady = true; @@ -2288,6 +2332,9 @@ public class ConnectivityService extends IConnectivityManager.Stub for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { handlePerNetworkPrivateDnsConfig(nai, cfg); + if (networkRequiresValidation(nai)) { + handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties)); + } } } @@ -2312,6 +2359,15 @@ public class ConnectivityService extends IConnectivityManager.Stub updateDnses(nai.linkProperties, null, nai.network.netId); } + private void handlePrivateDnsValidationUpdate(PrivateDnsValidationUpdate update) { + NetworkAgentInfo nai = getNetworkAgentInfoForNetId(update.netId); + if (nai == null) { + return; + } + mDnsManager.updatePrivateDnsValidation(update); + handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties)); + } + private void updateLingerState(NetworkAgentInfo nai, long now) { // 1. Update the linger timer. If it's changed, reschedule or cancel the alarm. // 2. If the network was lingering and there are now requests, unlinger it. @@ -3002,6 +3058,10 @@ public class ConnectivityService extends IConnectivityManager.Stub case EVENT_PRIVATE_DNS_SETTINGS_CHANGED: handlePrivateDnsSettingsChanged(); break; + case EVENT_PRIVATE_DNS_VALIDATION_UPDATE: + handlePrivateDnsValidationUpdate( + (PrivateDnsValidationUpdate) msg.obj); + break; } } } @@ -4575,6 +4635,11 @@ public class ConnectivityService extends IConnectivityManager.Stub updateRoutes(newLp, oldLp, netId); updateDnses(newLp, oldLp, netId); + // Make sure LinkProperties represents the latest private DNS status. + // This does not need to be done before updateDnses because the + // LinkProperties are not the source of the private DNS configuration. + // updateDnses will fetch the private DNS configuration from DnsManager. + mDnsManager.updatePrivateDnsStatus(netId, newLp); // Start or stop clat accordingly to network state. networkAgent.updateClat(mNetd); diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java index 2a361a02d7d2..7aaac06024e7 100644 --- a/services/core/java/com/android/server/connectivity/DnsManager.java +++ b/services/core/java/com/android/server/connectivity/DnsManager.java @@ -37,10 +37,10 @@ import android.net.Uri; import android.net.dns.ResolvUtil; import android.os.Binder; import android.os.INetworkManagementService; -import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; +import android.util.Pair; import android.util.Slog; import com.android.server.connectivity.MockableSystemProperties; @@ -50,8 +50,12 @@ import java.net.UnknownHostException; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; +import java.util.Set; import java.util.StringJoiner; @@ -110,6 +114,7 @@ import java.util.StringJoiner; */ public class DnsManager { private static final String TAG = DnsManager.class.getSimpleName(); + private static final PrivateDnsConfig PRIVATE_DNS_OFF = new PrivateDnsConfig(); /* Defaults for resolver parameters. */ private static final int DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS = 1800; @@ -183,11 +188,89 @@ public class DnsManager { }; } + public static class PrivateDnsValidationUpdate { + final public int netId; + final public InetAddress ipAddress; + final public String hostname; + final public boolean validated; + + public PrivateDnsValidationUpdate(int netId, InetAddress ipAddress, + String hostname, boolean validated) { + this.netId = netId; + this.ipAddress = ipAddress; + this.hostname = hostname; + this.validated = validated; + } + } + + private static class PrivateDnsValidationStatuses { + enum ValidationStatus { + IN_PROGRESS, + FAILED, + SUCCEEDED + } + + // Validation statuses of <hostname, ipAddress> pairs for a single netId + private Map<Pair<String, InetAddress>, ValidationStatus> mValidationMap; + + private PrivateDnsValidationStatuses() { + mValidationMap = new HashMap<>(); + } + + private boolean hasValidatedServer() { + for (ValidationStatus status : mValidationMap.values()) { + if (status == ValidationStatus.SUCCEEDED) { + return true; + } + } + return false; + } + + private void updateTrackedDnses(String[] ipAddresses, String hostname) { + Set<Pair<String, InetAddress>> latestDnses = new HashSet<>(); + for (String ipAddress : ipAddresses) { + try { + latestDnses.add(new Pair(hostname, + InetAddress.parseNumericAddress(ipAddress))); + } catch (IllegalArgumentException e) {} + } + // Remove <hostname, ipAddress> pairs that should not be tracked. + for (Iterator<Map.Entry<Pair<String, InetAddress>, ValidationStatus>> it = + mValidationMap.entrySet().iterator(); it.hasNext(); ) { + Map.Entry<Pair<String, InetAddress>, ValidationStatus> entry = it.next(); + if (!latestDnses.contains(entry.getKey())) { + it.remove(); + } + } + // Add new <hostname, ipAddress> pairs that should be tracked. + for (Pair<String, InetAddress> p : latestDnses) { + if (!mValidationMap.containsKey(p)) { + mValidationMap.put(p, ValidationStatus.IN_PROGRESS); + } + } + } + + private void updateStatus(PrivateDnsValidationUpdate update) { + Pair<String, InetAddress> p = new Pair(update.hostname, + update.ipAddress); + if (!mValidationMap.containsKey(p)) { + return; + } + if (update.validated) { + mValidationMap.put(p, ValidationStatus.SUCCEEDED); + } else { + mValidationMap.put(p, ValidationStatus.FAILED); + } + } + } + private final Context mContext; private final ContentResolver mContentResolver; private final INetworkManagementService mNMS; private final MockableSystemProperties mSystemProperties; + // TODO: Replace these Maps with SparseArrays. private final Map<Integer, PrivateDnsConfig> mPrivateDnsMap; + private final Map<Integer, PrivateDnsValidationStatuses> mPrivateDnsValidationMap; private int mNumDnsEntries; private int mSampleValidity; @@ -203,6 +286,7 @@ public class DnsManager { mNMS = nms; mSystemProperties = sp; mPrivateDnsMap = new HashMap<>(); + mPrivateDnsValidationMap = new HashMap<>(); // TODO: Create and register ContentObservers to track every setting // used herein, posting messages to respond to changes. @@ -214,6 +298,7 @@ public class DnsManager { public void removeNetwork(Network network) { mPrivateDnsMap.remove(network.netId); + mPrivateDnsValidationMap.remove(network.netId); } public PrivateDnsConfig updatePrivateDns(Network network, PrivateDnsConfig cfg) { @@ -223,6 +308,40 @@ public class DnsManager { : mPrivateDnsMap.remove(network.netId); } + public void updatePrivateDnsStatus(int netId, LinkProperties lp) { + // Use the PrivateDnsConfig data pushed to this class instance + // from ConnectivityService. + final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.getOrDefault(netId, + PRIVATE_DNS_OFF); + + final boolean useTls = privateDnsCfg.useTls; + final boolean strictMode = privateDnsCfg.inStrictMode(); + final String tlsHostname = strictMode ? privateDnsCfg.hostname : ""; + + if (strictMode) { + lp.setUsePrivateDns(true); + lp.setPrivateDnsServerName(tlsHostname); + } else if (useTls) { + // We are in opportunistic mode. Private DNS should be used if there + // is a known DNS-over-TLS validated server. + boolean validated = mPrivateDnsValidationMap.containsKey(netId) && + mPrivateDnsValidationMap.get(netId).hasValidatedServer(); + lp.setUsePrivateDns(validated); + lp.setPrivateDnsServerName(null); + } else { + // Private DNS is disabled. + lp.setUsePrivateDns(false); + lp.setPrivateDnsServerName(null); + } + } + + public void updatePrivateDnsValidation(PrivateDnsValidationUpdate update) { + final PrivateDnsValidationStatuses statuses = + mPrivateDnsValidationMap.get(update.netId); + if (statuses == null) return; + statuses.updateStatus(update); + } + public void setDnsConfigurationForNetwork( int netId, LinkProperties lp, boolean isDefaultNetwork) { final String[] assignedServers = NetworkUtils.makeStrings(lp.getDnsServers()); @@ -238,10 +357,11 @@ public class DnsManager { // // At this time we do not attempt to enable Private DNS on non-Internet // networks like IMS. - final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.get(netId); + final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.getOrDefault(netId, + PRIVATE_DNS_OFF); - final boolean useTls = (privateDnsCfg != null) && privateDnsCfg.useTls; - final boolean strictMode = (privateDnsCfg != null) && privateDnsCfg.inStrictMode(); + final boolean useTls = privateDnsCfg.useTls; + final boolean strictMode = privateDnsCfg.inStrictMode(); final String tlsHostname = strictMode ? privateDnsCfg.hostname : ""; final String[] tlsServers = strictMode ? NetworkUtils.makeStrings( @@ -251,6 +371,17 @@ public class DnsManager { : useTls ? assignedServers // Opportunistic : new String[0]; // Off + // Prepare to track the validation status of the DNS servers in the + // resolver config when private DNS is in opportunistic or strict mode. + if (useTls) { + if (!mPrivateDnsValidationMap.containsKey(netId)) { + mPrivateDnsValidationMap.put(netId, new PrivateDnsValidationStatuses()); + } + mPrivateDnsValidationMap.get(netId).updateTrackedDnses(tlsServers, tlsHostname); + } else { + mPrivateDnsValidationMap.remove(netId); + } + Slog.d(TAG, String.format("setDnsConfigurationForNetwork(%d, %s, %s, %s, %s, %s)", netId, Arrays.toString(assignedServers), Arrays.toString(domainStrs), Arrays.toString(params), tlsHostname, Arrays.toString(tlsServers))); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index b0e11c4e8b48..482d6e1fdb31 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -161,6 +161,7 @@ import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -879,6 +880,10 @@ public class ConnectivityServiceTest { return mMetricsService; } + @Override + protected void registerNetdEventCallback() { + } + public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() { return mLastCreatedNetworkMonitor; } @@ -3777,6 +3782,11 @@ public class ConnectivityServiceTest { // The default on Android is opportunistic mode ("Automatic"). setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com"); + final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + mCm.requestNetwork(cellRequest, cellNetworkCallback); + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); waitForIdle(); // CS tells netd about the empty DNS config for this network. @@ -3812,6 +3822,14 @@ public class ConnectivityServiceTest { assertTrue(ArrayUtils.containsAll(tlsServers.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); reset(mNetworkManagementService); + cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, + mCellNetworkAgent); + CallbackInfo cbi = cellNetworkCallback.expectCallback( + CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive()); + assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com"); verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork( @@ -3821,6 +3839,7 @@ public class ConnectivityServiceTest { assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); reset(mNetworkManagementService); + cellNetworkCallback.assertNoCallback(); setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com"); verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork( @@ -3833,8 +3852,112 @@ public class ConnectivityServiceTest { assertTrue(ArrayUtils.containsAll(tlsServers.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); reset(mNetworkManagementService); + cellNetworkCallback.assertNoCallback(); - // Can't test strict mode without properly mocking out the DNS lookups. + 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. + cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, + mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive()); + assertEquals("strict.example.com", ((LinkProperties)cbi.arg).getPrivateDnsServerName()); + } + + @Test + public void testLinkPropertiesWithPrivateDnsValidationEvents() throws Exception { + // The default on Android is opportunistic mode ("Automatic"). + setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com"); + + final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + mCm.requestNetwork(cellRequest, cellNetworkCallback); + + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + waitForIdle(); + LinkProperties lp = new LinkProperties(); + mCellNetworkAgent.sendLinkProperties(lp); + mCellNetworkAgent.connect(false); + waitForIdle(); + cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, + mCellNetworkAgent); + CallbackInfo cbi = cellNetworkCallback.expectCallback( + CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive()); + assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); + Set<InetAddress> dnsServers = new HashSet<>(); + checkDnsServers(cbi.arg, dnsServers); + + // Send a validation event for a server that is not part of the current + // resolver config. The validation event should be ignored. + mService.mNetdEventCallback.onPrivateDnsValidationEvent( + mCellNetworkAgent.getNetwork().netId, "", "145.100.185.18", true); + cellNetworkCallback.assertNoCallback(); + + // Add a dns server to the LinkProperties. + LinkProperties lp2 = new LinkProperties(lp); + lp2.addDnsServer(InetAddress.getByName("145.100.185.16")); + mCellNetworkAgent.sendLinkProperties(lp2); + cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, + mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive()); + assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); + dnsServers.add(InetAddress.getByName("145.100.185.16")); + checkDnsServers(cbi.arg, dnsServers); + + // Send a validation event containing a hostname that is not part of + // the current resolver config. The validation event should be ignored. + mService.mNetdEventCallback.onPrivateDnsValidationEvent( + mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "hostname", true); + cellNetworkCallback.assertNoCallback(); + + // Send a validation event where validation failed. + mService.mNetdEventCallback.onPrivateDnsValidationEvent( + mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "", false); + cellNetworkCallback.assertNoCallback(); + + // Send a validation event where validation succeeded for a server in + // the current resolver config. A LinkProperties callback with updated + // private dns fields should be sent. + mService.mNetdEventCallback.onPrivateDnsValidationEvent( + mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "", true); + cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, + mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive()); + assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); + checkDnsServers(cbi.arg, dnsServers); + + // The private dns fields in LinkProperties should be preserved when + // the network agent sends unrelated changes. + LinkProperties lp3 = new LinkProperties(lp2); + lp3.setMtu(1300); + mCellNetworkAgent.sendLinkProperties(lp3); + cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, + mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive()); + assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); + checkDnsServers(cbi.arg, dnsServers); + assertEquals(1300, ((LinkProperties)cbi.arg).getMtu()); + + // Removing the only validated server should affect the private dns + // fields in LinkProperties. + LinkProperties lp4 = new LinkProperties(lp3); + lp4.removeDnsServer(InetAddress.getByName("145.100.185.16")); + mCellNetworkAgent.sendLinkProperties(lp4); + cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, + mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive()); + assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); + dnsServers.remove(InetAddress.getByName("145.100.185.16")); + checkDnsServers(cbi.arg, dnsServers); + assertEquals(1300, ((LinkProperties)cbi.arg).getMtu()); } private void checkDirectlyConnectedRoutes(Object callbackObj, @@ -3854,6 +3977,13 @@ public class ConnectivityServiceTest { assertTrue(observedRoutes.containsAll(expectedRoutes)); } + private static void checkDnsServers(Object callbackObj, Set<InetAddress> dnsServers) { + assertTrue(callbackObj instanceof LinkProperties); + LinkProperties lp = (LinkProperties) callbackObj; + assertEquals(dnsServers.size(), lp.getDnsServers().size()); + assertTrue(lp.getDnsServers().containsAll(dnsServers)); + } + private static <T> void assertEmpty(T[] ts) { int length = ts.length; assertEquals("expected empty array, but length was " + length, 0, length); diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java new file mode 100644 index 000000000000..bcd8bf3df7ce --- /dev/null +++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +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 org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.net.LinkProperties; +import android.net.Network; +import android.os.INetworkManagementService; +import android.provider.Settings; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.test.mock.MockContentResolver; + +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.server.connectivity.MockableSystemProperties; + +import java.net.InetAddress; + +import org.junit.runner.RunWith; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link DnsManager}. + * + * Build, install and run with: + * runtest frameworks-net -c com.android.server.connectivity.DnsManagerTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DnsManagerTest { + static final int TEST_NETID = 100; + static final int TEST_NETID_ALTERNATE = 101; + static final int TEST_NETID_UNTRACKED = 102; + final boolean IS_DEFAULT = true; + final boolean NOT_DEFAULT = false; + + DnsManager mDnsManager; + MockContentResolver mContentResolver; + + @Mock Context mCtx; + @Mock INetworkManagementService mNMService; + @Mock MockableSystemProperties mSystemProperties; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mContentResolver = new MockContentResolver(); + mContentResolver.addProvider(Settings.AUTHORITY, + new FakeSettingsProvider()); + when(mCtx.getContentResolver()).thenReturn(mContentResolver); + mDnsManager = new DnsManager(mCtx, mNMService, mSystemProperties); + + // Clear the private DNS settings + Settings.Global.putString(mContentResolver, + Settings.Global.PRIVATE_DNS_MODE, ""); + Settings.Global.putString(mContentResolver, + Settings.Global.PRIVATE_DNS_SPECIFIER, ""); + } + + @Test + public void testTrackedValidationUpdates() throws Exception { + mDnsManager.updatePrivateDns(new Network(TEST_NETID), + mDnsManager.getPrivateDnsConfig()); + mDnsManager.updatePrivateDns(new Network(TEST_NETID_ALTERNATE), + mDnsManager.getPrivateDnsConfig()); + LinkProperties lp = new LinkProperties(); + lp.addDnsServer(InetAddress.getByName("3.3.3.3")); + lp.addDnsServer(InetAddress.getByName("4.4.4.4")); + + // Send a validation event that is tracked on the alternate netId + mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT); + mDnsManager.setDnsConfigurationForNetwork(TEST_NETID_ALTERNATE, lp, NOT_DEFAULT); + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_ALTERNATE, + InetAddress.parseNumericAddress("4.4.4.4"), "", true)); + LinkProperties fixedLp = new LinkProperties(lp); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp); + assertFalse(fixedLp.isPrivateDnsActive()); + assertNull(fixedLp.getPrivateDnsServerName()); + fixedLp = new LinkProperties(lp); + mDnsManager.updatePrivateDnsStatus(TEST_NETID_ALTERNATE, fixedLp); + assertTrue(fixedLp.isPrivateDnsActive()); + assertNull(fixedLp.getPrivateDnsServerName()); + + // Switch to strict mode + Settings.Global.putString(mContentResolver, + Settings.Global.PRIVATE_DNS_MODE, + PRIVATE_DNS_MODE_PROVIDER_HOSTNAME); + Settings.Global.putString(mContentResolver, + Settings.Global.PRIVATE_DNS_SPECIFIER, "strictmode.com"); + mDnsManager.updatePrivateDns(new Network(TEST_NETID), + mDnsManager.getPrivateDnsConfig()); + mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT); + fixedLp = new LinkProperties(lp); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp); + assertTrue(fixedLp.isPrivateDnsActive()); + assertEquals("strictmode.com", fixedLp.getPrivateDnsServerName()); + fixedLp = new LinkProperties(lp); + } + + @Test + public void testIgnoreUntrackedValidationUpdates() throws Exception { + // The PrivateDnsConfig map is empty, so no validation events will + // be tracked. + LinkProperties lp = new LinkProperties(); + lp.addDnsServer(InetAddress.getByName("3.3.3.3")); + mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT); + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, + InetAddress.parseNumericAddress("3.3.3.3"), "", true)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + assertFalse(lp.isPrivateDnsActive()); + assertNull(lp.getPrivateDnsServerName()); + + // Validation event has untracked netId + mDnsManager.updatePrivateDns(new Network(TEST_NETID), + mDnsManager.getPrivateDnsConfig()); + mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT); + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_UNTRACKED, + InetAddress.parseNumericAddress("3.3.3.3"), "", true)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + assertFalse(lp.isPrivateDnsActive()); + assertNull(lp.getPrivateDnsServerName()); + + // Validation event has untracked ipAddress + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, + InetAddress.parseNumericAddress("4.4.4.4"), "", true)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + assertFalse(lp.isPrivateDnsActive()); + assertNull(lp.getPrivateDnsServerName()); + + // Validation event has untracked hostname + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, + InetAddress.parseNumericAddress("3.3.3.3"), "hostname", + true)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + assertFalse(lp.isPrivateDnsActive()); + assertNull(lp.getPrivateDnsServerName()); + + // Validation event failed + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, + InetAddress.parseNumericAddress("3.3.3.3"), "", false)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + assertFalse(lp.isPrivateDnsActive()); + assertNull(lp.getPrivateDnsServerName()); + + // Network removed + mDnsManager.removeNetwork(new Network(TEST_NETID)); + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, + InetAddress.parseNumericAddress("3.3.3.3"), "", true)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + assertFalse(lp.isPrivateDnsActive()); + assertNull(lp.getPrivateDnsServerName()); + + // Turn private DNS mode off + Settings.Global.putString(mContentResolver, + Settings.Global.PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_OFF); + mDnsManager.updatePrivateDns(new Network(TEST_NETID), + mDnsManager.getPrivateDnsConfig()); + mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT); + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, + InetAddress.parseNumericAddress("3.3.3.3"), "", true)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + assertFalse(lp.isPrivateDnsActive()); + assertNull(lp.getPrivateDnsServerName()); + } +} |