Improve connection overview

- Show the decryption status
- Show payload size
- Show warning if connection start is not seen (root)
This commit is contained in:
emanuele-f 2022-04-14 15:00:42 +02:00
parent 06a98d3486
commit 05ca7a4617
13 changed files with 136 additions and 43 deletions

View File

@ -59,6 +59,7 @@ import android.text.SpannableString;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
@ -67,6 +68,7 @@ import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.SearchView;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
import com.emanuelef.remote_capture.interfaces.TextAdapter;
@ -1083,4 +1085,28 @@ public class Utils {
return sb.toString();
}
public static void setDecryptionIcon(ImageView icon, ConnectionDescriptor conn) {
int color;
switch(conn.getDecryptionStatus()) {
case DECRYPTED:
color = R.color.ok;
break;
case NOT_DECRYPTABLE:
color = R.color.warning;
break;
case TLS_ERROR:
color = R.color.danger;
break;
default:
color = R.color.lightGray;
}
Context context = icon.getContext();
int resid = (conn.isCleartext() || conn.isDecrypted()) ? R.drawable.ic_lock_open : R.drawable.ic_lock;
icon.setColorFilter(ContextCompat.getColor(context, color));
icon.setImageDrawable(ContextCompat.getDrawable(context, resid));
}
}

View File

@ -158,7 +158,7 @@ public class ConnectionDetailsActivity extends BaseActivity implements Connectio
case POS_HTTP:
return R.string.http;
case POS_RAW_PAYLOAD:
return R.string.raw_payload;
return R.string.payload;
case POS_OVERVIEW:
default:
return R.string.overview;

View File

@ -84,8 +84,6 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
TextView lastSeen;
//FlagImageView countryFlag;
final String mProtoAndPort;
final Drawable lockOpen;
final Drawable lockClosed;
ViewHolder(View itemView) {
super(itemView);
@ -104,9 +102,6 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
Context context = itemView.getContext();
mProtoAndPort = context.getString(R.string.proto_and_port);
lockOpen = ContextCompat.getDrawable(context, R.drawable.ic_lock_open);
lockClosed = ContextCompat.getDrawable(context, R.drawable.ic_lock);
}
@SuppressWarnings("deprecation")
@ -161,25 +156,8 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
blockedInd.setVisibility(conn.is_blocked ? View.VISIBLE : View.GONE);
if(CaptureService.isDecryptingTLS()) {
switch(conn.getDecryptionStatus()) {
case CLEARTEXT:
case DECRYPTION_IN_PROGRESS:
color = R.color.lightGray;
break;
case DECRYPTED:
color = R.color.ok;
break;
case NOT_DECRYPTABLE:
color = R.color.warning;
break;
case TLS_ERROR:
color = R.color.danger;
break;
}
decryptionInd.setVisibility(View.VISIBLE);
decryptionInd.setColorFilter(ContextCompat.getColor(context, color));
decryptionInd.setImageDrawable((conn.isCleartext() || conn.isDecrypted()) ? lockOpen : lockClosed);
Utils.setDecryptionIcon(decryptionInd, conn);
} else
decryptionInd.setVisibility(View.GONE);
}

View File

