1/*
2 * Copyright 2016, 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 <JNIHelp.h>
18#include <ScopedUtfChars.h>
19#include <jni.h>
20#include <pcap.h>
21#include <stdlib.h>
22#include <string>
23#include <utils/Log.h>
24
25#include "apf_interpreter.h"
26
27#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
28
29// JNI function acting as simply call-through to native APF interpreter.
30static jint com_android_server_ApfTest_apfSimulate(
31        JNIEnv* env, jclass, jbyteArray program, jbyteArray packet, jint filter_age) {
32    return accept_packet(
33            (uint8_t*)env->GetByteArrayElements(program, NULL),
34            env->GetArrayLength(program),
35            (uint8_t*)env->GetByteArrayElements(packet, NULL),
36            env->GetArrayLength(packet),
37            filter_age);
38}
39
40class ScopedPcap {
41  public:
42    explicit ScopedPcap(pcap_t* pcap) : pcap_ptr(pcap) {}
43    ~ScopedPcap() {
44        pcap_close(pcap_ptr);
45    }
46
47    pcap_t* get() const { return pcap_ptr; };
48  private:
49    pcap_t* const pcap_ptr;
50};
51
52class ScopedFILE {
53  public:
54    explicit ScopedFILE(FILE* fp) : file(fp) {}
55    ~ScopedFILE() {
56        fclose(file);
57    }
58
59    FILE* get() const { return file; };
60  private:
61    FILE* const file;
62};
63
64static void throwException(JNIEnv* env, const std::string& error) {
65    jclass newExcCls = env->FindClass("java/lang/IllegalStateException");
66    if (newExcCls == 0) {
67      abort();
68      return;
69    }
70    env->ThrowNew(newExcCls, error.c_str());
71}
72
73static jstring com_android_server_ApfTest_compileToBpf(JNIEnv* env, jclass, jstring jfilter) {
74    ScopedUtfChars filter(env, jfilter);
75    std::string bpf_string;
76    ScopedPcap pcap(pcap_open_dead(DLT_EN10MB, 65535));
77    if (pcap.get() == NULL) {
78        throwException(env, "pcap_open_dead failed");
79        return NULL;
80    }
81
82    // Compile "filter" to a BPF program
83    bpf_program bpf;
84    if (pcap_compile(pcap.get(), &bpf, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN)) {
85        throwException(env, "pcap_compile failed");
86        return NULL;
87    }
88
89    // Translate BPF program to human-readable format
90    const struct bpf_insn* insn = bpf.bf_insns;
91    for (uint32_t i = 0; i < bpf.bf_len; i++) {
92        bpf_string += bpf_image(insn++, i);
93        bpf_string += "\n";
94    }
95
96    return env->NewStringUTF(bpf_string.c_str());
97}
98
99static jboolean com_android_server_ApfTest_compareBpfApf(JNIEnv* env, jclass, jstring jfilter,
100        jstring jpcap_filename, jbyteArray japf_program) {
101    ScopedUtfChars filter(env, jfilter);
102    ScopedUtfChars pcap_filename(env, jpcap_filename);
103    const uint8_t* apf_program = (uint8_t*)env->GetByteArrayElements(japf_program, NULL);
104    const uint32_t apf_program_len = env->GetArrayLength(japf_program);
105
106    // Open pcap file for BPF filtering
107    ScopedFILE bpf_fp(fopen(pcap_filename.c_str(), "rb"));
108    char pcap_error[PCAP_ERRBUF_SIZE];
109    ScopedPcap bpf_pcap(pcap_fopen_offline(bpf_fp.get(), pcap_error));
110    if (bpf_pcap.get() == NULL) {
111        throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error));
112        return false;
113    }
114
115    // Open pcap file for APF filtering
116    ScopedFILE apf_fp(fopen(pcap_filename.c_str(), "rb"));
117    ScopedPcap apf_pcap(pcap_fopen_offline(apf_fp.get(), pcap_error));
118    if (apf_pcap.get() == NULL) {
119        throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error));
120        return false;
121    }
122
123    // Compile "filter" to a BPF program
124    bpf_program bpf;
125    if (pcap_compile(bpf_pcap.get(), &bpf, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN)) {
126        throwException(env, "pcap_compile failed");
127        return false;
128    }
129
130    // Install BPF filter on bpf_pcap
131    if (pcap_setfilter(bpf_pcap.get(), &bpf)) {
132        throwException(env, "pcap_setfilter failed");
133        return false;
134    }
135
136    while (1) {
137        pcap_pkthdr bpf_header, apf_header;
138        // Run BPF filter to the next matching packet.
139        const uint8_t* bpf_packet = pcap_next(bpf_pcap.get(), &bpf_header);
140
141        // Run APF filter to the next matching packet.
142        const uint8_t* apf_packet;
143        do {
144            apf_packet = pcap_next(apf_pcap.get(), &apf_header);
145        } while (apf_packet != NULL && !accept_packet(
146                apf_program, apf_program_len, apf_packet, apf_header.len, 0));
147
148        // Make sure both filters matched the same packet.
149        if (apf_packet == NULL && bpf_packet == NULL)
150             break;
151        if (apf_packet == NULL || bpf_packet == NULL)
152             return false;
153        if (apf_header.len != bpf_header.len ||
154                apf_header.ts.tv_sec != bpf_header.ts.tv_sec ||
155                apf_header.ts.tv_usec != bpf_header.ts.tv_usec ||
156                memcmp(apf_packet, bpf_packet, apf_header.len))
157            return false;
158    }
159    return true;
160}
161
162extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
163    JNIEnv *env;
164    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
165        ALOGE("ERROR: GetEnv failed");
166        return -1;
167    }
168
169    static JNINativeMethod gMethods[] = {
170            { "apfSimulate", "([B[BI)I",
171                    (void*)com_android_server_ApfTest_apfSimulate },
172            { "compileToBpf", "(Ljava/lang/String;)Ljava/lang/String;",
173                    (void*)com_android_server_ApfTest_compileToBpf },
174            { "compareBpfApf", "(Ljava/lang/String;Ljava/lang/String;[B)Z",
175                    (void*)com_android_server_ApfTest_compareBpfApf },
176    };
177
178    jniRegisterNativeMethods(env, "android/net/apf/ApfTest",
179            gMethods, ARRAY_SIZE(gMethods));
180
181    return JNI_VERSION_1_6;
182}
183