1 /* 2 * Copyright (C) 2024 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 package android.net.thread.utils; 17 18 import static android.Manifest.permission.MANAGE_TEST_NETWORKS; 19 import static android.net.InetAddresses.parseNumericAddress; 20 import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; 21 import static android.net.NetworkCapabilities.TRANSPORT_TEST; 22 import static android.system.OsConstants.AF_INET6; 23 import static android.system.OsConstants.IPPROTO_UDP; 24 import static android.system.OsConstants.SOCK_DGRAM; 25 26 import static com.android.testutils.RecorderCallback.CallbackEntry.LINK_PROPERTIES_CHANGED; 27 import static com.android.testutils.TestPermissionUtil.runAsShell; 28 29 import android.content.Context; 30 import android.net.ConnectivityManager; 31 import android.net.LinkAddress; 32 import android.net.LinkProperties; 33 import android.net.Network; 34 import android.net.NetworkAgentConfig; 35 import android.net.NetworkCapabilities; 36 import android.net.NetworkRequest; 37 import android.net.TestNetworkInterface; 38 import android.net.TestNetworkManager; 39 import android.net.TestNetworkSpecifier; 40 import android.os.Looper; 41 import android.system.ErrnoException; 42 import android.system.Os; 43 44 import com.android.compatibility.common.util.PollingCheck; 45 import com.android.testutils.TestableNetworkAgent; 46 import com.android.testutils.TestableNetworkCallback; 47 48 import java.io.FileDescriptor; 49 import java.io.IOException; 50 import java.net.InterfaceAddress; 51 import java.net.NetworkInterface; 52 import java.net.SocketException; 53 import java.time.Duration; 54 import java.util.ArrayList; 55 import java.util.Collections; 56 import java.util.List; 57 58 /** A class that can create/destroy a test network based on TAP interface. */ 59 public final class TapTestNetworkTracker { 60 private static final Duration TIMEOUT = Duration.ofSeconds(2); 61 private final Context mContext; 62 private final Looper mLooper; 63 private TestNetworkInterface mInterface; 64 private TestableNetworkAgent mAgent; 65 private Network mNetwork; 66 private final TestableNetworkCallback mNetworkCallback; 67 private final ConnectivityManager mConnectivityManager; 68 69 /** 70 * Constructs a {@link TapTestNetworkTracker}. 71 * 72 * <p>It creates a TAP interface (e.g. testtap0) and registers a test network using that 73 * interface. It also requests the test network by {@link ConnectivityManager#requestNetwork} so 74 * the test network won't be automatically turned down by {@link 75 * com.android.server.ConnectivityService}. 76 */ TapTestNetworkTracker(Context context, Looper looper)77 public TapTestNetworkTracker(Context context, Looper looper) { 78 mContext = context; 79 mLooper = looper; 80 mConnectivityManager = mContext.getSystemService(ConnectivityManager.class); 81 mNetworkCallback = new TestableNetworkCallback(); 82 runAsShell(MANAGE_TEST_NETWORKS, this::setUpTestNetwork); 83 } 84 85 /** Tears down the test network. */ tearDown()86 public void tearDown() { 87 runAsShell(MANAGE_TEST_NETWORKS, this::tearDownTestNetwork); 88 } 89 90 /** Returns the interface name of the test network. */ getInterfaceName()91 public String getInterfaceName() { 92 return mInterface.getInterfaceName(); 93 } 94 95 /** Returns the {@link android.net.Network} of the test network. */ getNetwork()96 public Network getNetwork() { 97 return mNetwork; 98 } 99 setUpTestNetwork()100 private void setUpTestNetwork() throws Exception { 101 mInterface = mContext.getSystemService(TestNetworkManager.class).createTapInterface(); 102 103 mConnectivityManager.requestNetwork(newNetworkRequest(), mNetworkCallback); 104 105 LinkProperties lp = new LinkProperties(); 106 lp.setInterfaceName(getInterfaceName()); 107 mAgent = 108 new TestableNetworkAgent( 109 mContext, 110 mLooper, 111 newNetworkCapabilities(), 112 lp, 113 new NetworkAgentConfig.Builder().build()); 114 mNetwork = mAgent.register(); 115 mAgent.markConnected(); 116 117 PollingCheck.check( 118 "No usable address on interface", 119 TIMEOUT.toMillis(), 120 () -> hasUsableAddress(mNetwork, getInterfaceName())); 121 122 lp.setLinkAddresses(makeLinkAddresses()); 123 mAgent.sendLinkProperties(lp); 124 mNetworkCallback.eventuallyExpect( 125 LINK_PROPERTIES_CHANGED, 126 TIMEOUT.toMillis(), 127 l -> !l.getLp().getAddresses().isEmpty()); 128 } 129 tearDownTestNetwork()130 private void tearDownTestNetwork() throws IOException { 131 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 132 mAgent.unregister(); 133 mInterface.getFileDescriptor().close(); 134 mAgent.waitForIdle(TIMEOUT.toMillis()); 135 } 136 newNetworkRequest()137 private NetworkRequest newNetworkRequest() { 138 return new NetworkRequest.Builder() 139 .removeCapability(NET_CAPABILITY_TRUSTED) 140 .addTransportType(TRANSPORT_TEST) 141 .setNetworkSpecifier(new TestNetworkSpecifier(getInterfaceName())) 142 .build(); 143 } 144 newNetworkCapabilities()145 private NetworkCapabilities newNetworkCapabilities() { 146 return new NetworkCapabilities() 147 .removeCapability(NET_CAPABILITY_TRUSTED) 148 .addTransportType(TRANSPORT_TEST) 149 .setNetworkSpecifier(new TestNetworkSpecifier(getInterfaceName())); 150 } 151 makeLinkAddresses()152 private List<LinkAddress> makeLinkAddresses() { 153 List<LinkAddress> linkAddresses = new ArrayList<>(); 154 List<InterfaceAddress> interfaceAddresses = Collections.emptyList(); 155 156 try { 157 interfaceAddresses = 158 NetworkInterface.getByName(getInterfaceName()).getInterfaceAddresses(); 159 } catch (SocketException ignored) { 160 // Ignore failures when getting the addresses. 161 } 162 163 for (InterfaceAddress address : interfaceAddresses) { 164 linkAddresses.add( 165 new LinkAddress(address.getAddress(), address.getNetworkPrefixLength())); 166 } 167 168 return linkAddresses; 169 } 170 hasUsableAddress(Network network, String interfaceName)171 private static boolean hasUsableAddress(Network network, String interfaceName) { 172 try { 173 if (NetworkInterface.getByName(interfaceName).getInterfaceAddresses().isEmpty()) { 174 return false; 175 } 176 } catch (SocketException e) { 177 return false; 178 } 179 // Check if the link-local address can be used. Address flags are not available without 180 // elevated permissions, so check that bindSocket works. 181 try { 182 FileDescriptor sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); 183 network.bindSocket(sock); 184 Os.connect(sock, parseNumericAddress("ff02::fb%" + interfaceName), 12345); 185 Os.close(sock); 186 } catch (ErrnoException | IOException e) { 187 return false; 188 } 189 return true; 190 } 191 } 192