1 /*
2 * Copyright 2018, 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 #include <android/log.h>
18 #include <nativehelper/JNIHelp.h>
19 #include <nativehelper/ScopedUtfChars.h>
20 #include <jni.h>
21 #include <pcap.h>
22 #include <stdlib.h>
23 #include <string>
24 #include <vector>
25
26 #include "v4/apf_interpreter.h"
27 #include "disassembler.h"
28 #include "nativehelper/scoped_primitive_array.h"
29
30 #include "next/test_buf_allocator.h"
31
32 #ifdef APF_INTERPRETER_NEXT
33 #include "next/apf_interpreter.h"
34 #endif
35
36 #ifdef APF_INTERPRETER_V6
37 #include "v6/apf_interpreter.h"
38 #endif
39
40 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
41 #define LOG_TAG "ApfJniUtils"
42
run_apf_interpreter(int apf_version,uint32_t * program,uint32_t program_len,uint32_t ram_len,const uint8_t * packet,uint32_t packet_len,uint32_t filter_age)43 static int run_apf_interpreter(int apf_version, uint32_t* program,
44 uint32_t program_len, uint32_t ram_len,
45 const uint8_t* packet, uint32_t packet_len,
46 uint32_t filter_age) {
47 if (apf_version <= 4) {
48 return accept_packet((uint8_t*)program, program_len, ram_len, packet, packet_len,
49 filter_age);
50 } else {
51 return apf_run(nullptr, program, program_len, ram_len, packet, packet_len,
52 filter_age << 14);
53 }
54 }
55
56 // JNI function acting as simply call-through to native APF interpreter.
57 static jint
com_android_server_ApfTest_apfSimulate(JNIEnv * env,jclass,jint apf_version,jbyteArray jprogram,jbyteArray jpacket,jbyteArray jdata,jint filter_age)58 com_android_server_ApfTest_apfSimulate(JNIEnv* env, jclass, jint apf_version,
59 jbyteArray jprogram, jbyteArray jpacket,
60 jbyteArray jdata, jint filter_age) {
61
62 ScopedByteArrayRO packet(env, jpacket);
63 uint32_t packet_len = (uint32_t)packet.size();
64 uint32_t program_len = env->GetArrayLength(jprogram);
65 uint32_t data_len = jdata ? env->GetArrayLength(jdata) : 0;
66 // we need to guarantee room for APFv6's 5 u32 counters (20 bytes)
67 // and APFv6.1's 6 u32 counters (24 bytes)
68 // and we need to make sure ram_len is a multiple of 4 bytes,
69 // so that the counters (which are indexed from the back are aligned.
70 uint32_t ram_len = program_len + data_len;
71 if (apf_version > 4) {
72 ram_len += 3; ram_len &= ~3;
73 uint32_t need = 24; // TODO: (apf_version > 6000) ? 24 : 20;
74 if (data_len < need) ram_len += need;
75 }
76 std::vector<uint32_t> buf((ram_len + 3) / 4, 0);
77 jbyte* jbuf = reinterpret_cast<jbyte*>(buf.data());
78
79 env->GetByteArrayRegion(jprogram, 0, program_len, jbuf);
80 if (jdata) {
81 // Merge program and data into a single buffer.
82 env->GetByteArrayRegion(jdata, 0, data_len, jbuf + ram_len - data_len);
83 }
84
85 jint result = run_apf_interpreter(
86 apf_version, buf.data(), program_len, ram_len,
87 reinterpret_cast<const uint8_t *>(packet.get()), packet_len,
88 filter_age);
89
90 if (jdata) {
91 env->SetByteArrayRegion(jdata, 0, data_len, jbuf + ram_len - data_len);
92 }
93
94 return result;
95 }
96
97 class ScopedPcap {
98 public:
ScopedPcap(pcap_t * pcap)99 explicit ScopedPcap(pcap_t* pcap) : pcap_ptr(pcap) {}
~ScopedPcap()100 ~ScopedPcap() {
101 pcap_close(pcap_ptr);
102 }
103
get() const104 pcap_t* get() const { return pcap_ptr; };
105 private:
106 pcap_t* const pcap_ptr;
107 };
108
109 class ScopedFILE {
110 public:
ScopedFILE(FILE * fp)111 explicit ScopedFILE(FILE* fp) : file(fp) {}
~ScopedFILE()112 ~ScopedFILE() {
113 fclose(file);
114 }
115
get() const116 FILE* get() const { return file; };
117 private:
118 FILE* const file;
119 };
120
throwException(JNIEnv * env,const std::string & error)121 static void throwException(JNIEnv* env, const std::string& error) {
122 jclass newExcCls = env->FindClass("java/lang/IllegalStateException");
123 if (newExcCls == 0) {
124 abort();
125 return;
126 }
127 env->ThrowNew(newExcCls, error.c_str());
128 }
129
com_android_server_ApfTest_compileToBpf(JNIEnv * env,jclass,jstring jfilter)130 static jstring com_android_server_ApfTest_compileToBpf(JNIEnv* env, jclass, jstring jfilter) {
131 ScopedUtfChars filter(env, jfilter);
132 std::string bpf_string;
133 ScopedPcap pcap(pcap_open_dead(DLT_EN10MB, 65535));
134 if (pcap.get() == NULL) {
135 throwException(env, "pcap_open_dead failed");
136 return NULL;
137 }
138
139 // Compile "filter" to a BPF program
140 bpf_program bpf;
141 if (pcap_compile(pcap.get(), &bpf, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN)) {
142 throwException(env, "pcap_compile failed");
143 return NULL;
144 }
145
146 // Translate BPF program to human-readable format
147 const struct bpf_insn* insn = bpf.bf_insns;
148 for (uint32_t i = 0; i < bpf.bf_len; i++) {
149 bpf_string += bpf_image(insn++, i);
150 bpf_string += "\n";
151 }
152
153 return env->NewStringUTF(bpf_string.c_str());
154 }
155
com_android_server_ApfTest_compareBpfApf(JNIEnv * env,jclass,jint apf_version,jstring jfilter,jstring jpcap_filename,jbyteArray japf_program)156 static jboolean com_android_server_ApfTest_compareBpfApf(
157 JNIEnv* env, jclass, jint apf_version, jstring jfilter,
158 jstring jpcap_filename, jbyteArray japf_program) {
159 ScopedUtfChars filter(env, jfilter);
160 ScopedUtfChars pcap_filename(env, jpcap_filename);
161 uint32_t program_len = env->GetArrayLength(japf_program);
162 uint32_t data_len = (apf_version > 4) ? 20 : 0;
163 uint32_t ram_len = program_len + data_len;
164 if (apf_version > 4) { ram_len += 3; ram_len &= ~3; }
165 std::vector<uint32_t> apf_program((ram_len + 3) / 4, 0);
166 env->GetByteArrayRegion(japf_program, 0, program_len,
167 reinterpret_cast<jbyte*>(apf_program.data()));
168
169 // Open pcap file for BPF filtering
170 ScopedFILE bpf_fp(fopen(pcap_filename.c_str(), "rb"));
171 char pcap_error[PCAP_ERRBUF_SIZE];
172 ScopedPcap bpf_pcap(pcap_fopen_offline(bpf_fp.get(), pcap_error));
173 if (bpf_pcap.get() == NULL) {
174 throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error));
175 return false;
176 }
177
178 // Open pcap file for APF filtering
179 ScopedFILE apf_fp(fopen(pcap_filename.c_str(), "rb"));
180 ScopedPcap apf_pcap(pcap_fopen_offline(apf_fp.get(), pcap_error));
181 if (apf_pcap.get() == NULL) {
182 throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error));
183 return false;
184 }
185
186 // Compile "filter" to a BPF program
187 bpf_program bpf;
188 if (pcap_compile(bpf_pcap.get(), &bpf, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN)) {
189 throwException(env, "pcap_compile failed");
190 return false;
191 }
192
193 // Install BPF filter on bpf_pcap
194 if (pcap_setfilter(bpf_pcap.get(), &bpf)) {
195 throwException(env, "pcap_setfilter failed");
196 return false;
197 }
198
199 while (1) {
200 pcap_pkthdr bpf_header, apf_header;
201 // Run BPF filter to the next matching packet.
202 const uint8_t* bpf_packet = pcap_next(bpf_pcap.get(), &bpf_header);
203
204 // Run APF filter to the next matching packet.
205 const uint8_t* apf_packet;
206 do {
207 apf_packet = pcap_next(apf_pcap.get(), &apf_header);
208 } while (apf_packet != NULL && !run_apf_interpreter(apf_version,
209 apf_program.data(), program_len, ram_len,
210 apf_packet, apf_header.len, 0 /* filter_age */));
211
212 // Make sure both filters matched the same packet.
213 if (apf_packet == NULL && bpf_packet == NULL)
214 break;
215 if (apf_packet == NULL || bpf_packet == NULL)
216 return false;
217 if (apf_header.len != bpf_header.len ||
218 apf_header.ts.tv_sec != bpf_header.ts.tv_sec ||
219 apf_header.ts.tv_usec != bpf_header.ts.tv_usec ||
220 memcmp(apf_packet, bpf_packet, apf_header.len))
221 return false;
222 }
223 return true;
224 }
225
com_android_server_ApfTest_dropsAllPackets(JNIEnv * env,jclass,jint apf_version,jbyteArray jprogram,jbyteArray jdata,jstring jpcap_filename)226 static jboolean com_android_server_ApfTest_dropsAllPackets(
227 JNIEnv* env, jclass, jint apf_version, jbyteArray jprogram,
228 jbyteArray jdata, jstring jpcap_filename) {
229 ScopedUtfChars pcap_filename(env, jpcap_filename);
230 ScopedByteArrayRO apf_program(env, jprogram);
231 uint32_t apf_program_len = (uint32_t)apf_program.size();
232 uint32_t data_len = env->GetArrayLength(jdata);
233 uint32_t ram_len = apf_program_len + data_len;
234 if (apf_version > 4) {
235 ram_len += 3; ram_len &= ~3;
236 if (data_len < 20) ram_len += 20;
237 }
238 pcap_pkthdr apf_header;
239 const uint8_t* apf_packet;
240 char pcap_error[PCAP_ERRBUF_SIZE];
241 std::vector<uint32_t> buf((ram_len + 3) / 4, 0);
242 jbyte* jbuf = reinterpret_cast<jbyte*>(buf.data());
243
244 // Merge program and data into a single buffer.
245 env->GetByteArrayRegion(jprogram, 0, apf_program_len, jbuf);
246 env->GetByteArrayRegion(jdata, 0, data_len, jbuf + ram_len - data_len);
247
248 // Open pcap file
249 ScopedFILE apf_fp(fopen(pcap_filename.c_str(), "rb"));
250 ScopedPcap apf_pcap(pcap_fopen_offline(apf_fp.get(), pcap_error));
251
252 if (apf_pcap.get() == NULL) {
253 throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error));
254 return false;
255 }
256
257 while ((apf_packet = pcap_next(apf_pcap.get(), &apf_header)) != NULL) {
258 int result = run_apf_interpreter(
259 apf_version, buf.data(), apf_program_len, ram_len, apf_packet, apf_header.len, 0);
260
261 // Return false once packet passes the filter
262 if (result) {
263 env->SetByteArrayRegion(jdata, 0, data_len, jbuf + ram_len - data_len);
264 return false;
265 }
266 }
267
268 env->SetByteArrayRegion(jdata, 0, data_len, jbuf + ram_len - data_len);
269 return true;
270 }
271
com_android_server_ApfTest_disassembleApf(JNIEnv * env,jclass,jbyteArray jprogram)272 static jobjectArray com_android_server_ApfTest_disassembleApf(
273 JNIEnv* env, jclass, jbyteArray jprogram) {
274 uint32_t program_len = env->GetArrayLength(jprogram);
275 std::vector<uint8_t> buf(program_len, 0);
276
277 env->GetByteArrayRegion(jprogram, 0, program_len,
278 reinterpret_cast<jbyte*>(buf.data()));
279 std::vector<std::string> disassemble_output;
280 for (uint32_t pc = 0; pc < program_len;) {
281 // TODO: Implement proper selection of APFv4 or APFv6 code for
282 // disassembly.
283 const disas_ret ret =
284 apf_disassemble(buf.data(), program_len, &pc, true /* is_v6*/);
285 disassemble_output.emplace_back(ret.content);
286 }
287 jclass stringClass = env->FindClass("java/lang/String");
288 jobjectArray disassembleOutput =
289 env->NewObjectArray(disassemble_output.size(), stringClass, nullptr);
290
291 for (jsize i = 0; i < (jsize) disassemble_output.size(); i++) {
292 jstring j_disassemble_output =
293 env->NewStringUTF(disassemble_output[i].c_str());
294 env->SetObjectArrayElement(disassembleOutput, i, j_disassemble_output);
295 env->DeleteLocalRef(j_disassemble_output);
296 }
297
298 return disassembleOutput;
299 }
300
com_android_server_ApfTest_getAllTransmittedPackets(JNIEnv * env,jclass)301 static jobjectArray com_android_server_ApfTest_getAllTransmittedPackets(JNIEnv* env,
302 jclass) {
303 jclass arrayListClass = env->FindClass("java/util/ArrayList");
304 jmethodID arrayListConstructor = env->GetMethodID(arrayListClass, "<init>", "()V");
305 jobject arrayList = env->NewObject(arrayListClass, arrayListConstructor);
306
307 jmethodID addMethod = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
308 packet_buffer *ptr = head;
309 while (ptr) {
310 jbyteArray jdata = env->NewByteArray((jint) ptr->len);
311 if (jdata == NULL) {
312 return static_cast<jobjectArray>(arrayList);
313 }
314
315 env->SetByteArrayRegion(jdata, 0, (jint) ptr->len,
316 reinterpret_cast<jbyte*>(ptr->data));
317 env->CallBooleanMethod(arrayList, addMethod, jdata);
318 env->DeleteLocalRef(jdata);
319
320 ptr = ptr->next;
321 }
322
323 env->DeleteLocalRef(arrayListClass);
324 return static_cast<jobjectArray>(arrayList);
325 }
326
com_android_server_ApfTest_resetTransmittedPacketMemory(JNIEnv,jclass)327 void com_android_server_ApfTest_resetTransmittedPacketMemory(JNIEnv, jclass) {
328 packet_buffer* current = head;
329 packet_buffer* tmp = NULL;
330 while (current) {
331 tmp = current->next;
332 free(current);
333 current = tmp;
334 }
335
336 head = NULL;
337 tail = NULL;
338 }
339
JNI_OnLoad(JavaVM * vm,void *)340 extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
341 JNIEnv *env;
342 if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
343 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
344 return -1;
345 }
346
347 static JNINativeMethod gMethods[] = {
348 { "apfSimulate", "(I[B[B[BI)I",
349 (void*)com_android_server_ApfTest_apfSimulate },
350 { "compileToBpf", "(Ljava/lang/String;)Ljava/lang/String;",
351 (void*)com_android_server_ApfTest_compileToBpf },
352 { "compareBpfApf", "(ILjava/lang/String;Ljava/lang/String;[B)Z",
353 (void*)com_android_server_ApfTest_compareBpfApf },
354 { "dropsAllPackets", "(I[B[BLjava/lang/String;)Z",
355 (void*)com_android_server_ApfTest_dropsAllPackets },
356 { "disassembleApf", "([B)[Ljava/lang/String;",
357 (void*)com_android_server_ApfTest_disassembleApf },
358 { "getAllTransmittedPackets", "()Ljava/util/List;",
359 (void*)com_android_server_ApfTest_getAllTransmittedPackets },
360 { "resetTransmittedPacketMemory", "()V",
361 (void*)com_android_server_ApfTest_resetTransmittedPacketMemory },
362 };
363
364 jniRegisterNativeMethods(env, "android/net/apf/ApfJniUtils",
365 gMethods, ARRAY_SIZE(gMethods));
366
367 return JNI_VERSION_1_6;
368 }
369