summaryrefslogtreecommitdiff
path: root/tests/src/android/net/apf/ApfTest.java
diff options
context:
space:
mode:
authorRemi NGUYEN VAN <reminv@google.com>2019-01-10 19:12:46 +0900
committerRemi NGUYEN VAN <reminv@google.com>2019-01-23 16:44:21 +0900
commit4a2eb87a8e16874938fa1ce7d39bbfa7b59b9890 (patch)
tree45acea97a40f77dce3a557b121efe06254d7858f /tests/src/android/net/apf/ApfTest.java
parent3fab1c749775db02c1409009ff374542bad929cb (diff)
Move IpClient to NetworkStack
Test: atest FrameworksNetTests NetworkStackTests Bug: b/112869080 Change-Id: I7d00848c052382cd1b6ce458868bed6a1e9e8ec5
Diffstat (limited to 'tests/src/android/net/apf/ApfTest.java')
-rw-r--r--tests/src/android/net/apf/ApfTest.java1802
1 files changed, 1802 insertions, 0 deletions
diff --git a/tests/src/android/net/apf/ApfTest.java b/tests/src/android/net/apf/ApfTest.java
new file mode 100644
index 0000000..f76e412
--- /dev/null
+++ b/tests/src/android/net/apf/ApfTest.java
@@ -0,0 +1,1802 @@
+/*
+ * Copyright (C) 2012 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.apf;
+
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.ARPHRD_ETHER;
+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.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_STREAM;
+
+import static com.android.internal.util.BitUtils.bytesToBEInt;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.apf.ApfFilter.ApfConfiguration;
+import android.net.apf.ApfGenerator.IllegalInstructionException;
+import android.net.apf.ApfGenerator.Register;
+import android.net.ip.IIpClientCallbacks;
+import android.net.ip.IpClient;
+import android.net.ip.IpClient.IpClientCallbacksWrapper;
+import android.net.ip.IpClientCallbacks;
+import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.RaEvent;
+import android.net.util.InterfaceParams;
+import android.net.util.SharedLog;
+import android.os.ConditionVariable;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.format.DateUtils;
+import android.util.Log;
+
+import com.android.internal.util.HexDump;
+import com.android.server.networkstack.tests.R;
+import com.android.server.util.NetworkStackConstants;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Tests for APF program generator and interpreter.
+ *
+ * Build, install and run with:
+ * runtest frameworks-net -c android.net.apf.ApfTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ApfTest {
+ private static final int TIMEOUT_MS = 500;
+ private static final int MIN_APF_VERSION = 2;
+
+ @Mock IpConnectivityLog mLog;
+ @Mock Context mContext;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ // Load up native shared library containing APF interpreter exposed via JNI.
+ System.loadLibrary("networkstacktestsjni");
+ }
+
+ private static final String TAG = "ApfTest";
+ // Expected return codes from APF interpreter.
+ private static final int PASS = 1;
+ private static final int DROP = 0;
+ // Interpreter will just accept packets without link layer headers, so pad fake packet to at
+ // least the minimum packet size.
+ private static final int MIN_PKT_SIZE = 15;
+
+ private static final ApfCapabilities MOCK_APF_CAPABILITIES =
+ new ApfCapabilities(2, 1700, ARPHRD_ETHER);
+
+ private static final boolean DROP_MULTICAST = true;
+ private static final boolean ALLOW_MULTICAST = false;
+
+ private static final boolean DROP_802_3_FRAMES = true;
+ private static final boolean ALLOW_802_3_FRAMES = false;
+
+ // Constants for opcode encoding
+ private static final byte LI_OP = (byte)(13 << 3);
+ private static final byte LDDW_OP = (byte)(22 << 3);
+ private static final byte STDW_OP = (byte)(23 << 3);
+ private static final byte SIZE0 = (byte)(0 << 1);
+ private static final byte SIZE8 = (byte)(1 << 1);
+ private static final byte SIZE16 = (byte)(2 << 1);
+ private static final byte SIZE32 = (byte)(3 << 1);
+ private static final byte R1 = 1;
+
+ private static ApfConfiguration getDefaultConfig() {
+ ApfFilter.ApfConfiguration config = new ApfConfiguration();
+ config.apfCapabilities = MOCK_APF_CAPABILITIES;
+ config.multicastFilter = ALLOW_MULTICAST;
+ config.ieee802_3Filter = ALLOW_802_3_FRAMES;
+ config.ethTypeBlackList = new int[0];
+ return config;
+ }
+
+ private static String label(int code) {
+ switch (code) {
+ case PASS: return "PASS";
+ case DROP: return "DROP";
+ default: return "UNKNOWN";
+ }
+ }
+
+ private static void assertReturnCodesEqual(int expected, int got) {
+ assertEquals(label(expected), label(got));
+ }
+
+ private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) {
+ assertReturnCodesEqual(expected, apfSimulate(program, packet, null, filterAge));
+ }
+
+ private void assertVerdict(int expected, byte[] program, byte[] packet) {
+ assertReturnCodesEqual(expected, apfSimulate(program, packet, null, 0));
+ }
+
+ private void assertPass(byte[] program, byte[] packet, int filterAge) {
+ assertVerdict(PASS, program, packet, filterAge);
+ }
+
+ private void assertPass(byte[] program, byte[] packet) {
+ assertVerdict(PASS, program, packet);
+ }
+
+ private void assertDrop(byte[] program, byte[] packet, int filterAge) {
+ assertVerdict(DROP, program, packet, filterAge);
+ }
+
+ private void assertDrop(byte[] program, byte[] packet) {
+ assertVerdict(DROP, program, packet);
+ }
+
+ private void assertProgramEquals(byte[] expected, byte[] program) throws AssertionError {
+ // assertArrayEquals() would only print one byte, making debugging difficult.
+ if (!java.util.Arrays.equals(expected, program)) {
+ throw new AssertionError(
+ "\nexpected: " + HexDump.toHexString(expected) +
+ "\nactual: " + HexDump.toHexString(program));
+ }
+ }
+
+ private void assertDataMemoryContents(
+ int expected, byte[] program, byte[] packet, byte[] data, byte[] expected_data)
+ throws IllegalInstructionException, Exception {
+ assertReturnCodesEqual(expected, apfSimulate(program, packet, data, 0 /* filterAge */));
+
+ // assertArrayEquals() would only print one byte, making debugging difficult.
+ if (!java.util.Arrays.equals(expected_data, data)) {
+ throw new Exception(
+ "\nprogram: " + HexDump.toHexString(program) +
+ "\ndata memory: " + HexDump.toHexString(data) +
+ "\nexpected: " + HexDump.toHexString(expected_data));
+ }
+ }
+
+ private void assertVerdict(int expected, ApfGenerator gen, byte[] packet, int filterAge)
+ throws IllegalInstructionException {
+ assertReturnCodesEqual(expected, apfSimulate(gen.generate(), packet, null,
+ filterAge));
+ }
+
+ private void assertPass(ApfGenerator gen, byte[] packet, int filterAge)
+ throws IllegalInstructionException {
+ assertVerdict(PASS, gen, packet, filterAge);
+ }
+
+ private void assertDrop(ApfGenerator gen, byte[] packet, int filterAge)
+ throws IllegalInstructionException {
+ assertVerdict(DROP, gen, packet, filterAge);
+ }
+
+ private void assertPass(ApfGenerator gen)
+ throws IllegalInstructionException {
+ assertVerdict(PASS, gen, new byte[MIN_PKT_SIZE], 0);
+ }
+
+ private void assertDrop(ApfGenerator gen)
+ throws IllegalInstructionException {
+ assertVerdict(DROP, gen, new byte[MIN_PKT_SIZE], 0);
+ }
+
+ /**
+ * Test each instruction by generating a program containing the instruction,
+ * generating bytecode for that program and running it through the
+ * interpreter to verify it functions correctly.
+ */
+ @Test
+ public void testApfInstructions() throws IllegalInstructionException {
+ // Empty program should pass because having the program counter reach the
+ // location immediately after the program indicates the packet should be
+ // passed to the AP.
+ ApfGenerator gen = new ApfGenerator(MIN_APF_VERSION);
+ assertPass(gen);
+
+ // Test jumping to pass label.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJump(gen.PASS_LABEL);
+ byte[] program = gen.generate();
+ assertEquals(1, program.length);
+ assertEquals((14 << 3) | (0 << 1) | 0, program[0]);
+ assertPass(program, new byte[MIN_PKT_SIZE], 0);
+
+ // Test jumping to drop label.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJump(gen.DROP_LABEL);
+ program = gen.generate();
+ assertEquals(2, program.length);
+ assertEquals((14 << 3) | (1 << 1) | 0, program[0]);
+ assertEquals(1, program[1]);
+ assertDrop(program, new byte[15], 15);
+
+ // Test jumping if equal to 0.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if not equal to 0.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if registers equal.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0EqualsR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if registers not equal.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test load immediate.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test add.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addAdd(1234567890);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test subtract.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addAdd(-1234567890);
+ gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test or.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addOr(1234567890);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test and.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addAnd(123456789);
+ gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test left shift.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLeftShift(1);
+ gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test right shift.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addRightShift(1);
+ gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test multiply.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 123456789);
+ gen.addMul(2);
+ gen.addJumpIfR0Equals(123456789 * 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addDiv(2);
+ gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide by zero.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addDiv(0);
+ gen.addJump(gen.DROP_LABEL);
+ assertPass(gen);
+
+ // Test add.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addAddR1();
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test subtract.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, -1234567890);
+ gen.addAddR1();
+ gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test or.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addOrR1();
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test and.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, 123456789);
+ gen.addAndR1();
+ gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test left shift.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLeftShiftR1();
+ gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test right shift.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, -1);
+ gen.addLeftShiftR1();
+ gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test multiply.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 123456789);
+ gen.addLoadImmediate(Register.R1, 2);
+ gen.addMulR1();
+ gen.addJumpIfR0Equals(123456789 * 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, 2);
+ gen.addDivR1();
+ gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide by zero.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addDivR1();
+ gen.addJump(gen.DROP_LABEL);
+ assertPass(gen);
+
+ // Test byte load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoad8(Register.R0, 1);
+ gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test out of bounds load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoad8(Register.R0, 16);
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test half-word load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoad16(Register.R0, 1);
+ gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test word load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoad32(Register.R0, 1);
+ gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test byte indexed load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLoad8Indexed(Register.R0, 0);
+ gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test out of bounds indexed load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 8);
+ gen.addLoad8Indexed(Register.R0, 8);
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test half-word indexed load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLoad16Indexed(Register.R0, 0);
+ gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test word indexed load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLoad32Indexed(Register.R0, 0);
+ gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test jumping if greater than.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if less than.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0LessThan(0, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0LessThan(1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if any bits set.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 3);
+ gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if register greater than.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 2);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if register less than.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if any bits set in register.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 3);
+ gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 3);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 3);
+ gen.addLoadImmediate(Register.R0, 3);
+ gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test load from memory.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadFromMemory(Register.R0, 0);
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test store to memory.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addStoreToMemory(Register.R1, 12);
+ gen.addLoadFromMemory(Register.R0, 12);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test filter age pre-filled memory.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen, new byte[MIN_PKT_SIZE], 1234567890);
+
+ // Test packet size pre-filled memory.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
+ gen.addJumpIfR0Equals(MIN_PKT_SIZE, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test IPv4 header size pre-filled memory.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+ gen.addJumpIfR0Equals(20, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x45}, 0);
+
+ // Test not.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addNot(Register.R0);
+ gen.addJumpIfR0Equals(~1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test negate.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addNeg(Register.R0);
+ gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test move.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addMove(Register.R0);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addMove(Register.R1);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test swap.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addSwap();
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addSwap();
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jump if bytes not equal.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
+ program = gen.generate();
+ assertEquals(6, program.length);
+ assertEquals((13 << 3) | (1 << 1) | 0, program[0]);
+ assertEquals(1, program[1]);
+ assertEquals(((20 << 3) | (1 << 1) | 0) - 256, program[2]);
+ assertEquals(1, program[3]);
+ assertEquals(1, program[4]);
+ assertEquals(123, program[5]);
+ assertDrop(program, new byte[MIN_PKT_SIZE], 0);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
+ byte[] packet123 = {0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
+ assertPass(gen, packet123, 0);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
+ assertDrop(gen, packet123, 0);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
+ byte[] packet12345 = {0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
+ assertDrop(gen, packet12345, 0);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL);
+ assertPass(gen, packet12345, 0);
+ }
+
+ @Test(expected = ApfGenerator.IllegalInstructionException.class)
+ public void testApfGeneratorWantsV2OrGreater() throws Exception {
+ // The minimum supported APF version is 2.
+ new ApfGenerator(1);
+ }
+
+ @Test
+ public void testApfDataOpcodesWantApfV3() throws IllegalInstructionException, Exception {
+ ApfGenerator gen = new ApfGenerator(MIN_APF_VERSION);
+ try {
+ gen.addStoreData(Register.R0, 0);
+ fail();
+ } catch (IllegalInstructionException expected) {
+ /* pass */
+ }
+ try {
+ gen.addLoadData(Register.R0, 0);
+ fail();
+ } catch (IllegalInstructionException expected) {
+ /* pass */
+ }
+ }
+
+ /**
+ * Test that the generator emits immediates using the shortest possible encoding.
+ */
+ @Test
+ public void testImmediateEncoding() throws IllegalInstructionException {
+ ApfGenerator gen;
+
+ // 0-byte immediate: li R0, 0
+ gen = new ApfGenerator(4);
+ gen.addLoadImmediate(Register.R0, 0);
+ assertProgramEquals(new byte[]{LI_OP | SIZE0}, gen.generate());
+
+ // 1-byte immediate: li R0, 42
+ gen = new ApfGenerator(4);
+ gen.addLoadImmediate(Register.R0, 42);
+ assertProgramEquals(new byte[]{LI_OP | SIZE8, 42}, gen.generate());
+
+ // 2-byte immediate: li R1, 0x1234
+ gen = new ApfGenerator(4);
+ gen.addLoadImmediate(Register.R1, 0x1234);
+ assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1, 0x12, 0x34}, gen.generate());
+
+ // 4-byte immediate: li R0, 0x12345678
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, 0x12345678);
+ assertProgramEquals(
+ new byte[]{LI_OP | SIZE32, 0x12, 0x34, 0x56, 0x78},
+ gen.generate());
+ }
+
+ /**
+ * Test that the generator emits negative immediates using the shortest possible encoding.
+ */
+ @Test
+ public void testNegativeImmediateEncoding() throws IllegalInstructionException {
+ ApfGenerator gen;
+
+ // 1-byte negative immediate: li R0, -42
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, -42);
+ assertProgramEquals(new byte[]{LI_OP | SIZE8, -42}, gen.generate());
+
+ // 2-byte negative immediate: li R1, -0x1122
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R1, -0x1122);
+ assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1, (byte)0xEE, (byte)0xDE},
+ gen.generate());
+
+ // 4-byte negative immediate: li R0, -0x11223344
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, -0x11223344);
+ assertProgramEquals(
+ new byte[]{LI_OP | SIZE32, (byte)0xEE, (byte)0xDD, (byte)0xCC, (byte)0xBC},
+ gen.generate());
+ }
+
+ /**
+ * Test that the generator correctly emits positive and negative immediates for LDDW/STDW.
+ */
+ @Test
+ public void testLoadStoreDataEncoding() throws IllegalInstructionException {
+ ApfGenerator gen;
+
+ // Load data with no offset: lddw R0, [0 + r1]
+ gen = new ApfGenerator(3);
+ gen.addLoadData(Register.R0, 0);
+ assertProgramEquals(new byte[]{LDDW_OP | SIZE0}, gen.generate());
+
+ // Store data with 8bit negative offset: lddw r0, [-42 + r1]
+ gen = new ApfGenerator(3);
+ gen.addStoreData(Register.R0, -42);
+ assertProgramEquals(new byte[]{STDW_OP | SIZE8, -42}, gen.generate());
+
+ // Store data to R1 with 16bit negative offset: stdw r1, [-0x1122 + r0]
+ gen = new ApfGenerator(3);
+ gen.addStoreData(Register.R1, -0x1122);
+ assertProgramEquals(new byte[]{STDW_OP | SIZE16 | R1, (byte)0xEE, (byte)0xDE},
+ gen.generate());
+
+ // Load data to R1 with 32bit negative offset: lddw r1, [0xDEADBEEF + r0]
+ gen = new ApfGenerator(3);
+ gen.addLoadData(Register.R1, 0xDEADBEEF);
+ assertProgramEquals(
+ new byte[]{LDDW_OP | SIZE32 | R1, (byte)0xDE, (byte)0xAD, (byte)0xBE, (byte)0xEF},
+ gen.generate());
+ }
+
+ /**
+ * Test that the interpreter correctly executes STDW with a negative 8bit offset
+ */
+ @Test
+ public void testApfDataWrite() throws IllegalInstructionException, Exception {
+ byte[] packet = new byte[MIN_PKT_SIZE];
+ byte[] data = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
+ byte[] expected_data = data.clone();
+
+ // No memory access instructions: should leave the data segment untouched.
+ ApfGenerator gen = new ApfGenerator(3);
+ assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
+
+ // Expect value 0x87654321 to be stored starting from address -11 from the end of the
+ // data buffer, in big-endian order.
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, 0x87654321);
+ gen.addLoadImmediate(Register.R1, -5);
+ gen.addStoreData(Register.R0, -6); // -5 + -6 = -11 (offset +5 with data_len=16)
+ expected_data[5] = (byte)0x87;
+ expected_data[6] = (byte)0x65;
+ expected_data[7] = (byte)0x43;
+ expected_data[8] = (byte)0x21;
+ assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
+ }
+
+ /**
+ * Test that the interpreter correctly executes LDDW with a negative 16bit offset
+ */
+ @Test
+ public void testApfDataRead() throws IllegalInstructionException, Exception {
+ // Program that DROPs if address 10 (-6) contains 0x87654321.
+ ApfGenerator gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R1, 1000);
+ gen.addLoadData(Register.R0, -1006); // 1000 + -1006 = -6 (offset +10 with data_len=16)
+ gen.addJumpIfR0Equals(0x87654321, gen.DROP_LABEL);
+ byte[] program = gen.generate();
+ byte[] packet = new byte[MIN_PKT_SIZE];
+
+ // Content is incorrect (last byte does not match) -> PASS
+ byte[] data = new byte[16];
+ data[10] = (byte)0x87;
+ data[11] = (byte)0x65;
+ data[12] = (byte)0x43;
+ data[13] = (byte)0x00; // != 0x21
+ byte[] expected_data = data.clone();
+ assertDataMemoryContents(PASS, program, packet, data, expected_data);
+
+ // Fix the last byte -> conditional jump taken -> DROP
+ data[13] = (byte)0x21;
+ expected_data = data;
+ assertDataMemoryContents(DROP, program, packet, data, expected_data);
+ }
+
+ /**
+ * Test that the interpreter correctly executes LDDW followed by a STDW.
+ * To cover a few more edge cases, LDDW has a 0bit offset, while STDW has a positive 8bit
+ * offset.
+ */
+ @Test
+ public void testApfDataReadModifyWrite() throws IllegalInstructionException, Exception {
+ ApfGenerator gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R1, -22);
+ gen.addLoadData(Register.R0, 0); // Load from address 32 -22 + 0 = 10
+ gen.addAdd(0x78453412); // 87654321 + 78453412 = FFAA7733
+ gen.addStoreData(Register.R0, 4); // Write back to address 32 -22 + 4 = 14
+
+ byte[] packet = new byte[MIN_PKT_SIZE];
+ byte[] data = new byte[32];
+ data[10] = (byte)0x87;
+ data[11] = (byte)0x65;
+ data[12] = (byte)0x43;
+ data[13] = (byte)0x21;
+ byte[] expected_data = data.clone();
+ expected_data[14] = (byte)0xFF;
+ expected_data[15] = (byte)0xAA;
+ expected_data[16] = (byte)0x77;
+ expected_data[17] = (byte)0x33;
+ assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
+ }
+
+ @Test
+ public void testApfDataBoundChecking() throws IllegalInstructionException, Exception {
+ byte[] packet = new byte[MIN_PKT_SIZE];
+ byte[] data = new byte[32];
+ byte[] expected_data = data;
+
+ // Program that DROPs unconditionally. This is our the baseline.
+ ApfGenerator gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, 3);
+ gen.addLoadData(Register.R1, 7);
+ gen.addJump(gen.DROP_LABEL);
+ assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
+
+ // Same program as before, but this time we're trying to load past the end of the data.
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, 20);
+ gen.addLoadData(Register.R1, 15); // 20 + 15 > 32
+ gen.addJump(gen.DROP_LABEL); // Not reached.
+ assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
+
+ // Subtracting an immediate should work...
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, 20);
+ gen.addLoadData(Register.R1, -4);
+ gen.addJump(gen.DROP_LABEL);
+ assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
+
+ // ...and underflowing simply wraps around to the end of the buffer...
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, 20);
+ gen.addLoadData(Register.R1, -30);
+ gen.addJump(gen.DROP_LABEL);
+ assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
+
+ // ...but doesn't allow accesses before the start of the buffer
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, 20);
+ gen.addLoadData(Register.R1, -1000);
+ gen.addJump(gen.DROP_LABEL); // Not reached.
+ assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
+ }
+
+ /**
+ * Generate some BPF programs, translate them to APF, then run APF and BPF programs
+ * over packet traces and verify both programs filter out the same packets.
+ */
+ @Test
+ public void testApfAgainstBpf() throws Exception {
+ String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
+ "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
+ "arp or icmp6 or portrange 53-54", "portrange 53-54 or portrange 100-50000",
+ "tcp[tcpflags] & (tcp-ack|tcp-fin) != 0 and (ip[2:2] > 57 or icmp)" };
+ String pcap_filename = stageFile(R.raw.apf);
+ for (String tcpdump_filter : tcpdump_filters) {
+ byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter));
+ assertTrue("Failed to match for filter: " + tcpdump_filter,
+ compareBpfApf(tcpdump_filter, pcap_filename, apf_program));
+ }
+ }
+
+ /**
+ * Generate APF program, run pcap file though APF filter, then check all the packets in the file
+ * should be dropped.
+ */
+ @Test
+ public void testApfFilterPcapFile() throws Exception {
+ final byte[] MOCK_PCAP_IPV4_ADDR = {(byte) 172, 16, 7, (byte) 151};
+ String pcapFilename = stageFile(R.raw.apfPcap);
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_PCAP_IPV4_ADDR), 16);
+ LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(link);
+
+ ApfConfiguration config = getDefaultConfig();
+ ApfCapabilities MOCK_APF_PCAP_CAPABILITIES = new ApfCapabilities(4, 1700, ARPHRD_ETHER);
+ config.apfCapabilities = MOCK_APF_PCAP_CAPABILITIES;
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+ apfFilter.setLinkProperties(lp);
+ byte[] program = ipClientCallback.getApfProgram();
+ byte[] data = new byte[ApfFilter.Counter.totalSize()];
+ final boolean result;
+
+ result = dropsAllPackets(program, data, pcapFilename);
+ Log.i(TAG, "testApfFilterPcapFile(): Data counters: " + HexDump.toHexString(data, false));
+
+ assertTrue("Failed to drop all packets by filter. \nAPF counters:" +
+ HexDump.toHexString(data, false), result);
+ }
+
+ private class MockIpClientCallback extends IpClientCallbacksWrapper {
+ private final ConditionVariable mGotApfProgram = new ConditionVariable();
+ private byte[] mLastApfProgram;
+
+ MockIpClientCallback() {
+ super(mock(IIpClientCallbacks.class), mock(SharedLog.class));
+ }
+
+ @Override
+ public void installPacketFilter(byte[] filter) {
+ mLastApfProgram = filter;
+ mGotApfProgram.open();
+ }
+
+ public void resetApfProgramWait() {
+ mGotApfProgram.close();
+ }
+
+ public byte[] getApfProgram() {
+ assertTrue(mGotApfProgram.block(TIMEOUT_MS));
+ return mLastApfProgram;
+ }
+
+ public void assertNoProgramUpdate() {
+ assertFalse(mGotApfProgram.block(TIMEOUT_MS));
+ }
+ }
+
+ private static class TestApfFilter extends ApfFilter {
+ public static final byte[] MOCK_MAC_ADDR = {1,2,3,4,5,6};
+
+ private FileDescriptor mWriteSocket;
+ private final long mFixedTimeMs = SystemClock.elapsedRealtime();
+
+ public TestApfFilter(Context context, ApfConfiguration config,
+ IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) throws Exception {
+ super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, log);
+ }
+
+ // Pretend an RA packet has been received and show it to ApfFilter.
+ public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException {
+ // ApfFilter's ReceiveThread will be waiting to read this.
+ Os.write(mWriteSocket, packet, 0, packet.length);
+ }
+
+ @Override
+ protected long currentTimeSeconds() {
+ return mFixedTimeMs / DateUtils.SECOND_IN_MILLIS;
+ }
+
+ @Override
+ void maybeStartFilter() {
+ mHardwareAddress = MOCK_MAC_ADDR;
+ installNewProgramLocked();
+
+ // Create two sockets, "readSocket" and "mWriteSocket" and connect them together.
+ FileDescriptor readSocket = new FileDescriptor();
+ mWriteSocket = new FileDescriptor();
+ try {
+ Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket);
+ } catch (ErrnoException e) {
+ fail();
+ return;
+ }
+ // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs.
+ // This allows us to pretend RA packets have been recieved via pretendPacketReceived().
+ mReceiveThread = new ReceiveThread(readSocket);
+ mReceiveThread.start();
+ }
+
+ @Override
+ public void shutdown() {
+ super.shutdown();
+ IoUtils.closeQuietly(mWriteSocket);
+ }
+ }
+
+ private static final int ETH_HEADER_LEN = 14;
+ private static final int ETH_DEST_ADDR_OFFSET = 0;
+ private static final int ETH_ETHERTYPE_OFFSET = 12;
+ private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
+ {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
+
+ private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0;
+ private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
+ private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
+ private static final byte[] IPV4_BROADCAST_ADDRESS =
+ {(byte) 255, (byte) 255, (byte) 255, (byte) 255};
+
+ private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
+ private static final int IPV6_HEADER_LEN = 40;
+ private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
+ // The IPv6 all nodes address ff02::1
+ private static final byte[] IPV6_ALL_NODES_ADDRESS =
+ { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+ private static final byte[] IPV6_ALL_ROUTERS_ADDRESS =
+ { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 };
+
+ private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
+ private static final int ICMP6_ROUTER_SOLICITATION = 133;
+ private static final int ICMP6_ROUTER_ADVERTISEMENT = 134;
+ private static final int ICMP6_NEIGHBOR_SOLICITATION = 135;
+ 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 =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
+ private static final int ICMP6_RA_CHECKSUM_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
+ private static final int ICMP6_RA_OPTION_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
+
+ private static final int ICMP6_PREFIX_OPTION_TYPE = 3;
+ private static final int ICMP6_PREFIX_OPTION_LEN = 32;
+ private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4;
+ private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;
+
+ // From RFC6106: Recursive DNS Server option
+ private static final int ICMP6_RDNSS_OPTION_TYPE = 25;
+ // From RFC6106: DNS Search List option
+ private static final int ICMP6_DNSSL_OPTION_TYPE = 31;
+
+ // From RFC4191: Route Information option
+ private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24;
+ // Above three options all have the same format:
+ private static final int ICMP6_4_BYTE_OPTION_LEN = 8;
+ private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4;
+ private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4;
+
+ private static final int UDP_HEADER_LEN = 8;
+ private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 22;
+
+ private static final int DHCP_CLIENT_PORT = 68;
+ private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48;
+
+ private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
+ private static final byte[] ARP_IPV4_REQUEST_HEADER = {
+ 0, 1, // Hardware type: Ethernet (1)
+ 8, 0, // Protocol type: IP (0x0800)
+ 6, // Hardware size: 6
+ 4, // Protocol size: 4
+ 0, 1 // Opcode: request (1)
+ };
+ private static final byte[] ARP_IPV4_REPLY_HEADER = {
+ 0, 1, // Hardware type: Ethernet (1)
+ 8, 0, // Protocol type: IP (0x0800)
+ 6, // Hardware size: 6
+ 4, // Protocol size: 4
+ 0, 2 // Opcode: reply (2)
+ };
+ private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14;
+ private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24;
+
+ private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1};
+ private static final byte[] MOCK_BROADCAST_IPV4_ADDR = {10, 0, 31, (byte) 255}; // prefix = 19
+ private static final byte[] MOCK_MULTICAST_IPV4_ADDR = {(byte) 224, 0, 0, 1};
+ private static final byte[] ANOTHER_IPV4_ADDR = {10, 0, 0, 2};
+ private static final byte[] IPV4_SOURCE_ADDR = {10, 0, 0, 3};
+ private static final byte[] ANOTHER_IPV4_SOURCE_ADDR = {(byte) 192, 0, 2, 1};
+ private static final byte[] BUG_PROBE_SOURCE_ADDR1 = {0, 0, 1, 2};
+ private static final byte[] BUG_PROBE_SOURCE_ADDR2 = {3, 4, 0, 0};
+ private static final byte[] IPV4_ANY_HOST_ADDR = {0, 0, 0, 0};
+
+ // Helper to initialize a default apfFilter.
+ private ApfFilter setupApfFilter(
+ IpClientCallbacksWrapper ipClientCallback, ApfConfiguration config) throws Exception {
+ LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
+ LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(link);
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+ apfFilter.setLinkProperties(lp);
+ return apfFilter;
+ }
+
+ @Test
+ public void testApfFilterIPv4() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
+ LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(link);
+
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+ apfFilter.setLinkProperties(lp);
+
+ byte[] program = ipClientCallback.getApfProgram();
+
+ // Verify empty packet of 100 zero bytes is passed
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ assertPass(program, packet.array());
+
+ // Verify unicast IPv4 packet is passed
+ put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_IPV4_ADDR);
+ assertPass(program, packet.array());
+
+ // Verify L2 unicast to IPv4 broadcast addresses is dropped (b/30231088)
+ put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
+ assertDrop(program, packet.array());
+
+ // Verify multicast/broadcast IPv4, not DHCP to us, is dropped
+ put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
+ assertDrop(program, packet.array());
+ packet.put(IPV4_VERSION_IHL_OFFSET, (byte)0x45);
+ assertDrop(program, packet.array());
+ packet.put(IPV4_PROTOCOL_OFFSET, (byte)IPPROTO_UDP);
+ assertDrop(program, packet.array());
+ packet.putShort(UDP_DESTINATION_PORT_OFFSET, (short)DHCP_CLIENT_PORT);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_MULTICAST_IPV4_ADDR);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
+ assertDrop(program, packet.array());
+
+ // Verify broadcast IPv4 DHCP to us is passed
+ put(packet, DHCP_CLIENT_MAC_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
+ assertPass(program, packet.array());
+
+ // Verify unicast IPv4 DHCP to us is passed
+ put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
+ assertPass(program, packet.array());
+
+ apfFilter.shutdown();
+ }
+
+ @Test
+ public void testApfFilterIPv6() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+ byte[] program = ipClientCallback.getApfProgram();
+
+ // Verify empty IPv6 packet is passed
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ assertPass(program, packet.array());
+
+ // Verify empty ICMPv6 packet is passed
+ packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+ assertPass(program, packet.array());
+
+ // Verify empty ICMPv6 NA packet is passed
+ packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_NEIGHBOR_ANNOUNCEMENT);
+ assertPass(program, packet.array());
+
+ // Verify ICMPv6 NA to ff02::1 is dropped
+ put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_NODES_ADDRESS);
+ assertDrop(program, packet.array());
+
+ // Verify ICMPv6 RS to any is dropped
+ packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_SOLICITATION);
+ assertDrop(program, packet.array());
+ put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS);
+ assertDrop(program, packet.array());
+
+ apfFilter.shutdown();
+ }
+
+ @Test
+ public void testApfFilterMulticast() throws Exception {
+ final byte[] unicastIpv4Addr = {(byte)192,0,2,63};
+ final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255};
+ final byte[] multicastIpv4Addr = {(byte)224,0,0,1};
+ final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
+
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24);
+ LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(link);
+
+ ApfConfiguration config = getDefaultConfig();
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+ apfFilter.setLinkProperties(lp);
+
+ byte[] program = ipClientCallback.getApfProgram();
+
+ // Construct IPv4 and IPv6 multicast packets.
+ ByteBuffer mcastv4packet = ByteBuffer.wrap(new byte[100]);
+ mcastv4packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(mcastv4packet, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
+
+ ByteBuffer mcastv6packet = ByteBuffer.wrap(new byte[100]);
+ mcastv6packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_UDP);
+ put(mcastv6packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
+
+ // Construct IPv4 broadcast packet.
+ ByteBuffer bcastv4packet1 = ByteBuffer.wrap(new byte[100]);
+ bcastv4packet1.put(ETH_BROADCAST_MAC_ADDRESS);
+ bcastv4packet1.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(bcastv4packet1, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
+
+ ByteBuffer bcastv4packet2 = ByteBuffer.wrap(new byte[100]);
+ bcastv4packet2.put(ETH_BROADCAST_MAC_ADDRESS);
+ bcastv4packet2.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(bcastv4packet2, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
+
+ // Construct IPv4 broadcast with L2 unicast address packet (b/30231088).
+ ByteBuffer bcastv4unicastl2packet = ByteBuffer.wrap(new byte[100]);
+ bcastv4unicastl2packet.put(TestApfFilter.MOCK_MAC_ADDR);
+ bcastv4unicastl2packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(bcastv4unicastl2packet, IPV4_DEST_ADDR_OFFSET, broadcastIpv4Addr);
+
+ // Verify initially disabled multicast filter is off
+ assertPass(program, mcastv4packet.array());
+ assertPass(program, mcastv6packet.array());
+ assertPass(program, bcastv4packet1.array());
+ assertPass(program, bcastv4packet2.array());
+ assertPass(program, bcastv4unicastl2packet.array());
+
+ // Turn on multicast filter and verify it works
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.setMulticastFilter(true);
+ program = ipClientCallback.getApfProgram();
+ assertDrop(program, mcastv4packet.array());
+ assertDrop(program, mcastv6packet.array());
+ assertDrop(program, bcastv4packet1.array());
+ assertDrop(program, bcastv4packet2.array());
+ assertDrop(program, bcastv4unicastl2packet.array());
+
+ // Turn off multicast filter and verify it's off
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.setMulticastFilter(false);
+ program = ipClientCallback.getApfProgram();
+ assertPass(program, mcastv4packet.array());
+ assertPass(program, mcastv6packet.array());
+ assertPass(program, bcastv4packet1.array());
+ assertPass(program, bcastv4packet2.array());
+ assertPass(program, bcastv4unicastl2packet.array());
+
+ // Verify it can be initialized to on
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.shutdown();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+ apfFilter.setLinkProperties(lp);
+ program = ipClientCallback.getApfProgram();
+ assertDrop(program, mcastv4packet.array());
+ assertDrop(program, mcastv6packet.array());
+ assertDrop(program, bcastv4packet1.array());
+ assertDrop(program, bcastv4unicastl2packet.array());
+
+ // Verify that ICMPv6 multicast is not dropped.
+ mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+ assertPass(program, mcastv6packet.array());
+
+ apfFilter.shutdown();
+ }
+
+ @Test
+ public void testApfFilterMulticastPingWhileDozing() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfFilter apfFilter = setupApfFilter(ipClientCallback, getDefaultConfig());
+
+ // Construct a multicast ICMPv6 ECHO request.
+ final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+ packet.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ECHO_REQUEST_TYPE);
+ put(packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
+
+ // Normally, we let multicast pings alone...
+ assertPass(ipClientCallback.getApfProgram(), packet.array());
+
+ // ...and even while dozing...
+ apfFilter.setDozeMode(true);
+ assertPass(ipClientCallback.getApfProgram(), packet.array());
+
+ // ...but when the multicast filter is also enabled, drop the multicast pings to save power.
+ apfFilter.setMulticastFilter(true);
+ assertDrop(ipClientCallback.getApfProgram(), packet.array());
+
+ // However, we should still let through all other ICMPv6 types.
+ ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone());
+ raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT);
+ assertPass(ipClientCallback.getApfProgram(), raPacket.array());
+
+ // Now wake up from doze mode to ensure that we no longer drop the packets.
+ // (The multicast filter is still enabled at this point).
+ apfFilter.setDozeMode(false);
+ assertPass(ipClientCallback.getApfProgram(), packet.array());
+
+ apfFilter.shutdown();
+ }
+
+ @Test
+ public void testApfFilter802_3() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ ApfFilter apfFilter = setupApfFilter(ipClientCallback, config);
+ byte[] program = ipClientCallback.getApfProgram();
+
+ // Verify empty packet of 100 zero bytes is passed
+ // Note that eth-type = 0 makes it an IEEE802.3 frame
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ assertPass(program, packet.array());
+
+ // Verify empty packet with IPv4 is passed
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ assertPass(program, packet.array());
+
+ // Verify empty IPv6 packet is passed
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ assertPass(program, packet.array());
+
+ // Now turn on the filter
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.shutdown();
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ apfFilter = setupApfFilter(ipClientCallback, config);
+ program = ipClientCallback.getApfProgram();
+
+ // Verify that IEEE802.3 frame is dropped
+ // In this case ethtype is used for payload length
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)(100 - 14));
+ assertDrop(program, packet.array());
+
+ // Verify that IPv4 (as example of Ethernet II) frame will pass
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ assertPass(program, packet.array());
+
+ // Verify that IPv6 (as example of Ethernet II) frame will pass
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ assertPass(program, packet.array());
+
+ apfFilter.shutdown();
+ }
+
+ @Test
+ public void testApfFilterEthTypeBL() throws Exception {
+ final int[] emptyBlackList = {};
+ final int[] ipv4BlackList = {ETH_P_IP};
+ final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6};
+
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ ApfFilter apfFilter = setupApfFilter(ipClientCallback, config);
+ byte[] program = ipClientCallback.getApfProgram();
+
+ // Verify empty packet of 100 zero bytes is passed
+ // Note that eth-type = 0 makes it an IEEE802.3 frame
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ assertPass(program, packet.array());
+
+ // Verify empty packet with IPv4 is passed
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ assertPass(program, packet.array());
+
+ // Verify empty IPv6 packet is passed
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ assertPass(program, packet.array());
+
+ // Now add IPv4 to the black list
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.shutdown();
+ config.ethTypeBlackList = ipv4BlackList;
+ apfFilter = setupApfFilter(ipClientCallback, config);
+ program = ipClientCallback.getApfProgram();
+
+ // Verify that IPv4 frame will be dropped
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ assertDrop(program, packet.array());
+
+ // Verify that IPv6 frame will pass
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ assertPass(program, packet.array());
+
+ // Now let us have both IPv4 and IPv6 in the black list
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.shutdown();
+ config.ethTypeBlackList = ipv4Ipv6BlackList;
+ apfFilter = setupApfFilter(ipClientCallback, config);
+ program = ipClientCallback.getApfProgram();
+
+ // Verify that IPv4 frame will be dropped
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ assertDrop(program, packet.array());
+
+ // Verify that IPv6 frame will be dropped
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ assertDrop(program, packet.array());
+
+ apfFilter.shutdown();
+ }
+
+ private byte[] getProgram(MockIpClientCallback cb, ApfFilter filter, LinkProperties lp) {
+ cb.resetApfProgramWait();
+ filter.setLinkProperties(lp);
+ return cb.getApfProgram();
+ }
+
+ private void verifyArpFilter(byte[] program, int filterResult) {
+ // Verify ARP request packet
+ assertPass(program, arpRequestBroadcast(MOCK_IPV4_ADDR));
+ assertVerdict(filterResult, program, arpRequestBroadcast(ANOTHER_IPV4_ADDR));
+ assertDrop(program, arpRequestBroadcast(IPV4_ANY_HOST_ADDR));
+
+ // Verify ARP reply packets from different source ip
+ assertDrop(program, arpReply(IPV4_ANY_HOST_ADDR, IPV4_ANY_HOST_ADDR));
+ assertPass(program, arpReply(ANOTHER_IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR));
+ assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR1, IPV4_ANY_HOST_ADDR));
+ assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR2, IPV4_ANY_HOST_ADDR));
+
+ // Verify unicast ARP reply packet is always accepted.
+ assertPass(program, arpReply(IPV4_SOURCE_ADDR, MOCK_IPV4_ADDR));
+ assertPass(program, arpReply(IPV4_SOURCE_ADDR, ANOTHER_IPV4_ADDR));
+ assertPass(program, arpReply(IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR));
+
+ // Verify GARP reply packets are always filtered
+ assertDrop(program, garpReply());
+ }
+
+ @Test
+ public void testApfFilterArp() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+
+ // Verify initially ARP request filter is off, and GARP filter is on.
+ verifyArpFilter(ipClientCallback.getApfProgram(), PASS);
+
+ // Inform ApfFilter of our address and verify ARP filtering is on
+ LinkAddress linkAddress = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24);
+ LinkProperties lp = new LinkProperties();
+ assertTrue(lp.addLinkAddress(linkAddress));
+ verifyArpFilter(getProgram(ipClientCallback, apfFilter, lp), DROP);
+
+ // Inform ApfFilter of loss of IP and verify ARP filtering is off
+ verifyArpFilter(getProgram(ipClientCallback, apfFilter, new LinkProperties()), PASS);
+
+ apfFilter.shutdown();
+ }
+
+ private static byte[] arpReply(byte[] sip, byte[] tip) {
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
+ put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
+ put(packet, ARP_SOURCE_IP_ADDRESS_OFFSET, sip);
+ put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
+ return packet.array();
+ }
+
+ private static byte[] arpRequestBroadcast(byte[] tip) {
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
+ put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
+ put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REQUEST_HEADER);
+ put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
+ return packet.array();
+ }
+
+ private static byte[] garpReply() {
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
+ put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
+ put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
+ put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, IPV4_ANY_HOST_ADDR);
+ return packet.array();
+ }
+
+ // Verify that the last program pushed to the IpClient.Callback properly filters the
+ // given packet for the given lifetime.
+ private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime) {
+ final int FRACTION_OF_LIFETIME = 6;
+ final int ageLimit = lifetime / FRACTION_OF_LIFETIME;
+
+ // Verify new program should drop RA for 1/6th its lifetime and pass afterwards.
+ assertDrop(program, packet.array());
+ assertDrop(program, packet.array(), ageLimit);
+ assertPass(program, packet.array(), ageLimit + 1);
+ assertPass(program, packet.array(), lifetime);
+ // Verify RA checksum is ignored
+ final short originalChecksum = packet.getShort(ICMP6_RA_CHECKSUM_OFFSET);
+ packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)12345);
+ assertDrop(program, packet.array());
+ packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)-12345);
+ assertDrop(program, packet.array());
+ packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, originalChecksum);
+
+ // Verify other changes to RA make it not match filter
+ final byte originalFirstByte = packet.get(0);
+ packet.put(0, (byte)-1);
+ assertPass(program, packet.array());
+ packet.put(0, (byte)0);
+ assertDrop(program, packet.array());
+ packet.put(0, originalFirstByte);
+ }
+
+ // Test that when ApfFilter is shown the given packet, it generates a program to filter it
+ // for the given lifetime.
+ private void verifyRaLifetime(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback,
+ ByteBuffer packet, int lifetime) throws IOException, ErrnoException {
+ // Verify new program generated if ApfFilter witnesses RA
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.pretendPacketReceived(packet.array());
+ byte[] program = ipClientCallback.getApfProgram();
+ verifyRaLifetime(program, packet, lifetime);
+ }
+
+ private void verifyRaEvent(RaEvent expected) {
+ ArgumentCaptor<IpConnectivityLog.Event> captor =
+ ArgumentCaptor.forClass(IpConnectivityLog.Event.class);
+ verify(mLog, atLeastOnce()).log(captor.capture());
+ RaEvent got = lastRaEvent(captor.getAllValues());
+ if (!raEventEquals(expected, got)) {
+ assertEquals(expected, got); // fail for printing an assertion error message.
+ }
+ }
+
+ private RaEvent lastRaEvent(List<IpConnectivityLog.Event> events) {
+ RaEvent got = null;
+ for (Parcelable ev : events) {
+ if (ev instanceof RaEvent) {
+ got = (RaEvent) ev;
+ }
+ }
+ return got;
+ }
+
+ private boolean raEventEquals(RaEvent ev1, RaEvent ev2) {
+ return (ev1 != null) && (ev2 != null)
+ && (ev1.routerLifetime == ev2.routerLifetime)
+ && (ev1.prefixValidLifetime == ev2.prefixValidLifetime)
+ && (ev1.prefixPreferredLifetime == ev2.prefixPreferredLifetime)
+ && (ev1.routeInfoLifetime == ev2.routeInfoLifetime)
+ && (ev1.rdnssLifetime == ev2.rdnssLifetime)
+ && (ev1.dnsslLifetime == ev2.dnsslLifetime);
+ }
+
+ private void assertInvalidRa(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback,
+ ByteBuffer packet) throws IOException, ErrnoException {
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.pretendPacketReceived(packet.array());
+ ipClientCallback.assertNoProgramUpdate();
+ }
+
+ @Test
+ public void testApfFilterRa() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+ byte[] program = ipClientCallback.getApfProgram();
+
+ final int ROUTER_LIFETIME = 1000;
+ final int PREFIX_VALID_LIFETIME = 200;
+ final int PREFIX_PREFERRED_LIFETIME = 100;
+ final int RDNSS_LIFETIME = 300;
+ final int ROUTE_LIFETIME = 400;
+ // Note that lifetime of 2000 will be ignored in favor of shorter route lifetime of 1000.
+ final int DNSSL_LIFETIME = 2000;
+ 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;
+
+ // 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);
+ assertPass(program, basePacket.array());
+
+ verifyRaLifetime(apfFilter, ipClientCallback, basePacket, ROUTER_LIFETIME);
+ verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, -1));
+
+ ByteBuffer newFlowLabelPacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
+ basePacket.clear();
+ newFlowLabelPacket.put(basePacket);
+ // Check that changes are ignored in every byte of the flow label.
+ newFlowLabelPacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET,
+ VERSION_TRAFFIC_CLASS_FLOW_LABEL + 0x11111);
+
+ // Ensure zero-length options cause the packet to be silently skipped.
+ // Do this before we test other packets. http://b/29586253
+ ByteBuffer zeroLengthOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+ basePacket.clear();
+ zeroLengthOptionPacket.put(basePacket);
+ zeroLengthOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE);
+ zeroLengthOptionPacket.put((byte)0);
+ assertInvalidRa(apfFilter, ipClientCallback, zeroLengthOptionPacket);
+
+ // Generate several RAs with different options and lifetimes, and verify when
+ // ApfFilter is shown these packets, it generates programs to filter them for the
+ // appropriate lifetime.
+ ByteBuffer prefixOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_LEN]);
+ basePacket.clear();
+ prefixOptionPacket.put(basePacket);
+ prefixOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE);
+ prefixOptionPacket.put((byte)(ICMP6_PREFIX_OPTION_LEN / 8));
+ prefixOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET,
+ PREFIX_PREFERRED_LIFETIME);
+ prefixOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET,
+ PREFIX_VALID_LIFETIME);
+ verifyRaLifetime(
+ apfFilter, ipClientCallback, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
+ verifyRaEvent(new RaEvent(
+ ROUTER_LIFETIME, PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, -1, -1, -1));
+
+ ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+ basePacket.clear();
+ rdnssOptionPacket.put(basePacket);
+ rdnssOptionPacket.put((byte)ICMP6_RDNSS_OPTION_TYPE);
+ rdnssOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
+ rdnssOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, RDNSS_LIFETIME);
+ verifyRaLifetime(apfFilter, ipClientCallback, rdnssOptionPacket, RDNSS_LIFETIME);
+ verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, RDNSS_LIFETIME, -1));
+
+ ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+ basePacket.clear();
+ routeInfoOptionPacket.put(basePacket);
+ routeInfoOptionPacket.put((byte)ICMP6_ROUTE_INFO_OPTION_TYPE);
+ routeInfoOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
+ routeInfoOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, ROUTE_LIFETIME);
+ verifyRaLifetime(apfFilter, ipClientCallback, routeInfoOptionPacket, ROUTE_LIFETIME);
+ verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, ROUTE_LIFETIME, -1, -1));
+
+ ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+ basePacket.clear();
+ dnsslOptionPacket.put(basePacket);
+ dnsslOptionPacket.put((byte)ICMP6_DNSSL_OPTION_TYPE);
+ dnsslOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
+ dnsslOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, DNSSL_LIFETIME);
+ verifyRaLifetime(apfFilter, ipClientCallback, dnsslOptionPacket, ROUTER_LIFETIME);
+ verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, DNSSL_LIFETIME));
+
+ // Verify that current program filters all five RAs:
+ program = ipClientCallback.getApfProgram();
+ verifyRaLifetime(program, basePacket, ROUTER_LIFETIME);
+ verifyRaLifetime(program, newFlowLabelPacket, ROUTER_LIFETIME);
+ verifyRaLifetime(program, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
+ verifyRaLifetime(program, rdnssOptionPacket, RDNSS_LIFETIME);
+ verifyRaLifetime(program, routeInfoOptionPacket, ROUTE_LIFETIME);
+ verifyRaLifetime(program, dnsslOptionPacket, ROUTER_LIFETIME);
+
+ apfFilter.shutdown();
+ }
+
+ /**
+ * 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.
+ */
+ private String stageFile(int rawId) throws Exception {
+ File file = new File(InstrumentationRegistry.getContext().getFilesDir(), "staged_file");
+ new File(file.getParent()).mkdirs();
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId);
+ out = new FileOutputStream(file);
+ Streams.copy(in, out);
+ } finally {
+ if (in != null) in.close();
+ if (out != null) out.close();
+ }
+ return file.getAbsolutePath();
+ }
+
+ private static void put(ByteBuffer buffer, int position, byte[] bytes) {
+ final int original = buffer.position();
+ buffer.position(position);
+ buffer.put(bytes);
+ buffer.position(original);
+ }
+
+ @Test
+ public void testRaParsing() throws Exception {
+ final int maxRandomPacketSize = 512;
+ final Random r = new Random();
+ MockIpClientCallback cb = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
+ for (int i = 0; i < 1000; i++) {
+ byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
+ r.nextBytes(packet);
+ try {
+ apfFilter.new Ra(packet, packet.length);
+ } catch (ApfFilter.InvalidRaException e) {
+ } catch (Exception e) {
+ throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
+ }
+ }
+ }
+
+ @Test
+ public void testRaProcessing() throws Exception {
+ final int maxRandomPacketSize = 512;
+ final Random r = new Random();
+ MockIpClientCallback cb = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
+ for (int i = 0; i < 1000; i++) {
+ byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
+ r.nextBytes(packet);
+ try {
+ apfFilter.processRa(packet, packet.length);
+ } catch (Exception e) {
+ throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
+ }
+ }
+ }
+
+ /**
+ * Call the APF interpreter to run {@code program} on {@code packet} with persistent memory
+ * segment {@data} pretending the filter was installed {@code filter_age} seconds ago.
+ */
+ private native static int apfSimulate(byte[] program, byte[] packet, byte[] data,
+ int filter_age);
+
+ /**
+ * Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF
+ * prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d".
+ */
+ private native static String compileToBpf(String filter);
+
+ /**
+ * Open packet capture file {@code pcap_filename} and filter the packets using tcpdump
+ * human-readable filter (e.g. "icmp" or "tcp port 54") compiled to a BPF program and
+ * at the same time using APF program {@code apf_program}. Return {@code true} if
+ * both APF and BPF programs filter out exactly the same packets.
+ */
+ private native static boolean compareBpfApf(String filter, String pcap_filename,
+ byte[] apf_program);
+
+
+ /**
+ * Open packet capture file {@code pcapFilename} and run it through APF filter. Then
+ * checks whether all the packets are dropped and populates data[] {@code data} with
+ * the APF counters.
+ */
+ private native static boolean dropsAllPackets(byte[] program, byte[] data, String pcapFilename);
+
+ @Test
+ public void testBroadcastAddress() throws Exception {
+ assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 0));
+ assertEqualsIp("0.0.0.0", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 32));
+ assertEqualsIp("0.0.3.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 22));
+ assertEqualsIp("0.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 8));
+
+ assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 0));
+ assertEqualsIp("10.0.0.1", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 32));
+ assertEqualsIp("10.0.0.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 24));
+ assertEqualsIp("10.0.255.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 16));
+ }
+
+ public void assertEqualsIp(String expected, int got) throws Exception {
+ int want = bytesToBEInt(InetAddress.getByName(expected).getAddress());
+ assertEquals(want, got);
+ }
+}