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 { parseImm(String line, String arg)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 */ convertLine(String line, ApfGenerator gen)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 */ convert(String bpf)309 public static byte[] convert(String bpf) throws IllegalInstructionException { 310 ApfGenerator gen = new ApfGenerator(); 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 */ main(String[] args)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(); 324 while ((line = in.readLine()) != null) convertLine(line, gen); 325 System.out.write(gen.generate()); 326 } 327 } 328