1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.connectivity;
18 
19 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertTrue;
24 import static org.mockito.Mockito.anyString;
25 import static org.mockito.Mockito.eq;
26 import static org.mockito.Mockito.inOrder;
27 import static org.mockito.Mockito.never;
28 import static org.mockito.Mockito.times;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.verifyNoMoreInteractions;
31 import static org.mockito.Mockito.when;
32 
33 import android.net.ConnectivityManager;
34 import android.net.IDnsResolver;
35 import android.net.INetd;
36 import android.net.InterfaceConfigurationParcel;
37 import android.net.IpPrefix;
38 import android.net.LinkAddress;
39 import android.net.LinkProperties;
40 import android.net.NetworkAgentConfig;
41 import android.net.NetworkCapabilities;
42 import android.net.NetworkInfo;
43 import android.os.Build;
44 import android.os.Handler;
45 import android.os.test.TestLooper;
46 
47 import androidx.test.filters.SmallTest;
48 
49 import com.android.server.ConnectivityService;
50 import com.android.testutils.DevSdkIgnoreRule;
51 import com.android.testutils.DevSdkIgnoreRunner;
52 
53 import org.junit.Before;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.mockito.ArgumentCaptor;
57 import org.mockito.InOrder;
58 import org.mockito.Mock;
59 import org.mockito.MockitoAnnotations;
60 
61 @RunWith(DevSdkIgnoreRunner.class)
62 @SmallTest
63 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
64 public class Nat464XlatTest {
65 
66     static final String BASE_IFACE = "test0";
67     static final String STACKED_IFACE = "v4-test0";
68     static final LinkAddress V6ADDR = new LinkAddress("2001:db8:1::f00/64");
69     static final LinkAddress ADDR = new LinkAddress("192.0.2.5/29");
70     static final String NAT64_PREFIX = "64:ff9b::/96";
71     static final String OTHER_NAT64_PREFIX = "2001:db8:0:64::/96";
72     static final int NETID = 42;
73 
74     @Mock ConnectivityService mConnectivity;
75     @Mock IDnsResolver mDnsResolver;
76     @Mock INetd mNetd;
77     @Mock NetworkAgentInfo mNai;
78 
79     TestLooper mLooper;
80     Handler mHandler;
81     NetworkAgentConfig mAgentConfig = new NetworkAgentConfig();
82 
makeNat464Xlat(boolean isCellular464XlatEnabled)83     Nat464Xlat makeNat464Xlat(boolean isCellular464XlatEnabled) {
84         return new Nat464Xlat(mNai, mNetd, mDnsResolver, new ConnectivityService.Dependencies()) {
85             @Override protected int getNetId() {
86                 return NETID;
87             }
88 
89             @Override protected boolean isCellular464XlatEnabled() {
90                 return isCellular464XlatEnabled;
91             }
92         };
93     }
94 
95     private void markNetworkConnected() {
96         mNai.networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", "");
97     }
98 
99     private void markNetworkDisconnected() {
100         mNai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, "", "");
101     }
102 
103     @Before
104     public void setUp() throws Exception {
105         mLooper = new TestLooper();
106         mHandler = new Handler(mLooper.getLooper());
107 
108         MockitoAnnotations.initMocks(this);
109 
110         mNai.linkProperties = new LinkProperties();
111         mNai.linkProperties.setInterfaceName(BASE_IFACE);
112         mNai.networkInfo = new NetworkInfo(null);
113         mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI);
114         mNai.networkCapabilities = new NetworkCapabilities();
115         markNetworkConnected();
116         when(mNai.connService()).thenReturn(mConnectivity);
117         when(mNai.netAgentConfig()).thenReturn(mAgentConfig);
118         when(mNai.handler()).thenReturn(mHandler);
119         final InterfaceConfigurationParcel mConfig = new InterfaceConfigurationParcel();
120         when(mNetd.interfaceGetCfg(eq(STACKED_IFACE))).thenReturn(mConfig);
121         mConfig.ipv4Addr = ADDR.getAddress().getHostAddress();
122         mConfig.prefixLength =  ADDR.getPrefixLength();
123     }
124 
125     private void assertRequiresClat(boolean expected, NetworkAgentInfo nai) {
126         Nat464Xlat nat = makeNat464Xlat(true);
127         String msg = String.format("requiresClat expected %b for type=%d state=%s skip=%b "
128                 + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
129                 nai.networkInfo.getDetailedState(),
130                 mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(),
131                 nai.linkProperties.getLinkAddresses());
132         assertEquals(msg, expected, nat.requiresClat(nai));
133     }
134 
135     private void assertShouldStartClat(boolean expected, NetworkAgentInfo nai) {
136         Nat464Xlat nat = makeNat464Xlat(true);
137         String msg = String.format("shouldStartClat expected %b for type=%d state=%s skip=%b "
138                 + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
139                 nai.networkInfo.getDetailedState(),
140                 mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(),
141                 nai.linkProperties.getLinkAddresses());
142         assertEquals(msg, expected, nat.shouldStartClat(nai));
143     }
144 
145     @Test
146     public void testRequiresClat() throws Exception {
147         final int[] supportedTypes = {
148             ConnectivityManager.TYPE_MOBILE,
149             ConnectivityManager.TYPE_WIFI,
150             ConnectivityManager.TYPE_ETHERNET,
151         };
152 
153         // NetworkInfo doesn't allow setting the State directly, but rather
154         // requires setting DetailedState in order set State as a side-effect.
155         final NetworkInfo.DetailedState[] supportedDetailedStates = {
156             NetworkInfo.DetailedState.CONNECTED,
157             NetworkInfo.DetailedState.SUSPENDED,
158         };
159 
160         LinkProperties oldLp = new LinkProperties(mNai.linkProperties);
161         for (int type : supportedTypes) {
162             mNai.networkInfo.setType(type);
163             for (NetworkInfo.DetailedState state : supportedDetailedStates) {
164                 mNai.networkInfo.setDetailedState(state, "reason", "extraInfo");
165 
166                 mNai.linkProperties.setNat64Prefix(new IpPrefix(OTHER_NAT64_PREFIX));
167                 assertRequiresClat(false, mNai);
168                 assertShouldStartClat(false, mNai);
169 
170                 mNai.linkProperties.addLinkAddress(new LinkAddress("fc00::1/64"));
171                 assertRequiresClat(false, mNai);
172                 assertShouldStartClat(false, mNai);
173 
174                 mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
175                 assertRequiresClat(true, mNai);
176                 assertShouldStartClat(true, mNai);
177 
178                 mAgentConfig.skip464xlat = true;
179                 assertRequiresClat(false, mNai);
180                 assertShouldStartClat(false, mNai);
181 
182                 mAgentConfig.skip464xlat = false;
183                 assertRequiresClat(true, mNai);
184                 assertShouldStartClat(true, mNai);
185 
186                 mNai.linkProperties.addLinkAddress(new LinkAddress("192.0.2.2/24"));
187                 assertRequiresClat(false, mNai);
188                 assertShouldStartClat(false, mNai);
189 
190                 mNai.linkProperties.removeLinkAddress(new LinkAddress("192.0.2.2/24"));
191                 assertRequiresClat(true, mNai);
192                 assertShouldStartClat(true, mNai);
193 
194                 mNai.linkProperties.setNat64Prefix(null);
195                 assertRequiresClat(true, mNai);
196                 assertShouldStartClat(false, mNai);
197 
198                 mNai.linkProperties = new LinkProperties(oldLp);
199             }
200         }
201     }
202 
203     private void makeClatUnnecessary(boolean dueToDisconnect) {
204         if (dueToDisconnect) {
205             markNetworkDisconnected();
206         } else {
207             mNai.linkProperties.addLinkAddress(ADDR);
208         }
209     }
210 
211     private void checkNormalStartAndStop(boolean dueToDisconnect) throws Exception {
212         Nat464Xlat nat = makeNat464Xlat(true);
213         ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
214 
215         mNai.linkProperties.addLinkAddress(V6ADDR);
216 
217         nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
218 
219         // Start clat.
220         nat.start();
221 
222         verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
223 
224         // Stacked interface up notification arrives.
225         nat.interfaceLinkStateChanged(STACKED_IFACE, true);
226         mLooper.dispatchNext();
227 
228         verify(mNetd).interfaceGetCfg(eq(STACKED_IFACE));
229         verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture());
230         assertFalse(c.getValue().getStackedLinks().isEmpty());
231         assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
232         assertRunning(nat);
233 
234         // Stop clat (Network disconnects, IPv4 addr appears, ...).
235         makeClatUnnecessary(dueToDisconnect);
236         nat.stop();
237 
238         verify(mNetd).clatdStop(eq(BASE_IFACE));
239         verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
240         assertTrue(c.getValue().getStackedLinks().isEmpty());
241         assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
242         verify(mDnsResolver).stopPrefix64Discovery(eq(NETID));
243         assertIdle(nat);
244 
245         // Stacked interface removed notification arrives and is ignored.
246         nat.interfaceRemoved(STACKED_IFACE);
247         mLooper.dispatchNext();
248 
249         verifyNoMoreInteractions(mNetd, mConnectivity);
250     }
251 
252     @Test
253     public void testNormalStartAndStopDueToDisconnect() throws Exception {
254         checkNormalStartAndStop(true);
255     }
256 
257     @Test
258     public void testNormalStartAndStopDueToIpv4Addr() throws Exception {
259         checkNormalStartAndStop(false);
260     }
261 
262     private void checkStartStopStart(boolean interfaceRemovedFirst) throws Exception {
263         Nat464Xlat nat = makeNat464Xlat(true);
264         ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
265         InOrder inOrder = inOrder(mNetd, mConnectivity);
266 
267         mNai.linkProperties.addLinkAddress(V6ADDR);
268 
269         nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
270 
271         nat.start();
272 
273         inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
274 
275         // Stacked interface up notification arrives.
276         nat.interfaceLinkStateChanged(STACKED_IFACE, true);
277         mLooper.dispatchNext();
278 
279         inOrder.verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture());
280         assertFalse(c.getValue().getStackedLinks().isEmpty());
281         assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
282         assertRunning(nat);
283 
284         // ConnectivityService stops clat (Network disconnects, IPv4 addr appears, ...).
285         nat.stop();
286 
287         inOrder.verify(mNetd).clatdStop(eq(BASE_IFACE));
288 
289         inOrder.verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture());
290         assertTrue(c.getValue().getStackedLinks().isEmpty());
291         assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
292         assertIdle(nat);
293 
294         if (interfaceRemovedFirst) {
295             // Stacked interface removed notification arrives and is ignored.
296             nat.interfaceRemoved(STACKED_IFACE);
297             mLooper.dispatchNext();
298             nat.interfaceLinkStateChanged(STACKED_IFACE, false);
299             mLooper.dispatchNext();
300         }
301 
302         assertTrue(c.getValue().getStackedLinks().isEmpty());
303         assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
304         assertIdle(nat);
305         inOrder.verifyNoMoreInteractions();
306 
307         nat.start();
308 
309         inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
310 
311         if (!interfaceRemovedFirst) {
312             // Stacked interface removed notification arrives and is ignored.
313             nat.interfaceRemoved(STACKED_IFACE);
314             mLooper.dispatchNext();
315             nat.interfaceLinkStateChanged(STACKED_IFACE, false);
316             mLooper.dispatchNext();
317         }
318 
319         // Stacked interface up notification arrives.
320         nat.interfaceLinkStateChanged(STACKED_IFACE, true);
321         mLooper.dispatchNext();
322 
323         inOrder.verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture());
324         assertFalse(c.getValue().getStackedLinks().isEmpty());
325         assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
326         assertRunning(nat);
327 
328         // ConnectivityService stops clat again.
329         nat.stop();
330 
331         inOrder.verify(mNetd).clatdStop(eq(BASE_IFACE));
332 
333         inOrder.verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture());
334         assertTrue(c.getValue().getStackedLinks().isEmpty());
335         assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
336         assertIdle(nat);
337 
338         inOrder.verifyNoMoreInteractions();
339     }
340 
341     @Test
342     public void testStartStopStart() throws Exception {
343         checkStartStopStart(true);
344     }
345 
346     @Test
347     public void testStartStopStartBeforeInterfaceRemoved() throws Exception {
348         checkStartStopStart(false);
349     }
350 
351     @Test
352     public void testClatdCrashWhileRunning() throws Exception {
353         Nat464Xlat nat = makeNat464Xlat(true);
354         ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
355 
356         nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
357 
358         nat.start();
359 
360         verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
361 
362         // Stacked interface up notification arrives.
363         nat.interfaceLinkStateChanged(STACKED_IFACE, true);
364         mLooper.dispatchNext();
365 
366         verify(mNetd).interfaceGetCfg(eq(STACKED_IFACE));
367         verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture());
368         assertFalse(c.getValue().getStackedLinks().isEmpty());
369         assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
370         assertRunning(nat);
371 
372         // Stacked interface removed notification arrives (clatd crashed, ...).
373         nat.interfaceRemoved(STACKED_IFACE);
374         mLooper.dispatchNext();
375 
376         verify(mNetd).clatdStop(eq(BASE_IFACE));
377         verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
378         verify(mDnsResolver).stopPrefix64Discovery(eq(NETID));
379         assertTrue(c.getValue().getStackedLinks().isEmpty());
380         assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
381         assertIdle(nat);
382 
383         // ConnectivityService stops clat: no-op.
384         nat.stop();
385 
386         verifyNoMoreInteractions(mNetd, mConnectivity);
387     }
388 
389     private void checkStopBeforeClatdStarts(boolean dueToDisconnect) throws Exception {
390         Nat464Xlat nat = makeNat464Xlat(true);
391 
392         mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
393 
394         nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
395 
396         nat.start();
397 
398         verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
399 
400         // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
401         makeClatUnnecessary(dueToDisconnect);
402         nat.stop();
403 
404         verify(mNetd).clatdStop(eq(BASE_IFACE));
405         verify(mDnsResolver).stopPrefix64Discovery(eq(NETID));
406         assertIdle(nat);
407 
408         // In-flight interface up notification arrives: no-op
409         nat.interfaceLinkStateChanged(STACKED_IFACE, true);
410         mLooper.dispatchNext();
411 
412         // Interface removed notification arrives after stopClatd() takes effect: no-op.
413         nat.interfaceRemoved(STACKED_IFACE);
414         mLooper.dispatchNext();
415 
416         assertIdle(nat);
417 
418         verifyNoMoreInteractions(mNetd, mConnectivity);
419     }
420 
421     @Test
422     public void testStopDueToDisconnectBeforeClatdStarts() throws Exception {
423         checkStopBeforeClatdStarts(true);
424     }
425 
426     @Test
427     public void testStopDueToIpv4AddrBeforeClatdStarts() throws Exception {
428         checkStopBeforeClatdStarts(false);
429     }
430 
431     private void checkStopAndClatdNeverStarts(boolean dueToDisconnect) throws Exception {
432         Nat464Xlat nat = makeNat464Xlat(true);
433 
434         mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
435 
436         nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
437 
438         nat.start();
439 
440         verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
441 
442         // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
443         makeClatUnnecessary(dueToDisconnect);
444         nat.stop();
445 
446         verify(mNetd).clatdStop(eq(BASE_IFACE));
447         verify(mDnsResolver).stopPrefix64Discovery(eq(NETID));
448         assertIdle(nat);
449 
450         verifyNoMoreInteractions(mNetd, mConnectivity);
451     }
452 
453     @Test
454     public void testStopDueToDisconnectAndClatdNeverStarts() throws Exception {
455         checkStopAndClatdNeverStarts(true);
456     }
457 
458     @Test
459     public void testStopDueToIpv4AddressAndClatdNeverStarts() throws Exception {
460         checkStopAndClatdNeverStarts(false);
461     }
462 
463     @Test
464     public void testNat64PrefixPreference() throws Exception {
465         final IpPrefix prefixFromDns = new IpPrefix(NAT64_PREFIX);
466         final IpPrefix prefixFromRa = new IpPrefix(OTHER_NAT64_PREFIX);
467 
468         Nat464Xlat nat = makeNat464Xlat(true);
469 
470         final LinkProperties emptyLp = new LinkProperties();
471         LinkProperties fixedupLp;
472 
473         fixedupLp = new LinkProperties();
474         nat.setNat64PrefixFromDns(prefixFromDns);
475         nat.fixupLinkProperties(emptyLp, fixedupLp);
476         assertEquals(prefixFromDns, fixedupLp.getNat64Prefix());
477 
478         fixedupLp = new LinkProperties();
479         nat.setNat64PrefixFromRa(prefixFromRa);
480         nat.fixupLinkProperties(emptyLp, fixedupLp);
481         assertEquals(prefixFromRa, fixedupLp.getNat64Prefix());
482 
483         fixedupLp = new LinkProperties();
484         nat.setNat64PrefixFromRa(null);
485         nat.fixupLinkProperties(emptyLp, fixedupLp);
486         assertEquals(prefixFromDns, fixedupLp.getNat64Prefix());
487 
488         fixedupLp = new LinkProperties();
489         nat.setNat64PrefixFromRa(prefixFromRa);
490         nat.fixupLinkProperties(emptyLp, fixedupLp);
491         assertEquals(prefixFromRa, fixedupLp.getNat64Prefix());
492 
493         fixedupLp = new LinkProperties();
494         nat.setNat64PrefixFromDns(null);
495         nat.fixupLinkProperties(emptyLp, fixedupLp);
496         assertEquals(prefixFromRa, fixedupLp.getNat64Prefix());
497 
498         fixedupLp = new LinkProperties();
499         nat.setNat64PrefixFromRa(null);
500         nat.fixupLinkProperties(emptyLp, fixedupLp);
501         assertEquals(null, fixedupLp.getNat64Prefix());
502     }
503 
504     private void checkClatDisabledOnCellular(boolean onCellular) throws Exception {
505         // Disable 464xlat on cellular networks.
506         Nat464Xlat nat = makeNat464Xlat(false);
507         mNai.linkProperties.addLinkAddress(V6ADDR);
508         mNai.networkCapabilities.setTransportType(TRANSPORT_CELLULAR, onCellular);
509         nat.update();
510 
511         final IpPrefix nat64Prefix = new IpPrefix(NAT64_PREFIX);
512         if (onCellular) {
513             // Prefix discovery is never started.
514             verify(mDnsResolver, never()).startPrefix64Discovery(eq(NETID));
515             assertIdle(nat);
516 
517             // If a NAT64 prefix comes in from an RA, clat is not started either.
518             mNai.linkProperties.setNat64Prefix(nat64Prefix);
519             nat.setNat64PrefixFromRa(nat64Prefix);
520             nat.update();
521             verify(mNetd, never()).clatdStart(anyString(), anyString());
522             assertIdle(nat);
523         } else {
524             // Prefix discovery is started.
525             verify(mDnsResolver).startPrefix64Discovery(eq(NETID));
526             assertIdle(nat);
527 
528             // If a NAT64 prefix comes in from an RA, clat is started.
529             mNai.linkProperties.setNat64Prefix(nat64Prefix);
530             nat.setNat64PrefixFromRa(nat64Prefix);
531             nat.update();
532             verify(mNetd).clatdStart(BASE_IFACE, NAT64_PREFIX);
533             assertStarting(nat);
534         }
535     }
536 
537     @Test
538     public void testClatDisabledOnCellular() throws Exception {
539         checkClatDisabledOnCellular(true);
540     }
541 
542     @Test
543     public void testClatDisabledOnNonCellular() throws Exception {
544         checkClatDisabledOnCellular(false);
545     }
546 
547     static void assertIdle(Nat464Xlat nat) {
548         assertTrue("Nat464Xlat was not IDLE", !nat.isStarted());
549     }
550 
551     static void assertStarting(Nat464Xlat nat) {
552         assertTrue("Nat464Xlat was not STARTING", nat.isStarting());
553     }
554 
555     static void assertRunning(Nat464Xlat nat) {
556         assertTrue("Nat464Xlat was not RUNNING", nat.isRunning());
557     }
558 }
559