Making magic with the network stack
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1116 lines
38 KiB

/*
This file is part of NetGuard.
NetGuard is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
NetGuard is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with NetGuard. If not, see <http://www.gnu.org/licenses/>.
Copyright 2015-2019 by Marcel Bokhorst (M66B)
*/
#include "netguard.h"
// It is assumed that no packets will get lost and that packets arrive in order
// https://android.googlesource.com/platform/frameworks/base.git/+/master/services/core/jni/com_android_server_connectivity_Vpn.cpp
// Global variables
char socks5_addr[INET6_ADDRSTRLEN + 1];
int socks5_port = 0;
char socks5_username[127 + 1];
char socks5_password[127 + 1];
int loglevel = ANDROID_LOG_WARN;
extern int max_tun_msg;
extern FILE *pcap_file;
extern size_t pcap_record_size;
extern long pcap_file_size;
extern int uid_cache_size;
extern struct uid_cache_entry *uid_cache;
// JNI
jclass clsPacket;
jclass clsAllowed;
jclass clsRR;
jclass clsUsage;
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
log_android(ANDROID_LOG_INFO, "JNI load");
JNIEnv *env;
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
log_android(ANDROID_LOG_INFO, "JNI load GetEnv failed");
return -1;
}
const char *packet = "eu/faircode/netguard/Packet";
clsPacket = jniGlobalRef(env, jniFindClass(env, packet));
ng_add_alloc(clsPacket, "clsPacket");
const char *allowed = "eu/faircode/netguard/Allowed";
clsAllowed = jniGlobalRef(env, jniFindClass(env, allowed));
ng_add_alloc(clsAllowed, "clsAllowed");
const char *rr = "eu/faircode/netguard/ResourceRecord";
clsRR = jniGlobalRef(env, jniFindClass(env, rr));
ng_add_alloc(clsRR, "clsRR");
const char *usage = "eu/faircode/netguard/Usage";
clsUsage = jniGlobalRef(env, jniFindClass(env, usage));
ng_add_alloc(clsUsage, "clsUsage");
// Raise file number limit to maximum
struct rlimit rlim;
if (getrlimit(RLIMIT_NOFILE, &rlim))
log_android(ANDROID_LOG_WARN, "getrlimit error %d: %s", errno, strerror(errno));
else {
rlim_t soft = rlim.rlim_cur;
rlim.rlim_cur = rlim.rlim_max;
if (setrlimit(RLIMIT_NOFILE, &rlim))
log_android(ANDROID_LOG_WARN, "setrlimit error %d: %s", errno, strerror(errno));
else
log_android(ANDROID_LOG_WARN, "raised file limit from %d to %d", soft, rlim.rlim_cur);
}
return JNI_VERSION_1_6;
}
void JNI_OnUnload(JavaVM *vm, void *reserved) {
log_android(ANDROID_LOG_INFO, "JNI unload");
JNIEnv *env;
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK)
log_android(ANDROID_LOG_INFO, "JNI load GetEnv failed");
else {
(*env)->DeleteGlobalRef(env, clsPacket);
(*env)->DeleteGlobalRef(env, clsAllowed);
(*env)->DeleteGlobalRef(env, clsRR);
(*env)->DeleteGlobalRef(env, clsUsage);
ng_delete_alloc(clsPacket, __FILE__, __LINE__);
ng_delete_alloc(clsAllowed, __FILE__, __LINE__);
ng_delete_alloc(clsRR, __FILE__, __LINE__);
ng_delete_alloc(clsUsage, __FILE__, __LINE__);
}
}
// JNI ServiceSinkhole
JNIEXPORT jlong JNICALL
Java_eu_faircode_netguard_ServiceSinkhole_jni_1init(
JNIEnv *env, jobject instance, jint sdk) {
struct context *ctx = ng_calloc(1, sizeof(struct context), "init");
ctx->sdk = sdk;
loglevel = ANDROID_LOG_WARN;
*socks5_addr = 0;
socks5_port = 0;
*socks5_username = 0;
*socks5_password = 0;
pcap_file = NULL;
if (pthread_mutex_init(&ctx->lock, NULL))
log_android(ANDROID_LOG_ERROR, "pthread_mutex_init failed");
// Create signal pipe
if (pipe(ctx->pipefds))
log_android(ANDROID_LOG_ERROR, "Create pipe error %d: %s", errno, strerror(errno));
else
for (int i = 0; i < 2; i++) {
int flags = fcntl(ctx->pipefds[i], F_GETFL, 0);
if (flags < 0 || fcntl(ctx->pipefds[i], F_SETFL, flags | O_NONBLOCK) < 0)
log_android(ANDROID_LOG_ERROR, "fcntl pipefds[%d] O_NONBLOCK error %d: %s",
i, errno, strerror(errno));
}
return (jlong) ctx;
}
JNIEXPORT void JNICALL
Java_eu_faircode_netguard_ServiceSinkhole_jni_1start(
JNIEnv *env, jobject instance, jlong context, jint loglevel_) {
struct context *ctx = (struct context *) context;
loglevel = loglevel_;
max_tun_msg = 0;
ctx->stopping = 0;
log_android(ANDROID_LOG_WARN, "Starting level %d", loglevel);
}
JNIEXPORT void JNICALL
Java_eu_faircode_netguard_ServiceSinkhole_jni_1run(
JNIEnv *env, jobject instance, jlong context, jint tun, jboolean fwd53, jint rcode) {
struct context *ctx = (struct context *) context;
log_android(ANDROID_LOG_WARN, "Running tun %d fwd53 %d level %d", tun, fwd53, loglevel);
// Set blocking
int flags = fcntl(tun, F_GETFL, 0);
if (flags < 0 || fcntl(tun, F_SETFL, flags & ~O_NONBLOCK) < 0)
log_android(ANDROID_LOG_ERROR, "fcntl tun ~O_NONBLOCK error %d: %s",
errno, strerror(errno));
// Get arguments
struct arguments *args = ng_malloc(sizeof(struct arguments), "arguments");
args->env = env;
args->instance = instance;
args->tun = tun;
args->fwd53 = fwd53;
args->rcode = rcode;
args->ctx = ctx;
handle_events(args);
}
JNIEXPORT void JNICALL
Java_eu_faircode_netguard_ServiceSinkhole_jni_1stop(
JNIEnv *env, jobject instance, jlong context) {
struct context *ctx = (struct context *) context;
ctx->stopping = 1;
log_android(ANDROID_LOG_WARN, "Write pipe wakeup");
log_android(ANDROID_LOG_ERROR, "writing to file descriptor: %d", ctx->pipefds[1]);
if (write(ctx->pipefds[1], "w", 1) < 0)
log_android(ANDROID_LOG_WARN, "Write pipe error %d: %s", errno, strerror(errno));
}
JNIEXPORT void JNICALL
Java_eu_faircode_netguard_ServiceSinkhole_jni_1clear(
JNIEnv *env, jobject instance, jlong context) {
struct context *ctx = (struct context *) context;
clear(ctx);
}
JNIEXPORT jint JNICALL
Java_eu_faircode_netguard_ServiceSinkhole_jni_1get_1mtu(JNIEnv *env, jobject instance) {
return get_mtu();
}
JNIEXPORT jintArray JNICALL
Java_eu_faircode_netguard_ServiceSinkhole_jni_1get_1stats(
JNIEnv *env, jobject instance, jlong context) {
struct context *ctx = (struct context *) context;
if (pthread_mutex_lock(&ctx->lock))
log_android(ANDROID_LOG_ERROR, "pthread_mutex_lock failed");
jintArray jarray = (*env)->NewIntArray(env, 5);
jint *jcount = (*env)->GetIntArrayElements(env, jarray, NULL);
struct ng_session *s = ctx->ng_session;
while (s != NULL) {
if (s->protocol == IPPROTO_ICMP || s->protocol == IPPROTO_ICMPV6) {
if (!s->icmp.stop)
jcount[0]++;
} else if (s->protocol == IPPROTO_UDP) {
if (s->udp.state == UDP_ACTIVE)
jcount[1]++;
} else if (s->protocol == IPPROTO_TCP) {
if (s->tcp.state != TCP_CLOSING && s->tcp.state != TCP_CLOSE)
jcount[2]++;
}
s = s->next;
}
if (pthread_mutex_unlock(&ctx->lock))
log_android(ANDROID_LOG_ERROR, "pthread_mutex_unlock failed");
jcount[3] = 0;
DIR *d = opendir("/proc/self/fd");
if (d) {
struct dirent *dir;
while ((dir = readdir(d)) != NULL)
if (dir->d_type != DT_DIR)
jcount[3]++;
closedir(d);
}
struct rlimit rlim;
memset(&rlim, 0, sizeof(struct rlimit));
getrlimit(RLIMIT_NOFILE, &rlim);
jcount[4] = (jint) rlim.rlim_cur;
(*env)->ReleaseIntArrayElements(env, jarray, jcount, 0);
return jarray;
}
JNIEXPORT void JNICALL
Java_eu_faircode_netguard_ServiceSinkhole_jni_1pcap(
JNIEnv *env, jclass type,
jstring name_, jint record_size, jint file_size) {
pcap_record_size = (size_t) record_size;
pcap_file_size = file_size;
//if (pthread_mutex_lock(&lock))
// log_android(ANDROID_LOG_ERROR, "pthread_mutex_lock failed");
if (name_ == NULL) {
if (pcap_file != NULL) {
int flags = fcntl(fileno(pcap_file), F_GETFL, 0);
if (flags < 0 || fcntl(fileno(pcap_file), F_SETFL, flags & ~O_NONBLOCK) < 0)
log_android(ANDROID_LOG_ERROR, "PCAP fcntl ~O_NONBLOCK error %d: %s",
errno, strerror(errno));
if (fsync(fileno(pcap_file)))
log_android(ANDROID_LOG_ERROR, "PCAP fsync error %d: %s", errno, strerror(errno));
if (fclose(pcap_file))
log_android(ANDROID_LOG_ERROR, "PCAP fclose error %d: %s", errno, strerror(errno));
pcap_file = NULL;
}
log_android(ANDROID_LOG_WARN, "PCAP disabled");
} else {
const char *name = (*env)->GetStringUTFChars(env, name_, 0);
ng_add_alloc(name, "name");
log_android(ANDROID_LOG_WARN, "PCAP file %s record size %d truncate @%ld",
name, pcap_record_size, pcap_file_size);
pcap_file = fopen(name, "ab+");
if (pcap_file == NULL)
log_android(ANDROID_LOG_ERROR, "PCAP fopen error %d: %s", errno, strerror(errno));
else {
int flags = fcntl(fileno(pcap_file), F_GETFL, 0);
if (flags < 0 || fcntl(fileno(pcap_file), F_SETFL, flags | O_NONBLOCK) < 0)
log_android(ANDROID_LOG_ERROR, "PCAP fcntl O_NONBLOCK error %d: %s",
errno, strerror(errno));
long size = ftell(pcap_file);
if (size == 0) {
log_android(ANDROID_LOG_WARN, "PCAP initialize");
write_pcap_hdr();
} else
log_android(ANDROID_LOG_WARN, "PCAP current size %ld", size);
}
(*env)->ReleaseStringUTFChars(env, name_, name);
ng_delete_alloc(name, __FILE__, __LINE__);
}
//if (pthread_mutex_unlock(&lock))
// log_android(ANDROID_LOG_ERROR, "pthread_mutex_unlock failed");
}
JNIEXPORT void JNICALL
Java_eu_faircode_netguard_ServiceSinkhole_jni_1socks5(JNIEnv *env, jobject instance, jstring addr_,
jint port, jstring username_,
jstring password_) {
const char *addr = (*env)->GetStringUTFChars(env, addr_, 0);
const char *username = (*env)->GetStringUTFChars(env, username_, 0);
const char *password = (*env)->GetStringUTFChars(env, password_, 0);
ng_add_alloc(addr, "addr");
ng_add_alloc(username, "username");
ng_add_alloc(password, "password");
strcpy(socks5_addr, addr);
socks5_port = port;
strcpy(socks5_username, username);
strcpy(socks5_password, password);
log_android(ANDROID_LOG_WARN, "SOCKS5 %s:%d user=%s",
socks5_addr, socks5_port, socks5_username);
(*env)->ReleaseStringUTFChars(env, addr_, addr);
(*env)->ReleaseStringUTFChars(env, username_, username);
(*env)->ReleaseStringUTFChars(env, password_, password);
ng_delete_alloc(addr, __FILE__, __LINE__);
ng_delete_alloc(username, __FILE__, __LINE__);
ng_delete_alloc(password, __FILE__, __LINE__);
}
JNIEXPORT void JNICALL
Java_eu_faircode_netguard_ServiceSinkhole_jni_1done(
JNIEnv *env, jobject instance, jlong context) {
struct context *ctx = (struct context *) context;
log_android(ANDROID_LOG_INFO, "Done");
clear(ctx);
if (pthread_mutex_destroy(&ctx->lock))
log_android(ANDROID_LOG_ERROR, "pthread_mutex_destroy failed");
for (int i = 0; i < 2; i++)
if (close(ctx->pipefds[i]))
log_android(ANDROID_LOG_ERROR, "Close pipe error %d: %s", errno, strerror(errno));
if (uid_cache != NULL)
ng_free(uid_cache, __FILE__, __LINE__);
uid_cache_size = 0;
uid_cache = NULL;
ng_free(ctx, __FILE__, __LINE__);
}
// JNI Util
JNIEXPORT jstring JNICALL
Java_eu_faircode_netguard_Util_jni_1getprop(JNIEnv *env, jclass type, jstring name_) {
const char *name = (*env)->GetStringUTFChars(env, name_, 0);
ng_add_alloc(name, "name");
char value[PROP_VALUE_MAX + 1] = "";
__system_property_get(name, value);
(*env)->ReleaseStringUTFChars(env, name_, name);
ng_delete_alloc(name, __FILE__, __LINE__);
return (*env)->NewStringUTF(env, value); // Freed by Java
}
JNIEXPORT jboolean JNICALL
Java_eu_faircode_netguard_Util_is_1numeric_1address(JNIEnv *env, jclass type, jstring ip_) {
jboolean numeric = 0;
const char *ip = (*env)->GetStringUTFChars(env, ip_, 0);
ng_add_alloc(ip, "ip");
struct addrinfo hints;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_NUMERICHOST;
struct addrinfo *result;
int err = getaddrinfo(ip, NULL, &hints, &result);
if (err)
log_android(ANDROID_LOG_DEBUG, "getaddrinfo(%s) error %d: %s", ip, err, gai_strerror(err));
else
numeric = (jboolean) (result != NULL);
if (result != NULL)
freeaddrinfo(result);
(*env)->ReleaseStringUTFChars(env, ip_, ip);
ng_delete_alloc(ip, __FILE__, __LINE__);
return numeric;
}
void report_exit(const struct arguments *args, const char *fmt, ...) {
jclass cls = (*args->env)->GetObjectClass(args->env, args->instance);
ng_add_alloc(cls, "cls");
jmethodID mid = jniGetMethodID(args->env, cls, "nativeExit", "(Ljava/lang/String;)V");
jstring jreason = NULL;
if (fmt != NULL) {
char line[1024];
va_list argptr;
va_start(argptr, fmt);
vsprintf(line, fmt, argptr);
jreason = (*args->env)->NewStringUTF(args->env, line);
ng_add_alloc(jreason, "jreason");
va_end(argptr);
}
(*args->env)->CallVoidMethod(args->env, args->instance, mid, jreason);
jniCheckException(args->env);
if (jreason != NULL) {
(*args->env)->DeleteLocalRef(args->env, jreason);
ng_delete_alloc(jreason, __FILE__, __LINE__);
}
(*args->env)->DeleteLocalRef(args->env, cls);
ng_delete_alloc(cls, __FILE__, __LINE__);
}
void report_error(const struct arguments *args, jint error, const char *fmt, ...) {
jclass cls = (*args->env)->GetObjectClass(args->env, args->instance);
ng_add_alloc(cls, "cls");
jmethodID mid = jniGetMethodID(args->env, cls, "nativeError", "(ILjava/lang/String;)V");
jstring jreason = NULL;
if (fmt != NULL) {
char line[1024];
va_list argptr;
va_start(argptr, fmt);
vsprintf(line, fmt, argptr);
jreason = (*args->env)->NewStringUTF(args->env, line);
ng_add_alloc(jreason, "jreason");
va_end(argptr);
}
(*args->env)->CallVoidMethod(args->env, args->instance, mid, error, jreason);
jniCheckException(args->env);
if (jreason != NULL) {
(*args->env)->DeleteLocalRef(args->env, jreason);
ng_delete_alloc(jreason, __FILE__, __LINE__);
}
(*args->env)->DeleteLocalRef(args->env, cls);
ng_delete_alloc(cls, __FILE__, __LINE__);
}
static jmethodID midProtect = NULL;
int protect_socket(const struct arguments *args, int socket) {
if (args->ctx->sdk >= 21)
return 0;
jclass cls = (*args->env)->GetObjectClass(args->env, args->instance);
ng_add_alloc(cls, "cls");
if (cls == NULL) {
log_android(ANDROID_LOG_ERROR, "protect socket failed to get class");
return -1;
}
if (midProtect == NULL)
midProtect = jniGetMethodID(args->env, cls, "protect", "(I)Z");
if (midProtect == NULL) {
log_android(ANDROID_LOG_ERROR, "protect socket failed to get method");
return -1;
}
jboolean isProtected = (*args->env)->CallBooleanMethod(
args->env, args->instance, midProtect, socket);
jniCheckException(args->env);
if (!isProtected) {
log_android(ANDROID_LOG_ERROR, "protect socket failed");
return -1;
}
(*args->env)->DeleteLocalRef(args->env, cls);
ng_delete_alloc(cls, __FILE__, __LINE__);
return 0;
}
// http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html
// http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/index.html
jobject jniGlobalRef(JNIEnv *env, jobject cls) {
jobject gcls = (*env)->NewGlobalRef(env, cls);
if (gcls == NULL)
log_android(ANDROID_LOG_ERROR, "Global ref failed (out of memory?)");
return gcls;
}
jclass jniFindClass(JNIEnv *env, const char *name) {
jclass cls = (*env)->FindClass(env, name);
if (cls == NULL)
log_android(ANDROID_LOG_ERROR, "Class %s not found", name);
else
jniCheckException(env);
return cls;
}
jmethodID jniGetMethodID(JNIEnv *env, jclass cls, const char *name, const char *signature) {
jmethodID method = (*env)->GetMethodID(env, cls, name, signature);
if (method == NULL) {
log_android(ANDROID_LOG_ERROR, "Method %s %s not found", name, signature);
jniCheckException(env);
}
return method;
}
jfieldID jniGetFieldID(JNIEnv *env, jclass cls, const char *name, const char *type) {
jfieldID field = (*env)->GetFieldID(env, cls, name, type);
if (field == NULL)
log_android(ANDROID_LOG_ERROR, "Field %s type %s not found", name, type);
return field;
}
jobject jniNewObject(JNIEnv *env, jclass cls, jmethodID constructor, const char *name) {
jobject object = (*env)->NewObject(env, cls, constructor);
if (object == NULL)
log_android(ANDROID_LOG_ERROR, "Create object %s failed", name);
else
jniCheckException(env);
return object;
}
int jniCheckException(JNIEnv *env) {
jthrowable ex = (*env)->ExceptionOccurred(env);
if (ex) {
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
(*env)->DeleteLocalRef(env, ex);
ng_delete_alloc(ex, __FILE__, __LINE__);
return 1;
}
return 0;
}
static jmethodID midLogPacket = NULL;
void log_packet(const struct arguments *args, jobject jpacket) {
#ifdef PROFILE_JNI
float mselapsed;
struct timeval start, end;
gettimeofday(&start, NULL);
#endif
jclass clsService = (*args->env)->GetObjectClass(args->env, args->instance);
ng_add_alloc(clsService, "clsService");
const char *signature = "(Leu/faircode/netguard/Packet;)V";
if (midLogPacket == NULL)
midLogPacket = jniGetMethodID(args->env, clsService, "logPacket", signature);
(*args->env)->CallVoidMethod(args->env, args->instance, midLogPacket, jpacket);
jniCheckException(args->env);
(*args->env)->DeleteLocalRef(args->env, clsService);
(*args->env)->DeleteLocalRef(args->env, jpacket);
ng_delete_alloc(clsService, __FILE__, __LINE__);
ng_delete_alloc(jpacket, __FILE__, __LINE__);
#ifdef PROFILE_JNI
gettimeofday(&end, NULL);
mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 +
(end.tv_usec - start.tv_usec) / 1000.0;
if (mselapsed > PROFILE_JNI)
log_android(ANDROID_LOG_WARN, "log_packet %f", mselapsed);
#endif
}
static jmethodID midDnsResolved = NULL;
static jmethodID midInitRR = NULL;
jfieldID fidQTime = NULL;
jfieldID fidQName = NULL;
jfieldID fidAName = NULL;
jfieldID fidResource = NULL;
jfieldID fidTTL = NULL;
void dns_resolved(const struct arguments *args,
const char *qname, const char *aname, const char *resource, int ttl) {
#ifdef PROFILE_JNI
float mselapsed;
struct timeval start, end;
gettimeofday(&start, NULL);
#endif
jclass clsService = (*args->env)->GetObjectClass(args->env, args->instance);
ng_add_alloc(clsService, "clsService");
const char *signature = "(Leu/faircode/netguard/ResourceRecord;)V";
if (midDnsResolved == NULL)
midDnsResolved = jniGetMethodID(args->env, clsService, "dnsResolved", signature);
const char *rr = "eu/faircode/netguard/ResourceRecord";
if (midInitRR == NULL)
midInitRR = jniGetMethodID(args->env, clsRR, "<init>", "()V");
jobject jrr = jniNewObject(args->env, clsRR, midInitRR, rr);
ng_add_alloc(jrr, "jrr");
if (fidQTime == NULL) {
const char *string = "Ljava/lang/String;";
fidQTime = jniGetFieldID(args->env, clsRR, "Time", "J");
fidQName = jniGetFieldID(args->env, clsRR, "QName", string);
fidAName = jniGetFieldID(args->env, clsRR, "AName", string);
fidResource = jniGetFieldID(args->env, clsRR, "Resource", string);
fidTTL = jniGetFieldID(args->env, clsRR, "TTL", "I");
}
jlong jtime = time(NULL) * 1000LL;
jstring jqname = (*args->env)->NewStringUTF(args->env, qname);
jstring janame = (*args->env)->NewStringUTF(args->env, aname);
jstring jresource = (*args->env)->NewStringUTF(args->env, resource);
ng_add_alloc(jqname, "jqname");
ng_add_alloc(janame, "janame");
ng_add_alloc(jresource, "jresource");
(*args->env)->SetLongField(args->env, jrr, fidQTime, jtime);
(*args->env)->SetObjectField(args->env, jrr, fidQName, jqname);
(*args->env)->SetObjectField(args->env, jrr, fidAName, janame);
(*args->env)->SetObjectField(args->env, jrr, fidResource, jresource);
(*args->env)->SetIntField(args->env, jrr, fidTTL, ttl);
(*args->env)->CallVoidMethod(args->env, args->instance, midDnsResolved, jrr);
jniCheckException(args->env);
(*args->env)->DeleteLocalRef(args->env, jresource);
(*args->env)->DeleteLocalRef(args->env, janame);
(*args->env)->DeleteLocalRef(args->env, jqname);
(*args->env)->DeleteLocalRef(args->env, jrr);
(*args->env)->DeleteLocalRef(args->env, clsService);
ng_delete_alloc(jresource, __FILE__, __LINE__);
ng_delete_alloc(janame, __FILE__, __LINE__);
ng_delete_alloc(jqname, __FILE__, __LINE__);
ng_delete_alloc(jrr, __FILE__, __LINE__);
ng_delete_alloc(clsService, __FILE__, __LINE__);
#ifdef PROFILE_JNI
gettimeofday(&end, NULL);
mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 +
(end.tv_usec - start.tv_usec) / 1000.0;
if (mselapsed > PROFILE_JNI)
log_android(ANDROID_LOG_WARN, "log_packet %f", mselapsed);
#endif
}
static jmethodID midIsDomainBlocked = NULL;
jboolean is_domain_blocked(const struct arguments *args, const char *name) {
#ifdef PROFILE_JNI
float mselapsed;
struct timeval start, end;
gettimeofday(&start, NULL);
#endif
jclass clsService = (*args->env)->GetObjectClass(args->env, args->instance);
ng_add_alloc(clsService, "clsService");
const char *signature = "(Ljava/lang/String;)Z";
if (midIsDomainBlocked == NULL)
midIsDomainBlocked = jniGetMethodID(args->env, clsService, "isDomainBlocked", signature);
jstring jname = (*args->env)->NewStringUTF(args->env, name);
ng_add_alloc(jname, "jname");
jboolean jallowed = (*args->env)->CallBooleanMethod(
args->env, args->instance, midIsDomainBlocked, jname);
jniCheckException(args->env);
(*args->env)->DeleteLocalRef(args->env, jname);
(*args->env)->DeleteLocalRef(args->env, clsService);
ng_delete_alloc(jname, __FILE__, __LINE__);
ng_delete_alloc(clsService, __FILE__, __LINE__);
#ifdef PROFILE_JNI
gettimeofday(&end, NULL);
mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 +
(end.tv_usec - start.tv_usec) / 1000.0;
if (mselapsed > PROFILE_JNI)
log_android(ANDROID_LOG_WARN, "is_domain_blocked %f", mselapsed);
#endif
return jallowed;
}
static jmethodID midGetUidQ = NULL;
jint get_uid_q(const struct arguments *args,
jint version, jint protocol,
const char *source, jint sport,
const char *dest, jint dport) {
#ifdef PROFILE_JNI
float mselapsed;
struct timeval start, end;
gettimeofday(&start, NULL);
#endif
jclass clsService = (*args->env)->GetObjectClass(args->env, args->instance);
ng_add_alloc(clsService, "clsService");
const char *signature = "(IILjava/lang/String;ILjava/lang/String;I)I";
if (midGetUidQ == NULL)
midGetUidQ = jniGetMethodID(args->env, clsService, "getUidQ", signature);
jstring jsource = (*args->env)->NewStringUTF(args->env, source);
jstring jdest = (*args->env)->NewStringUTF(args->env, dest);
ng_add_alloc(jsource, "jsource");
ng_add_alloc(jdest, "jdest");
jint juid = (*args->env)->CallIntMethod(
args->env, args->instance, midGetUidQ,
version, protocol, jsource, sport, jdest, dport);
jniCheckException(args->env);
(*args->env)->DeleteLocalRef(args->env, jdest);
(*args->env)->DeleteLocalRef(args->env, jsource);
(*args->env)->DeleteLocalRef(args->env, clsService);
ng_delete_alloc(jdest, __FILE__, __LINE__);
ng_delete_alloc(jsource, __FILE__, __LINE__);
ng_delete_alloc(clsService, __FILE__, __LINE__);
#ifdef PROFILE_JNI
gettimeofday(&end, NULL);
mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 +
(end.tv_usec - start.tv_usec) / 1000.0;
if (mselapsed > PROFILE_JNI)
log_android(ANDROID_LOG_WARN, "get_uid_q %f", mselapsed);
#endif
return juid;
}
static jmethodID midIsAddressAllowed = NULL;
jfieldID fidRaddr = NULL;
jfieldID fidRport = NULL;
struct allowed allowed;
struct allowed *is_address_allowed(const struct arguments *args, jobject jpacket) {
#ifdef PROFILE_JNI
float mselapsed;
struct timeval start, end;
gettimeofday(&start, NULL);
#endif
jclass clsService = (*args->env)->GetObjectClass(args->env, args->instance);
ng_add_alloc(clsService, "clsService");
const char *signature = "(Leu/faircode/netguard/Packet;)Leu/faircode/netguard/Allowed;";
if (midIsAddressAllowed == NULL)
midIsAddressAllowed = jniGetMethodID(args->env, clsService, "isAddressAllowed", signature);
jobject jallowed = (*args->env)->CallObjectMethod(
args->env, args->instance, midIsAddressAllowed, jpacket);
ng_add_alloc(jallowed, "jallowed");
jniCheckException(args->env);
if (jallowed != NULL) {
if (fidRaddr == NULL) {
const char *string = "Ljava/lang/String;";
fidRaddr = jniGetFieldID(args->env, clsAllowed, "raddr", string);
fidRport = jniGetFieldID(args->env, clsAllowed, "rport", "I");
}
jstring jraddr = (*args->env)->GetObjectField(args->env, jallowed, fidRaddr);
ng_add_alloc(jraddr, "jraddr");
if (jraddr == NULL)
*allowed.raddr = 0;
else {
const char *raddr = (*args->env)->GetStringUTFChars(args->env, jraddr, NULL);
ng_add_alloc(raddr, "raddr");
strcpy(allowed.raddr, raddr);
(*args->env)->ReleaseStringUTFChars(args->env, jraddr, raddr);
ng_delete_alloc(raddr, __FILE__, __LINE__);
}
allowed.rport = (uint16_t) (*args->env)->GetIntField(args->env, jallowed, fidRport);
(*args->env)->DeleteLocalRef(args->env, jraddr);
ng_delete_alloc(jraddr, __FILE__, __LINE__);
}
(*args->env)->DeleteLocalRef(args->env, jpacket);
(*args->env)->DeleteLocalRef(args->env, clsService);
(*args->env)->DeleteLocalRef(args->env, jallowed);
ng_delete_alloc(jpacket, __FILE__, __LINE__);
ng_delete_alloc(clsService, __FILE__, __LINE__);
ng_delete_alloc(jallowed, __FILE__, __LINE__);
#ifdef PROFILE_JNI
gettimeofday(&end, NULL);
mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 +
(end.tv_usec - start.tv_usec) / 1000.0;
if (mselapsed > PROFILE_JNI)
log_android(ANDROID_LOG_WARN, "is_address_allowed %f", mselapsed);
#endif
return (jallowed == NULL ? NULL : &allowed);
}
jmethodID midInitPacket = NULL;
jfieldID fidTime = NULL;
jfieldID fidVersion = NULL;
jfieldID fidProtocol = NULL;
jfieldID fidFlags = NULL;
jfieldID fidSaddr = NULL;
jfieldID fidSport = NULL;
jfieldID fidDaddr = NULL;
jfieldID fidDport = NULL;
jfieldID fidData = NULL;
jfieldID fidUid = NULL;
jfieldID fidAllowed = NULL;
jobject create_packet(const struct arguments *args,
jint version,
jint protocol,
const char *flags,
const char *source,
jint sport,
const char *dest,
jint dport,
const char *data,
jint uid,
jboolean allowed) {
JNIEnv *env = args->env;
#ifdef PROFILE_JNI
float mselapsed;
struct timeval start, end;
gettimeofday(&start, NULL);
#endif
/*
jbyte b[] = {1,2,3};
jbyteArray ret = env->NewByteArray(3);
env->SetByteArrayRegion (ret, 0, 3, b);
*/
const char *packet = "eu/faircode/netguard/Packet";
if (midInitPacket == NULL)
midInitPacket = jniGetMethodID(env, clsPacket, "<init>", "()V");
jobject jpacket = jniNewObject(env, clsPacket, midInitPacket, packet);
ng_add_alloc(jpacket, "jpacket");
if (fidTime == NULL) {
const char *string = "Ljava/lang/String;";
fidTime = jniGetFieldID(env, clsPacket, "time", "J");
fidVersion = jniGetFieldID(env, clsPacket, "version", "I");
fidProtocol = jniGetFieldID(env, clsPacket, "protocol", "I");
fidFlags = jniGetFieldID(env, clsPacket, "flags", string);
fidSaddr = jniGetFieldID(env, clsPacket, "saddr", string);
fidSport = jniGetFieldID(env, clsPacket, "sport", "I");
fidDaddr = jniGetFieldID(env, clsPacket, "daddr", string);
fidDport = jniGetFieldID(env, clsPacket, "dport", "I");
fidData = jniGetFieldID(env, clsPacket, "data", string);
fidUid = jniGetFieldID(env, clsPacket, "uid", "I");
fidAllowed = jniGetFieldID(env, clsPacket, "allowed", "Z");
}
struct timeval tv;
gettimeofday(&tv, NULL);
jlong t = tv.tv_sec * 1000LL + tv.tv_usec / 1000;
jstring jflags = (*env)->NewStringUTF(env, flags);
jstring jsource = (*env)->NewStringUTF(env, source);
jstring jdest = (*env)->NewStringUTF(env, dest);
jstring jdata = (*env)->NewStringUTF(env, data);
ng_add_alloc(jflags, "jflags");
ng_add_alloc(jsource, "jsource");
ng_add_alloc(jdest, "jdest");
ng_add_alloc(jdata, "jdata");
(*env)->SetLongField(env, jpacket, fidTime, t);
(*env)->SetIntField(env, jpacket, fidVersion, version);
(*env)->SetIntField(env, jpacket, fidProtocol, protocol);
(*env)->SetObjectField(env, jpacket, fidFlags, jflags);
(*env)->SetObjectField(env, jpacket, fidSaddr, jsource);
(*env)->SetIntField(env, jpacket, fidSport, sport);
(*env)->SetObjectField(env, jpacket, fidDaddr, jdest);
(*env)->SetIntField(env, jpacket, fidDport, dport);
(*env)->SetObjectField(env, jpacket, fidData, jdata);
(*env)->SetIntField(env, jpacket, fidUid, uid);
(*env)->SetBooleanField(env, jpacket, fidAllowed, allowed);
(*env)->DeleteLocalRef(env, jdata);
(*env)->DeleteLocalRef(env, jdest);
(*env)->DeleteLocalRef(env, jsource);
(*env)->DeleteLocalRef(env, jflags);
ng_delete_alloc(jdata, __FILE__, __LINE__);
ng_delete_alloc(jdest, __FILE__, __LINE__);
ng_delete_alloc(jsource, __FILE__, __LINE__);
ng_delete_alloc(jflags, __FILE__, __LINE__);
// Caller needs to delete reference to packet
#ifdef PROFILE_JNI
gettimeofday(&end, NULL);
mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 +
(end.tv_usec - start.tv_usec) / 1000.0;
if (mselapsed > PROFILE_JNI)
log_android(ANDROID_LOG_WARN, "create_packet %f", mselapsed);
#endif
return jpacket;
}
jmethodID midAccountUsage = NULL;
jmethodID midInitUsage = NULL;
jfieldID fidUsageTime = NULL;
jfieldID fidUsageVersion = NULL;
jfieldID fidUsageProtocol = NULL;
jfieldID fidUsageDAddr = NULL;
jfieldID fidUsageDPort = NULL;
jfieldID fidUsageUid = NULL;
jfieldID fidUsageSent = NULL;
jfieldID fidUsageReceived = NULL;
void account_usage(const struct arguments *args, jint version, jint protocol,
const char *daddr, jint dport, jint uid, jlong sent, jlong received) {
#ifdef PROFILE_JNI
float mselapsed;
struct timeval start, end;
gettimeofday(&start, NULL);
#endif
jclass clsService = (*args->env)->GetObjectClass(args->env, args->instance);
ng_add_alloc(clsService, "clsService");
const char *signature = "(Leu/faircode/netguard/Usage;)V";
if (midAccountUsage == NULL)
midAccountUsage = jniGetMethodID(args->env, clsService, "accountUsage", signature);
const char *usage = "eu/faircode/netguard/Usage";
if (midInitUsage == NULL)
midInitUsage = jniGetMethodID(args->env, clsUsage, "<init>", "()V");
jobject jusage = jniNewObject(args->env, clsUsage, midInitUsage, usage);
ng_add_alloc(jusage, "jusage");
if (fidUsageTime == NULL) {
const char *string = "Ljava/lang/String;";
fidUsageTime = jniGetFieldID(args->env, clsUsage, "Time", "J");
fidUsageVersion = jniGetFieldID(args->env, clsUsage, "Version", "I");
fidUsageProtocol = jniGetFieldID(args->env, clsUsage, "Protocol", "I");
fidUsageDAddr = jniGetFieldID(args->env, clsUsage, "DAddr", string);
fidUsageDPort = jniGetFieldID(args->env, clsUsage, "DPort", "I");
fidUsageUid = jniGetFieldID(args->env, clsUsage, "Uid", "I");
fidUsageSent = jniGetFieldID(args->env, clsUsage, "Sent", "J");
fidUsageReceived = jniGetFieldID(args->env, clsUsage, "Received", "J");
}
jlong jtime = time(NULL) * 1000LL;
jstring jdaddr = (*args->env)->NewStringUTF(args->env, daddr);
ng_add_alloc(jdaddr, "jdaddr");
(*args->env)->SetLongField(args->env, jusage, fidUsageTime, jtime);
(*args->env)->SetIntField(args->env, jusage, fidUsageVersion, version);
(*args->env)->SetIntField(args->env, jusage, fidUsageProtocol, protocol);
(*args->env)->SetObjectField(args->env, jusage, fidUsageDAddr, jdaddr);
(*args->env)->SetIntField(args->env, jusage, fidUsageDPort, dport);
(*args->env)->SetIntField(args->env, jusage, fidUsageUid, uid);
(*args->env)->SetLongField(args->env, jusage, fidUsageSent, sent);
(*args->env)->SetLongField(args->env, jusage, fidUsageReceived, received);
(*args->env)->CallVoidMethod(args->env, args->instance, midAccountUsage, jusage);
jniCheckException(args->env);
(*args->env)->DeleteLocalRef(args->env, jdaddr);
(*args->env)->DeleteLocalRef(args->env, jusage);
(*args->env)->DeleteLocalRef(args->env, clsService);
ng_delete_alloc(jdaddr, __FILE__, __LINE__);
ng_delete_alloc(jusage, __FILE__, __LINE__);
ng_delete_alloc(clsService, __FILE__, __LINE__);
#ifdef PROFILE_JNI
gettimeofday(&end, NULL);
mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 +
(end.tv_usec - start.tv_usec) / 1000.0;
if (mselapsed > PROFILE_JNI)
log_android(ANDROID_LOG_WARN, "log_packet %f", mselapsed);
#endif
}
struct alloc_record {
const char *tag;
time_t time;
void *ptr;
};
int allocs = 0;
int balance = 0;
struct alloc_record *alloc = NULL;
pthread_mutex_t *alock = NULL;
void ng_add_alloc(void *ptr, const char *tag) {
#ifdef PROFILE_MEMORY
if (ptr == NULL)
return;
if (alock == NULL) {
alock = malloc(sizeof(pthread_mutex_t));
if (pthread_mutex_init(alock, NULL))
log_android(ANDROID_LOG_ERROR, "pthread_mutex_init failed");
}
if (pthread_mutex_lock(alock))
log_android(ANDROID_LOG_ERROR, "pthread_mutex_lock failed");
int c = 0;
for (; c < allocs; c++)
if (alloc[c].ptr == NULL)
break;
if (c >= allocs) {
if (allocs == 0)
alloc = malloc(sizeof(struct alloc_record));
else
alloc = realloc(alloc, sizeof(struct alloc_record) * (allocs + 1));
c = allocs;
allocs++;
}
alloc[c].tag = tag;
alloc[c].time = time(NULL);
alloc[c].ptr = ptr;
balance++;
if (pthread_mutex_unlock(alock))
log_android(ANDROID_LOG_ERROR, "pthread_mutex_unlock failed");
#endif
}
void ng_delete_alloc(void *ptr, const char *file, int line) {
#ifdef PROFILE_MEMORY
if (ptr == NULL)
return;
if (pthread_mutex_lock(alock))
log_android(ANDROID_LOG_ERROR, "pthread_mutex_lock failed");
int found = 0;
for (int c = 0; c < allocs; c++)
if (alloc[c].ptr == ptr) {
found = 1;
alloc[c].tag = "[free]";
alloc[c].ptr = NULL;
break;
}
if (found == 1)
balance--;
log_android(found ? ANDROID_LOG_DEBUG : ANDROID_LOG_ERROR,
"alloc/free balance %d records %d found %d", balance, allocs, found);
if (found == 0)
log_android(ANDROID_LOG_ERROR, "Not found at %s:%d", file, line);
if (pthread_mutex_unlock(alock))
log_android(ANDROID_LOG_ERROR, "pthread_mutex_unlock failed");
#endif
}
void *ng_malloc(size_t __byte_count, const char *tag) {
void *ptr = malloc(__byte_count);
ng_add_alloc(ptr, tag);
return ptr;
}
void *ng_calloc(size_t __item_count, size_t __item_size, const char *tag) {
void *ptr = calloc(__item_count, __item_size);
ng_add_alloc(ptr, tag);
return ptr;
}
void *ng_realloc(void *__ptr, size_t __byte_count, const char *tag) {
ng_delete_alloc(__ptr, NULL, 0);
void *ptr = realloc(__ptr, __byte_count);
ng_add_alloc(ptr, tag);
return ptr;
}
void ng_free(void *__ptr, const char *file, int line) {
ng_delete_alloc(__ptr, file, line);
free(__ptr);
}
void ng_dump() {
int r = 0;
for (int c = 0; c < allocs; c++)
if (alloc[c].ptr != NULL)
log_android(ANDROID_LOG_WARN,
"holding %d [%s] %s",
++r, alloc[c].tag, ctime(&alloc[c].time));
}
JNIEXPORT void JNICALL
Java_eu_faircode_netguard_Util_dump_1memory_1profile(JNIEnv *env, jclass type) {
#ifdef PROFILE_MEMORY
log_android(ANDROID_LOG_DEBUG, "Dump memory profile");
if (pthread_mutex_lock(alock))
log_android(ANDROID_LOG_ERROR, "pthread_mutex_lock failed");
ng_dump();
if (pthread_mutex_unlock(alock))
log_android(ANDROID_LOG_ERROR, "pthread_mutex_unlock failed");
#endif
}