Moved PCAP to log view, added setting to enable filtering

This commit is contained in:
M66B 2016-01-19 17:54:07 +01:00
parent 5f79b291e5
commit 05c01f08e0
8 changed files with 180 additions and 157 deletions

View File

@ -19,17 +19,18 @@ package eu.faircode.netguard;
Copyright 2015-2016 by Marcel Bokhorst (M66B)
*/
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
@ -40,14 +41,24 @@ import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ActivityLog extends AppCompatActivity {
private static final String TAG = "NetGuard.Log";
private ListView lvLog;
private LogAdapter adapter;
private DatabaseHelper dh;
private boolean live;
private static final int REQUEST_PCAP = 1;
private DatabaseHelper.LogChangedListener listener = new DatabaseHelper.LogChangedListener() {
@Override
public void onChanged() {
@ -67,7 +78,7 @@ public class ActivityLog extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.logview);
getSupportActionBar().setTitle(R.string.title_log);
getSupportActionBar().setTitle(R.string.menu_log);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
dh = new DatabaseHelper(this);
@ -154,22 +165,34 @@ public class ActivityLog extends AppCompatActivity {
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
menu.findItem(R.id.menu_enabled).setChecked(prefs.getBoolean("log", true));
boolean log = prefs.getBoolean("log", false);
boolean filter = prefs.getBoolean("filter", false);
boolean pcap = prefs.getBoolean("pcap", false);
boolean export = (getPackageManager().resolveActivity(getIntentPCAPDocument(), 0) != null);
menu.findItem(R.id.menu_log_enabled).setChecked(log);
menu.findItem(R.id.menu_pcap_enabled).setChecked(pcap);
menu.findItem(R.id.menu_pcap_enabled).setEnabled(log || filter);
menu.findItem(R.id.menu_pcap_export).setEnabled(pcap && export);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
switch (item.getItemId()) {
case R.id.menu_enabled:
case R.id.menu_log_enabled:
item.setChecked(!item.isChecked());
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean("log", item.isChecked()).apply();
SinkholeService.reload(null, "setting changed", this);
return true;
case R.id.menu_live:
case R.id.menu_log_live:
item.setChecked(!item.isChecked());
live = item.isChecked();
if (live) {
@ -180,7 +203,7 @@ public class ActivityLog extends AppCompatActivity {
DatabaseHelper.removeLocationChangedListener(listener);
return true;
case R.id.menu_clear:
case R.id.menu_log_clear:
dh.clear();
if (!live) {
adapter = new LogAdapter(this, dh.getLog());
@ -188,7 +211,17 @@ public class ActivityLog extends AppCompatActivity {
}
return true;
case R.id.menu_support:
case R.id.menu_pcap_enabled:
item.setChecked(!item.isChecked());
prefs.edit().putBoolean("pcap", item.isChecked()).apply();
SinkholeService.setPcap(item.isChecked(), this);
return true;
case R.id.menu_pcap_export:
startActivityForResult(getIntentPCAPDocument(), REQUEST_PCAP);
return true;
case R.id.menu_log_support:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://github.com/M66B/NetGuard/blob/master/FAQ.md#FAQ27"));
if (getPackageManager().resolveActivity(intent, 0) != null)
@ -199,4 +232,75 @@ public class ActivityLog extends AppCompatActivity {
return super.onOptionsItemSelected(item);
}
}
private static Intent getIntentPCAPDocument() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/octet-stream");
intent.putExtra(Intent.EXTRA_TITLE, "netguard_" + new SimpleDateFormat("yyyyMMdd").format(new Date().getTime()) + ".pcap");
return intent;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
Log.i(TAG, "onActivityResult request=" + requestCode + " result=" + requestCode + " ok=" + (resultCode == RESULT_OK));
if (requestCode == REQUEST_PCAP) {
if (resultCode == RESULT_OK && data != null)
handleExportPCAP(data);
} else {
Log.w(TAG, "Unknown activity result request=" + requestCode);
super.onActivityResult(requestCode, resultCode, data);
}
}
private void handleExportPCAP(final Intent data) {
new AsyncTask<Object, Object, Throwable>() {
@Override
protected Throwable doInBackground(Object... objects) {
OutputStream out = null;
InputStream in = null;
try {
Log.i(TAG, "Export PCAP URI=" + data.getData());
out = getContentResolver().openOutputStream(data.getData());
File pcap = new File(getCacheDir(), "netguard.pcap");
in = new FileInputStream(pcap);
byte[] buf = new byte[4096];
int len;
while ((len = in.read(buf)) > 0)
out.write(buf, 0, len);
return null;
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
Util.sendCrashReport(ex, ActivityLog.this);
return ex;
} finally {
if (out != null)
try {
out.close();
} catch (IOException ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
}
if (in != null)
try {
in.close();
} catch (IOException ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
}
}
}
@Override
protected void onPostExecute(Throwable ex) {
if (ex == null)
Toast.makeText(ActivityLog.this, R.string.msg_completed, Toast.LENGTH_LONG).show();
else
Toast.makeText(ActivityLog.this, ex.toString(), Toast.LENGTH_LONG).show();
}
}.execute();
}
}

View File

@ -58,8 +58,6 @@ import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -82,10 +80,9 @@ public class ActivitySettings extends AppCompatActivity implements SharedPrefere
private static final int REQUEST_EXPORT = 1;
private static final int REQUEST_IMPORT = 2;
private static final int REQUEST_PCAP = 3;
private static final int REQUEST_METERED = 4;
private static final int REQUEST_ROAMING_NATIONAL = 5;
private static final int REQUEST_ROAMING_INTERNATIONAL = 6;
private static final int REQUEST_METERED = 3;
private static final int REQUEST_ROAMING_NATIONAL = 4;
private static final int REQUEST_ROAMING_INTERNATIONAL = 5;
private static final Intent INTENT_VPN_SETTINGS = new Intent("android.net.vpn.SETTINGS");
@ -172,17 +169,6 @@ public class ActivitySettings extends AppCompatActivity implements SharedPrefere
}
});
// Handle pcap export
Preference pref_pcap = screen.findPreference("pcap_export");
pref_pcap.setEnabled(getIntentPCAPDocument().resolveActivity(getPackageManager()) != null);
pref_pcap.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
startActivityForResult(getIntentPCAPDocument(), ActivitySettings.REQUEST_PCAP);
return true;
}
});
// Handle technical info
Preference.OnPreferenceClickListener listener = new Preference.OnPreferenceClickListener() {
@Override
@ -229,12 +215,6 @@ public class ActivitySettings extends AppCompatActivity implements SharedPrefere
options.removePreference(screen.findPreference("unmetered_4g"));
options.removePreference(screen.findPreference("national_roaming"));
}
// Development
if (!Util.isDebuggable(this)) {
PreferenceCategory development = (PreferenceCategory) screen.findPreference("category_development");
screen.removePreference(development);
}
}
@Override
@ -361,6 +341,10 @@ public class ActivitySettings extends AppCompatActivity implements SharedPrefere
} else
SinkholeService.reload(null, "setting changed", this);
} else if ("filter".equals(name)) {
// TODO pro feature
SinkholeService.reload(null, "setting changed", this);
} else if ("auto_enable".equals(name))
getPreferenceScreen().findPreference(name).setTitle(getString(R.string.setting_auto, prefs.getString(name, "0")));
@ -414,17 +398,6 @@ public class ActivitySettings extends AppCompatActivity implements SharedPrefere
} else if ("stats_samples".equals(name)) {
getPreferenceScreen().findPreference(name).setTitle(getString(R.string.setting_stats_samples, prefs.getString(name, "90")));
} else if ("native".equals(name))
SinkholeService.reload(null, "setting changed", this);
else if ("pcap_enabled".equals(name)) {
File pcap = new File(getCacheDir(), "netguard.pcap");
if (pcap.exists())
pcap.delete();
if (prefs.getBoolean(name, false)) {
SinkholeService.setPcap(pcap.getAbsolutePath());
} else
SinkholeService.setPcap(null);
}
}
@ -514,10 +487,6 @@ public class ActivitySettings extends AppCompatActivity implements SharedPrefere
if (resultCode == RESULT_OK && data != null)
handleImport(data);
} else if (requestCode == REQUEST_PCAP) {
if (resultCode == RESULT_OK && data != null)
handleExportPCAP(data);
} else {
Log.w(TAG, "Unknown activity result request=" + requestCode);
super.onActivityResult(requestCode, resultCode, data);
@ -539,14 +508,6 @@ public class ActivitySettings extends AppCompatActivity implements SharedPrefere
return intent;
}
private static Intent getIntentPCAPDocument() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/octet-stream");
intent.putExtra(Intent.EXTRA_TITLE, "netguard_" + new SimpleDateFormat("yyyyMMdd").format(new Date().getTime()) + ".pcap");
return intent;
}
private void handleExport(final Intent data) {
new AsyncTask<Object, Object, Throwable>() {
@Override
@ -616,55 +577,6 @@ public class ActivitySettings extends AppCompatActivity implements SharedPrefere
}.execute();
}
private void handleExportPCAP(final Intent data) {
new AsyncTask<Object, Object, Throwable>() {
@Override
protected Throwable doInBackground(Object... objects) {
OutputStream out = null;
InputStream in = null;
try {
Log.i(TAG, "Export PCAP URI=" + data.getData());
out = getContentResolver().openOutputStream(data.getData());
File pcap = new File(getCacheDir(), "netguard.pcap");
in = new FileInputStream(pcap);
byte[] buf = new byte[4096];
int len;
while ((len = in.read(buf)) > 0)
out.write(buf, 0, len);
return null;
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
Util.sendCrashReport(ex, ActivitySettings.this);
return ex;
} finally {
if (out != null)
try {
out.close();
} catch (IOException ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
}
if (in != null)
try {
in.close();
} catch (IOException ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
}
}
}
@Override
protected void onPostExecute(Throwable ex) {
if (ex == null)
Toast.makeText(ActivitySettings.this, R.string.msg_completed, Toast.LENGTH_LONG).show();
else
Toast.makeText(ActivitySettings.this, ex.toString(), Toast.LENGTH_LONG).show();
}
}.execute();
}
private void xmlExport(OutputStream out) throws IOException {
XmlSerializer serializer = Xml.newSerializer();
serializer.setOutput(out, "UTF-8");

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, int loglevel, boolean native_);
private native void jni_start(int tun, boolean log, boolean filter, int loglevel);
private native void jni_stop(int tun, boolean clear);
@ -122,8 +122,11 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
private static native void jni_pcap(String name);
public static void setPcap(String name) {
jni_pcap(name);
public static void setPcap(boolean enabled, Context context) {
File pcap = new File(context.getCacheDir(), "netguard.pcap");
if (pcap.exists())
pcap.delete();
jni_pcap(enabled ? pcap.getAbsolutePath() : null);
}
static {
@ -252,8 +255,11 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
vpn = startVPN();
if (vpn == null)
throw new IllegalStateException("VPN start failed");
if (prefs.getBoolean("log", false))
jni_start(vpn.getFd(), Log.INFO, prefs.getBoolean("native", false));
boolean log = prefs.getBoolean("log", false);
boolean filter = prefs.getBoolean("filter", false);
if (log || filter)
jni_start(vpn.getFd(), log, filter, Log.INFO);
removeDisabledNotification();
}
break;
@ -280,9 +286,13 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
if (vpn == null)
throw new IllegalStateException("Handover failed");
}
jni_stop(vpn.getFd(), false);
if (prefs.getBoolean("log", false))
jni_start(vpn.getFd(), Log.INFO, prefs.getBoolean("native", 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 (prev != null)
stopVPN(prev);
break;
@ -608,7 +618,7 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
boolean national = prefs.getBoolean("national_roaming", false);
boolean telephony = Util.hasTelephony(this);
boolean tethering = prefs.getBoolean("tethering", false);
boolean native_ = prefs.getBoolean("native", false);
boolean filter = prefs.getBoolean("filter", false);
// Update connected state
last_connected = Util.isConnected(SinkholeService.this);
@ -640,7 +650,9 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
" telephony=" + telephony +
" generation=" + generation +
" roaming=" + roaming +
" interactive=" + last_interactive);
" interactive=" + last_interactive +
" tethering=" + tethering +
" filter=" + filter);
// Build VPN service
final Builder builder = new Builder();
@ -671,7 +683,7 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
// Add list of allowed applications
int nAllowed = 0;
int nBlocked = 0;
if (last_connected && !native_)
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);
@ -885,13 +897,7 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
// Native init
jni_init();
File pcap = new File(getCacheDir(), "netguard.pcap");
if (pcap.exists())
pcap.delete();
if (prefs.getBoolean("pcap_enabled", false)) {
SinkholeService.setPcap(pcap.getAbsolutePath());
} else
SinkholeService.setPcap(null);
setPcap(prefs.getBoolean("pcap", false), this);
prefs.registerOnSharedPreferenceChangeListener(this);

