| /* |
| * 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.BaseApfGenerator.APF_VERSION_4; |
| 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.net.apf.ApfJniUtils.compareBpfApf; |
| import static android.net.apf.ApfJniUtils.compileToBpf; |
| import static android.net.apf.ApfJniUtils.dropsAllPackets; |
| import static android.net.apf.ApfTestUtils.DROP; |
| import static android.net.apf.ApfTestUtils.MIN_PKT_SIZE; |
| import static android.net.apf.ApfTestUtils.PASS; |
| import static android.net.apf.ApfTestUtils.assertProgramEquals; |
| 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.ARPHRD_ETHER; |
| import static android.system.OsConstants.ETH_P_ARP; |
| import static android.system.OsConstants.ETH_P_IP; |
| import static android.system.OsConstants.ETH_P_IPV6; |
| import static android.system.OsConstants.IPPROTO_ICMPV6; |
| import static android.system.OsConstants.IPPROTO_IPV6; |
| import static android.system.OsConstants.IPPROTO_TCP; |
| import static android.system.OsConstants.IPPROTO_UDP; |
| |
| 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.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.mockito.ArgumentMatchers.anyBoolean; |
| import static org.mockito.Mockito.any; |
| import static org.mockito.Mockito.doAnswer; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.mock; |
| 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.NattKeepalivePacketDataParcelable; |
| import android.net.TcpKeepalivePacketDataParcelable; |
| import android.net.apf.ApfCounterTracker.Counter; |
| import android.net.apf.ApfFilter.ApfConfiguration; |
| import android.net.apf.ApfTestUtils.MockIpClientCallback; |
| import android.net.apf.ApfTestUtils.TestApfFilter; |
| import android.net.apf.ApfTestUtils.TestLegacyApfFilter; |
| import android.net.apf.BaseApfGenerator.IllegalInstructionException; |
| import android.net.metrics.IpConnectivityLog; |
| import android.os.Build; |
| import android.os.PowerManager; |
| import android.stats.connectivity.NetworkQuirkEvent; |
| import android.system.ErrnoException; |
| 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.net.module.util.DnsPacket; |
| import com.android.net.module.util.Inet4AddressUtils; |
| 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 libcore.io.Streams; |
| |
| import org.junit.After; |
| import org.junit.Assert; |
| 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.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| 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; |
| |
| /** |
| * 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; |
| |
| @Rule |
| public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule(); |
| // Indicates which apf interpreter to run. |
| @Parameterized.Parameter() |
| public int mApfVersion; |
| |
| @Parameterized.Parameters |
| public static Iterable<? extends Object> 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.Clock mClock; |
| @GuardedBy("mApfFilterCreated") |
| private final ArrayList<AndroidPacketFilter> mApfFilterCreated = new ArrayList<>(); |
| @GuardedBy("mThreadsToBeCleared") |
| private final ArrayList<Thread> mThreadsToBeCleared = new ArrayList<>(); |
| |
| @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(); |
| doAnswer((invocation) -> { |
| synchronized (mApfFilterCreated) { |
| mApfFilterCreated.add(invocation.getArgument(0)); |
| } |
| return null; |
| }).when(mDependencies).onApfFilterCreated(any()); |
| doAnswer((invocation) -> { |
| synchronized (mThreadsToBeCleared) { |
| mThreadsToBeCleared.add(invocation.getArgument(0)); |
| } |
| return null; |
| }).when(mDependencies).onThreadCreated(any()); |
| } |
| |
| private void quitThreads() throws Exception { |
| ConcurrentUtils.quitThreads( |
| THREAD_QUIT_MAX_RETRY_COUNT, |
| false /* interrupt */, |
| HANDLER_TIMEOUT_MS, |
| () -> { |
| synchronized (mThreadsToBeCleared) { |
| final ArrayList<Thread> ret = new ArrayList<>(mThreadsToBeCleared); |
| mThreadsToBeCleared.clear(); |
| return ret; |
| } |
| }); |
| } |
| |
| private void shutdownApfFilters() throws Exception { |
| ConcurrentUtils.quitResources(THREAD_QUIT_MAX_RETRY_COUNT, () -> { |
| synchronized (mApfFilterCreated) { |
| final ArrayList<AndroidPacketFilter> ret = |
| new ArrayList<>(mApfFilterCreated); |
| mApfFilterCreated.clear(); |
| return ret; |
| } |
| }, (apf) -> { |
| apf.shutdown(); |
| }); |
| synchronized (mApfFilterCreated) { |
| assertEquals("ApfFilters did not fully shutdown.", |
| 0, mApfFilterCreated.size()); |
| } |
| // It's necessary to wait until all ReceiveThreads have finished running because |
| // clearInlineMocks clears all Mock objects, including some privilege frameworks |
| // required by logStats, at the end of ReceiveThread#run. |
| quitThreads(); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| shutdownApfFilters(); |
| // Clear mocks to prevent from stubs holding instances and cause memory leaks. |
| Mockito.framework().clearInlineMocks(); |
| } |
| |
| private static final String TAG = "ApfTest"; |
| // Expected return codes from APF interpreter. |
| private static final ApfCapabilities MOCK_APF_CAPABILITIES = |
| new ApfCapabilities(2, 4096, ARPHRD_ETHER); |
| |
| private static final boolean DROP_MULTICAST = true; |
| private static final boolean ALLOW_MULTICAST = false; |
| |
| private static final boolean DROP_802_3_FRAMES = true; |
| private static final boolean ALLOW_802_3_FRAMES = false; |
| |
| private static final int MIN_RDNSS_LIFETIME_SEC = 0; |
| private static final int MIN_METRICS_SESSION_DURATIONS_MS = 300_000; |
| |
| private static final int HANDLER_TIMEOUT_MS = 1000; |
| 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 ApfConfiguration getDefaultConfig() { |
| ApfFilter.ApfConfiguration config = new ApfConfiguration(); |
| config.apfCapabilities = MOCK_APF_CAPABILITIES; |
| config.multicastFilter = ALLOW_MULTICAST; |
| config.ieee802_3Filter = ALLOW_802_3_FRAMES; |
| config.ethTypeBlackList = new int[0]; |
| 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 { |
| ApfTestUtils.assertPass(mApfVersion, gen); |
| } |
| |
| private void assertDrop(ApfV4Generator gen) throws ApfV4Generator.IllegalInstructionException { |
| ApfTestUtils.assertDrop(mApfVersion, gen); |
| } |
| |
| private void assertPass(byte[] program, byte[] packet) { |
| ApfTestUtils.assertPass(mApfVersion, program, packet); |
| } |
| |
| private void assertDrop(byte[] program, byte[] packet) { |
| ApfTestUtils.assertDrop(mApfVersion, program, packet); |
| } |
| |
| private void assertPass(byte[] program, byte[] packet, int filterAge) { |
| ApfTestUtils.assertPass(mApfVersion, program, packet, filterAge); |
| } |
| |
| private void assertDrop(byte[] program, byte[] packet, int filterAge) { |
| ApfTestUtils.assertDrop(mApfVersion, program, packet, filterAge); |
| } |
| |
| private void assertPass(ApfV4Generator gen, byte[] packet, int filterAge) |
| throws ApfV4Generator.IllegalInstructionException { |
| ApfTestUtils.assertPass(mApfVersion, gen, packet, filterAge); |
| } |
| |
| private void assertDrop(ApfV4Generator gen, byte[] packet, int filterAge) |
| throws ApfV4Generator.IllegalInstructionException { |
| ApfTestUtils.assertDrop(mApfVersion, gen, packet, filterAge); |
| } |
| |
| private void assertDataMemoryContents(int expected, byte[] program, byte[] packet, |
| byte[] data, byte[] expectedData) throws Exception { |
| ApfTestUtils.assertDataMemoryContents(mApfVersion, expected, program, packet, data, |
| expectedData, false /* ignoreInterpreterVersion */); |
| } |
| |
| private void assertDataMemoryContentsIgnoreVersion(int expected, byte[] program, |
| byte[] packet, byte[] data, byte[] expectedData) throws Exception { |
| ApfTestUtils.assertDataMemoryContents(mApfVersion, expected, program, packet, data, |
| expectedData, true /* ignoreInterpreterVersion */); |
| } |
| |
| private void assertVerdict(String msg, int expected, byte[] program, |
| byte[] packet, int filterAge) { |
| ApfTestUtils.assertVerdict(mApfVersion, msg, expected, program, packet, filterAge); |
| } |
| |
| private void assertVerdict(int expected, byte[] program, byte[] packet) { |
| ApfTestUtils.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); |
| assertPass(gen); |
| |
| // Test pass opcode |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addPass(); |
| gen.addJump(DROP_LABEL); |
| assertPass(gen); |
| |
| // Test jumping to pass label. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| 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); |
| 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); |
| gen.addJumpIfR0Equals(0, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test jumping if not equal to 0. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addJumpIfR0NotEquals(0, DROP_LABEL); |
| assertPass(gen); |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R0, 1); |
| gen.addJumpIfR0NotEquals(0, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test jumping if registers equal. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addJumpIfR0EqualsR1(DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test jumping if registers not equal. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addJumpIfR0NotEqualsR1(DROP_LABEL); |
| assertPass(gen); |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R0, 1); |
| gen.addJumpIfR0NotEqualsR1(DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test load immediate. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R0, 1234567890); |
| gen.addJumpIfR0Equals(1234567890, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test add. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addAdd(1234567890); |
| gen.addJumpIfR0Equals(1234567890, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test add with a small signed negative value. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addAdd(-1); |
| gen.addJumpIfR0Equals(-1, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test subtract. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addAdd(-1234567890); |
| gen.addJumpIfR0Equals(-1234567890, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test or. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addOr(1234567890); |
| gen.addJumpIfR0Equals(1234567890, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test and. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R0, 1234567890); |
| gen.addAnd(123456789); |
| gen.addJumpIfR0Equals(1234567890 & 123456789, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test left shift. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R0, 1234567890); |
| gen.addLeftShift(1); |
| gen.addJumpIfR0Equals(1234567890 << 1, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test right shift. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R0, 1234567890); |
| gen.addRightShift(1); |
| gen.addJumpIfR0Equals(1234567890 >> 1, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test multiply. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R0, 123456789); |
| gen.addMul(2); |
| gen.addJumpIfR0Equals(123456789 * 2, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test divide. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| 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); |
| gen.addDiv(0); |
| gen.addJump(DROP_LABEL); |
| assertPass(gen); |
| |
| // Test add. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R1, 1234567890); |
| gen.addAddR1ToR0(); |
| gen.addJumpIfR0Equals(1234567890, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test subtract. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R1, -1234567890); |
| gen.addAddR1ToR0(); |
| gen.addJumpIfR0Equals(-1234567890, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test or. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R1, 1234567890); |
| gen.addOrR0WithR1(); |
| gen.addJumpIfR0Equals(1234567890, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test and. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| 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); |
| 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); |
| 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); |
| 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); |
| 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); |
| gen.addDivR0ByR1(); |
| gen.addJump(DROP_LABEL); |
| assertPass(gen); |
| |
| // Test byte load. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoad8(R0, 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); |
| gen.addLoad8(R0, 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); |
| gen.addLoad16(R0, 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); |
| gen.addLoad32(R0, 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); |
| gen.addLoadImmediate(R1, 1); |
| gen.addLoad8Indexed(R0, 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); |
| gen.addLoadImmediate(R1, 8); |
| gen.addLoad8Indexed(R0, 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); |
| gen.addLoadImmediate(R1, 1); |
| gen.addLoad16Indexed(R0, 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); |
| gen.addLoadImmediate(R1, 1); |
| gen.addLoad32Indexed(R0, 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); |
| gen.addJumpIfR0GreaterThan(0, DROP_LABEL); |
| assertPass(gen); |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R0, 1); |
| gen.addJumpIfR0GreaterThan(0, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test jumping if less than. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addJumpIfR0LessThan(0, DROP_LABEL); |
| assertPass(gen); |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addJumpIfR0LessThan(1, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test jumping if any bits set. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addJumpIfR0AnyBitsSet(3, DROP_LABEL); |
| assertPass(gen); |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R0, 1); |
| gen.addJumpIfR0AnyBitsSet(3, DROP_LABEL); |
| assertDrop(gen); |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R0, 3); |
| gen.addJumpIfR0AnyBitsSet(3, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test jumping if register greater than. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addJumpIfR0GreaterThanR1(DROP_LABEL); |
| assertPass(gen); |
| gen = new ApfV4Generator(APF_VERSION_2); |
| 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); |
| gen.addJumpIfR0LessThanR1(DROP_LABEL); |
| assertPass(gen); |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R1, 1); |
| gen.addJumpIfR0LessThanR1(DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test jumping if any bits set in register. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R1, 3); |
| gen.addJumpIfR0AnyBitsSetR1(DROP_LABEL); |
| assertPass(gen); |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R1, 3); |
| gen.addLoadImmediate(R0, 1); |
| gen.addJumpIfR0AnyBitsSetR1(DROP_LABEL); |
| assertDrop(gen); |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R1, 3); |
| gen.addLoadImmediate(R0, 3); |
| gen.addJumpIfR0AnyBitsSetR1(DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test load from memory. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadFromMemory(R0, MemorySlot.SLOT_0); |
| gen.addJumpIfR0Equals(0, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test store to memory. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| 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); |
| 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); |
| 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); |
| 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); |
| gen.addLoadImmediate(R0, 1234567890); |
| gen.addNot(R0); |
| gen.addJumpIfR0Equals(~1234567890, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test negate. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R0, 1234567890); |
| gen.addNeg(R0); |
| gen.addJumpIfR0Equals(-1234567890, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test move. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R1, 1234567890); |
| gen.addMove(R0); |
| gen.addJumpIfR0Equals(1234567890, DROP_LABEL); |
| assertDrop(gen); |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R0, 1234567890); |
| gen.addMove(R1); |
| gen.addJumpIfR0Equals(1234567890, DROP_LABEL); |
| assertDrop(gen); |
| |
| // Test swap. |
| gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R1, 1234567890); |
| gen.addSwap(); |
| gen.addJumpIfR0Equals(1234567890, DROP_LABEL); |
| assertDrop(gen); |
| gen = new ApfV4Generator(APF_VERSION_2); |
| 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); |
| 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); |
| 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); |
| gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, DROP_LABEL); |
| assertDrop(gen, packet123, 0); |
| gen = new ApfV4Generator(APF_VERSION_2); |
| 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); |
| 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); |
| } |
| |
| @Test |
| public void testApfDataOpcodesWantApfV3() throws IllegalInstructionException, Exception { |
| ApfV4Generator gen = new ApfV4Generator(APF_VERSION_2); |
| 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(4); |
| gen.addLoadImmediate(R0, 0); |
| assertProgramEquals(new byte[]{LI_OP | SIZE0}, gen.generate()); |
| |
| // 1-byte immediate: li R0, 42 |
| gen = new ApfV4Generator(4); |
| gen.addLoadImmediate(R0, 42); |
| assertProgramEquals(new byte[]{LI_OP | SIZE8, 42}, gen.generate()); |
| |
| // 2-byte immediate: li R1, 0x1234 |
| gen = new ApfV4Generator(4); |
| 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(3); |
| 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(3); |
| gen.addLoadImmediate(R0, -42); |
| assertProgramEquals(new byte[]{LI_OP | SIZE8, -42}, gen.generate()); |
| |
| // 2-byte negative immediate: li R1, -0x1122 |
| gen = new ApfV4Generator(3); |
| 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(3); |
| 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_4); |
| 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_4); |
| 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_4); |
| 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_4); |
| 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_4); |
| 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_4); |
| 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_4); |
| 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_4); |
| 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_4); |
| 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_4); |
| 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_4); |
| 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_4); |
| 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_4); |
| gen.addLoadImmediate(R0, 20); |
| gen.addLoadData(R1, -1000); |
| gen.addJump(DROP_LABEL); // Not reached. |
| assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data); |
| } |
| |
| /** |
| * Generate some BPF programs, translate them to APF, then run APF and BPF programs |
| * over packet traces and verify both programs filter out the same packets. |
| */ |
| @Test |
| public void testApfAgainstBpf() throws Exception { |
| String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53", |
| "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24", |
| "arp or icmp6 or portrange 53-54", "portrange 53-54 or portrange 100-50000", |
| "tcp[tcpflags] & (tcp-ack|tcp-fin) != 0 and (ip[2:2] > 57 or icmp)" }; |
| String pcap_filename = stageFile(R.raw.apf); |
| for (String tcpdump_filter : tcpdump_filters) { |
| byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter)); |
| assertTrue("Failed to match for filter: " + tcpdump_filter, |
| compareBpfApf(mApfVersion, tcpdump_filter, pcap_filename, apf_program)); |
| } |
| } |
| |
| /** |
| * Generate APF program, run pcap file though APF filter, then check all the packets in the file |
| * should be dropped. |
| */ |
| @Test |
| public void testApfFilterPcapFile() throws Exception { |
| final byte[] MOCK_PCAP_IPV4_ADDR = {(byte) 172, 16, 7, (byte) 151}; |
| String pcapFilename = stageFile(R.raw.apfPcap); |
| MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_PCAP_IPV4_ADDR), 16); |
| LinkProperties lp = new LinkProperties(); |
| lp.addLinkAddress(link); |
| |
| ApfConfiguration config = getDefaultConfig(); |
| ApfCapabilities MOCK_APF_PCAP_CAPABILITIES = new ApfCapabilities(4, 1700, ARPHRD_ETHER); |
| config.apfCapabilities = MOCK_APF_PCAP_CAPABILITIES; |
| config.multicastFilter = DROP_MULTICAST; |
| config.ieee802_3Filter = DROP_802_3_FRAMES; |
| TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| apfFilter.setLinkProperties(lp); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| byte[] data = new byte[Counter.totalSize()]; |
| final boolean result; |
| |
| result = 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_TOTAL_LENGTH_OFFSET = IP_HEADER_OFFSET + 2; |
| private static final int IPV4_PROTOCOL_OFFSET = IP_HEADER_OFFSET + 9; |
| private static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12; |
| private static final int IPV4_DEST_ADDR_OFFSET = IP_HEADER_OFFSET + 16; |
| |
| private static final int IPV4_TCP_HEADER_LEN = 20; |
| private static final int IPV4_TCP_HEADER_OFFSET = IP_HEADER_OFFSET + IPV4_HEADER_LEN; |
| private static final int IPV4_TCP_SRC_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 0; |
| private static final int IPV4_TCP_DEST_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 2; |
| private static final int IPV4_TCP_SEQ_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 4; |
| private static final int IPV4_TCP_ACK_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 8; |
| private static final int IPV4_TCP_HEADER_LENGTH_OFFSET = IPV4_TCP_HEADER_OFFSET + 12; |
| private static final int IPV4_TCP_HEADER_FLAG_OFFSET = IPV4_TCP_HEADER_OFFSET + 13; |
| |
| private static final int IPV4_UDP_HEADER_OFFSET = IP_HEADER_OFFSET + IPV4_HEADER_LEN; |
| private static final int IPV4_UDP_SRC_PORT_OFFSET = IPV4_UDP_HEADER_OFFSET + 0; |
| private static final int IPV4_UDP_DEST_PORT_OFFSET = IPV4_UDP_HEADER_OFFSET + 2; |
| private static final int IPV4_UDP_LENGTH_OFFSET = IPV4_UDP_HEADER_OFFSET + 4; |
| private static final int IPV4_UDP_PAYLOAD_OFFSET = IPV4_UDP_HEADER_OFFSET + 8; |
| 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; |
| private static final int IPV6_TCP_SRC_PORT_OFFSET = IPV6_PAYLOAD_OFFSET + 0; |
| private static final int IPV6_TCP_DEST_PORT_OFFSET = IPV6_PAYLOAD_OFFSET + 2; |
| private static final int IPV6_TCP_SEQ_NUM_OFFSET = IPV6_PAYLOAD_OFFSET + 4; |
| private static final int IPV6_TCP_ACK_NUM_OFFSET = IPV6_PAYLOAD_OFFSET + 8; |
| // 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_SOLICITATION = 135; |
| private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136; |
| |
| private static final int ICMP6_RA_HEADER_LEN = 16; |
| private static final int ICMP6_RA_CHECKSUM_OFFSET = |
| IP_HEADER_OFFSET + IPV6_HEADER_LEN + 2; |
| private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET = |
| IP_HEADER_OFFSET + IPV6_HEADER_LEN + 6; |
| private static final int ICMP6_RA_REACHABLE_TIME_OFFSET = |
| IP_HEADER_OFFSET + IPV6_HEADER_LEN + 8; |
| private static final int ICMP6_RA_RETRANSMISSION_TIMER_OFFSET = |
| IP_HEADER_OFFSET + IPV6_HEADER_LEN + 12; |
| private static final int ICMP6_RA_OPTION_OFFSET = |
| IP_HEADER_OFFSET + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN; |
| |
| private static final int ICMP6_PREFIX_OPTION_TYPE = 3; |
| private static final int ICMP6_PREFIX_OPTION_LEN = 32; |
| private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4; |
| private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8; |
| |
| // From RFC6106: Recursive DNS Server option |
| private static final int ICMP6_RDNSS_OPTION_TYPE = 25; |
| // From RFC6106: DNS Search List option |
| private static final int ICMP6_DNSSL_OPTION_TYPE = 31; |
| |
| // From RFC4191: Route Information option |
| private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24; |
| // Above three options all have the same format: |
| private static final int ICMP6_4_BYTE_OPTION_LEN = 8; |
| private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4; |
| private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4; |
| |
| private static final int UDP_HEADER_LEN = 8; |
| private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 22; |
| |
| private static final int DHCP_CLIENT_PORT = 68; |
| private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48; |
| |
| private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN; |
| private static final byte[] ARP_IPV4_REQUEST_HEADER = { |
| 0, 1, // Hardware type: Ethernet (1) |
| 8, 0, // Protocol type: IP (0x0800) |
| 6, // Hardware size: 6 |
| 4, // Protocol size: 4 |
| 0, 1 // Opcode: request (1) |
| }; |
| private static final byte[] ARP_IPV4_REPLY_HEADER = { |
| 0, 1, // Hardware type: Ethernet (1) |
| 8, 0, // Protocol type: IP (0x0800) |
| 6, // Hardware size: 6 |
| 4, // Protocol size: 4 |
| 0, 2 // Opcode: reply (2) |
| }; |
| private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14; |
| private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24; |
| |
| private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1}; |
| private static final byte[] MOCK_BROADCAST_IPV4_ADDR = {10, 0, 31, (byte) 255}; // prefix = 19 |
| private static final byte[] MOCK_MULTICAST_IPV4_ADDR = {(byte) 224, 0, 0, 1}; |
| private static final byte[] ANOTHER_IPV4_ADDR = {10, 0, 0, 2}; |
| private static final byte[] IPV4_SOURCE_ADDR = {10, 0, 0, 3}; |
| private static final byte[] ANOTHER_IPV4_SOURCE_ADDR = {(byte) 192, 0, 2, 1}; |
| private static final byte[] BUG_PROBE_SOURCE_ADDR1 = {0, 0, 1, 2}; |
| private static final byte[] BUG_PROBE_SOURCE_ADDR2 = {3, 4, 0, 0}; |
| private static final byte[] IPV4_ANY_HOST_ADDR = {0, 0, 0, 0}; |
| 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 IPV6_UDP_DEST_PORT_OFFSET = IPV6_PAYLOAD_OFFSET + 2; |
| 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 { |
| MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19); |
| LinkProperties lp = new LinkProperties(); |
| lp.addLinkAddress(link); |
| |
| ApfConfiguration config = getDefaultConfig(); |
| config.multicastFilter = DROP_MULTICAST; |
| TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| apfFilter.setLinkProperties(lp); |
| |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // Verify empty packet of 100 zero bytes is passed |
| ByteBuffer packet = ByteBuffer.wrap(new byte[100]); |
| assertPass(program, packet.array()); |
| |
| // Verify unicast IPv4 packet is passed |
| put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR); |
| packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP); |
| put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_IPV4_ADDR); |
| assertPass(program, packet.array()); |
| |
| // Verify L2 unicast to IPv4 broadcast addresses is dropped (b/30231088) |
| put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS); |
| assertDrop(program, packet.array()); |
| put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR); |
| assertDrop(program, packet.array()); |
| |
| // Verify multicast/broadcast IPv4, not DHCP to us, is dropped |
| put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS); |
| assertDrop(program, packet.array()); |
| packet.put(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, TestApfFilter.MOCK_MAC_ADDR); |
| assertPass(program, packet.array()); |
| |
| // Verify unicast IPv4 DHCP to us is passed |
| put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR); |
| assertPass(program, packet.array()); |
| } |
| |
| @Test |
| public void testApfFilterIPv6() throws Exception { |
| MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| ApfConfiguration config = getDefaultConfig(); |
| TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // 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<Pair<Integer, String>> getDnsLabels(int startOffset, String... names) { |
| // Maps all possible name suffixes to packet offsets. |
| final HashMap<String, Integer> mPointerOffsets = new HashMap<>(); |
| final List<Pair<Integer, String>> 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<Integer, String> label(String label) { |
| return Pair.create(label.length(), label); |
| } |
| |
| static Pair<Integer, String> pointer(int offset) { |
| return Pair.create(0xc000 | offset, null); |
| } |
| |
| @Test |
| public void testGetDnsLabels() throws Exception { |
| int startOffset = 12; |
| List<Pair<Integer, String>> 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<Pair<Integer, String>> labels = getDnsLabels(questions.position(), names); |
| for (Pair<Integer, String> 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); |
| gen.addNop(); |
| assertEquals(1, gen.generate().length); |
| |
| final int count = 42; |
| gen = new ApfV4Generator(APF_VERSION_2); |
| for (int i = 0; i < count; i++) { |
| gen.addNop(); |
| } |
| assertEquals(count, gen.generate().length); |
| } |
| |
| @Test |
| public void testQnameEncoding() { |
| String[] qname = new String[]{"abcd", "ef", "日本"}; |
| byte[] encodedQname = ApfFilter.encodeQname(qname); |
| Assert.assertArrayEquals( |
| new byte[]{0x04, 0x61, 0x62, 0x63, 0x64, 0x02, 0x65, 0x66, 0x06, (byte) 0xe6, |
| (byte) 0x97, (byte) 0xa5, (byte) 0xe6, (byte) 0x9c, (byte) 0xac, 0x00}, |
| encodedQname); |
| } |
| |
| @Test |
| public void testApfFilterMdns() throws Exception { |
| final byte[] unicastIpv4Addr = {(byte) 192, 0, 2, 63}; |
| |
| MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24); |
| LinkProperties lp = new LinkProperties(); |
| lp.addLinkAddress(link); |
| |
| ApfConfiguration config = getDefaultConfig(); |
| TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| apfFilter.setLinkProperties(lp); |
| |
| // Construct IPv4 mDNS packet |
| byte[] mdnsv4packet = makeMdnsV4Packet("test.local"); |
| byte[] mdnsv6packet = makeMdnsV6Packet("test.local"); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| // mDNSv4 packet is passed if no mDns filter is turned on |
| assertPass(program, mdnsv4packet); |
| // mDNSv6 packet is passed if no mDNS filter is turned on |
| assertPass(program, mdnsv6packet); |
| |
| // mDNSv4 packet with qname in the allowlist is passed |
| apfFilter.addToMdnsAllowList(new String[]{"test", "local"}); |
| apfFilter.addToMdnsAllowList(new String[]{"abcd", "local"}); |
| apfFilter.setMulticastFilter(true); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| assertPass(program, mdnsv4packet); |
| assertPass(program, mdnsv6packet); |
| // If packet contains more than one qname, pass the packet |
| mdnsv4packet = makeMdnsV4Packet("cccc.local", "dddd.local"); |
| mdnsv6packet = makeMdnsV6Packet("cccc.local", "dddd.local"); |
| assertPass(program, mdnsv4packet); |
| assertPass(program, mdnsv6packet); |
| // If packet doesn't contain any qname, pass the packet |
| mdnsv4packet = makeMdnsV4Packet(); |
| mdnsv6packet = makeMdnsV6Packet(); |
| assertPass(program, mdnsv4packet); |
| assertPass(program, mdnsv6packet); |
| |
| mdnsv4packet = makeMdnsV4Packet("abcd.local"); |
| mdnsv6packet = makeMdnsV6Packet("abcd.local"); |
| assertPass(program, mdnsv4packet); |
| assertPass(program, mdnsv6packet); |
| |
| // mDNSv4 packet with qname not in the allowlist is dropped |
| mdnsv4packet = makeMdnsV4Packet("ffff.local"); |
| mdnsv6packet = makeMdnsV6Packet("ffff.local"); |
| assertDrop(program, mdnsv4packet); |
| assertDrop(program, mdnsv6packet); |
| |
| apfFilter.removeFromAllowList(new String[]{"abcd", "local"}); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| mdnsv4packet = makeMdnsV4Packet("abcd.local"); |
| mdnsv6packet = makeMdnsV6Packet("abcd.local"); |
| assertDrop(program, mdnsv4packet); |
| assertDrop(program, mdnsv6packet); |
| } |
| |
| private ApfV4Generator generateDnsFilter(boolean ipv6, String... labels) throws Exception { |
| ApfV4Generator gen = new ApfV4Generator(APF_VERSION_2); |
| gen.addLoadImmediate(R1, ipv6 ? IPV6_HEADER_LEN : IPV4_HEADER_LEN); |
| DnsUtils.generateFilter(gen, labels); |
| return gen; |
| } |
| |
| private void doTestDnsParsing(boolean expectPass, boolean ipv6, String filterName, |
| byte[] pkt) throws Exception { |
| final String[] labels = filterName.split(/*regex=*/ "[.]"); |
| ApfV4Generator gen = generateDnsFilter(ipv6, labels); |
| |
| // Hack to prevent the APF instruction limit triggering. |
| for (int i = 0; i < 500; i++) { |
| gen.addNop(); |
| } |
| |
| byte[] program = gen.generate(); |
| Log.d(TAG, "prog_len=" + program.length); |
| if (expectPass) { |
| assertPass(program, pkt, 0); |
| } else { |
| assertDrop(program, pkt, 0); |
| } |
| } |
| |
| private void doTestDnsParsing(boolean expectPass, boolean ipv6, String filterName, |
| String... packetNames) throws Exception { |
| final byte[] pkt = ipv6 ? makeMdnsV6Packet(packetNames) : makeMdnsV4Packet(packetNames); |
| doTestDnsParsing(expectPass, ipv6, filterName, pkt); |
| } |
| |
| @Test |
| public void testDnsParsing() throws Exception { |
| final boolean ipv4 = false, ipv6 = true; |
| |
| // Packets with one question. |
| // Names don't start with _ because DnsPacket thinks such names are invalid. |
| doTestDnsParsing(true, ipv6, "googlecast.tcp.local", "googlecast.tcp.local"); |
| doTestDnsParsing(true, ipv4, "googlecast.tcp.local", "googlecast.tcp.local"); |
| doTestDnsParsing(false, ipv6, "googlecast.tcp.lozal", "googlecast.tcp.local"); |
| doTestDnsParsing(false, ipv4, "googlecast.tcp.lozal", "googlecast.tcp.local"); |
| doTestDnsParsing(false, ipv6, "googlecast.udp.local", "googlecast.tcp.local"); |
| doTestDnsParsing(false, ipv4, "googlecast.udp.local", "googlecast.tcp.local"); |
| |
| // Packets with multiple questions that can't be compressed. Not realistic for MDNS since |
| // everything ends in .local, but useful to ensure only the non-compression code is tested. |
| doTestDnsParsing(true, ipv6, "googlecast.tcp.local", |
| "googlecast.tcp.local", "developer.android.com"); |
| doTestDnsParsing(true, ipv4, "googlecast.tcp.local", |
| "developer.android.com", "googlecast.tcp.local"); |
| doTestDnsParsing(false, ipv4, "googlecast.tcp.local", |
| "developer.android.com", "googlecast.tcp.invalid"); |
| doTestDnsParsing(true, ipv6, "googlecast.tcp.local", |
| "developer.android.com", "www.google.co.jp", "googlecast.tcp.local"); |
| doTestDnsParsing(false, ipv4, "veryverylongservicename.tcp.local", |
| "www.google.co.jp", "veryverylongservicename.tcp.invalid"); |
| doTestDnsParsing(true, ipv6, "googlecast.tcp.local", |
| "www.google.co.jp", "googlecast.tcp.local", "developer.android.com"); |
| |
| // Name with duplicate labels. |
| doTestDnsParsing(true, ipv6, "local.tcp.local", "local.tcp.local"); |
| |
| final byte[] pkt = makeMdnsCompressedV6Packet(); |
| doTestDnsParsing(true, ipv6, "googlecast.tcp.local", pkt); |
| doTestDnsParsing(true, ipv6, "matter.tcp.local", pkt); |
| doTestDnsParsing(true, ipv6, "myservice.tcp.local", pkt); |
| doTestDnsParsing(false, ipv6, "otherservice.tcp.local", pkt); |
| } |
| |
| private void doTestDnsParsingProgramLength(int expectedLength, |
| String filterName) throws Exception { |
| final String[] labels = filterName.split(/*regex=*/ "[.]"); |
| |
| ApfV4Generator gen = generateDnsFilter(/*ipv6=*/ true, labels); |
| assertEquals("Program for " + filterName + " had unexpected length:", |
| expectedLength, gen.generate().length); |
| } |
| |
| /** |
| * Rough metric of code size. Checks how large the generated filter is in various scenarios. |
| * Helps ensure any changes to the code do not substantially increase APF code size. |
| */ |
| @Test |
| public void testDnsParsingProgramLength() throws Exception { |
| doTestDnsParsingProgramLength(237, "MyDevice.local"); |
| doTestDnsParsingProgramLength(285, "_googlecast.tcp.local"); |
| doTestDnsParsingProgramLength(291, "_googlecast12345.tcp.local"); |
| doTestDnsParsingProgramLength(244, "_googlecastZtcp.local"); |
| doTestDnsParsingProgramLength(249, "_googlecastZtcp12345.local"); |
| } |
| |
| private void doTestDnsParsingNecessaryOverhead(int expectedNecessaryOverhead, |
| String filterName, byte[] pkt, String description) throws Exception { |
| final String[] labels = filterName.split(/*regex=*/ "[.]"); |
| |
| // Check that the generated code, when the program contains the specified number of extra |
| // bytes, is capable of dropping the packet. |
| ApfV4Generator gen = generateDnsFilter(/*ipv6=*/ true, labels); |
| for (int i = 0; i < expectedNecessaryOverhead; i++) { |
| gen.addNop(); |
| } |
| final byte[] programWithJustEnoughOverhead = gen.generate(); |
| assertVerdict( |
| "Overhead too low: filter for " + filterName + " with " + expectedNecessaryOverhead |
| + " extra instructions unexpectedly passed " + description, |
| DROP, programWithJustEnoughOverhead, pkt, 0); |
| |
| if (expectedNecessaryOverhead == 0) return; |
| |
| // Check that the generated code, without the specified number of extra program bytes, |
| // cannot correctly drop the packet because it hits the interpreter instruction limit. |
| gen = generateDnsFilter(/*ipv6=*/ true, labels); |
| for (int i = 0; i < expectedNecessaryOverhead - 1; i++) { |
| gen.addNop(); |
| } |
| final byte[] programWithNotEnoughOverhead = gen.generate(); |
| |
| assertVerdict( |
| "Overhead too high: filter for " + filterName + " with " + expectedNecessaryOverhead |
| + " extra instructions unexpectedly dropped " + description, |
| PASS, programWithNotEnoughOverhead, pkt, 0); |
| } |
| |
| private void doTestDnsParsingNecessaryOverhead(int expectedNecessaryOverhead, |
| String filterName, String... packetNames) throws Exception { |
| doTestDnsParsingNecessaryOverhead(expectedNecessaryOverhead, filterName, |
| makeMdnsV6Packet(packetNames), |
| "IPv6 MDNS packet containing: " + Arrays.toString(packetNames)); |
| } |
| |
| /** |
| * Rough metric of filter efficiency. Because the filter uses backwards jumps, on complex |
| * packets it will not finish running before the interpreter hits the maximum number of allowed |
| * instructions (== number of bytes in the program) and unconditionally accepts the packet. |
| * This test checks much extra code the program must contain in order for the generated filter |
| * to successfully drop the packet. It helps ensure any changes to the code do not reduce the |
| * complexity of packets that the APF code can drop. |
| */ |
| @Test |
| public void testDnsParsingNecessaryOverhead() throws Exception { |
| // Simple packets can be parsed with zero extra code. |
| doTestDnsParsingNecessaryOverhead(0, "googlecast.tcp.local", |
| "matter.tcp.local", "developer.android.com"); |
| |
| doTestDnsParsingNecessaryOverhead(0, "googlecast.tcp.local", |
| "developer.android.com", "matter.tcp.local"); |
| |
| doTestDnsParsingNecessaryOverhead(0, "googlecast.tcp.local", |
| "developer.android.com", "matter.tcp.local", "www.google.co.jp"); |
| |
| doTestDnsParsingNecessaryOverhead(0, "googlecast.tcp.local", |
| "developer.android.com", "matter.tcp.local", "www.google.co.jp", |
| "example.org"); |
| |
| // More complicated packets cause more instructions to be run and can only be dropped if |
| // the program contains lots of extra code. |
| doTestDnsParsingNecessaryOverhead(57, "googlecast.tcp.local", |
| "developer.android.com", "matter.tcp.local", "www.google.co.jp", |
| "example.org", "otherexample.net"); |
| |
| doTestDnsParsingNecessaryOverhead(115, "googlecast.tcp.local", |
| "developer.android.com", "matter.tcp.local", "www.google.co.jp", |
| "example.org", "otherexample.net", "docs.new"); |
| |
| doTestDnsParsingNecessaryOverhead(0, "foo.tcp.local", |
| makeMdnsCompressedV6Packet(), "compressed packet"); |
| |
| doTestDnsParsingNecessaryOverhead(235, "foo.tcp.local", |
| makeMdnsCompressedV6PacketWithManyNames(), "compressed packet with many names"); |
| } |
| |
| @Test |
| public void testApfFilterMulticast() throws Exception { |
| final byte[] unicastIpv4Addr = {(byte)192,0,2,63}; |
| final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255}; |
| final byte[] multicastIpv4Addr = {(byte)224,0,0,1}; |
| final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb}; |
| |
| MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24); |
| LinkProperties lp = new LinkProperties(); |
| lp.addLinkAddress(link); |
| |
| ApfConfiguration config = getDefaultConfig(); |
| config.ieee802_3Filter = DROP_802_3_FRAMES; |
| TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| apfFilter.setLinkProperties(lp); |
| |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // 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(TestApfFilter.MOCK_MAC_ADDR); |
| bcastv4unicastl2packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP); |
| put(bcastv4unicastl2packet, IPV4_DEST_ADDR_OFFSET, broadcastIpv4Addr); |
| |
| // Verify initially disabled multicast filter is off |
| assertPass(program, mcastv4packet.array()); |
| assertPass(program, mcastv6packet.array()); |
| assertPass(program, bcastv4packet1.array()); |
| assertPass(program, bcastv4packet2.array()); |
| assertPass(program, bcastv4unicastl2packet.array()); |
| |
| // Turn on multicast filter and verify it works |
| ipClientCallback.resetApfProgramWait(); |
| apfFilter.setMulticastFilter(true); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| assertDrop(program, mcastv4packet.array()); |
| assertDrop(program, mcastv6packet.array()); |
| assertDrop(program, bcastv4packet1.array()); |
| assertDrop(program, bcastv4packet2.array()); |
| assertDrop(program, bcastv4unicastl2packet.array()); |
| |
| // Turn off multicast filter and verify it's off |
| ipClientCallback.resetApfProgramWait(); |
| apfFilter.setMulticastFilter(false); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| assertPass(program, mcastv4packet.array()); |
| assertPass(program, mcastv6packet.array()); |
| assertPass(program, bcastv4packet1.array()); |
| assertPass(program, bcastv4packet2.array()); |
| assertPass(program, bcastv4unicastl2packet.array()); |
| |
| // Verify it can be initialized to on |
| ipClientCallback.resetApfProgramWait(); |
| config.multicastFilter = DROP_MULTICAST; |
| config.ieee802_3Filter = DROP_802_3_FRAMES; |
| apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics, |
| mDependencies); |
| apfFilter.setLinkProperties(lp); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| 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 */); |
| } |
| |
| @Test |
| @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) |
| public void testShouldHandleLightDozeKillSwitch() throws Exception { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| final ApfConfiguration configuration = getDefaultConfig(); |
| configuration.shouldHandleLightDoze = false; |
| final ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, |
| configuration, mNetworkQuirkMetrics, mDependencies); |
| final ArgumentCaptor<BroadcastReceiver> receiverCaptor = |
| ArgumentCaptor.forClass(BroadcastReceiver.class); |
| verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean()); |
| final BroadcastReceiver receiver = receiverCaptor.getValue(); |
| doReturn(true).when(mPowerManager).isDeviceLightIdleMode(); |
| receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED)); |
| assertFalse(apfFilter.isInDozeMode()); |
| } |
| |
| private void doTestApfFilterMulticastPingWhileDozing(boolean isLightDozing) throws Exception { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| final ApfConfiguration configuration = getDefaultConfig(); |
| configuration.shouldHandleLightDoze = true; |
| final ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, |
| configuration, mNetworkQuirkMetrics, mDependencies); |
| final ArgumentCaptor<BroadcastReceiver> receiverCaptor = |
| ArgumentCaptor.forClass(BroadcastReceiver.class); |
| verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean()); |
| 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(ipClientCallback.assertProgramUpdateAndGet(), 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)); |
| } |
| // ...and even while dozing... |
| assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array()); |
| |
| // ...but when the multicast filter is also enabled, drop the multicast pings to save power. |
| apfFilter.setMulticastFilter(true); |
| assertDrop(ipClientCallback.assertProgramUpdateAndGet(), 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(ipClientCallback.assertProgramUpdateAndGet(), 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)); |
| } |
| assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array()); |
| } |
| |
| @Test |
| public void testApfFilter802_3() throws Exception { |
| MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| ApfConfiguration config = getDefaultConfig(); |
| ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, |
| mNetworkQuirkMetrics, mDependencies); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // 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 |
| ipClientCallback.resetApfProgramWait(); |
| config.ieee802_3Filter = DROP_802_3_FRAMES; |
| apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, |
| mNetworkQuirkMetrics, mDependencies); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // 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 |
| public void testApfFilterEthTypeBL() throws Exception { |
| final int[] emptyBlackList = {}; |
| final int[] ipv4BlackList = {ETH_P_IP}; |
| final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6}; |
| |
| MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| ApfConfiguration config = getDefaultConfig(); |
| ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, |
| mNetworkQuirkMetrics, mDependencies); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // 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 |
| ipClientCallback.resetApfProgramWait(); |
| config.ethTypeBlackList = ipv4BlackList; |
| apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, |
| mNetworkQuirkMetrics, mDependencies); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // 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 |
| ipClientCallback.resetApfProgramWait(); |
| config.ethTypeBlackList = ipv4Ipv6BlackList; |
| apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, |
| mNetworkQuirkMetrics, mDependencies); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // 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 byte[] getProgram(MockIpClientCallback cb, ApfFilter filter, LinkProperties lp) { |
| cb.resetApfProgramWait(); |
| filter.setLinkProperties(lp); |
| return cb.assertProgramUpdateAndGet(); |
| } |
| |
| 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 { |
| MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| ApfConfiguration config = getDefaultConfig(); |
| config.multicastFilter = DROP_MULTICAST; |
| config.ieee802_3Filter = DROP_802_3_FRAMES; |
| TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| |
| // Verify initially ARP request filter is off, and GARP filter is on. |
| verifyArpFilter(ipClientCallback.assertProgramUpdateAndGet(), PASS); |
| |
| // Inform ApfFilter of our address and verify ARP filtering is on |
| LinkAddress linkAddress = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24); |
| LinkProperties lp = new LinkProperties(); |
| assertTrue(lp.addLinkAddress(linkAddress)); |
| verifyArpFilter(getProgram(ipClientCallback, apfFilter, lp), DROP); |
| |
| // Inform ApfFilter of loss of IP and verify ARP filtering is off |
| verifyArpFilter(getProgram(ipClientCallback, apfFilter, new LinkProperties()), PASS); |
| } |
| |
| 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[] IPV4_KEEPALIVE_SRC_ADDR = {10, 0, 0, 5}; |
| private static final byte[] IPV4_KEEPALIVE_DST_ADDR = {10, 0, 0, 6}; |
| private static final byte[] IPV4_ANOTHER_ADDR = {10, 0 , 0, 7}; |
| private static final byte[] IPV6_KEEPALIVE_SRC_ADDR = |
| {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf1}; |
| private static final byte[] IPV6_KEEPALIVE_DST_ADDR = |
| {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf2}; |
| 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}; |
| |
| @Test |
| public void testApfFilterKeepaliveAck() throws Exception { |
| final MockIpClientCallback cb = new MockIpClientCallback(); |
| final ApfConfiguration config = getDefaultConfig(); |
| config.multicastFilter = DROP_MULTICAST; |
| config.ieee802_3Filter = DROP_802_3_FRAMES; |
| final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, |
| mNetworkQuirkMetrics, mDependencies); |
| byte[] program; |
| final int srcPort = 12345; |
| final int dstPort = 54321; |
| final int seqNum = 2123456789; |
| final int ackNum = 1234567890; |
| final int anotherSrcPort = 23456; |
| final int anotherDstPort = 65432; |
| final int anotherSeqNum = 2123456780; |
| final int anotherAckNum = 1123456789; |
| final int slot1 = 1; |
| final int slot2 = 2; |
| final int window = 14480; |
| final int windowScale = 4; |
| |
| // src: 10.0.0.5, port: 12345 |
| // dst: 10.0.0.6, port: 54321 |
| InetAddress srcAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_SRC_ADDR); |
| InetAddress dstAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_DST_ADDR); |
| |
| final TcpKeepalivePacketDataParcelable parcel = new TcpKeepalivePacketDataParcelable(); |
| parcel.srcAddress = srcAddr.getAddress(); |
| parcel.srcPort = srcPort; |
| parcel.dstAddress = dstAddr.getAddress(); |
| parcel.dstPort = dstPort; |
| parcel.seq = seqNum; |
| parcel.ack = ackNum; |
| |
| apfFilter.addTcpKeepalivePacketFilter(slot1, parcel); |
| program = cb.assertProgramUpdateAndGet(); |
| |
| // Verify IPv4 keepalive ack packet is dropped |
| // src: 10.0.0.6, port: 54321 |
| // dst: 10.0.0.5, port: 12345 |
| assertDrop(program, |
| ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, |
| dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */)); |
| // Verify IPv4 non-keepalive ack packet from the same source address is passed |
| assertPass(program, |
| ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, |
| dstPort, srcPort, ackNum + 100, seqNum, 0 /* dataLength */)); |
| assertPass(program, |
| ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, |
| dstPort, srcPort, ackNum, seqNum + 1, 10 /* dataLength */)); |
| // Verify IPv4 packet from another address is passed |
| assertPass(program, |
| ipv4TcpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort, |
| anotherDstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */)); |
| |
| // Remove IPv4 keepalive filter |
| apfFilter.removeKeepalivePacketFilter(slot1); |
| |
| try { |
| // src: 2404:0:0:0:0:0:faf1, port: 12345 |
| // dst: 2404:0:0:0:0:0:faf2, port: 54321 |
| srcAddr = InetAddress.getByAddress(IPV6_KEEPALIVE_SRC_ADDR); |
| dstAddr = InetAddress.getByAddress(IPV6_KEEPALIVE_DST_ADDR); |
| |
| final TcpKeepalivePacketDataParcelable ipv6Parcel = |
| new TcpKeepalivePacketDataParcelable(); |
| ipv6Parcel.srcAddress = srcAddr.getAddress(); |
| ipv6Parcel.srcPort = srcPort; |
| ipv6Parcel.dstAddress = dstAddr.getAddress(); |
| ipv6Parcel.dstPort = dstPort; |
| ipv6Parcel.seq = seqNum; |
| ipv6Parcel.ack = ackNum; |
| |
| apfFilter.addTcpKeepalivePacketFilter(slot1, ipv6Parcel); |
| program = cb.assertProgramUpdateAndGet(); |
| |
| // Verify IPv6 keepalive ack packet is dropped |
| // src: 2404:0:0:0:0:0:faf2, port: 54321 |
| // dst: 2404:0:0:0:0:0:faf1, port: 12345 |
| assertDrop(program, |
| ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR, |
| dstPort, srcPort, ackNum, seqNum + 1)); |
| // Verify IPv6 non-keepalive ack packet from the same source address is passed |
| assertPass(program, |
| ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR, |
| dstPort, srcPort, ackNum + 100, seqNum)); |
| // Verify IPv6 packet from another address is passed |
| assertPass(program, |
| ipv6TcpPacket(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort, |
| anotherDstPort, anotherSeqNum, anotherAckNum)); |
| |
| // Remove IPv6 keepalive filter |
| apfFilter.removeKeepalivePacketFilter(slot1); |
| |
| // Verify multiple filters |
| apfFilter.addTcpKeepalivePacketFilter(slot1, parcel); |
| apfFilter.addTcpKeepalivePacketFilter(slot2, ipv6Parcel); |
| program = cb.assertProgramUpdateAndGet(); |
| |
| // Verify IPv4 keepalive ack packet is dropped |
| // src: 10.0.0.6, port: 54321 |
| // dst: 10.0.0.5, port: 12345 |
| assertDrop(program, |
| ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, |
| dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */)); |
| // Verify IPv4 non-keepalive ack packet from the same source address is passed |
| assertPass(program, |
| ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, |
| dstPort, srcPort, ackNum + 100, seqNum, 0 /* dataLength */)); |
| // Verify IPv4 packet from another address is passed |
| assertPass(program, |
| ipv4TcpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort, |
| anotherDstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */)); |
| |
| // Verify IPv6 keepalive ack packet is dropped |
| // src: 2404:0:0:0:0:0:faf2, port: 54321 |
| // dst: 2404:0:0:0:0:0:faf1, port: 12345 |
| assertDrop(program, |
| ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR, |
| dstPort, srcPort, ackNum, seqNum + 1)); |
| // Verify IPv6 non-keepalive ack packet from the same source address is passed |
| assertPass(program, |
| ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR, |
| dstPort, srcPort, ackNum + 100, seqNum)); |
| // Verify IPv6 packet from another address is passed |
| assertPass(program, |
| ipv6TcpPacket(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort, |
| anotherDstPort, anotherSeqNum, anotherAckNum)); |
| |
| // Remove keepalive filters |
| apfFilter.removeKeepalivePacketFilter(slot1); |
| apfFilter.removeKeepalivePacketFilter(slot2); |
| } catch (UnsupportedOperationException e) { |
| // TODO: support V6 packets |
| } |
| |
| program = cb.assertProgramUpdateAndGet(); |
| |
| // Verify IPv4, IPv6 packets are passed |
| assertPass(program, |
| ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, |
| dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */)); |
| assertPass(program, |
| ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR, |
| dstPort, srcPort, ackNum, seqNum + 1)); |
| assertPass(program, |
| ipv4TcpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, srcPort, |
| dstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */)); |
| assertPass(program, |
| ipv6TcpPacket(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, srcPort, |
| dstPort, anotherSeqNum, anotherAckNum)); |
| } |
| |
| private static byte[] ipv4TcpPacket(byte[] sip, byte[] dip, int sport, |
| int dport, int seq, int ack, int dataLength) { |
| final int totalLength = dataLength + IPV4_HEADER_LEN + IPV4_TCP_HEADER_LEN; |
| |
| ByteBuffer packet = ByteBuffer.wrap(new byte[totalLength + ETH_HEADER_LEN]); |
| |
| // Ethertype and IPv4 header |
| setIpv4VersionFields(packet); |
| packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength); |
| packet.put(IPV4_PROTOCOL_OFFSET, (byte) IPPROTO_TCP); |
| put(packet, IPV4_SRC_ADDR_OFFSET, sip); |
| put(packet, IPV4_DEST_ADDR_OFFSET, dip); |
| packet.putShort(IPV4_TCP_SRC_PORT_OFFSET, (short) sport); |
| packet.putShort(IPV4_TCP_DEST_PORT_OFFSET, (short) dport); |
| packet.putInt(IPV4_TCP_SEQ_NUM_OFFSET, seq); |
| packet.putInt(IPV4_TCP_ACK_NUM_OFFSET, ack); |
| |
| // TCP header length 5(20 bytes), reserved 3 bits, NS=0 |
| packet.put(IPV4_TCP_HEADER_LENGTH_OFFSET, (byte) 0x50); |
| // TCP flags: ACK set |
| packet.put(IPV4_TCP_HEADER_FLAG_OFFSET, (byte) 0x10); |
| return packet.array(); |
| } |
| |
| private static byte[] ipv6TcpPacket(byte[] sip, byte[] tip, int sport, |
| int dport, int seq, int ack) { |
| ByteBuffer packet = ByteBuffer.wrap(new byte[100]); |
| setIpv6VersionFields(packet); |
| packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) IPPROTO_TCP); |
| put(packet, IPV6_SRC_ADDR_OFFSET, sip); |
| put(packet, IPV6_DEST_ADDR_OFFSET, tip); |
| packet.putShort(IPV6_TCP_SRC_PORT_OFFSET, (short) sport); |
| packet.putShort(IPV6_TCP_DEST_PORT_OFFSET, (short) dport); |
| packet.putInt(IPV6_TCP_SEQ_NUM_OFFSET, seq); |
| packet.putInt(IPV6_TCP_ACK_NUM_OFFSET, ack); |
| return packet.array(); |
| } |
| |
| @Test |
| public void testApfFilterNattKeepalivePacket() throws Exception { |
| final MockIpClientCallback cb = new MockIpClientCallback(); |
| final ApfConfiguration config = getDefaultConfig(); |
| config.multicastFilter = DROP_MULTICAST; |
| config.ieee802_3Filter = DROP_802_3_FRAMES; |
| final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, |
| mNetworkQuirkMetrics, mDependencies); |
| byte[] program; |
| final int srcPort = 1024; |
| final int dstPort = 4500; |
| final int slot1 = 1; |
| // NAT-T keepalive |
| final byte[] kaPayload = {(byte) 0xff}; |
| final byte[] nonKaPayload = {(byte) 0xfe}; |
| |
| // src: 10.0.0.5, port: 1024 |
| // dst: 10.0.0.6, port: 4500 |
| InetAddress srcAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_SRC_ADDR); |
| InetAddress dstAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_DST_ADDR); |
| |
| final NattKeepalivePacketDataParcelable parcel = new NattKeepalivePacketDataParcelable(); |
| parcel.srcAddress = srcAddr.getAddress(); |
| parcel.srcPort = srcPort; |
| parcel.dstAddress = dstAddr.getAddress(); |
| parcel.dstPort = dstPort; |
| |
| apfFilter.addNattKeepalivePacketFilter(slot1, parcel); |
| program = cb.assertProgramUpdateAndGet(); |
| |
| // Verify IPv4 keepalive packet is dropped |
| // src: 10.0.0.6, port: 4500 |
| // dst: 10.0.0.5, port: 1024 |
| byte[] pkt = ipv4UdpPacket(IPV4_KEEPALIVE_DST_ADDR, |
| IPV4_KEEPALIVE_SRC_ADDR, dstPort, srcPort, 1 /* dataLength */); |
| System.arraycopy(kaPayload, 0, pkt, IPV4_UDP_PAYLOAD_OFFSET, kaPayload.length); |
| assertDrop(program, pkt); |
| |
| // Verify a packet with payload length 1 byte but it is not 0xff will pass the filter. |
| System.arraycopy(nonKaPayload, 0, pkt, IPV4_UDP_PAYLOAD_OFFSET, nonKaPayload.length); |
| assertPass(program, pkt); |
| |
| // Verify IPv4 non-keepalive response packet from the same source address is passed |
| assertPass(program, |
| ipv4UdpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, |
| dstPort, srcPort, 10 /* dataLength */)); |
| |
| // Verify IPv4 non-keepalive response packet from other source address is passed |
| assertPass(program, |
| ipv4UdpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, |
| dstPort, srcPort, 10 /* dataLength */)); |
| |
| apfFilter.removeKeepalivePacketFilter(slot1); |
| } |
| |
| private static byte[] ipv4UdpPacket(byte[] sip, byte[] dip, int sport, |
| int dport, int dataLength) { |
| final int totalLength = dataLength + IPV4_HEADER_LEN + UDP_HEADER_LEN; |
| final int udpLength = UDP_HEADER_LEN + dataLength; |
| ByteBuffer packet = ByteBuffer.wrap(new byte[totalLength + ETH_HEADER_LEN]); |
| |
| // Ethertype and IPv4 header |
| setIpv4VersionFields(packet); |
| packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength); |
| packet.put(IPV4_PROTOCOL_OFFSET, (byte) IPPROTO_UDP); |
| put(packet, IPV4_SRC_ADDR_OFFSET, sip); |
| put(packet, IPV4_DEST_ADDR_OFFSET, dip); |
| packet.putShort(IPV4_UDP_SRC_PORT_OFFSET, (short) sport); |
| packet.putShort(IPV4_UDP_DEST_PORT_OFFSET, (short) dport); |
| packet.putShort(IPV4_UDP_LENGTH_OFFSET, (short) udpLength); |
| |
| return packet.array(); |
| } |
| |
| 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 { |
| MockIpClientCallback cb = new MockIpClientCallback(); |
| ApfConfiguration config = getDefaultConfig(); |
| TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics, |
| mDependencies); |
| |
| 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 void verifyRaLifetime(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback, |
| ByteBuffer packet, int lifetime) throws IOException, ErrnoException { |
| // Verify new program generated if ApfFilter witnesses RA |
| apfFilter.pretendPacketReceived(packet.array()); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| verifyRaLifetime(program, packet, lifetime); |
| } |
| |
| private void assertInvalidRa(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback, |
| ByteBuffer packet) throws IOException, ErrnoException { |
| apfFilter.pretendPacketReceived(packet.array()); |
| ipClientCallback.assertNoProgramUpdate(); |
| } |
| |
| @Test |
| public void testApfFilterRa() throws Exception { |
| MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| ApfConfiguration config = getDefaultConfig(); |
| config.multicastFilter = DROP_MULTICAST; |
| config.ieee802_3Filter = DROP_802_3_FRAMES; |
| TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| 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(apfFilter, ipClientCallback, 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(apfFilter, ipClientCallback, zeroLengthOptionPacket); |
| |
| // Generate several RAs with different options and lifetimes, and verify when |
| // ApfFilter is shown these packets, it generates programs to filter them for the |
| // appropriate lifetime. |
| ra = new RaPacketBuilder(ROUTER_LIFETIME); |
| ra.addPioOption(PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, "2001:db8::/64"); |
| ByteBuffer prefixOptionPacket = ByteBuffer.wrap(ra.build()); |
| verifyRaLifetime( |
| apfFilter, ipClientCallback, 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(apfFilter, ipClientCallback, 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(apfFilter, ipClientCallback, lowLifetimeRdnssOptionPacket, |
| ROUTER_LIFETIME); |
| |
| ra = new RaPacketBuilder(ROUTER_LIFETIME); |
| ra.addRioOption(ROUTE_LIFETIME, "64:ff9b::/96"); |
| ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(ra.build()); |
| verifyRaLifetime(apfFilter, ipClientCallback, 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. |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| 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(apfFilter, ipClientCallback, dnsslOptionPacket, ROUTER_LIFETIME); |
| |
| ByteBuffer largeRaPacket = ByteBuffer.wrap(buildLargeRa()); |
| verifyRaLifetime(apfFilter, ipClientCallback, largeRaPacket, 300); |
| |
| // Verify that current program filters all the RAs (note: ApfFilter.MAX_RAS == 10). |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| 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 MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| final ApfConfiguration config = getDefaultConfig(); |
| config.multicastFilter = DROP_MULTICAST; |
| config.ieee802_3Filter = DROP_802_3_FRAMES; |
| final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| 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. |
| apfFilter.pretendPacketReceived(raPacket); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| 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 MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| final ApfConfiguration config = getDefaultConfig(); |
| config.multicastFilter = DROP_MULTICAST; |
| config.ieee802_3Filter = DROP_802_3_FRAMES; |
| final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| 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(apfFilter, ipClientCallback, basePacket, routerLifetime); |
| apfFilter.increaseCurrentTimeSeconds(timePassedSeconds); |
| synchronized (apfFilter) { |
| apfFilter.installNewProgramLocked(); |
| } |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| verifyRaLifetime(program, basePacket, routerLifetime, timePassedSeconds); |
| |
| // Packet should be passed if the program is installed after 1/6 * lifetime from last seen |
| apfFilter.increaseCurrentTimeSeconds((int) (routerLifetime / 6) - timePassedSeconds - 1); |
| synchronized (apfFilter) { |
| apfFilter.installNewProgramLocked(); |
| } |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| assertDrop(program, basePacket.array()); |
| apfFilter.increaseCurrentTimeSeconds(1); |
| synchronized (apfFilter) { |
| apfFilter.installNewProgramLocked(); |
| } |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| 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(); |
| MockIpClientCallback cb = new MockIpClientCallback(); |
| ApfConfiguration config = getDefaultConfig(); |
| config.multicastFilter = DROP_MULTICAST; |
| config.ieee802_3Filter = DROP_802_3_FRAMES; |
| TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics, |
| mDependencies); |
| for (int i = 0; i < 1000; i++) { |
| byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)]; |
| r.nextBytes(packet); |
| try { |
| apfFilter.new Ra(packet, packet.length); |
| } catch (ApfFilter.InvalidRaException e) { |
| } catch (Exception e) { |
| throw new Exception("bad packet: " + HexDump.toHexString(packet), e); |
| } |
| } |
| } |
| |
| @Test |
| public void testRaProcessing() throws Exception { |
| final int maxRandomPacketSize = 512; |
| final Random r = new Random(); |
| MockIpClientCallback cb = new MockIpClientCallback(); |
| ApfConfiguration config = getDefaultConfig(); |
| config.multicastFilter = DROP_MULTICAST; |
| config.ieee802_3Filter = DROP_802_3_FRAMES; |
| TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics, |
| mDependencies); |
| 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 MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| final TestApfFilter apfFilter = new TestApfFilter(mContext, getDefaultConfig(), |
| ipClientCallback, mNetworkQuirkMetrics, mDependencies); |
| |
| // Create an RA and build an APF program |
| byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build(); |
| apfFilter.pretendPacketReceived(ra); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // lifetime dropped significantly, assert pass |
| ra = new RaPacketBuilder(200 /* router lifetime */).build(); |
| assertPass(program, ra); |
| |
| // update program with the new RA |
| apfFilter.pretendPacketReceived(ra); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // assert program was updated and new lifetimes were taken into account. |
| assertDrop(program, ra); |
| } |
| |
| @Test |
| public void testProcessRaWithInfiniteLifeTimeWithoutCrash() throws Exception { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| // configure accept_ra_min_lft |
| final ApfConfiguration config = getDefaultConfig(); |
| config.acceptRaMinLft = 180; |
| TestApfFilter apfFilter; |
| // 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<String> lifetimes = List.of("FFFFFFFF", "00000000", "00000001", "00001B58"); |
| for (String lifetime : lifetimes) { |
| apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics, |
| mDependencies); |
| final byte[] ra = hexStringToByteArray( |
| String.format(packetStringFmt, lifetime + lifetime)); |
| // feed the RA into APF and generate the filter, the filter shouldn't crash. |
| apfFilter.pretendPacketReceived(ra); |
| ipClientCallback.assertProgramUpdateAndGet(); |
| } |
| } |
| |
| // Test for go/apf-ra-filter Case 1a. |
| // Old lifetime is 0 |
| @Test |
| public void testAcceptRaMinLftCase1a() throws Exception { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| // configure accept_ra_min_lft |
| final ApfConfiguration config = getDefaultConfig(); |
| config.acceptRaMinLft = 180; |
| final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| |
| // 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(); |
| |
| apfFilter.pretendPacketReceived(ra); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // 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 { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| // configure accept_ra_min_lft |
| final ApfConfiguration config = getDefaultConfig(); |
| config.acceptRaMinLft = 180; |
| final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| |
| // 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(); |
| |
| apfFilter.pretendPacketReceived(ra); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // 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 { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| // configure accept_ra_min_lft |
| final ApfConfiguration config = getDefaultConfig(); |
| config.acceptRaMinLft = 180; |
| final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| |
| // Create an initial RA and build an APF program |
| byte[] ra = new RaPacketBuilder(0 /* router lifetime */).build(); |
| |
| apfFilter.pretendPacketReceived(ra); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // 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 { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| // configure accept_ra_min_lft |
| final ApfConfiguration config = getDefaultConfig(); |
| config.acceptRaMinLft = 180; |
| final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| |
| // Create an initial RA and build an APF program |
| byte[] ra = new RaPacketBuilder(100 /* router lifetime */).build(); |
| |
| apfFilter.pretendPacketReceived(ra); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // 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 { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| // configure accept_ra_min_lft |
| final ApfConfiguration config = getDefaultConfig(); |
| config.acceptRaMinLft = 180; |
| final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| |
| // Create an initial RA and build an APF program |
| byte[] ra = new RaPacketBuilder(200 /* router lifetime */).build(); |
| |
| apfFilter.pretendPacketReceived(ra); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // 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 { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| // configure accept_ra_min_lft |
| final ApfConfiguration config = getDefaultConfig(); |
| config.acceptRaMinLft = 180; |
| final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| |
| // Create an initial RA and build an APF program |
| byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build(); |
| |
| apfFilter.pretendPacketReceived(ra); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // 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 { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| // configure accept_ra_min_lft |
| final ApfConfiguration config = getDefaultConfig(); |
| config.acceptRaMinLft = 180; |
| final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, |
| mNetworkQuirkMetrics, mDependencies); |
| |
| // Create an initial RA and build an APF program |
| byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build(); |
| apfFilter.pretendPacketReceived(ra); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| // 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); |
| apfFilter.pretendPacketReceived(ra); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| assertDrop(program, ra); |
| |
| ra = new RaPacketBuilder(180 /* router lifetime */).build(); |
| assertPass(program, ra); |
| apfFilter.pretendPacketReceived(ra); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| assertDrop(program, ra); |
| |
| ra = new RaPacketBuilder(0 /* router lifetime */).build(); |
| assertPass(program, ra); |
| apfFilter.pretendPacketReceived(ra); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| assertDrop(program, ra); |
| |
| ra = new RaPacketBuilder(180 /* router lifetime */).build(); |
| assertPass(program, ra); |
| apfFilter.pretendPacketReceived(ra); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| assertDrop(program, ra); |
| |
| ra = new RaPacketBuilder(599 /* router lifetime */).build(); |
| assertPass(program, ra); |
| apfFilter.pretendPacketReceived(ra); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| assertDrop(program, ra); |
| |
| ra = new RaPacketBuilder(1800 /* router lifetime */).build(); |
| assertPass(program, ra); |
| apfFilter.pretendPacketReceived(ra); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| 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); |
| } |
| |
| private TestAndroidPacketFilter makeTestApfFilter(ApfConfiguration config, |
| MockIpClientCallback ipClientCallback, boolean isLegacy) throws Exception { |
| final TestAndroidPacketFilter apfFilter; |
| if (isLegacy) { |
| apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, |
| mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); |
| } else { |
| apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics, |
| mDependencies, mClock); |
| } |
| return apfFilter; |
| } |
| |
| // LegacyApfFilter ignores zero lifetime RAs (doesn't update program) but ApfFilter won't. |
| private void verifyUpdateProgramForZeroLifetimeRa(MockIpClientCallback ipClientCallback, |
| boolean isLegacy) { |
| if (isLegacy) { |
| ipClientCallback.assertNoProgramUpdate(); |
| } else { |
| ipClientCallback.assertProgramUpdateAndGet(); |
| } |
| } |
| |
| private void verifyInstallPacketFilterFailure(boolean isLegacy) throws Exception { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(false); |
| final ApfConfiguration config = getDefaultConfig(); |
| final TestAndroidPacketFilter apfFilter = |
| makeTestApfFilter(config, ipClientCallback, isLegacy); |
| verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); |
| verify(mNetworkQuirkMetrics).statsWrite(); |
| reset(mNetworkQuirkMetrics); |
| synchronized (apfFilter) { |
| apfFilter.installNewProgramLocked(); |
| } |
| verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); |
| verify(mNetworkQuirkMetrics).statsWrite(); |
| } |
| |
| @Test |
| public void testInstallPacketFilterFailure() throws Exception { |
| verifyInstallPacketFilterFailure(false /* isLegacy */); |
| } |
| |
| @Test |
| public void testInstallPacketFilterFailure_LegacyApfFilter() throws Exception { |
| verifyInstallPacketFilterFailure(true /* isLegacy */); |
| } |
| |
| private void verifyApfProgramOverSize(boolean isLegacy) throws Exception { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| final ApfConfiguration config = getDefaultConfig(); |
| final ApfCapabilities capabilities = new ApfCapabilities(2, 512, ARPHRD_ETHER); |
| config.apfCapabilities = capabilities; |
| final TestAndroidPacketFilter apfFilter = |
| makeTestApfFilter(config, ipClientCallback, isLegacy); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| final byte[] ra = buildLargeRa(); |
| apfFilter.pretendPacketReceived(ra); |
| // The generated program size will be 529, which is larger than 512 |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE); |
| verify(mNetworkQuirkMetrics).statsWrite(); |
| } |
| |
| @Test |
| public void testApfProgramOverSize() throws Exception { |
| verifyApfProgramOverSize(false /* isLegacy */); |
| } |
| |
| @Test |
| public void testApfProgramOverSize_LegacyApfFilter() throws Exception { |
| verifyApfProgramOverSize(true /* isLegacy */); |
| } |
| |
| private void verifyGenerateApfProgramException(boolean isLegacy) throws Exception { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| final ApfConfiguration config = getDefaultConfig(); |
| final TestAndroidPacketFilter apfFilter; |
| if (isLegacy) { |
| apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, |
| mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, |
| true /* throwsExceptionWhenGeneratesProgram */); |
| } else { |
| apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics, |
| mDependencies, true /* throwsExceptionWhenGeneratesProgram */); |
| } |
| synchronized (apfFilter) { |
| apfFilter.installNewProgramLocked(); |
| } |
| verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION); |
| verify(mNetworkQuirkMetrics).statsWrite(); |
| } |
| |
| @Test |
| public void testGenerateApfProgramException() throws Exception { |
| verifyGenerateApfProgramException(false /* isLegacy */); |
| } |
| |
| @Test |
| public void testGenerateApfProgramException_LegacyApfFilter() throws Exception { |
| verifyGenerateApfProgramException(true /* isLegacy */); |
| } |
| |
| private void verifyApfSessionInfoMetrics(boolean isLegacy) throws Exception { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| final ApfConfiguration config = getDefaultConfig(); |
| final ApfCapabilities capabilities = new ApfCapabilities(4, 4096, ARPHRD_ETHER); |
| config.apfCapabilities = capabilities; |
| final long startTimeMs = 12345; |
| final long durationTimeMs = config.minMetricsSessionDurationMs; |
| doReturn(startTimeMs).when(mClock).elapsedRealtime(); |
| final TestAndroidPacketFilter apfFilter = |
| makeTestApfFilter(config, ipClientCallback, isLegacy); |
| int maxProgramSize = 0; |
| int numProgramUpdated = 0; |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| 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); |
| apfFilter.pretendPacketReceived(ra); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| maxProgramSize = Math.max(maxProgramSize, program.length); |
| numProgramUpdated++; |
| |
| apfFilter.setMulticastFilter(true); |
| // setMulticastFilter will trigger program installation. |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| 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(mClock).elapsedRealtime(); |
| apfFilter.shutdown(); |
| verify(mApfSessionInfoMetrics).setVersion(4); |
| verify(mApfSessionInfoMetrics).setMemorySize(4096); |
| |
| // Verify Counters |
| final Map<Counter, Long> expectedCounters = Map.of(Counter.TOTAL_PACKETS, 2L, |
| Counter.PASSED_IPV6_ICMP, 1L, Counter.DROPPED_IPV4_MULTICAST, 1L); |
| final ArgumentCaptor<Counter> counterCaptor = ArgumentCaptor.forClass(Counter.class); |
| final ArgumentCaptor<Long> valueCaptor = ArgumentCaptor.forClass(Long.class); |
| verify(mApfSessionInfoMetrics, times(expectedCounters.size())).addApfCounter( |
| counterCaptor.capture(), valueCaptor.capture()); |
| final List<Counter> counters = counterCaptor.getAllValues(); |
| final List<Long> values = valueCaptor.getAllValues(); |
| final ArrayMap<Counter, Long> 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 testApfSessionInfoMetrics() throws Exception { |
| verifyApfSessionInfoMetrics(false /* isLegacy */); |
| } |
| |
| @Test |
| public void testApfSessionInfoMetrics_LegacyApfFilter() throws Exception { |
| verifyApfSessionInfoMetrics(true /* isLegacy */); |
| } |
| |
| private void verifyIpClientRaInfoMetrics(boolean isLegacy) throws Exception { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| final ApfConfiguration config = getDefaultConfig(); |
| final long startTimeMs = 12345; |
| final long durationTimeMs = config.minMetricsSessionDurationMs; |
| doReturn(startTimeMs).when(mClock).elapsedRealtime(); |
| final TestAndroidPacketFilter apfFilter = |
| makeTestApfFilter(config, ipClientCallback, isLegacy); |
| byte[] program = ipClientCallback.assertProgramUpdateAndGet(); |
| |
| 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. |
| apfFilter.pretendPacketReceived(ra1.build()); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| apfFilter.pretendPacketReceived(ra2.build()); |
| program = ipClientCallback.assertProgramUpdateAndGet(); |
| apfFilter.pretendPacketReceived(raInvalid.build()); |
| ipClientCallback.assertNoProgramUpdate(); |
| apfFilter.pretendPacketReceived(raZeroRouterLifetime.build()); |
| verifyUpdateProgramForZeroLifetimeRa(ipClientCallback, isLegacy); |
| apfFilter.pretendPacketReceived(raZeroPioValidLifetime.build()); |
| verifyUpdateProgramForZeroLifetimeRa(ipClientCallback, isLegacy); |
| apfFilter.pretendPacketReceived(raZeroRdnssLifetime.build()); |
| verifyUpdateProgramForZeroLifetimeRa(ipClientCallback, isLegacy); |
| apfFilter.pretendPacketReceived(raZeroRioRouteLifetime.build()); |
| verifyUpdateProgramForZeroLifetimeRa(ipClientCallback, isLegacy); |
| |
| // Write metrics data to statsd pipeline when shutdown. |
| doReturn(startTimeMs + durationTimeMs).when(mClock).elapsedRealtime(); |
| apfFilter.shutdown(); |
| |
| // Verify each metric fields in IpClientRaInfoMetrics. |
| if (isLegacy) { |
| // LegacyApfFilter will purge expired RAs before adding new RA. Every time a new zero |
| // lifetime RA is received, zero lifetime RAs except the newly added one will be |
| // cleared, so the number of distinct RAs is 3 (ra1, ra2 and the newly added RA). |
| verify(mIpClientRaInfoMetrics).setMaxNumberOfDistinctRas(3); |
| } else { |
| 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 testIpClientRaInfoMetrics() throws Exception { |
| verifyIpClientRaInfoMetrics(false /* isLegacy */); |
| } |
| |
| @Test |
| public void testIpClientRaInfoMetrics_LegacyApfFilter() throws Exception { |
| verifyIpClientRaInfoMetrics(true /* isLegacy */); |
| } |
| |
| private void verifyNoMetricsWrittenForShortDuration(boolean isLegacy) throws Exception { |
| final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); |
| 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(mClock).elapsedRealtime(); |
| final TestAndroidPacketFilter apfFilter = |
| makeTestApfFilter(config, ipClientCallback, isLegacy); |
| doReturn(startTimeMs + durationTimeMs - 1).when(mClock).elapsedRealtime(); |
| apfFilter.shutdown(); |
| verify(mApfSessionInfoMetrics, never()).statsWrite(); |
| verify(mIpClientRaInfoMetrics, never()).statsWrite(); |
| |
| // Verify metrics data written to statsd for duration greater than or equal to |
| // durationTimeMs. |
| ApfFilter.Clock clock = mock(ApfFilter.Clock.class); |
| doReturn(startTimeMs).when(clock).elapsedRealtime(); |
| final TestAndroidPacketFilter apfFilter2 = new TestApfFilter(mContext, config, |
| ipClientCallback, mNetworkQuirkMetrics, mDependencies, clock); |
| doReturn(startTimeMs + durationTimeMs).when(clock).elapsedRealtime(); |
| apfFilter2.shutdown(); |
| verify(mApfSessionInfoMetrics).statsWrite(); |
| verify(mIpClientRaInfoMetrics).statsWrite(); |
| } |
| |
| @Test |
| public void testNoMetricsWrittenForShortDuration() throws Exception { |
| verifyNoMetricsWrittenForShortDuration(false /* isLegacy */); |
| } |
| |
| @Test |
| public void testNoMetricsWrittenForShortDuration_LegacyApfFilter() throws Exception { |
| verifyNoMetricsWrittenForShortDuration(true /* isLegacy */); |
| } |
| |
| 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_4); |
| ApfV6Generator v6Gen = new ApfV6Generator(); |
| assertEquals(4, deriveApfGeneratorVersion(v4Gen)); |
| assertEquals(6, deriveApfGeneratorVersion(v6Gen)); |
| } |
| |
| @Test |
| public void testFullApfV4ProgramGenerationIPV6() throws IllegalInstructionException { |
| ApfV4Generator gen = new ApfV4Generator(APF_VERSION_4); |
| gen.addLoadImmediate(R1, -4); |
| gen.addLoadData(R0, 0); |
| gen.addAdd(1); |
| gen.addStoreData(R0, 0); |
| gen.addLoad16(R0, 12); |
| gen.addLoadImmediate(R1, -108); |
| gen.addJumpIfR0LessThan(0x600, "LABEL_504"); |
| gen.addLoadImmediate(R1, -112); |
| gen.addJumpIfR0Equals(0x88a2, "LABEL_504"); |
| gen.addJumpIfR0Equals(0x88a4, "LABEL_504"); |
| gen.addJumpIfR0Equals(0x88b8, "LABEL_504"); |
| gen.addJumpIfR0Equals(0x88cd, "LABEL_504"); |
| gen.addJumpIfR0Equals(0x88e1, "LABEL_504"); |
| gen.addJumpIfR0Equals(0x88e3, "LABEL_504"); |
| gen.addJumpIfR0NotEquals(0x806, "LABEL_116"); |
| gen.addLoadImmediate(R0, 14); |
| gen.addLoadImmediate(R1, -36); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), "LABEL_498"); |
| gen.addLoad16(R0, 20); |
| gen.addJumpIfR0Equals(0x1, "LABEL_102"); |
| gen.addLoadImmediate(R1, -40); |
| gen.addJumpIfR0NotEquals(0x2, "LABEL_498"); |
| gen.addLoad32(R0, 28); |
| gen.addLoadImmediate(R1, -116); |
| gen.addJumpIfR0Equals(0x0, "LABEL_504"); |
| gen.addLoadImmediate(R0, 0); |
| gen.addLoadImmediate(R1, -44); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_498"); |
| |
| gen.defineLabel("LABEL_102"); |
| gen.addLoad32(R0, 38); |
| gen.addLoadImmediate(R1, -64); |
| gen.addJumpIfR0Equals(0x0, "LABEL_504"); |
| gen.addLoadImmediate(R1, -8); |
| gen.addJump("LABEL_498"); |
| |
| gen.defineLabel("LABEL_116"); |
| gen.addLoad16(R0, 12); |
| gen.addJumpIfR0NotEquals(0x800, "LABEL_207"); |
| gen.addLoad8(R0, 23); |
| gen.addJumpIfR0NotEquals(0x11, "LABEL_159"); |
| gen.addLoad16(R0, 20); |
| gen.addJumpIfR0AnyBitsSet(0x1fff, "LABEL_159"); |
| gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE); |
| gen.addLoad16Indexed(R0, 16); |
| gen.addJumpIfR0NotEquals(0x44, "LABEL_159"); |
| gen.addLoadImmediate(R0, 50); |
| gen.addAddR1ToR0(); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("e212507c6345"), "LABEL_159"); |
| gen.addLoadImmediate(R1, -12); |
| gen.addJump("LABEL_498"); |
| |
| gen.defineLabel("LABEL_159"); |
| gen.addLoad8(R0, 30); |
| gen.addAnd(240); |
| gen.addLoadImmediate(R1, -84); |
| gen.addJumpIfR0Equals(0xe0, "LABEL_504"); |
| gen.addLoadImmediate(R1, -76); |
| gen.addLoad32(R0, 30); |
| gen.addJumpIfR0Equals(0xffffffff, "LABEL_504"); |
| gen.addLoadImmediate(R1, -24); |
| gen.addLoadImmediate(R0, 0); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_498"); |
| gen.addLoadImmediate(R1, -72); |
| gen.addJump("LABEL_504"); |
| gen.addLoadImmediate(R1, -16); |
| gen.addJump("LABEL_498"); |
| |
| gen.defineLabel("LABEL_207"); |
| gen.addJumpIfR0Equals(0x86dd, "LABEL_231"); |
| gen.addLoadImmediate(R0, 0); |
| gen.addLoadImmediate(R1, -48); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_498"); |
| gen.addLoadImmediate(R1, -56); |
| gen.addJump("LABEL_504"); |
| |
| gen.defineLabel("LABEL_231"); |
| gen.addLoad8(R0, 20); |
| gen.addJumpIfR0Equals(0x3a, "LABEL_249"); |
| gen.addLoadImmediate(R1, -104); |
| gen.addLoad8(R0, 38); |
| gen.addJumpIfR0Equals(0xff, "LABEL_504"); |
| gen.addLoadImmediate(R1, -32); |
| gen.addJump("LABEL_498"); |
| |
| gen.defineLabel("LABEL_249"); |
| gen.addLoad8(R0, 54); |
| gen.addLoadImmediate(R1, -88); |
| gen.addJumpIfR0Equals(0x85, "LABEL_504"); |
| gen.addJumpIfR0NotEquals(0x88, "LABEL_283"); |
| gen.addLoadImmediate(R0, 38); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), "LABEL_283"); |
| gen.addLoadImmediate(R1, -92); |
| gen.addJump("LABEL_504"); |
| |
| gen.defineLabel("LABEL_283"); |
| gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE); |
| gen.addJumpIfR0NotEquals(0xa6, "LABEL_496"); |
| gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS); |
| gen.addJumpIfR0GreaterThan(0x254, "LABEL_496"); |
| gen.addLoadImmediate(R0, 0); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("e212507c6345648788fd6df086dd68"), "LABEL_496"); |
| gen.addLoadImmediate(R0, 18); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("00703afffe800000000000002a0079e10abc1539fe80000000000000e01250fffe7c63458600"), "LABEL_496"); |
| gen.addLoadImmediate(R0, 58); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("4000"), "LABEL_496"); |
| gen.addLoad16(R0, 60); |
| gen.addJumpIfR0LessThan(0x254, "LABEL_496"); |
| gen.addLoadImmediate(R0, 62); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("0000000000000000"), "LABEL_496"); |
| gen.addLoadImmediate(R0, 78); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("19050000"), "LABEL_496"); |
| gen.addLoad32(R0, 82); |
| gen.addJumpIfR0LessThan(0x254, "LABEL_496"); |
| gen.addLoadImmediate(R0, 86); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("2001486048600000000000000000646420014860486000000000000000000064"), "LABEL_496"); |
| gen.addLoadImmediate(R0, 118); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("030440c0"), "LABEL_496"); |
| gen.addLoad32(R0, 122); |
| gen.addJumpIfR0LessThan(0x254, "LABEL_496"); |
| gen.addLoad32(R0, 126); |
| gen.addJumpIfR0LessThan(0x254, "LABEL_496"); |
| gen.addLoadImmediate(R0, 130); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("00000000"), "LABEL_496"); |
| gen.addLoadImmediate(R0, 134); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("2a0079e10abc15390000000000000000"), "LABEL_496"); |
| gen.addLoadImmediate(R1, -60); |
| gen.addJump("LABEL_504"); |
| |
| gen.defineLabel("LABEL_496"); |
| gen.addLoadImmediate(R1, -28); |
| |
| gen.defineLabel("LABEL_498"); |
| gen.addLoadData(R0, 0); |
| gen.addAdd(1); |
| gen.addStoreData(R0, 0); |
| gen.addJump(PASS_LABEL); |
| |
| gen.defineLabel("LABEL_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_4); |
| gen.addLoadImmediate(R1, -4); |
| gen.addLoadData(R0, 0); |
| gen.addAdd(1); |
| gen.addStoreData(R0, 0); |
| gen.addLoad16(R0, 12); |
| gen.addLoadImmediate(R1, -108); |
| gen.addJumpIfR0LessThan(0x600, "LABEL_283"); |
| gen.addLoadImmediate(R1, -112); |
| gen.addJumpIfR0Equals(0x88a2, "LABEL_283"); |
| gen.addJumpIfR0Equals(0x88a4, "LABEL_283"); |
| gen.addJumpIfR0Equals(0x88b8, "LABEL_283"); |
| gen.addJumpIfR0Equals(0x88cd, "LABEL_283"); |
| gen.addJumpIfR0Equals(0x88e1, "LABEL_283"); |
| gen.addJumpIfR0Equals(0x88e3, "LABEL_283"); |
| gen.addJumpIfR0NotEquals(0x806, "LABEL_109"); |
| gen.addLoadImmediate(R0, 14); |
| gen.addLoadImmediate(R1, -36); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), "LABEL_277"); |
| gen.addLoad16(R0, 20); |
| gen.addJumpIfR0Equals(0x1, "LABEL_94"); |
| gen.addLoadImmediate(R1, -40); |
| gen.addJumpIfR0NotEquals(0x2, "LABEL_277"); |
| gen.addLoad32(R0, 28); |
| gen.addLoadImmediate(R1, -116); |
| gen.addJumpIfR0Equals(0x0, "LABEL_283"); |
| gen.addLoadImmediate(R0, 0); |
| gen.addLoadImmediate(R1, -44); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_277"); |
| |
| gen.defineLabel("LABEL_94"); |
| gen.addLoadImmediate(R0, 38); |
| gen.addLoadImmediate(R1, -68); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("c0a801b3"), "LABEL_283"); |
| gen.addLoadImmediate(R1, -8); |
| gen.addJump("LABEL_277"); |
| |
| gen.defineLabel("LABEL_109"); |
| gen.addLoad16(R0, 12); |
| gen.addJumpIfR0NotEquals(0x800, "LABEL_204"); |
| gen.addLoad8(R0, 23); |
| gen.addJumpIfR0NotEquals(0x11, "LABEL_151"); |
| gen.addLoad16(R0, 20); |
| gen.addJumpIfR0AnyBitsSet(0x1fff, "LABEL_151"); |
| gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE); |
| gen.addLoad16Indexed(R0, 16); |
| gen.addJumpIfR0NotEquals(0x44, "LABEL_151"); |
| gen.addLoadImmediate(R0, 50); |
| gen.addAddR1ToR0(); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("f683d58f832b"), "LABEL_151"); |
| gen.addLoadImmediate(R1, -12); |
| gen.addJump("LABEL_277"); |
| |
| gen.defineLabel("LABEL_151"); |
| gen.addLoad8(R0, 30); |
| gen.addAnd(240); |
| gen.addLoadImmediate(R1, -84); |
| gen.addJumpIfR0Equals(0xe0, "LABEL_283"); |
| gen.addLoadImmediate(R1, -76); |
| gen.addLoad32(R0, 30); |
| gen.addJumpIfR0Equals(0xffffffff, "LABEL_283"); |
| gen.addLoadImmediate(R1, -80); |
| gen.addJumpIfR0Equals(0xc0a801ff, "LABEL_283"); |
| gen.addLoadImmediate(R1, -24); |
| gen.addLoadImmediate(R0, 0); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_277"); |
| gen.addLoadImmediate(R1, -72); |
| gen.addJump("LABEL_283"); |
| gen.addLoadImmediate(R1, -16); |
| gen.addJump("LABEL_277"); |
| |
| gen.defineLabel("LABEL_204"); |
| gen.addJumpIfR0Equals(0x86dd, "LABEL_225"); |
| gen.addLoadImmediate(R0, 0); |
| gen.addLoadImmediate(R1, -48); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_277"); |
| gen.addLoadImmediate(R1, -56); |
| gen.addJump("LABEL_283"); |
| |
| gen.defineLabel("LABEL_225"); |
| gen.addLoad8(R0, 20); |
| gen.addJumpIfR0Equals(0x3a, "LABEL_241"); |
| gen.addLoadImmediate(R1, -104); |
| gen.addLoad8(R0, 38); |
| gen.addJumpIfR0Equals(0xff, "LABEL_283"); |
| gen.addLoadImmediate(R1, -32); |
| gen.addJump("LABEL_277"); |
| |
| gen.defineLabel("LABEL_241"); |
| gen.addLoad8(R0, 54); |
| gen.addLoadImmediate(R1, -88); |
| gen.addJumpIfR0Equals(0x85, "LABEL_283"); |
| gen.addJumpIfR0NotEquals(0x88, "LABEL_275"); |
| gen.addLoadImmediate(R0, 38); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), "LABEL_275"); |
| gen.addLoadImmediate(R1, -92); |
| gen.addJump("LABEL_283"); |
| |
| gen.defineLabel("LABEL_275"); |
| gen.addLoadImmediate(R1, -28); |
| |
| gen.defineLabel("LABEL_277"); |
| gen.addLoadData(R0, 0); |
| gen.addAdd(1); |
| gen.addStoreData(R0, 0); |
| gen.addJump(PASS_LABEL); |
| |
| gen.defineLabel("LABEL_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_4); |
| gen.addLoadImmediate(R1, -4); |
| gen.addLoadData(R0, 0); |
| gen.addAdd(1); |
| gen.addStoreData(R0, 0); |
| gen.addLoad16(R0, 12); |
| gen.addLoadImmediate(R1, -108); |
| gen.addJumpIfR0LessThan(0x600, "LABEL_345"); |
| gen.addLoadImmediate(R1, -112); |
| gen.addJumpIfR0Equals(0x88a2, "LABEL_345"); |
| gen.addJumpIfR0Equals(0x88a4, "LABEL_345"); |
| gen.addJumpIfR0Equals(0x88b8, "LABEL_345"); |
| gen.addJumpIfR0Equals(0x88cd, "LABEL_345"); |
| gen.addJumpIfR0Equals(0x88e1, "LABEL_345"); |
| gen.addJumpIfR0Equals(0x88e3, "LABEL_345"); |
| gen.addJumpIfR0NotEquals(0x806, "LABEL_115"); |
| gen.addLoadImmediate(R0, 14); |
| gen.addLoadImmediate(R1, -36); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), "LABEL_339"); |
| gen.addLoad16(R0, 20); |
| gen.addJumpIfR0Equals(0x1, "LABEL_100"); |
| gen.addLoadImmediate(R1, -40); |
| gen.addJumpIfR0NotEquals(0x2, "LABEL_339"); |
| gen.addLoad32(R0, 28); |
| gen.addLoadImmediate(R1, -116); |
| gen.addJumpIfR0Equals(0x0, "LABEL_345"); |
| gen.addLoadImmediate(R0, 0); |
| gen.addLoadImmediate(R1, -44); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_339"); |
| |
| gen.defineLabel("LABEL_100"); |
| gen.addLoadImmediate(R0, 38); |
| gen.addLoadImmediate(R1, -68); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("c0a801be"), "LABEL_345"); |
| gen.addLoadImmediate(R1, -8); |
| gen.addJump("LABEL_339"); |
| |
| gen.defineLabel("LABEL_115"); |
| gen.addLoad16(R0, 12); |
| gen.addJumpIfR0NotEquals(0x800, "LABEL_263"); |
| gen.addLoad8(R0, 23); |
| gen.addJumpIfR0NotEquals(0x11, "LABEL_157"); |
| gen.addLoad16(R0, 20); |
| gen.addJumpIfR0AnyBitsSet(0x1fff, "LABEL_157"); |
| gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE); |
| gen.addLoad16Indexed(R0, 16); |
| gen.addJumpIfR0NotEquals(0x44, "LABEL_157"); |
| gen.addLoadImmediate(R0, 50); |
| gen.addAddR1ToR0(); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ea42226789c0"), "LABEL_157"); |
| gen.addLoadImmediate(R1, -12); |
| gen.addJump("LABEL_339"); |
| |
| gen.defineLabel("LABEL_157"); |
| gen.addLoad8(R0, 30); |
| gen.addAnd(240); |
| gen.addLoadImmediate(R1, -84); |
| gen.addJumpIfR0Equals(0xe0, "LABEL_345"); |
| gen.addLoadImmediate(R1, -76); |
| gen.addLoad32(R0, 30); |
| gen.addJumpIfR0Equals(0xffffffff, "LABEL_345"); |
| gen.addLoadImmediate(R1, -80); |
| gen.addJumpIfR0Equals(0xc0a801ff, "LABEL_345"); |
| gen.addLoad8(R0, 23); |
| gen.addJumpIfR0NotEquals(0x11, "LABEL_243"); |
| gen.addLoadImmediate(R0, 26); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("6b7a1f1fc0a801be"), "LABEL_243"); |
| gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE); |
| gen.addAdd(8); |
| gen.addSwap(); |
| gen.addLoad16(R0, 16); |
| gen.addNeg(R1); |
| gen.addAddR1ToR0(); |
| gen.addJumpIfR0NotEquals(0x1, "LABEL_243"); |
| gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE); |
| gen.addAdd(14); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("1194ceca"), "LABEL_243"); |
| gen.addAdd(8); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff"), "LABEL_243"); |
| gen.addLoadImmediate(R1, -128); |
| gen.addJump("LABEL_345"); |
| |
| gen.defineLabel("LABEL_243"); |
| gen.addLoadImmediate(R1, -24); |
| gen.addLoadImmediate(R0, 0); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_339"); |
| gen.addLoadImmediate(R1, -72); |
| gen.addJump("LABEL_345"); |
| gen.addLoadImmediate(R1, -16); |
| gen.addJump("LABEL_339"); |
| |
| gen.defineLabel("LABEL_263"); |
| gen.addJumpIfR0Equals(0x86dd, "LABEL_284"); |
| gen.addLoadImmediate(R0, 0); |
| gen.addLoadImmediate(R1, -48); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_339"); |
| gen.addLoadImmediate(R1, -56); |
| gen.addJump("LABEL_345"); |
| |
| gen.defineLabel("LABEL_284"); |
| gen.addLoad8(R0, 20); |
| gen.addJumpIfR0Equals(0x0, "LABEL_339"); |
| gen.addJumpIfR0Equals(0x3a, "LABEL_303"); |
| gen.addLoadImmediate(R1, -104); |
| gen.addLoad8(R0, 38); |
| gen.addJumpIfR0Equals(0xff, "LABEL_345"); |
| gen.addLoadImmediate(R1, -32); |
| gen.addJump("LABEL_339"); |
| |
| gen.defineLabel("LABEL_303"); |
| gen.addLoad8(R0, 54); |
| gen.addLoadImmediate(R1, -88); |
| gen.addJumpIfR0Equals(0x85, "LABEL_345"); |
| gen.addJumpIfR0NotEquals(0x88, "LABEL_337"); |
| gen.addLoadImmediate(R0, 38); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), "LABEL_337"); |
| gen.addLoadImmediate(R1, -92); |
| gen.addJump("LABEL_345"); |
| |
| gen.defineLabel("LABEL_337"); |
| gen.addLoadImmediate(R1, -28); |
| |
| gen.defineLabel("LABEL_339"); |
| gen.addLoadData(R0, 0); |
| gen.addAdd(1); |
| gen.addStoreData(R0, 0); |
| gen.addJump(PASS_LABEL); |
| |
| gen.defineLabel("LABEL_345"); |
| 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 = "6bfcb03a01b8120c6b9494014a06006b907c014388a27c013e88a47c013988b87c013488cd7c012f88e17c012a88e384003f08066a0e6bdca40110000600010800060412147a1c016bd884010400021a1c6b8c7c01010000686bd4a2ef06ffffffffffff6a266bbca2ea04c0a801be6bf872e0120c84008d08000a17821e1112149c00171fffab0d2a108210446a3239a20406ea42226789c06bf472b60a1e52f06bac7ab3e06bb41a1e7e000000a6ffffffff6bb07e0000009bc0a801ff0a178230116a1aa223086b7a1f1fc0a801beaa0d3a08aa221210ab2139821501aa0d3a0ea20a041194ceca3a08a20401ff6b8072666be868a25406ffffffffffff6bb872566bf0724c7c001086dd686bd0a23b06ffffffffffff6bc8723d0a147a32007a0b3a6b980a267a2eff6be072240a366ba87a23858218886a26a2040fff02000000000000000000000000006ba472086be4b03a01b87206b03a01b87201"; |
| assertEquals(referenceProgramHexString, programString); |
| } |
| |
| @Test |
| public void testInfiniteLifetimeFullApfV4ProgramGeneration() throws IllegalInstructionException { |
| ApfV4Generator gen = new ApfV4Generator(APF_VERSION_4, true); |
| gen.addLoadCounter(R0, getCounterEnumFromOffset(-8)); |
| gen.addAdd(1); |
| gen.addStoreData(R0, 0); |
| gen.addLoad16(R0, 12); |
| gen.addLoadImmediate(R1, -120); |
| gen.addJumpIfR0LessThan(0x600, gen.mCountAndDropLabel); |
| 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, "LABEL_122"); |
| gen.addLoadImmediate(R0, 14); |
| gen.addCountAndDropIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), getCounterEnumFromOffset(-152)); |
| gen.addLoad16(R0, 20); |
| gen.addJumpIfR0Equals(0x1, "LABEL_104"); |
| gen.addLoadImmediate(R1, -156); |
| gen.addJumpIfR0NotEquals(0x2, gen.mCountAndDropLabel); |
| gen.addLoad32(R0, 28); |
| gen.addLoadImmediate(R1, -128); |
| gen.addJumpIfR0Equals(0x0, gen.mCountAndDropLabel); |
| gen.addLoadImmediate(R0, 0); |
| gen.addLoadImmediate(R1, -56); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), gen.mCountAndPassLabel); |
| |
| gen.defineLabel("LABEL_104"); |
| gen.addLoadImmediate(R0, 38); |
| gen.addLoadImmediate(R1, -80); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("c0a801ec"), gen.mCountAndDropLabel); |
| gen.addLoadImmediate(R1, -20); |
| gen.addJump(gen.mCountAndPassLabel); |
| |
| gen.defineLabel("LABEL_122"); |
| gen.addLoad16(R0, 12); |
| gen.addJumpIfR0NotEquals(0x800, "LABEL_249"); |
| gen.addLoad8(R0, 23); |
| gen.addJumpIfR0NotEquals(0x11, "LABEL_165"); |
| gen.addLoad16(R0, 20); |
| gen.addJumpIfR0AnyBitsSet(0x1fff, "LABEL_165"); |
| gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE); |
| gen.addLoad16Indexed(R0, 16); |
| gen.addJumpIfR0NotEquals(0x44, "LABEL_165"); |
| gen.addLoadImmediate(R0, 50); |
| gen.addAddR1ToR0(); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("7e9046bc7008"), "LABEL_165"); |
| gen.addLoadImmediate(R1, -24); |
| gen.addJump(gen.mCountAndPassLabel); |
| |
| gen.defineLabel("LABEL_165"); |
| gen.addLoad8(R0, 30); |
| gen.addAnd(240); |
| gen.addLoadImmediate(R1, -96); |
| gen.addJumpIfR0Equals(0xe0, gen.mCountAndDropLabel); |
| gen.addLoadImmediate(R1, -88); |
| gen.addLoad32(R0, 30); |
| gen.addJumpIfR0Equals(0xffffffff, gen.mCountAndDropLabel); |
| gen.addLoadImmediate(R1, -92); |
| gen.addJumpIfR0Equals(0xc0a801ff, gen.mCountAndDropLabel); |
| gen.addLoad8(R0, 23); |
| gen.addJumpIfR0NotEquals(0x6, "LABEL_225"); |
| gen.addLoad16(R0, 20); |
| gen.addJumpIfR0AnyBitsSet(0x1fff, "LABEL_225"); |
| gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE); |
| gen.addLoad16Indexed(R0, 16); |
| gen.addJumpIfR0NotEquals(0x7, "LABEL_225"); |
| gen.addLoadImmediate(R1, -148); |
| gen.addJump(gen.mCountAndDropLabel); |
| |
| gen.defineLabel("LABEL_225"); |
| gen.addLoadImmediate(R1, -36); |
| gen.addLoadImmediate(R0, 0); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), gen.mCountAndPassLabel); |
| gen.addLoadImmediate(R1, -84); |
| gen.addJump(gen.mCountAndDropLabel); |
| gen.addLoadImmediate(R1, -28); |
| gen.addJump(gen.mCountAndPassLabel); |
| |
| gen.defineLabel("LABEL_249"); |
| gen.addJumpIfR0Equals(0x86dd, "LABEL_273"); |
| gen.addLoadImmediate(R0, 0); |
| gen.addLoadImmediate(R1, -60); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), gen.mCountAndPassLabel); |
| gen.addLoadImmediate(R1, -68); |
| gen.addJump(gen.mCountAndDropLabel); |
| |
| gen.defineLabel("LABEL_273"); |
| gen.addLoad8(R0, 20); |
| gen.addJumpIfR0Equals(0x0, gen.mCountAndPassLabel); |
| gen.addJumpIfR0Equals(0x3a, "LABEL_297"); |
| gen.addLoadImmediate(R1, -116); |
| gen.addLoad8(R0, 38); |
| gen.addJumpIfR0Equals(0xff, gen.mCountAndDropLabel); |
| gen.addLoadImmediate(R1, -44); |
| gen.addJump(gen.mCountAndPassLabel); |
| |
| gen.defineLabel("LABEL_297"); |
| gen.addLoad8(R0, 54); |
| gen.addLoadImmediate(R1, -100); |
| gen.addJumpIfR0Equals(0x85, gen.mCountAndDropLabel); |
| gen.addJumpIfR0NotEquals(0x88, "LABEL_333"); |
| gen.addLoadImmediate(R0, 38); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), "LABEL_333"); |
| gen.addLoadImmediate(R1, -104); |
| gen.addJump(gen.mCountAndDropLabel); |
| |
| gen.defineLabel("LABEL_333"); |
| gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE); |
| gen.addJumpIfR0NotEquals(0x96, "LABEL_574"); |
| gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS); |
| gen.addJumpIfR0GreaterThan(0x48e, "LABEL_574"); |
| gen.addLoadImmediate(R0, 0); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("7e9046bc700828c68e23672c86dd60"), "LABEL_574"); |
| gen.addLoadImmediate(R0, 18); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("00603afffe800000000000002ac68efffe23672c"), "LABEL_574"); |
| gen.addLoadImmediate(R0, 54); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("8600"), "LABEL_574"); |
| gen.addLoadImmediate(R0, 58); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("40c0"), "LABEL_574"); |
| gen.addLoad16(R0, 60); |
| gen.addJumpIfR0Equals(0x0, "LABEL_574"); |
| gen.addJumpIfR0LessThan(0xb4, "LABEL_421"); |
| gen.addJumpIfR0LessThan(0x91e, "LABEL_574"); |
| gen.addJumpIfR0GreaterThan(0x1b58, "LABEL_574"); |
| |
| gen.defineLabel("LABEL_421"); |
| gen.addLoadImmediate(R0, 62); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("0000000000000000010128c68e23672c05010000000005dc030440c0"), "LABEL_574"); |
| gen.addLoad32(R0, 90); |
| gen.addJumpIfR0Equals(0x0, "LABEL_574"); |
| gen.addJumpIfR0LessThan(0xb4, "LABEL_480"); |
| gen.addJumpIfR0LessThan(0x55555555, "LABEL_574"); |
| gen.addJumpIfR0GreaterThan(0xffffffffL, "LABEL_574"); |
| |
| gen.defineLabel("LABEL_480"); |
| gen.addLoad32(R0, 94); |
| gen.addJumpIfR0LessThan(0x55555555, "LABEL_574"); |
| gen.addJumpIfR0GreaterThan(0xffffffffL, "LABEL_574"); |
| gen.addLoadImmediate(R0, 98); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000000002401fa000480f000000000000000000019030000"), "LABEL_574"); |
| gen.addLoad32(R0, 122); |
| gen.addJumpIfR0Equals(0x0, "LABEL_574"); |
| gen.addJumpIfR0LessThan(0x78, "LABEL_547"); |
| gen.addJumpIfR0LessThan(0x91e, "LABEL_574"); |
| gen.addJumpIfR0GreaterThan(0x1b58, "LABEL_574"); |
| |
| gen.defineLabel("LABEL_547"); |
| gen.addLoadImmediate(R0, 126); |
| gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("2401fa000480f00000000000000000010701"), "LABEL_574"); |
| gen.addLoadImmediate(R1, -72); |
| gen.addJump(gen.mCountAndDropLabel); |
| |
| gen.defineLabel("LABEL_574"); |
| gen.addLoadImmediate(R1, -40); |
| |
| gen.addCountTrampoline(); |
| |
| byte[] program = gen.generate(); |
| final String programString = toHexString(program).toLowerCase(); |
| final String referenceProgramHexString = "6bf8b03a01b8120c6b8894023706006b847c023088a27c022b88a47c022688b87c022188cd7c021c88e17c021788e384004608066a0e6dff68a40202000600010800060412147a1f016dff648401f500021a1c6b807c01ec0000686bc8a401d80006ffffffffffff6a266bb0a401d10004c0a801ec6bec7401c6120c84007808000a17821f1112149c00181fffab0d2a108211446a3239a205067e9046bc70086be874019b0a1e52f06ba07c019600e06ba81a1e7e00000189ffffffff6ba47e0000017ec0a801ff0a1782140612149c000d1fffab0d2a108206076dff6c7401656bdc68a401510006ffffffffffff6bac7401526be47401477c001386dd686bc4a401340006ffffffffffff6bbc7401350a147c012800007a0e3a6b8c0a267c012200ff6bd47401170a366b9c7c011400858218886a26a2040fff02000000000000000000000000006b9872f9aa0e82ec96aa0f8c00e5048e68a2d20f7e9046bc700828c68e23672c86dd606a12a2b91400603afffe800000000000002ac68efffe23672c6a36a2b20286006a3aa2ab0240c0123c7aa600920ab494009e091e8c00991b586a3ea2781c0000000000000000010128c68e23672c05010000000005dc030440c01a5a7a73009212b49600000067555555558e0000005effffffff1a5e9600000053555555558e0000004affffffff6a62a22d18000000002401fa000480f0000000000000000000190300001a7a7a2800920a78940020091e8c001b1b586a7ea204122401fa000480f000000000000000000107016bb872086bd8b03a01b87206b03a01b87201"; |
| assertEquals(referenceProgramHexString, programString); |
| } |
| } |