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