diff options
author | Remi NGUYEN VAN <reminv@google.com> | 2019-01-23 15:05:00 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2019-01-23 15:05:00 +0000 |
commit | e1f80469fad97019bc2bcfa39dc474bd1b2e8373 (patch) | |
tree | 735ece4dbed44d6e037a65e0081cd2d6ae6df673 /tests/src/android/net/apf/ApfTest.java | |
parent | d44768965b0aeb15b1643353bc454f0cbcbb5f63 (diff) | |
parent | 4a2eb87a8e16874938fa1ce7d39bbfa7b59b9890 (diff) |
Merge changes I1250730c,I7d00848c
* changes:
Create a wrapper for IpClient
Move IpClient to NetworkStack
Diffstat (limited to 'tests/src/android/net/apf/ApfTest.java')
-rw-r--r-- | tests/src/android/net/apf/ApfTest.java | 1802 |
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); + } +} |