Native filtering integration

This commit is contained in:
M66B 2016-01-19 20:58:51 +01:00
parent ca103c7313
commit 65a26a7bc1
5 changed files with 176 additions and 97 deletions

View File

@ -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<LogChangedListener> logChangedListeners = new ArrayList<LogChangedListener>();
@ -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");

View File

@ -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);

View File

@ -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<Rule> listRule = Rule.getRules(true, TAG, SinkholeService.this);
List<Rule> 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<Rule> listRule = Rule.getRules(true, TAG, SinkholeService.this);
List<Rule> 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<Rule> 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<Rule> getAllowedRules(List<Rule> listRule) {
List<Rule> 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);

View File

@ -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) {

View File

@ -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);