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 org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertTrue; 22 import static org.mockito.Mockito.eq; 23 import static org.mockito.Mockito.inOrder; 24 import static org.mockito.Mockito.times; 25 import static org.mockito.Mockito.verify; 26 import static org.mockito.Mockito.verifyNoMoreInteractions; 27 import static org.mockito.Mockito.when; 28 29 import android.net.ConnectivityManager; 30 import android.net.IDnsResolver; 31 import android.net.INetd; 32 import android.net.InterfaceConfiguration; 33 import android.net.IpPrefix; 34 import android.net.LinkAddress; 35 import android.net.LinkProperties; 36 import android.net.NetworkAgentConfig; 37 import android.net.NetworkInfo; 38 import android.os.Handler; 39 import android.os.INetworkManagementService; 40 import android.os.test.TestLooper; 41 42 import androidx.test.filters.SmallTest; 43 import androidx.test.runner.AndroidJUnit4; 44 45 import com.android.server.ConnectivityService; 46 47 import org.junit.Before; 48 import org.junit.Test; 49 import org.junit.runner.RunWith; 50 import org.mockito.ArgumentCaptor; 51 import org.mockito.InOrder; 52 import org.mockito.Mock; 53 import org.mockito.MockitoAnnotations; 54 55 @RunWith(AndroidJUnit4.class) 56 @SmallTest 57 public class Nat464XlatTest { 58 59 static final String BASE_IFACE = "test0"; 60 static final String STACKED_IFACE = "v4-test0"; 61 static final LinkAddress V6ADDR = new LinkAddress("2001:db8:1::f00/64"); 62 static final LinkAddress ADDR = new LinkAddress("192.0.2.5/29"); 63 static final String NAT64_PREFIX = "64:ff9b::/96"; 64 static final String OTHER_NAT64_PREFIX = "2001:db8:0:64::/96"; 65 static final int NETID = 42; 66 67 @Mock ConnectivityService mConnectivity; 68 @Mock IDnsResolver mDnsResolver; 69 @Mock INetd mNetd; 70 @Mock INetworkManagementService mNms; 71 @Mock InterfaceConfiguration mConfig; 72 @Mock NetworkAgentInfo mNai; 73 74 TestLooper mLooper; 75 Handler mHandler; 76 NetworkAgentConfig mAgentConfig = new NetworkAgentConfig(); 77 makeNat464Xlat()78 Nat464Xlat makeNat464Xlat() { 79 return new Nat464Xlat(mNai, mNetd, mDnsResolver, mNms) { 80 @Override protected int getNetId() { 81 return NETID; 82 } 83 }; 84 } 85 86 private void markNetworkConnected() { 87 mNai.networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", ""); 88 } 89 90 private void markNetworkDisconnected() { 91 mNai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, "", ""); 92 } 93 94 @Before 95 public void setUp() throws Exception { 96 mLooper = new TestLooper(); 97 mHandler = new Handler(mLooper.getLooper()); 98 99 MockitoAnnotations.initMocks(this); 100 101 mNai.linkProperties = new LinkProperties(); 102 mNai.linkProperties.setInterfaceName(BASE_IFACE); 103 mNai.networkInfo = new NetworkInfo(null); 104 mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI); 105 markNetworkConnected(); 106 when(mNai.connService()).thenReturn(mConnectivity); 107 when(mNai.netAgentConfig()).thenReturn(mAgentConfig); 108 when(mNai.handler()).thenReturn(mHandler); 109 110 when(mNms.getInterfaceConfig(eq(STACKED_IFACE))).thenReturn(mConfig); 111 when(mConfig.getLinkAddress()).thenReturn(ADDR); 112 } 113 114 private void assertRequiresClat(boolean expected, NetworkAgentInfo nai) { 115 String msg = String.format("requiresClat expected %b for type=%d state=%s skip=%b " 116 + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(), 117 nai.networkInfo.getDetailedState(), 118 mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(), 119 nai.linkProperties.getLinkAddresses()); 120 assertEquals(msg, expected, Nat464Xlat.requiresClat(nai)); 121 } 122 123 private void assertShouldStartClat(boolean expected, NetworkAgentInfo nai) { 124 String msg = String.format("shouldStartClat expected %b for type=%d state=%s skip=%b " 125 + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(), 126 nai.networkInfo.getDetailedState(), 127 mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(), 128 nai.linkProperties.getLinkAddresses()); 129 assertEquals(msg, expected, Nat464Xlat.shouldStartClat(nai)); 130 } 131 132 @Test 133 public void testRequiresClat() throws Exception { 134 final int[] supportedTypes = { 135 ConnectivityManager.TYPE_MOBILE, 136 ConnectivityManager.TYPE_WIFI, 137 ConnectivityManager.TYPE_ETHERNET, 138 }; 139 140 // NetworkInfo doesn't allow setting the State directly, but rather 141 // requires setting DetailedState in order set State as a side-effect. 142 final NetworkInfo.DetailedState[] supportedDetailedStates = { 143 NetworkInfo.DetailedState.CONNECTED, 144 NetworkInfo.DetailedState.SUSPENDED, 145 }; 146 147 LinkProperties oldLp = new LinkProperties(mNai.linkProperties); 148 for (int type : supportedTypes) { 149 mNai.networkInfo.setType(type); 150 for (NetworkInfo.DetailedState state : supportedDetailedStates) { 151 mNai.networkInfo.setDetailedState(state, "reason", "extraInfo"); 152 153 mNai.linkProperties.setNat64Prefix(new IpPrefix(OTHER_NAT64_PREFIX)); 154 assertRequiresClat(false, mNai); 155 assertShouldStartClat(false, mNai); 156 157 mNai.linkProperties.addLinkAddress(new LinkAddress("fc00::1/64")); 158 assertRequiresClat(false, mNai); 159 assertShouldStartClat(false, mNai); 160 161 mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64")); 162 assertRequiresClat(true, mNai); 163 assertShouldStartClat(true, mNai); 164 165 mAgentConfig.skip464xlat = true; 166 assertRequiresClat(false, mNai); 167 assertShouldStartClat(false, mNai); 168 169 mAgentConfig.skip464xlat = false; 170 assertRequiresClat(true, mNai); 171 assertShouldStartClat(true, mNai); 172 173 mNai.linkProperties.addLinkAddress(new LinkAddress("192.0.2.2/24")); 174 assertRequiresClat(false, mNai); 175 assertShouldStartClat(false, mNai); 176 177 mNai.linkProperties.removeLinkAddress(new LinkAddress("192.0.2.2/24")); 178 assertRequiresClat(true, mNai); 179 assertShouldStartClat(true, mNai); 180 181 mNai.linkProperties.setNat64Prefix(null); 182 assertRequiresClat(true, mNai); 183 assertShouldStartClat(false, mNai); 184 185 mNai.linkProperties = new LinkProperties(oldLp); 186 } 187 } 188 } 189 190 private void makeClatUnnecessary(boolean dueToDisconnect) { 191 if (dueToDisconnect) { 192 markNetworkDisconnected(); 193 } else { 194 mNai.linkProperties.addLinkAddress(ADDR); 195 } 196 } 197 198 private void checkNormalStartAndStop(boolean dueToDisconnect) throws Exception { 199 Nat464Xlat nat = makeNat464Xlat(); 200 ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); 201 202 mNai.linkProperties.addLinkAddress(V6ADDR); 203 204 nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); 205 206 // Start clat. 207 nat.start(); 208 209 verify(mNms).registerObserver(eq(nat)); 210 verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); 211 212 // Stacked interface up notification arrives. 213 nat.interfaceLinkStateChanged(STACKED_IFACE, true); 214 mLooper.dispatchNext(); 215 216 verify(mNms).getInterfaceConfig(eq(STACKED_IFACE)); 217 verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture()); 218 assertFalse(c.getValue().getStackedLinks().isEmpty()); 219 assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); 220 assertRunning(nat); 221 222 // Stop clat (Network disconnects, IPv4 addr appears, ...). 223 makeClatUnnecessary(dueToDisconnect); 224 nat.stop(); 225 226 verify(mNetd).clatdStop(eq(BASE_IFACE)); 227 verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture()); 228 verify(mNms).unregisterObserver(eq(nat)); 229 assertTrue(c.getValue().getStackedLinks().isEmpty()); 230 assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); 231 verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); 232 assertIdle(nat); 233 234 // Stacked interface removed notification arrives and is ignored. 235 nat.interfaceRemoved(STACKED_IFACE); 236 mLooper.dispatchNext(); 237 238 verifyNoMoreInteractions(mNetd, mNms, mConnectivity); 239 } 240 241 @Test 242 public void testNormalStartAndStopDueToDisconnect() throws Exception { 243 checkNormalStartAndStop(true); 244 } 245 246 @Test 247 public void testNormalStartAndStopDueToIpv4Addr() throws Exception { 248 checkNormalStartAndStop(false); 249 } 250 251 private void checkStartStopStart(boolean interfaceRemovedFirst) throws Exception { 252 Nat464Xlat nat = makeNat464Xlat(); 253 ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); 254 InOrder inOrder = inOrder(mNetd, mConnectivity); 255 256 mNai.linkProperties.addLinkAddress(V6ADDR); 257 258 nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); 259 260 nat.start(); 261 262 inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); 263 264 // Stacked interface up notification arrives. 265 nat.interfaceLinkStateChanged(STACKED_IFACE, true); 266 mLooper.dispatchNext(); 267 268 inOrder.verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture()); 269 assertFalse(c.getValue().getStackedLinks().isEmpty()); 270 assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); 271 assertRunning(nat); 272 273 // ConnectivityService stops clat (Network disconnects, IPv4 addr appears, ...). 274 nat.stop(); 275 276 inOrder.verify(mNetd).clatdStop(eq(BASE_IFACE)); 277 278 inOrder.verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture()); 279 assertTrue(c.getValue().getStackedLinks().isEmpty()); 280 assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); 281 assertIdle(nat); 282 283 if (interfaceRemovedFirst) { 284 // Stacked interface removed notification arrives and is ignored. 285 nat.interfaceRemoved(STACKED_IFACE); 286 mLooper.dispatchNext(); 287 nat.interfaceLinkStateChanged(STACKED_IFACE, false); 288 mLooper.dispatchNext(); 289 } 290 291 assertTrue(c.getValue().getStackedLinks().isEmpty()); 292 assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); 293 assertIdle(nat); 294 inOrder.verifyNoMoreInteractions(); 295 296 nat.start(); 297 298 inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); 299 300 if (!interfaceRemovedFirst) { 301 // Stacked interface removed notification arrives and is ignored. 302 nat.interfaceRemoved(STACKED_IFACE); 303 mLooper.dispatchNext(); 304 nat.interfaceLinkStateChanged(STACKED_IFACE, false); 305 mLooper.dispatchNext(); 306 } 307 308 // Stacked interface up notification arrives. 309 nat.interfaceLinkStateChanged(STACKED_IFACE, true); 310 mLooper.dispatchNext(); 311 312 inOrder.verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture()); 313 assertFalse(c.getValue().getStackedLinks().isEmpty()); 314 assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); 315 assertRunning(nat); 316 317 // ConnectivityService stops clat again. 318 nat.stop(); 319 320 inOrder.verify(mNetd).clatdStop(eq(BASE_IFACE)); 321 322 inOrder.verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture()); 323 assertTrue(c.getValue().getStackedLinks().isEmpty()); 324 assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); 325 assertIdle(nat); 326 327 inOrder.verifyNoMoreInteractions(); 328 } 329 330 @Test 331 public void testStartStopStart() throws Exception { 332 checkStartStopStart(true); 333 } 334 335 @Test 336 public void testStartStopStartBeforeInterfaceRemoved() throws Exception { 337 checkStartStopStart(false); 338 } 339 340 @Test 341 public void testClatdCrashWhileRunning() throws Exception { 342 Nat464Xlat nat = makeNat464Xlat(); 343 ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); 344 345 nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); 346 347 nat.start(); 348 349 verify(mNms).registerObserver(eq(nat)); 350 verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); 351 352 // Stacked interface up notification arrives. 353 nat.interfaceLinkStateChanged(STACKED_IFACE, true); 354 mLooper.dispatchNext(); 355 356 verify(mNms).getInterfaceConfig(eq(STACKED_IFACE)); 357 verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture()); 358 assertFalse(c.getValue().getStackedLinks().isEmpty()); 359 assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); 360 assertRunning(nat); 361 362 // Stacked interface removed notification arrives (clatd crashed, ...). 363 nat.interfaceRemoved(STACKED_IFACE); 364 mLooper.dispatchNext(); 365 366 verify(mNetd).clatdStop(eq(BASE_IFACE)); 367 verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture()); 368 verify(mNms).unregisterObserver(eq(nat)); 369 verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); 370 assertTrue(c.getValue().getStackedLinks().isEmpty()); 371 assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); 372 assertIdle(nat); 373 374 // ConnectivityService stops clat: no-op. 375 nat.stop(); 376 377 verifyNoMoreInteractions(mNetd, mNms, mConnectivity); 378 } 379 380 private void checkStopBeforeClatdStarts(boolean dueToDisconnect) throws Exception { 381 Nat464Xlat nat = makeNat464Xlat(); 382 383 mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64")); 384 385 nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); 386 387 nat.start(); 388 389 verify(mNms).registerObserver(eq(nat)); 390 verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); 391 392 // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...) 393 makeClatUnnecessary(dueToDisconnect); 394 nat.stop(); 395 396 verify(mNetd).clatdStop(eq(BASE_IFACE)); 397 verify(mNms).unregisterObserver(eq(nat)); 398 verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); 399 assertIdle(nat); 400 401 // In-flight interface up notification arrives: no-op 402 nat.interfaceLinkStateChanged(STACKED_IFACE, true); 403 mLooper.dispatchNext(); 404 405 // Interface removed notification arrives after stopClatd() takes effect: no-op. 406 nat.interfaceRemoved(STACKED_IFACE); 407 mLooper.dispatchNext(); 408 409 assertIdle(nat); 410 411 verifyNoMoreInteractions(mNetd, mNms, mConnectivity); 412 } 413 414 @Test 415 public void testStopDueToDisconnectBeforeClatdStarts() throws Exception { 416 checkStopBeforeClatdStarts(true); 417 } 418 419 @Test 420 public void testStopDueToIpv4AddrBeforeClatdStarts() throws Exception { 421 checkStopBeforeClatdStarts(false); 422 } 423 424 private void checkStopAndClatdNeverStarts(boolean dueToDisconnect) throws Exception { 425 Nat464Xlat nat = makeNat464Xlat(); 426 427 mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64")); 428 429 nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); 430 431 nat.start(); 432 433 verify(mNms).registerObserver(eq(nat)); 434 verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); 435 436 // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...) 437 makeClatUnnecessary(dueToDisconnect); 438 nat.stop(); 439 440 verify(mNetd).clatdStop(eq(BASE_IFACE)); 441 verify(mNms).unregisterObserver(eq(nat)); 442 verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); 443 assertIdle(nat); 444 445 verifyNoMoreInteractions(mNetd, mNms, mConnectivity); 446 } 447 448 @Test 449 public void testStopDueToDisconnectAndClatdNeverStarts() throws Exception { 450 checkStopAndClatdNeverStarts(true); 451 } 452 453 @Test 454 public void testStopDueToIpv4AddressAndClatdNeverStarts() throws Exception { 455 checkStopAndClatdNeverStarts(false); 456 } 457 458 @Test 459 public void testNat64PrefixPreference() throws Exception { 460 final IpPrefix prefixFromDns = new IpPrefix(NAT64_PREFIX); 461 final IpPrefix prefixFromRa = new IpPrefix(OTHER_NAT64_PREFIX); 462 463 Nat464Xlat nat = makeNat464Xlat(); 464 465 final LinkProperties emptyLp = new LinkProperties(); 466 LinkProperties fixedupLp; 467 468 fixedupLp = new LinkProperties(); 469 nat.setNat64PrefixFromDns(prefixFromDns); 470 nat.fixupLinkProperties(emptyLp, fixedupLp); 471 assertEquals(prefixFromDns, fixedupLp.getNat64Prefix()); 472 473 fixedupLp = new LinkProperties(); 474 nat.setNat64PrefixFromRa(prefixFromRa); 475 nat.fixupLinkProperties(emptyLp, fixedupLp); 476 assertEquals(prefixFromRa, fixedupLp.getNat64Prefix()); 477 478 fixedupLp = new LinkProperties(); 479 nat.setNat64PrefixFromRa(null); 480 nat.fixupLinkProperties(emptyLp, fixedupLp); 481 assertEquals(prefixFromDns, fixedupLp.getNat64Prefix()); 482 483 fixedupLp = new LinkProperties(); 484 nat.setNat64PrefixFromRa(prefixFromRa); 485 nat.fixupLinkProperties(emptyLp, fixedupLp); 486 assertEquals(prefixFromRa, fixedupLp.getNat64Prefix()); 487 488 fixedupLp = new LinkProperties(); 489 nat.setNat64PrefixFromDns(null); 490 nat.fixupLinkProperties(emptyLp, fixedupLp); 491 assertEquals(prefixFromRa, fixedupLp.getNat64Prefix()); 492 493 fixedupLp = new LinkProperties(); 494 nat.setNat64PrefixFromRa(null); 495 nat.fixupLinkProperties(emptyLp, fixedupLp); 496 assertEquals(null, fixedupLp.getNat64Prefix()); 497 } 498 499 static void assertIdle(Nat464Xlat nat) { 500 assertTrue("Nat464Xlat was not IDLE", !nat.isStarted()); 501 } 502 503 static void assertRunning(Nat464Xlat nat) { 504 assertTrue("Nat464Xlat was not RUNNING", nat.isRunning()); 505 } 506 } 507