View File

@ -55,9 +55,8 @@ static JavaVM *jvm;
pthread_t thread_id;
int signaled = 0;
struct session *session = NULL;
int loglevel = 0;
char *pcap_fn = NULL;
int loglevel = ANDROID_LOG_WARN;
int native = 0;
// JNI
@ -69,11 +68,11 @@ 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, jint loglevel_, jboolean native_) {
jint tun, jboolean log, jboolean filter,
jint loglevel_) {
loglevel = loglevel_;
native = native_;
log_android(ANDROID_LOG_INFO, "Starting tun=%d level %d native %d", tun, loglevel, native);
log_android(ANDROID_LOG_INFO, "Starting tun=%d log %d filter %d level %d",
tun, log, filter, loglevel_);
// Set blocking
uint8_t flags = fcntl(tun, F_GETFL, 0);
@ -91,6 +90,8 @@ Java_eu_faircode_netguard_SinkholeService_jni_1start(JNIEnv *env, jobject instan
struct arguments *args = malloc(sizeof(struct arguments));
args->instance = (*env)->NewGlobalRef(env, instance);
args->tun = tun;
args->log = log;
args->filter = filter;
int err = pthread_create(&thread_id, NULL, handle_events, args);
if (err == 0)
log_android(ANDROID_LOG_INFO, "Started thread %lu", thread_id);
@ -388,7 +389,7 @@ int check_tun(const struct arguments *args, fd_set *rfds, fd_set *wfds, fd_set *
}
else if (length > 0) {
// Write pcap record
if (native && pcap_fn != NULL)
if (pcap_fn != NULL)
write_pcap_rec(buffer, length);
// Handle IP from tun
@ -614,7 +615,7 @@ void handle_ip(const struct arguments *args, const uint8_t *buffer, const uint16
// Get uid
jint uid = -1;
if ((protocol == IPPROTO_TCP && (syn || !native)) || protocol == IPPROTO_UDP) {
if ((protocol == IPPROTO_TCP && (syn || !args->filter)) || 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) {
@ -653,12 +654,13 @@ 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 && native)
if (protocol == IPPROTO_TCP && args->filter)
handle_tcp(args, buffer, length, uid);
if ((protocol == IPPROTO_TCP && (syn || !native)) || protocol == IPPROTO_UDP)
log_java(args, version, source, sport, dest, dport, protocol, flags, uid, 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);
}
}
void handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_t length, int uid) {
@ -1167,7 +1169,7 @@ int write_tcp(const struct session *cur,
#endif
// Write pcap record
if (native && pcap_fn != NULL)
if (pcap_fn != NULL)
write_pcap_rec(buffer, len);
free(buffer);

View File

@ -17,6 +17,8 @@ struct arguments {
JNIEnv *env;
jobject instance;
int tun;
int log;
int filter;
};
struct session {

View File

@ -1,18 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_enabled"
android:id="@+id/menu_log_enabled"
android:checkable="true"
android:title="@string/menu_enabled" />
<item
android:id="@+id/menu_live"
android:id="@+id/menu_log_live"
android:checkable="true"
android:checked="true"
android:title="@string/menu_live" />
<item
android:id="@+id/menu_clear"
android:id="@+id/menu_log_clear"
android:title="@string/menu_clear" />
<item
android:id="@+id/menu_support"
android:id="@+id/menu_pcap_enabled"
android:checkable="true"
android:title="@string/menu_pcap_enabled" />
<item
android:id="@+id/menu_pcap_export"
android:title="@string/menu_pcap_export" />
<item
android:id="@+id/menu_log_support"
android:title="@string/menu_support" />
</menu>

View File

@ -26,6 +26,8 @@ These issues are caused by bugs in Android, or in the software provided by the m
<string name="menu_enabled">Enabled</string>
<string name="menu_live">Live updates</string>
<string name="menu_pcap_enabled">PCAP enabled</string>
<string name="menu_pcap_export">PCAP export</string>
<string name="menu_clear">Clear</string>
<string name="setting_defaults">Defaults</string>
@ -38,7 +40,8 @@ These issues are caused by bugs in Android, or in the software provided by the m
<string name="setting_options">Options</string>
<string name="setting_system">Manage system applications</string>
<string name="setting_tethering">Allow tethering</string>
<string name="setting_log">Log blocked traffic</string>
<string name="setting_log">Log traffic</string>
<string name="setting_filter">Filter traffic</string>
<string name="setting_auto">Auto enable after %1$s minutes</string>
<string name="setting_delay">Delay screen off %1$s minutes</string>
<string name="setting_theme">Theme: %1$s</string>
@ -65,6 +68,7 @@ These issues are caused by bugs in Android, or in the software provided by the m
<string name="summary_system">Define rules for system applications (for experts)</string>
<string name="summary_log">Log addresses of IP packets going into the VPN sinkhole. This might result in extra battery usage.</string>
<string name="summary_filter">Filter IP packets going out of the VPN sinkhole. This might result in extra battery usage.</string>
<string name="summary_stats">Show network speed graph in status bar notification</string>
<string name="summary_auto">After disabling using the widget, automatically enable NetGuard again after the selected number of minutes (enter zero to disable this option)</string>
<string name="summary_delay">After turning the screen off, keep screen on rules active for the selected number of minutes (enter zero to disable this option)</string>
@ -105,7 +109,6 @@ Since NetGuard has no internet permission, you know your internet traffic is not
<string name="title_allow">Allow</string>
<string name="title_block">Block</string>
<string name="title_log">Blocked traffic</string>
<string name="title_log_whois">Whois %1$s</string>
<string name="title_log_port">Port %1$d</string>

View File

@ -41,6 +41,11 @@
android:key="log"
android:summary="@string/summary_log"
android:title="@string/setting_log" />
<SwitchPreference
android:defaultValue="false"
android:key="filter"
android:summary="@string/summary_filter"
android:title="@string/setting_filter" />
<EditTextPreference
android:defaultValue="0"
android:inputType="number"
@ -129,24 +134,6 @@
android:key="import"
android:title="@string/setting_import" />
</PreferenceCategory>
<PreferenceCategory
android:key="category_development"
android:title="Development">
<SwitchPreference
android:defaultValue="false"
android:dependency="log"
android:key="native"
android:title="Native code" />
<SwitchPreference
android:defaultValue="false"
android:dependency="native"
android:key="pcap_enabled"
android:title="PCAP" />
<Preference
android:dependency="pcap_enabled"
android:key="pcap_export"
android:title="Export PCAP" />
</PreferenceCategory>
<PreferenceCategory
android:key="category_technical"
android:title="@string/setting_technical">