Implement IPv6 echo request offload for unicast ping6 replies.
This commit implements the logic to offload IPv6 echo requests,
generating ping6 replies to unicast requests.
Test: TH
Change-Id: I2ae6511d5219174cc3321905115b207a2fbe07cf
diff --git a/src/android/net/apf/ApfConstants.java b/src/android/net/apf/ApfConstants.java
index 4926550..c971fb8 100644
--- a/src/android/net/apf/ApfConstants.java
+++ b/src/android/net/apf/ApfConstants.java
@@ -126,6 +126,7 @@
public static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
public static final int ICMP6_CODE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN + 1;
public static final int ICMP6_CHECKSUM_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
+ public static final int ICMP6_CONTENT_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN + 4;
public static final int ICMP6_NS_TARGET_IP_OFFSET = ICMP6_TYPE_OFFSET + 8;
public static final int ICMP6_NS_OPTION_TYPE_OFFSET = ICMP6_NS_TARGET_IP_OFFSET + 16;
// From RFC4861:
diff --git a/src/android/net/apf/ApfCounterTracker.java b/src/android/net/apf/ApfCounterTracker.java
index 877d5cd..5a8511c 100644
--- a/src/android/net/apf/ApfCounterTracker.java
+++ b/src/android/net/apf/ApfCounterTracker.java
@@ -79,6 +79,7 @@
DROPPED_IPV4_PING_REQUEST_REPLIED,
DROPPED_IPV4_UDP_INVALID,
DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID,
+ DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED,
DROPPED_IPV6_ROUTER_SOLICITATION,
DROPPED_IPV6_MULTICAST_NA,
DROPPED_IPV6_NON_ICMP_MULTICAST,
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index c1b1e8b..6d54a75 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -46,6 +46,7 @@
import static android.net.apf.ApfConstants.ICMP6_CAPTIVE_PORTAL_OPTION_TYPE;
import static android.net.apf.ApfConstants.ICMP6_CHECKSUM_OFFSET;
import static android.net.apf.ApfConstants.ICMP6_CODE_OFFSET;
+import static android.net.apf.ApfConstants.ICMP6_CONTENT_OFFSET;
import static android.net.apf.ApfConstants.ICMP6_DNSSL_OPTION_TYPE;
import static android.net.apf.ApfConstants.ICMP6_ECHO_REQUEST_HEADER_LEN;
import static android.net.apf.ApfConstants.ICMP6_MTU_OPTION_TYPE;
@@ -133,6 +134,7 @@
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_PING_REQUEST_REPLIED;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_TCP_PORT7_UNICAST;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_NA;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NON_ICMP_MULTICAST;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID;
@@ -181,6 +183,7 @@
import static android.system.OsConstants.ETH_P_ARP;
import static android.system.OsConstants.ETH_P_IP;
import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.ICMP6_ECHO_REPLY;
import static android.system.OsConstants.ICMP_ECHO;
import static android.system.OsConstants.ICMP_ECHOREPLY;
import static android.system.OsConstants.IFA_F_TENTATIVE;
@@ -745,6 +748,13 @@
}
/**
+ * Returns the default HopLimit value for IPv6 packets.
+ */
+ public int getIpv6DefaultHopLimit(@NonNull String ifname) {
+ return ProcfsParsingUtils.getIpv6DefaultHopLimit(ifname);
+ }
+
+ /**
* Loads the existing IPv4 multicast addresses from the file
* `/proc/net/igmp`.
*/
@@ -2426,8 +2436,39 @@
ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_ECHO_REQUEST_HEADER_LEN,
DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID);
- // TODO: implement IPv6 ping reply
- gen.addCountAndPass(PASSED_IPV6_ICMP);
+ int hopLimit = mDependencies.getIpv6DefaultHopLimit(mInterfaceParams.name);
+ // Construct the ICMPv6 echo reply packet.
+ gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
+ .addAllocateR0()
+ // Eth header
+ .addPacketCopy(ETHER_SRC_ADDR_OFFSET, ETHER_ADDR_LEN) // Dst MAC address
+ .addDataCopy(mHardwareAddress) // Src MAC address
+ // Reuse the following fields from input packet
+ // 2 byte: ethertype
+ // 4 bytes: version, traffic class, flowlabel
+ // 2 bytes: payload length
+ // 1 byte: next header
+ .addPacketCopy(ETH_ETHERTYPE_OFFSET, 9)
+ .addWriteU8(hopLimit)
+ .addPacketCopy(IPV6_DEST_ADDR_OFFSET, IPV6_ADDR_LEN) // Src ip
+ .addPacketCopy(IPV6_SRC_ADDR_OFFSET, IPV6_ADDR_LEN) // Dst ip
+ .addWriteU16((ICMP6_ECHO_REPLY << 8) | 0) // Type: echo reply, code: 0
+ // Checksum: initialized to the IPv6 payload length as a partial checksum. The final
+ // checksum will be calculated by the interpreter.
+ .addPacketCopy(IPV6_PAYLOAD_LEN_OFFSET, 2)
+ // Copy identifier, sequence number and ping payload
+ .addSub(ICMP6_CONTENT_OFFSET)
+ .addLoadImmediate(R1, ICMP6_CONTENT_OFFSET)
+ .addSwap() // Swaps R0 and R1, so they're the offset and length.
+ .addPacketCopyFromR0LenR1()
+ .addTransmitL4(
+ ETHER_HEADER_LEN, // ip_ofs
+ ICMP6_CHECKSUM_OFFSET, // csum_ofs
+ IPV6_SRC_ADDR_OFFSET, // csum_start
+ IPPROTO_ICMPV6, // partial_sum
+ false // udp
+ )
+ .addCountAndDrop(DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED);
gen.defineLabel(skipPing6Offload);
}
@@ -2501,7 +2542,7 @@
// transmit NA and drop
//
// (APFv6+ specific logic) if it's unicast ICMPv6 echo request to our host:
- // pass
+ // transmit echo reply and drop
//
// if it's ICMPv6 RS to any:
// drop
diff --git a/tests/unit/src/android/net/apf/ApfFilterTest.kt b/tests/unit/src/android/net/apf/ApfFilterTest.kt
index 71f7fb2..b2d7961 100644
--- a/tests/unit/src/android/net/apf/ApfFilterTest.kt
+++ b/tests/unit/src/android/net/apf/ApfFilterTest.kt
@@ -45,6 +45,7 @@
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_PING_REQUEST_REPLIED
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_TCP_PORT7_UNICAST
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_NA
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NON_ICMP_MULTICAST
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID
@@ -2819,7 +2820,8 @@
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
- fun testIpv6EchoRequestPassed() {
+ fun testIpv6EchoRequestReplied() {
+ doReturn(64).`when`(dependencies).getIpv6DefaultHopLimit(ifParams.name)
val (apfFilter, program) = getApfWithIpv6PingOffloadEnabled()
// Using scapy to generate IPv6 echo request packet:
// eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07")
@@ -2836,7 +2838,38 @@
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(ipv6EchoRequestPkt),
- PASSED_IPV6_ICMP
+ DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED
+ )
+ val transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0]
+
+ // ###[ Ethernet ]###
+ // dst = 01:02:03:04:05:06
+ // src = 02:03:04:05:06:07
+ // type = IPv6
+ // ###[ IPv6 ]###
+ // version = 6
+ // tc = 0
+ // fl = 0
+ // plen = 13
+ // nh = ICMPv6
+ // hlim = 64
+ // src = fe80::3
+ // dst = fe80::1
+ // ###[ ICMPv6 Echo Reply ]###
+ // type = Echo Reply
+ // code = 0
+ // cksum = 0x3d64
+ // id = 0x1
+ // seq = 0x7b
+ // data = b'hello'
+ val expectedReply = """
+ 01020304050602030405060786DD60000000000D3A40FE80000000000000000
+ 0000000000003FE80000000000000000000000000000181003D640001007B68
+ 656C6C6F
+ """.replace("\\s+".toRegex(), "").trim()
+ assertContentEquals(
+ HexDump.hexStringToByteArray(expectedReply),
+ transmitPkt
)
}