Merge "Fix RAs with different retansmission timer would be dropped by apf"
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 33f69f4..75a737d 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -514,9 +514,9 @@
         public final Type type;
         /** Offset into the packet at which this section begins. */
         public final int start;
-        /** Length of this section. */
+        /** Length of this section in bytes. */
         public final int length;
-        /** If this is a lifetime, the ICMP option that the defined it. 0 for router lifetime. */
+        /** If this is a lifetime, the ICMP option that defined it. 0 for router lifetime. */
         public final int option;
         /** If this is a lifetime, the lifetime value. */
         public final long lifetime;
@@ -785,8 +785,9 @@
             addLifetimeSection(ICMP6_RA_ROUTER_LIFETIME_LEN, 0, routerLifetime);
             builder.updateRouterLifetime(routerLifetime);
 
-            // Ensures that the RA is not truncated.
-            mPacket.position(ICMP6_RA_OPTION_OFFSET);
+            // Add remaining fields (reachable time and retransmission timer) to match section.
+            addMatchUntil(ICMP6_RA_OPTION_OFFSET);
+
             while (mPacket.hasRemaining()) {
                 final int position = mPacket.position();
                 final int optionType = getUint8(mPacket, position);
@@ -797,7 +798,7 @@
                         mPrefixOptionOffsets.add(position);
 
                         // Parse valid lifetime
-                        addMatchSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN);
+                        addMatchSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET);
                         lifetime = getUint32(mPacket, mPacket.position());
                         addLifetimeSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN,
                                 ICMP6_PREFIX_OPTION_TYPE, lifetime);
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java
index 24c67d7..ab92892 100644
--- a/tests/unit/src/android/net/apf/ApfTest.java
+++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -1056,10 +1056,14 @@
     private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
 
     private static final int ICMP6_RA_HEADER_LEN = 16;
-    private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
-            IP_HEADER_OFFSET + IPV6_HEADER_LEN + 6;
     private static final int ICMP6_RA_CHECKSUM_OFFSET =
             IP_HEADER_OFFSET + IPV6_HEADER_LEN + 2;
+    private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
+            IP_HEADER_OFFSET + IPV6_HEADER_LEN + 6;
+    private static final int ICMP6_RA_REACHABLE_TIME_OFFSET =
+            IP_HEADER_OFFSET + IPV6_HEADER_LEN + 8;
+    private static final int ICMP6_RA_RETRANSMISSION_TIMER_OFFSET =
+            IP_HEADER_OFFSET + IPV6_HEADER_LEN + 12;
     private static final int ICMP6_RA_OPTION_OFFSET =
             IP_HEADER_OFFSET + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
 
@@ -2000,6 +2004,25 @@
         ipClientCallback.assertNoProgramUpdate();
     }
 
+    private ByteBuffer makeBaseRaPacket() {
+        ByteBuffer basePacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
+        final int ROUTER_LIFETIME = 1000;
+        final int VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET = ETH_HEADER_LEN;
+        // IPv6, traffic class = 0, flow label = 0x12345
+        final int VERSION_TRAFFIC_CLASS_FLOW_LABEL = 0x60012345;
+
+        basePacket.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6);
+        basePacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET,
+                VERSION_TRAFFIC_CLASS_FLOW_LABEL);
+        basePacket.put(IPV6_NEXT_HEADER_OFFSET, (byte) IPPROTO_ICMPV6);
+        basePacket.put(ICMP6_TYPE_OFFSET, (byte) ICMP6_ROUTER_ADVERTISEMENT);
+        basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short) ROUTER_LIFETIME);
+        basePacket.position(IPV6_DEST_ADDR_OFFSET);
+        basePacket.put(IPV6_ALL_NODES_ADDRESS);
+
+        return basePacket;
+    }
+
     @Test
     public void testApfFilterRa() throws Exception {
         MockIpClientCallback ipClientCallback = new MockIpClientCallback();
@@ -2021,15 +2044,7 @@
         final int VERSION_TRAFFIC_CLASS_FLOW_LABEL = 0x60012345;
 
         // Verify RA is passed the first time
-        ByteBuffer basePacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
-        basePacket.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
-        basePacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET,
-                VERSION_TRAFFIC_CLASS_FLOW_LABEL);
-        basePacket.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
-        basePacket.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_ADVERTISEMENT);
-        basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short)ROUTER_LIFETIME);
-        basePacket.position(IPV6_DEST_ADDR_OFFSET);
-        basePacket.put(IPV6_ALL_NODES_ADDRESS);
+        ByteBuffer basePacket = makeBaseRaPacket();
         assertPass(program, basePacket.array());
 
         verifyRaLifetime(apfFilter, ipClientCallback, basePacket, ROUTER_LIFETIME);
