/* * Copyright 2018, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> #include <jni.h> #include <pcap.h> #include <stdlib.h> #include <string> #include <utils/Log.h> #include <vector> #include "apf_interpreter.h" #include "nativehelper/scoped_primitive_array.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) // JNI function acting as simply call-through to native APF interpreter. static jint com_android_server_ApfTest_apfSimulate( JNIEnv* env, jclass, jbyteArray jprogram, jbyteArray jpacket, jbyteArray jdata, jint filter_age) { ScopedByteArrayRO packet(env, jpacket); uint32_t packet_len = (uint32_t)packet.size(); uint32_t program_len = env->GetArrayLength(jprogram); uint32_t data_len = jdata ? env->GetArrayLength(jdata) : 0; std::vector<uint8_t> buf(program_len + data_len, 0); env->GetByteArrayRegion(jprogram, 0, program_len, reinterpret_cast<jbyte*>(buf.data())); if (jdata) { // Merge program and data into a single buffer. env->GetByteArrayRegion(jdata, 0, data_len, reinterpret_cast<jbyte*>(buf.data() + program_len)); } jint result = accept_packet(buf.data(), program_len, program_len + data_len, reinterpret_cast<const uint8_t*>(packet.get()), packet_len, filter_age); if (jdata) { env->SetByteArrayRegion(jdata, 0, data_len, reinterpret_cast<jbyte*>(buf.data() + program_len)); } return result; } class ScopedPcap { public: explicit ScopedPcap(pcap_t* pcap) : pcap_ptr(pcap) {} ~ScopedPcap() { pcap_close(pcap_ptr); } pcap_t* get() const { return pcap_ptr; }; private: pcap_t* const pcap_ptr; }; class ScopedFILE { public: explicit ScopedFILE(FILE* fp) : file(fp) {} ~ScopedFILE() { fclose(file); } FILE* get() const { return file; }; private: FILE* const file; }; static void throwException(JNIEnv* env, const std::string& error) { jclass newExcCls = env->FindClass("java/lang/IllegalStateException"); if (newExcCls == 0) { abort(); return; } env->ThrowNew(newExcCls, error.c_str()); } static jstring com_android_server_ApfTest_compileToBpf(JNIEnv* env, jclass, jstring jfilter) { ScopedUtfChars filter(env, jfilter); std::string bpf_string; ScopedPcap pcap(pcap_open_dead(DLT_EN10MB, 65535)); if (pcap.get() == NULL) { throwException(env, "pcap_open_dead failed"); return NULL; } // Compile "filter" to a BPF program bpf_program bpf; if (pcap_compile(pcap.get(), &bpf, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN)) { throwException(env, "pcap_compile failed"); return NULL; } // Translate BPF program to human-readable format const struct bpf_insn* insn = bpf.bf_insns; for (uint32_t i = 0; i < bpf.bf_len; i++) { bpf_string += bpf_image(insn++, i); bpf_string += "\n"; } return env->NewStringUTF(bpf_string.c_str()); } static jboolean com_android_server_ApfTest_compareBpfApf(JNIEnv* env, jclass, jstring jfilter, jstring jpcap_filename, jbyteArray japf_program) { ScopedUtfChars filter(env, jfilter); ScopedUtfChars pcap_filename(env, jpcap_filename); ScopedByteArrayRO apf_program(env, japf_program); // Open pcap file for BPF filtering ScopedFILE bpf_fp(fopen(pcap_filename.c_str(), "rb")); char pcap_error[PCAP_ERRBUF_SIZE]; ScopedPcap bpf_pcap(pcap_fopen_offline(bpf_fp.get(), pcap_error)); if (bpf_pcap.get() == NULL) { throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error)); return false; } // Open pcap file for APF filtering ScopedFILE apf_fp(fopen(pcap_filename.c_str(), "rb")); ScopedPcap apf_pcap(pcap_fopen_offline(apf_fp.get(), pcap_error)); if (apf_pcap.get() == NULL) { throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error)); return false; } // Compile "filter" to a BPF program bpf_program bpf; if (pcap_compile(bpf_pcap.get(), &bpf, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN)) { throwException(env, "pcap_compile failed"); return false; } // Install BPF filter on bpf_pcap if (pcap_setfilter(bpf_pcap.get(), &bpf)) { throwException(env, "pcap_setfilter failed"); return false; } while (1) { pcap_pkthdr bpf_header, apf_header; // Run BPF filter to the next matching packet. const uint8_t* bpf_packet = pcap_next(bpf_pcap.get(), &bpf_header); // Run APF filter to the next matching packet. const uint8_t* apf_packet; do { apf_packet = pcap_next(apf_pcap.get(), &apf_header); } while (apf_packet != NULL && !accept_packet( reinterpret_cast<uint8_t*>(const_cast<int8_t*>(apf_program.get())), apf_program.size(), 0 /* data_len */, apf_packet, apf_header.len, 0 /* filter_age */)); // Make sure both filters matched the same packet. if (apf_packet == NULL && bpf_packet == NULL) break; if (apf_packet == NULL || bpf_packet == NULL) return false; if (apf_header.len != bpf_header.len || apf_header.ts.tv_sec != bpf_header.ts.tv_sec || apf_header.ts.tv_usec != bpf_header.ts.tv_usec || memcmp(apf_packet, bpf_packet, apf_header.len)) return false; } return true; } static jboolean com_android_server_ApfTest_dropsAllPackets(JNIEnv* env, jclass, jbyteArray jprogram, jbyteArray jdata, jstring jpcap_filename) { ScopedUtfChars pcap_filename(env, jpcap_filename); ScopedByteArrayRO apf_program(env, jprogram); uint32_t apf_program_len = (uint32_t)apf_program.size(); uint32_t data_len = env->GetArrayLength(jdata); pcap_pkthdr apf_header; const uint8_t* apf_packet; char pcap_error[PCAP_ERRBUF_SIZE]; std::vector<uint8_t> buf(apf_program_len + data_len, 0); // Merge program and data into a single buffer. env->GetByteArrayRegion(jprogram, 0, apf_program_len, reinterpret_cast<jbyte*>(buf.data())); env->GetByteArrayRegion(jdata, 0, data_len, reinterpret_cast<jbyte*>(buf.data() + apf_program_len)); // Open pcap file ScopedFILE apf_fp(fopen(pcap_filename.c_str(), "rb")); ScopedPcap apf_pcap(pcap_fopen_offline(apf_fp.get(), pcap_error)); if (apf_pcap.get() == NULL) { throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error)); return false; } while ((apf_packet = pcap_next(apf_pcap.get(), &apf_header)) != NULL) { int result = accept_packet(buf.data(), apf_program_len, apf_program_len + data_len, apf_packet, apf_header.len, 0); // Return false once packet passes the filter if (result) { env->SetByteArrayRegion(jdata, 0, data_len, reinterpret_cast<jbyte*>(buf.data() + apf_program_len)); return false; } } env->SetByteArrayRegion(jdata, 0, data_len, reinterpret_cast<jbyte*>(buf.data() + apf_program_len)); return true; } extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { JNIEnv *env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { ALOGE("ERROR: GetEnv failed"); return -1; } static JNINativeMethod gMethods[] = { { "apfSimulate", "([B[B[BI)I", (void*)com_android_server_ApfTest_apfSimulate }, { "compileToBpf", "(Ljava/lang/String;)Ljava/lang/String;", (void*)com_android_server_ApfTest_compileToBpf }, { "compareBpfApf", "(Ljava/lang/String;Ljava/lang/String;[B)Z", (void*)com_android_server_ApfTest_compareBpfApf }, { "dropsAllPackets", "([B[BLjava/lang/String;)Z", (void*)com_android_server_ApfTest_dropsAllPackets }, }; jniRegisterNativeMethods(env, "android/net/apf/ApfTest", gMethods, ARRAY_SIZE(gMethods)); return JNI_VERSION_1_6; }