e.rethrowFromSystemServer();
}
mNetworkAgentInfos.remove(nai.messenger);
- nai.updateClat();
+ nai.clatd.update();
synchronized (mNetworkForNetId) {
// Remove the NetworkAgent, but don't mark the netId as
// available until we've told netd to delete it below.
synchronized (networkAgent) {
networkAgent.linkProperties = newLp;
}
- // Start or stop clat accordingly to network state.
- networkAgent.updateClat();
+ // Start or stop DNS64 detection and 464xlat according to network state.
+ networkAgent.clatd.update();
notifyIfacesChangedForNetworkStats();
if (networkAgent.everConnected) {
try {
import android.net.RouteInfo;
import android.os.INetworkManagementService;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.server.net.BaseNetworkObserver;
private final NetworkAgentInfo mNetwork;
private enum State {
- IDLE, // start() not called. Base iface and stacked iface names are null.
- STARTING, // start() called. Base iface and stacked iface names are known.
- RUNNING, // start() called, and the stacked iface is known to be up.
+ IDLE, // start() not called. Base iface and stacked iface names are null.
+ DISCOVERING, // same as IDLE, except prefix discovery in progress.
+ STARTING, // start() called. Base iface and stacked iface names are known.
+ RUNNING, // start() called, and the stacked iface is known to be up.
}
private IpPrefix mNat64Prefix;
}
/**
- * Determines whether a network requires clat.
+ * Whether to attempt 464xlat on this network. This is true for an IPv6-only network that is
+ * currently connected and where the NetworkAgent has not disabled 464xlat. It is the signal to
+ * enable NAT64 prefix discovery.
+ *
* @param network the NetworkAgentInfo corresponding to the network.
* @return true if the network requires clat, false otherwise.
*/
- public static boolean requiresClat(NetworkAgentInfo nai) {
+ @VisibleForTesting
+ protected static boolean requiresClat(NetworkAgentInfo nai) {
// 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 have a global IPv6 address and a NAT64 prefix and don't
- // have a native IPv4 address.
+ // Only run clat on networks that have a global IPv6 address and don't have a native IPv4
+ // address.
LinkProperties lp = nai.linkProperties;
- final boolean isNat64Network = (lp != null) && lp.hasGlobalIPv6Address()
- && lp.getNat64Prefix() != null && !lp.hasIPv4Address();
+ final boolean isIpv6OnlyNetwork = (lp != null) && lp.hasGlobalIPv6Address()
+ && !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;
+ return supported && connected && isIpv6OnlyNetwork && !skip464xlat;
+ }
+
+ /**
+ * Whether the clat demon should be started on this network now. This is true if requiresClat is
+ * true and a NAT64 prefix has been discovered.
+ *
+ * @param nai the NetworkAgentInfo corresponding to the network.
+ * @return true if the network should start clat, false otherwise.
+ */
+ @VisibleForTesting
+ protected static boolean shouldStartClat(NetworkAgentInfo nai) {
+ LinkProperties lp = nai.linkProperties;
+ return requiresClat(nai) && lp != null && lp.getNat64Prefix() != null;
+ }
+
+ /**
+ * @return true if we have started prefix discovery and not yet stopped it (regardless of
+ * whether it is still running or has succeeded).
+ * A true result corresponds to internal states DISCOVERING, STARTING and RUNNING.
+ */
+ public boolean isPrefixDiscoveryStarted() {
+ return mState == State.DISCOVERING || isStarted();
}
/**
* A true result corresponds to internal states STARTING and RUNNING.
*/
public boolean isStarted() {
- return mState != State.IDLE;
+ return (mState == State.STARTING || mState == State.RUNNING);
}
/**
/**
* Unregister as a base observer for the stacked interface, and clear internal state.
*/
- private void enterIdleState() {
+ private void leaveStartedState() {
try {
mNMService.unregisterObserver(this);
} catch (RemoteException | IllegalStateException e) {
mIface = null;
mBaseIface = null;
mState = State.IDLE;
+ if (requiresClat(mNetwork)) {
+ mState = State.DISCOVERING;
+ } else {
+ stopPrefixDiscovery();
+ mState = State.IDLE;
+ }
}
- /**
- * Starts the clat daemon.
- */
- public void start() {
+ @VisibleForTesting
+ protected void start() {
if (isStarted()) {
Slog.e(TAG, "startClat: already started");
return;
enterStartingState(baseIface);
}
- /**
- * Stops the clat daemon.
- */
- public void stop() {
+ @VisibleForTesting
+ protected void stop() {
if (!isStarted()) {
Slog.e(TAG, "stopClat: already stopped");
return;
String iface = mIface;
boolean wasRunning = isRunning();
- // Enter IDLE state before updating LinkProperties. handleUpdateLinkProperties ends up
- // calling fixupLinkProperties, and if at that time the state is still RUNNING,
- // fixupLinkProperties would wrongly inform ConnectivityService that there is still a
- // stacked interface.
- enterIdleState();
+ // Change state before updating LinkProperties. handleUpdateLinkProperties ends up calling
+ // fixupLinkProperties, and if at that time the state is still RUNNING, fixupLinkProperties
+ // would wrongly inform ConnectivityService that there is still a stacked interface.
+ leaveStartedState();
if (wasRunning) {
LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
}
}
+ private void startPrefixDiscovery() {
+ try {
+ mNetd.resolverStartPrefix64Discovery(getNetId());
+ mState = State.DISCOVERING;
+ } catch (RemoteException | ServiceSpecificException e) {
+ Slog.e(TAG, "Error starting prefix discovery on netId " + getNetId(), e);
+ }
+ }
+
+ private void stopPrefixDiscovery() {
+ try {
+ mNetd.resolverStopPrefix64Discovery(getNetId());
+ } catch (RemoteException | ServiceSpecificException e) {
+ Slog.e(TAG, "Error stopping prefix discovery on netId " + getNetId(), e);
+ }
+ }
+
+ /**
+ * Starts/stops NAT64 prefix discovery and clatd as necessary.
+ */
+ public void update() {
+ // TODO: turn this class into a proper StateMachine. // http://b/126113090
+ if (requiresClat(mNetwork)) {
+ if (!isPrefixDiscoveryStarted()) {
+ startPrefixDiscovery();
+ } else if (shouldStartClat(mNetwork)) {
+ // NAT64 prefix detected. Start clatd.
+ // TODO: support the NAT64 prefix changing after it's been discovered. There is no
+ // need to support this at the moment because it cannot happen without changes to
+ // the Dns64Configuration code in netd.
+ start();
+ } else {
+ // NAT64 prefix removed. Stop clatd and go back into DISCOVERING state.
+ stop();
+ }
+ } else {
+ // Network no longer requires clat. Stop clat and prefix discovery.
+ if (isStarted()) {
+ stop();
+ } else if (isPrefixDiscoveryStarted()) {
+ leaveStartedState();
+ }
+ }
+ }
+
public void setNat64Prefix(IpPrefix nat64Prefix) {
mNat64Prefix = nat64Prefix;
}
* Adds stacked link on base link and transitions to RUNNING state.
*/
private void handleInterfaceLinkStateChanged(String iface, boolean up) {
+ // TODO: if we call start(), then stop(), then start() again, and the
+ // interfaceLinkStateChanged notification for the first start is delayed past the first
+ // stop, then the code becomes out of sync with system state and will behave incorrectly.
+ //
+ // This is not trivial to fix because:
+ // 1. It is not guaranteed that start() will eventually result in the interface coming up,
+ // because there could be an error starting clat (e.g., if the interface goes down before
+ // the packet socket can be bound).
+ // 2. If start is called multiple times, there is nothing in the interfaceLinkStateChanged
+ // notification that says which start() call the interface was created by.
+ //
+ // Once this code is converted to StateMachine, it will be possible to use deferMessage to
+ // ensure it stays in STARTING state until the interfaceLinkStateChanged notification fires,
+ // and possibly use a timeout (or provide some guarantees at the lower layer) to address #1.
if (!isStarting() || !up || !Objects.equals(mIface, iface)) {
return;
}
public String toString() {
return "mBaseIface: " + mBaseIface + ", mIface: " + mIface + ", mState: " + mState;
}
+
+ @VisibleForTesting
+ protected int getNetId() {
+ return mNetwork.network.netId;
+ }
}
for (LingerTimer timer : mLingerTimers) { pw.println(timer); }
}
- /** Starts or stops clatd as required. */
- public void updateClat() {
- if (!clatd.isStarted() && Nat464Xlat.requiresClat(this)) {
- clatd.start();
- } else if (clatd.isStarted() && !Nat464Xlat.requiresClat(this)) {
- clatd.stop();
- }
- }
-
public String toString() {
return "NetworkAgentInfo{ ni{" + networkInfo + "} "
+ "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} "
final TestNetworkCallback networkCallback = new TestNetworkCallback();
mCm.registerNetworkCallback(networkRequest, networkCallback);
- // Prepare ipv6 only link properties and connect.
+ // Prepare ipv6 only link properties.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ final int cellNetId = mCellNetworkAgent.getNetwork().netId;
final LinkProperties cellLp = new LinkProperties();
cellLp.setInterfaceName(MOBILE_IFNAME);
cellLp.addLinkAddress(myIpv6);
when(mNetworkManagementService.getInterfaceConfig(CLAT_PREFIX + MOBILE_IFNAME))
.thenReturn(getClatInterfaceConfig(myIpv4));
- // Connect with ipv6 link properties, then expect clat setup ipv4 and update link
- // properties properly.
+ // Connect with ipv6 link properties. Expect prefix discovery to be started.
mCellNetworkAgent.sendLinkProperties(cellLp);
mCellNetworkAgent.connect(true);
networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ verify(mMockNetd, times(1)).resolverStartPrefix64Discovery(cellNetId);
- // When NAT64 prefix detection succeeds, LinkProperties are updated and clatd is started.
+ // Switching default network updates TCP buffer sizes.
+ verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES);
+
+ // Add an IPv4 address. Expect prefix discovery to be stopped. Netd doesn't tell us that
+ // the NAT64 prefix was removed because one was never discovered.
+ cellLp.addLinkAddress(myIpv4);
+ mCellNetworkAgent.sendLinkProperties(cellLp);
+ networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+ verify(mMockNetd, times(1)).resolverStopPrefix64Discovery(cellNetId);
+
+ verifyNoMoreInteractions(mMockNetd);
+ reset(mMockNetd);
+
+ // Remove IPv4 address. Expect prefix discovery to be started again.
+ cellLp.removeLinkAddress(myIpv4);
+ cellLp.removeRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME));
+ mCellNetworkAgent.sendLinkProperties(cellLp);
+ networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+ verify(mMockNetd, times(1)).resolverStartPrefix64Discovery(cellNetId);
+
+ // When NAT64 prefix discovery 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);
+ mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */,
+ kNat64PrefixString, 96);
LinkProperties lpBeforeClat = (LinkProperties) networkCallback.expectCallback(
CallbackState.LINK_PROPERTIES, mCellNetworkAgent).arg;
assertEquals(0, lpBeforeClat.getStackedLinks().size());
assertNotEquals(stackedLpsAfterChange, Collections.EMPTY_LIST);
assertEquals(makeClatLinkProperties(myIpv4), stackedLpsAfterChange.get(0));
- // Add ipv4 address, expect that clatd is stopped and stacked linkproperties are cleaned up.
+ // Add ipv4 address, expect that clatd and prefix discovery are 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);
- networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+ verify(mMockNetd, times(1)).resolverStopPrefix64Discovery(cellNetId);
// As soon as stop is called, the linkproperties lose the stacked interface.
+ networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellNetworkAgent.getNetwork());
LinkProperties expected = new LinkProperties(cellLp);
expected.setNat64Prefix(kNat64Prefix);
clat.interfaceRemoved(CLAT_PREFIX + MOBILE_IFNAME);
networkCallback.assertNoCallback();
+ verifyNoMoreInteractions(mMockNetd);
reset(mMockNetd);
- // Remove IPv4 address and expect clatd to be started again.
+ // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone.
+ mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */,
+ kNat64PrefixString, 96);
+ networkCallback.expectLinkPropertiesLike((lp) -> lp.getNat64Prefix() == null,
+ mCellNetworkAgent);
+
+ // Remove IPv4 address and expect prefix discovery and 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)).resolverStartPrefix64Discovery(cellNetId);
+ mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */,
+ kNat64PrefixString, 96);
+ 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,
+ networkCallback.expectLinkPropertiesLike(
+ (lp) -> lp.getStackedLinks().size() == 1 && lp.getNat64Prefix() != null,
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,
+ mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */,
+ kNat64PrefixString, 96);
+ networkCallback.expectLinkPropertiesLike(
+ (lp) -> lp.getStackedLinks().size() == 0 && lp.getNat64Prefix() == null,
mCellNetworkAgent);
verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
networkCallback.expectLinkPropertiesLike((lp) -> lp.getStackedLinks().size() == 0,
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";
+ static final int NETID = 42;
@Mock ConnectivityService mConnectivity;
@Mock NetworkMisc mMisc;
Handler mHandler;
Nat464Xlat makeNat464Xlat() {
- return new Nat464Xlat(mNai, mNetd, mNms);
+ return new Nat464Xlat(mNai, mNetd, mNms) {
+ @Override protected int getNetId() {
+ return NETID;
+ }
+ };
}
@Before
}
private void assertRequiresClat(boolean expected, NetworkAgentInfo nai) {
- String msg = String.format("requiresClat expected %b for type=%d state=%s skip464xlat=%b "
+ String msg = String.format("requiresClat expected %b for type=%d state=%s skip=%b "
+ "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
nai.networkInfo.getDetailedState(),
mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(),
assertEquals(msg, expected, Nat464Xlat.requiresClat(nai));
}
+ private void assertShouldStartClat(boolean expected, NetworkAgentInfo nai) {
+ String msg = String.format("shouldStartClat expected %b for type=%d state=%s skip=%b "
+ + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
+ nai.networkInfo.getDetailedState(),
+ mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(),
+ nai.linkProperties.getLinkAddresses());
+ assertEquals(msg, expected, Nat464Xlat.shouldStartClat(nai));
+ }
+
@Test
public void testRequiresClat() throws Exception {
final int[] supportedTypes = {
mNai.linkProperties.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96"));
assertRequiresClat(false, mNai);
+ assertShouldStartClat(false, mNai);
mNai.linkProperties.addLinkAddress(new LinkAddress("fc00::1/64"));
assertRequiresClat(false, mNai);
+ assertShouldStartClat(false, mNai);
mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
assertRequiresClat(true, mNai);
+ assertShouldStartClat(true, mNai);
mMisc.skip464xlat = true;
assertRequiresClat(false, mNai);
+ assertShouldStartClat(false, mNai);
mMisc.skip464xlat = false;
assertRequiresClat(true, mNai);
+ assertShouldStartClat(true, mNai);
mNai.linkProperties.addLinkAddress(new LinkAddress("192.0.2.2/24"));
assertRequiresClat(false, mNai);
+ assertShouldStartClat(false, mNai);
+
+ mNai.linkProperties.removeLinkAddress(new LinkAddress("192.0.2.2/24"));
+ assertRequiresClat(true, mNai);
+ assertShouldStartClat(true, mNai);
+
+ mNai.linkProperties.setNat64Prefix(null);
+ assertRequiresClat(true, mNai);
+ assertShouldStartClat(false, mNai);
mNai.linkProperties = new LinkProperties(oldLp);
}
nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX));
- // ConnectivityService starts clat.
+ // Start clat.
nat.start();
verify(mNms).registerObserver(eq(nat));
assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
assertRunning(nat);
- // ConnectivityService stops clat (Network disconnects, IPv4 addr appears, ...).
+ // Stop clat (Network disconnects, IPv4 addr appears, ...).
nat.stop();
verify(mNetd).clatdStop(eq(BASE_IFACE));
verify(mNms).unregisterObserver(eq(nat));
assertTrue(c.getValue().getStackedLinks().isEmpty());
assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
+ verify(mNetd).resolverStopPrefix64Discovery(eq(NETID));
assertIdle(nat);
// Stacked interface removed notification arrives and is ignored.
nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX));
- // ConnectivityService starts clat.
nat.start();
inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
assertIdle(nat);
inOrder.verifyNoMoreInteractions();
- // ConnectivityService starts clatd again.
nat.start();
inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX));
- // ConnectivityService starts clat.
nat.start();
verify(mNms).registerObserver(eq(nat));
verify(mNetd).clatdStop(eq(BASE_IFACE));
verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
verify(mNms).unregisterObserver(eq(nat));
+ verify(mNetd).resolverStopPrefix64Discovery(eq(NETID));
assertTrue(c.getValue().getStackedLinks().isEmpty());
assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
assertIdle(nat);
nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX));
- // ConnectivityService starts clat.
nat.start();
verify(mNms).registerObserver(eq(nat));
verify(mNetd).clatdStop(eq(BASE_IFACE));
verify(mNms).unregisterObserver(eq(nat));
+ verify(mNetd).resolverStopPrefix64Discovery(eq(NETID));
assertIdle(nat);
// In-flight interface up notification arrives: no-op
nat.interfaceLinkStateChanged(STACKED_IFACE, true);
mLooper.dispatchNext();
-
// Interface removed notification arrives after stopClatd() takes effect: no-op.
nat.interfaceRemoved(STACKED_IFACE);
mLooper.dispatchNext();
nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX));
- // ConnectivityService starts clat.
nat.start();
verify(mNms).registerObserver(eq(nat));
verify(mNetd).clatdStop(eq(BASE_IFACE));
verify(mNms).unregisterObserver(eq(nat));
+ verify(mNetd).resolverStopPrefix64Discovery(eq(NETID));
assertIdle(nat);
verifyNoMoreInteractions(mNetd, mNms, mConnectivity);