diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 08a10262..6f8ae9ca 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,11 @@
+
+
+
+
+
+
+ android:label="@string/connection_details"
+ android:launchMode="singleTop">
@@ -38,10 +44,4 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java
index 47d8d7c3..c3efd9d4 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java
@@ -68,6 +68,7 @@ public class CaptureService extends VpnService implements Runnable {
public static final String ACTION_TRAFFIC_STATS_UPDATE = "traffic_stats_update";
public static final String ACTION_CONNECTIONS_DUMP = "connections_dump";
+ public static final String ACTION_STATS_DUMP = "stats_dump";
public static final String TRAFFIC_STATS_UPDATE_SENT_BYTES = "sent_bytes";
public static final String TRAFFIC_STATS_UPDATE_RCVD_BYTES = "rcvd_bytes";
public static final String TRAFFIC_STATS_UPDATE_SENT_PKTS = "sent_pkts";
@@ -358,6 +359,17 @@ public class CaptureService extends VpnService implements Runnable {
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
+ public void sendStatsDump(VPNStats stats) {
+ Log.d(TAG, "sendStatsDump");
+
+ Bundle bundle = new Bundle();
+ bundle.putSerializable("value", stats);
+ Intent intent = new Intent(ACTION_STATS_DUMP);
+ intent.putExtras(bundle);
+
+ LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
+ }
+
private void sendServiceStatus(String cur_status) {
Intent intent = new Intent(ACTION_SERVICE_STATUS);
intent.putExtra(SERVICE_STATUS_KEY, cur_status);
@@ -377,4 +389,5 @@ public class CaptureService extends VpnService implements Runnable {
public static native void runPacketLoop(int fd, CaptureService vpn, int sdk);
public static native void stopPacketLoop();
public static native void askConnectionsDump();
+ public static native void askStatsDump();
}
diff --git a/app/src/main/java/com/emanuelef/remote_capture/MainActivity.java b/app/src/main/java/com/emanuelef/remote_capture/MainActivity.java
index b0707d38..75243a32 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/MainActivity.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/MainActivity.java
@@ -48,7 +48,6 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
-import android.widget.Toast;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
@@ -62,6 +61,9 @@ import cat.ereza.customactivityoncrash.config.CaocConfig;
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks> {
SharedPreferences mPrefs;
Menu mMenu;
+ MenuItem mMenuItemStats;
+ MenuItem mMenuItemStartBtn;
+ MenuItem mMenuItemAppSel;
Drawable mFilterIcon;
String mFilterApp;
boolean mOpenAppsWhenDone;
@@ -76,8 +78,6 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
private final static int TOTAL_COUNT = 2;
private static final int REQUEST_CODE_VPN = 2;
- private static final int MENU_ITEM_START_BTN = 0;
- private static final int MENU_ITEM_APP_SELECTOR_IDX = 1;
public static final int OPERATION_SEARCH_LOADER = 23;
public static final String TELEGRAM_GROUP_NAME = "PCAPdroid";
@@ -176,46 +176,53 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
mState = AppState.ready;
notifyAppState();
- mMenu.getItem(MENU_ITEM_START_BTN).setIcon(
+ mMenuItemStartBtn.setIcon(
ContextCompat.getDrawable(this, android.R.drawable.ic_media_play));
- mMenu.getItem(MENU_ITEM_START_BTN).setTitle(R.string.start_button);
- mMenu.getItem(MENU_ITEM_START_BTN).setEnabled(true);
- mMenu.getItem(MENU_ITEM_APP_SELECTOR_IDX).setEnabled(true);
+ mMenuItemStartBtn.setTitle(R.string.start_button);
+ mMenuItemStartBtn.setEnabled(true);
+ mMenuItemAppSel.setEnabled(true);
+ mMenuItemStats.setVisible(false);
}
public void appStateStarting() {
mState = AppState.starting;
notifyAppState();
- mMenu.getItem(MENU_ITEM_START_BTN).setEnabled(false);
- mMenu.getItem(MENU_ITEM_APP_SELECTOR_IDX).setEnabled(false);
+ mMenuItemStartBtn.setEnabled(false);
+ mMenuItemAppSel.setEnabled(false);
}
public void appStateRunning() {
mState = AppState.running;
notifyAppState();
- mMenu.getItem(MENU_ITEM_START_BTN).setIcon(
+ mMenuItemStartBtn.setIcon(
ContextCompat.getDrawable(this, R.drawable.ic_media_stop));
- mMenu.getItem(MENU_ITEM_START_BTN).setTitle(R.string.stop_button);
- mMenu.getItem(MENU_ITEM_START_BTN).setEnabled(true);
- mMenu.getItem(MENU_ITEM_APP_SELECTOR_IDX).setEnabled(false);
+ mMenuItemStartBtn.setTitle(R.string.stop_button);
+ mMenuItemStartBtn.setEnabled(true);
+ mMenuItemAppSel.setEnabled(false);
+ mMenuItemStats.setVisible(true);
}
public void appStateStopping() {
mState = AppState.stopping;
notifyAppState();
- mMenu.getItem(MENU_ITEM_START_BTN).setEnabled(false);
- mMenu.getItem(MENU_ITEM_APP_SELECTOR_IDX).setEnabled(false);
+ mMenuItemStartBtn.setEnabled(false);
+ mMenuItemAppSel.setEnabled(false);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.settings_menu, menu);
+
mMenu = menu;
- mFilterIcon = mMenu.getItem(MENU_ITEM_APP_SELECTOR_IDX).getIcon();
+ mMenuItemStats = mMenu.findItem(R.id.action_stats);
+ mMenuItemStartBtn = mMenu.findItem(R.id.action_start);
+ mMenuItemAppSel = mMenu.findItem(R.id.action_show_app_filter);
+
+ mFilterIcon = mMenuItemAppSel.getIcon();
initAppState();
@@ -276,6 +283,10 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
} else if (id == R.id.action_rate_app) {
rateApp();
return true;
+ } else if (id == R.id.action_stats) {
+ Intent intent = new Intent(MainActivity.this, StatsActivity.class);
+ startActivity(intent);
+ return true;
}
return super.onOptionsItemSelected(item);
@@ -435,7 +446,7 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
private void setSelectedAppIcon(AppDescriptor app) {
// clone the drawable to avoid a "zoom-in" effect when clicked
Drawable drawable = Objects.requireNonNull(app.getIcon().getConstantState()).newDrawable();
- mMenu.getItem(MENU_ITEM_APP_SELECTOR_IDX).setIcon(drawable);
+ mMenuItemAppSel.setIcon(drawable);
}
private void openAppSelector() {
@@ -471,7 +482,7 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
setSelectedAppIcon(app);
} else {
// no filter
- mMenu.getItem(MENU_ITEM_APP_SELECTOR_IDX).setIcon(mFilterIcon);
+ mMenuItemAppSel.setIcon(mFilterIcon);
mFilterApp = null;
}
diff --git a/app/src/main/java/com/emanuelef/remote_capture/StatsActivity.java b/app/src/main/java/com/emanuelef/remote_capture/StatsActivity.java
new file mode 100644
index 00000000..872acf7c
--- /dev/null
+++ b/app/src/main/java/com/emanuelef/remote_capture/StatsActivity.java
@@ -0,0 +1,104 @@
+package com.emanuelef.remote_capture;
+
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.widget.TextView;
+
+public class StatsActivity extends AppCompatActivity {
+ TextView mBytesSent;
+ TextView mBytesRcvd;
+ TextView mPacketsSent;
+ TextView mPacketsRcvd;
+ TextView mActiveConns;
+ TextView mDroppedConns;
+ TextView mTotConns;
+ TextView mMaxFd;
+ TextView mOpenSocks;
+ TextView mDnsQueries;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_stats);
+
+ setTitle(R.string.stats);
+
+ mBytesSent = findViewById(R.id.bytes_sent);
+ mBytesRcvd = findViewById(R.id.bytes_rcvd);
+ mPacketsSent = findViewById(R.id.packets_sent);
+ mPacketsRcvd = findViewById(R.id.packets_rcvd);
+ mActiveConns = findViewById(R.id.active_connections);
+ mDroppedConns = findViewById(R.id.dropped_connections);
+ mTotConns = findViewById(R.id.tot_connections);
+ mMaxFd = findViewById(R.id.max_fd);
+ mOpenSocks = findViewById(R.id.open_sockets);
+ mDnsQueries = findViewById(R.id.dns_queries);
+
+ LocalBroadcastManager bcast_man = LocalBroadcastManager.getInstance(this);
+
+ /* Register for updates */
+ bcast_man.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateVPNStats(intent);
+ }
+ }, new IntentFilter(CaptureService.ACTION_STATS_DUMP));
+ bcast_man.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateTrafficStats(intent);
+ }
+ }, new IntentFilter(CaptureService.ACTION_TRAFFIC_STATS_UPDATE));
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ CaptureService.askStatsDump();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case android.R.id.home:
+ /* Make the back button in the action bar behave like the back button */
+ onBackPressed();
+ return true;
+ default:
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void updateVPNStats(Intent intent) {
+ VPNStats stats = (VPNStats) intent.getSerializableExtra("value");
+
+ mActiveConns.setText(Utils.formatNumber(this, stats.active_conns));
+ mDroppedConns.setText(Utils.formatNumber(this, stats.num_dropped_conns));
+ mTotConns.setText(Utils.formatNumber(this, stats.tot_conns));
+ mMaxFd.setText(Utils.formatNumber(this, stats.max_fd));
+ mOpenSocks.setText(Utils.formatNumber(this, stats.num_open_sockets));
+ mDnsQueries.setText(Utils.formatNumber(this, stats.num_dns_queries));
+ }
+
+ private void updateTrafficStats(Intent intent) {
+ long bytes_sent = intent.getLongExtra(CaptureService.TRAFFIC_STATS_UPDATE_SENT_BYTES, 0);
+ long bytes_rcvd = intent.getLongExtra(CaptureService.TRAFFIC_STATS_UPDATE_RCVD_BYTES, 0);
+ int pkts_sent = intent.getIntExtra(CaptureService.TRAFFIC_STATS_UPDATE_SENT_PKTS, 0);
+ int pkts_rcvd = intent.getIntExtra(CaptureService.TRAFFIC_STATS_UPDATE_RCVD_PKTS, 0);
+
+ mBytesSent.setText(Utils.formatBytes(bytes_sent));
+ mBytesRcvd.setText(Utils.formatBytes(bytes_rcvd));
+ mPacketsSent.setText(Utils.formatPkts(pkts_sent));
+ mPacketsRcvd.setText(Utils.formatPkts(pkts_rcvd));
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/emanuelef/remote_capture/StatusFragment.java b/app/src/main/java/com/emanuelef/remote_capture/StatusFragment.java
index 88101dc0..eb2d06f8 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/StatusFragment.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/StatusFragment.java
@@ -75,6 +75,13 @@ public class StatusFragment extends Fragment implements AppStateListener {
mCaptureStatus = view.findViewById(R.id.status_view);
mPrefs = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ mCaptureStatus.setOnClickListener(v -> {
+ if(mActivity.getState() == AppState.running) {
+ Intent intent = new Intent(getActivity(), StatsActivity.class);
+ startActivity(intent);
+ }
+ });
+
// Make URLs clickable
mCollectorInfo.setMovementMethod(LinkMovementMethod.getInstance());
diff --git a/app/src/main/java/com/emanuelef/remote_capture/Utils.java b/app/src/main/java/com/emanuelef/remote_capture/Utils.java
index e433eb15..38fe439b 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/Utils.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/Utils.java
@@ -47,6 +47,7 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
public class Utils {
static String formatBytes(long bytes) {
@@ -73,6 +74,11 @@ public class Utils {
return String.format("%.1f %s", ((float)pkts) / divisor, suffix);
}
+ static String formatNumber(Context context, long num) {
+ Locale locale = context.getResources().getConfiguration().locale;
+ return String.format(locale, "%,d", num);
+ }
+
static String formatDuration(long seconds) {
if(seconds == 0)
return "< 1 s";
diff --git a/app/src/main/java/com/emanuelef/remote_capture/VPNStats.java b/app/src/main/java/com/emanuelef/remote_capture/VPNStats.java
new file mode 100644
index 00000000..9d33ba38
--- /dev/null
+++ b/app/src/main/java/com/emanuelef/remote_capture/VPNStats.java
@@ -0,0 +1,23 @@
+package com.emanuelef.remote_capture;
+
+import java.io.Serializable;
+
+public class VPNStats implements Serializable {
+ int num_dropped_conns;
+ int num_open_sockets;
+ int max_fd;
+ int active_conns;
+ int tot_conns;
+ int num_dns_queries;
+
+ /* Invoked by native code */
+ public void setData(int _num_dropped_conns, int _num_open_sockets, int _max_fd,
+ int _active_conns, int _tot_conns, int _num_dns_queries) {
+ num_dropped_conns = _num_dropped_conns;
+ num_open_sockets = _num_open_sockets;
+ max_fd = _max_fd;
+ active_conns = _active_conns;
+ tot_conns = _tot_conns;
+ num_dns_queries = _num_dns_queries;
+ }
+}
diff --git a/app/src/main/jni/vpnproxy-jni/vpnproxy.c b/app/src/main/jni/vpnproxy-jni/vpnproxy.c
index a1560da6..7882637d 100644
--- a/app/src/main/jni/vpnproxy-jni/vpnproxy.c
+++ b/app/src/main/jni/vpnproxy-jni/vpnproxy.c
@@ -69,17 +69,23 @@ typedef struct jni_methods {
jmethodID connInit;
jmethodID connSetData;
jmethodID sendServiceStatus;
+ jmethodID sendStatsDump;
+ jmethodID statsInit;
+ jmethodID statsSetData;
} jni_methods_t;
typedef struct jni_classes {
jclass vpn_service;
jclass conn;
+ jclass stats;
} jni_classes_t;
static jni_classes_t cls;
static jni_methods_t mids;
static bool running = false;
static bool dump_connections_now = false;
+static bool dump_vpn_stats_now = false;
+static bool dump_capture_stats_now = false;
/* TCP/IP packet to hold the mitmproxy header */
static char mitmproxy_pkt_buffer[] = {
@@ -569,6 +575,7 @@ static int check_dns_req_dnat(struct vpnproxy_data *proxy, zdtun_pkt_t *pkt, zdt
* here as zdtun will proxy the connection.
*/
zdtun_conn_dnat(conn, proxy->public_dns, 0);
+ proxy->num_dns_requests++;
return(1);
}
@@ -700,18 +707,27 @@ static int connection_dumper(zdtun_t *tun, const zdtun_5tuple_t *conn_info, conn
jobject dst_string = (*env)->NewStringUTF(env, dstip);
jobject conn_descriptor = (*env)->NewObject(env, cls.conn, mids.connInit);
- /* NOTE: as an alternative to pass all the params into the constructor, GetFieldID and
- * SetIntField like methods could be used. */
- (*env)->CallVoidMethod(env, conn_descriptor, mids.connSetData,
- src_string, dst_string, info_string, url_string, proto_string,
- conn_info->ipproto, ntohs(conn_info->src_port), ntohs(conn_info->dst_port),
- data->first_seen, data->last_seen, data->sent_bytes, data->rcvd_bytes,
- data->sent_pkts, data->rcvd_pkts, data->uid, data->incr_id);
- jniCheckException(env);
+ if((conn_descriptor != NULL) && !jniCheckException(env)) {
+ /* NOTE: as an alternative to pass all the params into the constructor, GetFieldID and
+ * SetIntField like methods could be used. */
+ (*env)->CallVoidMethod(env, conn_descriptor, mids.connSetData,
+ src_string, dst_string, info_string, url_string, proto_string,
+ conn_info->ipproto, ntohs(conn_info->src_port),
+ ntohs(conn_info->dst_port),
+ data->first_seen, data->last_seen, data->sent_bytes,
+ data->rcvd_bytes,
+ data->sent_pkts, data->rcvd_pkts, data->uid, data->incr_id);
+ jniCheckException(env);
+
+ /* Add the connection to the array */
+ (*env)->SetObjectArrayElement(env, dump_data->connections, dump_data->idx++,
+ conn_descriptor);
+ jniCheckException(env);
+
+ DeleteLocalRef(env, conn_descriptor);
+ } else
+ log_android(ANDROID_LOG_ERROR, "NewObject(ConnDescriptor) failed");
- /* Add the connection to the array */
- (*env)->SetObjectArrayElement(env, dump_data->connections, dump_data->idx++, conn_descriptor);
- jniCheckException(env);
data->notified = true;
DeleteLocalRef(env, info_string);
@@ -719,7 +735,6 @@ static int connection_dumper(zdtun_t *tun, const zdtun_5tuple_t *conn_info, conn
DeleteLocalRef(env, proto_string);
DeleteLocalRef(env, src_string);
DeleteLocalRef(env, dst_string);
- DeleteLocalRef(env, conn_descriptor);
/* Continue */
return(0);
@@ -771,6 +786,31 @@ static void sendConnectionsDump(zdtun_t *tun, vpnproxy_data_t *proxy) {
/* ******************************************************* */
+static void sendVPNStats(const vpnproxy_data_t *proxy, const zdtun_statistics_t *stats) {
+ JNIEnv *env = proxy->env;
+ int active_conns = stats->num_icmp_conn + stats->num_tcp_conn + stats->num_udp_conn;
+ int tot_conns = stats->num_icmp_opened + stats->num_tcp_opened + stats->num_udp_opened;
+
+ jobject stats_obj = (*env)->NewObject(env, cls.stats, mids.statsInit);
+
+ if((stats_obj == NULL) || jniCheckException(env)) {
+ log_android(ANDROID_LOG_ERROR, "NewObject(VPNStats) failed");
+ return;
+ }
+
+ (*env)->CallVoidMethod(env, stats_obj, mids.statsSetData, proxy->num_failed_connections,
+ stats->num_open_sockets, stats->all_max_fd, active_conns, tot_conns, proxy->num_dns_requests);
+
+ if(!jniCheckException(env)) {
+ (*env)->CallVoidMethod(env, proxy->vpn_service, mids.sendStatsDump, stats_obj);
+ jniCheckException(env);
+ }
+
+ DeleteLocalRef(env, stats_obj);
+}
+
+/* ******************************************************* */
+
static void notifyServiceStatus(vpnproxy_data_t *proxy, const char *status) {
JNIEnv *env = proxy->env;
jstring status_str;
@@ -832,6 +872,7 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) {
/* Classes */
cls.vpn_service = vpn_class;
cls.conn = jniFindClass(env, "com/emanuelef/remote_capture/ConnDescriptor");
+ cls.stats = jniFindClass(env, "com/emanuelef/remote_capture/VPNStats");
/* Methods */
mids.getApplicationByUid = jniGetMethodID(env, vpn_class, "getApplicationByUid", "(I)Ljava/lang/String;"),
@@ -839,11 +880,14 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) {
mids.dumpPcapData = jniGetMethodID(env, vpn_class, "dumpPcapData", "([B)V");
mids.sendCaptureStats = jniGetMethodID(env, vpn_class, "sendCaptureStats", "(JJII)V");
mids.sendConnectionsDump = jniGetMethodID(env, vpn_class, "sendConnectionsDump", "([Lcom/emanuelef/remote_capture/ConnDescriptor;)V");
+ mids.sendStatsDump = jniGetMethodID(env, vpn_class, "sendStatsDump", "(Lcom/emanuelef/remote_capture/VPNStats;)V");
mids.sendServiceStatus = jniGetMethodID(env, vpn_class, "sendServiceStatus", "(Ljava/lang/String;)V");
- mids.connInit = jniGetMethodID(env, vpn_class, "", "()V");
+ mids.connInit = jniGetMethodID(env, cls.conn, "", "()V");
mids.connSetData = jniGetMethodID(env, cls.conn, "setData",
/* NOTE: must match ConnDescriptor::setData */
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIJJJJIIII)V");
+ mids.statsInit = jniGetMethodID(env, cls.stats, "", "()V");
+ mids.statsSetData = jniGetMethodID(env, cls.stats, "setData", "(IIIIII)V");
vpnproxy_data_t proxy = {
.tapfd = tapfd,
@@ -975,7 +1019,8 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) {
zdtun_conn_t *conn = zdtun_lookup(tun, &pkt.tuple, 1 /* create if not exists */);
if (!conn) {
- log_android(ANDROID_LOG_DEBUG, "zdtun_lookup failed");
+ proxy.num_failed_connections++;
+ log_android(ANDROID_LOG_ERROR, "zdtun_lookup failed");
goto housekeeping;
}
@@ -989,8 +1034,8 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) {
check_tls_mitm(tun, &proxy, &pkt, conn);
if ((rc = zdtun_forward(tun, &pkt, conn)) != 0) {
- /* NOTE: rc -1 is currently returned for unhandled non-IPv4 flows */
- log_android(ANDROID_LOG_DEBUG, "zdtun_forward failed with code %d", rc);
+ log_android(ANDROID_LOG_ERROR, "zdtun_forward failed with code %d", rc);
+ proxy.num_failed_connections++;
zdtun_destroy_conn(tun, conn);
goto housekeeping;
@@ -1004,7 +1049,8 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) {
housekeeping:
if(proxy.capture_stats.new_stats
- && ((now_ms - proxy.capture_stats.last_update_ms) >= CAPTURE_STATS_UPDATE_FREQUENCY_MS)) {
+ && ((now_ms - proxy.capture_stats.last_update_ms) >= CAPTURE_STATS_UPDATE_FREQUENCY_MS) || dump_capture_stats_now) {
+ dump_capture_stats_now = false;
sendCaptureStats(&proxy);
proxy.capture_stats.new_stats = false;
proxy.capture_stats.last_update_ms = now_ms;
@@ -1015,18 +1061,15 @@ housekeeping:
} else if((proxy.java_dump.buffer_idx > 0)
&& (now_ms - proxy.java_dump.last_dump_ms) >= MAX_JAVA_DUMP_DELAY_MS) {
javaPcapDump(tun, &proxy);
- } else if(now_ms >= next_purge_ms) {
+ } else if((now_ms >= next_purge_ms) || dump_vpn_stats_now) {
+ dump_vpn_stats_now = false;
zdtun_statistics_t stats;
zdtun_purge_expired(tun, now_ms/1000);
next_purge_ms = now_ms + PERIODIC_PURGE_TIMEOUT_MS;
zdtun_get_stats(tun, &stats);
- log_android(ANDROID_LOG_INFO, "open sockets: %u, open connections: %u, tot connections: %u, all_max_fd: %d",
- stats.num_open_sockets,
- stats.num_icmp_conn + stats.num_tcp_conn + stats.num_udp_conn,
- stats.num_icmp_opened + stats.num_tcp_opened + stats.num_udp_opened,
- stats.all_max_fd);
+ sendVPNStats(&proxy, &stats);
}
}
@@ -1079,4 +1122,12 @@ JNIEXPORT void JNICALL
Java_com_emanuelef_remote_1capture_CaptureService_askConnectionsDump(JNIEnv *env, jclass clazz) {
if(running)
dump_connections_now = true;
+}
+
+JNIEXPORT void JNICALL
+Java_com_emanuelef_remote_1capture_CaptureService_askStatsDump(JNIEnv *env, jclass clazz) {
+ if(running) {
+ dump_vpn_stats_now = true;
+ dump_capture_stats_now = true;
+ }
}
\ No newline at end of file
diff --git a/app/src/main/jni/vpnproxy-jni/vpnproxy.h b/app/src/main/jni/vpnproxy-jni/vpnproxy.h
index bdc42e9a..b123002e 100644
--- a/app/src/main/jni/vpnproxy-jni/vpnproxy.h
+++ b/app/src/main/jni/vpnproxy-jni/vpnproxy.h
@@ -75,6 +75,8 @@ typedef struct vpnproxy_data {
u_int32_t cur_notif_pending;
u_int32_t notif_pending_size;
uint64_t now_ms;
+ u_int32_t num_failed_connections;
+ u_int32_t num_dns_requests;
struct {
u_int32_t collector_addr;
diff --git a/app/src/main/res/layout/activity_stats.xml b/app/src/main/res/layout/activity_stats.xml
new file mode 100644
index 00000000..37964be8
--- /dev/null
+++ b/app/src/main/res/layout/activity_stats.xml
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/settings_menu.xml b/app/src/main/res/menu/settings_menu.xml
index c92f6ce1..5bd7b891 100644
--- a/app/src/main/res/menu/settings_menu.xml
+++ b/app/src/main/res/menu/settings_menu.xml
@@ -25,6 +25,13 @@
app:showAsAction="never"
/>
+
+
- VPN setup failed
App "%1$s" not found
Apps loading in progress, please wait
+ Stats
+ Active Connections
+ Dropped Connections
+ Total Connections
+ Open Sockets
+ Max FD
+ Bytes Sent
+ Bytes Received
+ Packets Sent
+ Packets Received
+ DNS Queries