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
17package android.net.apf;
18
19import android.net.apf.ApfGenerator;
20import android.net.apf.ApfGenerator.IllegalInstructionException;
21import android.net.apf.ApfGenerator.Register;
22
23import java.io.BufferedReader;
24import 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 */
38public 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();
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();
324        while ((line = in.readLine()) != null) convertLine(line, gen);
325        System.out.write(gen.generate());
326    }
327}
328