/* * 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.net.apf.ApfCounterTracker.Counter.getCounterEnumFromOffset; import static android.net.apf.ApfTestHelpers.TIMEOUT_MS; import static android.net.apf.ApfTestHelpers.DROP; import static android.net.apf.ApfTestHelpers.MIN_PKT_SIZE; import static android.net.apf.ApfTestHelpers.PASS; import static android.net.apf.ApfTestHelpers.assertProgramEquals; import static android.net.apf.BaseApfGenerator.APF_VERSION_3; import static android.net.apf.BaseApfGenerator.APF_VERSION_4; import static android.net.apf.BaseApfGenerator.APF_VERSION_6; import static android.net.apf.BaseApfGenerator.DROP_LABEL; import static android.net.apf.BaseApfGenerator.MemorySlot; import static android.net.apf.BaseApfGenerator.PASS_LABEL; import static android.net.apf.BaseApfGenerator.Register.R0; import static android.net.apf.BaseApfGenerator.Register.R1; import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED; import static android.system.OsConstants.AF_UNIX; 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_IPV6; import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_STREAM; import static com.android.net.module.util.HexDump.hexStringToByteArray; import static com.android.net.module.util.HexDump.toHexString; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.net.InetAddresses; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MacAddress; import android.net.apf.ApfCounterTracker.Counter; import android.net.apf.ApfFilter.ApfConfiguration; import android.net.apf.BaseApfGenerator.IllegalInstructionException; import android.net.metrics.IpConnectivityLog; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.PowerManager; import android.os.SystemClock; import android.stats.connectivity.NetworkQuirkEvent; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.HexDump; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.DnsPacket; import com.android.net.module.util.Inet4AddressUtils; import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.NetworkStackConstants; import com.android.net.module.util.PacketBuilder; import com.android.networkstack.metrics.ApfSessionInfoMetrics; import com.android.networkstack.metrics.IpClientRaInfoMetrics; import com.android.networkstack.metrics.NetworkQuirkMetrics; import com.android.server.networkstack.tests.R; import com.android.testutils.ConcurrentUtils; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRunner; import com.android.testutils.HandlerUtils; import libcore.io.IoUtils; import libcore.io.Streams; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.atomic.AtomicReference; /** * Tests for APF program generator and interpreter. * * The test cases will be executed by both APFv4 and APFv6 interpreter. */ @DevSdkIgnoreRunner.MonitorThreadLeak @RunWith(DevSdkIgnoreRunner.class) @SmallTest public class ApfTest { private static final int APF_VERSION_2 = 2; private int mRamSize = 1024; private int mClampSize = 1024; @Rule public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule(); // Indicates which apf interpreter to run. @Parameterized.Parameter() public int mApfVersion; @Parameterized.Parameters public static Iterable data() { return Arrays.asList(4, 6); } @Mock private Context mContext; @Mock private ApfFilter.Dependencies mDependencies; @Mock private PowerManager mPowerManager; @Mock private IpConnectivityLog mIpConnectivityLog; @Mock private NetworkQuirkMetrics mNetworkQuirkMetrics; @Mock private ApfSessionInfoMetrics mApfSessionInfoMetrics; @Mock private IpClientRaInfoMetrics mIpClientRaInfoMetrics; @Mock private ApfFilter.IApfController mApfController; @GuardedBy("mApfFilterCreated") private final ArrayList mApfFilterCreated = new ArrayList<>(); private FileDescriptor mWriteSocket; private HandlerThread mHandlerThread; private Handler mHandler; private long mCurrentTimeMs; private ApfTestHelpers mApfTestHelpers; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class); doReturn(mApfSessionInfoMetrics).when(mDependencies).getApfSessionInfoMetrics(); doReturn(mIpClientRaInfoMetrics).when(mDependencies).getIpClientRaInfoMetrics(); FileDescriptor readSocket = new FileDescriptor(); mWriteSocket = new FileDescriptor(); Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket); doReturn(readSocket).when(mDependencies).createPacketReaderSocket(anyInt()); mCurrentTimeMs = SystemClock.elapsedRealtime(); doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime(); doReturn(true).when(mApfController).installPacketFilter(any(), any()); doAnswer((invocation) -> { synchronized (mApfFilterCreated) { mApfFilterCreated.add(invocation.getArgument(0)); } return null; }).when(mDependencies).onApfFilterCreated(any()); mHandlerThread = new HandlerThread("ApfTestThread"); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); mApfTestHelpers = new ApfTestHelpers(ApfJniUtils.APF_INTERPRETER_VERSION_V6); } private void shutdownApfFilters() throws Exception { ConcurrentUtils.quitResources(THREAD_QUIT_MAX_RETRY_COUNT, () -> { synchronized (mApfFilterCreated) { final ArrayList ret = new ArrayList<>(mApfFilterCreated); mApfFilterCreated.clear(); return ret; } }, (apf) -> mHandler.post(apf::shutdown)); synchronized (mApfFilterCreated) { assertEquals("ApfFilters did not fully shutdown.", 0, mApfFilterCreated.size()); } } @After public void tearDown() throws Exception { IoUtils.closeQuietly(mWriteSocket); shutdownApfFilters(); HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS); // Clear mocks to prevent from stubs holding instances and cause memory leaks. Mockito.framework().clearInlineMocks(); mHandlerThread.quitSafely(); mHandlerThread.join(); } private static final String TAG = "ApfTest"; // Expected return codes from APF interpreter. 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; private static final int MIN_RDNSS_LIFETIME_SEC = 0; private static final int MIN_METRICS_SESSION_DURATIONS_MS = 300_000; private static final int NO_CALLBACK_TIMEOUT_MS = 500; private static final int THREAD_QUIT_MAX_RETRY_COUNT = 3; // 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_REG = 1; private static final byte[] TEST_MAC_ADDR = {2, 3, 4, 5, 6, 7}; private static final int TEST_IFACE_IDX = 1234; private static final InterfaceParams TEST_PARAMS = new InterfaceParams("lo", TEST_IFACE_IDX, MacAddress.fromBytes(TEST_MAC_ADDR), 1500 /* defaultMtu */); private static ApfConfiguration getDefaultConfig() { ApfFilter.ApfConfiguration config = new ApfConfiguration(); config.apfVersionSupported = 2; config.apfRamSize = 4096; config.multicastFilter = ALLOW_MULTICAST; config.ieee802_3Filter = ALLOW_802_3_FRAMES; config.ethTypeBlackList = new int[0]; config.minRdnssLifetimeSec = MIN_RDNSS_LIFETIME_SEC; config.minRdnssLifetimeSec = 67; config.minMetricsSessionDurationMs = MIN_METRICS_SESSION_DURATIONS_MS; return config; } private void assertPass(ApfV4Generator gen) throws ApfV4Generator.IllegalInstructionException { mApfTestHelpers.assertPass(mApfVersion, gen); } private void assertDrop(ApfV4Generator gen) throws ApfV4Generator.IllegalInstructionException { mApfTestHelpers.assertDrop(mApfVersion, gen); } private void assertPass(byte[] program, byte[] packet) { mApfTestHelpers.assertPass(mApfVersion, program, packet); } private void assertDrop(byte[] program, byte[] packet) { mApfTestHelpers.assertDrop(mApfVersion, program, packet); } private void assertPass(byte[] program, byte[] packet, int filterAge) { mApfTestHelpers.assertPass(mApfVersion, program, packet, filterAge); } private void assertDrop(byte[] program, byte[] packet, int filterAge) { mApfTestHelpers.assertDrop(mApfVersion, program, packet, filterAge); } private void assertPass(ApfV4Generator gen, byte[] packet, int filterAge) throws ApfV4Generator.IllegalInstructionException { mApfTestHelpers.assertPass(mApfVersion, gen, packet, filterAge); } private void assertDrop(ApfV4Generator gen, byte[] packet, int filterAge) throws ApfV4Generator.IllegalInstructionException { mApfTestHelpers.assertDrop(mApfVersion, gen, packet, filterAge); } private void assertDataMemoryContents(int expected, byte[] program, byte[] packet, byte[] data, byte[] expectedData) throws Exception { mApfTestHelpers.assertDataMemoryContents(mApfVersion, expected, program, packet, data, expectedData, false /* ignoreInterpreterVersion */); } private void assertDataMemoryContentsIgnoreVersion(int expected, byte[] program, byte[] packet, byte[] data, byte[] expectedData) throws Exception { mApfTestHelpers.assertDataMemoryContents(mApfVersion, expected, program, packet, data, expectedData, true /* ignoreInterpreterVersion */); } private void assertVerdict(String msg, int expected, byte[] program, byte[] packet, int filterAge) { mApfTestHelpers.assertVerdict(mApfVersion, msg, expected, program, packet, filterAge); } private void assertVerdict(int expected, byte[] program, byte[] packet) { mApfTestHelpers.assertVerdict(mApfVersion, expected, program, packet); } /** * 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. ApfV4Generator gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); assertPass(gen); // Test pass opcode gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addPass(); gen.addJump(DROP_LABEL); assertPass(gen); // Test jumping to pass label. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJump(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 ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJump(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 ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0Equals(0, DROP_LABEL); assertDrop(gen); // Test jumping if not equal to 0. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0NotEquals(0, DROP_LABEL); assertPass(gen); gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfR0NotEquals(0, DROP_LABEL); assertDrop(gen); // Test jumping if registers equal. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0EqualsR1(DROP_LABEL); assertDrop(gen); // Test jumping if registers not equal. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0NotEqualsR1(DROP_LABEL); assertPass(gen); gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfR0NotEqualsR1(DROP_LABEL); assertDrop(gen); // Test load immediate. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test add. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addAdd(1234567890); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test add with a small signed negative value. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addAdd(-1); gen.addJumpIfR0Equals(-1, DROP_LABEL); assertDrop(gen); // Test subtract. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addAdd(-1234567890); gen.addJumpIfR0Equals(-1234567890, DROP_LABEL); assertDrop(gen); // Test or. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addOr(1234567890); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test and. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addAnd(123456789); gen.addJumpIfR0Equals(1234567890 & 123456789, DROP_LABEL); assertDrop(gen); // Test left shift. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addLeftShift(1); gen.addJumpIfR0Equals(1234567890 << 1, DROP_LABEL); assertDrop(gen); // Test right shift. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addRightShift(1); gen.addJumpIfR0Equals(1234567890 >> 1, DROP_LABEL); assertDrop(gen); // Test multiply. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 123456789); gen.addMul(2); gen.addJumpIfR0Equals(123456789 * 2, DROP_LABEL); assertDrop(gen); // Test divide. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addDiv(2); gen.addJumpIfR0Equals(1234567890 / 2, DROP_LABEL); assertDrop(gen); // Test divide by zero. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addDiv(0); gen.addJump(DROP_LABEL); assertPass(gen); // Test add. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1234567890); gen.addAddR1ToR0(); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test subtract. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, -1234567890); gen.addAddR1ToR0(); gen.addJumpIfR0Equals(-1234567890, DROP_LABEL); assertDrop(gen); // Test or. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1234567890); gen.addOrR0WithR1(); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test and. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addLoadImmediate(R1, 123456789); gen.addAndR0WithR1(); gen.addJumpIfR0Equals(1234567890 & 123456789, DROP_LABEL); assertDrop(gen); // Test left shift. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addLoadImmediate(R1, 1); gen.addLeftShiftR0ByR1(); gen.addJumpIfR0Equals(1234567890 << 1, DROP_LABEL); assertDrop(gen); // Test right shift. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addLoadImmediate(R1, -1); gen.addLeftShiftR0ByR1(); gen.addJumpIfR0Equals(1234567890 >> 1, DROP_LABEL); assertDrop(gen); // Test multiply. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 123456789); gen.addLoadImmediate(R1, 2); gen.addMulR0ByR1(); gen.addJumpIfR0Equals(123456789 * 2, DROP_LABEL); assertDrop(gen); // Test divide. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addLoadImmediate(R1, 2); gen.addDivR0ByR1(); gen.addJumpIfR0Equals(1234567890 / 2, DROP_LABEL); assertDrop(gen); // Test divide by zero. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addDivR0ByR1(); gen.addJump(DROP_LABEL); assertPass(gen); // Test byte load. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoad8intoR0(1); gen.addJumpIfR0Equals(45, 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 ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoad8intoR0(16); gen.addJumpIfR0Equals(0, 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 ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoad16intoR0(1); gen.addJumpIfR0Equals((45 << 8) | 67, 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 ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoad32intoR0(1); gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, 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 ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1); gen.addLoad8R1IndexedIntoR0(0); gen.addJumpIfR0Equals(45, 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 ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 8); gen.addLoad8R1IndexedIntoR0(8); gen.addJumpIfR0Equals(0, 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 ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1); gen.addLoad16R1IndexedIntoR0(0); gen.addJumpIfR0Equals((45 << 8) | 67, 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 ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1); gen.addLoad32R1IndexedIntoR0(0); gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, 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 ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0GreaterThan(0, DROP_LABEL); assertPass(gen); gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfR0GreaterThan(0, DROP_LABEL); assertDrop(gen); // Test jumping if less than. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0LessThan(0, DROP_LABEL); assertPass(gen); gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0LessThan(1, DROP_LABEL); assertDrop(gen); // Test jumping if any bits set. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0AnyBitsSet(3, DROP_LABEL); assertPass(gen); gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfR0AnyBitsSet(3, DROP_LABEL); assertDrop(gen); gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 3); gen.addJumpIfR0AnyBitsSet(3, DROP_LABEL); assertDrop(gen); // Test jumping if register greater than. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0GreaterThanR1(DROP_LABEL); assertPass(gen); gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 2); gen.addLoadImmediate(R1, 1); gen.addJumpIfR0GreaterThanR1(DROP_LABEL); assertDrop(gen); // Test jumping if register less than. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0LessThanR1(DROP_LABEL); assertPass(gen); gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1); gen.addJumpIfR0LessThanR1(DROP_LABEL); assertDrop(gen); // Test jumping if any bits set in register. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 3); gen.addJumpIfR0AnyBitsSetR1(DROP_LABEL); assertPass(gen); gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 3); gen.addLoadImmediate(R0, 1); gen.addJumpIfR0AnyBitsSetR1(DROP_LABEL); assertDrop(gen); gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 3); gen.addLoadImmediate(R0, 3); gen.addJumpIfR0AnyBitsSetR1(DROP_LABEL); assertDrop(gen); // Test load from memory. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadFromMemory(R0, MemorySlot.SLOT_0); gen.addJumpIfR0Equals(0, DROP_LABEL); assertDrop(gen); // Test store to memory. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1234567890); gen.addStoreToMemory(MemorySlot.RAM_LEN, R1); gen.addLoadFromMemory(R0, MemorySlot.RAM_LEN); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test filter age pre-filled memory. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS); gen.addJumpIfR0Equals(123, DROP_LABEL); assertDrop(gen, new byte[MIN_PKT_SIZE], 123); // Test packet size pre-filled memory. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE); gen.addJumpIfR0Equals(MIN_PKT_SIZE, DROP_LABEL); assertDrop(gen); // Test IPv4 header size pre-filled memory. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE); gen.addJumpIfR0Equals(20, DROP_LABEL); assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,8,0,0x45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0); // Test not. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addNot(R0); gen.addJumpIfR0Equals(~1234567890, DROP_LABEL); assertDrop(gen); // Test negate. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addNeg(R0); gen.addJumpIfR0Equals(-1234567890, DROP_LABEL); assertDrop(gen); // Test move. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1234567890); gen.addMove(R0); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addMove(R1); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test swap. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1234567890); gen.addSwap(); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addSwap(); gen.addJumpIfR0Equals(0, DROP_LABEL); assertDrop(gen); // Test jump if bytes not equal. gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, 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 ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, 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 ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, DROP_LABEL); assertDrop(gen, packet123, 0); gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 30, 4, 5}, 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 ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 3, 4, 5}, DROP_LABEL); assertPass(gen, packet12345, 0); } @Test(expected = ApfV4Generator.IllegalInstructionException.class) public void testApfGeneratorWantsV2OrGreater() throws Exception { // The minimum supported APF version is 2. new ApfV4Generator(1, mRamSize, mClampSize); } @Test public void testApfDataOpcodesWantApfV3() throws IllegalInstructionException, Exception { ApfV4Generator gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); try { gen.addStoreData(R0, 0); fail(); } catch (IllegalInstructionException expected) { /* pass */ } try { gen.addLoadData(R0, 0); fail(); } catch (IllegalInstructionException expected) { /* pass */ } } /** * Test that the generator emits immediates using the shortest possible encoding. */ @Test public void testImmediateEncoding() throws IllegalInstructionException { ApfV4Generator gen; // 0-byte immediate: li R0, 0 gen = new ApfV4Generator(APF_VERSION_4, mRamSize, mClampSize); gen.addLoadImmediate(R0, 0); assertProgramEquals(new byte[]{LI_OP | SIZE0}, gen.generate()); // 1-byte immediate: li R0, 42 gen = new ApfV4Generator(APF_VERSION_4, mRamSize, mClampSize); gen.addLoadImmediate(R0, 42); assertProgramEquals(new byte[]{LI_OP | SIZE8, 42}, gen.generate()); // 2-byte immediate: li R1, 0x1234 gen = new ApfV4Generator(APF_VERSION_4, mRamSize, mClampSize); gen.addLoadImmediate(R1, 0x1234); assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1_REG, 0x12, 0x34}, gen.generate()); // 4-byte immediate: li R0, 0x12345678 gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(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 { ApfV4Generator gen; // 1-byte negative immediate: li R0, -42 gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, -42); assertProgramEquals(new byte[]{LI_OP | SIZE8, -42}, gen.generate()); // 2-byte negative immediate: li R1, -0x1122 gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R1, -0x1122); assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1_REG, (byte)0xEE, (byte)0xDE}, gen.generate()); // 4-byte negative immediate: li R0, -0x11223344 gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(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 { ApfV4Generator gen; // Load data with no offset: lddw R0, [0 + r1] gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadData(R0, 0); assertProgramEquals(new byte[]{LDDW_OP | SIZE0}, gen.generate()); // Store data with 8bit negative offset: lddw r0, [-42 + r1] gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addStoreData(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 ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addStoreData(R1, -0x1122); assertProgramEquals(new byte[]{STDW_OP | SIZE16 | R1_REG, (byte)0xEE, (byte)0xDE}, gen.generate()); // Load data to R1 with 32bit negative offset: lddw r1, [0xDEADBEEF + r0] gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadData(R1, 0xDEADBEEF); assertProgramEquals( new byte[]{LDDW_OP | SIZE32 | R1_REG, (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. ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); 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 ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, 0x87654321); gen.addLoadImmediate(R1, -5); gen.addStoreData(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. ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1000); gen.addLoadData(R0, -1006); // 1000 + -1006 = -6 (offset +10 with data_len=16) gen.addJumpIfR0Equals(0x87654321, 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 { ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R1, -22); gen.addLoadData(R0, 0); // Load from address 32 -22 + 0 = 10 gen.addAdd(0x78453412); // 87654321 + 78453412 = FFAA7733 gen.addStoreData(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. ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, 3); gen.addLoadData(R1, 7); gen.addJump(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. // 3 instructions, all normal opcodes (LI, LDDW, JMP) with 1 byte immediate = 6 byte program // 32 byte data length, for a total of 38 byte ram len. // APFv6 needs to round this up to be a multiple of 4, so 40. gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, 20); if (mApfVersion == 4) { gen.addLoadData(R1, 15); // R0(20)+15+U32[0..3] >= 6 prog + 32 data, so invalid } else { gen.addLoadData(R1, 17); // R0(20)+17+U32[0..3] >= 6 prog + 2 pad + 32 data, so invalid } gen.addJump(DROP_LABEL); // Not reached. assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data); // Subtracting an immediate should work... gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, -4); gen.addJump(DROP_LABEL); assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data); // ...and underflowing simply wraps around to the end of the buffer... gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, -30); gen.addJump(DROP_LABEL); assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data); // ...but doesn't allow accesses before the start of the buffer gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, -1000); gen.addJump(DROP_LABEL); // Not reached. assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data); } private void pretendPacketReceived(byte[] packet) throws InterruptedIOException, ErrnoException { Os.write(mWriteSocket, packet, 0, packet.length); } private ApfFilter getApfFilter(ApfFilter.ApfConfiguration config) { AtomicReference apfFilter = new AtomicReference<>(); mHandler.post(() -> apfFilter.set(new ApfFilter(mHandler, mContext, config, TEST_PARAMS, mApfController, mNetworkQuirkMetrics, mDependencies))); HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS); return apfFilter.get(); } /** * 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); LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_PCAP_IPV4_ADDR), 16); LinkProperties lp = new LinkProperties(); lp.addLinkAddress(link); ApfConfiguration config = getDefaultConfig(); config.apfVersionSupported = 4; config.apfRamSize = 1700; config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; final ApfFilter apfFilter = getApfFilter(config); mApfTestHelpers.consumeInstalledProgram(mApfController, 2 /* installCnt */); apfFilter.setLinkProperties(lp); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); byte[] data = new byte[Counter.totalSize()]; final boolean result; result = mApfTestHelpers.dropsAllPackets( mApfVersion, 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 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 byte[] ETH_MULTICAST_MDNS_v4_MAC_ADDRESS = {(byte) 0x01, (byte) 0x00, (byte) 0x5e, (byte) 0x00, (byte) 0x00, (byte) 0xfb}; private static final byte[] ETH_MULTICAST_MDNS_V6_MAC_ADDRESS = {(byte) 0x33, (byte) 0x33, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xfb}; private static final int IP_HEADER_OFFSET = ETH_HEADER_LEN; private static final int IPV4_HEADER_LEN = 20; private static final int IPV4_PROTOCOL_OFFSET = IP_HEADER_OFFSET + 9; private static final int IPV4_DEST_ADDR_OFFSET = IP_HEADER_OFFSET + 16; private static final int IPV4_TCP_HEADER_OFFSET = IP_HEADER_OFFSET + IPV4_HEADER_LEN; private static final int IPV4_UDP_HEADER_OFFSET = IP_HEADER_OFFSET + IPV4_HEADER_LEN; private static final byte[] IPV4_BROADCAST_ADDRESS = {(byte) 255, (byte) 255, (byte) 255, (byte) 255}; private static final int IPV6_HEADER_LEN = 40; private static final int IPV6_PAYLOAD_LENGTH_OFFSET = IP_HEADER_OFFSET + 4; private static final int IPV6_NEXT_HEADER_OFFSET = IP_HEADER_OFFSET + 6; private static final int IPV6_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 8; private static final int IPV6_DEST_ADDR_OFFSET = IP_HEADER_OFFSET + 24; private static final int IPV6_PAYLOAD_OFFSET = IP_HEADER_OFFSET + IPV6_HEADER_LEN; // 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 byte[] IPV6_SOLICITED_NODE_MULTICAST_ADDRESS = { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, (byte) 0xff, (byte) 0xab, (byte) 0xcd, (byte) 0xef, }; private static final int ICMP6_TYPE_OFFSET = IP_HEADER_OFFSET + 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_ANNOUNCEMENT = 136; private static final int ICMP6_RA_HEADER_LEN = 16; private static final int ICMP6_RA_CHECKSUM_OFFSET = IP_HEADER_OFFSET + IPV6_HEADER_LEN + 2; private static final int ICMP6_RA_REACHABLE_TIME_OFFSET = IP_HEADER_OFFSET + IPV6_HEADER_LEN + 8; private static final int ICMP6_RA_OPTION_OFFSET = IP_HEADER_OFFSET + 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; // 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 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}; private static final byte[] IPV4_MDNS_MULTICAST_ADDR = {(byte) 224, 0, 0, (byte) 251}; private static final byte[] IPV6_MDNS_MULTICAST_ADDR = {(byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfb}; private static final int MDNS_UDP_PORT = 5353; private static void setIpv4VersionFields(ByteBuffer packet) { packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IP); packet.put(IP_HEADER_OFFSET, (byte) 0x45); } private static void setIpv6VersionFields(ByteBuffer packet) { packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6); packet.put(IP_HEADER_OFFSET, (byte) 0x60); } private static ByteBuffer makeIpv4Packet(int proto) { ByteBuffer packet = ByteBuffer.wrap(new byte[100]); setIpv4VersionFields(packet); packet.put(IPV4_PROTOCOL_OFFSET, (byte) proto); return packet; } private static ByteBuffer makeIpv6Packet(int nextHeader) { ByteBuffer packet = ByteBuffer.wrap(new byte[100]); setIpv6VersionFields(packet); packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) nextHeader); return packet; } @Test public void testApfFilterIPv4() throws Exception { LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19); LinkProperties lp = new LinkProperties(); lp.addLinkAddress(link); ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; final ApfFilter apfFilter = getApfFilter(config); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); apfFilter.setLinkProperties(lp); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); ByteBuffer packet = ByteBuffer.wrap(new byte[100]); if (SdkLevel.isAtLeastV()) { // Verify empty packet of 100 zero bytes is dropped assertDrop(program, packet.array()); } else { // Verify empty packet of 100 zero bytes is passed assertPass(program, packet.array()); } // Verify unicast IPv4 packet is passed put(packet, ETH_DEST_ADDR_OFFSET, TEST_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(IP_HEADER_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, TEST_MAC_ADDR); assertPass(program, packet.array()); // Verify unicast IPv4 DHCP to us is passed put(packet, ETH_DEST_ADDR_OFFSET, TEST_MAC_ADDR); assertPass(program, packet.array()); } @Test public void testApfFilterIPv6() throws Exception { ApfConfiguration config = getDefaultConfig(); ApfFilter apfFilter = getApfFilter(config); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Verify empty IPv6 packet is passed ByteBuffer packet = makeIpv6Packet(IPPROTO_UDP); 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 NA to ff02::2 is dropped put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS); assertDrop(program, packet.array()); // Verify ICMPv6 NA to Solicited-Node Multicast is passed put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_SOLICITED_NODE_MULTICAST_ADDRESS); assertPass(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()); } private static void fillQuestionSection(ByteBuffer buf, String... qnames) throws IOException { buf.put(new DnsPacket.DnsHeader(0 /* id */, 0 /* flags */, qnames.length, 0 /* ancount */) .getBytes()); for (String qname : qnames) { buf.put(DnsPacket.DnsRecord.makeQuestion(qname, 0 /* nsType */, 0 /* nsClass */) .getBytes()); } } private static byte[] makeMdnsV4Packet(String... qnames) throws IOException { final ByteBuffer buf = ByteBuffer.wrap(new byte[256]); final PacketBuilder builder = new PacketBuilder(buf); builder.writeL2Header(MacAddress.fromString("11:22:33:44:55:66"), MacAddress.fromBytes(ETH_MULTICAST_MDNS_v4_MAC_ADDRESS), (short) ETH_P_IP); builder.writeIpv4Header((byte) 0 /* tos */, (short) 0 /* id */, (short) 0 /* flagsAndFragmentOffset */, (byte) 0 /* ttl */, (byte) IPPROTO_UDP, (Inet4Address) Inet4Address.getByAddress(IPV4_SOURCE_ADDR), (Inet4Address) Inet4Address.getByAddress(IPV4_MDNS_MULTICAST_ADDR)); builder.writeUdpHeader((short) MDNS_UDP_PORT, (short) MDNS_UDP_PORT); fillQuestionSection(buf, qnames); return builder.finalizePacket().array(); } private static byte[] makeMdnsV6Packet(String... qnames) throws IOException { ByteBuffer buf = ByteBuffer.wrap(new byte[256]); final PacketBuilder builder = new PacketBuilder(buf); builder.writeL2Header(MacAddress.fromString("11:22:33:44:55:66"), MacAddress.fromBytes(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS), (short) ETH_P_IPV6); builder.writeIpv6Header(0x680515ca /* vtf */, (byte) IPPROTO_UDP, (short) 0 /* hopLimit */, (Inet6Address) InetAddress.getByAddress(IPV6_ANOTHER_ADDR), (Inet6Address) Inet6Address.getByAddress(IPV6_MDNS_MULTICAST_ADDR)); builder.writeUdpHeader((short) MDNS_UDP_PORT, (short) MDNS_UDP_PORT); fillQuestionSection(buf, qnames); return builder.finalizePacket().array(); } private static void putLabel(ByteBuffer buf, String label) { final byte[] bytes = label.getBytes(StandardCharsets.UTF_8); buf.put((byte) bytes.length); buf.put(bytes); } private static void putPointer(ByteBuffer buf, int offset) { short pointer = (short) (offset | 0xc000); buf.putShort(pointer); } // Simplistic DNS compression code that intentionally does not depend on production code. private static List> getDnsLabels(int startOffset, String... names) { // Maps all possible name suffixes to packet offsets. final HashMap mPointerOffsets = new HashMap<>(); final List> out = new ArrayList<>(); int offset = startOffset; for (int i = 0; i < names.length; i++) { String name = names[i]; while (true) { if (name.length() == 0) { out.add(label("")); offset += 1 + 4; // 1-byte label, DNS query break; } final int pointerOffset = mPointerOffsets.getOrDefault(name, -1); if (pointerOffset != -1) { out.add(pointer(pointerOffset)); offset += 2 + 4; // 2-byte pointer, DNS query break; } mPointerOffsets.put(name, offset); final int indexOfDot = name.indexOf("."); final String label; if (indexOfDot == -1) { label = name; name = ""; } else { label = name.substring(0, indexOfDot); name = name.substring(indexOfDot + 1); } out.add(label(label)); offset += 1 + label.length(); } } return out; } static Pair label(String label) { return Pair.create(label.length(), label); } static Pair pointer(int offset) { return Pair.create(0xc000 | offset, null); } @Test public void testGetDnsLabels() throws Exception { int startOffset = 12; List> actual = getDnsLabels(startOffset, "myservice.tcp.local"); assertEquals(4, actual.size()); assertEquals(label("myservice"), actual.get(0)); assertEquals(label("tcp"), actual.get(1)); assertEquals(label("local"), actual.get(2)); assertEquals(label(""), actual.get(3)); startOffset = 30; actual = getDnsLabels(startOffset, "myservice.tcp.local", "foo.tcp.local", "myhostname.local", "bar.udp.local", "foo.myhostname.local"); final int tcpLocalOffset = startOffset + 1 + "myservice".length(); final int localOffset = startOffset + 1 + "myservice".length() + 1 + "tcp".length(); final int myhostnameLocalOffset = 30 + 1 + "myservice".length() + 1 + "tcp".length() + 1 + "local".length() + 1 + 4 + 1 + "foo".length() + 2 + 4; assertEquals(13, actual.size()); assertEquals(label("myservice"), actual.get(0)); assertEquals(label("tcp"), actual.get(1)); assertEquals(label("local"), actual.get(2)); assertEquals(label(""), actual.get(3)); assertEquals(label("foo"), actual.get(4)); assertEquals(pointer(tcpLocalOffset), actual.get(5)); assertEquals(label("myhostname"), actual.get(6)); assertEquals(pointer(localOffset), actual.get(7)); assertEquals(label("bar"), actual.get(8)); assertEquals(label("udp"), actual.get(9)); assertEquals(pointer(localOffset), actual.get(10)); assertEquals(label("foo"), actual.get(11)); assertEquals(pointer(myhostnameLocalOffset), actual.get(12)); } private static byte[] makeMdnsCompressedV6Packet(String... names) throws IOException { ByteBuffer questions = ByteBuffer.allocate(1500); questions.put(new DnsPacket.DnsHeader(123, 0, names.length, 0).getBytes()); final List> labels = getDnsLabels(questions.position(), names); for (Pair label : labels) { final String name = label.second; if (name == null) { putPointer(questions, label.first); } else { putLabel(questions, name); } if (TextUtils.isEmpty(name)) { questions.put(new byte[4]); } } questions.flip(); ByteBuffer buf = PacketBuilder.allocate(/*hasEther=*/ true, IPPROTO_IPV6, IPPROTO_UDP, questions.limit()); final PacketBuilder builder = new PacketBuilder(buf); builder.writeL2Header(MacAddress.fromString("11:22:33:44:55:66"), MacAddress.fromBytes(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS), (short) ETH_P_IPV6); builder.writeIpv6Header(0x680515ca /* vtf */, (byte) IPPROTO_UDP, (short) 0 /* hopLimit */, (Inet6Address) InetAddress.getByAddress(IPV6_ANOTHER_ADDR), (Inet6Address) Inet6Address.getByAddress(IPV6_MDNS_MULTICAST_ADDR)); builder.writeUdpHeader((short) MDNS_UDP_PORT, (short) MDNS_UDP_PORT); buf.put(questions); return builder.finalizePacket().array(); } private static byte[] makeMdnsCompressedV6Packet() throws IOException { return makeMdnsCompressedV6Packet("myservice.tcp.local", "googlecast.tcp.local", "matter.tcp.local", "myhostname.local"); } private static byte[] makeMdnsCompressedV6PacketWithManyNames() throws IOException { return makeMdnsCompressedV6Packet("myservice.tcp.local", "googlecast.tcp.local", "matter.tcp.local", "myhostname.local", "myhostname2.local", "myhostname3.local", "myhostname4.local", "myhostname5.local", "myhostname6.local", "myhostname7.local"); } @Test public void testAddNopAddsOneByte() throws Exception { ApfV4Generator gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addNop(); assertEquals(1, gen.generate().length); final int count = 42; gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); for (int i = 0; i < count; i++) { gen.addNop(); } assertEquals(count, gen.generate().length); } @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}; 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; final ApfFilter apfFilter = getApfFilter(config); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); apfFilter.setLinkProperties(lp); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Construct IPv4 and IPv6 multicast packets. ByteBuffer mcastv4packet = makeIpv4Packet(IPPROTO_UDP); put(mcastv4packet, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr); ByteBuffer mcastv6packet = makeIpv6Packet(IPPROTO_UDP); put(mcastv6packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr); // Construct IPv4 broadcast packet. ByteBuffer bcastv4packet1 = makeIpv4Packet(IPPROTO_UDP); bcastv4packet1.put(ETH_BROADCAST_MAC_ADDRESS); bcastv4packet1.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP); put(bcastv4packet1, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr); ByteBuffer bcastv4packet2 = makeIpv4Packet(IPPROTO_UDP); 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 = makeIpv4Packet(IPPROTO_UDP); bcastv4unicastl2packet.put(TEST_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 apfFilter.setMulticastFilter(true); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); 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 apfFilter.setMulticastFilter(false); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); 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 config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; clearInvocations(mApfController); final ApfFilter apfFilter2 = getApfFilter(config); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); apfFilter2.setLinkProperties(lp); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); 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()); } @Test public void testApfFilterMulticastPingWhileDozing() throws Exception { doTestApfFilterMulticastPingWhileDozing(false /* isLightDozing */); } @Test @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) public void testApfFilterMulticastPingWhileLightDozing() throws Exception { doTestApfFilterMulticastPingWhileDozing(true /* isLightDozing */); } private void doTestApfFilterMulticastPingWhileDozing(boolean isLightDozing) throws Exception { final ApfConfiguration configuration = getDefaultConfig(); final ApfFilter apfFilter = getApfFilter(configuration); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); final ArgumentCaptor receiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture()); final BroadcastReceiver receiver = receiverCaptor.getValue(); // 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}; final ByteBuffer packet = makeIpv6Packet(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(program, packet.array()); if (isLightDozing) { doReturn(true).when(mPowerManager).isDeviceLightIdleMode(); receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED)); } else { doReturn(true).when(mPowerManager).isDeviceIdleMode(); receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED)); } program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // ...and even while dozing... assertPass(program, packet.array()); // ...but when the multicast filter is also enabled, drop the multicast pings to save power. apfFilter.setMulticastFilter(true); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); assertDrop(program, packet.array()); // However, we should still let through all other ICMPv6 types. ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone()); setIpv6VersionFields(packet); packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) IPPROTO_ICMPV6); raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT); assertPass(program, 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). if (isLightDozing) { doReturn(false).when(mPowerManager).isDeviceLightIdleMode(); receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED)); } else { doReturn(false).when(mPowerManager).isDeviceIdleMode(); receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED)); } program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); assertPass(program, packet.array()); } @Test @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void testApfFilter802_3() throws Exception { ApfConfiguration config = getDefaultConfig(); ApfFilter apfFilter = getApfFilter(config); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // 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 setIpv4VersionFields(packet); assertPass(program, packet.array()); // Verify empty IPv6 packet is passed setIpv6VersionFields(packet); assertPass(program, packet.array()); // Now turn on the filter config.ieee802_3Filter = DROP_802_3_FRAMES; apfFilter = getApfFilter(config); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // 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 setIpv4VersionFields(packet); assertPass(program, packet.array()); // Verify that IPv6 (as example of Ethernet II) frame will pass setIpv6VersionFields(packet); assertPass(program, packet.array()); } @Test @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void testApfFilterEthTypeBL() throws Exception { final int[] emptyBlackList = {}; final int[] ipv4BlackList = {ETH_P_IP}; final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6}; ApfConfiguration config = getDefaultConfig(); ApfFilter apfFilter = getApfFilter(config); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // 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 setIpv4VersionFields(packet); assertPass(program, packet.array()); // Verify empty IPv6 packet is passed setIpv6VersionFields(packet); assertPass(program, packet.array()); // Now add IPv4 to the black list config.ethTypeBlackList = ipv4BlackList; apfFilter = getApfFilter(config); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Verify that IPv4 frame will be dropped setIpv4VersionFields(packet); assertDrop(program, packet.array()); // Verify that IPv6 frame will pass setIpv6VersionFields(packet); assertPass(program, packet.array()); // Now let us have both IPv4 and IPv6 in the black list config.ethTypeBlackList = ipv4Ipv6BlackList; apfFilter = getApfFilter(config); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Verify that IPv4 frame will be dropped setIpv4VersionFields(packet); assertDrop(program, packet.array()); // Verify that IPv6 frame will be dropped setIpv6VersionFields(packet); assertDrop(program, packet.array()); } private void verifyArpFilter(byte[] program, int filterResult) { // Verify ARP request packet assertPass(program, arpRequestBroadcast(MOCK_IPV4_ADDR)); assertVerdict(filterResult, program, arpRequestBroadcast(ANOTHER_IPV4_ADDR)); assertVerdict(filterResult, 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 { ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; ApfFilter apfFilter = getApfFilter(config); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Verify initially ARP request filter is off, and GARP filter is on. verifyArpFilter(program, 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)); apfFilter.setLinkProperties(lp); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); verifyArpFilter(program, DROP); apfFilter.setLinkProperties(new LinkProperties()); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Inform ApfFilter of loss of IP and verify ARP filtering is off verifyArpFilter(program, PASS); } 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(); } private static final byte[] IPV6_ANOTHER_ADDR = {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf5}; private static class RaPacketBuilder { final ByteArrayOutputStream mPacket = new ByteArrayOutputStream(); int mFlowLabel = 0x12345; int mReachableTime = 30_000; int mRetransmissionTimer = 1000; public RaPacketBuilder(int routerLft) throws Exception { InetAddress src = InetAddress.getByName("fe80::1234:abcd"); ByteBuffer buffer = ByteBuffer.allocate(ICMP6_RA_OPTION_OFFSET); buffer.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6); buffer.position(ETH_HEADER_LEN); // skip version, tclass, flowlabel; set in build() buffer.position(buffer.position() + 4); buffer.putShort((short) 0); // Payload length; updated later buffer.put((byte) IPPROTO_ICMPV6); // Next header buffer.put((byte) 0xff); // Hop limit buffer.put(src.getAddress()); // Source address buffer.put(IPV6_ALL_NODES_ADDRESS); // Destination address buffer.put((byte) ICMP6_ROUTER_ADVERTISEMENT); // Type buffer.put((byte) 0); // Code (0) buffer.putShort((short) 0); // Checksum (ignored) buffer.put((byte) 64); // Hop limit buffer.put((byte) 0); // M/O, reserved buffer.putShort((short) routerLft); // Router lifetime // skip reachable time; set in build() // skip retransmission timer; set in build(); mPacket.write(buffer.array(), 0, buffer.capacity()); } public RaPacketBuilder setFlowLabel(int flowLabel) { mFlowLabel = flowLabel; return this; } public RaPacketBuilder setReachableTime(int reachable) { mReachableTime = reachable; return this; } public RaPacketBuilder setRetransmissionTimer(int retrans) { mRetransmissionTimer = retrans; return this; } public RaPacketBuilder addPioOption(int valid, int preferred, String prefixString) throws Exception { ByteBuffer buffer = ByteBuffer.allocate(ICMP6_PREFIX_OPTION_LEN); IpPrefix prefix = new IpPrefix(prefixString); buffer.put((byte) ICMP6_PREFIX_OPTION_TYPE); // Type buffer.put((byte) 4); // Length in 8-byte units buffer.put((byte) prefix.getPrefixLength()); // Prefix length buffer.put((byte) 0b11000000); // L = 1, A = 1 buffer.putInt(valid); buffer.putInt(preferred); buffer.putInt(0); // Reserved buffer.put(prefix.getRawAddress()); mPacket.write(buffer.array(), 0, buffer.capacity()); return this; } public RaPacketBuilder addRioOption(int lifetime, String prefixString) throws Exception { IpPrefix prefix = new IpPrefix(prefixString); int optionLength; if (prefix.getPrefixLength() == 0) { optionLength = 1; } else if (prefix.getPrefixLength() <= 64) { optionLength = 2; } else { optionLength = 3; } ByteBuffer buffer = ByteBuffer.allocate(optionLength * 8); buffer.put((byte) ICMP6_ROUTE_INFO_OPTION_TYPE); // Type buffer.put((byte) optionLength); // Length in 8-byte units buffer.put((byte) prefix.getPrefixLength()); // Prefix length buffer.put((byte) 0b00011000); // Pref = high buffer.putInt(lifetime); // Lifetime byte[] prefixBytes = prefix.getRawAddress(); buffer.put(prefixBytes, 0, (optionLength - 1) * 8); mPacket.write(buffer.array(), 0, buffer.capacity()); return this; } public RaPacketBuilder addDnsslOption(int lifetime, String... domains) { ByteArrayOutputStream dnssl = new ByteArrayOutputStream(); for (String domain : domains) { for (String label : domain.split("\\.")) { final byte[] bytes = label.getBytes(StandardCharsets.UTF_8); dnssl.write((byte) bytes.length); dnssl.write(bytes, 0, bytes.length); } dnssl.write((byte) 0); } // Extend with 0s to make it 8-byte aligned. while (dnssl.size() % 8 != 0) { dnssl.write((byte) 0); } final int length = ICMP6_4_BYTE_OPTION_LEN + dnssl.size(); ByteBuffer buffer = ByteBuffer.allocate(length); buffer.put((byte) ICMP6_DNSSL_OPTION_TYPE); // Type buffer.put((byte) (length / 8)); // Length // skip past reserved bytes buffer.position(buffer.position() + 2); buffer.putInt(lifetime); // Lifetime buffer.put(dnssl.toByteArray()); // Domain names mPacket.write(buffer.array(), 0, buffer.capacity()); return this; } public RaPacketBuilder addRdnssOption(int lifetime, String... servers) throws Exception { int optionLength = 1 + 2 * servers.length; // In 8-byte units ByteBuffer buffer = ByteBuffer.allocate(optionLength * 8); buffer.put((byte) ICMP6_RDNSS_OPTION_TYPE); // Type buffer.put((byte) optionLength); // Length buffer.putShort((short) 0); // Reserved buffer.putInt(lifetime); // Lifetime for (String server : servers) { buffer.put(InetAddress.getByName(server).getAddress()); } mPacket.write(buffer.array(), 0, buffer.capacity()); return this; } public RaPacketBuilder addZeroLengthOption() throws Exception { ByteBuffer buffer = ByteBuffer.allocate(ICMP6_4_BYTE_OPTION_LEN); buffer.put((byte) ICMP6_PREFIX_OPTION_TYPE); buffer.put((byte) 0); mPacket.write(buffer.array(), 0, buffer.capacity()); return this; } public byte[] build() { ByteBuffer buffer = ByteBuffer.wrap(mPacket.toByteArray()); // IPv6, traffic class = 0, flow label = mFlowLabel buffer.putInt(IP_HEADER_OFFSET, 0x60000000 | (0xFFFFF & mFlowLabel)); buffer.putShort(IPV6_PAYLOAD_LENGTH_OFFSET, (short) buffer.capacity()); buffer.position(ICMP6_RA_REACHABLE_TIME_OFFSET); buffer.putInt(mReachableTime); buffer.putInt(mRetransmissionTimer); return buffer.array(); } } private byte[] buildLargeRa() throws Exception { RaPacketBuilder builder = new RaPacketBuilder(1800 /* router lft */); builder.addRioOption(1200, "64:ff9b::/96"); builder.addRdnssOption(7200, "2001:db8:1::1", "2001:db8:1::2"); builder.addRioOption(2100, "2000::/3"); builder.addRioOption(2400, "::/0"); builder.addPioOption(600, 300, "2001:db8:a::/64"); builder.addRioOption(1500, "2001:db8:c:d::/64"); builder.addPioOption(86400, 43200, "fd95:d1e:12::/64"); return builder.build(); } @Test public void testRaToString() throws Exception { ApfConfiguration config = getDefaultConfig(); ApfFilter apfFilter = getApfFilter(config); byte[] packet = buildLargeRa(); ApfFilter.Ra ra = apfFilter.new Ra(packet, packet.length); String expected = "RA fe80::1234:abcd -> ff02::1 1800s " + "2001:db8:a::/64 600s/300s fd95:d1e:12::/64 86400s/43200s " + "DNS 7200s 2001:db8:1::1 2001:db8:1::2 " + "RIO 1200s 64:ff9b::/96 RIO 2100s 2000::/3 " + "RIO 2400s ::/0 RIO 1500s 2001:db8:c:d::/64 "; assertEquals(expected, ra.toString()); } // 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) { verifyRaLifetime(program, packet, lifetime, 0); } // Verify that the last program pushed to the IpClient.Callback properly filters the // given packet for the given lifetime and programInstallTime. programInstallTime is // the time difference between when RA is last seen and the program is installed. private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime, int programInstallTime) { final int FRACTION_OF_LIFETIME = 6; final int ageLimit = lifetime / FRACTION_OF_LIFETIME - programInstallTime; // 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 (e.g., a change in the source address) make it not match. final int offset = IPV6_SRC_ADDR_OFFSET + 5; final byte originalByte = packet.get(offset); packet.put(offset, (byte) (~originalByte)); assertPass(program, packet.array()); packet.put(offset, originalByte); assertDrop(program, packet.array()); } // Test that when ApfFilter is shown the given packet, it generates a program to filter it // for the given lifetime. private byte[] verifyRaLifetime(ByteBuffer packet, int lifetime) throws IOException, ErrnoException { // Verify new program generated if ApfFilter witnesses RA clearInvocations(mApfController); pretendPacketReceived(packet.array()); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); verifyRaLifetime(program, packet, lifetime); return program; } private void assertInvalidRa(ByteBuffer packet) throws IOException, ErrnoException, InterruptedException { clearInvocations(mApfController); pretendPacketReceived(packet.array()); Thread.sleep(NO_CALLBACK_TIMEOUT_MS); verify(mApfController, never()).installPacketFilter(any(), any()); } @Test public void testApfFilterRa() throws Exception { ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; ApfFilter apfFilter = getApfFilter(config); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); 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; // Verify RA is passed the first time RaPacketBuilder ra = new RaPacketBuilder(ROUTER_LIFETIME); ByteBuffer basePacket = ByteBuffer.wrap(ra.build()); assertPass(program, basePacket.array()); verifyRaLifetime(basePacket, ROUTER_LIFETIME); ra = new RaPacketBuilder(ROUTER_LIFETIME); // Check that changes are ignored in every byte of the flow label. ra.setFlowLabel(0x56789); ByteBuffer newFlowLabelPacket = ByteBuffer.wrap(ra.build()); // Ensure zero-length options cause the packet to be silently skipped. // Do this before we test other packets. http://b/29586253 ra = new RaPacketBuilder(ROUTER_LIFETIME); ra.addZeroLengthOption(); ByteBuffer zeroLengthOptionPacket = ByteBuffer.wrap(ra.build()); assertInvalidRa(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. ra = new RaPacketBuilder(ROUTER_LIFETIME); ra.addPioOption(PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, "2001:db8::/64"); ByteBuffer prefixOptionPacket = ByteBuffer.wrap(ra.build()); verifyRaLifetime(prefixOptionPacket, PREFIX_PREFERRED_LIFETIME); ra = new RaPacketBuilder(ROUTER_LIFETIME); ra.addRdnssOption(RDNSS_LIFETIME, "2001:4860:4860::8888", "2001:4860:4860::8844"); ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(ra.build()); verifyRaLifetime(rdnssOptionPacket, RDNSS_LIFETIME); final int lowLifetime = 60; ra = new RaPacketBuilder(ROUTER_LIFETIME); ra.addRdnssOption(lowLifetime, "2620:fe::9"); ByteBuffer lowLifetimeRdnssOptionPacket = ByteBuffer.wrap(ra.build()); verifyRaLifetime(lowLifetimeRdnssOptionPacket, ROUTER_LIFETIME); ra = new RaPacketBuilder(ROUTER_LIFETIME); ra.addRioOption(ROUTE_LIFETIME, "64:ff9b::/96"); ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(ra.build()); program = verifyRaLifetime(routeInfoOptionPacket, ROUTE_LIFETIME); // Check that RIOs differing only in the first 4 bytes are different. ra = new RaPacketBuilder(ROUTER_LIFETIME); ra.addRioOption(ROUTE_LIFETIME, "64:ff9b::/64"); // Packet should be passed because it is different. assertPass(program, ra.build()); ra = new RaPacketBuilder(ROUTER_LIFETIME); ra.addDnsslOption(DNSSL_LIFETIME, "test.example.com", "one.more.example.com"); ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(ra.build()); verifyRaLifetime(dnsslOptionPacket, ROUTER_LIFETIME); ByteBuffer largeRaPacket = ByteBuffer.wrap(buildLargeRa()); program = verifyRaLifetime(largeRaPacket, 300); // Verify that current program filters all the RAs (note: ApfFilter.MAX_RAS == 10). verifyRaLifetime(program, basePacket, ROUTER_LIFETIME); verifyRaLifetime(program, newFlowLabelPacket, ROUTER_LIFETIME); verifyRaLifetime(program, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME); verifyRaLifetime(program, rdnssOptionPacket, RDNSS_LIFETIME); verifyRaLifetime(program, lowLifetimeRdnssOptionPacket, ROUTER_LIFETIME); verifyRaLifetime(program, routeInfoOptionPacket, ROUTE_LIFETIME); verifyRaLifetime(program, dnsslOptionPacket, ROUTER_LIFETIME); verifyRaLifetime(program, largeRaPacket, 300); } @Test public void testRaWithDifferentReachableTimeAndRetransTimer() throws Exception { final ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; final ApfFilter apfFilter = getApfFilter(config); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); final int RA_REACHABLE_TIME = 1800; final int RA_RETRANSMISSION_TIMER = 1234; // Create an Ra packet without options // Reachable time = 1800, retransmission timer = 1234 RaPacketBuilder ra = new RaPacketBuilder(1800 /* router lft */); ra.setReachableTime(RA_REACHABLE_TIME); ra.setRetransmissionTimer(RA_RETRANSMISSION_TIMER); byte[] raPacket = ra.build(); // First RA passes filter assertPass(program, raPacket); // Assume apf is shown the given RA, it generates program to filter it. pretendPacketReceived(raPacket); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); assertDrop(program, raPacket); // A packet with different reachable time should be passed. // Reachable time = 2300, retransmission timer = 1234 ra.setReachableTime(RA_REACHABLE_TIME + 500); raPacket = ra.build(); assertPass(program, raPacket); // A packet with different retransmission timer should be passed. // Reachable time = 1800, retransmission timer = 2234 ra.setReachableTime(RA_REACHABLE_TIME); ra.setRetransmissionTimer(RA_RETRANSMISSION_TIMER + 1000); raPacket = ra.build(); assertPass(program, raPacket); } // The ByteBuffer is always created by ByteBuffer#wrap in the helper functions @SuppressWarnings("ByteBufferBackingArray") @Test public void testRaWithProgramInstalledSomeTimeAfterLastSeen() throws Exception { final ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; final ApfFilter apfFilter = getApfFilter(config); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); final int routerLifetime = 1000; final int timePassedSeconds = 12; // Verify that when the program is generated and installed some time after RA is last seen // it should be installed with the correct remaining lifetime. ByteBuffer basePacket = ByteBuffer.wrap(new RaPacketBuilder(routerLifetime).build()); verifyRaLifetime(basePacket, routerLifetime); mCurrentTimeMs += timePassedSeconds * DateUtils.SECOND_IN_MILLIS; doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime(); synchronized (apfFilter) { apfFilter.installNewProgram(); } byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); verifyRaLifetime(program, basePacket, routerLifetime, timePassedSeconds); // Packet should be passed if the program is installed after 1/6 * lifetime from last seen mCurrentTimeMs += ((routerLifetime / 6) - timePassedSeconds - 1) * DateUtils.SECOND_IN_MILLIS; doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime(); synchronized (apfFilter) { apfFilter.installNewProgram(); } program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); assertDrop(program, basePacket.array()); mCurrentTimeMs += DateUtils.SECOND_IN_MILLIS; doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime(); synchronized (apfFilter) { apfFilter.installNewProgram(); } program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); assertPass(program, basePacket.array()); } /** * Stage a file for testing, i.e. make it native accessible. Given a resource ID, * copy that resource into the app's data directory and return the path to it. */ 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(); ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; ApfFilter apfFilter = getApfFilter(config); 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(); ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; ApfFilter apfFilter = getApfFilter(config); 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); } } } @Test public void testMatchedRaUpdatesLifetime() throws Exception { final ApfFilter apfFilter = getApfFilter(getDefaultConfig()); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Create an RA and build an APF program byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build(); pretendPacketReceived(ra); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // lifetime dropped significantly, assert pass ra = new RaPacketBuilder(200 /* router lifetime */).build(); assertPass(program, ra); // update program with the new RA pretendPacketReceived(ra); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // assert program was updated and new lifetimes were taken into account. assertDrop(program, ra); } @Test public void testProcessRaWithInfiniteLifeTimeWithoutCrash() throws Exception { // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; ApfFilter apfFilter = getApfFilter(config); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Template packet: // Frame 1: 150 bytes on wire (1200 bits), 150 bytes captured (1200 bits) // Ethernet II, Src: Netgear_23:67:2c (28:c6:8e:23:67:2c), Dst: IPv6mcast_01 (33:33:00:00:00:01) // Internet Protocol Version 6, Src: fe80::2ac6:8eff:fe23:672c, Dst: ff02::1 // Internet Control Message Protocol v6 // Type: Router Advertisement (134) // Code: 0 // Checksum: 0x0acd [correct] // Checksum Status: Good // Cur hop limit: 64 // Flags: 0xc0, Managed address configuration, Other configuration, Prf (Default Router Preference): Medium // Router lifetime (s): 7000 // Reachable time (ms): 0 // Retrans timer (ms): 0 // ICMPv6 Option (Source link-layer address : 28:c6:8e:23:67:2c) // Type: Source link-layer address (1) // Length: 1 (8 bytes) // Link-layer address: Netgear_23:67:2c (28:c6:8e:23:67:2c) // Source Link-layer address: Netgear_23:67:2c (28:c6:8e:23:67:2c) // ICMPv6 Option (MTU : 1500) // Type: MTU (5) // Length: 1 (8 bytes) // Reserved // MTU: 1500 // ICMPv6 Option (Prefix information : 2401:fa00:480:f000::/64) // Type: Prefix information (3) // Length: 4 (32 bytes) // Prefix Length: 64 // Flag: 0xc0, On-link flag(L), Autonomous address-configuration flag(A) // Valid Lifetime: Infinity (4294967295) // Preferred Lifetime: Infinity (4294967295) // Reserved // Prefix: 2401:fa00:480:f000:: // ICMPv6 Option (Recursive DNS Server 2401:fa00:480:f000::1) // Type: Recursive DNS Server (25) // Length: 3 (24 bytes) // Reserved // Lifetime: 7000 // Recursive DNS Servers: 2401:fa00:480:f000::1 // ICMPv6 Option (Advertisement Interval : 600000) // Type: Advertisement Interval (7) // Length: 1 (8 bytes) // Reserved // Advertisement Interval: 600000 final String packetStringFmt = "33330000000128C68E23672C86DD60054C6B00603AFFFE800000000000002AC68EFFFE23672CFF02000000000000000000000000000186000ACD40C01B580000000000000000010128C68E23672C05010000000005DC030440C0%s000000002401FA000480F00000000000000000001903000000001B582401FA000480F000000000000000000107010000000927C0"; final List lifetimes = List.of("FFFFFFFF", "00000000", "00000001", "00001B58"); for (String lifetime : lifetimes) { final byte[] ra = hexStringToByteArray( String.format(packetStringFmt, lifetime + lifetime)); // feed the RA into APF and generate the filter, the filter shouldn't crash. pretendPacketReceived(ra); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); } } // Test for go/apf-ra-filter Case 1a. // Old lifetime is 0 @Test public void testAcceptRaMinLftCase1a() throws Exception { // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; final ApfFilter apfFilter = getApfFilter(config); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Create an initial RA and build an APF program byte[] ra = new RaPacketBuilder(1800 /* router lifetime */) .addPioOption(1800 /*valid*/, 0 /*preferred*/, "2001:db8::/64") .build(); pretendPacketReceived(ra); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // repeated RA is dropped assertDrop(program, ra); // PIO preferred lifetime increases ra = new RaPacketBuilder(1800 /* router lifetime */) .addPioOption(1800 /*valid*/, 1 /*preferred*/, "2001:db8::/64") .build(); assertPass(program, ra); } // Test for go/apf-ra-filter Case 2a. // Old lifetime is > 0 @Test public void testAcceptRaMinLftCase2a() throws Exception { // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; final ApfFilter apfFilter = getApfFilter(config); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Create an initial RA and build an APF program byte[] ra = new RaPacketBuilder(1800 /* router lifetime */) .addPioOption(1800 /*valid*/, 100 /*preferred*/, "2001:db8::/64") .build(); pretendPacketReceived(ra); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // repeated RA is dropped assertDrop(program, ra); // PIO preferred lifetime increases ra = new RaPacketBuilder(1800 /* router lifetime */) .addPioOption(1800 /*valid*/, 101 /*preferred*/, "2001:db8::/64") .build(); assertPass(program, ra); // PIO preferred lifetime decreases significantly ra = new RaPacketBuilder(1800 /* router lifetime */) .addPioOption(1800 /*valid*/, 33 /*preferred*/, "2001:db8::/64") .build(); assertPass(program, ra); } // Test for go/apf-ra-filter Case 1b. // Old lifetime is 0 @Test public void testAcceptRaMinLftCase1b() throws Exception { // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; final ApfFilter apfFilter = getApfFilter(config); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Create an initial RA and build an APF program byte[] ra = new RaPacketBuilder(0 /* router lifetime */).build(); pretendPacketReceived(ra); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // repeated RA is dropped assertDrop(program, ra); // lifetime increases below accept_ra_min_lft ra = new RaPacketBuilder(179 /* router lifetime */).build(); assertDrop(program, ra); // lifetime increases to accept_ra_min_lft ra = new RaPacketBuilder(180 /* router lifetime */).build(); assertPass(program, ra); } // Test for go/apf-ra-filter Case 2b. // Old lifetime is < accept_ra_min_lft (but not 0). @Test public void testAcceptRaMinLftCase2b() throws Exception { // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; final ApfFilter apfFilter = getApfFilter(config); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Create an initial RA and build an APF program byte[] ra = new RaPacketBuilder(100 /* router lifetime */).build(); pretendPacketReceived(ra); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // repeated RA is dropped assertDrop(program, ra); // lifetime increases ra = new RaPacketBuilder(101 /* router lifetime */).build(); assertDrop(program, ra); // lifetime decreases significantly ra = new RaPacketBuilder(1 /* router lifetime */).build(); assertDrop(program, ra); // equals accept_ra_min_lft ra = new RaPacketBuilder(180 /* router lifetime */).build(); assertPass(program, ra); // lifetime is 0 ra = new RaPacketBuilder(0 /* router lifetime */).build(); assertPass(program, ra); } // Test for go/apf-ra-filter Case 3b. // Old lifetime is >= accept_ra_min_lft and <= 3 * accept_ra_min_lft @Test public void testAcceptRaMinLftCase3b() throws Exception { // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; final ApfFilter apfFilter = getApfFilter(config); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Create an initial RA and build an APF program byte[] ra = new RaPacketBuilder(200 /* router lifetime */).build(); pretendPacketReceived(ra); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // repeated RA is dropped assertDrop(program, ra); // lifetime increases ra = new RaPacketBuilder(201 /* router lifetime */).build(); assertPass(program, ra); // lifetime is below accept_ra_min_lft (but not 0) ra = new RaPacketBuilder(1 /* router lifetime */).build(); assertDrop(program, ra); // lifetime is 0 ra = new RaPacketBuilder(0 /* router lifetime */).build(); assertPass(program, ra); } // Test for go/apf-ra-filter Case 4b. // Old lifetime is > 3 * accept_ra_min_lft @Test public void testAcceptRaMinLftCase4b() throws Exception { // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; final ApfFilter apfFilter = getApfFilter(config); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Create an initial RA and build an APF program byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build(); pretendPacketReceived(ra); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // repeated RA is dropped assertDrop(program, ra); // lifetime increases ra = new RaPacketBuilder(1801 /* router lifetime */).build(); assertPass(program, ra); // lifetime is 1/3 of old lft ra = new RaPacketBuilder(600 /* router lifetime */).build(); assertDrop(program, ra); // lifetime is below 1/3 of old lft ra = new RaPacketBuilder(599 /* router lifetime */).build(); assertPass(program, ra); // lifetime is below accept_ra_min_lft (but not 0) ra = new RaPacketBuilder(1 /* router lifetime */).build(); assertDrop(program, ra); // lifetime is 0 ra = new RaPacketBuilder(0 /* router lifetime */).build(); assertPass(program, ra); } @Test public void testRaFilterIsUpdated() throws Exception { // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; final ApfFilter apfFilter = getApfFilter(config); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Create an initial RA and build an APF program byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build(); pretendPacketReceived(ra); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // repeated RA is dropped. assertDrop(program, ra); // updated RA is passed, repeated RA is dropped after program update. ra = new RaPacketBuilder(599 /* router lifetime */).build(); assertPass(program, ra); pretendPacketReceived(ra); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); assertDrop(program, ra); ra = new RaPacketBuilder(180 /* router lifetime */).build(); assertPass(program, ra); pretendPacketReceived(ra); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); assertDrop(program, ra); ra = new RaPacketBuilder(0 /* router lifetime */).build(); assertPass(program, ra); pretendPacketReceived(ra); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); assertDrop(program, ra); ra = new RaPacketBuilder(180 /* router lifetime */).build(); assertPass(program, ra); pretendPacketReceived(ra); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); assertDrop(program, ra); ra = new RaPacketBuilder(599 /* router lifetime */).build(); assertPass(program, ra); pretendPacketReceived(ra); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); assertDrop(program, ra); ra = new RaPacketBuilder(1800 /* router lifetime */).build(); assertPass(program, ra); pretendPacketReceived(ra); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); assertDrop(program, ra); } @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 = Inet4AddressUtils.inet4AddressToIntHTH( (Inet4Address) InetAddresses.parseNumericAddress(expected)); assertEquals(want, got); } @Test public void testInstallPacketFilterFailure() throws Exception { doReturn(false).when(mApfController).installPacketFilter(any(), any()); final ApfConfiguration config = getDefaultConfig(); final ApfFilter apfFilter = getApfFilter(config); verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); verify(mNetworkQuirkMetrics).statsWrite(); reset(mNetworkQuirkMetrics); synchronized (apfFilter) { apfFilter.installNewProgram(); } verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); verify(mNetworkQuirkMetrics).statsWrite(); } @Test public void testApfProgramOverSize() throws Exception { final ApfConfiguration config = getDefaultConfig(); config.apfVersionSupported = 2; config.apfRamSize = 256; final ApfFilter apfFilter = getApfFilter(config); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE); verify(mNetworkQuirkMetrics).statsWrite(); } @Test public void testApfSessionInfoMetrics() throws Exception { final ApfConfiguration config = getDefaultConfig(); config.apfVersionSupported = 4; config.apfRamSize = 4096; final long startTimeMs = 12345; final long durationTimeMs = config.minMetricsSessionDurationMs; doReturn(startTimeMs).when(mDependencies).elapsedRealtime(); final ApfFilter apfFilter = getApfFilter(config); byte[] program = mApfTestHelpers.consumeInstalledProgram(mApfController, 2 /* installCnt */); int maxProgramSize = 0; int numProgramUpdated = 0; maxProgramSize = Math.max(maxProgramSize, program.length); numProgramUpdated++; final byte[] data = new byte[Counter.totalSize()]; final byte[] expectedData = data.clone(); final int totalPacketsCounterIdx = Counter.totalSize() + Counter.TOTAL_PACKETS.offset(); final int passedIpv6IcmpCounterIdx = Counter.totalSize() + Counter.PASSED_IPV6_ICMP.offset(); final int droppedIpv4MulticastIdx = Counter.totalSize() + Counter.DROPPED_IPV4_MULTICAST.offset(); // Receive an RA packet (passed). final byte[] ra = buildLargeRa(); expectedData[totalPacketsCounterIdx + 3] += 1; expectedData[passedIpv6IcmpCounterIdx + 3] += 1; assertDataMemoryContentsIgnoreVersion(PASS, program, ra, data, expectedData); pretendPacketReceived(ra); program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); maxProgramSize = Math.max(maxProgramSize, program.length); numProgramUpdated++; apfFilter.setMulticastFilter(true); // setMulticastFilter will trigger program installation. program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); maxProgramSize = Math.max(maxProgramSize, program.length); numProgramUpdated++; // Receive IPv4 multicast packet (dropped). final byte[] multicastIpv4Addr = {(byte) 224, 0, 0, 1}; ByteBuffer mcastv4packet = makeIpv4Packet(IPPROTO_UDP); put(mcastv4packet, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr); expectedData[totalPacketsCounterIdx + 3] += 1; expectedData[droppedIpv4MulticastIdx + 3] += 1; assertDataMemoryContentsIgnoreVersion(DROP, program, mcastv4packet.array(), data, expectedData); // Set data snapshot and update counters. apfFilter.setDataSnapshot(data); // Write metrics data to statsd pipeline when shutdown. doReturn(startTimeMs + durationTimeMs).when(mDependencies).elapsedRealtime(); mHandler.post(apfFilter::shutdown); IoUtils.closeQuietly(mWriteSocket); HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS); verify(mApfSessionInfoMetrics).setVersion(4); verify(mApfSessionInfoMetrics).setMemorySize(4096); // Verify Counters final Map expectedCounters = Map.of(Counter.TOTAL_PACKETS, 2L, Counter.PASSED_IPV6_ICMP, 1L, Counter.DROPPED_IPV4_MULTICAST, 1L); final ArgumentCaptor counterCaptor = ArgumentCaptor.forClass(Counter.class); final ArgumentCaptor valueCaptor = ArgumentCaptor.forClass(Long.class); verify(mApfSessionInfoMetrics, times(expectedCounters.size())).addApfCounter( counterCaptor.capture(), valueCaptor.capture()); final List counters = counterCaptor.getAllValues(); final List values = valueCaptor.getAllValues(); final ArrayMap capturedCounters = new ArrayMap<>(); for (int i = 0; i < counters.size(); i++) { capturedCounters.put(counters.get(i), values.get(i)); } assertEquals(expectedCounters, capturedCounters); verify(mApfSessionInfoMetrics).setApfSessionDurationSeconds( (int) (durationTimeMs / DateUtils.SECOND_IN_MILLIS)); verify(mApfSessionInfoMetrics).setNumOfTimesApfProgramUpdated(numProgramUpdated); verify(mApfSessionInfoMetrics).setMaxProgramSize(maxProgramSize); verify(mApfSessionInfoMetrics).statsWrite(); } @Test public void testIpClientRaInfoMetrics() throws Exception { final ApfConfiguration config = getDefaultConfig(); final long startTimeMs = 12345; final long durationTimeMs = config.minMetricsSessionDurationMs; doReturn(startTimeMs).when(mDependencies).elapsedRealtime(); final ApfFilter apfFilter = getApfFilter(config); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); final int routerLifetime = 1000; final int prefixValidLifetime = 200; final int prefixPreferredLifetime = 100; final int rdnssLifetime = 300; final int routeLifetime = 400; // Construct 2 RAs with partial lifetimes larger than predefined constants final RaPacketBuilder ra1 = new RaPacketBuilder(routerLifetime); ra1.addPioOption(prefixValidLifetime + 123, prefixPreferredLifetime, "2001:db8::/64"); ra1.addRdnssOption(rdnssLifetime, "2001:4860:4860::8888", "2001:4860:4860::8844"); ra1.addRioOption(routeLifetime + 456, "64:ff9b::/96"); final RaPacketBuilder ra2 = new RaPacketBuilder(routerLifetime + 123); ra2.addPioOption(prefixValidLifetime, prefixPreferredLifetime, "2001:db9::/64"); ra2.addRdnssOption(rdnssLifetime + 456, "2001:4860:4860::8888", "2001:4860:4860::8844"); ra2.addRioOption(routeLifetime, "64:ff9b::/96"); // Construct an invalid RA packet final RaPacketBuilder raInvalid = new RaPacketBuilder(routerLifetime); raInvalid.addZeroLengthOption(); // Construct 4 different kinds of zero lifetime RAs final RaPacketBuilder raZeroRouterLifetime = new RaPacketBuilder(0 /* routerLft */); final RaPacketBuilder raZeroPioValidLifetime = new RaPacketBuilder(routerLifetime); raZeroPioValidLifetime.addPioOption(0, prefixPreferredLifetime, "2001:db10::/64"); final RaPacketBuilder raZeroRdnssLifetime = new RaPacketBuilder(routerLifetime); raZeroRdnssLifetime.addPioOption( prefixValidLifetime, prefixPreferredLifetime, "2001:db11::/64"); raZeroRdnssLifetime.addRdnssOption(0, "2001:4860:4860::8888", "2001:4860:4860::8844"); final RaPacketBuilder raZeroRioRouteLifetime = new RaPacketBuilder(routerLifetime); raZeroRioRouteLifetime.addPioOption( prefixValidLifetime, prefixPreferredLifetime, "2001:db12::/64"); raZeroRioRouteLifetime.addRioOption(0, "64:ff9b::/96"); // Inject RA packets. Calling assertProgramUpdateAndGet()/assertNoProgramUpdate() is to make // sure that the RA packet has been processed. pretendPacketReceived(ra1.build()); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); pretendPacketReceived(ra2.build()); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); pretendPacketReceived(raInvalid.build()); Thread.sleep(NO_CALLBACK_TIMEOUT_MS); verify(mApfController, never()).installPacketFilter(any(), any()); pretendPacketReceived(raZeroRouterLifetime.build()); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); pretendPacketReceived(raZeroPioValidLifetime.build()); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); pretendPacketReceived(raZeroRdnssLifetime.build()); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); pretendPacketReceived(raZeroRioRouteLifetime.build()); mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */); // Write metrics data to statsd pipeline when shutdown. doReturn(startTimeMs + durationTimeMs).when(mDependencies).elapsedRealtime(); mHandler.post(apfFilter::shutdown); IoUtils.closeQuietly(mWriteSocket); HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS); // Verify each metric fields in IpClientRaInfoMetrics. verify(mIpClientRaInfoMetrics).setMaxNumberOfDistinctRas(6); verify(mIpClientRaInfoMetrics).setNumberOfZeroLifetimeRas(4); verify(mIpClientRaInfoMetrics).setNumberOfParsingErrorRas(1); verify(mIpClientRaInfoMetrics).setLowestRouterLifetimeSeconds(routerLifetime); verify(mIpClientRaInfoMetrics).setLowestPioValidLifetimeSeconds(prefixValidLifetime); verify(mIpClientRaInfoMetrics).setLowestRioRouteLifetimeSeconds(routeLifetime); verify(mIpClientRaInfoMetrics).setLowestRdnssLifetimeSeconds(rdnssLifetime); verify(mIpClientRaInfoMetrics).statsWrite(); } @Test public void testNoMetricsWrittenForShortDuration() throws Exception { final ApfConfiguration config = getDefaultConfig(); final long startTimeMs = 12345; final long durationTimeMs = config.minMetricsSessionDurationMs; // Verify no metrics data written to statsd for duration less than durationTimeMs. doReturn(startTimeMs).when(mDependencies).elapsedRealtime(); final ApfFilter apfFilter = getApfFilter(config); doReturn(startTimeMs + durationTimeMs - 1).when(mDependencies).elapsedRealtime(); mHandler.post(apfFilter::shutdown); HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS); verify(mApfSessionInfoMetrics, never()).statsWrite(); verify(mIpClientRaInfoMetrics, never()).statsWrite(); // Verify metrics data written to statsd for duration greater than or equal to // durationTimeMs. doReturn(startTimeMs).when(mDependencies).elapsedRealtime(); final ApfFilter apfFilter2 = getApfFilter(config); doReturn(startTimeMs + durationTimeMs).when(mDependencies).elapsedRealtime(); mHandler.post(apfFilter2::shutdown); HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS); verify(mApfSessionInfoMetrics).statsWrite(); verify(mIpClientRaInfoMetrics).statsWrite(); } private int deriveApfGeneratorVersion(ApfV4GeneratorBase gen) { if (gen instanceof ApfV4Generator) { return 4; } else if (gen instanceof ApfV6Generator) { return 6; } return -1; } @Test public void testApfGeneratorPropagation() throws IllegalInstructionException { ApfV4Generator v4Gen = new ApfV4Generator(APF_VERSION_3, 1024 /* ramSize */, 1024 /* clampSize */); ApfV6Generator v6Gen = new ApfV6Generator(APF_VERSION_6, 1024 /* ramSize */, 1024 /* clampSize */); assertEquals(4, deriveApfGeneratorVersion(v4Gen)); assertEquals(6, deriveApfGeneratorVersion(v6Gen)); } @Test public void testFullApfV4ProgramGenerationIPV6() throws IllegalInstructionException { ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, 1024 /* ramSize */, 1024 /* clampSize */); gen.addLoadImmediate(R1, -4); gen.addLoadData(R0, 0); gen.addAdd(1); gen.addStoreData(R0, 0); gen.addLoad16intoR0(12); gen.addLoadImmediate(R1, -108); gen.addJumpIfR0LessThan(0x600, (short) -504); gen.addLoadImmediate(R1, -112); gen.addJumpIfR0Equals(0x88a2, (short) -504); gen.addJumpIfR0Equals(0x88a4, (short) -504); gen.addJumpIfR0Equals(0x88b8, (short) -504); gen.addJumpIfR0Equals(0x88cd, (short) -504); gen.addJumpIfR0Equals(0x88e1, (short) -504); gen.addJumpIfR0Equals(0x88e3, (short) -504); gen.addJumpIfR0NotEquals(0x806, (short) -116); gen.addLoadImmediate(R0, 14); gen.addLoadImmediate(R1, -36); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), (short) -498); gen.addLoad16intoR0(20); gen.addJumpIfR0Equals(0x1, (short) -102); gen.addLoadImmediate(R1, -40); gen.addJumpIfR0NotEquals(0x2, (short) -498); gen.addLoad32intoR0(28); gen.addLoadImmediate(R1, -116); gen.addJumpIfR0Equals(0x0, (short) -504); gen.addLoadImmediate(R0, 0); gen.addLoadImmediate(R1, -44); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), (short) -498); gen.defineLabel((short) -102); gen.addLoad32intoR0(38); gen.addLoadImmediate(R1, -64); gen.addJumpIfR0Equals(0x0, (short) -504); gen.addLoadImmediate(R1, -8); gen.addJump((short) -498); gen.defineLabel((short) -116); gen.addLoad16intoR0(12); gen.addJumpIfR0NotEquals(0x800, (short) -207); gen.addLoad8intoR0(23); gen.addJumpIfR0NotEquals(0x11, (short) -159); gen.addLoad16intoR0(20); gen.addJumpIfR0AnyBitsSet(0x1fff, (short) -159); gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE); gen.addLoad16R1IndexedIntoR0(16); gen.addJumpIfR0NotEquals(0x44, (short) -159); gen.addLoadImmediate(R0, 50); gen.addAddR1ToR0(); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("e212507c6345"), (short) -159); gen.addLoadImmediate(R1, -12); gen.addJump((short) -498); gen.defineLabel((short) -159); gen.addLoad8intoR0(30); gen.addAnd(240); gen.addLoadImmediate(R1, -84); gen.addJumpIfR0Equals(0xe0, (short) -504); gen.addLoadImmediate(R1, -76); gen.addLoad32intoR0(30); gen.addJumpIfR0Equals(0xffffffff, (short) -504); gen.addLoadImmediate(R1, -24); gen.addLoadImmediate(R0, 0); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), (short) -498); gen.addLoadImmediate(R1, -72); gen.addJump((short) -504); gen.addLoadImmediate(R1, -16); gen.addJump((short) -498); gen.defineLabel((short) -207); gen.addJumpIfR0Equals(0x86dd, (short) -231); gen.addLoadImmediate(R0, 0); gen.addLoadImmediate(R1, -48); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), (short) -498); gen.addLoadImmediate(R1, -56); gen.addJump((short) -504); gen.defineLabel((short) -231); gen.addLoad8intoR0(20); gen.addJumpIfR0Equals(0x3a, (short) -249); gen.addLoadImmediate(R1, -104); gen.addLoad8intoR0(38); gen.addJumpIfR0Equals(0xff, (short) -504); gen.addLoadImmediate(R1, -32); gen.addJump((short) -498); gen.defineLabel((short) -249); gen.addLoad8intoR0(54); gen.addLoadImmediate(R1, -88); gen.addJumpIfR0Equals(0x85, (short) -504); gen.addJumpIfR0NotEquals(0x88, (short) -283); gen.addLoadImmediate(R0, 38); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), (short) -283); gen.addLoadImmediate(R1, -92); gen.addJump((short) -504); gen.defineLabel((short) -283); gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE); gen.addJumpIfR0NotEquals(0xa6, (short) -496); gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS); gen.addJumpIfR0GreaterThan(0x254, (short) -496); gen.addLoadImmediate(R0, 0); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("e212507c6345648788fd6df086dd68"), (short) -496); gen.addLoadImmediate(R0, 18); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("00703afffe800000000000002a0079e10abc1539fe80000000000000e01250fffe7c63458600"), (short) -496); gen.addLoadImmediate(R0, 58); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("4000"), (short) -496); gen.addLoad16intoR0(60); gen.addJumpIfR0LessThan(0x254, (short) -496); gen.addLoadImmediate(R0, 62); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("0000000000000000"), (short) -496); gen.addLoadImmediate(R0, 78); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("19050000"), (short) -496); gen.addLoad32intoR0(82); gen.addJumpIfR0LessThan(0x254, (short) -496); gen.addLoadImmediate(R0, 86); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("2001486048600000000000000000646420014860486000000000000000000064"), (short) -496); gen.addLoadImmediate(R0, 118); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("030440c0"), (short) -496); gen.addLoad32intoR0(122); gen.addJumpIfR0LessThan(0x254, (short) -496); gen.addLoad32intoR0(126); gen.addJumpIfR0LessThan(0x254, (short) -496); gen.addLoadImmediate(R0, 130); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("00000000"), (short) -496); gen.addLoadImmediate(R0, 134); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("2a0079e10abc15390000000000000000"), (short) -496); gen.addLoadImmediate(R1, -60); gen.addJump((short) -504); gen.defineLabel((short) -496); gen.addLoadImmediate(R1, -28); gen.defineLabel((short) -498); gen.addLoadData(R0, 0); gen.addAdd(1); gen.addStoreData(R0, 0); gen.addJump(PASS_LABEL); gen.defineLabel((short) -504); gen.addLoadData(R0, 0); gen.addAdd(1); gen.addStoreData(R0, 0); gen.addJump(DROP_LABEL); byte[] program = gen.generate(); final String programString = toHexString(program).toLowerCase(); final String referenceProgramHexString = "6bfcb03a01b8120c6b949401e906006b907c01e288a27c01dd88a47c01d888b87c01d388cd7c01ce88e17c01c988e384004008066a0e6bdca401af000600010800060412147a1e016bd88401a300021a1c6b8c7c01a00000686bd4a4018c0006ffffffffffff1a266bc07c018900006bf874017e120c84005408000a17821f1112149c00181fffab0d2a108211446a3239a20506e212507c63456bf47401530a1e52f06bac7c014e00e06bb41a1e7e00000141ffffffff6be868a4012d0006ffffffffffff6bb874012e6bf07401237c001386dd686bd0a401100006ffffffffffff6bc87401110a147a0d3a6b980a267c010300ff6be072f90a366ba87af8858218886a26a2040fff02000000000000000000000000006ba472ddaa0e82d0a6aa0f8c00c9025468a2b60fe212507c6345648788fd6df086dd686a12a28b2600703afffe800000000000002a0079e10abc1539fe80000000000000e01250fffe7c634586006a3aa284024000123c94007d02546a3ea2700800000000000000006a4ea26704190500001a5294006002546a56a23b2020014860486000000000000000006464200148604860000000000000000000646a76a23204030440c01a7a94002b02541a7e94002402546c0082a21a04000000006c0086a204102a0079e10abc153900000000000000006bc472086be4b03a01b87206b03a01b87201"; assertEquals(referenceProgramHexString, programString); } @Test public void testFullApfV4ProgramGenerationIPV4() throws IllegalInstructionException { ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, 1024 /* ramSize */, 1024 /* clampSize */); gen.addLoadImmediate(R1, -4); gen.addLoadData(R0, 0); gen.addAdd(1); gen.addStoreData(R0, 0); gen.addLoad16intoR0(12); gen.addLoadImmediate(R1, -108); gen.addJumpIfR0LessThan(0x600, (short) -283); gen.addLoadImmediate(R1, -112); gen.addJumpIfR0Equals(0x88a2, (short) -283); gen.addJumpIfR0Equals(0x88a4, (short) -283); gen.addJumpIfR0Equals(0x88b8, (short) -283); gen.addJumpIfR0Equals(0x88cd, (short) -283); gen.addJumpIfR0Equals(0x88e1, (short) -283); gen.addJumpIfR0Equals(0x88e3, (short) -283); gen.addJumpIfR0NotEquals(0x806, (short) -109); gen.addLoadImmediate(R0, 14); gen.addLoadImmediate(R1, -36); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), (short) -277); gen.addLoad16intoR0(20); gen.addJumpIfR0Equals(0x1, (short) -94); gen.addLoadImmediate(R1, -40); gen.addJumpIfR0NotEquals(0x2, (short) -277); gen.addLoad32intoR0(28); gen.addLoadImmediate(R1, -116); gen.addJumpIfR0Equals(0x0, (short) -283); gen.addLoadImmediate(R0, 0); gen.addLoadImmediate(R1, -44); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), (short) -277); gen.defineLabel((short) -94); gen.addLoadImmediate(R0, 38); gen.addLoadImmediate(R1, -68); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("c0a801b3"), (short) -283); gen.addLoadImmediate(R1, -8); gen.addJump((short) -277); gen.defineLabel((short) -109); gen.addLoad16intoR0(12); gen.addJumpIfR0NotEquals(0x800, (short) -204); gen.addLoad8intoR0(23); gen.addJumpIfR0NotEquals(0x11, (short) -151); gen.addLoad16intoR0(20); gen.addJumpIfR0AnyBitsSet(0x1fff, (short) -151); gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE); gen.addLoad16R1IndexedIntoR0(16); gen.addJumpIfR0NotEquals(0x44, (short) -151); gen.addLoadImmediate(R0, 50); gen.addAddR1ToR0(); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("f683d58f832b"), (short) -151); gen.addLoadImmediate(R1, -12); gen.addJump((short) -277); gen.defineLabel((short) -151); gen.addLoad8intoR0(30); gen.addAnd(240); gen.addLoadImmediate(R1, -84); gen.addJumpIfR0Equals(0xe0, (short) -283); gen.addLoadImmediate(R1, -76); gen.addLoad32intoR0(30); gen.addJumpIfR0Equals(0xffffffff, (short) -283); gen.addLoadImmediate(R1, -80); gen.addJumpIfR0Equals(0xc0a801ff, (short) -283); gen.addLoadImmediate(R1, -24); gen.addLoadImmediate(R0, 0); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), (short) -277); gen.addLoadImmediate(R1, -72); gen.addJump((short) -283); gen.addLoadImmediate(R1, -16); gen.addJump((short) -277); gen.defineLabel((short) -204); gen.addJumpIfR0Equals(0x86dd, (short) -225); gen.addLoadImmediate(R0, 0); gen.addLoadImmediate(R1, -48); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), (short) -277); gen.addLoadImmediate(R1, -56); gen.addJump((short) -283); gen.defineLabel((short) -225); gen.addLoad8intoR0(20); gen.addJumpIfR0Equals(0x3a, (short) -241); gen.addLoadImmediate(R1, -104); gen.addLoad8intoR0(38); gen.addJumpIfR0Equals(0xff, (short) -283); gen.addLoadImmediate(R1, -32); gen.addJump((short) -277); gen.defineLabel((short) -241); gen.addLoad8intoR0(54); gen.addLoadImmediate(R1, -88); gen.addJumpIfR0Equals(0x85, (short) -283); gen.addJumpIfR0NotEquals(0x88, (short) -275); gen.addLoadImmediate(R0, 38); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), (short) -275); gen.addLoadImmediate(R1, -92); gen.addJump((short) -283); gen.defineLabel((short) -275); gen.addLoadImmediate(R1, -28); gen.defineLabel((short) -277); gen.addLoadData(R0, 0); gen.addAdd(1); gen.addStoreData(R0, 0); gen.addJump(PASS_LABEL); gen.defineLabel((short) -283); gen.addLoadData(R0, 0); gen.addAdd(1); gen.addStoreData(R0, 0); gen.addJump(DROP_LABEL); byte[] program = gen.generate(); final String programString = toHexString(program).toLowerCase(); final String referenceProgramHexString = "6bfcb03a01b8120c6b9494010c06006b907c010588a27c010088a47c00fb88b87c00f688cd7c00f188e17c00ec88e384003908066a0e6bdca2d40600010800060412147a18016bd882ca021a1c6b8c7ac900686bd4a2b706ffffffffffff6a266bbca2b204c0a801b36bf872a8120c84005808000a17821e1112149c00171fffab0d2a108210446a3239a20406f683d58f832b6bf4727e0a1e52f06bac7a7be06bb41a1e7e0000006effffffff6bb07e00000063c0a801ff6be868a25106ffffffffffff6bb872536bf072497c001086dd686bd0a23806ffffffffffff6bc8723a0a147a0b3a6b980a267a2eff6be072240a366ba87a23858218886a26a2040fff02000000000000000000000000006ba472086be4b03a01b87206b03a01b87201"; assertEquals(referenceProgramHexString, programString); } @Test public void testFullApfV4ProgramGenerationNatTKeepAliveV4() throws IllegalInstructionException { ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize, true); gen.addLoadImmediate(R1, -4); gen.addLoadData(R0, 0); gen.addAdd(1); gen.addStoreData(R0, 0); gen.addLoad16intoR0(12); gen.addCountAndDropIfR0LessThan(0x600, getCounterEnumFromOffset(-108)); gen.addLoadImmediate(R1, -112); gen.addJumpIfR0Equals(0x88a2, gen.mCountAndDropLabel); gen.addJumpIfR0Equals(0x88a4, gen.mCountAndDropLabel); gen.addJumpIfR0Equals(0x88b8, gen.mCountAndDropLabel); gen.addJumpIfR0Equals(0x88cd, gen.mCountAndDropLabel); gen.addJumpIfR0Equals(0x88e1, gen.mCountAndDropLabel); gen.addJumpIfR0Equals(0x88e3, gen.mCountAndDropLabel); gen.addJumpIfR0NotEquals(0x806, (short) -115); gen.addLoadImmediate(R0, 14); gen.addCountAndPassIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), getCounterEnumFromOffset(-36)); gen.addLoad16intoR0(20); gen.addJumpIfR0Equals(0x1, (short) -100); gen.addCountAndPassIfR0NotEquals(0x2, getCounterEnumFromOffset(-40)); gen.addLoad32intoR0(28); gen.addCountAndDropIfR0Equals(0x0, getCounterEnumFromOffset(-116)); gen.addLoadImmediate(R0, 0); gen.addCountAndPassIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), getCounterEnumFromOffset(-44)); gen.defineLabel((short) -100); gen.addLoadImmediate(R0, 38); gen.addCountAndDropIfBytesAtR0NotEqual(hexStringToByteArray("c0a801be"), getCounterEnumFromOffset(-68)); gen.addCountAndPass(getCounterEnumFromOffset(-8)); gen.defineLabel((short) -115); gen.addLoad16intoR0(12); gen.addJumpIfR0NotEquals(0x800, (short) -263); gen.addLoad8intoR0(23); gen.addJumpIfR0NotEquals(0x11, (short) -157); gen.addLoad16intoR0(20); gen.addJumpIfR0AnyBitsSet(0x1fff, (short) -157); gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE); gen.addLoad16R1IndexedIntoR0(16); gen.addJumpIfR0NotEquals(0x44, (short) -157); gen.addLoadImmediate(R0, 50); gen.addAddR1ToR0(); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ea42226789c0"), (short) -157); gen.addCountAndPass(getCounterEnumFromOffset(-12)); gen.defineLabel((short) -157); gen.addLoad8intoR0(30); gen.addAnd(240); gen.addCountAndDropIfR0Equals(0xe0, getCounterEnumFromOffset(-84)); gen.addLoadImmediate(R1, -76); gen.addLoad32intoR0(30); gen.addJumpIfR0Equals(0xffffffff, gen.mCountAndDropLabel); gen.addCountAndDropIfR0Equals(0xc0a801ff, getCounterEnumFromOffset(-80)); gen.addLoad8intoR0(23); gen.addJumpIfR0NotEquals(0x11, (short) -243); gen.addLoadImmediate(R0, 26); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("6b7a1f1fc0a801be"), (short) -243); gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE); gen.addAdd(8); gen.addSwap(); gen.addLoad16intoR0(16); gen.addNeg(R1); gen.addAddR1ToR0(); gen.addJumpIfR0NotEquals(0x1, (short) -243); gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE); gen.addAdd(14); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("1194ceca"), (short) -243); gen.addAdd(8); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff"), (short) -243); gen.addCountAndDrop(getCounterEnumFromOffset(-128)); gen.defineLabel((short) -243); gen.addLoadImmediate(R1, -24); gen.addLoadImmediate(R0, 0); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), gen.mCountAndPassLabel); gen.addCountAndDrop(getCounterEnumFromOffset(-72)); gen.addCountAndPass(getCounterEnumFromOffset(-16)); gen.defineLabel((short) -263); gen.addJumpIfR0Equals(0x86dd, (short) -284); gen.addLoadImmediate(R0, 0); gen.addCountAndPassIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), getCounterEnumFromOffset(-48)); gen.addCountAndDrop(getCounterEnumFromOffset(-56)); gen.defineLabel((short) -284); gen.addLoad8intoR0(20); gen.addJumpIfR0Equals(0x0, gen.mCountAndPassLabel); gen.addJumpIfR0Equals(0x3a, (short) -303); gen.addLoadImmediate(R1, -104); gen.addLoad8intoR0(38); gen.addJumpIfR0Equals(0xff, gen.mCountAndDropLabel); gen.addCountAndPass(getCounterEnumFromOffset(-32)); gen.defineLabel((short) -303); gen.addLoad8intoR0(54); gen.addLoadImmediate(R1, -88); gen.addJumpIfR0Equals(0x85, gen.mCountAndDropLabel); gen.addJumpIfR0NotEquals(0x88, (short) -337); gen.addLoadImmediate(R0, 38); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), (short) -337); gen.addCountAndDrop(getCounterEnumFromOffset(-92)); gen.defineLabel((short) -337); gen.addLoadImmediate(R1, -28); gen.addCountTrampoline(); byte[] program = gen.generate(); final String programString = toHexString(program).toLowerCase(); final String referenceProgramHexString = "6bfcb03a01b8120c6b9494014a06006b907c014388a27c013e88a47c013988b87c013488cd7c012f88e17c012a88e384003f08066a0e6bdca40110000600010800060412147a1c016bd884010400021a1c6b8c7c01010000686bd4a2ef06ffffffffffff6a266bbca2ea04c0a801be6bf872e0120c84008d08000a17821e1112149c00171fffab0d2a108210446a3239a20406ea42226789c06bf472b60a1e52f06bac7ab3e06bb41a1e7e000000a6ffffffff6bb07e0000009bc0a801ff0a178230116a1aa223086b7a1f1fc0a801beaa0d3a08aa221210ab2139821501aa0d3a0ea20a041194ceca3a08a20401ff6b8072666be868a25406ffffffffffff6bb872566bf0724c7c001086dd686bd0a23b06ffffffffffff6bc8723d0a147a32007a0b3a6b980a267a2eff6be072240a366ba87a23858218886a26a2040fff02000000000000000000000000006ba472086be4b03a01b87206b03a01b87201"; assertEquals(referenceProgramHexString, programString); } @Test public void testInfiniteLifetimeFullApfV4ProgramGeneration() throws IllegalInstructionException { ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, 1024 /* ramSize */, 1024 /* clampSize */, true); gen.addLoadCounter(R0, getCounterEnumFromOffset(-8)); gen.addAdd(1); gen.addStoreData(R0, 0); gen.addLoad16intoR0(12); gen.addCountAndDropIfR0LessThan(0x600, getCounterEnumFromOffset(-120)); gen.addLoadImmediate(R1, -124); gen.addJumpIfR0Equals(0x88a2, gen.mCountAndDropLabel); gen.addJumpIfR0Equals(0x88a4, gen.mCountAndDropLabel); gen.addJumpIfR0Equals(0x88b8, gen.mCountAndDropLabel); gen.addJumpIfR0Equals(0x88cd, gen.mCountAndDropLabel); gen.addJumpIfR0Equals(0x88e1, gen.mCountAndDropLabel); gen.addJumpIfR0Equals(0x88e3, gen.mCountAndDropLabel); gen.addJumpIfR0NotEquals(0x806, (short) -122); gen.addLoadImmediate(R0, 14); gen.addCountAndDropIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), getCounterEnumFromOffset(-152)); gen.addLoad16intoR0(20); gen.addJumpIfR0Equals(0x1, (short) -104); gen.addCountAndDropIfR0NotEquals(0x2, getCounterEnumFromOffset(-156)); gen.addLoad32intoR0(28); gen.addCountAndDropIfR0Equals(0x0, getCounterEnumFromOffset(-128)); gen.addLoadImmediate(R0, 0); gen.addCountAndPassIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), getCounterEnumFromOffset(-56)); gen.defineLabel((short) -104); gen.addLoadImmediate(R0, 38); gen.addCountAndDropIfBytesAtR0NotEqual(hexStringToByteArray("c0a801ec"), getCounterEnumFromOffset(-80)); gen.addCountAndPass(getCounterEnumFromOffset(-20)); gen.defineLabel((short) -122); gen.addLoad16intoR0(12); gen.addJumpIfR0NotEquals(0x800, (short) -249); gen.addLoad8intoR0(23); gen.addJumpIfR0NotEquals(0x11, (short) -165); gen.addLoad16intoR0(20); gen.addJumpIfR0AnyBitsSet(0x1fff, (short) -165); gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE); gen.addLoad16R1IndexedIntoR0(16); gen.addJumpIfR0NotEquals(0x44, (short) -165); gen.addLoadImmediate(R0, 50); gen.addAddR1ToR0(); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("7e9046bc7008"), (short) -165); gen.addCountAndPass(getCounterEnumFromOffset(-24)); gen.defineLabel((short) -165); gen.addLoad8intoR0(30); gen.addAnd(240); gen.addCountAndDropIfR0Equals(0xe0, getCounterEnumFromOffset(-96)); gen.addLoadImmediate(R1, -88); gen.addLoad32intoR0(30); gen.addJumpIfR0Equals(0xffffffff, gen.mCountAndDropLabel); gen.addCountAndDropIfR0Equals(0xc0a801ff, getCounterEnumFromOffset(-92)); gen.addLoad8intoR0(23); gen.addJumpIfR0NotEquals(0x6, (short) -225); gen.addLoad16intoR0(20); gen.addJumpIfR0AnyBitsSet(0x1fff, (short) -225); gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE); gen.addLoad16R1IndexedIntoR0(16); gen.addJumpIfR0NotEquals(0x7, (short) -225); gen.addCountAndDrop(getCounterEnumFromOffset(-148)); gen.defineLabel((short) -225); gen.addLoadImmediate(R1, -36); gen.addLoadImmediate(R0, 0); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), gen.mCountAndPassLabel); gen.addCountAndDrop(getCounterEnumFromOffset(-84)); gen.addCountAndPass(getCounterEnumFromOffset(-28)); gen.defineLabel((short) -249); gen.addJumpIfR0Equals(0x86dd, (short) -273); gen.addLoadImmediate(R0, 0); gen.addCountAndPassIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), getCounterEnumFromOffset(-60)); gen.addCountAndDrop(getCounterEnumFromOffset(-68)); gen.defineLabel((short) -273); gen.addLoad8intoR0(20); gen.addJumpIfR0Equals(0x0, gen.mCountAndPassLabel); gen.addJumpIfR0Equals(0x3a, (short) -297); gen.addLoadImmediate(R1, -116); gen.addLoad8intoR0(38); gen.addJumpIfR0Equals(0xff, gen.mCountAndDropLabel); gen.addCountAndPass(getCounterEnumFromOffset(-44)); gen.defineLabel((short) -297); gen.addLoad8intoR0(54); gen.addCountAndDropIfR0Equals(0x85, getCounterEnumFromOffset(-100)); gen.addJumpIfR0NotEquals(0x88, (short) -333); gen.addLoadImmediate(R0, 38); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), (short) -333); gen.addCountAndDrop(getCounterEnumFromOffset(-104)); gen.defineLabel((short) -333); gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE); gen.addJumpIfR0NotEquals(0x96, (short) -574); gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS); gen.addJumpIfR0GreaterThan(0x48e, (short) -574); gen.addLoadImmediate(R0, 0); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("7e9046bc700828c68e23672c86dd60"), (short) -574); gen.addLoadImmediate(R0, 18); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("00603afffe800000000000002ac68efffe23672c"), (short) -574); gen.addLoadImmediate(R0, 54); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("8600"), (short) -574); gen.addLoadImmediate(R0, 58); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("40c0"), (short) -574); gen.addLoad16intoR0(60); gen.addJumpIfR0Equals(0x0, (short) -574); gen.addJumpIfR0LessThan(0xb4, (short) -421); gen.addJumpIfR0LessThan(0x91e, (short) -574); gen.addJumpIfR0GreaterThan(0x1b58, (short) -574); gen.defineLabel((short) -421); gen.addLoadImmediate(R0, 62); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("0000000000000000010128c68e23672c05010000000005dc030440c0"), (short) -574); gen.addLoad32intoR0(90); gen.addJumpIfR0Equals(0x0, (short) -574); gen.addJumpIfR0LessThan(0xb4, (short) -480); gen.addJumpIfR0LessThan(0x55555555, (short) -574); gen.addJumpIfR0GreaterThan(0xffffffffL, (short) -574); gen.defineLabel((short) -480); gen.addLoad32intoR0(94); gen.addJumpIfR0LessThan(0x55555555, (short) -574); gen.addJumpIfR0GreaterThan(0xffffffffL, (short) -574); gen.addLoadImmediate(R0, 98); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000000002401fa000480f000000000000000000019030000"), (short) -574); gen.addLoad32intoR0(122); gen.addJumpIfR0Equals(0x0, (short) -574); gen.addJumpIfR0LessThan(0x78, (short) -547); gen.addJumpIfR0LessThan(0x91e, (short) -574); gen.addJumpIfR0GreaterThan(0x1b58, (short) -574); gen.defineLabel((short) -547); gen.addLoadImmediate(R0, 126); gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("2401fa000480f00000000000000000010701"), (short) -574); gen.addCountAndDrop(getCounterEnumFromOffset(-72)); gen.defineLabel((short) -574); gen.addLoadImmediate(R1, -40); gen.addCountTrampoline(); byte[] program = gen.generate(); final String programString = toHexString(program).toLowerCase(); final String referenceProgramHexString = "6bf8b03a01b8120c6b8894023706006b847c023088a27c022b88a47c022688b87c022188cd7c021c88e17c021788e384004608066a0e6dff68a40202000600010800060412147a1f016dff648401f500021a1c6b807c01ec0000686bc8a401d80006ffffffffffff6a266bb0a401d10004c0a801ec6bec7401c6120c84007808000a17821f1112149c00181fffab0d2a108211446a3239a205067e9046bc70086be874019b0a1e52f06ba07c019600e06ba81a1e7e00000189ffffffff6ba47e0000017ec0a801ff0a1782140612149c000d1fffab0d2a108206076dff6c7401656bdc68a401510006ffffffffffff6bac7401526be47401477c001386dd686bc4a401340006ffffffffffff6bbc7401350a147c012800007a0e3a6b8c0a267c012200ff6bd47401170a366b9c7c011400858218886a26a2040fff02000000000000000000000000006b9872f9aa0e82ec96aa0f8c00e5048e68a2d20f7e9046bc700828c68e23672c86dd606a12a2b91400603afffe800000000000002ac68efffe23672c6a36a2b20286006a3aa2ab0240c0123c7aa600920ab494009e091e8c00991b586a3ea2781c0000000000000000010128c68e23672c05010000000005dc030440c01a5a7a73009212b49600000067555555558e0000005effffffff1a5e9600000053555555558e0000004affffffff6a62a22d18000000002401fa000480f0000000000000000000190300001a7a7a2800920a78940020091e8c001b1b586a7ea204122401fa000480f000000000000000000107016bb872086bd8b03a01b87206b03a01b87201"; assertEquals(referenceProgramHexString, programString); } }