Merge "Add tethering icmpv6 forwarding test" am: 86a800f911

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/1817920

Change-Id: I1e50559c47fa1ed32207b72d0be9cad00887381e
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Mark Chien
2022-06-01 15:44:43 +00:00
committed by Automerger Merge Worker
2 changed files with 219 additions and 12 deletions

View File

@@ -27,6 +27,7 @@ import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringTester.RemoteResponder;
import static android.net.TetheringTester.isIcmpv6Type;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.IPPROTO_IP;
import static android.system.OsConstants.IPPROTO_UDP;
@@ -35,6 +36,8 @@ import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
import static com.android.net.module.util.HexDump.dumpHexString;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
import static com.android.testutils.DeviceInfoUtils.KVersion;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
@@ -73,6 +76,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.PacketBuilder;
import com.android.net.module.util.Struct;
import com.android.net.module.util.bpf.Tether4Key;
@@ -101,6 +105,7 @@ import org.junit.runner.RunWith;
import java.io.FileDescriptor;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
@@ -781,21 +786,26 @@ public class EthernetTetheringTest {
}
}
private TestNetworkTracker createTestUpstream(final List<LinkAddress> addresses)
throws Exception {
private TestNetworkTracker createTestUpstream(final List<LinkAddress> addresses,
final List<InetAddress> dnses) throws Exception {
mTm.setPreferTestNetworks(true);
return initTestNetwork(mContext, addresses, TIMEOUT_MS);
final LinkProperties lp = new LinkProperties();
lp.setLinkAddresses(addresses);
lp.setDnsServers(dnses);
return initTestNetwork(mContext, lp, TIMEOUT_MS);
}
@Test
public void testTestNetworkUpstream() throws Exception {
public void testIcmpv6Echo() throws Exception {
assumeFalse(mEm.isAvailable());
// MyTetheringEventCallback currently only support await first available upstream. Tethering
// may select internet network as upstream if test network is not available and not be
// preferred yet. Create test upstream network before enable tethering.
mUpstreamTracker = createTestUpstream(toList(TEST_IP4_ADDR, TEST_IP6_ADDR));
mUpstreamTracker = createTestUpstream(toList(TEST_IP4_ADDR, TEST_IP6_ADDR),
toList(TEST_IP4_DNS, TEST_IP6_DNS));
mDownstreamIface = createTestInterface();
mEm.setIncludeTestInterfaces(true);
@@ -810,7 +820,39 @@ public class EthernetTetheringTest {
mTetheringEventCallback.awaitUpstreamChanged());
mDownstreamReader = makePacketReader(mDownstreamIface);
// TODO: do basic forwarding test here.
mUpstreamReader = makePacketReader(mUpstreamTracker.getTestIface());
runPing6Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader));
}
private void runPing6Test(TetheringTester tester, RemoteResponder remote) throws Exception {
// Currently tethering don't have API to tell when ipv6 tethering is available. Thus, let
// TetheringTester test ipv6 tethering connectivity before testing ipv6.
tester.waitForIpv6TetherConnectivityVerified();
TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString("1:2:3:4:5:6"),
true /* hasIpv6 */);
Inet6Address remoteIp6Addr = (Inet6Address) parseNumericAddress("2400:222:222::222");
ByteBuffer request = Ipv6Utils.buildEchoRequestPacket(tethered.macAddr,
tethered.routerMacAddr, tethered.ipv6Addr, remoteIp6Addr);
tester.sendPacket(request);
final byte[] echoRequest = remote.getNextMatchedPacket((p) -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
return isIcmpv6Type(p, false /* hasEth */, ICMPV6_ECHO_REQUEST_TYPE);
});
assertNotNull("No icmpv6 echo request in upstream", echoRequest);
ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(remoteIp6Addr, tethered.ipv6Addr);
remote.sendPacket(reply);
final byte[] echoReply = tester.getNextMatchedPacket((p) -> {
Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
return isIcmpv6Type(p, true /* hasEth */, ICMPV6_ECHO_REPLY_TYPE);
});
assertNotNull("No icmpv6 echo reply in downstream", echoReply);
}
// Test network topology:
@@ -922,7 +964,7 @@ public class EthernetTetheringTest {
private void runUdp4Test(TetheringTester tester, RemoteResponder remote, boolean usingBpf)
throws Exception {
final TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString(
"1:2:3:4:5:6"));
"1:2:3:4:5:6"), false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
// Because async upstream connected notification can't guarantee the tethering routing is
@@ -1043,7 +1085,7 @@ public class EthernetTetheringTest {
// MyTetheringEventCallback currently only support await first available upstream. Tethering
// may select internet network as upstream if test network is not available and not be
// preferred yet. Create test upstream network before enable tethering.
mUpstreamTracker = createTestUpstream(toList(TEST_IP4_ADDR));
mUpstreamTracker = createTestUpstream(toList(TEST_IP4_ADDR), toList(TEST_IP4_DNS));
mDownstreamIface = createTestInterface();
mEm.setIncludeTestInterfaces(true);

View File

@@ -16,10 +16,22 @@
package android.net;
import static android.net.InetAddresses.parseNumericAddress;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -32,11 +44,24 @@ import android.util.Log;
import androidx.annotation.Nullable;
import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.Struct;
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Icmpv6Header;
import com.android.net.module.util.structs.Ipv6Header;
import com.android.net.module.util.structs.LlaOption;
import com.android.net.module.util.structs.NsHeader;
import com.android.net.module.util.structs.PrefixInformationOption;
import com.android.net.module.util.structs.RaHeader;
import com.android.networkstack.arp.ArpPacket;
import com.android.testutils.TapPacketReader;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeoutException;
import java.util.function.Predicate;
@@ -50,12 +75,14 @@ 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 int READ_RA_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 InetAddress LINK_LOCAL = parseNumericAddress("fe80::1");
public static final String DHCP_HOSTNAME = "testhostname";
@@ -69,12 +96,13 @@ public final class TetheringTester {
mTetheredDevices = new ArrayMap<>();
}
public TetheredDevice createTetheredDevice(MacAddress macAddr) throws Exception {
public TetheredDevice createTetheredDevice(MacAddress macAddr, boolean hasIpv6)
throws Exception {
if (mTetheredDevices.get(macAddr) != null) {
fail("Tethered device already created");
}
TetheredDevice tethered = new TetheredDevice(macAddr);
TetheredDevice tethered = new TetheredDevice(macAddr, hasIpv6);
mTetheredDevices.put(macAddr, tethered);
return tethered;
@@ -84,14 +112,15 @@ public final class TetheringTester {
public final MacAddress macAddr;
public final MacAddress routerMacAddr;
public final Inet4Address ipv4Addr;
public final Inet6Address ipv6Addr;
private TetheredDevice(MacAddress mac) throws Exception {
private TetheredDevice(MacAddress mac, boolean hasIpv6) throws Exception {
macAddr = mac;
DhcpResults dhcpResults = runDhcp(macAddr.toByteArray());
ipv4Addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
routerMacAddr = getRouterMacAddressFromArp(ipv4Addr, macAddr,
dhcpResults.serverAddress);
ipv6Addr = hasIpv6 ? runSlaac(macAddr, routerMacAddr) : null;
}
}
@@ -216,6 +245,141 @@ public final class TetheringTester {
return null;
}
public void waitForIpv6TetherConnectivityVerified() throws Exception {
Log.d(TAG, "Waiting RA multicast");
// Wait for RA multicast message from router to confirm that the IPv6 tethering
// connectivity is ready. We don't extract the router mac address from RA because
// we get the router mac address from IPv4 ARP packet. See #getRouterMacAddressFromArp.
for (int i = 0; i < READ_RA_ATTEMPTS; i++) {
final byte[] raPacket = getNextMatchedPacket((p) -> {
return isIcmpv6Type(p, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT);
});
if (raPacket != null) return;
}
fail("Could not get RA multicast packet after " + READ_RA_ATTEMPTS + " attempts");
}
private List<PrefixInformationOption> getRaPrefixOptions(byte[] packet) {
ByteBuffer buf = ByteBuffer.wrap(packet);
if (!isIcmpv6Type(buf, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT)) return null;
Struct.parse(RaHeader.class, buf);
final ArrayList<PrefixInformationOption> pioList = new ArrayList<>();
while (buf.position() < packet.length) {
final int currentPos = buf.position();
final int type = Byte.toUnsignedInt(buf.get());
final int length = Byte.toUnsignedInt(buf.get());
if (type == ICMPV6_ND_OPTION_PIO) {
final ByteBuffer pioBuf = ByteBuffer.wrap(buf.array(), currentPos,
Struct.getSize(PrefixInformationOption.class));
final PrefixInformationOption pio =
Struct.parse(PrefixInformationOption.class, pioBuf);
pioList.add(pio);
// Move ByteBuffer position to the next option.
buf.position(currentPos + Struct.getSize(PrefixInformationOption.class));
} else {
buf.position(currentPos + (length * 8));
}
}
return pioList;
}
private Inet6Address runSlaac(MacAddress srcMac, MacAddress dstMac) throws Exception {
sendRsPacket(srcMac, dstMac);
final byte[] raPacket = getNextMatchedPacket((p) -> {
return isIcmpv6Type(p, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT);
});
if (raPacket == null) {
fail("Could not get ra for prefix options");
}
final List<PrefixInformationOption> options = getRaPrefixOptions(raPacket);
for (PrefixInformationOption pio : options) {
if (pio.validLifetime > 0) {
final byte[] addressBytes = pio.prefix;
// Random the last two bytes as suffix.
// TODO: Currently do not implmement DAD in the test. Rely the gateway ipv6 address
// genetrated by tethering module always has random the last byte.
addressBytes[addressBytes.length - 1] = (byte) (new Random()).nextInt();
addressBytes[addressBytes.length - 2] = (byte) (new Random()).nextInt();
return (Inet6Address) InetAddress.getByAddress(addressBytes);
}
}
fail("Could not get ipv6 address");
return null;
}
private void sendRsPacket(MacAddress srcMac, MacAddress dstMac) throws Exception {
Log.d(TAG, "Sending RS");
ByteBuffer slla = LlaOption.build((byte) ICMPV6_ND_OPTION_SLLA, srcMac);
ByteBuffer rs = Ipv6Utils.buildRsPacket(srcMac, dstMac, (Inet6Address) LINK_LOCAL,
IPV6_ADDR_ALL_NODES_MULTICAST, slla);
sendPacket(rs);
}
private void maybeReplyNa(byte[] packet) {
ByteBuffer buf = ByteBuffer.wrap(packet);
final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
if (ethHdr.etherType != ETHER_TYPE_IPV6) return;
final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return;
final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
if (icmpv6Hdr.type != (short) ICMPV6_NEIGHBOR_SOLICITATION) return;
final NsHeader nsHdr = Struct.parse(NsHeader.class, buf);
for (int i = 0; i < mTetheredDevices.size(); i++) {
TetheredDevice tethered = mTetheredDevices.valueAt(i);
if (!nsHdr.target.equals(tethered.ipv6Addr)) continue;
final ByteBuffer tlla = LlaOption.build((byte) ICMPV6_ND_OPTION_TLLA, tethered.macAddr);
int flags = NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED
| NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
ByteBuffer ns = Ipv6Utils.buildNaPacket(tethered.macAddr, tethered.routerMacAddr,
nsHdr.target, ipv6Hdr.srcIp, flags, nsHdr.target, tlla);
try {
sendPacket(ns);
} catch (Exception e) {
fail("Failed to reply NA for " + tethered.ipv6Addr);
}
return;
}
}
public static boolean isIcmpv6Type(byte[] packet, boolean hasEth, int type) {
final ByteBuffer buf = ByteBuffer.wrap(packet);
return isIcmpv6Type(buf, hasEth, type);
}
private static boolean isIcmpv6Type(ByteBuffer buf, boolean hasEth, int type) {
try {
if (hasEth) {
final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
if (ethHdr.etherType != ETHER_TYPE_IPV6) return false;
}
final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return false;
final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
return icmpv6Hdr.type == (short) type;
} catch (Exception e) {
// Parsing packet fail means it is not icmpv6 packet.
}
return false;
}
public void sendPacket(ByteBuffer packet) throws Exception {
mDownstreamReader.sendResponse(packet);
}
@@ -226,6 +390,7 @@ public final class TetheringTester {
if (filter.test(packet)) return packet;
maybeReplyArp(packet);
maybeReplyNa(packet);
}
return null;