Home | History | Annotate | Download | only in apf
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.net.apf;
     18 
     19 import android.net.apf.ApfGenerator;
     20 import android.net.apf.ApfGenerator.IllegalInstructionException;
     21 import android.net.apf.ApfGenerator.Register;
     22 
     23 import java.io.BufferedReader;
     24 import java.io.InputStreamReader;
     25 
     26 /**
     27  * BPF to APF translator.
     28  *
     29  * Note: This is for testing purposes only and is not guaranteed to support
     30  *       translation of all BPF programs.
     31  *
     32  * Example usage:
     33  *   javac net/java/android/net/apf/ApfGenerator.java \
     34  *         tests/servicestests/src/android/net/apf/Bpf2Apf.java
     35  *   sudo tcpdump -i em1 -d icmp | java -classpath tests/servicestests/src:net/java \
     36  *                                      android.net.apf.Bpf2Apf
     37  */
     38 public class Bpf2Apf {
     39     private static int parseImm(String line, String arg) {
     40         if (!arg.startsWith("#0x")) {
     41             throw new IllegalArgumentException("Unhandled instruction: " + line);
     42         }
     43         final long val_long = Long.parseLong(arg.substring(3), 16);
     44         if (val_long < 0 || val_long > Long.parseLong("ffffffff", 16)) {
     45             throw new IllegalArgumentException("Unhandled instruction: " + line);
     46         }
     47         return new Long((val_long << 32) >> 32).intValue();
     48     }
     49 
     50     /**
     51      * Convert a single line of "tcpdump -d" (human readable BPF program dump) {@code line} into
     52      * APF instruction(s) and append them to {@code gen}. Here's an example line:
     53      * (001) jeq      #0x86dd          jt 2    jf 7
     54      */
     55     private static void convertLine(String line, ApfGenerator gen)
     56             throws IllegalInstructionException {
     57         if (line.indexOf("(") != 0 || line.indexOf(")") != 4 || line.indexOf(" ") != 5) {
     58             throw new IllegalArgumentException("Unhandled instruction: " + line);
     59         }
     60         int label = Integer.parseInt(line.substring(1, 4));
     61         gen.defineLabel(Integer.toString(label));
     62         String opcode = line.substring(6, 10).trim();
     63         String arg = line.substring(15, Math.min(32, line.length())).trim();
     64         switch (opcode) {
     65             case "ld":
     66             case "ldh":
     67             case "ldb":
     68             case "ldx":
     69             case "ldxb":
     70             case "ldxh":
     71                 Register dest = opcode.contains("x") ? Register.R1 : Register.R0;
     72                 if (arg.equals("4*([14]&0xf)")) {
     73                     if (!opcode.equals("ldxb")) {
     74                         throw new IllegalArgumentException("Unhandled instruction: " + line);
     75                     }
     76                     gen.addLoadFromMemory(dest, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
     77                     break;
     78                 }
     79                 if (arg.equals("#pktlen")) {
     80                     if (!opcode.equals("ld")) {
     81                         throw new IllegalArgumentException("Unhandled instruction: " + line);
     82                     }
     83                     gen.addLoadFromMemory(dest, gen.PACKET_SIZE_MEMORY_SLOT);
     84                     break;
     85                 }
     86                 if (arg.startsWith("#0x")) {
     87                     if (!opcode.equals("ld")) {
     88                         throw new IllegalArgumentException("Unhandled instruction: " + line);
     89                     }
     90                     gen.addLoadImmediate(dest, parseImm(line, arg));
     91                     break;
     92                 }
     93                 if (arg.startsWith("M[")) {
     94                     if (!opcode.startsWith("ld")) {
     95                         throw new IllegalArgumentException("Unhandled instruction: " + line);
     96                     }
     97                     int memory_slot = Integer.parseInt(arg.substring(2, arg.length() - 1));
     98                     if (memory_slot < 0 || memory_slot >= gen.MEMORY_SLOTS ||
     99                             // Disallow use of pre-filled slots as BPF programs might
    100                             // wrongfully assume they're initialized to 0.
    101                             (memory_slot >= gen.FIRST_PREFILLED_MEMORY_SLOT &&
    102                                     memory_slot <= gen.LAST_PREFILLED_MEMORY_SLOT)) {
    103                         throw new IllegalArgumentException("Unhandled instruction: " + line);
    104                     }
    105                     gen.addLoadFromMemory(dest, memory_slot);
    106                     break;
    107                 }
    108                 if (arg.startsWith("[x + ")) {
    109                     int offset = Integer.parseInt(arg.substring(5, arg.length() - 1));
    110                     switch (opcode) {
    111                         case "ld":
    112                         case "ldx":
    113                             gen.addLoad32Indexed(dest, offset);
    114                             break;
    115                         case "ldh":
    116                         case "ldxh":
    117                             gen.addLoad16Indexed(dest, offset);
    118                             break;
    119                         case "ldb":
    120                         case "ldxb":
    121                             gen.addLoad8Indexed(dest, offset);
    122                             break;
    123                     }
    124                 } else {
    125                     int offset = Integer.parseInt(arg.substring(1, arg.length() - 1));
    126                     switch (opcode) {
    127                         case "ld":
    128                         case "ldx":
    129                             gen.addLoad32(dest, offset);
    130                             break;
    131                         case "ldh":
    132                         case "ldxh":
    133                             gen.addLoad16(dest, offset);
    134                             break;
    135                         case "ldb":
    136                         case "ldxb":
    137                             gen.addLoad8(dest, offset);
    138                             break;
    139                     }
    140                 }
    141                 break;
    142             case "st":
    143             case "stx":
    144                 Register src = opcode.contains("x") ? Register.R1 : Register.R0;
    145                 if (!arg.startsWith("M[")) {
    146                     throw new IllegalArgumentException("Unhandled instruction: " + line);
    147                 }
    148                 int memory_slot = Integer.parseInt(arg.substring(2, arg.length() - 1));
    149                 if (memory_slot < 0 || memory_slot >= gen.MEMORY_SLOTS ||
    150                         // Disallow overwriting pre-filled slots
    151                         (memory_slot >= gen.FIRST_PREFILLED_MEMORY_SLOT &&
    152                                 memory_slot <= gen.LAST_PREFILLED_MEMORY_SLOT)) {
    153                     throw new IllegalArgumentException("Unhandled instruction: " + line);
    154                 }
    155                 gen.addStoreToMemory(src, memory_slot);
    156                 break;
    157             case "add":
    158             case "and":
    159             case "or":
    160             case "sub":
    161                 if (arg.equals("x")) {
    162                     switch(opcode) {
    163                         case "add":
    164                             gen.addAddR1();
    165                             break;
    166                         case "and":
    167                             gen.addAndR1();
    168                             break;
    169                         case "or":
    170                             gen.addOrR1();
    171                             break;
    172                         case "sub":
    173                             gen.addNeg(Register.R1);
    174                             gen.addAddR1();
    175                             gen.addNeg(Register.R1);
    176                             break;
    177                     }
    178                 } else {
    179                     int imm = parseImm(line, arg);
    180                     switch(opcode) {
    181                         case "add":
    182                             gen.addAdd(imm);
    183                             break;
    184                         case "and":
    185                             gen.addAnd(imm);
    186                             break;
    187                         case "or":
    188                             gen.addOr(imm);
    189                             break;
    190                         case "sub":
    191                             gen.addAdd(-imm);
    192                             break;
    193                     }
    194                 }
    195                 break;
    196             case "jeq":
    197             case "jset":
    198             case "jgt":
    199             case "jge":
    200                 int val = 0;
    201                 boolean reg_compare;
    202                 if (arg.startsWith("x")) {
    203                     reg_compare = true;
    204                 } else {
    205                     reg_compare = false;
    206                     val = parseImm(line, arg);
    207                 }
    208                 int jt_offset = line.indexOf("jt");
    209                 int jf_offset = line.indexOf("jf");
    210                 String true_label = line.substring(jt_offset + 2, jf_offset).trim();
    211                 String false_label = line.substring(jf_offset + 2).trim();
    212                 boolean true_label_is_fallthrough = Integer.parseInt(true_label) == label + 1;
    213                 boolean false_label_is_fallthrough = Integer.parseInt(false_label) == label + 1;
    214                 if (true_label_is_fallthrough && false_label_is_fallthrough)
    215                     break;
    216                 switch (opcode) {
    217                     case "jeq":
    218                         if (!true_label_is_fallthrough) {
    219                             if (reg_compare) {
    220                                 gen.addJumpIfR0EqualsR1(true_label);
    221                             } else {
    222                                 gen.addJumpIfR0Equals(val, true_label);
    223                             }
    224                         }
    225                         if (!false_label_is_fallthrough) {
    226                             if (!true_label_is_fallthrough) {
    227                                 gen.addJump(false_label);
    228                             } else if (reg_compare) {
    229                                 gen.addJumpIfR0NotEqualsR1(false_label);
    230                             } else {
    231                                 gen.addJumpIfR0NotEquals(val, false_label);
    232                             }
    233                         }
    234                         break;
    235                     case "jset":
    236                         if (reg_compare) {
    237                             gen.addJumpIfR0AnyBitsSetR1(true_label);
    238                         } else {
    239                             gen.addJumpIfR0AnyBitsSet(val, true_label);
    240                         }
    241                         if (!false_label_is_fallthrough) {
    242                             gen.addJump(false_label);
    243                         }
    244                         break;
    245                     case "jgt":
    246                         if (!true_label_is_fallthrough ||
    247                                 // We have no less-than-or-equal-to register to register
    248                                 // comparison instruction, so in this case we'll jump
    249                                 // around an unconditional jump.
    250                                 (!false_label_is_fallthrough && reg_compare)) {
    251                             if (reg_compare) {
    252                                 gen.addJumpIfR0GreaterThanR1(true_label);
    253                             } else {
    254                                 gen.addJumpIfR0GreaterThan(val, true_label);
    255                             }
    256                         }
    257                         if (!false_label_is_fallthrough) {
    258                             if (!true_label_is_fallthrough || reg_compare) {
    259                                 gen.addJump(false_label);
    260                             } else {
    261                                 gen.addJumpIfR0LessThan(val + 1, false_label);
    262                             }
    263                         }
    264                         break;
    265                     case "jge":
    266                         if (!false_label_is_fallthrough ||
    267                                 // We have no greater-than-or-equal-to register to register
    268                                 // comparison instruction, so in this case we'll jump
    269                                 // around an unconditional jump.
    270                                 (!true_label_is_fallthrough && reg_compare)) {
    271                             if (reg_compare) {
    272                                 gen.addJumpIfR0LessThanR1(false_label);
    273                             } else {
    274                                 gen.addJumpIfR0LessThan(val, false_label);
    275                             }
    276                         }
    277                         if (!true_label_is_fallthrough) {
    278                             if (!false_label_is_fallthrough || reg_compare) {
    279                                 gen.addJump(true_label);
    280                             } else {
    281                                 gen.addJumpIfR0GreaterThan(val - 1, true_label);
    282                             }
    283                         }
    284                         break;
    285                 }
    286                 break;
    287             case "ret":
    288                 if (arg.equals("#0")) {
    289                     gen.addJump(gen.DROP_LABEL);
    290                 } else {
    291                     gen.addJump(gen.PASS_LABEL);
    292                 }
    293                 break;
    294             case "tax":
    295                 gen.addMove(Register.R1);
    296                 break;
    297             case "txa":
    298                 gen.addMove(Register.R0);
    299                 break;
    300             default:
    301                 throw new IllegalArgumentException("Unhandled instruction: " + line);
    302         }
    303     }
    304 
    305     /**
    306      * Convert the output of "tcpdump -d" (human readable BPF program dump) {@code bpf} into an APF
    307      * program and return it.
    308      */
    309     public static byte[] convert(String bpf) throws IllegalInstructionException {
    310         ApfGenerator gen = new ApfGenerator(3);
    311         for (String line : bpf.split("\\n")) convertLine(line, gen);
    312         return gen.generate();
    313     }
    314 
    315     /**
    316      * Convert the output of "tcpdump -d" (human readable BPF program dump) piped in stdin into an
    317      * APF program and output it via stdout.
    318      */
    319     public static void main(String[] args) throws Exception {
    320         BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    321         String line = null;
    322         StringBuilder responseData = new StringBuilder();
    323         ApfGenerator gen = new ApfGenerator(3);
    324         while ((line = in.readLine()) != null) convertLine(line, gen);
    325         System.out.write(gen.generate());
    326     }
    327 }
    328