blob: 9ba27cbb698025d689fff3af6c1e017eaac9fc8e [file] [log] [blame]
/*
* 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 com.android.server;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import com.android.frameworks.servicestests.R;
import android.net.apf.ApfGenerator;
import android.net.apf.ApfGenerator.IllegalInstructionException;
import android.net.apf.ApfGenerator.Register;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import libcore.io.IoUtils;
import libcore.io.Streams;
/**
* Tests for APF program generator and interpreter.
*
* Build, install and run with:
* runtest frameworks-services -c com.android.server.ApfTest
*/
public class ApfTest extends AndroidTestCase {
@Override
public void setUp() throws Exception {
super.setUp();
// Load up native shared library containing APF interpreter exposed via JNI.
System.loadLibrary("servicestestsjni");
}
// Expected return codes from APF interpreter.
private final static int PASS = 1;
private final static int DROP = 0;
// Interpreter will just accept packets without link layer headers, so pad fake packet to at
// least the minimum packet size.
private final static int MIN_PKT_SIZE = 15;
private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) {
assertEquals(expected, apfSimulate(program, packet, filterAge));
}
private void assertPass(byte[] program, byte[] packet, int filterAge) {
assertVerdict(PASS, program, packet, filterAge);
}
private void assertDrop(byte[] program, byte[] packet, int filterAge) {
assertVerdict(DROP, program, packet, filterAge);
}
private void assertVerdict(int expected, ApfGenerator gen, byte[] packet, int filterAge)
throws IllegalInstructionException {
assertEquals(expected, apfSimulate(gen.generate(), packet, filterAge));
}
private void assertPass(ApfGenerator gen, byte[] packet, int filterAge)
throws IllegalInstructionException {
assertVerdict(PASS, gen, packet, filterAge);
}
private void assertDrop(ApfGenerator gen, byte[] packet, int filterAge)
throws IllegalInstructionException {
assertVerdict(DROP, gen, packet, filterAge);
}
private void assertPass(ApfGenerator gen)
throws IllegalInstructionException {
assertVerdict(PASS, gen, new byte[MIN_PKT_SIZE], 0);
}
private void assertDrop(ApfGenerator gen)
throws IllegalInstructionException {
assertVerdict(DROP, gen, new byte[MIN_PKT_SIZE], 0);
}
/**
* Test each instruction by generating a program containing the instruction,
* generating bytecode for that program and running it through the
* interpreter to verify it functions correctly.
*/
@LargeTest
public void testApfInstructions() throws IllegalInstructionException {
// Empty program should pass because having the program counter reach the
// location immediately after the program indicates the packet should be
// passed to the AP.
ApfGenerator gen = new ApfGenerator();
assertPass(gen);
// Test jumping to pass label.
gen = new ApfGenerator();
gen.addJump(gen.PASS_LABEL);
byte[] program = gen.generate();
assertEquals(1, program.length);
assertEquals((14 << 3) | (0 << 1) | 0, program[0]);
assertPass(program, new byte[MIN_PKT_SIZE], 0);
// Test jumping to drop label.
gen = new ApfGenerator();
gen.addJump(gen.DROP_LABEL);
program = gen.generate();
assertEquals(2, program.length);
assertEquals((14 << 3) | (1 << 1) | 0, program[0]);
assertEquals(1, program[1]);
assertDrop(program, new byte[15], 15);
// Test jumping if equal to 0.
gen = new ApfGenerator();
gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
assertDrop(gen);
// Test jumping if not equal to 0.
gen = new ApfGenerator();
gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
assertPass(gen);
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1);
gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
assertDrop(gen);
// Test jumping if registers equal.
gen = new ApfGenerator();
gen.addJumpIfR0EqualsR1(gen.DROP_LABEL);
assertDrop(gen);
// Test jumping if registers not equal.
gen = new ApfGenerator();
gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
assertPass(gen);
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1);
gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
assertDrop(gen);
// Test load immediate.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1234567890);
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
assertDrop(gen);
// Test add.
gen = new ApfGenerator();
gen.addAdd(1234567890);
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
assertDrop(gen);
// Test subtract.
gen = new ApfGenerator();
gen.addAdd(-1234567890);
gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
assertDrop(gen);
// Test or.
gen = new ApfGenerator();
gen.addOr(1234567890);
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
assertDrop(gen);
// Test and.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1234567890);
gen.addAnd(123456789);
gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
assertDrop(gen);
// Test left shift.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1234567890);
gen.addLeftShift(1);
gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
assertDrop(gen);
// Test right shift.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1234567890);
gen.addRightShift(1);
gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
assertDrop(gen);
// Test multiply.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1234567890);
gen.addMul(2);
gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
assertDrop(gen);
// Test divide.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1234567890);
gen.addDiv(2);
gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
assertDrop(gen);
// Test divide by zero.
gen = new ApfGenerator();
gen.addDiv(0);
gen.addJump(gen.DROP_LABEL);
assertPass(gen);
// Test add.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R1, 1234567890);
gen.addAddR1();
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
assertDrop(gen);
// Test subtract.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R1, -1234567890);
gen.addAddR1();
gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
assertDrop(gen);
// Test or.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R1, 1234567890);
gen.addOrR1();
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
assertDrop(gen);
// Test and.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1234567890);
gen.addLoadImmediate(Register.R1, 123456789);
gen.addAndR1();
gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
assertDrop(gen);
// Test left shift.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1234567890);
gen.addLoadImmediate(Register.R1, 1);
gen.addLeftShiftR1();
gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
assertDrop(gen);
// Test right shift.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1234567890);
gen.addLoadImmediate(Register.R1, -1);
gen.addLeftShiftR1();
gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
assertDrop(gen);
// Test multiply.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1234567890);
gen.addLoadImmediate(Register.R1, 2);
gen.addMulR1();
gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
assertDrop(gen);
// Test divide.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1234567890);
gen.addLoadImmediate(Register.R1, 2);
gen.addDivR1();
gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
assertDrop(gen);
// Test divide by zero.
gen = new ApfGenerator();
gen.addDivR1();
gen.addJump(gen.DROP_LABEL);
assertPass(gen);
// Test byte load.
gen = new ApfGenerator();
gen.addLoad8(Register.R0, 1);
gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
// Test out of bounds load.
gen = new ApfGenerator();
gen.addLoad8(Register.R0, 16);
gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
// Test half-word load.
gen = new ApfGenerator();
gen.addLoad16(Register.R0, 1);
gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
// Test word load.
gen = new ApfGenerator();
gen.addLoad32(Register.R0, 1);
gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
// Test byte indexed load.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R1, 1);
gen.addLoad8Indexed(Register.R0, 0);
gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
// Test out of bounds indexed load.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R1, 8);
gen.addLoad8Indexed(Register.R0, 8);
gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
// Test half-word indexed load.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R1, 1);
gen.addLoad16Indexed(Register.R0, 0);
gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
// Test word indexed load.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R1, 1);
gen.addLoad32Indexed(Register.R0, 0);
gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
// Test jumping if greater than.
gen = new ApfGenerator();
gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
assertPass(gen);
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1);
gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
assertDrop(gen);
// Test jumping if less than.
gen = new ApfGenerator();
gen.addJumpIfR0LessThan(0, gen.DROP_LABEL);
assertPass(gen);
gen = new ApfGenerator();
gen.addJumpIfR0LessThan(1, gen.DROP_LABEL);
assertDrop(gen);
// Test jumping if any bits set.
gen = new ApfGenerator();
gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
assertPass(gen);
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1);
gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
assertDrop(gen);
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 3);
gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
assertDrop(gen);
// Test jumping if register greater than.
gen = new ApfGenerator();
gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
assertPass(gen);
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 2);
gen.addLoadImmediate(Register.R1, 1);
gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
assertDrop(gen);
// Test jumping if register less than.
gen = new ApfGenerator();
gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
assertPass(gen);
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R1, 1);
gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
assertDrop(gen);
// Test jumping if any bits set in register.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R1, 3);
gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
assertPass(gen);
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R1, 3);
gen.addLoadImmediate(Register.R0, 1);
gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
assertDrop(gen);
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R1, 3);
gen.addLoadImmediate(Register.R0, 3);
gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
assertDrop(gen);
// Test load from memory.
gen = new ApfGenerator();
gen.addLoadFromMemory(Register.R0, 0);
gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
assertDrop(gen);
// Test store to memory.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R1, 1234567890);
gen.addStoreToMemory(Register.R1, 12);
gen.addLoadFromMemory(Register.R0, 12);
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
assertDrop(gen);
// Test filter age pre-filled memory.
gen = new ApfGenerator();
gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
assertDrop(gen, new byte[MIN_PKT_SIZE], 1234567890);
// Test packet size pre-filled memory.
gen = new ApfGenerator();
gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
gen.addJumpIfR0Equals(MIN_PKT_SIZE, gen.DROP_LABEL);
assertDrop(gen);
// Test IPv4 header size pre-filled memory.
gen = new ApfGenerator();
gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
gen.addJumpIfR0Equals(20, gen.DROP_LABEL);
assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x45}, 0);
// Test not.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1234567890);
gen.addNot(Register.R0);
gen.addJumpIfR0Equals(~1234567890, gen.DROP_LABEL);
assertDrop(gen);
// Test negate.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1234567890);
gen.addNeg(Register.R0);
gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
assertDrop(gen);
// Test move.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R1, 1234567890);
gen.addMove(Register.R0);
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
assertDrop(gen);
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1234567890);
gen.addMove(Register.R1);
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
assertDrop(gen);
// Test swap.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R1, 1234567890);
gen.addSwap();
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
assertDrop(gen);
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1234567890);
gen.addSwap();
gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
assertDrop(gen);
// Test jump if bytes not equal.
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1);
gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
program = gen.generate();
assertEquals(6, program.length);
assertEquals((13 << 3) | (1 << 1) | 0, program[0]);
assertEquals(1, program[1]);
assertEquals(((20 << 3) | (1 << 1) | 0) - 256, program[2]);
assertEquals(1, program[3]);
assertEquals(1, program[4]);
assertEquals(123, program[5]);
assertDrop(program, new byte[MIN_PKT_SIZE], 0);
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1);
gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
byte[] packet123 = new byte[]{0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
assertPass(gen, packet123, 0);
gen = new ApfGenerator();
gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
assertDrop(gen, packet123, 0);
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1);
gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
byte[] packet12345 = new byte[]{0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
assertDrop(gen, packet12345, 0);
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1);
gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL);
assertPass(gen, packet12345, 0);
}
/**
* 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.
*/
@LargeTest
public void testApfAgainstBpf() throws Exception {
String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
"arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
"arp or icmp6 or portrange 53-54", "portrange 53-54 or portrange 100-50000",
"tcp[tcpflags] & (tcp-ack|tcp-fin) != 0 and (ip[2:2] > 57 or icmp)" };
String pcap_filename = stageFile(R.raw.apf);
for (String tcpdump_filter : tcpdump_filters) {
byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter));
assertTrue("Failed to match for filter: " + tcpdump_filter,
compareBpfApf(tcpdump_filter, pcap_filename, apf_program));
}
}
/**
* 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(getContext().getFilesDir(), "staged_file");
new File(file.getParent()).mkdirs();
InputStream in = null;
OutputStream out = null;
try {
in = 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();
}
/**
* Call the APF interpreter the run {@code program} on {@code packet} pretending the
* filter was installed {@code filter_age} seconds ago.
*/
private native static int apfSimulate(byte[] program, byte[] packet, int filter_age);
/**
* Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF
* prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d".
*/
private native static String compileToBpf(String filter);
/**
* Open packet capture file {@code pcap_filename} and filter the packets using tcpdump
* human-readable filter (e.g. "icmp" or "tcp port 54") compiled to a BPF program and
* at the same time using APF program {@code apf_program}. Return {@code true} if
* both APF and BPF programs filter out exactly the same packets.
*/
private native static boolean compareBpfApf(String filter, String pcap_filename,
byte[] apf_program);
}