| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| 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 static org.mockito.Mockito.inOrder; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.verifyNoMoreInteractions; |
| import static org.mockito.Mockito.when; |
| |
| import android.net.ConnectivityManager; |
| import android.net.IDnsResolver; |
| import android.net.INetd; |
| import android.net.InterfaceConfiguration; |
| import android.net.IpPrefix; |
| import android.net.LinkAddress; |
| import android.net.LinkProperties; |
| import android.net.NetworkInfo; |
| import android.net.NetworkMisc; |
| import android.os.Handler; |
| import android.os.INetworkManagementService; |
| import android.os.test.TestLooper; |
| |
| import androidx.test.filters.SmallTest; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.server.ConnectivityService; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.InOrder; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| @RunWith(AndroidJUnit4.class) |
| @SmallTest |
| 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"; |
| static final int NETID = 42; |
| |
| @Mock ConnectivityService mConnectivity; |
| @Mock NetworkMisc mMisc; |
| @Mock IDnsResolver mDnsResolver; |
| @Mock INetd mNetd; |
| @Mock INetworkManagementService mNms; |
| @Mock InterfaceConfiguration mConfig; |
| @Mock NetworkAgentInfo mNai; |
| |
| TestLooper mLooper; |
| Handler mHandler; |
| |
| Nat464Xlat makeNat464Xlat() { |
| return new Nat464Xlat(mNai, mNetd, mDnsResolver, mNms) { |
| @Override protected int getNetId() { |
| return NETID; |
| } |
| }; |
| } |
| |
| @Before |
| public void setUp() throws Exception { |
| mLooper = new TestLooper(); |
| mHandler = new Handler(mLooper.getLooper()); |
| |
| MockitoAnnotations.initMocks(this); |
| |
| mNai.linkProperties = new LinkProperties(); |
| mNai.linkProperties.setInterfaceName(BASE_IFACE); |
| mNai.networkInfo = new NetworkInfo(null); |
| mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI); |
| when(mNai.connService()).thenReturn(mConnectivity); |
| when(mNai.netMisc()).thenReturn(mMisc); |
| when(mNai.handler()).thenReturn(mHandler); |
| |
| when(mNms.getInterfaceConfig(eq(STACKED_IFACE))).thenReturn(mConfig); |
| when(mConfig.getLinkAddress()).thenReturn(ADDR); |
| } |
| |
| private void assertRequiresClat(boolean expected, NetworkAgentInfo nai) { |
| 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(), |
| nai.linkProperties.getLinkAddresses()); |
| 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 = { |
| ConnectivityManager.TYPE_MOBILE, |
| ConnectivityManager.TYPE_WIFI, |
| ConnectivityManager.TYPE_ETHERNET, |
| }; |
| |
| // NetworkInfo doesn't allow setting the State directly, but rather |
| // requires setting DetailedState in order set State as a side-effect. |
| final NetworkInfo.DetailedState[] supportedDetailedStates = { |
| NetworkInfo.DetailedState.CONNECTED, |
| 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"); |
| |
| 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); |
| } |
| } |
| } |
| |
| @Test |
| public void testNormalStartAndStop() throws Exception { |
| Nat464Xlat nat = makeNat464Xlat(); |
| ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); |
| |
| nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); |
| |
| // Start clat. |
| nat.start(); |
| |
| verify(mNms).registerObserver(eq(nat)); |
| verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); |
| |
| // Stacked interface up notification arrives. |
| nat.interfaceLinkStateChanged(STACKED_IFACE, true); |
| mLooper.dispatchNext(); |
| |
| verify(mNms).getInterfaceConfig(eq(STACKED_IFACE)); |
| verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture()); |
| assertFalse(c.getValue().getStackedLinks().isEmpty()); |
| assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); |
| assertRunning(nat); |
| |
| // Stop clat (Network disconnects, IPv4 addr appears, ...). |
| nat.stop(); |
| |
| verify(mNetd).clatdStop(eq(BASE_IFACE)); |
| verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture()); |
| verify(mNms).unregisterObserver(eq(nat)); |
| assertTrue(c.getValue().getStackedLinks().isEmpty()); |
| assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); |
| verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); |
| assertIdle(nat); |
| |
| // Stacked interface removed notification arrives and is ignored. |
| nat.interfaceRemoved(STACKED_IFACE); |
| mLooper.dispatchNext(); |
| |
| verifyNoMoreInteractions(mNetd, mNms, mConnectivity); |
| } |
| |
| private void checkStartStopStart(boolean interfaceRemovedFirst) throws Exception { |
| Nat464Xlat nat = makeNat464Xlat(); |
| ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); |
| InOrder inOrder = inOrder(mNetd, mConnectivity); |
| |
| nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); |
| |
| nat.start(); |
| |
| inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); |
| |
| // Stacked interface up notification arrives. |
| nat.interfaceLinkStateChanged(STACKED_IFACE, true); |
| mLooper.dispatchNext(); |
| |
| inOrder.verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture()); |
| assertFalse(c.getValue().getStackedLinks().isEmpty()); |
| assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); |
| assertRunning(nat); |
| |
| // ConnectivityService stops clat (Network disconnects, IPv4 addr appears, ...). |
| nat.stop(); |
| |
| inOrder.verify(mNetd).clatdStop(eq(BASE_IFACE)); |
| |
| inOrder.verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture()); |
| assertTrue(c.getValue().getStackedLinks().isEmpty()); |
| assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); |
| assertIdle(nat); |
| |
| if (interfaceRemovedFirst) { |
| // Stacked interface removed notification arrives and is ignored. |
| nat.interfaceRemoved(STACKED_IFACE); |
| mLooper.dispatchNext(); |
| nat.interfaceLinkStateChanged(STACKED_IFACE, false); |
| mLooper.dispatchNext(); |
| } |
| |
| assertTrue(c.getValue().getStackedLinks().isEmpty()); |
| assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); |
| assertIdle(nat); |
| inOrder.verifyNoMoreInteractions(); |
| |
| nat.start(); |
| |
| inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); |
| |
| if (!interfaceRemovedFirst) { |
| // Stacked interface removed notification arrives and is ignored. |
| nat.interfaceRemoved(STACKED_IFACE); |
| mLooper.dispatchNext(); |
| nat.interfaceLinkStateChanged(STACKED_IFACE, false); |
| mLooper.dispatchNext(); |
| } |
| |
| // Stacked interface up notification arrives. |
| nat.interfaceLinkStateChanged(STACKED_IFACE, true); |
| mLooper.dispatchNext(); |
| |
| inOrder.verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture()); |
| assertFalse(c.getValue().getStackedLinks().isEmpty()); |
| assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); |
| assertRunning(nat); |
| |
| // ConnectivityService stops clat again. |
| nat.stop(); |
| |
| inOrder.verify(mNetd).clatdStop(eq(BASE_IFACE)); |
| |
| inOrder.verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture()); |
| assertTrue(c.getValue().getStackedLinks().isEmpty()); |
| assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); |
| assertIdle(nat); |
| |
| inOrder.verifyNoMoreInteractions(); |
| } |
| |
| @Test |
| public void testStartStopStart() throws Exception { |
| checkStartStopStart(true); |
| } |
| |
| @Test |
| public void testStartStopStartBeforeInterfaceRemoved() throws Exception { |
| checkStartStopStart(false); |
| } |
| |
| @Test |
| public void testClatdCrashWhileRunning() throws Exception { |
| Nat464Xlat nat = makeNat464Xlat(); |
| ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); |
| |
| nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); |
| |
| nat.start(); |
| |
| verify(mNms).registerObserver(eq(nat)); |
| verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); |
| |
| // Stacked interface up notification arrives. |
| nat.interfaceLinkStateChanged(STACKED_IFACE, true); |
| mLooper.dispatchNext(); |
| |
| verify(mNms).getInterfaceConfig(eq(STACKED_IFACE)); |
| verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture()); |
| assertFalse(c.getValue().getStackedLinks().isEmpty()); |
| assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); |
| assertRunning(nat); |
| |
| // Stacked interface removed notification arrives (clatd crashed, ...). |
| nat.interfaceRemoved(STACKED_IFACE); |
| mLooper.dispatchNext(); |
| |
| verify(mNetd).clatdStop(eq(BASE_IFACE)); |
| verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture()); |
| verify(mNms).unregisterObserver(eq(nat)); |
| verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); |
| assertTrue(c.getValue().getStackedLinks().isEmpty()); |
| assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); |
| assertIdle(nat); |
| |
| // ConnectivityService stops clat: no-op. |
| nat.stop(); |
| |
| verifyNoMoreInteractions(mNetd, mNms, mConnectivity); |
| } |
| |
| @Test |
| public void testStopBeforeClatdStarts() throws Exception { |
| Nat464Xlat nat = makeNat464Xlat(); |
| |
| nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); |
| |
| nat.start(); |
| |
| verify(mNms).registerObserver(eq(nat)); |
| verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); |
| |
| // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...) |
| nat.stop(); |
| |
| verify(mNetd).clatdStop(eq(BASE_IFACE)); |
| verify(mNms).unregisterObserver(eq(nat)); |
| verify(mDnsResolver).stopPrefix64Discovery(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(); |
| |
| assertIdle(nat); |
| |
| verifyNoMoreInteractions(mNetd, mNms, mConnectivity); |
| } |
| |
| @Test |
| public void testStopAndClatdNeverStarts() throws Exception { |
| Nat464Xlat nat = makeNat464Xlat(); |
| |
| nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); |
| |
| nat.start(); |
| |
| verify(mNms).registerObserver(eq(nat)); |
| verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); |
| |
| // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...) |
| nat.stop(); |
| |
| verify(mNetd).clatdStop(eq(BASE_IFACE)); |
| verify(mNms).unregisterObserver(eq(nat)); |
| verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); |
| assertIdle(nat); |
| |
| verifyNoMoreInteractions(mNetd, mNms, mConnectivity); |
| } |
| |
| static void assertIdle(Nat464Xlat nat) { |
| assertTrue("Nat464Xlat was not IDLE", !nat.isStarted()); |
| } |
| |
| static void assertRunning(Nat464Xlat nat) { |
| assertTrue("Nat464Xlat was not RUNNING", nat.isRunning()); |
| } |
| } |