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(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      */
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(3);
324         while ((line = in.readLine()) != null) convertLine(line, gen);
325         System.out.write(gen.generate());
326     }
327 }
328