Loading services/core/java/com/android/server/ConnectivityService.java +48 −93 Original line number Diff line number Diff line Loading @@ -943,6 +943,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)); Loading Loading @@ -972,8 +978,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); } } Loading Loading @@ -1026,8 +1032,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); } } Loading Loading @@ -1167,9 +1177,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(); Loading Loading @@ -2155,41 +2163,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( Loading Loading @@ -2218,10 +2206,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(); Loading Loading @@ -2252,18 +2237,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; } } Loading Loading @@ -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() { final PrivateDnsConfig cfg = mDnsManager.getPrivateDnsConfig(); for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { handlePerNetworkPrivateDnsConfig(nai, cfg); } } 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 (!NetworkMonitor.isValidationRequired( mDefaultRequest.networkCapabilities, nai.networkCapabilities)) { continue; } if (!networkRequiresValidation(nai)) return; // 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. // 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 boolean updatePrivateDns(NetworkAgentInfo nai, PrivateDnsConfig newCfg) { final boolean reevaluationRequired = true; final boolean dontReevaluate = false; final PrivateDnsConfig oldCfg = mDnsManager.updatePrivateDns(nai.network, newCfg); 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) { Loading Loading @@ -3300,7 +3254,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() { Loading Loading @@ -4919,7 +4873,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; } Loading Loading @@ -5495,6 +5449,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) { networkAgent.everConnected = true; handlePerNetworkPrivateDnsConfig(networkAgent, mDnsManager.getPrivateDnsConfig()); updateLinkProperties(networkAgent, null); notifyIfacesChangedForNetworkStats(); Loading services/core/java/com/android/server/connectivity/DnsManager.java +45 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading services/core/java/com/android/server/connectivity/NetworkMonitor.java +200 −73 File changed.Preview size limit exceeded, changes collapsed. Show changes tests/net/java/com/android/server/ConnectivityServiceTest.java +116 −18 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -928,6 +935,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 Loading Loading @@ -2582,6 +2590,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); Loading Loading @@ -3583,7 +3599,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()); Loading Loading @@ -3672,52 +3688,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"; Loading @@ -3730,7 +3757,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); Loading @@ -3739,6 +3766,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); Loading Loading
services/core/java/com/android/server/ConnectivityService.java +48 −93 Original line number Diff line number Diff line Loading @@ -943,6 +943,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)); Loading Loading @@ -972,8 +978,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); } } Loading Loading @@ -1026,8 +1032,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); } } Loading Loading @@ -1167,9 +1177,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(); Loading Loading @@ -2155,41 +2163,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( Loading Loading @@ -2218,10 +2206,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(); Loading Loading @@ -2252,18 +2237,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; } } Loading Loading @@ -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() { final PrivateDnsConfig cfg = mDnsManager.getPrivateDnsConfig(); for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { handlePerNetworkPrivateDnsConfig(nai, cfg); } } 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 (!NetworkMonitor.isValidationRequired( mDefaultRequest.networkCapabilities, nai.networkCapabilities)) { continue; } if (!networkRequiresValidation(nai)) return; // 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. // 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 boolean updatePrivateDns(NetworkAgentInfo nai, PrivateDnsConfig newCfg) { final boolean reevaluationRequired = true; final boolean dontReevaluate = false; final PrivateDnsConfig oldCfg = mDnsManager.updatePrivateDns(nai.network, newCfg); 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) { Loading Loading @@ -3300,7 +3254,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() { Loading Loading @@ -4919,7 +4873,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; } Loading Loading @@ -5495,6 +5449,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) { networkAgent.everConnected = true; handlePerNetworkPrivateDnsConfig(networkAgent, mDnsManager.getPrivateDnsConfig()); updateLinkProperties(networkAgent, null); notifyIfacesChangedForNetworkStats(); Loading
services/core/java/com/android/server/connectivity/DnsManager.java +45 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading
services/core/java/com/android/server/connectivity/NetworkMonitor.java +200 −73 File changed.Preview size limit exceeded, changes collapsed. Show changes
tests/net/java/com/android/server/ConnectivityServiceTest.java +116 −18 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -928,6 +935,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 Loading Loading @@ -2582,6 +2590,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); Loading Loading @@ -3583,7 +3599,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()); Loading Loading @@ -3672,52 +3688,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"; Loading @@ -3730,7 +3757,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); Loading @@ -3739,6 +3766,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); Loading