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
This commit is contained in:
@@ -47,14 +47,10 @@ import android.net.EthernetManager.TetheredInterfaceRequest;
|
|||||||
import android.net.TetheringManager.StartTetheringCallback;
|
import android.net.TetheringManager.StartTetheringCallback;
|
||||||
import android.net.TetheringManager.TetheringEventCallback;
|
import android.net.TetheringManager.TetheringEventCallback;
|
||||||
import android.net.TetheringManager.TetheringRequest;
|
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.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.os.SystemProperties;
|
import android.os.SystemProperties;
|
||||||
import android.system.Os;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
import androidx.test.InstrumentationRegistry;
|
||||||
@@ -75,7 +71,6 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.net.Inet4Address;
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InterfaceAddress;
|
import java.net.InterfaceAddress;
|
||||||
import java.net.NetworkInterface;
|
import java.net.NetworkInterface;
|
||||||
@@ -97,15 +92,6 @@ public class EthernetTetheringTest {
|
|||||||
|
|
||||||
private static final String TAG = EthernetTetheringTest.class.getSimpleName();
|
private static final String TAG = EthernetTetheringTest.class.getSimpleName();
|
||||||
private static final int TIMEOUT_MS = 5000;
|
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_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 LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64");
|
||||||
private static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8");
|
private static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8");
|
||||||
@@ -254,11 +240,12 @@ public class EthernetTetheringTest {
|
|||||||
|
|
||||||
FileDescriptor fd = mDownstreamIface.getFileDescriptor().getFileDescriptor();
|
FileDescriptor fd = mDownstreamIface.getFileDescriptor().getFileDescriptor();
|
||||||
mDownstreamReader = makePacketReader(fd, getMTU(mDownstreamIface));
|
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);
|
assertEquals(new LinkAddress(clientAddr), dhcpResults.ipAddress);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
runDhcp(fd, client2);
|
tester.runDhcp(client2);
|
||||||
fail("Only one client should get an IP address");
|
fail("Only one client should get an IP address");
|
||||||
} catch (TimeoutException expected) { }
|
} catch (TimeoutException expected) { }
|
||||||
|
|
||||||
@@ -558,38 +545,16 @@ public class EthernetTetheringTest {
|
|||||||
FileDescriptor fd = iface.getFileDescriptor().getFileDescriptor();
|
FileDescriptor fd = iface.getFileDescriptor().getFileDescriptor();
|
||||||
mDownstreamReader = makePacketReader(fd, mtu);
|
mDownstreamReader = makePacketReader(fd, mtu);
|
||||||
mTetheringEventCallback = enableEthernetTethering(iface.getInterfaceName());
|
mTetheringEventCallback = enableEthernetTethering(iface.getInterfaceName());
|
||||||
checkTetheredClientCallbacks(fd);
|
checkTetheredClientCallbacks(mDownstreamReader);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DhcpResults runDhcp(FileDescriptor fd, byte[] clientMacAddr) throws Exception {
|
private void checkTetheredClientCallbacks(TapPacketReader packetReader) 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 {
|
|
||||||
// Create a fake client.
|
// Create a fake client.
|
||||||
byte[] clientMacAddr = new byte[6];
|
byte[] clientMacAddr = new byte[6];
|
||||||
new Random().nextBytes(clientMacAddr);
|
new Random().nextBytes(clientMacAddr);
|
||||||
|
|
||||||
DhcpResults dhcpResults = runDhcp(fd, clientMacAddr);
|
TetheringTester tester = new TetheringTester(packetReader);
|
||||||
|
DhcpResults dhcpResults = tester.runDhcp(clientMacAddr);
|
||||||
|
|
||||||
final Collection<TetheredClient> clients = mTetheringEventCallback.awaitClientConnected();
|
final Collection<TetheredClient> clients = mTetheringEventCallback.awaitClientConnected();
|
||||||
assertEquals(1, clients.size());
|
assertEquals(1, clients.size());
|
||||||
@@ -602,7 +567,7 @@ public class EthernetTetheringTest {
|
|||||||
// Check the hostname.
|
// Check the hostname.
|
||||||
assertEquals(1, client.getAddresses().size());
|
assertEquals(1, client.getAddresses().size());
|
||||||
TetheredClient.AddressInfo info = client.getAddresses().get(0);
|
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.
|
// Check the address is the one that was handed out in the DHCP ACK.
|
||||||
assertLinkAddressMatches(dhcpResults.ipAddress, info.getAddress());
|
assertLinkAddressMatches(dhcpResults.ipAddress, info.getAddress());
|
||||||
@@ -615,18 +580,6 @@ public class EthernetTetheringTest {
|
|||||||
assertTrue(msg, Math.abs(dhcpResults.leaseDuration - actualLeaseDuration) < 10);
|
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 static final class TetheredInterfaceRequester implements TetheredInterfaceCallback {
|
||||||
private final Handler mHandler;
|
private final Handler mHandler;
|
||||||
private final EthernetManager mEm;
|
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) {
|
public void assertLinkAddressMatches(LinkAddress l1, LinkAddress l2) {
|
||||||
// Check all fields except the deprecation and expiry times.
|
// Check all fields except the deprecation and expiry times.
|
||||||
String msg = String.format("LinkAddresses do not match. expected: %s actual: %s", l1, l2);
|
String msg = String.format("LinkAddresses do not match. expected: %s actual: %s", l1, l2);
|
||||||
|
|||||||
155
Tethering/tests/integration/src/android/net/TetheringTester.java
Normal file
155
Tethering/tests/integration/src/android/net/TetheringTester.java
Normal file
@@ -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<MacAddress, TetheredDevice> 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> R getNextMatchedPacket(Function<byte[], R> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user