| /* |
| * Copyright (C) 2015 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 android.net.apf.ApfGenerator; |
| import android.net.apf.ApfGenerator.IllegalInstructionException; |
| import android.net.apf.ApfGenerator.Register; |
| |
| import java.io.BufferedReader; |
| import java.io.InputStreamReader; |
| |
| /** |
| * BPF to APF translator. |
| * |
| * Note: This is for testing purposes only and is not guaranteed to support |
| * translation of all BPF programs. |
| * |
| * Example usage: |
| * javac net/java/android/net/apf/ApfGenerator.java \ |
| * tests/servicestests/src/android/net/apf/Bpf2Apf.java |
| * sudo tcpdump -i em1 -d icmp | java -classpath tests/servicestests/src:net/java \ |
| * android.net.apf.Bpf2Apf |
| */ |
| public class Bpf2Apf { |
| private static int parseImm(String line, String arg) { |
| if (!arg.startsWith("#0x")) { |
| throw new IllegalArgumentException("Unhandled instruction: " + line); |
| } |
| final long val_long = Long.parseLong(arg.substring(3), 16); |
| if (val_long < 0 || val_long > Long.parseLong("ffffffff", 16)) { |
| throw new IllegalArgumentException("Unhandled instruction: " + line); |
| } |
| return new Long((val_long << 32) >> 32).intValue(); |
| } |
| |
| /** |
| * Convert a single line of "tcpdump -d" (human readable BPF program dump) {@code line} into |
| * APF instruction(s) and append them to {@code gen}. Here's an example line: |
| * (001) jeq #0x86dd jt 2 jf 7 |
| */ |
| private static void convertLine(String line, ApfGenerator gen) |
| throws IllegalInstructionException { |
| if (line.indexOf("(") != 0 || line.indexOf(")") != 4 || line.indexOf(" ") != 5) { |
| throw new IllegalArgumentException("Unhandled instruction: " + line); |
| } |
| int label = Integer.parseInt(line.substring(1, 4)); |
| gen.defineLabel(Integer.toString(label)); |
| String opcode = line.substring(6, 10).trim(); |
| String arg = line.substring(15, Math.min(32, line.length())).trim(); |
| switch (opcode) { |
| case "ld": |
| case "ldh": |
| case "ldb": |
| case "ldx": |
| case "ldxb": |
| case "ldxh": |
| Register dest = opcode.contains("x") ? Register.R1 : Register.R0; |
| if (arg.equals("4*([14]&0xf)")) { |
| if (!opcode.equals("ldxb")) { |
| throw new IllegalArgumentException("Unhandled instruction: " + line); |
| } |
| gen.addLoadFromMemory(dest, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); |
| break; |
| } |
| if (arg.equals("#pktlen")) { |
| if (!opcode.equals("ld")) { |
| throw new IllegalArgumentException("Unhandled instruction: " + line); |
| } |
| gen.addLoadFromMemory(dest, gen.PACKET_SIZE_MEMORY_SLOT); |
| break; |
| } |
| if (arg.startsWith("#0x")) { |
| if (!opcode.equals("ld")) { |
| throw new IllegalArgumentException("Unhandled instruction: " + line); |
| } |
| gen.addLoadImmediate(dest, parseImm(line, arg)); |
| break; |
| } |
| if (arg.startsWith("M[")) { |
| if (!opcode.startsWith("ld")) { |
| throw new IllegalArgumentException("Unhandled instruction: " + line); |
| } |
| int memory_slot = Integer.parseInt(arg.substring(2, arg.length() - 1)); |
| if (memory_slot < 0 || memory_slot >= gen.MEMORY_SLOTS || |
| // Disallow use of pre-filled slots as BPF programs might |
| // wrongfully assume they're initialized to 0. |
| (memory_slot >= gen.FIRST_PREFILLED_MEMORY_SLOT && |
| memory_slot <= gen.LAST_PREFILLED_MEMORY_SLOT)) { |
| throw new IllegalArgumentException("Unhandled instruction: " + line); |
| } |
| gen.addLoadFromMemory(dest, memory_slot); |
| break; |
| } |
| if (arg.startsWith("[x + ")) { |
| int offset = Integer.parseInt(arg.substring(5, arg.length() - 1)); |
| switch (opcode) { |
| case "ld": |
| case "ldx": |
| gen.addLoad32Indexed(dest, offset); |
| break; |
| case "ldh": |
| case "ldxh": |
| gen.addLoad16Indexed(dest, offset); |
| break; |
| case "ldb": |
| case "ldxb": |
| gen.addLoad8Indexed(dest, offset); |
| break; |
| } |
| } else { |
| int offset = Integer.parseInt(arg.substring(1, arg.length() - 1)); |
| switch (opcode) { |
| case "ld": |
| case "ldx": |
| gen.addLoad32(dest, offset); |
| break; |
| case "ldh": |
| case "ldxh": |
| gen.addLoad16(dest, offset); |
| break; |
| case "ldb": |
| case "ldxb": |
| gen.addLoad8(dest, offset); |
| break; |
| } |
| } |
| break; |
| case "st": |
| case "stx": |
| Register src = opcode.contains("x") ? Register.R1 : Register.R0; |
| if (!arg.startsWith("M[")) { |
| throw new IllegalArgumentException("Unhandled instruction: " + line); |
| } |
| int memory_slot = Integer.parseInt(arg.substring(2, arg.length() - 1)); |
| if (memory_slot < 0 || memory_slot >= gen.MEMORY_SLOTS || |
| // Disallow overwriting pre-filled slots |
| (memory_slot >= gen.FIRST_PREFILLED_MEMORY_SLOT && |
| memory_slot <= gen.LAST_PREFILLED_MEMORY_SLOT)) { |
| throw new IllegalArgumentException("Unhandled instruction: " + line); |
| } |
| gen.addStoreToMemory(src, memory_slot); |
| break; |
| case "add": |
| case "and": |
| case "or": |
| case "sub": |
| if (arg.equals("x")) { |
| switch(opcode) { |
| case "add": |
| gen.addAddR1(); |
| break; |
| case "and": |
| gen.addAndR1(); |
| break; |
| case "or": |
| gen.addOrR1(); |
| break; |
| case "sub": |
| gen.addNeg(Register.R1); |
| gen.addAddR1(); |
| gen.addNeg(Register.R1); |
| break; |
| } |
| } else { |
| int imm = parseImm(line, arg); |
| switch(opcode) { |
| case "add": |
| gen.addAdd(imm); |
| break; |
| case "and": |
| gen.addAnd(imm); |
| break; |
| case "or": |
| gen.addOr(imm); |
| break; |
| case "sub": |
| gen.addAdd(-imm); |
| break; |
| } |
| } |
| break; |
| case "jeq": |
| case "jset": |
| case "jgt": |
| case "jge": |
| int val = 0; |
| boolean reg_compare; |
| if (arg.startsWith("x")) { |
| reg_compare = true; |
| } else { |
| reg_compare = false; |
| val = parseImm(line, arg); |
| } |
| int jt_offset = line.indexOf("jt"); |
| int jf_offset = line.indexOf("jf"); |
| String true_label = line.substring(jt_offset + 2, jf_offset).trim(); |
| String false_label = line.substring(jf_offset + 2).trim(); |
| boolean true_label_is_fallthrough = Integer.parseInt(true_label) == label + 1; |
| boolean false_label_is_fallthrough = Integer.parseInt(false_label) == label + 1; |
| if (true_label_is_fallthrough && false_label_is_fallthrough) |
| break; |
| switch (opcode) { |
| case "jeq": |
| if (!true_label_is_fallthrough) { |
| if (reg_compare) { |
| gen.addJumpIfR0EqualsR1(true_label); |
| } else { |
| gen.addJumpIfR0Equals(val, true_label); |
| } |
| } |
| if (!false_label_is_fallthrough) { |
| if (!true_label_is_fallthrough) { |
| gen.addJump(false_label); |
| } else if (reg_compare) { |
| gen.addJumpIfR0NotEqualsR1(false_label); |
| } else { |
| gen.addJumpIfR0NotEquals(val, false_label); |
| } |
| } |
| break; |
| case "jset": |
| if (reg_compare) { |
| gen.addJumpIfR0AnyBitsSetR1(true_label); |
| } else { |
| gen.addJumpIfR0AnyBitsSet(val, true_label); |
| } |
| if (!false_label_is_fallthrough) { |
| gen.addJump(false_label); |
| } |
| break; |
| case "jgt": |
| if (!true_label_is_fallthrough || |
| // We have no less-than-or-equal-to register to register |
| // comparison instruction, so in this case we'll jump |
| // around an unconditional jump. |
| (!false_label_is_fallthrough && reg_compare)) { |
| if (reg_compare) { |
| gen.addJumpIfR0GreaterThanR1(true_label); |
| } else { |
| gen.addJumpIfR0GreaterThan(val, true_label); |
| } |
| } |
| if (!false_label_is_fallthrough) { |
| if (!true_label_is_fallthrough || reg_compare) { |
| gen.addJump(false_label); |
| } else { |
| gen.addJumpIfR0LessThan(val + 1, false_label); |
| } |
| } |
| break; |
| case "jge": |
| if (!false_label_is_fallthrough || |
| // We have no greater-than-or-equal-to register to register |
| // comparison instruction, so in this case we'll jump |
| // around an unconditional jump. |
| (!true_label_is_fallthrough && reg_compare)) { |
| if (reg_compare) { |
| gen.addJumpIfR0LessThanR1(false_label); |
| } else { |
| gen.addJumpIfR0LessThan(val, false_label); |
| } |
| } |
| if (!true_label_is_fallthrough) { |
| if (!false_label_is_fallthrough || reg_compare) { |
| gen.addJump(true_label); |
| } else { |
| gen.addJumpIfR0GreaterThan(val - 1, true_label); |
| } |
| } |
| break; |
| } |
| break; |
| case "ret": |
| if (arg.equals("#0")) { |
| gen.addJump(gen.DROP_LABEL); |
| } else { |
| gen.addJump(gen.PASS_LABEL); |
| } |
| break; |
| case "tax": |
| gen.addMove(Register.R1); |
| break; |
| case "txa": |
| gen.addMove(Register.R0); |
| break; |
| default: |
| throw new IllegalArgumentException("Unhandled instruction: " + line); |
| } |
| } |
| |
| /** |
| * Convert the output of "tcpdump -d" (human readable BPF program dump) {@code bpf} into an APF |
| * program and return it. |
| */ |
| public static byte[] convert(String bpf) throws IllegalInstructionException { |
| ApfGenerator gen = new ApfGenerator(); |
| for (String line : bpf.split("\\n")) convertLine(line, gen); |
| return gen.generate(); |
| } |
| |
| /** |
| * Convert the output of "tcpdump -d" (human readable BPF program dump) piped in stdin into an |
| * APF program and output it via stdout. |
| */ |
| public static void main(String[] args) throws Exception { |
| BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); |
| String line = null; |
| StringBuilder responseData = new StringBuilder(); |
| ApfGenerator gen = new ApfGenerator(); |
| while ((line = in.readLine()) != null) convertLine(line, gen); |
| System.out.write(gen.generate()); |
| } |
| } |