blob: 220e54ddef5dcd0aa7e6bed6728110dd5681614b [file] [log] [blame]
Paul Jensend38fb7662016-01-07 23:13:19 -05001/*
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
Paul Jensen9132f342016-04-13 15:00:26 -040017package android.net.apf;
Paul Jensend38fb7662016-01-07 23:13:19 -050018
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 \
Paul Jensen9132f342016-04-13 15:00:26 -040034 * tests/servicestests/src/android/net/apf/Bpf2Apf.java
Paul Jensend38fb7662016-01-07 23:13:19 -050035 * sudo tcpdump -i em1 -d icmp | java -classpath tests/servicestests/src:net/java \
Paul Jensen9132f342016-04-13 15:00:26 -040036 * android.net.apf.Bpf2Apf
Paul Jensend38fb7662016-01-07 23:13:19 -050037 */
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}