/*
 * Copyright (C) 2020 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 android.net.InetAddresses.parseNumericAddress;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringTester.TestDnsPacket;
import static android.net.TetheringTester.buildIcmpEchoPacketV4;
import static android.net.TetheringTester.buildUdpPacket;
import static android.net.TetheringTester.isExpectedIcmpPacket;
import static android.net.TetheringTester.isExpectedUdpDnsPacket;
import static android.system.OsConstants.ICMP_ECHO;
import static android.system.OsConstants.ICMP_ECHOREPLY;

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.ICMPV6_ECHO_REPLY_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;

import android.net.TetheringManager.TetheringRequest;
import android.net.TetheringTester.TetheredDevice;
import android.os.Build;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.Struct;
import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.UdpHeader;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.NetworkStackModuleTest;
import com.android.testutils.TapPacketReader;

import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
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;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

@RunWith(AndroidJUnit4.class)
@LargeTest
public class EthernetTetheringTest extends EthernetTetheringTestBase {
    @Rule
    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();

    private static final String TAG = EthernetTetheringTest.class.getSimpleName();

    private static final short DNS_PORT = 53;
    private static final short ICMPECHO_ID = 0x0;
    private static final short ICMPECHO_SEQ = 0x0;

    // TODO: use class DnsPacket to build DNS query and reply message once DnsPacket supports
    // building packet for given arguments.
    private static final ByteBuffer DNS_QUERY = ByteBuffer.wrap(new byte[] {
            // scapy.DNS(
            //   id=0xbeef,
            //   qr=0,
            //   qd=scapy.DNSQR(qname="hello.example.com"))
            //
            /* Header */
            (byte) 0xbe, (byte) 0xef, /* Transaction ID: 0xbeef */
            (byte) 0x01, (byte) 0x00, /* Flags: rd */
            (byte) 0x00, (byte) 0x01, /* Questions: 1 */
            (byte) 0x00, (byte) 0x00, /* Answer RRs: 0 */
            (byte) 0x00, (byte) 0x00, /* Authority RRs: 0 */
            (byte) 0x00, (byte) 0x00, /* Additional RRs: 0 */
            /* Queries */
            (byte) 0x05, (byte) 0x68, (byte) 0x65, (byte) 0x6c,
            (byte) 0x6c, (byte) 0x6f, (byte) 0x07, (byte) 0x65,
            (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
            (byte) 0x6c, (byte) 0x65, (byte) 0x03, (byte) 0x63,
            (byte) 0x6f, (byte) 0x6d, (byte) 0x00, /* Name: hello.example.com */
            (byte) 0x00, (byte) 0x01,              /* Type: A */
            (byte) 0x00, (byte) 0x01               /* Class: IN */
    });

    private static final byte[] DNS_REPLY = new byte[] {
            // scapy.DNS(
            //   id=0,
            //   qr=1,
            //   qd=scapy.DNSQR(qname="hello.example.com"),
            //   an=scapy.DNSRR(rrname="hello.example.com", rdata='1.2.3.4'))
            //
            /* Header */
            (byte) 0x00, (byte) 0x00, /* Transaction ID: 0x0, must be updated by dns query id */
            (byte) 0x81, (byte) 0x00, /* Flags: qr rd */
            (byte) 0x00, (byte) 0x01, /* Questions: 1 */
            (byte) 0x00, (byte) 0x01, /* Answer RRs: 1 */
            (byte) 0x00, (byte) 0x00, /* Authority RRs: 0 */
            (byte) 0x00, (byte) 0x00, /* Additional RRs: 0 */
            /* Queries */
            (byte) 0x05, (byte) 0x68, (byte) 0x65, (byte) 0x6c,
            (byte) 0x6c, (byte) 0x6f, (byte) 0x07, (byte) 0x65,
            (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
            (byte) 0x6c, (byte) 0x65, (byte) 0x03, (byte) 0x63,
            (byte) 0x6f, (byte) 0x6d, (byte) 0x00,              /* Name: hello.example.com */
            (byte) 0x00, (byte) 0x01,                           /* Type: A */
            (byte) 0x00, (byte) 0x01,                           /* Class: IN */
            /* Answers */
            (byte) 0x05, (byte) 0x68, (byte) 0x65, (byte) 0x6c,
            (byte) 0x6c, (byte) 0x6f, (byte) 0x07, (byte) 0x65,
            (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
            (byte) 0x6c, (byte) 0x65, (byte) 0x03, (byte) 0x63,
            (byte) 0x6f, (byte) 0x6d, (byte) 0x00,              /* Name: hello.example.com */
            (byte) 0x00, (byte) 0x01,                           /* Type: A */
            (byte) 0x00, (byte) 0x01,                           /* Class: IN */
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, /* Time to live: 0 */
            (byte) 0x00, (byte) 0x04,                           /* Data length: 4 */
            (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04  /* Address: 1.2.3.4 */
    };

    /** Enable/disable tethering once before running the tests. */
    @BeforeClass
    public static void setUpOnce() throws Exception {
        // The first test case may experience tethering restart with IP conflict handling.
        // Tethering would cache the last upstreams so that the next enabled tethering avoids
        // picking up the address that is in conflict with the upstreams. To protect subsequent
        // tests, turn tethering on and off before running them.
        MyTetheringEventCallback callback = null;
        TestNetworkInterface testIface = null;
        assumeTrue(sEm != null);
        try {
            // If the physical ethernet interface is available, do nothing.
            if (isInterfaceForTetheringAvailable()) return;

            testIface = createTestInterface();
            setIncludeTestInterfaces(true);

            callback = enableEthernetTethering(testIface.getInterfaceName(), null);
            callback.awaitUpstreamChanged(true /* throwTimeoutException */);
        } catch (TimeoutException e) {
            Log.d(TAG, "WARNNING " + e);
        } finally {
            maybeCloseTestInterface(testIface);
            maybeUnregisterTetheringEventCallback(callback);

            setIncludeTestInterfaces(false);
        }
    }

    @Test
    public void testVirtualEthernetAlreadyExists() throws Exception {
        // This test requires manipulating packets. Skip if there is a physical Ethernet connected.
        assumeFalse(isInterfaceForTetheringAvailable());

        TestNetworkInterface downstreamIface = null;
        MyTetheringEventCallback tetheringEventCallback = null;
        TapPacketReader downstreamReader = null;

        try {
            downstreamIface = createTestInterface();
            // This must be done now because as soon as setIncludeTestInterfaces(true) is called,
            // the interface will be placed in client mode, which will delete the link-local
            // address. At that point NetworkInterface.getByName() will cease to work on the
            // interface, because starting in R NetworkInterface can no longer see interfaces
            // without IP addresses.
            int mtu = getMTU(downstreamIface);

            Log.d(TAG, "Including test interfaces");
            setIncludeTestInterfaces(true);

            final String iface = getTetheredInterface();
            assertEquals("TetheredInterfaceCallback for unexpected interface",
                    downstreamIface.getInterfaceName(), iface);

            // Check virtual ethernet.
            FileDescriptor fd = downstreamIface.getFileDescriptor().getFileDescriptor();
            downstreamReader = makePacketReader(fd, mtu);
            tetheringEventCallback = enableEthernetTethering(downstreamIface.getInterfaceName(),
                    null /* any upstream */);
            checkTetheredClientCallbacks(downstreamReader, tetheringEventCallback);
        } finally {
            maybeStopTapPacketReader(downstreamReader);
            maybeCloseTestInterface(downstreamIface);
            maybeUnregisterTetheringEventCallback(tetheringEventCallback);
        }
    }

    @Test
    public void testVirtualEthernet() throws Exception {
        // This test requires manipulating packets. Skip if there is a physical Ethernet connected.
        assumeFalse(isInterfaceForTetheringAvailable());

        CompletableFuture<String> futureIface = requestTetheredInterface();

        setIncludeTestInterfaces(true);

        TestNetworkInterface downstreamIface = null;
        MyTetheringEventCallback tetheringEventCallback = null;
        TapPacketReader downstreamReader = null;

        try {
            downstreamIface = createTestInterface();

            final String iface = futureIface.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
            assertEquals("TetheredInterfaceCallback for unexpected interface",
                    downstreamIface.getInterfaceName(), iface);

            // Check virtual ethernet.
            FileDescriptor fd = downstreamIface.getFileDescriptor().getFileDescriptor();
            downstreamReader = makePacketReader(fd, getMTU(downstreamIface));
            tetheringEventCallback = enableEthernetTethering(downstreamIface.getInterfaceName(),
                    null /* any upstream */);
            checkTetheredClientCallbacks(downstreamReader, tetheringEventCallback);
        } finally {
            maybeStopTapPacketReader(downstreamReader);
            maybeCloseTestInterface(downstreamIface);
            maybeUnregisterTetheringEventCallback(tetheringEventCallback);
        }
    }

    @Test
    public void testStaticIpv4() throws Exception {
        assumeFalse(isInterfaceForTetheringAvailable());

        setIncludeTestInterfaces(true);

        TestNetworkInterface downstreamIface = null;
        MyTetheringEventCallback tetheringEventCallback = null;
        TapPacketReader downstreamReader = null;

        try {
            downstreamIface = createTestInterface();

            final String iface = getTetheredInterface();
            assertEquals("TetheredInterfaceCallback for unexpected interface",
                    downstreamIface.getInterfaceName(), iface);

            assertInvalidStaticIpv4Request(iface, null, null);
            assertInvalidStaticIpv4Request(iface, "2001:db8::1/64", "2001:db8:2::/64");
            assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", "2001:db8:2::/28");
            assertInvalidStaticIpv4Request(iface, "2001:db8:2::/28", "192.0.2.2/28");
            assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", null);
            assertInvalidStaticIpv4Request(iface, null, "192.0.2.2/28");
            assertInvalidStaticIpv4Request(iface, "192.0.2.3/27", "192.0.2.2/28");

            final String localAddr = "192.0.2.3/28";
            final String clientAddr = "192.0.2.2/28";
            tetheringEventCallback = enableEthernetTethering(iface,
                    requestWithStaticIpv4(localAddr, clientAddr), null /* any upstream */);

            tetheringEventCallback.awaitInterfaceTethered();
            assertInterfaceHasIpAddress(iface, localAddr);

            byte[] client1 = MacAddress.fromString("1:2:3:4:5:6").toByteArray();
            byte[] client2 = MacAddress.fromString("a:b:c:d:e:f").toByteArray();

            FileDescriptor fd = downstreamIface.getFileDescriptor().getFileDescriptor();
            downstreamReader = makePacketReader(fd, getMTU(downstreamIface));
            TetheringTester tester = new TetheringTester(downstreamReader);
            DhcpResults dhcpResults = tester.runDhcp(client1);
            assertEquals(new LinkAddress(clientAddr), dhcpResults.ipAddress);

            try {
                tester.runDhcp(client2);
                fail("Only one client should get an IP address");
            } catch (TimeoutException expected) { }
        } finally {
            maybeStopTapPacketReader(downstreamReader);
            maybeCloseTestInterface(downstreamIface);
            maybeUnregisterTetheringEventCallback(tetheringEventCallback);
        }
    }

    private static void expectLocalOnlyAddresses(String iface) throws Exception {
        final List<InterfaceAddress> interfaceAddresses =
                NetworkInterface.getByName(iface).getInterfaceAddresses();

        boolean foundIpv6Ula = false;
        for (InterfaceAddress ia : interfaceAddresses) {
            final InetAddress addr = ia.getAddress();
            if (isIPv6ULA(addr)) {
                foundIpv6Ula = true;
            }
            final int prefixlen = ia.getNetworkPrefixLength();
            final LinkAddress la = new LinkAddress(addr, prefixlen);
            if (la.isIpv6() && la.isGlobalPreferred()) {
                fail("Found global IPv6 address on local-only interface: " + interfaceAddresses);
            }
        }

        assertTrue("Did not find IPv6 ULA on local-only interface " + iface,
                foundIpv6Ula);
    }

    @Test
    public void testLocalOnlyTethering() throws Exception {
        assumeFalse(isInterfaceForTetheringAvailable());

        setIncludeTestInterfaces(true);

        TestNetworkInterface downstreamIface = null;
        MyTetheringEventCallback tetheringEventCallback = null;
        TapPacketReader downstreamReader = null;

        try {
            downstreamIface = createTestInterface();

            final String iface = getTetheredInterface();
            assertEquals("TetheredInterfaceCallback for unexpected interface",
                    downstreamIface.getInterfaceName(), iface);

            final TetheringRequest request = new TetheringRequest.Builder(TETHERING_ETHERNET)
                    .setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build();
            tetheringEventCallback = enableEthernetTethering(iface, request,
                    null /* any upstream */);
            tetheringEventCallback.awaitInterfaceLocalOnly();

            // makePacketReader only works after tethering is started, because until then the
            // interface does not have an IP address, and unprivileged apps cannot see interfaces
            // without IP addresses. This shouldn't be flaky because the TAP interface will buffer
            // all packets even before the reader is started.
            downstreamReader = makePacketReader(downstreamIface);

            waitForRouterAdvertisement(downstreamReader, iface, WAIT_RA_TIMEOUT_MS);
            expectLocalOnlyAddresses(iface);

            // After testing the IPv6 local address, the DHCP server may still be in the process
            // of being created. If the downstream interface is killed by the test while the
            // DHCP server is starting, a DHCP server error may occur. To ensure that the DHCP
            // server has started completely before finishing the test, also test the dhcp server
            // by calling runDhcp.
            final TetheringTester tester = new TetheringTester(downstreamReader);
            tester.runDhcp(MacAddress.fromString("1:2:3:4:5:6").toByteArray());
        } finally {
            maybeStopTapPacketReader(downstreamReader);
            maybeCloseTestInterface(downstreamIface);
            maybeUnregisterTetheringEventCallback(tetheringEventCallback);
        }
    }

    private boolean isAdbOverNetwork() {
        // If adb TCP port opened, this test may running by adb over network.
        return (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1)
                || (SystemProperties.getInt("service.adb.tcp.port", -1) > -1);
    }

    @Test
    public void testPhysicalEthernet() throws Exception {
        assumeTrue(isInterfaceForTetheringAvailable());
        // Do not run this test if adb is over network and ethernet is connected.
        // It is likely the adb run over ethernet, the adb would break when ethernet is switching
        // from client mode to server mode. See b/160389275.
        assumeFalse(isAdbOverNetwork());

        MyTetheringEventCallback tetheringEventCallback = null;
        try {
            // Get an interface to use.
            final String iface = getTetheredInterface();

            // Enable Ethernet tethering and check that it starts.
            tetheringEventCallback = enableEthernetTethering(iface, null /* any upstream */);
        } finally {
            stopEthernetTethering(tetheringEventCallback);
        }
        // There is nothing more we can do on a physical interface without connecting an actual
        // client, which is not possible in this test.
    }

    private void checkTetheredClientCallbacks(final TapPacketReader packetReader,
            final MyTetheringEventCallback tetheringEventCallback) throws Exception {
        // Create a fake client.
        byte[] clientMacAddr = new byte[6];
        new Random().nextBytes(clientMacAddr);

        TetheringTester tester = new TetheringTester(packetReader);
        DhcpResults dhcpResults = tester.runDhcp(clientMacAddr);

        final Collection<TetheredClient> clients = tetheringEventCallback.awaitClientConnected();
        assertEquals(1, clients.size());
        final TetheredClient client = clients.iterator().next();

        // Check the MAC address.
        assertEquals(MacAddress.fromBytes(clientMacAddr), client.getMacAddress());
        assertEquals(TETHERING_ETHERNET, client.getTetheringType());

        // Check the hostname.
        assertEquals(1, client.getAddresses().size());
        TetheredClient.AddressInfo info = client.getAddresses().get(0);
        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());

        // Check that the lifetime is correct +/- 10s.
        final long now = SystemClock.elapsedRealtime();
        final long actualLeaseDuration = (info.getAddress().getExpirationTime() - now) / 1000;
        final String msg = String.format("IP address should have lifetime of %d, got %d",
                dhcpResults.leaseDuration, actualLeaseDuration);
        assertTrue(msg, Math.abs(dhcpResults.leaseDuration - actualLeaseDuration) < 10);
    }

    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);
        assertTrue(msg, l1.isSameAddressAs(l2));
        assertEquals("LinkAddress flags do not match", l1.getFlags(), l2.getFlags());
        assertEquals("LinkAddress scope does not match", l1.getScope(), l2.getScope());
    }

    private TetheringRequest requestWithStaticIpv4(String local, String client) {
        LinkAddress localAddr = local == null ? null : new LinkAddress(local);
        LinkAddress clientAddr = client == null ? null : new LinkAddress(client);
        return new TetheringRequest.Builder(TETHERING_ETHERNET)
                .setStaticIpv4Addresses(localAddr, clientAddr)
                .setShouldShowEntitlementUi(false).build();
    }

    private void assertInvalidStaticIpv4Request(String iface, String local, String client)
            throws Exception {
        try {
            enableEthernetTethering(iface, requestWithStaticIpv4(local, client),
                    null /* any upstream */);
            fail("Unexpectedly accepted invalid IPv4 configuration: " + local + ", " + client);
        } catch (IllegalArgumentException | NullPointerException expected) { }
    }

    private void assertInterfaceHasIpAddress(String iface, String expected) throws Exception {
        LinkAddress expectedAddr = new LinkAddress(expected);
        NetworkInterface nif = NetworkInterface.getByName(iface);
        for (InterfaceAddress ia : nif.getInterfaceAddresses()) {
            final LinkAddress addr = new LinkAddress(ia.getAddress(), ia.getNetworkPrefixLength());
            if (expectedAddr.equals(addr)) {
                return;
            }
        }
        fail("Expected " + iface + " to have IP address " + expected + ", found "
                + nif.getInterfaceAddresses());
    }

    @Test
    public void testIcmpv6Echo() throws Exception {
        runPing6Test(initTetheringTester(toList(TEST_IP4_ADDR, TEST_IP6_ADDR),
                toList(TEST_IP4_DNS, TEST_IP6_DNS)));
    }

    private void runPing6Test(TetheringTester tester) throws Exception {
        TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
        Inet6Address remoteIp6Addr = (Inet6Address) parseNumericAddress("2400:222:222::222");
        ByteBuffer request = Ipv6Utils.buildEchoRequestPacket(tethered.macAddr,
                tethered.routerMacAddr, tethered.ipv6Addr, remoteIp6Addr);
        tester.verifyUpload(request, p -> {
            Log.d(TAG, "Packet in upstream: " + dumpHexString(p));

            return isExpectedIcmpPacket(p, false /* hasEth */, false /* isIpv4 */,
                    ICMPV6_ECHO_REQUEST_TYPE);
        });

        ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(remoteIp6Addr, tethered.ipv6Addr);
        tester.verifyDownload(reply, p -> {
            Log.d(TAG, "Packet in downstream: " + dumpHexString(p));

            return isExpectedIcmpPacket(p, true /* hasEth */, false /* isIpv4 */,
                    ICMPV6_ECHO_REPLY_TYPE);
        });
    }

    @Test
    public void testTetherUdpV6() throws Exception {
        final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
                toList(TEST_IP6_DNS));
        final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
        sendUploadPacketUdp(tethered.macAddr, tethered.routerMacAddr,
                tethered.ipv6Addr, REMOTE_IP6_ADDR, tester, false /* is4To6 */);
        sendDownloadPacketUdp(REMOTE_IP6_ADDR, tethered.ipv6Addr, tester, false /* is6To4 */);

        // TODO: test BPF offload maps {rule, stats}.
    }

    // Test network topology:
    //
    //         public network (rawip)                 private network
    //                   |                 UE                |
    // +------------+    V    +------------+------------+    V    +------------+
    // |   Sever    +---------+  Upstream  | Downstream +---------+   Client   |
    // +------------+         +------------+------------+         +------------+
    // remote ip              public ip                           private ip
    // 8.8.8.8:443            <Upstream ip>:9876                  <TetheredDevice ip>:9876
    //
    private void runUdp4Test() throws Exception {
        final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
                toList(TEST_IP4_DNS));
        final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);

        // TODO: remove the connectivity verification for upstream connected notification race.
        // Because async upstream connected notification can't guarantee the tethering routing is
        // ready to use. Need to test tethering connectivity before testing.
        // For short term plan, consider using IPv6 RA to get MAC address because the prefix comes
        // from upstream. That can guarantee that the routing is ready. Long term plan is that
        // refactors upstream connected notification from async to sync.
        probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);

        final MacAddress srcMac = tethered.macAddr;
        final MacAddress dstMac = tethered.routerMacAddr;
        final InetAddress remoteIp = REMOTE_IP4_ADDR;
        final InetAddress tetheringUpstreamIp = TEST_IP4_ADDR.getAddress();
        final InetAddress clientIp = tethered.ipv4Addr;
        sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
        sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
    }

    /**
     * Basic IPv4 UDP tethering test. Verify that UDP tethered packets are transferred no matter
     * using which data path.
     */
    @Test
    public void testTetherUdpV4() throws Exception {
        runUdp4Test();
    }

    // Test network topology:
    //
    //            public network (rawip)                 private network
    //                      |         UE (CLAT support)         |
    // +---------------+    V    +------------+------------+    V    +------------+
    // | NAT64 Gateway +---------+  Upstream  | Downstream +---------+   Client   |
    // +---------------+         +------------+------------+         +------------+
    // remote ip                 public ip                           private ip
    // [64:ff9b::808:808]:443    [clat ipv6]:9876                    [TetheredDevice ipv4]:9876
    //
    // Note that CLAT IPv6 address is generated by ClatCoordinator. Get the CLAT IPv6 address by
    // sending out an IPv4 packet and extracting the source address from CLAT translated IPv6
    // packet.
    //
    private void runClatUdpTest() throws Exception {
        // CLAT only starts on IPv6 only network.
        final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
                toList(TEST_IP6_DNS));
        final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);

        // Get CLAT IPv6 address.
        final Inet6Address clatIp6 = getClatIpv6Address(tester, tethered);

        // Send an IPv4 UDP packet in original direction.
        // IPv4 packet -- CLAT translation --> IPv6 packet
        sendUploadPacketUdp(tethered.macAddr, tethered.routerMacAddr, tethered.ipv4Addr,
                REMOTE_IP4_ADDR, tester, true /* is4To6 */);

        // Send an IPv6 UDP packet in reply direction.
        // IPv6 packet -- CLAT translation --> IPv4 packet
        sendDownloadPacketUdp(REMOTE_NAT64_ADDR, clatIp6, tester, true /* is6To4 */);

        // TODO: test CLAT bpf maps.
    }

    // TODO: support R device. See b/234727688.
    @Test
    @IgnoreUpTo(Build.VERSION_CODES.R)
    public void testTetherClatUdp() throws Exception {
        runClatUdpTest();
    }

    @Test
    public void testIcmpv4Echo() throws Exception {
        final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
                toList(TEST_IP4_DNS));
        final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);

        // TODO: remove the connectivity verification for upstream connected notification race.
        // See the same reason in runUdp4Test().
        probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);

        final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */,
                tethered.routerMacAddr /* dstMac */, tethered.ipv4Addr /* srcIp */,
                REMOTE_IP4_ADDR /* dstIp */, ICMP_ECHO, ICMPECHO_ID, ICMPECHO_SEQ);
        tester.verifyUpload(request, p -> {
            Log.d(TAG, "Packet in upstream: " + dumpHexString(p));

            return isExpectedIcmpPacket(p, false /* hasEth */, true /* isIpv4 */, ICMP_ECHO);
        });

        final ByteBuffer reply = buildIcmpEchoPacketV4(REMOTE_IP4_ADDR /* srcIp*/,
                (Inet4Address) TEST_IP4_ADDR.getAddress() /* dstIp */, ICMP_ECHOREPLY, ICMPECHO_ID,
                ICMPECHO_SEQ);
        tester.verifyDownload(reply, p -> {
            Log.d(TAG, "Packet in downstream: " + dumpHexString(p));

            return isExpectedIcmpPacket(p, true /* hasEth */, true /* isIpv4 */, ICMP_ECHOREPLY);
        });
    }

    // TODO: support R device. See b/234727688.
    @Test
    @IgnoreUpTo(Build.VERSION_CODES.R)
    public void testTetherClatIcmp() throws Exception {
        // CLAT only starts on IPv6 only network.
        final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
                toList(TEST_IP6_DNS));
        final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);

        // Get CLAT IPv6 address.
        final Inet6Address clatIp6 = getClatIpv6Address(tester, tethered);

        // Send an IPv4 ICMP packet in original direction.
        // IPv4 packet -- CLAT translation --> IPv6 packet
        final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */,
                tethered.routerMacAddr /* dstMac */, tethered.ipv4Addr /* srcIp */,
                (Inet4Address) REMOTE_IP4_ADDR /* dstIp */, ICMP_ECHO, ICMPECHO_ID, ICMPECHO_SEQ);
        tester.verifyUpload(request, p -> {
            Log.d(TAG, "Packet in upstream: " + dumpHexString(p));

            return isExpectedIcmpPacket(p, false /* hasEth */, false /* isIpv4 */,
                    ICMPV6_ECHO_REQUEST_TYPE);
        });

        // Send an IPv6 ICMP packet in reply direction.
        // IPv6 packet -- CLAT translation --> IPv4 packet
        final ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(
                (Inet6Address) REMOTE_NAT64_ADDR /* srcIp */, clatIp6 /* dstIp */);
        tester.verifyDownload(reply, p -> {
            Log.d(TAG, "Packet in downstream: " + dumpHexString(p));

            return isExpectedIcmpPacket(p, true /* hasEth */, true /* isIpv4 */, ICMP_ECHOREPLY);
        });
    }

    @NonNull
    private ByteBuffer buildDnsReplyMessageById(short id) {
        byte[] replyMessage = Arrays.copyOf(DNS_REPLY, DNS_REPLY.length);
        // Assign transaction id of reply message pattern with a given DNS transaction id.
        replyMessage[0] = (byte) ((id >> 8) & 0xff);
        replyMessage[1] = (byte) (id & 0xff);
        Log.d(TAG, "Built DNS reply: " + dumpHexString(replyMessage));

        return ByteBuffer.wrap(replyMessage);
    }

    @NonNull
    private void sendDownloadPacketDnsV4(@NonNull final Inet4Address srcIp,
            @NonNull final Inet4Address dstIp, short srcPort, short dstPort, short dnsId,
            @NonNull final TetheringTester tester) throws Exception {
        // DNS response transaction id must be copied from DNS query. Used by the requester
        // to match up replies to outstanding queries. See RFC 1035 section 4.1.1.
        final ByteBuffer dnsReplyMessage = buildDnsReplyMessageById(dnsId);
        final ByteBuffer testPacket = buildUdpPacket((InetAddress) srcIp,
                (InetAddress) dstIp, srcPort, dstPort, dnsReplyMessage);

        tester.verifyDownload(testPacket, p -> {
            Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
            return isExpectedUdpDnsPacket(p, true /* hasEther */, true /* isIpv4 */,
                    dnsReplyMessage);
        });
    }

    // Send IPv4 UDP DNS packet and return the forwarded DNS packet on upstream.
    @NonNull
    private byte[] sendUploadPacketDnsV4(@NonNull final MacAddress srcMac,
            @NonNull final MacAddress dstMac, @NonNull final Inet4Address srcIp,
            @NonNull final Inet4Address dstIp, short srcPort, short dstPort,
            @NonNull final TetheringTester tester) throws Exception {
        final ByteBuffer testPacket = buildUdpPacket(srcMac, dstMac, srcIp, dstIp,
                srcPort, dstPort, DNS_QUERY);

        return tester.verifyUpload(testPacket, p -> {
            Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
            return isExpectedUdpDnsPacket(p, false /* hasEther */, true /* isIpv4 */,
                    DNS_QUERY);
        });
    }

    @Test
    public void testTetherUdpV4Dns() throws Exception {
        final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
                toList(TEST_IP4_DNS));
        final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);

        // TODO: remove the connectivity verification for upstream connected notification race.
        // See the same reason in runUdp4Test().
        probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);

        // [1] Send DNS query.
        // tethered device --> downstream --> dnsmasq forwarding --> upstream --> DNS server
        //
        // Need to extract DNS transaction id and source port from dnsmasq forwarded DNS query
        // packet. dnsmasq forwarding creats new query which means UDP source port and DNS
        // transaction id are changed from original sent DNS query. See forward_query() in
        // external/dnsmasq/src/forward.c. Note that #TetheringTester.isExpectedUdpDnsPacket
        // guarantees that |forwardedQueryPacket| is a valid DNS packet. So we can parse it as DNS
        // packet.
        final MacAddress srcMac = tethered.macAddr;
        final MacAddress dstMac = tethered.routerMacAddr;
        final Inet4Address clientIp = tethered.ipv4Addr;
        final Inet4Address gatewayIp = tethered.ipv4Gatway;
        final byte[] forwardedQueryPacket = sendUploadPacketDnsV4(srcMac, dstMac, clientIp,
                gatewayIp, LOCAL_PORT, DNS_PORT, tester);
        final ByteBuffer buf = ByteBuffer.wrap(forwardedQueryPacket);
        Struct.parse(Ipv4Header.class, buf);
        final UdpHeader udpHeader = Struct.parse(UdpHeader.class, buf);
        final TestDnsPacket dnsQuery = TestDnsPacket.getTestDnsPacket(buf);
        assertNotNull(dnsQuery);
        Log.d(TAG, "Forwarded UDP source port: " + udpHeader.srcPort + ", DNS query id: "
                + dnsQuery.getHeader().getId());

        // [2] Send DNS reply.
        // DNS server --> upstream --> dnsmasq forwarding --> downstream --> tethered device
        //
        // DNS reply transaction id must be copied from DNS query. Used by the requester to match
        // up replies to outstanding queries. See RFC 1035 section 4.1.1.
        final Inet4Address remoteIp = (Inet4Address) TEST_IP4_DNS;
        final Inet4Address tetheringUpstreamIp = (Inet4Address) TEST_IP4_ADDR.getAddress();
        sendDownloadPacketDnsV4(remoteIp, tetheringUpstreamIp, DNS_PORT,
                (short) udpHeader.srcPort, (short) dnsQuery.getHeader().getId(), tester);
    }

    @Test
    public void testTetherTcpV4() throws Exception {
        final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
                toList(TEST_IP4_DNS));
        final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);

        // TODO: remove the connectivity verification for upstream connected notification race.
        // See the same reason in runUdp4Test().
        probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);

        runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
                tethered.ipv4Addr /* uploadSrcIp */, REMOTE_IP4_ADDR /* uploadDstIp */,
                REMOTE_IP4_ADDR /* downloadSrcIp */, TEST_IP4_ADDR.getAddress() /* downloadDstIp */,
                tester, false /* isClat */);
    }

    @Test
    public void testTetherTcpV6() throws Exception {
        final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
                toList(TEST_IP6_DNS));
        final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);

        runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
                tethered.ipv6Addr /* uploadSrcIp */, REMOTE_IP6_ADDR /* uploadDstIp */,
                REMOTE_IP6_ADDR /* downloadSrcIp */, tethered.ipv6Addr /* downloadDstIp */,
                tester, false /* isClat */);
    }

    // TODO: support R device. See b/234727688.
    @Test
    @IgnoreUpTo(Build.VERSION_CODES.R)
    public void testTetherClatTcp() throws Exception {
        // CLAT only starts on IPv6 only network.
        final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
                toList(TEST_IP6_DNS));
        final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);

        // Get CLAT IPv6 address.
        final Inet6Address clatIp6 = getClatIpv6Address(tester, tethered);

        runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
                tethered.ipv4Addr /* uploadSrcIp */, REMOTE_IP4_ADDR /* uploadDstIp */,
                REMOTE_NAT64_ADDR /* downloadSrcIp */, clatIp6 /* downloadDstIp */,
                tester, true /* isClat */);
    }

    private static final byte[] ZeroLengthDhcpPacket = new byte[] {
            // scapy.Ether(
            //   dst="ff:ff:ff:ff:ff:ff")
            // scapy.IP(
            //   dst="255.255.255.255")
            // scapy.UDP(sport=68, dport=67)
            /* Ethernet Header */
            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
            (byte) 0xe0, (byte) 0x4f, (byte) 0x43, (byte) 0xe6, (byte) 0xfb, (byte) 0xd2,
            (byte) 0x08, (byte) 0x00,
            /* Ip header */
            (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x1c, (byte) 0x00, (byte) 0x01,
            (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x11, (byte) 0xb6, (byte) 0x58,
            (byte) 0x64, (byte) 0x4f, (byte) 0x60, (byte) 0x29, (byte) 0xff, (byte) 0xff,
            (byte) 0xff, (byte) 0xff,
            /* UDP header */
            (byte) 0x00, (byte) 0x44, (byte) 0x00, (byte) 0x43,
            (byte) 0x00, (byte) 0x08, (byte) 0x3a, (byte) 0xdf
    };

    // This test requires the update in NetworkStackModule(See b/269692093).
    @NetworkStackModuleTest
    @Test
    public void testTetherZeroLengthDhcpPacket() throws Exception {
        final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
                toList(TEST_IP4_DNS));
        tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);

        // Send a zero-length DHCP packet to upstream DHCP server.
        final ByteBuffer packet = ByteBuffer.wrap(ZeroLengthDhcpPacket);
        tester.sendUploadPacket(packet);

        // Send DHCPDISCOVER packet from another downstream tethered device to verify that
        // upstream DHCP server doesn't close the listening socket and stop reading, then we
        // can still receive the next DHCP packet from server.
        final MacAddress macAddress = MacAddress.fromString("11:22:33:44:55:66");
        assertTrue(tester.testDhcpServerAlive(macAddress));
    }
}