From 7fb7c3e0bf81bc76af84f299973c5a8d6e403228 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Thu, 2 Apr 2020 04:50:41 +0000 Subject: [PATCH] Only apply VPN isolation if it's fully routed VPN is considered fully routed if both IPv4 and IPv6 have either a default route or a prohibit route. Bug: 145332510 Test: atest FrameworksNetTests Merged-In: I59cf48552bca98092d1212e3d718fd420add5458 Change-Id: I59cf48552bca98092d1212e3d718fd420add5458 --- core/java/android/net/LinkProperties.java | 30 ++++++++++ core/java/android/net/RouteInfo.java | 26 +++++++++ .../android/server/ConnectivityService.java | 3 +- .../java/android/net/LinkPropertiesTest.java | 25 ++++++++ .../java/android/net/RouteInfoTest.java | 58 +++++++++++++++++++ .../server/ConnectivityServiceTest.java | 5 ++ 6 files changed, 146 insertions(+), 1 deletion(-) diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 7ff954bdc1..651494d1c9 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -1073,6 +1073,21 @@ public final class LinkProperties implements Parcelable { return false; } + /** + * Returns true if this link has an IPv4 unreachable default route. + * + * @return {@code true} if there is an IPv4 unreachable default route, {@code false} otherwise. + * @hide + */ + public boolean hasIpv4UnreachableDefaultRoute() { + for (RouteInfo r : mRoutes) { + if (r.isIPv4UnreachableDefault()) { + return true; + } + } + return false; + } + /** * For backward compatibility. * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely @@ -1101,6 +1116,21 @@ public final class LinkProperties implements Parcelable { return false; } + /** + * Returns true if this link has an IPv6 unreachable default route. + * + * @return {@code true} if there is an IPv6 unreachable default route, {@code false} otherwise. + * @hide + */ + public boolean hasIpv6UnreachableDefaultRoute() { + for (RouteInfo r : mRoutes) { + if (r.isIPv6UnreachableDefault()) { + return true; + } + } + return false; + } + /** * For backward compatibility. * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java index dbdaa4c2da..e550f85e6b 100644 --- a/core/java/android/net/RouteInfo.java +++ b/core/java/android/net/RouteInfo.java @@ -425,6 +425,16 @@ public final class RouteInfo implements Parcelable { return mType == RTN_UNICAST && mDestination.getPrefixLength() == 0; } + /** + * Indicates if this route is an unreachable default route. + * + * @return {@code true} if it's an unreachable route with prefix length of 0. + * @hide + */ + private boolean isUnreachableDefaultRoute() { + return mType == RTN_UNREACHABLE && mDestination.getPrefixLength() == 0; + } + /** * Indicates if this route is an IPv4 default route. * @hide @@ -433,6 +443,14 @@ public final class RouteInfo implements Parcelable { return isDefaultRoute() && mDestination.getAddress() instanceof Inet4Address; } + /** + * Indicates if this route is an IPv4 unreachable default route. + * @hide + */ + public boolean isIPv4UnreachableDefault() { + return isUnreachableDefaultRoute() && mDestination.getAddress() instanceof Inet4Address; + } + /** * Indicates if this route is an IPv6 default route. * @hide @@ -441,6 +459,14 @@ public final class RouteInfo implements Parcelable { return isDefaultRoute() && mDestination.getAddress() instanceof Inet6Address; } + /** + * Indicates if this route is an IPv6 unreachable default route. + * @hide + */ + public boolean isIPv6UnreachableDefault() { + return isUnreachableDefaultRoute() && mDestination.getAddress() instanceof Inet6Address; + } + /** * Indicates if this route is a host route (ie, matches only a single host address). * diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 4ab035e7f4..2eaa766ad3 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -6313,7 +6313,8 @@ public class ConnectivityService extends IConnectivityManager.Stub && !nai.networkAgentConfig.allowBypass && nc.getOwnerUid() != Process.SYSTEM_UID && lp.getInterfaceName() != null - && (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute()); + && (lp.hasIPv4DefaultRoute() || lp.hasIpv4UnreachableDefaultRoute()) + && (lp.hasIPv6DefaultRoute() || lp.hasIpv6UnreachableDefaultRoute()); } private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc, diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java index b4f0daa025..939f8eb8c1 100644 --- a/tests/net/common/java/android/net/LinkPropertiesTest.java +++ b/tests/net/common/java/android/net/LinkPropertiesTest.java @@ -16,6 +16,8 @@ package android.net; +import static android.net.RouteInfo.RTN_UNREACHABLE; + import static com.android.testutils.ParcelUtilsKt.assertParcelSane; import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless; import static com.android.testutils.ParcelUtilsKt.parcelingRoundTrip; @@ -46,6 +48,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; @@ -1251,4 +1254,26 @@ public class LinkPropertiesTest { final LinkProperties Ipv6 = makeIpv6LinkProperties(); assertTrue(Ipv6.hasIpv6DnsServer()); } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testHasIpv4UnreachableDefaultRoute() { + final LinkProperties lp = makeTestObject(); + assertFalse(lp.hasIpv4UnreachableDefaultRoute()); + assertFalse(lp.hasIpv6UnreachableDefaultRoute()); + + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); + assertTrue(lp.hasIpv4UnreachableDefaultRoute()); + assertFalse(lp.hasIpv6UnreachableDefaultRoute()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testHasIpv6UnreachableDefaultRoute() { + final LinkProperties lp = makeTestObject(); + assertFalse(lp.hasIpv6UnreachableDefaultRoute()); + assertFalse(lp.hasIpv4UnreachableDefaultRoute()); + + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); + assertTrue(lp.hasIpv6UnreachableDefaultRoute()); + assertFalse(lp.hasIpv4UnreachableDefaultRoute()); + } } diff --git a/tests/net/common/java/android/net/RouteInfoTest.java b/tests/net/common/java/android/net/RouteInfoTest.java index 1658262c17..8204b494bb 100644 --- a/tests/net/common/java/android/net/RouteInfoTest.java +++ b/tests/net/common/java/android/net/RouteInfoTest.java @@ -31,6 +31,7 @@ import static org.junit.Assert.fail; import android.os.Build; +import androidx.core.os.BuildCompat; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -62,6 +63,11 @@ public class RouteInfoTest { return new IpPrefix(prefix); } + private static boolean isAtLeastR() { + // BuildCompat.isAtLeastR is documented to return false on release SDKs (including R) + return Build.VERSION.SDK_INT > Build.VERSION_CODES.Q || BuildCompat.isAtLeastR(); + } + @Test public void testConstructor() { RouteInfo r; @@ -195,78 +201,130 @@ public class RouteInfoTest { assertTrue(r.isDefaultRoute()); assertTrue(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0"); assertFalse(r.isHostRoute()); assertTrue(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertTrue(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0"); assertFalse(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0"); assertFalse(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE); assertFalse(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertTrue(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE); assertFalse(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertTrue(r.isIPv6UnreachableDefault()); + } } @Test diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index b02398d1f1..912a27f08f 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -6336,6 +6336,7 @@ public class ConnectivityServiceTest { LinkProperties lp = new LinkProperties(); lp.setInterfaceName("tun0"); lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); // The uid range needs to cover the test app so the network is visible to it. final Set vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange); @@ -6361,6 +6362,7 @@ public class ConnectivityServiceTest { public void testLegacyVpnDoesNotResultInInterfaceFilteringRule() throws Exception { LinkProperties lp = new LinkProperties(); lp.setInterfaceName("tun0"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. final Set vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); @@ -6392,6 +6394,7 @@ public class ConnectivityServiceTest { LinkProperties lp = new LinkProperties(); lp.setInterfaceName("tun0"); lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. final Set vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange); @@ -6428,6 +6431,7 @@ public class ConnectivityServiceTest { reset(mMockNetd); lp = new LinkProperties(); lp.setInterfaceName("tun1"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); vpnNetworkAgent.sendLinkProperties(lp); waitForIdle(); @@ -6440,6 +6444,7 @@ public class ConnectivityServiceTest { public void testUidUpdateChangesInterfaceFilteringRule() throws Exception { LinkProperties lp = new LinkProperties(); lp.setInterfaceName("tun0"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. final UidRange vpnRange = UidRange.createForUser(VPN_USER);