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;
}
}
}
+
+ @Override
+ public void onNat64PrefixEvent(int netId, boolean added,
+ String prefixString, int prefixLength) {
+ mHandler.post(() -> handleNat64PrefixEvent(netId, added, prefixString, prefixLength));
+ }
};
@VisibleForTesting
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.
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;
import com.android.server.net.BaseNetworkObserver;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.util.Objects;
/**
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) {
// 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;
}
/**
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);
+ }
}
/**
}
}
+ 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;
}
fn.test((NetworkCapabilities) cbi.arg));
}
+ void expectLinkPropertiesLike(Predicate<LinkProperties> 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;
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)
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<LinkProperties> stackedLps = mCm.getLinkProperties(mCellNetworkAgent.getNetwork())
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);
+ networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
- // Clat iface removed, expect linkproperties revert to original one
+ // 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);
- LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellNetworkAgent.getNetwork());
- assertEquals(cellLp, actualLpAfterIpv4);
+ verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString());
- // Clean up
+ // 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();
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;
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;
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;
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 = {
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);
}
}
}
Nat464Xlat nat = makeNat464Xlat();
ArgumentCaptor<LinkProperties> 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);
ArgumentCaptor<LinkProperties> 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);
// 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.
Nat464Xlat nat = makeNat464Xlat();
ArgumentCaptor<LinkProperties> 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);
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();
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();