Add runtime stats view

This commit is contained in:
emanuele-f 2021-02-11 18:52:56 +01:00
parent e63ea8fa14
commit dc31cbcb59
12 changed files with 478 additions and 49 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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;
}
}

View File

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

View File

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

View 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>

View File

@ -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"

View File

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