From 65a26a7bc1d1179f488902341b4599e3dd2b416a Mon Sep 17 00:00:00 2001 From: M66B Date: Tue, 19 Jan 2016 20:58:51 +0100 Subject: [PATCH] Native filtering integration --- .../eu/faircode/netguard/DatabaseHelper.java | 11 +- .../java/eu/faircode/netguard/LogAdapter.java | 11 +- .../eu/faircode/netguard/SinkholeService.java | 174 ++++++++++-------- app/src/main/jni/netguard/netguard.c | 67 +++++-- app/src/main/jni/netguard/netguard.h | 10 +- 5 files changed, 176 insertions(+), 97 deletions(-) diff --git a/app/src/main/java/eu/faircode/netguard/DatabaseHelper.java b/app/src/main/java/eu/faircode/netguard/DatabaseHelper.java index e708d17f..088afa03 100644 --- a/app/src/main/java/eu/faircode/netguard/DatabaseHelper.java +++ b/app/src/main/java/eu/faircode/netguard/DatabaseHelper.java @@ -7,7 +7,6 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; - import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -16,7 +15,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { private static final String TAG = "NetGuard.Database"; private static final String DB_NAME = "Netguard"; - private static final int DB_VERSION = 5; + private static final int DB_VERSION = 6; private static List logChangedListeners = new ArrayList(); @@ -45,6 +44,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { ", uid INTEGER NULL" + ", connection INTEGER NULL" + ", interactive INTEGER NULL" + + ", allowed INTEGER NULL" + ");"); db.execSQL("CREATE INDEX idx_log_time ON log(time)"); } @@ -74,6 +74,10 @@ public class DatabaseHelper extends SQLiteOpenHelper { db.execSQL("ALTER TABLE log ADD COLUMN interactive INTEGER NULL"); oldVersion = 5; } + if (oldVersion < 6) { + db.execSQL("ALTER TABLE log ADD COLUMN allowed INTEGER NULL"); + oldVersion = 6; + } db.setVersion(DB_VERSION); @@ -89,7 +93,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { public DatabaseHelper insertLog( int version, String ip, int protocol, int port, String flags, - int uid, int connection, boolean interactive) { + int uid, int connection, boolean interactive, boolean allowed) { synchronized (mContext.getApplicationContext()) { SQLiteDatabase db = this.getWritableDatabase(); @@ -117,6 +121,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { cv.put("connection", connection); cv.put("interactive", interactive ? 1 : 0); + cv.put("allowed", allowed ? 1 : 0); if (db.insert("log", null, cv) == -1) Log.e(TAG, "Insert log failed"); diff --git a/app/src/main/java/eu/faircode/netguard/LogAdapter.java b/app/src/main/java/eu/faircode/netguard/LogAdapter.java index b7c212ba..d82b5e2f 100644 --- a/app/src/main/java/eu/faircode/netguard/LogAdapter.java +++ b/app/src/main/java/eu/faircode/netguard/LogAdapter.java @@ -26,6 +26,7 @@ public class LogAdapter extends CursorAdapter { private int colUid; private int colConnection; private int colInteractive; + private int colAllowed; public LogAdapter(Context context, Cursor cursor) { super(context, cursor, 0); @@ -38,6 +39,7 @@ public class LogAdapter extends CursorAdapter { colUid = cursor.getColumnIndex("uid"); colConnection = cursor.getColumnIndex("connection"); colInteractive = cursor.getColumnIndex("interactive"); + colAllowed = cursor.getColumnIndex("allowed"); } @Override @@ -57,6 +59,7 @@ public class LogAdapter extends CursorAdapter { int uid = (cursor.isNull(colUid) ? -1 : cursor.getInt(colUid)); int connection = (cursor.isNull(colConnection) ? -1 : cursor.getInt(colConnection)); int interactive = (cursor.isNull(colInteractive) ? -1 : cursor.getInt(colInteractive)); + int allowed = (cursor.isNull(colAllowed) ? -1 : cursor.getInt(colAllowed)); final String whois = (ip.length() > 1 && ip.charAt(0) == '/' ? ip.substring(1) : ip); @@ -76,8 +79,12 @@ public class LogAdapter extends CursorAdapter { if (connection <= 0) ivConnection.setImageDrawable(null); - else - ivConnection.setImageResource(connection == 1 ? R.drawable.wifi_off : R.drawable.other_off); + else { + if (allowed > 0) + ivConnection.setImageResource(connection == 1 ? R.drawable.wifi_on : R.drawable.other_on); + else + ivConnection.setImageResource(connection == 1 ? R.drawable.wifi_off : R.drawable.other_off); + } if (interactive <= 0) ivInteractive.setImageDrawable(null); diff --git a/app/src/main/java/eu/faircode/netguard/SinkholeService.java b/app/src/main/java/eu/faircode/netguard/SinkholeService.java index bb741127..a7f501b2 100644 --- a/app/src/main/java/eu/faircode/netguard/SinkholeService.java +++ b/app/src/main/java/eu/faircode/netguard/SinkholeService.java @@ -114,7 +114,7 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS private native void jni_init(); - private native void jni_start(int tun, boolean log, boolean filter, int loglevel); + private native void jni_start(int tun, int[] uid, boolean log, boolean filter, int loglevel); private native void jni_stop(int tun, boolean clear); @@ -252,15 +252,25 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS state = State.enforcing; Log.d(TAG, "Start foreground state=" + state.toString()); - vpn = startVPN(); + List listRule = Rule.getRules(true, TAG, SinkholeService.this); + List listAllowed = getAllowedRules(listRule); + + vpn = startVPN(listAllowed); if (vpn == null) throw new IllegalStateException("VPN start failed"); boolean log = prefs.getBoolean("log", false); boolean filter = prefs.getBoolean("filter", false); - if (log || filter) - jni_start(vpn.getFd(), log, filter, Log.INFO); + if (log || filter) { + int[] uid = new int[listAllowed.size()]; + for (int i = 0; i < listAllowed.size(); i++) + uid[i] = listAllowed.get(i).info.applicationInfo.uid; + + jni_start(vpn.getFd(), uid, log, filter, Log.INFO); + } + removeDisabledNotification(); + updateEnforcingNotification(listAllowed.size(), listRule.size()); } break; @@ -277,12 +287,15 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS // Seamless handover ParcelFileDescriptor prev = vpn; - vpn = startVPN(); + List listRule = Rule.getRules(true, TAG, SinkholeService.this); + List listAllowed = getAllowedRules(listRule); + + vpn = startVPN(listAllowed); if (prev != null && vpn == null) { Log.w(TAG, "Handover failed"); stopVPN(prev); prev = null; - vpn = startVPN(); + vpn = startVPN(listAllowed); if (vpn == null) throw new IllegalStateException("Handover failed"); } @@ -290,11 +303,18 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS jni_stop(vpn.getFd(), false); boolean log = prefs.getBoolean("log", false); boolean filter = prefs.getBoolean("filter", false); - if (log || filter) - jni_start(vpn.getFd(), log, filter, Log.INFO); + if (log || filter) { + int[] uid = new int[listAllowed.size()]; + for (int i = 0; i < listAllowed.size(); i++) + uid[i] = listAllowed.get(i).info.applicationInfo.uid; + + jni_start(vpn.getFd(), uid, log, filter, Log.INFO); + } if (prev != null) stopVPN(prev); + + updateEnforcingNotification(listAllowed.size(), listRule.size()); break; case stop: @@ -601,7 +621,63 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS } } - private ParcelFileDescriptor startVPN() { + private ParcelFileDescriptor startVPN(List listAllowed) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + boolean tethering = prefs.getBoolean("tethering", false); + boolean filter = prefs.getBoolean("filter", false); + + // Build VPN service + final Builder builder = new Builder(); + builder.setSession(getString(R.string.app_name) + " session"); + // TODO: make tunnel parameters configurable + builder.addAddress("10.1.10.1", 32); + builder.addAddress("fd00:1:fd00:1:fd00:1:fd00:1", 64); + + if (tethering) { + // USB Tethering 192.168.42.x + // Wi-Fi Tethering 192.168.43.x + for (int r = 1; r <= 255; r++) + if (r == 192) { + for (int s = 1; s <= 255; s++) + if (s == 168) { + for (int t = 1; t <= 255; t++) + if (t != 42 && t != 43) + builder.addRoute(String.format("%d.%d.%d.0", r, s, t), 24); + } else + builder.addRoute(String.format("%d.%d.0.0", r, s), 16); + } else if (r != 127) + builder.addRoute(String.format("%d.0.0.0", r), 8); + } else + builder.addRoute("0.0.0.0", 0); + + builder.addRoute("0:0:0:0:0:0:0:0", 0); + + // Add list of allowed applications + if (last_connected && !filter) + for (Rule rule : listAllowed) + try { + builder.addDisallowedApplication(rule.info.packageName); + } catch (PackageManager.NameNotFoundException ex) { + Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex)); + Util.sendCrashReport(ex, this); + } + + // Build configure intent + Intent configure = new Intent(this, ActivityMain.class); + PendingIntent pi = PendingIntent.getActivity(this, 0, configure, PendingIntent.FLAG_UPDATE_CURRENT); + builder.setConfigureIntent(pi); + + // Start VPN service + try { + return builder.establish(); + } catch (Throwable ex) { + Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex)); + return null; + } + } + + private List getAllowedRules(List listRule) { + List listAllowed = new ArrayList<>(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); // Check state @@ -654,70 +730,14 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS " tethering=" + tethering + " filter=" + filter); - // Build VPN service - final Builder builder = new Builder(); - builder.setSession(getString(R.string.app_name) + " session"); - // TODO: make tunnel parameters configurable - builder.addAddress("10.1.10.1", 32); - builder.addAddress("fd00:1:fd00:1:fd00:1:fd00:1", 64); - - if (tethering) { - // USB Tethering 192.168.42.x - // Wi-Fi Tethering 192.168.43.x - for (int r = 1; r <= 255; r++) - if (r == 192) { - for (int s = 1; s <= 255; s++) - if (s == 168) { - for (int t = 1; t <= 255; t++) - if (t != 42 && t != 43) - builder.addRoute(String.format("%d.%d.%d.0", r, s, t), 24); - } else - builder.addRoute(String.format("%d.%d.0.0", r, s), 16); - } else if (r != 127) - builder.addRoute(String.format("%d.0.0.0", r), 8); - } else - builder.addRoute("0.0.0.0", 0); - - builder.addRoute("0:0:0:0:0:0:0:0", 0); - - // Add list of allowed applications - int nAllowed = 0; - int nBlocked = 0; - if (last_connected && !filter) - for (Rule rule : Rule.getRules(true, TAG, this)) { - boolean blocked = (metered ? rule.other_blocked : rule.wifi_blocked); - boolean screen = (metered ? rule.screen_other : rule.screen_wifi); - if ((!blocked || (screen && last_interactive)) && (!metered || !(rule.roaming && roaming))) { - nAllowed++; - // Log.i(TAG, "Allowing " + rule.info.packageName); - try { - builder.addDisallowedApplication(rule.info.packageName); - } catch (PackageManager.NameNotFoundException ex) { - Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex)); - Util.sendCrashReport(ex, this); - } - } else - nBlocked++; - } - Log.i(TAG, "Allowed=" + nAllowed + " blocked=" + nBlocked); - - // Update notification - Notification notification = getEnforcingNotification(nAllowed, nBlocked); - NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - nm.notify(NOTIFY_ENFORCING, notification); - - // Build configure intent - Intent configure = new Intent(this, ActivityMain.class); - PendingIntent pi = PendingIntent.getActivity(this, 0, configure, PendingIntent.FLAG_UPDATE_CURRENT); - builder.setConfigureIntent(pi); - - // Start VPN service - try { - return builder.establish(); - } catch (Throwable ex) { - Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex)); - return null; + for (Rule rule : listRule) { + boolean blocked = (metered ? rule.other_blocked : rule.wifi_blocked); + boolean screen = (metered ? rule.screen_other : rule.screen_wifi); + if ((!blocked || (screen && last_interactive)) && (!metered || !(rule.roaming && roaming))) + listAllowed.add(rule); } + + return listAllowed; } private void stopVPN(ParcelFileDescriptor pfd) { @@ -745,7 +765,8 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS flags, uid, (last_connected ? last_metered ? 2 : 1 : 0), - last_interactive).close(); + last_interactive, + allowed).close(); } private BroadcastReceiver interactiveStateReceiver = new BroadcastReceiver() { @@ -1073,6 +1094,13 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS return notification.build(); } + private void updateEnforcingNotification(int allowed, int total) { + // Update notification + Notification notification = getEnforcingNotification(allowed, total - allowed); + NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + nm.notify(NOTIFY_ENFORCING, notification); + } + private Notification getWaitingNotification() { Intent main = new Intent(this, ActivityMain.class); PendingIntent pi = PendingIntent.getActivity(this, 0, main, PendingIntent.FLAG_UPDATE_CURRENT); diff --git a/app/src/main/jni/netguard/netguard.c b/app/src/main/jni/netguard/netguard.c index 80180dee..0c1554f6 100644 --- a/app/src/main/jni/netguard/netguard.c +++ b/app/src/main/jni/netguard/netguard.c @@ -49,6 +49,8 @@ // Window size < 2^31: x <= y: (uint32_t)(y-x) < 0x80000000 // It is assumed that no packets will get lost and that packets arrive in order +// http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/index.html + // Global variables static JavaVM *jvm; @@ -67,9 +69,12 @@ Java_eu_faircode_netguard_SinkholeService_jni_1init(JNIEnv *env) { } JNIEXPORT void JNICALL -Java_eu_faircode_netguard_SinkholeService_jni_1start(JNIEnv *env, jobject instance, - jint tun, jboolean log, jboolean filter, - jint loglevel_) { +Java_eu_faircode_netguard_SinkholeService_jni_1start( + JNIEnv *env, jobject instance, + jint tun, jintArray uid_, + jboolean log, jboolean filter, + jint loglevel_) { + loglevel = loglevel_; log_android(ANDROID_LOG_INFO, "Starting tun=%d log %d filter %d level %d", tun, log, filter, loglevel_); @@ -87,12 +92,23 @@ Java_eu_faircode_netguard_SinkholeService_jni_1start(JNIEnv *env, jobject instan if (rs != JNI_OK) log_android(ANDROID_LOG_ERROR, "GetJavaVM failed"); + // Get arguments struct arguments *args = malloc(sizeof(struct arguments)); args->instance = (*env)->NewGlobalRef(env, instance); args->tun = tun; + args->count = (*env)->GetArrayLength(env, uid_); + args->uid = malloc(args->count * sizeof(jint)); + jint *uid = (*env)->GetIntArrayElements(env, uid_, NULL); + memcpy(args->uid, uid, args->count * sizeof(jint)); + (*env)->ReleaseIntArrayElements(env, uid_, uid, 0); args->log = log; args->filter = filter; - int err = pthread_create(&thread_id, NULL, handle_events, args); + + for (int i = 0; i < args->count; i++) + log_android(ANDROID_LOG_INFO, "Allowed uid %d", args->uid[i]); + + // Start native thread + int err = pthread_create(&thread_id, NULL, handle_events, (void *) args); if (err == 0) log_android(ANDROID_LOG_INFO, "Started thread %lu", thread_id); else @@ -254,7 +270,8 @@ void handle_events(void *a) { #endif // Check upstream - check_tun(args, &rfds, &wfds, &efds); + if (check_tun(args, &rfds, &wfds, &efds) < 0) + break; #ifdef PROFILE gettimeofday(&end, NULL); @@ -279,11 +296,13 @@ void handle_events(void *a) { #endif } + free(args->uid); + free(args); + (*env)->DeleteGlobalRef(env, args->instance); rs = (*jvm)->DetachCurrentThread(jvm); if (rs != JNI_OK) log_android(ANDROID_LOG_ERROR, "DetachCurrentThread failed"); - free(args); log_android(ANDROID_LOG_INFO, "Stopped events tun=%d thread %lu", args->tun, thread_id); // TODO report exit to Java @@ -581,8 +600,8 @@ void handle_ip(const struct arguments *args, const uint8_t *buffer, const uint16 // Get ports & flags int syn = 0; - uint16_t sport = -1; - uint16_t dport = -1; + uint16_t sport = 0; + uint16_t dport = 0; if (protocol == IPPROTO_TCP) { struct tcphdr *tcp = payload; @@ -615,7 +634,7 @@ void handle_ip(const struct arguments *args, const uint8_t *buffer, const uint16 // Get uid jint uid = -1; - if ((protocol == IPPROTO_TCP && (syn || !args->filter)) || protocol == IPPROTO_UDP) { + if ((protocol == IPPROTO_TCP && version == 4) || protocol == IPPROTO_UDP) { log_android(ANDROID_LOG_INFO, "get uid %s/%u syn %d", dest, dport, syn); int tries = 0; while (uid < 0 && tries++ < UID_MAXTRY) { @@ -654,16 +673,30 @@ void handle_ip(const struct arguments *args, const uint8_t *buffer, const uint16 log_android(ANDROID_LOG_INFO, "handle ip %f", mselapsed); #endif - if (protocol == IPPROTO_TCP && args->filter) - handle_tcp(args, buffer, length, uid); + // Check if allowed + jboolean allowed = 0; + if (args->filter && uid >= 0) { + for (int i = 0; i < args->count; i++) + if (args->uid[i] == uid) { + allowed = 1; + break; + } + } + + if (allowed) { + if (protocol == IPPROTO_TCP) + allowed = handle_tcp(args, buffer, length, uid); + else + allowed = 0; + } if (args->log) { - if ((protocol == IPPROTO_TCP && (syn || !args->filter)) || protocol == IPPROTO_UDP) - log_java(args, version, source, sport, dest, dport, protocol, flags, uid, 0); + if (!args->filter || syn) + log_java(args, version, source, sport, dest, dport, protocol, flags, uid, allowed); } } -void handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_t length, int uid) { +jboolean handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_t length, int uid) { #ifdef PROFILE float mselapsed; struct timeval start, end; @@ -673,7 +706,7 @@ void handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_t le // Check version uint8_t version = (*buffer) >> 4; if (version != 4) - return; + return 0; // Get headers struct iphdr *iphdr = buffer; @@ -760,6 +793,8 @@ void handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_t le rst.daddr = iphdr->daddr; rst.dest = tcphdr->dest; write_rst(&rst, args->tun); + + return 0; } #ifdef PROFILE @@ -946,6 +981,8 @@ void handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_t le log_android(ANDROID_LOG_INFO, "existing session %f", mselapsed); #endif } + + return 1; } int open_tcp(const struct session *cur, const struct arguments *args) { diff --git a/app/src/main/jni/netguard/netguard.h b/app/src/main/jni/netguard/netguard.h index 3c4aa2af..db9f736d 100644 --- a/app/src/main/jni/netguard/netguard.h +++ b/app/src/main/jni/netguard/netguard.h @@ -17,14 +17,16 @@ struct arguments { JNIEnv *env; jobject instance; int tun; - int log; - int filter; + jint count; + jint *uid; + jboolean log; + jboolean filter; }; struct session { // TODO TCPv6 time_t time; - int uid; + jint uid; uint32_t remote_seq; // confirmed bytes received, host notation uint32_t local_seq; // confirmed bytes sent, host notation uint32_t remote_start; @@ -77,7 +79,7 @@ void check_sockets(const struct arguments *args, fd_set *rfds, fd_set *wfds, fd_ void handle_ip(const struct arguments *args, const uint8_t *buffer, const uint16_t length); -void handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_t length, int uid); +jboolean handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_t length, int uid); int open_tcp(const struct session *cur, const struct arguments *args);