• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "nativehelper/scoped_primitive_array.h"
28 
29 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
30 #define LOG_TAG "NetworkStackUtils-JNI"
31 
32 // JNI function acting as simply call-through to native APF interpreter.
com_android_server_ApfTest_apfSimulate(JNIEnv * env,jclass,jbyteArray jprogram,jbyteArray jpacket,jbyteArray jdata,jint filter_age)33 static jint com_android_server_ApfTest_apfSimulate(
34         JNIEnv* env, jclass, jbyteArray jprogram, jbyteArray jpacket,
35         jbyteArray jdata, jint filter_age) {
36 
37     ScopedByteArrayRO packet(env, jpacket);
38     uint32_t packet_len = (uint32_t)packet.size();
39     uint32_t program_len = env->GetArrayLength(jprogram);
40     uint32_t data_len = jdata ? env->GetArrayLength(jdata) : 0;
41     std::vector<uint8_t> buf(program_len + data_len, 0);
42 
43     env->GetByteArrayRegion(jprogram, 0, program_len, reinterpret_cast<jbyte*>(buf.data()));
44     if (jdata) {
45         // Merge program and data into a single buffer.
46         env->GetByteArrayRegion(jdata, 0, data_len,
47                                 reinterpret_cast<jbyte*>(buf.data() + program_len));
48     }
49 
50     jint result =
51         accept_packet(buf.data(), program_len, program_len + data_len,
52                         reinterpret_cast<const uint8_t*>(packet.get()), packet_len, filter_age);
53 
54     if (jdata) {
55         env->SetByteArrayRegion(jdata, 0, data_len,
56                                 reinterpret_cast<jbyte*>(buf.data() + program_len));
57     }
58 
59     return result;
60 }
61 
62 class ScopedPcap {
63   public:
ScopedPcap(pcap_t * pcap)64     explicit ScopedPcap(pcap_t* pcap) : pcap_ptr(pcap) {}
~ScopedPcap()65     ~ScopedPcap() {
66         pcap_close(pcap_ptr);
67     }
68 
get() const69     pcap_t* get() const { return pcap_ptr; };
70   private:
71     pcap_t* const pcap_ptr;
72 };
73 
74 class ScopedFILE {
75   public:
ScopedFILE(FILE * fp)76     explicit ScopedFILE(FILE* fp) : file(fp) {}
~ScopedFILE()77     ~ScopedFILE() {
78         fclose(file);
79     }
80 
get() const81     FILE* get() const { return file; };
82   private:
83     FILE* const file;
84 };
85 
throwException(JNIEnv * env,const std::string & error)86 static void throwException(JNIEnv* env, const std::string& error) {
87     jclass newExcCls = env->FindClass("java/lang/IllegalStateException");
88     if (newExcCls == 0) {
89       abort();
90       return;
91     }
92     env->ThrowNew(newExcCls, error.c_str());
93 }
94 
com_android_server_ApfTest_compileToBpf(JNIEnv * env,jclass,jstring jfilter)95 static jstring com_android_server_ApfTest_compileToBpf(JNIEnv* env, jclass, jstring jfilter) {
96     ScopedUtfChars filter(env, jfilter);
97     std::string bpf_string;
98     ScopedPcap pcap(pcap_open_dead(DLT_EN10MB, 65535));
99     if (pcap.get() == NULL) {
100         throwException(env, "pcap_open_dead failed");
101         return NULL;
102     }
103 
104     // Compile "filter" to a BPF program
105     bpf_program bpf;
106     if (pcap_compile(pcap.get(), &bpf, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN)) {
107         throwException(env, "pcap_compile failed");
108         return NULL;
109     }
110 
111     // Translate BPF program to human-readable format
112     const struct bpf_insn* insn = bpf.bf_insns;
113     for (uint32_t i = 0; i < bpf.bf_len; i++) {
114         bpf_string += bpf_image(insn++, i);
115         bpf_string += "\n";
116     }
117 
118     return env->NewStringUTF(bpf_string.c_str());
119 }
120 
com_android_server_ApfTest_compareBpfApf(JNIEnv * env,jclass,jstring jfilter,jstring jpcap_filename,jbyteArray japf_program)121 static jboolean com_android_server_ApfTest_compareBpfApf(JNIEnv* env, jclass, jstring jfilter,
122         jstring jpcap_filename, jbyteArray japf_program) {
123     ScopedUtfChars filter(env, jfilter);
124     ScopedUtfChars pcap_filename(env, jpcap_filename);
125     ScopedByteArrayRO apf_program(env, japf_program);
126 
127     // Open pcap file for BPF filtering
128     ScopedFILE bpf_fp(fopen(pcap_filename.c_str(), "rb"));
129     char pcap_error[PCAP_ERRBUF_SIZE];
130     ScopedPcap bpf_pcap(pcap_fopen_offline(bpf_fp.get(), pcap_error));
131     if (bpf_pcap.get() == NULL) {
132         throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error));
133         return false;
134     }
135 
136     // Open pcap file for APF filtering
137     ScopedFILE apf_fp(fopen(pcap_filename.c_str(), "rb"));
138     ScopedPcap apf_pcap(pcap_fopen_offline(apf_fp.get(), pcap_error));
139     if (apf_pcap.get() == NULL) {
140         throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error));
141         return false;
142     }
143 
144     // Compile "filter" to a BPF program
145     bpf_program bpf;
146     if (pcap_compile(bpf_pcap.get(), &bpf, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN)) {
147         throwException(env, "pcap_compile failed");
148         return false;
149     }
150 
151     // Install BPF filter on bpf_pcap
152     if (pcap_setfilter(bpf_pcap.get(), &bpf)) {
153         throwException(env, "pcap_setfilter failed");
154         return false;
155     }
156 
157     while (1) {
158         pcap_pkthdr bpf_header, apf_header;
159         // Run BPF filter to the next matching packet.
160         const uint8_t* bpf_packet = pcap_next(bpf_pcap.get(), &bpf_header);
161 
162         // Run APF filter to the next matching packet.
163         const uint8_t* apf_packet;
164         do {
165             apf_packet = pcap_next(apf_pcap.get(), &apf_header);
166         } while (apf_packet != NULL && !accept_packet(
167                 reinterpret_cast<uint8_t*>(const_cast<int8_t*>(apf_program.get())),
168                 apf_program.size(), 0 /* data_len */,
169                 apf_packet, apf_header.len, 0 /* filter_age */));
170 
171         // Make sure both filters matched the same packet.
172         if (apf_packet == NULL && bpf_packet == NULL)
173             break;
174         if (apf_packet == NULL || bpf_packet == NULL)
175             return false;
176         if (apf_header.len != bpf_header.len ||
177                 apf_header.ts.tv_sec != bpf_header.ts.tv_sec ||
178                 apf_header.ts.tv_usec != bpf_header.ts.tv_usec ||
179                 memcmp(apf_packet, bpf_packet, apf_header.len))
180             return false;
181     }
182     return true;
183 }
184 
com_android_server_ApfTest_dropsAllPackets(JNIEnv * env,jclass,jbyteArray jprogram,jbyteArray jdata,jstring jpcap_filename)185 static jboolean com_android_server_ApfTest_dropsAllPackets(JNIEnv* env, jclass, jbyteArray jprogram,
186         jbyteArray jdata, jstring jpcap_filename) {
187     ScopedUtfChars pcap_filename(env, jpcap_filename);
188     ScopedByteArrayRO apf_program(env, jprogram);
189     uint32_t apf_program_len = (uint32_t)apf_program.size();
190     uint32_t data_len = env->GetArrayLength(jdata);
191     pcap_pkthdr apf_header;
192     const uint8_t* apf_packet;
193     char pcap_error[PCAP_ERRBUF_SIZE];
194     std::vector<uint8_t> buf(apf_program_len + data_len, 0);
195 
196     // Merge program and data into a single buffer.
197     env->GetByteArrayRegion(jprogram, 0, apf_program_len, reinterpret_cast<jbyte*>(buf.data()));
198     env->GetByteArrayRegion(jdata, 0, data_len,
199                             reinterpret_cast<jbyte*>(buf.data() + apf_program_len));
200 
201     // Open pcap file
202     ScopedFILE apf_fp(fopen(pcap_filename.c_str(), "rb"));
203     ScopedPcap apf_pcap(pcap_fopen_offline(apf_fp.get(), pcap_error));
204 
205     if (apf_pcap.get() == NULL) {
206         throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error));
207         return false;
208     }
209 
210     while ((apf_packet = pcap_next(apf_pcap.get(), &apf_header)) != NULL) {
211         int result = accept_packet(buf.data(), apf_program_len,
212                                     apf_program_len + data_len, apf_packet, apf_header.len, 0);
213 
214         // Return false once packet passes the filter
215         if (result) {
216             env->SetByteArrayRegion(jdata, 0, data_len,
217                                     reinterpret_cast<jbyte*>(buf.data() + apf_program_len));
218             return false;
219          }
220     }
221 
222     env->SetByteArrayRegion(jdata, 0, data_len,
223                             reinterpret_cast<jbyte*>(buf.data() + apf_program_len));
224     return true;
225 }
226 
JNI_OnLoad(JavaVM * vm,void *)227 extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
228     JNIEnv *env;
229     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
230         __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
231         return -1;
232     }
233 
234     static JNINativeMethod gMethods[] = {
235             { "apfSimulate", "([B[B[BI)I",
236                     (void*)com_android_server_ApfTest_apfSimulate },
237             { "compileToBpf", "(Ljava/lang/String;)Ljava/lang/String;",
238                     (void*)com_android_server_ApfTest_compileToBpf },
239             { "compareBpfApf", "(Ljava/lang/String;Ljava/lang/String;[B)Z",
240                     (void*)com_android_server_ApfTest_compareBpfApf },
241             { "dropsAllPackets", "([B[BLjava/lang/String;)Z",
242                     (void*)com_android_server_ApfTest_dropsAllPackets },
243     };
244 
245     jniRegisterNativeMethods(env, "android/net/apf/ApfTest",
246             gMethods, ARRAY_SIZE(gMethods));
247 
248     return JNI_VERSION_1_6;
249 }
250