From f320c21120e54e3e3112bf86707caefdface6bfa Mon Sep 17 00:00:00 2001 From: markchien Date: Mon, 6 Sep 2021 15:28:40 +0800 Subject: [PATCH] Move runDhcp to TetheringTester This is a no-op CL which add TetheringTester and move runDhcp related logic to it. Test: atest EthernetTetheringTest Change-Id: Ib1c5647b2bd5a1b27c976450d3aa265aff8f5b70 --- .../android/net/EthernetTetheringTest.java | 88 +--------- .../src/android/net/TetheringTester.java | 155 ++++++++++++++++++ 2 files changed, 163 insertions(+), 80 deletions(-) create mode 100644 Tethering/tests/integration/src/android/net/TetheringTester.java diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java index 971eb10993..15f07f2c8a 100644 --- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java +++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java @@ -47,14 +47,10 @@ import android.net.EthernetManager.TetheredInterfaceRequest; import android.net.TetheringManager.StartTetheringCallback; import android.net.TetheringManager.TetheringEventCallback; import android.net.TetheringManager.TetheringRequest; -import android.net.dhcp.DhcpAckPacket; -import android.net.dhcp.DhcpOfferPacket; -import android.net.dhcp.DhcpPacket; import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; import android.os.SystemProperties; -import android.system.Os; import android.util.Log; import androidx.test.InstrumentationRegistry; @@ -75,7 +71,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.FileDescriptor; -import java.net.Inet4Address; import java.net.InetAddress; import java.net.InterfaceAddress; import java.net.NetworkInterface; @@ -97,15 +92,6 @@ public class EthernetTetheringTest { private static final String TAG = EthernetTetheringTest.class.getSimpleName(); private static final int TIMEOUT_MS = 5000; - private static final int PACKET_READ_TIMEOUT_MS = 100; - private static final int DHCP_DISCOVER_ATTEMPTS = 10; - private static final byte[] DHCP_REQUESTED_PARAMS = new byte[] { - DhcpPacket.DHCP_SUBNET_MASK, - DhcpPacket.DHCP_ROUTER, - DhcpPacket.DHCP_DNS_SERVER, - DhcpPacket.DHCP_LEASE_TIME, - }; - private static final String DHCP_HOSTNAME = "testhostname"; private static final LinkAddress TEST_IP4_ADDR = new LinkAddress("10.0.0.1/8"); private static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64"); private static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8"); @@ -254,11 +240,12 @@ public class EthernetTetheringTest { FileDescriptor fd = mDownstreamIface.getFileDescriptor().getFileDescriptor(); mDownstreamReader = makePacketReader(fd, getMTU(mDownstreamIface)); - DhcpResults dhcpResults = runDhcp(fd, client1); + TetheringTester tester = new TetheringTester(mDownstreamReader); + DhcpResults dhcpResults = tester.runDhcp(client1); assertEquals(new LinkAddress(clientAddr), dhcpResults.ipAddress); try { - runDhcp(fd, client2); + tester.runDhcp(client2); fail("Only one client should get an IP address"); } catch (TimeoutException expected) { } @@ -558,38 +545,16 @@ public class EthernetTetheringTest { FileDescriptor fd = iface.getFileDescriptor().getFileDescriptor(); mDownstreamReader = makePacketReader(fd, mtu); mTetheringEventCallback = enableEthernetTethering(iface.getInterfaceName()); - checkTetheredClientCallbacks(fd); + checkTetheredClientCallbacks(mDownstreamReader); } - private DhcpResults runDhcp(FileDescriptor fd, byte[] clientMacAddr) throws Exception { - // We have to retransmit DHCP requests because IpServer declares itself to be ready before - // its DhcpServer is actually started. TODO: fix this race and remove this loop. - DhcpPacket offerPacket = null; - for (int i = 0; i < DHCP_DISCOVER_ATTEMPTS; i++) { - Log.d(TAG, "Sending DHCP discover"); - sendDhcpDiscover(fd, clientMacAddr); - offerPacket = getNextDhcpPacket(); - if (offerPacket instanceof DhcpOfferPacket) break; - } - if (!(offerPacket instanceof DhcpOfferPacket)) { - throw new TimeoutException("No DHCPOFFER received on interface within timeout"); - } - - sendDhcpRequest(fd, offerPacket, clientMacAddr); - DhcpPacket ackPacket = getNextDhcpPacket(); - if (!(ackPacket instanceof DhcpAckPacket)) { - throw new TimeoutException("No DHCPACK received on interface within timeout"); - } - - return ackPacket.toDhcpResults(); - } - - private void checkTetheredClientCallbacks(FileDescriptor fd) throws Exception { + private void checkTetheredClientCallbacks(TapPacketReader packetReader) throws Exception { // Create a fake client. byte[] clientMacAddr = new byte[6]; new Random().nextBytes(clientMacAddr); - DhcpResults dhcpResults = runDhcp(fd, clientMacAddr); + TetheringTester tester = new TetheringTester(packetReader); + DhcpResults dhcpResults = tester.runDhcp(clientMacAddr); final Collection clients = mTetheringEventCallback.awaitClientConnected(); assertEquals(1, clients.size()); @@ -602,7 +567,7 @@ public class EthernetTetheringTest { // Check the hostname. assertEquals(1, client.getAddresses().size()); TetheredClient.AddressInfo info = client.getAddresses().get(0); - assertEquals(DHCP_HOSTNAME, info.getHostname()); + assertEquals(TetheringTester.DHCP_HOSTNAME, info.getHostname()); // Check the address is the one that was handed out in the DHCP ACK. assertLinkAddressMatches(dhcpResults.ipAddress, info.getAddress()); @@ -615,18 +580,6 @@ public class EthernetTetheringTest { assertTrue(msg, Math.abs(dhcpResults.leaseDuration - actualLeaseDuration) < 10); } - private DhcpPacket getNextDhcpPacket() throws ParseException { - byte[] packet; - while ((packet = mDownstreamReader.popPacket(PACKET_READ_TIMEOUT_MS)) != null) { - try { - return DhcpPacket.decodeFullPacket(packet, packet.length, DhcpPacket.ENCAP_L2); - } catch (DhcpPacket.ParseException e) { - // Not a DHCP packet. Continue. - } - } - return null; - } - private static final class TetheredInterfaceRequester implements TetheredInterfaceCallback { private final Handler mHandler; private final EthernetManager mEm; @@ -670,31 +623,6 @@ public class EthernetTetheringTest { } } - private void sendDhcpDiscover(FileDescriptor fd, byte[] macAddress) throws Exception { - ByteBuffer packet = DhcpPacket.buildDiscoverPacket(DhcpPacket.ENCAP_L2, - new Random().nextInt() /* transactionId */, (short) 0 /* secs */, - macAddress, false /* unicast */, DHCP_REQUESTED_PARAMS, - false /* rapid commit */, DHCP_HOSTNAME); - sendPacket(fd, packet); - } - - private void sendDhcpRequest(FileDescriptor fd, DhcpPacket offerPacket, byte[] macAddress) - throws Exception { - DhcpResults results = offerPacket.toDhcpResults(); - Inet4Address clientIp = (Inet4Address) results.ipAddress.getAddress(); - Inet4Address serverIdentifier = results.serverAddress; - ByteBuffer packet = DhcpPacket.buildRequestPacket(DhcpPacket.ENCAP_L2, - 0 /* transactionId */, (short) 0 /* secs */, DhcpPacket.INADDR_ANY /* clientIp */, - false /* broadcast */, macAddress, clientIp /* requestedIpAddress */, - serverIdentifier, DHCP_REQUESTED_PARAMS, DHCP_HOSTNAME); - sendPacket(fd, packet); - } - - private void sendPacket(FileDescriptor fd, ByteBuffer packet) throws Exception { - assertNotNull("Only tests on virtual interfaces can send packets", fd); - Os.write(fd, packet); - } - public void assertLinkAddressMatches(LinkAddress l1, LinkAddress l2) { // Check all fields except the deprecation and expiry times. String msg = String.format("LinkAddresses do not match. expected: %s actual: %s", l1, l2); diff --git a/Tethering/tests/integration/src/android/net/TetheringTester.java b/Tethering/tests/integration/src/android/net/TetheringTester.java new file mode 100644 index 0000000000..38d74ad292 --- /dev/null +++ b/Tethering/tests/integration/src/android/net/TetheringTester.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static org.junit.Assert.fail; + +import android.net.dhcp.DhcpAckPacket; +import android.net.dhcp.DhcpOfferPacket; +import android.net.dhcp.DhcpPacket; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.testutils.TapPacketReader; + +import java.net.Inet4Address; +import java.nio.ByteBuffer; +import java.util.Random; +import java.util.concurrent.TimeoutException; +import java.util.function.Function; + +/** + * A class simulate tethered client. When caller create TetheringTester, it would connect to + * tethering module that do the dhcp and slaac to obtain ipv4 and ipv6 address. Then caller can + * send/receive packets by this class. + */ +public final class TetheringTester { + private static final String TAG = TetheringTester.class.getSimpleName(); + private static final int PACKET_READ_TIMEOUT_MS = 100; + private static final int DHCP_DISCOVER_ATTEMPTS = 10; + private static final byte[] DHCP_REQUESTED_PARAMS = new byte[] { + DhcpPacket.DHCP_SUBNET_MASK, + DhcpPacket.DHCP_ROUTER, + DhcpPacket.DHCP_DNS_SERVER, + DhcpPacket.DHCP_LEASE_TIME, + }; + + public static final String DHCP_HOSTNAME = "testhostname"; + + private final ArrayMap mTetheredDevices; + private final TapPacketReader mDownstreamReader; + + public TetheringTester(TapPacketReader downstream) { + if (downstream == null) fail("Downstream reader could not be NULL"); + + mDownstreamReader = downstream; + mTetheredDevices = new ArrayMap<>(); + } + + public TetheredDevice createTetheredDevice(MacAddress macAddr) throws Exception { + if (mTetheredDevices.get(macAddr) != null) { + fail("Tethered device already created"); + } + + TetheredDevice tethered = new TetheredDevice(macAddr); + mTetheredDevices.put(macAddr, tethered); + + return tethered; + } + + public class TetheredDevice { + private final MacAddress mMacAddr; + + public final Inet4Address mIpv4Addr; + + private TetheredDevice(MacAddress mac) throws Exception { + mMacAddr = mac; + + DhcpResults dhcpResults = runDhcp(mMacAddr.toByteArray()); + mIpv4Addr = (Inet4Address) dhcpResults.ipAddress.getAddress(); + } + } + + /** Simulate dhcp client to obtain ipv4 address. */ + public DhcpResults runDhcp(byte[] clientMacAddr) + throws Exception { + // We have to retransmit DHCP requests because IpServer declares itself to be ready before + // its DhcpServer is actually started. TODO: fix this race and remove this loop. + DhcpPacket offerPacket = null; + for (int i = 0; i < DHCP_DISCOVER_ATTEMPTS; i++) { + Log.d(TAG, "Sending DHCP discover"); + sendDhcpDiscover(clientMacAddr); + offerPacket = getNextDhcpPacket(); + if (offerPacket instanceof DhcpOfferPacket) break; + } + if (!(offerPacket instanceof DhcpOfferPacket)) { + throw new TimeoutException("No DHCPOFFER received on interface within timeout"); + } + + sendDhcpRequest(offerPacket, clientMacAddr); + DhcpPacket ackPacket = getNextDhcpPacket(); + if (!(ackPacket instanceof DhcpAckPacket)) { + throw new TimeoutException("No DHCPACK received on interface within timeout"); + } + + return ackPacket.toDhcpResults(); + } + + private void sendDhcpDiscover(byte[] macAddress) throws Exception { + ByteBuffer packet = DhcpPacket.buildDiscoverPacket(DhcpPacket.ENCAP_L2, + new Random().nextInt() /* transactionId */, (short) 0 /* secs */, + macAddress, false /* unicast */, DHCP_REQUESTED_PARAMS, + false /* rapid commit */, DHCP_HOSTNAME); + mDownstreamReader.sendResponse(packet); + } + + private void sendDhcpRequest(DhcpPacket offerPacket, byte[] macAddress) + throws Exception { + DhcpResults results = offerPacket.toDhcpResults(); + Inet4Address clientIp = (Inet4Address) results.ipAddress.getAddress(); + Inet4Address serverIdentifier = results.serverAddress; + ByteBuffer packet = DhcpPacket.buildRequestPacket(DhcpPacket.ENCAP_L2, + 0 /* transactionId */, (short) 0 /* secs */, DhcpPacket.INADDR_ANY /* clientIp */, + false /* broadcast */, macAddress, clientIp /* requestedIpAddress */, + serverIdentifier, DHCP_REQUESTED_PARAMS, DHCP_HOSTNAME); + mDownstreamReader.sendResponse(packet); + } + + private DhcpPacket getNextDhcpPacket() { + return getNextMatchedPacket((p) -> { + try { + return DhcpPacket.decodeFullPacket(p, p.length, DhcpPacket.ENCAP_L2); + } catch (DhcpPacket.ParseException e) { + // Not a DHCP packet. Continue. + } + + return null; + }); + } + + private R getNextMatchedPacket(Function match) { + byte[] packet; + R result; + while ((packet = mDownstreamReader.popPacket(PACKET_READ_TIMEOUT_MS)) != null) { + result = match.apply(packet); + + if (result != null) return result; + } + + return null; + } +}