@ -37,6 +37,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import com.emanuelef.remote_capture.AppsResolver;
@ -53,14 +54,17 @@ public class ConnectionOverview extends Fragment implements ConnectionDetailsAct
private ConnectionDescriptor mConn;
private TableLayout mTable;
private TextView mBytesView;
private TextView mPayload;
private TextView mPacketsView;
private TextView mDurationView;
private TextView mBlockedPkts;
private View mBlockedPktsRow;
private TextView mStatus;
private TextView mDecStatus;
private ImageView mDecIcon;
private TextView mFirstSeen;
private TextView mLastSeen;
private TextView mTcpFlags;
//private TextView mTcpFlags;
private TextView mError;
private ImageView mBlacklistedIp;
private ImageView mBlacklistedHost;
@ -102,15 +106,18 @@ public class ConnectionOverview extends Fragment implements ConnectionDetailsAct
FlagImageView country_flag = view.findViewById(R.id.country_flag);
TextView asn = view.findViewById(R.id.asn);
mTable = view.findViewById(R.id.table);
mPayload = view.findViewById(R.id.detail_payload);
mBytesView = view.findViewById(R.id.detail_bytes);
mPacketsView = view.findViewById(R.id.detail_packets);
mBlockedPkts = view.findViewById(R.id.blocked_pkts);
mBlockedPktsRow = view.findViewById(R.id.blocked_row);
mDurationView = view.findViewById(R.id.detail_duration);
mStatus = view.findViewById(R.id.detail_status);
mDecStatus = view.findViewById(R.id.detail_decryption_status);
mDecIcon = view.findViewById(R.id.decryption_icon);
mFirstSeen = view.findViewById(R.id.first_seen);
mLastSeen = view.findViewById(R.id.last_seen);
mTcpFlags = view.findViewById(R.id.tcp_flags);
//mTcpFlags = view.findViewById(R.id.tcp_flags);
mError = view.findViewById(R.id.error_msg);
mBlacklistedIp = view.findViewById(R.id.blacklisted_ip);
mBlacklistedHost = view.findViewById(R.id.blacklisted_host);
@ -123,7 +130,7 @@ public class ConnectionOverview extends Fragment implements ConnectionDetailsAct
if(mConn != null) {
String l4proto = Utils.proto2str(mConn.ipproto);
//if(l4proto.equals("TCP"))
// findViewById(R.id.tcp_flags_row).setVisibility(View.VISIBLE);
// view.findViewById(R.id.tcp_flags_row).setVisibility(View.VISIBLE);
if(!mConn.l7proto.equals(l4proto))
proto.setText(String.format(getResources().getString(R.string.app_and_proto), mConn.l7proto, l4proto));
@ -154,6 +161,8 @@ public class ConnectionOverview extends Fragment implements ConnectionDetailsAct
else
appLabel.setText(uid_str);
view.findViewById(R.id.decryption_status_row).setVisibility(CaptureService.isDecryptingTLS() ? View.VISIBLE : View.GONE);
if(!mConn.url.isEmpty())
url.setText(mConn.url);
else
@ -216,6 +225,9 @@ public class ConnectionOverview extends Fragment implements ConnectionDetailsAct
@Override
public void connectionUpdated() {
Context context = mBytesView.getContext();
mPayload.setText(Utils.formatBytes(mConn.payload_length));
mBytesView.setText(String.format(getResources().getString(R.string.rcvd_and_sent), Utils.formatBytes(mConn.rcvd_bytes), Utils.formatBytes(mConn.sent_bytes)));
mPacketsView.setText(String.format(getResources().getString(R.string.rcvd_and_sent), Utils.formatIntShort(mConn.rcvd_pkts), Utils.formatIntShort(mConn.sent_pkts)));
@ -228,13 +240,20 @@ public class ConnectionOverview extends Fragment implements ConnectionDetailsAct
mFirstSeen.setText(Utils.formatEpochMillis(mActivity, mConn.first_seen));
mLastSeen.setText(Utils.formatEpochMillis(mActivity, mConn.last_seen));
mStatus.setText(mConn.getStatusLabel(mActivity));
mTcpFlags.setText(Utils.tcpFlagsToStr(mConn.getRcvdTcpFlags()) + " <- " + Utils.tcpFlagsToStr(mConn.getSentTcpFlags()));
mDecStatus.setText(mConn.getDecryptionStatusLabel(mActivity));
Utils.setDecryptionIcon(mDecIcon, mConn);
//mTcpFlags.setText(Utils.tcpFlagsToStr(mConn.getRcvdTcpFlags()) + " <- " + Utils.tcpFlagsToStr(mConn.getSentTcpFlags()));
mBlacklistedIp.setVisibility(mConn.isBlacklistedIp() ? View.VISIBLE : View.GONE);
mBlacklistedHost.setVisibility(mConn.isBlacklistedHost() ? View.VISIBLE : View.GONE);
if(mConn.tls_error != null) {
mError.setTextColor(ContextCompat.getColor(context, R.color.danger));
mError.setText(mConn.tls_error);
mError.setVisibility(View.VISIBLE);
} else if(!mConn.hasSeenStart()) {
mError.setTextColor(ContextCompat.getColor(context, R.color.warning));
mError.setText(context.getString(R.string.connection_start_not_seen));
mError.setVisibility(View.VISIBLE);
}
}
}

View File

