Merge "Add Fragmented Packet Test to testTetherClatBpfOffloadUdp" into main
This commit is contained in:
commit
cc6c9d3de7
3 changed files with 167 additions and 5 deletions
|
|
@ -26,7 +26,9 @@ import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
|
|||
import static android.net.TetheringManager.TETHERING_ETHERNET;
|
||||
import static android.net.TetheringTester.buildTcpPacket;
|
||||
import static android.net.TetheringTester.buildUdpPacket;
|
||||
import static android.net.TetheringTester.buildUdpPackets;
|
||||
import static android.net.TetheringTester.isAddressIpv4;
|
||||
import static android.net.TetheringTester.isExpectedFragmentIpPacket;
|
||||
import static android.net.TetheringTester.isExpectedIcmpPacket;
|
||||
import static android.net.TetheringTester.isExpectedTcpPacket;
|
||||
import static android.net.TetheringTester.isExpectedUdpPacket;
|
||||
|
|
@ -58,12 +60,14 @@ import android.net.cts.util.CtsNetUtils;
|
|||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.SystemClock;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import com.android.net.module.util.Struct;
|
||||
import com.android.net.module.util.structs.FragmentHeader;
|
||||
import com.android.net.module.util.structs.Ipv6Header;
|
||||
import com.android.testutils.HandlerUtils;
|
||||
import com.android.testutils.TapPacketReader;
|
||||
|
|
@ -678,6 +682,57 @@ public abstract class EthernetTetheringTestBase {
|
|||
});
|
||||
}
|
||||
|
||||
protected void sendDownloadFragmentedUdpPackets(@NonNull final Inet6Address srcIp,
|
||||
@NonNull final Inet6Address dstIp, @NonNull final TetheringTester tester,
|
||||
@NonNull final ByteBuffer payload, int l2mtu) throws Exception {
|
||||
final List<ByteBuffer> testPackets = buildUdpPackets(null /* srcMac */, null /* dstMac */,
|
||||
srcIp, dstIp, REMOTE_PORT, LOCAL_PORT, payload, l2mtu);
|
||||
assertTrue("No packet fragmentation occurs", testPackets.size() > 1);
|
||||
|
||||
short id = 0;
|
||||
final ArrayMap<Short, ByteBuffer> fragmentPayloads = new ArrayMap<>();
|
||||
for (ByteBuffer testPacket : testPackets) {
|
||||
Struct.parse(Ipv6Header.class, testPacket);
|
||||
final FragmentHeader fragmentHeader = Struct.parse(FragmentHeader.class, testPacket);
|
||||
// Conversion of IPv6's fragmentOffset field to IPv4's flagsAndFragmentOffset field.
|
||||
// IPv6 Fragment Header:
|
||||
// '13 bits of offset in multiples of 8' + 2 zero bits + more fragment bit
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Next Header | Reserved | Fragment Offset |Res|M|
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Identification |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// IPv4 Header:
|
||||
// zero bit + don't frag bit + more frag bit + '13 bits of offset in multiples of 8'
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |Version| IHL |Type of Service| Total Length |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Identification |Flags| Fragment Offset |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// + . . . +
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
short offset = (short) (((fragmentHeader.fragmentOffset & 0x1) << 13)
|
||||
| (fragmentHeader.fragmentOffset >> 3));
|
||||
// RFC6145: for fragment id, copied from the low-order 16 bits in the identification
|
||||
// field in the Fragment Header.
|
||||
id = (short) (fragmentHeader.identification & 0xffff);
|
||||
final byte[] fragmentPayload = new byte[testPacket.remaining()];
|
||||
testPacket.get(fragmentPayload);
|
||||
testPacket.flip();
|
||||
fragmentPayloads.put(offset, ByteBuffer.wrap(fragmentPayload));
|
||||
}
|
||||
|
||||
final short fragId = id;
|
||||
tester.verifyDownloadBatch(testPackets, p -> {
|
||||
Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
|
||||
return isExpectedFragmentIpPacket(p, fragId, fragmentPayloads);
|
||||
});
|
||||
}
|
||||
|
||||
protected void sendDownloadPacketTcp(@NonNull final InetAddress srcIp,
|
||||
@NonNull final InetAddress dstIp, short seq, short ack, byte tcpFlags,
|
||||
@NonNull final ByteBuffer payload, @NonNull final TetheringTester tester,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import static android.system.OsConstants.IPPROTO_IP;
|
|||
import static android.system.OsConstants.IPPROTO_IPV6;
|
||||
import static android.system.OsConstants.IPPROTO_TCP;
|
||||
import static android.system.OsConstants.IPPROTO_UDP;
|
||||
|
||||
import static com.android.net.module.util.DnsPacket.ANSECTION;
|
||||
import static com.android.net.module.util.DnsPacket.DnsHeader;
|
||||
import static com.android.net.module.util.DnsPacket.DnsRecord;
|
||||
|
|
@ -53,6 +54,7 @@ import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NO
|
|||
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 com.android.net.module.util.NetworkStackConstants.TCPHDR_SYN;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
|
|
@ -91,6 +93,7 @@ import java.nio.ByteBuffer;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Predicate;
|
||||
|
|
@ -497,6 +500,51 @@ public final class TetheringTester {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given raw packet data represents an expected fragmented IP packet.
|
||||
*
|
||||
* @param rawPacket the raw packet data to check.
|
||||
* @param id the identification field of the fragmented IP packet.
|
||||
* @param expectedPayloads a map of fragment offsets to their corresponding payload data.
|
||||
* @return true if the packet is a valid fragmented IP packet with matching payload fragments;
|
||||
* false otherwise.
|
||||
*/
|
||||
public static boolean isExpectedFragmentIpPacket(@NonNull final byte[] rawPacket, int id,
|
||||
@NonNull final Map<Short, ByteBuffer> expectedPayloads) {
|
||||
final ByteBuffer buf = ByteBuffer.wrap(rawPacket);
|
||||
try {
|
||||
// Validate Ethernet header and IPv4 header.
|
||||
if (!hasExpectedEtherHeader(buf, true /* isIpv4 */)) return false;
|
||||
final Ipv4Header ipv4Header = Struct.parse(Ipv4Header.class, buf);
|
||||
if (ipv4Header.protocol != (byte) IPPROTO_UDP) return false;
|
||||
if (ipv4Header.id != id) return false;
|
||||
// Validate payload data which expected at a specific fragment offset.
|
||||
final ByteBuffer expectedPayload =
|
||||
expectedPayloads.get(ipv4Header.flagsAndFragmentOffset);
|
||||
if (expectedPayload == null) return false;
|
||||
if (buf.remaining() != expectedPayload.limit()) return false;
|
||||
// Validate UDP header (which located in the 1st fragment).
|
||||
// TODO: Validate the checksum field in UDP header. Currently, it'll be altered by NAT.
|
||||
if ((ipv4Header.flagsAndFragmentOffset & 0x1FFF) == 0) {
|
||||
final UdpHeader receivedUdpHeader = Struct.parse(UdpHeader.class, buf);
|
||||
final UdpHeader expectedUdpHeader = Struct.parse(UdpHeader.class, expectedPayload);
|
||||
if (receivedUdpHeader == null || expectedUdpHeader == null) return false;
|
||||
if (receivedUdpHeader.srcPort != expectedUdpHeader.srcPort
|
||||
|| receivedUdpHeader.dstPort != expectedUdpHeader.dstPort
|
||||
|| receivedUdpHeader.length != expectedUdpHeader.length) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Check the contents of the remaining payload.
|
||||
return Arrays.equals(getRemaining(buf),
|
||||
getRemaining(expectedPayload.asReadOnlyBuffer()));
|
||||
} catch (Exception e) {
|
||||
// A failed packet parsing indicates that the packet is not a fragmented IPv4 packet.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// |expectedPayload| is copied as read-only because the caller may reuse it.
|
||||
// See hasExpectedDnsMessage.
|
||||
public static boolean isExpectedUdpDnsPacket(@NonNull final byte[] rawPacket, boolean hasEth,
|
||||
|
|
@ -683,10 +731,10 @@ public final class TetheringTester {
|
|||
}
|
||||
|
||||
@NonNull
|
||||
public static ByteBuffer buildUdpPacket(
|
||||
public static List<ByteBuffer> buildUdpPackets(
|
||||
@Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
|
||||
@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp,
|
||||
short srcPort, short dstPort, @Nullable final ByteBuffer payload)
|
||||
short srcPort, short dstPort, @Nullable final ByteBuffer payload, int l2mtu)
|
||||
throws Exception {
|
||||
final int ipProto = getIpProto(srcIp, dstIp);
|
||||
final boolean hasEther = (srcMac != null && dstMac != null);
|
||||
|
|
@ -720,7 +768,30 @@ public final class TetheringTester {
|
|||
payload.clear();
|
||||
}
|
||||
|
||||
return packetBuilder.finalizePacket();
|
||||
return l2mtu == 0
|
||||
? Arrays.asList(packetBuilder.finalizePacket())
|
||||
: packetBuilder.finalizePacket(l2mtu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a UDP packet.
|
||||
*
|
||||
* @param srcMac the source MAC address.
|
||||
* @param dstMac the destination MAC address.
|
||||
* @param srcIp the source IP address.
|
||||
* @param dstIp the destination IP address.
|
||||
* @param srcPort the source port number.
|
||||
* @param dstPort the destination port number.
|
||||
* @param payload the optional payload data to be included in the packet.
|
||||
* @return a ByteBuffer containing the constructed UDP packet.
|
||||
*/
|
||||
@NonNull
|
||||
public static ByteBuffer buildUdpPacket(
|
||||
@Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
|
||||
@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp,
|
||||
short srcPort, short dstPort, @Nullable final ByteBuffer payload)
|
||||
throws Exception {
|
||||
return buildUdpPackets(srcMac, dstMac, srcIp, dstIp, srcPort, dstPort, payload, 0).get(0);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
|
@ -994,6 +1065,28 @@ public final class TetheringTester {
|
|||
return verifyPacketNotNull("Download fail", getDownloadPacket(filter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a batch of download packets and verifies against a specified filtering condition.
|
||||
*
|
||||
* This method is designed for testing fragmented packets. All packets are sent before
|
||||
* verification because the kernel buffers fragments until the last one is received.
|
||||
* Captured packets are then verified against the provided filter.
|
||||
*
|
||||
* @param packets the list of ByteBuffers containing the packets to send.
|
||||
* @param filter a Predicate that defines the filtering condition to apply to each received
|
||||
* packet. If the filter returns true for a packet's data, it is considered to
|
||||
* meet the verification criteria.
|
||||
*/
|
||||
public void verifyDownloadBatch(final List<ByteBuffer> packets, final Predicate<byte[]> filter)
|
||||
throws Exception {
|
||||
for (ByteBuffer packet : packets) {
|
||||
sendDownloadPacket(packet);
|
||||
}
|
||||
for (int i = 0; i < packets.size(); ++i) {
|
||||
verifyPacketNotNull("Download fail", getDownloadPacket(filter));
|
||||
}
|
||||
}
|
||||
|
||||
// Send DHCPDISCOVER to DHCP server to see if DHCP server is still alive to handle
|
||||
// the upcoming DHCP packets. This method should be only used when we know the DHCP
|
||||
// server has been created successfully before.
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPL
|
|||
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
|
||||
import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
|
||||
import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
|
||||
import static com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN;
|
||||
import static com.android.testutils.DeviceInfoUtils.KVersion;
|
||||
import static com.android.testutils.TestPermissionUtil.runAsShell;
|
||||
|
||||
|
|
@ -1130,12 +1131,25 @@ public class EthernetTetheringTest extends EthernetTetheringTestBase {
|
|||
sendDownloadPacketUdp(REMOTE_NAT64_ADDR, clatIp6, tester, true /* is6To4 */);
|
||||
}
|
||||
|
||||
// Send fragmented IPv6 UDP packets in the reply direction.
|
||||
// IPv6 frament packet -- CLAT translation --> IPv4 fragment packet
|
||||
final int payloadLen = 1500;
|
||||
final int l2mtu = 1000;
|
||||
final int fragPktCnt = 2; // 1500 bytes of UDP payload were fragmented into two packets.
|
||||
final long fragRxBytes = payloadLen + UDP_HEADER_LEN + fragPktCnt * IPV4_HEADER_MIN_LEN;
|
||||
final byte[] payload = new byte[payloadLen];
|
||||
// Initialize the payload with random bytes.
|
||||
Random random = new Random();
|
||||
random.nextBytes(payload);
|
||||
sendDownloadFragmentedUdpPackets(REMOTE_NAT64_ADDR, clatIp6, tester,
|
||||
ByteBuffer.wrap(payload), l2mtu);
|
||||
|
||||
// After sending test packets, get stats again to verify their differences.
|
||||
final ClatEgress4Value newEgress4 = getClatEgress4Value();
|
||||
final ClatIngress6Value newIngress6 = getClatIngress6Value();
|
||||
|
||||
assertEquals(RX_UDP_PACKET_COUNT, newIngress6.packets - oldIngress6.packets);
|
||||
assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE,
|
||||
assertEquals(RX_UDP_PACKET_COUNT + fragPktCnt, newIngress6.packets - oldIngress6.packets);
|
||||
assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE + fragRxBytes,
|
||||
newIngress6.bytes - oldIngress6.bytes);
|
||||
assertEquals(TX_UDP_PACKET_COUNT, newEgress4.packets - oldEgress4.packets);
|
||||
// The increase in egress traffic equals the expected size of the translated UDP packets.
|
||||
|
|
|
|||
Loading…
Reference in a new issue