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