mirror of
https://github.com/emanuele-f/PCAPdroid.git
synced 2026-06-16 21:10:57 +08:00
Add runtime stats view
This commit is contained in:
parent
e63ea8fa14
commit
dc31cbcb59
@ -2,6 +2,11 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.emanuelef.remote_capture">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" /> <!-- https://developer.android.com/preview/privacy/package-visibility -->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
@ -9,10 +14,11 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name=".StatsActivity"></activity>
|
||||
<activity
|
||||
android:name=".ConnectionDetails"
|
||||
android:launchMode="singleTop"
|
||||
android:label="@string/connection_details">
|
||||
android:label="@string/connection_details"
|
||||
android:launchMode="singleTop">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".MainActivity" />
|
||||
@ -38,10 +44,4 @@
|
||||
</service>
|
||||
</application>
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<!-- https://developer.android.com/preview/privacy/package-visibility -->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
</manifest>
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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<List<AppDescriptor>> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
23
app/src/main/java/com/emanuelef/remote_capture/VPNStats.java
Normal file
23
app/src/main/java/com/emanuelef/remote_capture/VPNStats.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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, "<init>", "()V");
|
||||
mids.connInit = jniGetMethodID(env, cls.conn, "<init>", "()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, "<init>", "()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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
194
app/src/main/res/layout/activity_stats.xml
Normal file
194
app/src/main/res/layout/activity_stats.xml
Normal file
@ -0,0 +1,194 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<TableLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp">
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.60"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/bytes_sent" />
|
||||
<TextView
|
||||
android:id="@+id/bytes_sent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.40"
|
||||
android:textIsSelectable="true" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.60"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/bytes_rcvd" />
|
||||
<TextView
|
||||
android:id="@+id/bytes_rcvd"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.40"
|
||||
android:textIsSelectable="true" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.60"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/packets_sent" />
|
||||
<TextView
|
||||
android:id="@+id/packets_sent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.40"
|
||||
android:textIsSelectable="true" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.60"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/packets_rcvd" />
|
||||
<TextView
|
||||
android:id="@+id/packets_rcvd"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.40"
|
||||
android:textIsSelectable="true" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginTop="20dp">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.60"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/active_connections" />
|
||||
<TextView
|
||||
android:id="@+id/active_connections"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.40"
|
||||
android:textIsSelectable="true" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.60"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/dropped_connections" />
|
||||
<TextView
|
||||
android:id="@+id/dropped_connections"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.40"
|
||||
android:textIsSelectable="true" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.60"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/tot_connections" />
|
||||
<TextView
|
||||
android:id="@+id/tot_connections"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.40"
|
||||
android:textIsSelectable="true" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.60"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/dns_queries" />
|
||||
<TextView
|
||||
android:id="@+id/dns_queries"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.40"
|
||||
android:textIsSelectable="true" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.60"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/open_sockets" />
|
||||
<TextView
|
||||
android:id="@+id/open_sockets"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.40"
|
||||
android:textIsSelectable="true" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.60"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/max_file_descriptor" />
|
||||
<TextView
|
||||
android:id="@+id/max_fd"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.40"
|
||||
android:textIsSelectable="true" />
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
|
||||
</ScrollView>
|
||||
@ -25,6 +25,13 @@
|
||||
app:showAsAction="never"
|
||||
/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_stats"
|
||||
android:title="@string/stats"
|
||||
android:orderInCategory="105"
|
||||
app:showAsAction="never"
|
||||
/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_open_user_guide"
|
||||
android:title="@string/user_guide"
|
||||
|
||||
@ -68,5 +68,16 @@
|
||||
<string name="vpn_setup_failed">VPN setup failed</string>
|
||||
<string name="app_not_found">App "%1$s" not found</string>
|
||||
<string name="apps_loading_please_wait">Apps loading in progress, please wait</string>
|
||||
<string name="stats">Stats</string>
|
||||
<string name="active_connections">Active Connections</string>
|
||||
<string name="dropped_connections">Dropped Connections</string>
|
||||
<string name="tot_connections">Total Connections</string>
|
||||
<string name="open_sockets">Open Sockets</string>
|
||||
<string name="max_file_descriptor">Max FD</string>
|
||||
<string name="bytes_sent">Bytes Sent</string>
|
||||
<string name="bytes_rcvd">Bytes Received</string>
|
||||
<string name="packets_sent">Packets Sent</string>
|
||||
<string name="packets_rcvd">Packets Received</string>
|
||||
<string name="dns_queries">DNS Queries</string>
|
||||
</resources>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user