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