@@ -2083,6 +2098,16 @@
         verifyRaLifetime(apfFilter, ipClientCallback, routeInfoOptionPacket, ROUTE_LIFETIME);
         verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, ROUTE_LIFETIME, -1, -1));
 
+        // Check that RIOs differing only in the first 4 bytes are different.
+        ByteBuffer similarRouteInfoOptionPacket = ByteBuffer.wrap(
+                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN + IPV6_ADDR_LEN]);
+        basePacket.clear();
+        similarRouteInfoOptionPacket.put(basePacket);
+        addRioOption(similarRouteInfoOptionPacket, ROUTE_LIFETIME, "64:ff9b::/64");
+        // Packet should be passed because it is different.
+        program = ipClientCallback.getApfProgram();
+        assertPass(program, similarRouteInfoOptionPacket.array());
+
         ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(
                 new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
         basePacket.clear();
@@ -2111,6 +2136,46 @@
         apfFilter.shutdown();
     }
 
+    @Test
+    public void testRaWithDifferentReachableTimeAndRetransTimer() throws Exception {
+        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+        final ApfConfiguration config = getDefaultConfig();
+        config.multicastFilter = DROP_MULTICAST;
+        config.ieee802_3Filter = DROP_802_3_FRAMES;
+        final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+        byte[] program = ipClientCallback.getApfProgram();
+        final int RA_REACHABLE_TIME = 1800;
+        final int RA_RETRANSMISSION_TIMER = 1234;
+
+        // Create an Ra packet without options
+        // Reachable time = 1800, retransmission timer = 1234
+        ByteBuffer raPacket = makeBaseRaPacket();
+        raPacket.position(ICMP6_RA_REACHABLE_TIME_OFFSET);
+        raPacket.putInt(RA_REACHABLE_TIME);
+        raPacket.putInt(RA_RETRANSMISSION_TIMER);
+        // First RA passes filter
+        assertPass(program, raPacket.array());
+
+        // Assume apf is shown the given RA, it generates program to filter it.
+        ipClientCallback.resetApfProgramWait();
+        apfFilter.pretendPacketReceived(raPacket.array());
+        program = ipClientCallback.getApfProgram();
+        assertDrop(program, raPacket.array());
+
+        // A packet with different reachable time should be passed.
+        // Reachable time = 2300, retransmission timer = 1234
+        raPacket.clear();
+        raPacket.putInt(ICMP6_RA_REACHABLE_TIME_OFFSET, RA_REACHABLE_TIME + 500);
+        assertPass(program, raPacket.array());
+
+        // A packet with different retransmission timer should be passed.
+        // Reachable time = 1800, retransmission timer = 2234
+        raPacket.clear();
+        raPacket.putInt(ICMP6_RA_REACHABLE_TIME_OFFSET, RA_REACHABLE_TIME);
+        raPacket.putInt(ICMP6_RA_RETRANSMISSION_TIMER_OFFSET, RA_RETRANSMISSION_TIMER + 1000);
+        assertPass(program, raPacket.array());
+    }
+
     /**
      * Stage a file for testing, i.e. make it native accessible. Given a resource ID,
      * copy that resource into the app's data directory and return the path to it.