@ -84,6 +84,7 @@ public class ConnectionDescriptor {
/* Data */
public long first_seen;
public long last_seen;
public long payload_length;
public long sent_bytes;
public long rcvd_bytes;
public int sent_pkts;
@ -135,6 +136,7 @@ public class ConnectionDescriptor {
public void processUpdate(ConnectionUpdate update) {
// The "update_type" is used to limit the amount of data sent via the JNI
if((update.update_type & ConnectionUpdate.UPDATE_STATS) != 0) {
payload_length = update.payload_length;
sent_bytes = update.sent_bytes;
rcvd_bytes = update.rcvd_bytes;
sent_pkts = update.sent_pkts;
@ -145,7 +147,7 @@ public class ConnectionDescriptor {
blacklisted_ip = (update.status & 0x0100) != 0;
blacklisted_host = (update.status & 0x0200) != 0;
last_seen = update.last_seen;
tcp_flags = update.tcp_flags;
tcp_flags = update.tcp_flags; // NOTE: only for root capture
// see MitmReceiver.handlePayload
if((status == ConnectionDescriptor.CONN_STATUS_CLOSED) && (tls_error != null))
@ -239,7 +241,7 @@ public class ConnectionDescriptor {
int resid;
switch (status) {
case CLEARTEXT: resid = R.string.cleartext; break;
case CLEARTEXT: resid = R.string.not_encrypted; break;
case NOT_DECRYPTABLE: resid = R.string.not_decryptable; break;
case DECRYPTED: resid = R.string.decrypted; break;
case DECRYPTION_IN_PROGRESS: resid = R.string.in_progress; break;
@ -328,6 +330,13 @@ public class ConnectionDescriptor {
public String getHttpRequest() { return getHttp(true); }
public String getHttpResponse() { return getHttp(false); }
public boolean hasSeenStart() {
if((ipproto != 6 /* TCP */) || !CaptureService.isCapturingAsRoot())
return true;
return (getSentTcpFlags() & 0x2) != 0; // SYN
}
@Override
public @NonNull String toString() {
return "[proto=" + ipproto + "/" + l7proto + "]: " + src_ip + ":" + src_port + " -> " +

View File

@ -33,6 +33,7 @@ public class ConnectionUpdate {
/* set if update_type & UPDATE_STATS */
public long last_seen;
public long payload_length;
public long sent_bytes;
public long rcvd_bytes;
public int sent_pkts;
@ -55,12 +56,13 @@ public class ConnectionUpdate {
incr_id = _incr_id;
}
public void setStats(long _last_seen, long _sent_bytes, long _rcvd_bytes,
public void setStats(long _last_seen, long _payload_length, long _sent_bytes, long _rcvd_bytes,
int _sent_pkts, int _rcvd_pkts, int _blocked_pkts,
int _tcp_flags, int _status) {
update_type |= UPDATE_STATS;
last_seen = _last_seen;
payload_length = _payload_length;
sent_bytes = _sent_bytes;
rcvd_bytes = _rcvd_bytes;
sent_pkts = _sent_pkts;

View File

@ -151,7 +151,7 @@ static jobject getConnUpdate(pcapdroid_t *pd, const conn_and_tuple_t *conn) {
bool blocked = data->to_block && !pd->root_capture; // currently can only block connections in non-root mode
(*env)->CallVoidMethod(env, update, mids.connUpdateSetStats, data->last_seen,
data->sent_bytes, data->rcvd_bytes, data->sent_pkts, data->rcvd_pkts, data->blocked_pkts,
data->payload_length, data->sent_bytes, data->rcvd_bytes, data->sent_pkts, data->rcvd_pkts, data->blocked_pkts,
(data->tcp_flags[0] << 8) | data->tcp_flags[1],
(blocked << 10) | (data->blacklisted_domain << 9) |
(data->blacklisted_ip << 8) | (data->status & 0xFF));
@ -510,7 +510,7 @@ Java_com_emanuelef_remote_1capture_CaptureService_runPacketLoop(JNIEnv *env, jcl
mids.connInit = jniGetMethodID(env, cls.conn, "<init>", "(IIILjava/lang/String;Ljava/lang/String;IIIIIZJ)V");
mids.connProcessUpdate = jniGetMethodID(env, cls.conn, "processUpdate", "(Lcom/emanuelef/remote_capture/model/ConnectionUpdate;)V");
mids.connUpdateInit = jniGetMethodID(env, cls.conn_update, "<init>", "(I)V");
mids.connUpdateSetStats = jniGetMethodID(env, cls.conn_update, "setStats", "(JJJIIIII)V");
mids.connUpdateSetStats = jniGetMethodID(env, cls.conn_update, "setStats", "(JJJJIIIII)V");
mids.connUpdateSetInfo = jniGetMethodID(env, cls.conn_update, "setInfo", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V");
mids.connUpdateSetPayload = jniGetMethodID(env, cls.conn_update, "setPayload", "(Ljava/util/ArrayList;Z)V");
mids.statsInit = jniGetMethodID(env, cls.stats, "<init>", "()V");

View File

@ -1071,6 +1071,8 @@ void pd_account_stats(pcapdroid_t *pd, pkt_context_t *pctx) {
zdtun_pkt_t *pkt = pctx->pkt;
pd_conn_t *data = pctx->data;
data->payload_length += pkt->l7_len;
if(pctx->is_tx) {
data->sent_pkts++;
data->sent_bytes += pkt->len;

View File

@ -90,6 +90,7 @@ typedef struct {
jlong first_seen;
jlong last_seen;
jlong payload_length;
jlong sent_bytes;
jlong rcvd_bytes;
jint sent_pkts;

View File

@ -123,7 +123,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="@id/l7proto"
tools:tint="@color/ok"
android:contentDescription="@string/encrypted"
tools:src="@drawable/ic_lock_open" />
<TextView

View File

@ -270,25 +270,61 @@
android:textIsSelectable="true"
tools:text="ASN_1 - Public" />
</TableRow>
<TableRow
android:id="@+id/decryption_status_row"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="4dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.25"
android:textStyle="bold"
android:text="@string/bytes" />
android:text="@string/decryption" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.75"
android:gravity="center_vertical">
<ImageView
android:id="@+id/decryption_icon"
tools:tint="@color/ok"
tools:src="@drawable/ic_lock_open"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="-2dp"
android:layout_marginEnd="2dp"
android:adjustViewBounds="true"
android:paddingVertical="2dp" />
<TextView
android:id="@+id/detail_decryption_status"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.75"
android:textIsSelectable="true"
tools:text="Decrypted" />
</LinearLayout>
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="4dp"
android:layout_marginTop="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.25"
android:textStyle="bold"
android:text="@string/network_traffic" />
<TextView
android:id="@+id/detail_bytes"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.75"
android:textIsSelectable="true"
tools:text="1.5 MB down - 0.1 up" />
tools:text="1.5 MB received - 0.1 sent" />
</TableRow>
<TableRow
@ -328,7 +364,7 @@
android:layout_height="wrap_content"
android:layout_weight="0.75"
android:textIsSelectable="true"
tools:text="1.1 K down - 98 down" />
tools:text="1.1 K received - 98 sent" />
</TableRow>
<TableRow
@ -353,6 +389,25 @@
tools:text="11 pkts" />
</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.25"
android:textStyle="bold"
android:text="@string/payload" />
<TextView
android:id="@+id/detail_payload"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.75"
android:textIsSelectable="true"
tools:text="0.6 MB" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="0dp"

View File

@ -292,7 +292,7 @@
<string name="export_failed">Export failed</string>
<string name="connection">Connection</string>
<string name="encrypted">Encrypted</string>
<string name="cleartext">Cleartext</string>
<string name="not_encrypted">Not encrypted</string>
<string name="request">Request</string>
<string name="response">Response</string>
<string name="overview">Overview</string>
@ -301,7 +301,7 @@
<string name="payload_truncated">Payload is truncated. Enable \"%1$s\" to show it in full</string>
<string name="websocket">WebSocket</string>
<string name="http">HTTP</string>
<string name="raw_payload">Raw</string>
<string name="payload">Payload</string>
<string name="tx_direction">TX</string>
<string name="rx_direction">RX</string>
<string name="full_payload">Full payload</string>
@ -311,4 +311,6 @@
<string name="decryption">Decryption</string>
<string name="decryption_filter">Decryption: %1$s</string>
<string name="in_progress">In progress</string>
<string name="connection_start_not_seen">PCAPdroid has not seen the start of this connection. Some information may be missing</string>
<string name="network_traffic">Traffic</string>
</resources>

View File

@ -446,7 +446,7 @@ public class ConnectionsAdapterTest {
ConnectionUpdate update = new ConnectionUpdate(incr_id);
if(tp.equals(UpdateType.UPDATE_STATS))
update.setStats(0, 10, 10, 1, 1,
update.setStats(0, 0, 10, 10, 1, 1,
0, 0, ConnectionDescriptor.CONN_STATUS_CONNECTED);
else
update.setInfo("example.org", null, "TLS", ConnectionUpdate.UPDATE_INFO_FLAG_ENCRYPTED_L7);
@ -525,4 +525,4 @@ public class ConnectionsAdapterTest {
return notified;
}
}
}