1 /*
2  * Copyright (C) 2020 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 android.net;
18 
19 import static android.Manifest.permission.ACCESS_NETWORK_STATE;
20 import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
21 import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
22 import static android.Manifest.permission.NETWORK_SETTINGS;
23 import static android.Manifest.permission.TETHER_PRIVILEGED;
24 import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
25 import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
26 import static android.net.TetheringManager.TETHERING_ETHERNET;
27 import static android.system.OsConstants.IPPROTO_ICMPV6;
28 
29 import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
30 import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
31 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
32 
33 import static org.junit.Assert.assertEquals;
34 import static org.junit.Assert.assertNotNull;
35 import static org.junit.Assert.assertNull;
36 import static org.junit.Assert.assertTrue;
37 import static org.junit.Assert.fail;
38 import static org.junit.Assume.assumeFalse;
39 import static org.junit.Assume.assumeTrue;
40 
41 import android.app.UiAutomation;
42 import android.content.Context;
43 import android.net.EthernetManager.TetheredInterfaceCallback;
44 import android.net.EthernetManager.TetheredInterfaceRequest;
45 import android.net.TetheringManager.StartTetheringCallback;
46 import android.net.TetheringManager.TetheringEventCallback;
47 import android.net.TetheringManager.TetheringRequest;
48 import android.net.dhcp.DhcpAckPacket;
49 import android.net.dhcp.DhcpOfferPacket;
50 import android.net.dhcp.DhcpPacket;
51 import android.os.Handler;
52 import android.os.HandlerThread;
53 import android.os.SystemClock;
54 import android.os.SystemProperties;
55 import android.system.Os;
56 import android.util.Log;
57 
58 import androidx.test.InstrumentationRegistry;
59 import androidx.test.filters.MediumTest;
60 import androidx.test.runner.AndroidJUnit4;
61 
62 import com.android.net.module.util.Struct;
63 import com.android.net.module.util.structs.EthernetHeader;
64 import com.android.net.module.util.structs.Icmpv6Header;
65 import com.android.net.module.util.structs.Ipv6Header;
66 import com.android.testutils.HandlerUtils;
67 import com.android.testutils.TapPacketReader;
68 
69 import org.junit.After;
70 import org.junit.Before;
71 import org.junit.Test;
72 import org.junit.runner.RunWith;
73 
74 import java.io.FileDescriptor;
75 import java.net.Inet4Address;
76 import java.net.InetAddress;
77 import java.net.InterfaceAddress;
78 import java.net.NetworkInterface;
79 import java.net.SocketException;
80 import java.nio.ByteBuffer;
81 import java.util.Collection;
82 import java.util.List;
83 import java.util.Random;
84 import java.util.Set;
85 import java.util.concurrent.CompletableFuture;
86 import java.util.concurrent.CountDownLatch;
87 import java.util.concurrent.TimeUnit;
88 import java.util.concurrent.TimeoutException;
89 
90 @RunWith(AndroidJUnit4.class)
91 @MediumTest
92 public class EthernetTetheringTest {
93 
94     private static final String TAG = EthernetTetheringTest.class.getSimpleName();
95     private static final int TIMEOUT_MS = 5000;
96     private static final int PACKET_READ_TIMEOUT_MS = 100;
97     private static final int DHCP_DISCOVER_ATTEMPTS = 10;
98     private static final byte[] DHCP_REQUESTED_PARAMS = new byte[] {
99             DhcpPacket.DHCP_SUBNET_MASK,
100             DhcpPacket.DHCP_ROUTER,
101             DhcpPacket.DHCP_DNS_SERVER,
102             DhcpPacket.DHCP_LEASE_TIME,
103     };
104     private static final String DHCP_HOSTNAME = "testhostname";
105 
106     private final Context mContext = InstrumentationRegistry.getContext();
107     private final EthernetManager mEm = mContext.getSystemService(EthernetManager.class);
108     private final TetheringManager mTm = mContext.getSystemService(TetheringManager.class);
109 
110     private TestNetworkInterface mTestIface;
111     private HandlerThread mHandlerThread;
112     private Handler mHandler;
113     private TapPacketReader mTapPacketReader;
114 
115     private TetheredInterfaceRequester mTetheredInterfaceRequester;
116     private MyTetheringEventCallback mTetheringEventCallback;
117 
118     private UiAutomation mUiAutomation =
119             InstrumentationRegistry.getInstrumentation().getUiAutomation();
120     private boolean mRunTests;
121 
122     @Before
setUp()123     public void setUp() throws Exception {
124         // Needed to create a TestNetworkInterface, to call requestTetheredInterface, and to receive
125         // tethered client callbacks. The restricted networks permission is needed to ensure that
126         // EthernetManager#isAvailable will correctly return true on devices where Ethernet is
127         // marked restricted, like cuttlefish.
128         mUiAutomation.adoptShellPermissionIdentity(
129                 MANAGE_TEST_NETWORKS, NETWORK_SETTINGS, TETHER_PRIVILEGED, ACCESS_NETWORK_STATE,
130                 CONNECTIVITY_USE_RESTRICTED_NETWORKS);
131         mRunTests = mTm.isTetheringSupported() && mEm != null;
132         assumeTrue(mRunTests);
133 
134         mHandlerThread = new HandlerThread(getClass().getSimpleName());
135         mHandlerThread.start();
136         mHandler = new Handler(mHandlerThread.getLooper());
137         mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm);
138     }
139 
cleanUp()140     private void cleanUp() throws Exception {
141         mTm.stopTethering(TETHERING_ETHERNET);
142         if (mTetheringEventCallback != null) {
143             mTetheringEventCallback.awaitInterfaceUntethered();
144             mTetheringEventCallback.unregister();
145             mTetheringEventCallback = null;
146         }
147         if (mTapPacketReader != null) {
148             TapPacketReader reader = mTapPacketReader;
149             mHandler.post(() -> reader.stop());
150             mTapPacketReader = null;
151         }
152         mHandlerThread.quitSafely();
153         mTetheredInterfaceRequester.release();
154         mEm.setIncludeTestInterfaces(false);
155         maybeDeleteTestInterface();
156     }
157 
158     @After
tearDown()159     public void tearDown() throws Exception {
160         try {
161             if (mRunTests) cleanUp();
162         } finally {
163             mUiAutomation.dropShellPermissionIdentity();
164         }
165     }
166 
167     @Test
testVirtualEthernetAlreadyExists()168     public void testVirtualEthernetAlreadyExists() throws Exception {
169         // This test requires manipulating packets. Skip if there is a physical Ethernet connected.
170         assumeFalse(mEm.isAvailable());
171 
172         mTestIface = createTestInterface();
173         // This must be done now because as soon as setIncludeTestInterfaces(true) is called, the
174         // interface will be placed in client mode, which will delete the link-local address.
175         // At that point NetworkInterface.getByName() will cease to work on the interface, because
176         // starting in R NetworkInterface can no longer see interfaces without IP addresses.
177         int mtu = getMTU(mTestIface);
178 
179         Log.d(TAG, "Including test interfaces");
180         mEm.setIncludeTestInterfaces(true);
181 
182         final String iface = mTetheredInterfaceRequester.getInterface();
183         assertEquals("TetheredInterfaceCallback for unexpected interface",
184                 mTestIface.getInterfaceName(), iface);
185 
186         checkVirtualEthernet(mTestIface, mtu);
187     }
188 
189     @Test
testVirtualEthernet()190     public void testVirtualEthernet() throws Exception {
191         // This test requires manipulating packets. Skip if there is a physical Ethernet connected.
192         assumeFalse(mEm.isAvailable());
193 
194         CompletableFuture<String> futureIface = mTetheredInterfaceRequester.requestInterface();
195 
196         mEm.setIncludeTestInterfaces(true);
197 
198         mTestIface = createTestInterface();
199 
200         final String iface = futureIface.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
201         assertEquals("TetheredInterfaceCallback for unexpected interface",
202                 mTestIface.getInterfaceName(), iface);
203 
204         checkVirtualEthernet(mTestIface, getMTU(mTestIface));
205     }
206 
207     @Test
testStaticIpv4()208     public void testStaticIpv4() throws Exception {
209         assumeFalse(mEm.isAvailable());
210 
211         mEm.setIncludeTestInterfaces(true);
212 
213         mTestIface = createTestInterface();
214 
215         final String iface = mTetheredInterfaceRequester.getInterface();
216         assertEquals("TetheredInterfaceCallback for unexpected interface",
217                 mTestIface.getInterfaceName(), iface);
218 
219         assertInvalidStaticIpv4Request(iface, null, null);
220         assertInvalidStaticIpv4Request(iface, "2001:db8::1/64", "2001:db8:2::/64");
221         assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", "2001:db8:2::/28");
222         assertInvalidStaticIpv4Request(iface, "2001:db8:2::/28", "192.0.2.2/28");
223         assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", null);
224         assertInvalidStaticIpv4Request(iface, null, "192.0.2.2/28");
225         assertInvalidStaticIpv4Request(iface, "192.0.2.3/27", "192.0.2.2/28");
226 
227         final String localAddr = "192.0.2.3/28";
228         final String clientAddr = "192.0.2.2/28";
229         mTetheringEventCallback = enableEthernetTethering(iface,
230                 requestWithStaticIpv4(localAddr, clientAddr));
231 
232         mTetheringEventCallback.awaitInterfaceTethered();
233         assertInterfaceHasIpAddress(iface, localAddr);
234 
235         byte[] client1 = MacAddress.fromString("1:2:3:4:5:6").toByteArray();
236         byte[] client2 = MacAddress.fromString("a:b:c:d:e:f").toByteArray();
237 
238         FileDescriptor fd = mTestIface.getFileDescriptor().getFileDescriptor();
239         mTapPacketReader = makePacketReader(fd, getMTU(mTestIface));
240         DhcpResults dhcpResults = runDhcp(fd, client1);
241         assertEquals(new LinkAddress(clientAddr), dhcpResults.ipAddress);
242 
243         try {
244             runDhcp(fd, client2);
245             fail("Only one client should get an IP address");
246         } catch (TimeoutException expected) { }
247 
248     }
249 
isRouterAdvertisement(byte[] pkt)250     private static boolean isRouterAdvertisement(byte[] pkt) {
251         if (pkt == null) return false;
252 
253         ByteBuffer buf = ByteBuffer.wrap(pkt);
254 
255         final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
256         if (ethHdr.etherType != ETHER_TYPE_IPV6) return false;
257 
258         final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
259         if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return false;
260 
261         final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
262         return icmpv6Hdr.type == (short) ICMPV6_ROUTER_ADVERTISEMENT;
263     }
264 
expectRouterAdvertisement(TapPacketReader reader, String iface, long timeoutMs)265     private static void expectRouterAdvertisement(TapPacketReader reader, String iface,
266             long timeoutMs) {
267         final long deadline = SystemClock.uptimeMillis() + timeoutMs;
268         do {
269             byte[] pkt = reader.popPacket(timeoutMs);
270             if (isRouterAdvertisement(pkt)) return;
271             timeoutMs = deadline - SystemClock.uptimeMillis();
272         } while (timeoutMs > 0);
273         fail("Did not receive router advertisement on " + iface + " after "
274                 +  timeoutMs + "ms idle");
275     }
276 
expectLocalOnlyAddresses(String iface)277     private static void expectLocalOnlyAddresses(String iface) throws Exception {
278         final List<InterfaceAddress> interfaceAddresses =
279                 NetworkInterface.getByName(iface).getInterfaceAddresses();
280 
281         boolean foundIpv6Ula = false;
282         for (InterfaceAddress ia : interfaceAddresses) {
283             final InetAddress addr = ia.getAddress();
284             if (isIPv6ULA(addr)) {
285                 foundIpv6Ula = true;
286             }
287             final int prefixlen = ia.getNetworkPrefixLength();
288             final LinkAddress la = new LinkAddress(addr, prefixlen);
289             if (la.isIpv6() && la.isGlobalPreferred()) {
290                 fail("Found global IPv6 address on local-only interface: " + interfaceAddresses);
291             }
292         }
293 
294         assertTrue("Did not find IPv6 ULA on local-only interface " + iface,
295                 foundIpv6Ula);
296     }
297 
298     @Test
testLocalOnlyTethering()299     public void testLocalOnlyTethering() throws Exception {
300         assumeFalse(mEm.isAvailable());
301 
302         mEm.setIncludeTestInterfaces(true);
303 
304         mTestIface = createTestInterface();
305 
306         final String iface = mTetheredInterfaceRequester.getInterface();
307         assertEquals("TetheredInterfaceCallback for unexpected interface",
308                 mTestIface.getInterfaceName(), iface);
309 
310         final TetheringRequest request = new TetheringRequest.Builder(TETHERING_ETHERNET)
311                 .setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build();
312         mTetheringEventCallback = enableEthernetTethering(iface, request);
313         mTetheringEventCallback.awaitInterfaceLocalOnly();
314 
315         // makePacketReader only works after tethering is started, because until then the interface
316         // does not have an IP address, and unprivileged apps cannot see interfaces without IP
317         // addresses. This shouldn't be flaky because the TAP interface will buffer all packets even
318         // before the reader is started.
319         FileDescriptor fd = mTestIface.getFileDescriptor().getFileDescriptor();
320         mTapPacketReader = makePacketReader(fd, getMTU(mTestIface));
321 
322         expectRouterAdvertisement(mTapPacketReader, iface, 2000 /* timeoutMs */);
323         expectLocalOnlyAddresses(iface);
324     }
325 
isAdbOverNetwork()326     private boolean isAdbOverNetwork() {
327         // If adb TCP port opened, this test may running by adb over network.
328         return (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1)
329                 || (SystemProperties.getInt("service.adb.tcp.port", -1) > -1);
330     }
331 
332     @Test
testPhysicalEthernet()333     public void testPhysicalEthernet() throws Exception {
334         assumeTrue(mEm.isAvailable());
335         // Do not run this test if adb is over network and ethernet is connected.
336         // It is likely the adb run over ethernet, the adb would break when ethernet is switching
337         // from client mode to server mode. See b/160389275.
338         assumeFalse(isAdbOverNetwork());
339 
340         // Get an interface to use.
341         final String iface = mTetheredInterfaceRequester.getInterface();
342 
343         // Enable Ethernet tethering and check that it starts.
344         mTetheringEventCallback = enableEthernetTethering(iface);
345 
346         // There is nothing more we can do on a physical interface without connecting an actual
347         // client, which is not possible in this test.
348     }
349 
350     private static final class MyTetheringEventCallback implements TetheringEventCallback {
351         private final TetheringManager mTm;
352         private final CountDownLatch mTetheringStartedLatch = new CountDownLatch(1);
353         private final CountDownLatch mTetheringStoppedLatch = new CountDownLatch(1);
354         private final CountDownLatch mLocalOnlyStartedLatch = new CountDownLatch(1);
355         private final CountDownLatch mLocalOnlyStoppedLatch = new CountDownLatch(1);
356         private final CountDownLatch mClientConnectedLatch = new CountDownLatch(1);
357         private final TetheringInterface mIface;
358 
359         private volatile boolean mInterfaceWasTethered = false;
360         private volatile boolean mInterfaceWasLocalOnly = false;
361         private volatile boolean mUnregistered = false;
362         private volatile Collection<TetheredClient> mClients = null;
363 
MyTetheringEventCallback(TetheringManager tm, String iface)364         MyTetheringEventCallback(TetheringManager tm, String iface) {
365             mTm = tm;
366             mIface = new TetheringInterface(TETHERING_ETHERNET, iface);
367         }
368 
unregister()369         public void unregister() {
370             mTm.unregisterTetheringEventCallback(this);
371             mUnregistered = true;
372         }
373         @Override
onTetheredInterfacesChanged(List<String> interfaces)374         public void onTetheredInterfacesChanged(List<String> interfaces) {
375             fail("Should only call callback that takes a Set<TetheringInterface>");
376         }
377 
378         @Override
onTetheredInterfacesChanged(Set<TetheringInterface> interfaces)379         public void onTetheredInterfacesChanged(Set<TetheringInterface> interfaces) {
380             // Ignore stale callbacks registered by previous test cases.
381             if (mUnregistered) return;
382 
383             if (!mInterfaceWasTethered && interfaces.contains(mIface)) {
384                 // This interface is being tethered for the first time.
385                 Log.d(TAG, "Tethering started: " + interfaces);
386                 mInterfaceWasTethered = true;
387                 mTetheringStartedLatch.countDown();
388             } else if (mInterfaceWasTethered && !interfaces.contains(mIface)) {
389                 Log.d(TAG, "Tethering stopped: " + interfaces);
390                 mTetheringStoppedLatch.countDown();
391             }
392         }
393 
394         @Override
onLocalOnlyInterfacesChanged(List<String> interfaces)395         public void onLocalOnlyInterfacesChanged(List<String> interfaces) {
396             fail("Should only call callback that takes a Set<TetheringInterface>");
397         }
398 
399         @Override
onLocalOnlyInterfacesChanged(Set<TetheringInterface> interfaces)400         public void onLocalOnlyInterfacesChanged(Set<TetheringInterface> interfaces) {
401             // Ignore stale callbacks registered by previous test cases.
402             if (mUnregistered) return;
403 
404             if (!mInterfaceWasLocalOnly && interfaces.contains(mIface)) {
405                 // This interface is being put into local-only mode for the first time.
406                 Log.d(TAG, "Local-only started: " + interfaces);
407                 mInterfaceWasLocalOnly = true;
408                 mLocalOnlyStartedLatch.countDown();
409             } else if (mInterfaceWasLocalOnly && !interfaces.contains(mIface)) {
410                 Log.d(TAG, "Local-only stopped: " + interfaces);
411                 mLocalOnlyStoppedLatch.countDown();
412             }
413         }
414 
awaitInterfaceTethered()415         public void awaitInterfaceTethered() throws Exception {
416             assertTrue("Ethernet not tethered after " + TIMEOUT_MS + "ms",
417                     mTetheringStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
418         }
419 
awaitInterfaceLocalOnly()420         public void awaitInterfaceLocalOnly() throws Exception {
421             assertTrue("Ethernet not local-only after " + TIMEOUT_MS + "ms",
422                     mLocalOnlyStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
423         }
424 
awaitInterfaceUntethered()425         public void awaitInterfaceUntethered() throws Exception {
426             // Don't block teardown if the interface was never tethered.
427             // This is racy because the interface might become tethered right after this check, but
428             // that can only happen in tearDown if startTethering timed out, which likely means
429             // the test has already failed.
430             if (!mInterfaceWasTethered && !mInterfaceWasLocalOnly) return;
431 
432             if (mInterfaceWasTethered) {
433                 assertTrue(mIface + " not untethered after " + TIMEOUT_MS + "ms",
434                         mTetheringStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
435             } else if (mInterfaceWasLocalOnly) {
436                 assertTrue(mIface + " not untethered after " + TIMEOUT_MS + "ms",
437                         mLocalOnlyStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
438             } else {
439                 fail(mIface + " cannot be both tethered and local-only. Update this test class.");
440             }
441         }
442 
443         @Override
onError(String ifName, int error)444         public void onError(String ifName, int error) {
445             // Ignore stale callbacks registered by previous test cases.
446             if (mUnregistered) return;
447 
448             fail("TetheringEventCallback got error:" + error + " on iface " + ifName);
449         }
450 
451         @Override
onClientsChanged(Collection<TetheredClient> clients)452         public void onClientsChanged(Collection<TetheredClient> clients) {
453             // Ignore stale callbacks registered by previous test cases.
454             if (mUnregistered) return;
455 
456             Log.d(TAG, "Got clients changed: " + clients);
457             mClients = clients;
458             if (clients.size() > 0) {
459                 mClientConnectedLatch.countDown();
460             }
461         }
462 
awaitClientConnected()463         public Collection<TetheredClient> awaitClientConnected() throws Exception {
464             assertTrue("Did not receive client connected callback after " + TIMEOUT_MS + "ms",
465                     mClientConnectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
466             return mClients;
467         }
468     }
469 
enableEthernetTethering(String iface, TetheringRequest request)470     private MyTetheringEventCallback enableEthernetTethering(String iface,
471             TetheringRequest request) throws Exception {
472         MyTetheringEventCallback callback = new MyTetheringEventCallback(mTm, iface);
473         mTm.registerTetheringEventCallback(mHandler::post, callback);
474 
475         StartTetheringCallback startTetheringCallback = new StartTetheringCallback() {
476             @Override
477             public void onTetheringFailed(int resultCode) {
478                 fail("Unexpectedly got onTetheringFailed");
479             }
480         };
481         Log.d(TAG, "Starting Ethernet tethering");
482         mTm.startTethering(request, mHandler::post /* executor */,  startTetheringCallback);
483 
484         final int connectivityType = request.getConnectivityScope();
485         switch (connectivityType) {
486             case CONNECTIVITY_SCOPE_GLOBAL:
487                 callback.awaitInterfaceTethered();
488                 break;
489             case CONNECTIVITY_SCOPE_LOCAL:
490                 callback.awaitInterfaceLocalOnly();
491                 break;
492             default:
493                 fail("Unexpected connectivity type requested: " + connectivityType);
494         }
495 
496         return callback;
497     }
498 
enableEthernetTethering(String iface)499     private MyTetheringEventCallback enableEthernetTethering(String iface) throws Exception {
500         return enableEthernetTethering(iface,
501                 new TetheringRequest.Builder(TETHERING_ETHERNET)
502                 .setShouldShowEntitlementUi(false).build());
503     }
504 
getMTU(TestNetworkInterface iface)505     private int getMTU(TestNetworkInterface iface) throws SocketException {
506         NetworkInterface nif = NetworkInterface.getByName(iface.getInterfaceName());
507         assertNotNull("Can't get NetworkInterface object for " + iface.getInterfaceName(), nif);
508         return nif.getMTU();
509     }
510 
makePacketReader(FileDescriptor fd, int mtu)511     private TapPacketReader makePacketReader(FileDescriptor fd, int mtu) {
512         final TapPacketReader reader = new TapPacketReader(mHandler, fd, mtu);
513         mHandler.post(() -> reader.start());
514         HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
515         return reader;
516     }
517 
checkVirtualEthernet(TestNetworkInterface iface, int mtu)518     private void checkVirtualEthernet(TestNetworkInterface iface, int mtu) throws Exception {
519         FileDescriptor fd = iface.getFileDescriptor().getFileDescriptor();
520         mTapPacketReader = makePacketReader(fd, mtu);
521         mTetheringEventCallback = enableEthernetTethering(iface.getInterfaceName());
522         checkTetheredClientCallbacks(fd);
523     }
524 
runDhcp(FileDescriptor fd, byte[] clientMacAddr)525     private DhcpResults runDhcp(FileDescriptor fd, byte[] clientMacAddr) throws Exception {
526         // We have to retransmit DHCP requests because IpServer declares itself to be ready before
527         // its DhcpServer is actually started. TODO: fix this race and remove this loop.
528         DhcpPacket offerPacket = null;
529         for (int i = 0; i < DHCP_DISCOVER_ATTEMPTS; i++) {
530             Log.d(TAG, "Sending DHCP discover");
531             sendDhcpDiscover(fd, clientMacAddr);
532             offerPacket = getNextDhcpPacket();
533             if (offerPacket instanceof DhcpOfferPacket) break;
534         }
535         if (!(offerPacket instanceof DhcpOfferPacket)) {
536             throw new TimeoutException("No DHCPOFFER received on interface within timeout");
537         }
538 
539         sendDhcpRequest(fd, offerPacket, clientMacAddr);
540         DhcpPacket ackPacket = getNextDhcpPacket();
541         if (!(ackPacket instanceof DhcpAckPacket)) {
542             throw new TimeoutException("No DHCPACK received on interface within timeout");
543         }
544 
545         return ackPacket.toDhcpResults();
546     }
547 
checkTetheredClientCallbacks(FileDescriptor fd)548     private void checkTetheredClientCallbacks(FileDescriptor fd) throws Exception {
549         // Create a fake client.
550         byte[] clientMacAddr = new byte[6];
551         new Random().nextBytes(clientMacAddr);
552 
553         DhcpResults dhcpResults = runDhcp(fd, clientMacAddr);
554 
555         final Collection<TetheredClient> clients = mTetheringEventCallback.awaitClientConnected();
556         assertEquals(1, clients.size());
557         final TetheredClient client = clients.iterator().next();
558 
559         // Check the MAC address.
560         assertEquals(MacAddress.fromBytes(clientMacAddr), client.getMacAddress());
561         assertEquals(TETHERING_ETHERNET, client.getTetheringType());
562 
563         // Check the hostname.
564         assertEquals(1, client.getAddresses().size());
565         TetheredClient.AddressInfo info = client.getAddresses().get(0);
566         assertEquals(DHCP_HOSTNAME, info.getHostname());
567 
568         // Check the address is the one that was handed out in the DHCP ACK.
569         assertLinkAddressMatches(dhcpResults.ipAddress, info.getAddress());
570 
571         // Check that the lifetime is correct +/- 10s.
572         final long now = SystemClock.elapsedRealtime();
573         final long actualLeaseDuration = (info.getAddress().getExpirationTime() - now) / 1000;
574         final String msg = String.format("IP address should have lifetime of %d, got %d",
575                 dhcpResults.leaseDuration, actualLeaseDuration);
576         assertTrue(msg, Math.abs(dhcpResults.leaseDuration - actualLeaseDuration) < 10);
577     }
578 
getNextDhcpPacket()579     private DhcpPacket getNextDhcpPacket() throws ParseException {
580         byte[] packet;
581         while ((packet = mTapPacketReader.popPacket(PACKET_READ_TIMEOUT_MS)) != null) {
582             try {
583                 return DhcpPacket.decodeFullPacket(packet, packet.length, DhcpPacket.ENCAP_L2);
584             } catch (DhcpPacket.ParseException e) {
585                 // Not a DHCP packet. Continue.
586             }
587         }
588         return null;
589     }
590 
591     private static final class TetheredInterfaceRequester implements TetheredInterfaceCallback {
592         private final Handler mHandler;
593         private final EthernetManager mEm;
594 
595         private TetheredInterfaceRequest mRequest;
596         private final CompletableFuture<String> mFuture = new CompletableFuture<>();
597 
TetheredInterfaceRequester(Handler handler, EthernetManager em)598         TetheredInterfaceRequester(Handler handler, EthernetManager em) {
599             mHandler = handler;
600             mEm = em;
601         }
602 
603         @Override
onAvailable(String iface)604         public void onAvailable(String iface) {
605             Log.d(TAG, "Ethernet interface available: " + iface);
606             mFuture.complete(iface);
607         }
608 
609         @Override
onUnavailable()610         public void onUnavailable() {
611             mFuture.completeExceptionally(new IllegalStateException("onUnavailable received"));
612         }
613 
requestInterface()614         public CompletableFuture<String> requestInterface() {
615             assertNull("BUG: more than one tethered interface request", mRequest);
616             Log.d(TAG, "Requesting tethered interface");
617             mRequest = mEm.requestTetheredInterface(mHandler::post, this);
618             return mFuture;
619         }
620 
getInterface()621         public String getInterface() throws Exception {
622             return requestInterface().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
623         }
624 
release()625         public void release() {
626             if (mRequest != null) {
627                 mFuture.obtrudeException(new IllegalStateException("Request already released"));
628                 mRequest.release();
629                 mRequest = null;
630             }
631         }
632     }
633 
sendDhcpDiscover(FileDescriptor fd, byte[] macAddress)634     private void sendDhcpDiscover(FileDescriptor fd, byte[] macAddress) throws Exception {
635         ByteBuffer packet = DhcpPacket.buildDiscoverPacket(DhcpPacket.ENCAP_L2,
636                 new Random().nextInt() /* transactionId */, (short) 0 /* secs */,
637                 macAddress,  false /* unicast */, DHCP_REQUESTED_PARAMS,
638                 false /* rapid commit */,  DHCP_HOSTNAME);
639         sendPacket(fd, packet);
640     }
641 
sendDhcpRequest(FileDescriptor fd, DhcpPacket offerPacket, byte[] macAddress)642     private void sendDhcpRequest(FileDescriptor fd, DhcpPacket offerPacket, byte[] macAddress)
643             throws Exception {
644         DhcpResults results = offerPacket.toDhcpResults();
645         Inet4Address clientIp = (Inet4Address) results.ipAddress.getAddress();
646         Inet4Address serverIdentifier = results.serverAddress;
647         ByteBuffer packet = DhcpPacket.buildRequestPacket(DhcpPacket.ENCAP_L2,
648                 0 /* transactionId */, (short) 0 /* secs */, DhcpPacket.INADDR_ANY /* clientIp */,
649                 false /* broadcast */, macAddress, clientIp /* requestedIpAddress */,
650                 serverIdentifier, DHCP_REQUESTED_PARAMS, DHCP_HOSTNAME);
651         sendPacket(fd, packet);
652     }
653 
sendPacket(FileDescriptor fd, ByteBuffer packet)654     private void sendPacket(FileDescriptor fd, ByteBuffer packet) throws Exception {
655         assertNotNull("Only tests on virtual interfaces can send packets", fd);
656         Os.write(fd, packet);
657     }
658 
assertLinkAddressMatches(LinkAddress l1, LinkAddress l2)659     public void assertLinkAddressMatches(LinkAddress l1, LinkAddress l2) {
660         // Check all fields except the deprecation and expiry times.
661         String msg = String.format("LinkAddresses do not match. expected: %s actual: %s", l1, l2);
662         assertTrue(msg, l1.isSameAddressAs(l2));
663         assertEquals("LinkAddress flags do not match", l1.getFlags(), l2.getFlags());
664         assertEquals("LinkAddress scope does not match", l1.getScope(), l2.getScope());
665     }
666 
requestWithStaticIpv4(String local, String client)667     private TetheringRequest requestWithStaticIpv4(String local, String client) {
668         LinkAddress localAddr = local == null ? null : new LinkAddress(local);
669         LinkAddress clientAddr = client == null ? null : new LinkAddress(client);
670         return new TetheringRequest.Builder(TETHERING_ETHERNET)
671                 .setStaticIpv4Addresses(localAddr, clientAddr)
672                 .setShouldShowEntitlementUi(false).build();
673     }
674 
assertInvalidStaticIpv4Request(String iface, String local, String client)675     private void assertInvalidStaticIpv4Request(String iface, String local, String client)
676             throws Exception {
677         try {
678             enableEthernetTethering(iface, requestWithStaticIpv4(local, client));
679             fail("Unexpectedly accepted invalid IPv4 configuration: " + local + ", " + client);
680         } catch (IllegalArgumentException | NullPointerException expected) { }
681     }
682 
assertInterfaceHasIpAddress(String iface, String expected)683     private void assertInterfaceHasIpAddress(String iface, String expected) throws Exception {
684         LinkAddress expectedAddr = new LinkAddress(expected);
685         NetworkInterface nif = NetworkInterface.getByName(iface);
686         for (InterfaceAddress ia : nif.getInterfaceAddresses()) {
687             final LinkAddress addr = new LinkAddress(ia.getAddress(), ia.getNetworkPrefixLength());
688             if (expectedAddr.equals(addr)) {
689                 return;
690             }
691         }
692         fail("Expected " + iface + " to have IP address " + expected + ", found "
693                 + nif.getInterfaceAddresses());
694     }
695 
createTestInterface()696     private TestNetworkInterface createTestInterface() throws Exception {
697         TestNetworkManager tnm = mContext.getSystemService(TestNetworkManager.class);
698         TestNetworkInterface iface = tnm.createTapInterface();
699         Log.d(TAG, "Created test interface " + iface.getInterfaceName());
700         return iface;
701     }
702 
maybeDeleteTestInterface()703     private void maybeDeleteTestInterface() throws Exception {
704         if (mTestIface != null) {
705             mTestIface.getFileDescriptor().close();
706             Log.d(TAG, "Deleted test interface " + mTestIface.getInterfaceName());
707             mTestIface = null;
708         }
709     }
710 }
711