diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 113d92ca65..8bd3450f0a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -71,6 +71,8 @@ import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; +import android.net.InetAddresses; +import android.net.IpPrefix; import android.net.LinkProperties; import android.net.LinkProperties.CompareResult; import android.net.MatchAllNetworkSpecifier; @@ -1740,6 +1742,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } } } + + @Override + public void onNat64PrefixEvent(int netId, boolean added, + String prefixString, int prefixLength) { + mHandler.post(() -> handleNat64PrefixEvent(netId, added, prefixString, prefixLength)); + } }; @VisibleForTesting @@ -2762,6 +2770,29 @@ public class ConnectivityService extends IConnectivityManager.Stub handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties)); } + private void handleNat64PrefixEvent(int netId, boolean added, String prefixString, + int prefixLength) { + NetworkAgentInfo nai = mNetworkForNetId.get(netId); + if (nai == null) return; + + log(String.format("NAT64 prefix %s on netId %d: %s/%d", + (added ? "added" : "removed"), netId, prefixString, prefixLength)); + + IpPrefix prefix = null; + if (added) { + try { + prefix = new IpPrefix(InetAddresses.parseNumericAddress(prefixString), + prefixLength); + } catch (IllegalArgumentException e) { + loge("Invalid NAT64 prefix " + prefixString + "/" + prefixLength); + return; + } + } + + nai.clatd.setNat64Prefix(prefix); + 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. diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index c834eaa951..7b7fc0d672 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -18,7 +18,9 @@ package com.android.server.connectivity; import android.net.ConnectivityManager; import android.net.INetd; +import android.net.InetAddresses; import android.net.InterfaceConfiguration; +import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkInfo; @@ -31,6 +33,7 @@ import com.android.internal.util.ArrayUtils; import com.android.server.net.BaseNetworkObserver; import java.net.Inet4Address; +import java.net.Inet6Address; import java.util.Objects; /** @@ -72,8 +75,10 @@ public class Nat464Xlat extends BaseNetworkObserver { RUNNING, // start() called, and the stacked iface is known to be up. } + private IpPrefix mNat64Prefix; private String mBaseIface; private String mIface; + private Inet6Address mIPv6Address; private State mState = State.IDLE; public Nat464Xlat(NetworkAgentInfo nai, INetd netd, INetworkManagementService nmService) { @@ -91,12 +96,17 @@ public class Nat464Xlat extends BaseNetworkObserver { // TODO: migrate to NetworkCapabilities.TRANSPORT_*. final boolean supported = ArrayUtils.contains(NETWORK_TYPES, nai.networkInfo.getType()); final boolean connected = ArrayUtils.contains(NETWORK_STATES, nai.networkInfo.getState()); - // We only run clat on networks that don't have a native IPv4 address. - final boolean hasIPv4Address = - (nai.linkProperties != null) && nai.linkProperties.hasIPv4Address(); - final boolean skip464xlat = - (nai.netMisc() != null) && nai.netMisc().skip464xlat; - return supported && connected && !hasIPv4Address && !skip464xlat; + + // We only run clat on networks that have a global IPv6 address and a NAT64 prefix and don't + // have a native IPv4 address. + LinkProperties lp = nai.linkProperties; + final boolean isNat64Network = (lp != null) && lp.hasGlobalIPv6Address() + && lp.getNat64Prefix() != null && !lp.hasIPv4Address(); + + // If the network tells us it doesn't use clat, respect that. + final boolean skip464xlat = (nai.netMisc() != null) && nai.netMisc().skip464xlat; + + return supported && connected && isNat64Network && !skip464xlat; } /** @@ -132,14 +142,21 @@ public class Nat464Xlat extends BaseNetworkObserver { Slog.e(TAG, "Can't register interface observer for clat on " + mNetwork.name()); return; } + + String addrStr = null; try { - mNetd.clatdStart(baseIface); + addrStr = mNetd.clatdStart(baseIface, mNat64Prefix.toString()); } catch (RemoteException | IllegalStateException e) { Slog.e(TAG, "Error starting clatd on " + baseIface, e); } mIface = CLAT_PREFIX + baseIface; mBaseIface = baseIface; mState = State.STARTING; + try { + mIPv6Address = (Inet6Address) InetAddresses.parseNumericAddress(addrStr); + } catch (ClassCastException | IllegalArgumentException | NullPointerException e) { + Slog.e(TAG, "Invalid IPv6 address " + addrStr); + } } /** @@ -220,12 +237,18 @@ public class Nat464Xlat extends BaseNetworkObserver { } } + public void setNat64Prefix(IpPrefix nat64Prefix) { + mNat64Prefix = nat64Prefix; + } + /** * Copies the stacked clat link in oldLp, if any, to the passed LinkProperties. * This is necessary because the LinkProperties in mNetwork come from the transport layer, which * has no idea that 464xlat is running on top of it. */ public void fixupLinkProperties(LinkProperties oldLp, LinkProperties lp) { + lp.setNat64Prefix(mNat64Prefix); + if (!isRunning()) { return; } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 8ec8e78874..18008b6beb 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -1798,6 +1798,12 @@ public class ConnectivityServiceTest { fn.test((NetworkCapabilities) cbi.arg)); } + void expectLinkPropertiesLike(Predicate fn, MockNetworkAgent agent) { + CallbackInfo cbi = expectCallback(CallbackState.LINK_PROPERTIES, agent); + assertTrue("Received LinkProperties don't match expectations : " + cbi.arg, + fn.test((LinkProperties) cbi.arg)); + } + void expectBlockedStatusCallback(boolean expectBlocked, MockNetworkAgent agent) { CallbackInfo cbi = expectCallback(CallbackState.BLOCKED_STATUS, agent); boolean actualBlocked = (boolean) cbi.arg; @@ -5087,6 +5093,9 @@ public class ConnectivityServiceTest { public void testStackedLinkProperties() throws UnknownHostException, RemoteException { final LinkAddress myIpv4 = new LinkAddress("1.2.3.4/24"); final LinkAddress myIpv6 = new LinkAddress("2001:db8:1::1/64"); + final String kNat64PrefixString = "2001:db8:64:64:64:64::"; + final IpPrefix kNat64Prefix = new IpPrefix(InetAddress.getByName(kNat64PrefixString), 96); + final NetworkRequest networkRequest = new NetworkRequest.Builder() .addTransportType(TRANSPORT_CELLULAR) .addCapability(NET_CAPABILITY_INTERNET) @@ -5110,10 +5119,19 @@ public class ConnectivityServiceTest { mCellNetworkAgent.sendLinkProperties(cellLp); mCellNetworkAgent.connect(true); networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); - verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME); - Nat464Xlat clat = mService.getNat464Xlat(mCellNetworkAgent); - // Clat iface up, expect stack link updated. + // When NAT64 prefix detection succeeds, LinkProperties are updated and clatd is started. + Nat464Xlat clat = mService.getNat464Xlat(mCellNetworkAgent); + assertNull(mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getNat64Prefix()); + mService.mNetdEventCallback.onNat64PrefixEvent(mCellNetworkAgent.getNetwork().netId, + true /* added */, kNat64PrefixString, 96); + LinkProperties lpBeforeClat = (LinkProperties) networkCallback.expectCallback( + CallbackState.LINK_PROPERTIES, mCellNetworkAgent).arg; + assertEquals(0, lpBeforeClat.getStackedLinks().size()); + assertEquals(kNat64Prefix, lpBeforeClat.getNat64Prefix()); + verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString()); + + // Clat iface comes up. Expect stacked link to be added. clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true); networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); List stackedLps = mCm.getLinkProperties(mCellNetworkAgent.getNetwork()) @@ -5130,20 +5148,50 @@ public class ConnectivityServiceTest { assertNotEquals(stackedLpsAfterChange, Collections.EMPTY_LIST); assertEquals(makeClatLinkProperties(myIpv4), stackedLpsAfterChange.get(0)); - // Add ipv4 address, expect stacked linkproperties be cleaned up + // Add ipv4 address, expect that clatd is stopped and stacked linkproperties are cleaned up. cellLp.addLinkAddress(myIpv4); cellLp.addRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME)); mCellNetworkAgent.sendLinkProperties(cellLp); networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); - - // Clat iface removed, expect linkproperties revert to original one - clat.interfaceRemoved(CLAT_PREFIX + MOBILE_IFNAME); networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); - LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellNetworkAgent.getNetwork()); - assertEquals(cellLp, actualLpAfterIpv4); - // Clean up + // As soon as stop is called, the linkproperties lose the stacked interface. + LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellNetworkAgent.getNetwork()); + LinkProperties expected = new LinkProperties(cellLp); + expected.setNat64Prefix(kNat64Prefix); + assertEquals(expected, actualLpAfterIpv4); + assertEquals(0, actualLpAfterIpv4.getStackedLinks().size()); + + // The interface removed callback happens but has no effect after stop is called. + clat.interfaceRemoved(CLAT_PREFIX + MOBILE_IFNAME); + networkCallback.assertNoCallback(); + + reset(mMockNetd); + + // Remove IPv4 address and expect clatd to be started again. + cellLp.removeLinkAddress(myIpv4); + cellLp.removeRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME)); + cellLp.removeDnsServer(InetAddress.getByName("8.8.8.8")); + mCellNetworkAgent.sendLinkProperties(cellLp); + networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString()); + + // Clat iface comes up. Expect stacked link to be added. + clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true); + networkCallback.expectLinkPropertiesLike((lp) -> lp.getStackedLinks().size() == 1, + mCellNetworkAgent); + + // NAT64 prefix is removed. Expect that clat is stopped. + mService.mNetdEventCallback.onNat64PrefixEvent(mCellNetworkAgent.getNetwork().netId, + false /* added */, kNat64PrefixString, 96); + networkCallback.expectLinkPropertiesLike((lp) -> lp.getNat64Prefix() == null, + mCellNetworkAgent); + verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); + networkCallback.expectLinkPropertiesLike((lp) -> lp.getStackedLinks().size() == 0, + mCellNetworkAgent); + + // Clean up. mCellNetworkAgent.disconnect(); networkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); networkCallback.assertNoCallback(); diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java index b9450b31f1..9b7ff31e63 100644 --- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java +++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java @@ -16,6 +16,7 @@ package com.android.server.connectivity; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.eq; @@ -28,6 +29,7 @@ import static org.mockito.Mockito.when; import android.net.ConnectivityManager; import android.net.INetd; import android.net.InterfaceConfiguration; +import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkInfo; @@ -55,6 +57,7 @@ public class Nat464XlatTest { static final String BASE_IFACE = "test0"; static final String STACKED_IFACE = "v4-test0"; static final LinkAddress ADDR = new LinkAddress("192.0.2.5/29"); + static final String NAT64_PREFIX = "64:ff9b::/96"; @Mock ConnectivityService mConnectivity; @Mock NetworkMisc mMisc; @@ -89,6 +92,15 @@ public class Nat464XlatTest { when(mConfig.getLinkAddress()).thenReturn(ADDR); } + private void assertRequiresClat(boolean expected, NetworkAgentInfo nai) { + String msg = String.format("requiresClat expected %b for type=%d state=%s skip464xlat=%b " + + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(), + nai.networkInfo.getDetailedState(), + mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(), + nai.linkProperties.getLinkAddresses()); + assertEquals(msg, expected, Nat464Xlat.requiresClat(nai)); + } + @Test public void testRequiresClat() throws Exception { final int[] supportedTypes = { @@ -104,20 +116,31 @@ public class Nat464XlatTest { NetworkInfo.DetailedState.SUSPENDED, }; + LinkProperties oldLp = new LinkProperties(mNai.linkProperties); for (int type : supportedTypes) { mNai.networkInfo.setType(type); for (NetworkInfo.DetailedState state : supportedDetailedStates) { mNai.networkInfo.setDetailedState(state, "reason", "extraInfo"); - String msg = String.format("requiresClat expected for type=%d state=%s", - type, state); + + mNai.linkProperties.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96")); + assertRequiresClat(false, mNai); + + mNai.linkProperties.addLinkAddress(new LinkAddress("fc00::1/64")); + assertRequiresClat(false, mNai); + + mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64")); + assertRequiresClat(true, mNai); mMisc.skip464xlat = true; - String errorMsg = msg + String.format(" skip464xlat=%b", mMisc.skip464xlat); - assertFalse(errorMsg, Nat464Xlat.requiresClat(mNai)); + assertRequiresClat(false, mNai); mMisc.skip464xlat = false; - errorMsg = msg + String.format(" skip464xlat=%b", mMisc.skip464xlat); - assertTrue(errorMsg, Nat464Xlat.requiresClat(mNai)); + assertRequiresClat(true, mNai); + + mNai.linkProperties.addLinkAddress(new LinkAddress("192.0.2.2/24")); + assertRequiresClat(false, mNai); + + mNai.linkProperties = new LinkProperties(oldLp); } } } @@ -127,11 +150,13 @@ public class Nat464XlatTest { Nat464Xlat nat = makeNat464Xlat(); ArgumentCaptor c = ArgumentCaptor.forClass(LinkProperties.class); + nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); + // ConnectivityService starts clat. nat.start(); verify(mNms).registerObserver(eq(nat)); - verify(mNetd).clatdStart(eq(BASE_IFACE)); + verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); // Stacked interface up notification arrives. nat.interfaceLinkStateChanged(STACKED_IFACE, true); @@ -165,10 +190,12 @@ public class Nat464XlatTest { ArgumentCaptor c = ArgumentCaptor.forClass(LinkProperties.class); InOrder inOrder = inOrder(mNetd, mConnectivity); + nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); + // ConnectivityService starts clat. nat.start(); - inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE)); + inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); // Stacked interface up notification arrives. nat.interfaceLinkStateChanged(STACKED_IFACE, true); @@ -205,7 +232,7 @@ public class Nat464XlatTest { // ConnectivityService starts clatd again. nat.start(); - inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE)); + inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); if (!interfaceRemovedFirst) { // Stacked interface removed notification arrives and is ignored. @@ -252,11 +279,13 @@ public class Nat464XlatTest { Nat464Xlat nat = makeNat464Xlat(); ArgumentCaptor c = ArgumentCaptor.forClass(LinkProperties.class); + nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); + // ConnectivityService starts clat. nat.start(); verify(mNms).registerObserver(eq(nat)); - verify(mNetd).clatdStart(eq(BASE_IFACE)); + verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); // Stacked interface up notification arrives. nat.interfaceLinkStateChanged(STACKED_IFACE, true); @@ -289,11 +318,13 @@ public class Nat464XlatTest { public void testStopBeforeClatdStarts() throws Exception { Nat464Xlat nat = makeNat464Xlat(); + nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); + // ConnectivityService starts clat. nat.start(); verify(mNms).registerObserver(eq(nat)); - verify(mNetd).clatdStart(eq(BASE_IFACE)); + verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...) nat.stop(); @@ -320,11 +351,13 @@ public class Nat464XlatTest { public void testStopAndClatdNeverStarts() throws Exception { Nat464Xlat nat = makeNat464Xlat(); + nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); + // ConnectivityService starts clat. nat.start(); verify(mNms).registerObserver(eq(nat)); - verify(mNetd).clatdStart(eq(BASE_IFACE)); + verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...) nat.stop();