diff --git a/.gitignore b/.gitignore
index 37466a32..ace6d18a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,7 +13,7 @@ local.properties
.DS_Store
build/
captures/
-pcap/
+/pcap/
.externalNativeBuild
.cxx
app/release/
diff --git a/app/src/main/java/com/emanuelef/remote_capture/HTTPReassembly.java b/app/src/main/java/com/emanuelef/remote_capture/HTTPReassembly.java
index e2eb8889..6e140b32 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/HTTPReassembly.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/HTTPReassembly.java
@@ -108,8 +108,15 @@ public class HTTPReassembly {
/* The request/response tab shows reassembled HTTP chunks.
* Reassembling chunks is requires when using a content-encoding like gzip since we can only
* decode the data when we have the full chunk and we cannot determine data bounds.
+ *
* When reading data via the MitmReceiver, mitmproxy already performs chunks reassembly and
- * also handles HTTP/2 so that we only get the payload. */
+ * also handles HTTP/2 so that we only get the payload.
+ *
+ * Similarly, also ushark performs HTTP/2 request/reply reassembly, so we always get the whole
+ * HTTP headers and body in a single chunk. With ushark, though, HTTP/2 replies may arrive in
+ * a different order (due to HTTP/2 multiplexing). chunk.stream_id is used by ConnectionDescriptor
+ * to associate the reply to the correct request.
+ */
public void handleChunk(PayloadChunk chunk) {
int body_start = 0;
byte[] payload = chunk.payload;
@@ -158,6 +165,8 @@ public class HTTPReassembly {
}
mFirstChunk.httpPath = path;
+
+ //log_d(mFirstChunk.httpMethod + " " + mFirstChunk.httpPath);
}
} else if (line.startsWith("HTTP/")) {
int first_space = line.indexOf(' ');
@@ -255,6 +264,12 @@ public class HTTPReassembly {
if(!mReassembleChunks)
mReadingHeaders = false;
+ boolean httpRst = false;
+ if (mReadingHeaders && chunk.isHttp2Rst()) {
+ mReadingHeaders = false;
+ httpRst = true;
+ }
+
if(!mReadingHeaders) {
// Reading HTTP body
int body_size = payload.length - body_start;
@@ -349,6 +364,11 @@ public class HTTPReassembly {
to_add.httpQuery = mFirstChunk.httpQuery;
to_add.httpBodyLength = mBodySize;
+ if (httpRst)
+ // this is necessary when mDumpPayload=false, to ensure that
+ // the chunk is marked as HTTP RST
+ to_add.setHttpRst();
+
// Fix the chunk type after upgrade when read from ushark
if (mSwitchedProtocols && (to_add.type == PayloadChunk.ChunkType.HTTP)) {
to_add.type = mWebsocketUpgrade ? PayloadChunk.ChunkType.WEBSOCKET : PayloadChunk.ChunkType.RAW;
@@ -359,6 +379,12 @@ public class HTTPReassembly {
}
mBodySize = 0;
+
+ if ((to_add.type == PayloadChunk.ChunkType.HTTP)) {
+ Log.d(TAG, "Reassembled HTTP " +
+ (to_add.isHttp2Rst() ? "RST" : (to_add.is_sent ? "request" : "response")));
+ }
+
mListener.onChunkReassembled(to_add);
reset(); // mReadingHeaders = true
diff --git a/app/src/main/java/com/emanuelef/remote_capture/HttpLog.java b/app/src/main/java/com/emanuelef/remote_capture/HttpLog.java
index cb9c8b81..5c0c4929 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/HttpLog.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/HttpLog.java
@@ -43,8 +43,10 @@ public class HttpLog {
public String path = "";
public String query = "";
public HttpReply reply;
- public int bodyLength;
- public long timestamp;
+ public int bodyLength = 0;
+ public int streamId = 0;
+ public long timestamp = 0;
+ public boolean httpRst = false;
private int idx = -1;
public HttpRequest(ConnectionDescriptor conn, int firstChunkPos) {
diff --git a/app/src/main/java/com/emanuelef/remote_capture/MitmReceiver.java b/app/src/main/java/com/emanuelef/remote_capture/MitmReceiver.java
index ea227658..06883f80 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/MitmReceiver.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/MitmReceiver.java
@@ -327,7 +327,7 @@ public class MitmReceiver implements Runnable, ConnectionsListener, MitmListener
} else if(type == MsgType.JS_INJECTED) {
conn.js_injected_scripts = new String(message, StandardCharsets.US_ASCII);
} else
- conn.addPayloadChunkMitm(new PayloadChunk(message, getChunkType(type), isSent(type), tstamp));
+ conn.addPayloadChunkMitm(new PayloadChunk(message, getChunkType(type), isSent(type), tstamp, 0));
}
private synchronized void addPendingMessage(PendingMessage pending) {
diff --git a/app/src/main/java/com/emanuelef/remote_capture/adapters/PayloadAdapter.java b/app/src/main/java/com/emanuelef/remote_capture/adapters/PayloadAdapter.java
index 08ce4542..e3631468 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/adapters/PayloadAdapter.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/adapters/PayloadAdapter.java
@@ -126,7 +126,7 @@ public class PayloadAdapter extends RecyclerView.Adapter.
*
- * Copyright 2021-25 - Emanuele Faranda
+ * Copyright 2021-26 - Emanuele Faranda
*/
#include
@@ -406,34 +406,78 @@ static int get_ip_offset(int linktype) {
/* ******************************************************* */
static plain_data_t g_plain_data = {};
+static pkt_context_t *g_cur_ctx = NULL;
-static void handle_tls_data(const unsigned char *plain_data, unsigned int data_len) {
- if (data_len == 0)
- return;
+static void handle_http_data(bool is_tx, uint64_t ms, uint32_t stream_id, const unsigned char *plain_data, unsigned int data_len) {
+ // NOTE: (plain_data == NULL) / (data_len == 0) signals HTTP/2 resets
- //log_w(" %s", plain_data);
- g_plain_data.data = (unsigned char *) pd_realloc(g_plain_data.data, g_plain_data.tot_length + data_len);
- g_plain_data.lengths = (unsigned int *) pd_realloc(g_plain_data.lengths, (g_plain_data.n_items + 1) * sizeof(unsigned int));
- if (!g_plain_data.data || !g_plain_data.lengths) {
+ // allocate the data first, as a failure in this allocation is easier to handle now rather
+ // than after a plain_data_item_t allocation
+ unsigned char *new_item_data_buf = NULL;
+ if (plain_data) {
+ new_item_data_buf = pd_malloc(data_len);
+ if (!new_item_data_buf) {
+ log_e("alloc(http_data) failed[%d]: %s", errno, strerror(errno));
+ return;
+ }
+ }
+
+ plain_data_item_t* new_items = (plain_data_item_t*) pd_realloc(g_plain_data.items, (g_plain_data.n_items + 1) * sizeof(plain_data_item_t));
+ if (!new_items) {
log_e("realloc(tls_data) failed[%d]: %s", errno, strerror(errno));
- if (g_plain_data.data)
- pd_free(g_plain_data.data);
- if (g_plain_data.lengths)
- pd_free(g_plain_data.lengths);
+ if (new_item_data_buf)
+ pd_free(new_item_data_buf);
+ return;
+ }
- g_plain_data.data = NULL;
- g_plain_data.lengths = NULL;
- g_plain_data.tot_length = 0;
- g_plain_data.n_items = 0;
- } else {
- memcpy(g_plain_data.data + g_plain_data.tot_length, plain_data, data_len);
- g_plain_data.lengths[g_plain_data.n_items] = data_len;
- g_plain_data.tot_length += data_len;
- g_plain_data.n_items++;
+ g_plain_data.items = new_items;
+ plain_data_item_t *item = &g_plain_data.items[g_plain_data.n_items++];
+
+ memset(item, 0, sizeof(plain_data_item_t));
+ item->ms = ms;
+ item->is_tx = is_tx;
+ item->stream_id = stream_id;
+
+ if (new_item_data_buf) {
+ memcpy(new_item_data_buf, plain_data, data_len);
+
+ item->data = new_item_data_buf;
+ item->data_length = data_len;
}
}
+static void handle_ushark_http1_data(uint32_t conv_id, const unsigned char *plain_data, size_t data_len) {
+ if (!g_cur_ctx)
+ return;
+
+ // HTTP/1 is sequential, use 0 as the stream ID
+ handle_http_data(g_cur_ctx->is_tx, g_cur_ctx->ms, 0, plain_data, data_len);
+}
+
+static void handle_ushark_http2_request(uint32_t conv_id, uint32_t stream_id, const unsigned char *plain_data, size_t data_len) {
+ if (!g_cur_ctx)
+ return;
+
+ handle_http_data(g_cur_ctx->is_tx, g_cur_ctx->ms, stream_id, plain_data, data_len);
+}
+
+static void handle_ushark_http2_response(uint32_t conv_id, uint32_t stream_id, const unsigned char *plain_data, size_t data_len) {
+ if (!g_cur_ctx)
+ return;
+
+ handle_http_data(g_cur_ctx->is_tx, g_cur_ctx->ms, stream_id, plain_data, data_len);
+}
+
+static void handle_ushark_http2_reset(uint32_t conv_id, uint32_t stream_id) {
+ if (!g_cur_ctx)
+ return;
+
+ handle_http_data(g_cur_ctx->is_tx, g_cur_ctx->ms, stream_id, NULL, 0);
+}
+
+/* ******************************************************* */
+
/* Returns true if packet is valid. If false is returned, the pkt must still be dumped, so a call to
* pd_dump_packet is required. */
static bool handle_packet(pcapdroid_t *pd, pcapd_hdr_t *hdr, const char *buffer, int ipoffset) {
@@ -534,17 +578,21 @@ static bool handle_packet(pcapdroid_t *pd, pcapd_hdr_t *hdr, const char *buffer,
struct timeval tv = hdr->ts;
pkt_context_t pinfo;
pd_init_pkt_context(&pinfo, &pkt, is_tx, &conn_tuple, conn->data, &tv);
+ g_cur_ctx = &pinfo;
if (pd->pcap.usk && (pkt.len > 0)) {
struct pcap_pkthdr pcap_hdr;
pcap_hdr.len = pcap_hdr.caplen = pkt.len;
pcap_hdr.ts = hdr->ts;
- ushark_dissect_tls(pd->pcap.usk,
+ // This disables the reporting of leaks inside libushark.
+ // The reporting is enabled again in the individual callbacks (e.g. handle_ushark_http2_request)
+ // to detect leaks in pcapdroid
+ ushark_dissect(pd->pcap.usk,
(const unsigned char*) pkt.l3,
- &pcap_hdr, handle_tls_data);
+ &pcap_hdr);
- if (g_plain_data.data)
+ if (g_plain_data.n_items > 0)
pinfo.plain_data = &g_plain_data;
}
@@ -555,11 +603,14 @@ static bool handle_packet(pcapdroid_t *pd, pcapd_hdr_t *hdr, const char *buffer,
pd_account_stats(pd, &pinfo);
- if (g_plain_data.data) {
- pd_free(g_plain_data.data);
- pd_free(g_plain_data.lengths);
+ if (g_plain_data.items) {
+ for (size_t i = 0; i < g_plain_data.n_items; i++)
+ pd_free(g_plain_data.items[i].data);
+
+ pd_free(g_plain_data.items);
memset(&g_plain_data, 0, sizeof(g_plain_data));
}
+ g_cur_ctx = NULL;
return true;
}
@@ -714,6 +765,23 @@ static reader_rv read_file(pcapdroid_t *pd, pd_reader_t *reader, pcapd_hdr_t* hd
/* ******************************************************* */
+// Wrapper function for ushark initialization to allow LSAN suppression
+static void init_ushark_for_pcap(pcapdroid_t *pd, const char *keylog_path) {
+ if (ushark_init(pd)) {
+ pd->pcap.usk = ushark_new(PCAPD_DLT_RAW, "");
+
+ ushark_data_callbacks_t cbs = {
+ .on_http1_data = handle_ushark_http1_data,
+ .on_http2_request = handle_ushark_http2_request,
+ .on_http2_response = handle_ushark_http2_response,
+ .on_http2_reset = handle_ushark_http2_reset,
+ };
+ ushark_set_callbacks(pd->pcap.usk, &cbs);
+
+ ushark_set_pref("tls.keylog_file", keylog_path);
+ }
+}
+
int run_pcap(pcapdroid_t *pd) {
int sock = -1;
pd_reader_t *reader = NULL;
@@ -726,15 +794,14 @@ int run_pcap(pcapdroid_t *pd) {
if (pd->pcap_file_capture) {
// check if the SSL keylog exists
- const char *keylog_path = get_cache_path(pd, "sslkeylog.txt");
+ // Use override path if provided (for tests), otherwise use default location
+ const char *keylog_path = pd->keylog_path_override
+ ? pd->keylog_path_override
+ : get_cache_path(pd, "sslkeylog.txt");
if (access(keylog_path, F_OK) == 0) {
log_i("Use ushark for TLS decryption");
-
- if (ushark_init(pd)) {
- pd->pcap.usk = ushark_new(PCAPD_DLT_RAW, "");
- ushark_set_pref("tls.keylog_file", keylog_path);
- }
+ init_ushark_for_pcap(pd, keylog_path);
}
}
diff --git a/app/src/main/jni/core/jni_impl.c b/app/src/main/jni/core/jni_impl.c
index 39ab62b3..d42dd9dd 100644
--- a/app/src/main/jni/core/jni_impl.c
+++ b/app/src/main/jni/core/jni_impl.c
@@ -432,16 +432,16 @@ static void notifyBlacklistsLoaded(pcapdroid_t *pd, bl_status_arr_t *status_arr)
/* ******************************************************* */
-static bool dumpPayloadChunk(struct pcapdroid *pd, const pkt_context_t *pctx, const char *dump_data, int dump_size) {
+static bool dumpPayloadChunk(struct pcapdroid *pd, pd_conn_t *conn, bool is_tx, uint64_t ms, uint32_t stream_id, const char *dump_data, int dump_size) {
JNIEnv *env = pd->env;
bool rv = false;
- if(pctx->data->payload_chunks == NULL) {
+ if(conn->payload_chunks == NULL) {
// Directly allocating an ArrayList rather than creating it afterwards saves us from a data copy.
// However, this creates a local reference, which is retained until sendConnectionsDump is called.
// NOTE: Android only allows up to 512 local references.
- pctx->data->payload_chunks = (*env)->NewObject(env, cls.arraylist, mids.arraylistNew);
- if((pctx->data->payload_chunks == NULL) || jniCheckException(env))
+ conn->payload_chunks = (*env)->NewObject(env, cls.arraylist, mids.arraylistNew);
+ if((conn->payload_chunks == NULL) || jniCheckException(env))
return false;
}
@@ -449,12 +449,13 @@ static bool dumpPayloadChunk(struct pcapdroid *pd, const pkt_context_t *pctx, co
if(jniCheckException(env))
return false;
- jobject chunk_type = (pctx->data->l7proto == NDPI_PROTOCOL_HTTP) ? enums.chunktype_http : enums.chunktype_raw;
+ jobject chunk_type = (conn->l7proto == NDPI_PROTOCOL_HTTP) ? enums.chunktype_http : enums.chunktype_raw;
- jobject chunk = (*env)->NewObject(env, cls.payload_chunk, mids.payloadChunkInit, barray, chunk_type, pctx->is_tx, pctx->ms);
+ jobject chunk = (*env)->NewObject(env, cls.payload_chunk, mids.payloadChunkInit, barray, chunk_type, is_tx, ms, stream_id);
if(chunk && !jniCheckException(env)) {
- (*env)->SetByteArrayRegion(env, barray, 0, dump_size, (jbyte*) dump_data);
- rv = (*env)->CallBooleanMethod(env, pctx->data->payload_chunks, mids.arraylistAdd, chunk);
+ if (dump_data) // can be NULL for RST reporting in HTTP/2
+ (*env)->SetByteArrayRegion(env, barray, 0, dump_size, (jbyte*) dump_data);
+ rv = (*env)->CallBooleanMethod(env, conn->payload_chunks, mids.arraylistAdd, chunk);
}
//log_d("Dump chunk [size=%d]: %d", rv, dump_size);
@@ -567,7 +568,7 @@ static void init_jni(JNIEnv *env) {
mids.listGet = jniGetMethodID(env, cls.list, "get", "(I)Ljava/lang/Object;");
mids.arraylistNew = jniGetMethodID(env, cls.arraylist, "", "()V");
mids.arraylistAdd = jniGetMethodID(env, cls.arraylist, "add", "(Ljava/lang/Object;)Z");
- mids.payloadChunkInit = jniGetMethodID(env, cls.payload_chunk, "", "([BLcom/emanuelef/remote_capture/model/PayloadChunk$ChunkType;ZJ)V");
+ mids.payloadChunkInit = jniGetMethodID(env, cls.payload_chunk, "", "([BLcom/emanuelef/remote_capture/model/PayloadChunk$ChunkType;ZJI)V");
/* Fields */
fields.bldescr_fname = jniFieldID(env, cls.blacklist_descriptor, "fname", "Ljava/lang/String;");
diff --git a/app/src/main/jni/core/pcapdroid.c b/app/src/main/jni/core/pcapdroid.c
index d202035b..added875 100644
--- a/app/src/main/jni/core/pcapdroid.c
+++ b/app/src/main/jni/core/pcapdroid.c
@@ -588,8 +588,9 @@ void pd_giveup_dpi(pcapdroid_t *pd, pd_conn_t *data, const zdtun_5tuple_t *tuple
/* ******************************************************* */
// dumps the payload and returns true if fully dumped, false if failed or truncated
-static bool dump_payload(pcapdroid_t *pd, pkt_context_t *pctx, const char *to_dump, int dump_size) {
- pd_conn_t *data = pctx->data;
+static bool dump_payload(pcapdroid_t *pd, pd_conn_t *conn, bool is_tx, uint64_t ms, uint32_t stream_id,
+ const char *to_dump, int dump_size)
+{
bool truncated = false;
if((pd->payload_mode == PAYLOAD_MODE_MINIMAL) && (dump_size > MINIMAL_PAYLOAD_MAX_DIRECTION_SIZE)) {
@@ -597,8 +598,8 @@ static bool dump_payload(pcapdroid_t *pd, pkt_context_t *pctx, const char *to_du
truncated = true;
}
- if(pd->cb.dump_payload_chunk(pd, pctx, to_dump, dump_size))
- data->has_payload[pctx->is_tx] = true;
+ if(pd->cb.dump_payload_chunk(pd, conn, is_tx, ms, stream_id, to_dump, dump_size))
+ conn->has_payload[is_tx] = true;
else
truncated = true;
@@ -630,17 +631,18 @@ static void process_payload(pcapdroid_t *pd, pkt_context_t *pctx) {
data->has_decrypted_data = true;
}
- unsigned int data_offset = 0;
truncated = false;
for (unsigned int i = 0; i < pctx->plain_data->n_items; i++) {
- truncated |= !dump_payload(pd, pctx, (const char*) pctx->plain_data->data + data_offset,
- (int) pctx->plain_data->lengths[i]);
+ const plain_data_item_t *item = &pctx->plain_data->items[i];
- data_offset += pctx->plain_data->lengths[i];
+ // use the item is_tx and ms timestamp data, rather than the ones from pctx because
+ // http2.c may buffer http responses/resets so they may be processed with a different pctx
+ truncated |= !dump_payload(pd, pctx->data, item->is_tx, item->ms, item->stream_id,
+ (const char*) item->data, (int) item->data_length);
}
} else
- truncated = !dump_payload(pd, pctx, pkt->l7, pkt->l7_len);
+ truncated = !dump_payload(pd, pctx->data, pctx->is_tx, pctx->ms, 0, pkt->l7, pkt->l7_len);
updated = true;
} else
diff --git a/app/src/main/jni/core/pcapdroid.h b/app/src/main/jni/core/pcapdroid.h
index 1bd5c8ab..3f25bfda 100644
--- a/app/src/main/jni/core/pcapdroid.h
+++ b/app/src/main/jni/core/pcapdroid.h
@@ -153,8 +153,14 @@ typedef struct {
typedef struct {
unsigned char *data;
- unsigned int *lengths;
- unsigned int tot_length;
+ unsigned int data_length;
+ uint64_t ms;
+ uint32_t stream_id;
+ bool is_tx;
+} plain_data_item_t;
+
+typedef struct {
+ plain_data_item_t *items;
unsigned int n_items;
} plain_data_t;
@@ -184,7 +190,7 @@ typedef struct {
void (*stop_pcap_dump)(struct pcapdroid *pd);
void (*notify_service_status)(struct pcapdroid *pd, const char *status);
void (*notify_blacklists_loaded)(struct pcapdroid *pd, bl_status_arr_t *status_arr);
- bool (*dump_payload_chunk)(struct pcapdroid *pd, const pkt_context_t *pctx, const char *dump_data, int dump_size);
+ bool (*dump_payload_chunk)(struct pcapdroid *pd, pd_conn_t *conn, bool is_tx, uint64_t ms, uint32_t stream_id, const char *dump_data, int dump_size);
void (*clear_payload_chunks)(struct pcapdroid *pd, const pkt_context_t *pctx);
} pd_callbacks_t;
@@ -214,6 +220,7 @@ typedef struct pcapdroid {
jint mitm_addon_uid;
bool vpn_capture;
bool pcap_file_capture;
+ const char *keylog_path_override; // For tests: override sslkeylog.txt location
payload_mode_t payload_mode;
// stats
diff --git a/app/src/main/jni/core/ushark_dll.c b/app/src/main/jni/core/ushark_dll.c
index a4f7da15..8c541189 100644
--- a/app/src/main/jni/core/ushark_dll.c
+++ b/app/src/main/jni/core/ushark_dll.c
@@ -11,9 +11,9 @@ static void (*sk_init)();
static void (*sk_cleanup)();
static ushark_t* (*sk_new)(int, const char *);
static void (*sk_set_pref)(const char *, const char *);
+static void (*sk_set_callbacks)(ushark_t *, const ushark_data_callbacks_t *);
static void (*sk_destroy)(ushark_t*);
-static void (*sk_dissect_tls)(ushark_t*, const unsigned char *, const struct pcap_pkthdr *,
- ushark_tls_data_callback);
+static const char* (*sk_dissect)(ushark_t*, const unsigned char *, const struct pcap_pkthdr *);
bool ushark_init(pcapdroid_t *pd) {
assert(!sk_dll);
@@ -44,10 +44,11 @@ bool ushark_init(pcapdroid_t *pd) {
sk_cleanup = dlsym(sk_dll, "ushark_cleanup");
sk_new = dlsym(sk_dll, "ushark_new");
sk_set_pref = dlsym(sk_dll, "ushark_set_pref");
+ sk_set_callbacks = dlsym(sk_dll, "ushark_set_callbacks");
sk_destroy = dlsym(sk_dll, "ushark_destroy");
- sk_dissect_tls = dlsym(sk_dll, "ushark_dissect_tls");
+ sk_dissect = dlsym(sk_dll, "ushark_dissect");
- if (!sk_init || !sk_cleanup || !sk_new || !sk_set_pref || !sk_destroy || !sk_dissect_tls) {
+ if (!sk_init || !sk_cleanup || !sk_new || !sk_set_pref || !sk_set_callbacks || !sk_destroy || !sk_dissect) {
dlclose(sk_dll);
sk_dll = NULL;
log_e("libushark.so misses some required symbols");
@@ -60,14 +61,16 @@ bool ushark_init(pcapdroid_t *pd) {
void ushark_cleanup() {
assert(sk_dll);
+
sk_cleanup();
sk_init = NULL;
sk_cleanup = NULL;
sk_new = NULL;
sk_set_pref = NULL;
+ sk_set_callbacks = NULL;
sk_destroy = NULL;
- sk_dissect_tls = NULL;
+ sk_dissect = NULL;
// deallocates the static variables in wireshark, necessary to run cleanly again
dlclose(sk_dll);
@@ -76,20 +79,30 @@ void ushark_cleanup() {
ushark_t* ushark_new(int pcap_encap, const char *dfilter) {
assert(sk_new);
+
return sk_new(pcap_encap, dfilter);
}
void ushark_destroy(ushark_t *sk) {
assert(sk_destroy);
- return sk_destroy(sk);
+
+ sk_destroy(sk);
}
void ushark_set_pref(const char *name, const char *val) {
assert(sk_set_pref);
- return sk_set_pref(name, val);
+
+ sk_set_pref(name, val);
}
-void ushark_dissect_tls(ushark_t *sk, const unsigned char *buf, const struct pcap_pkthdr *hdr, ushark_tls_data_callback cb) {
- assert(sk_dissect_tls);
- return sk_dissect_tls(sk, buf, hdr, cb);
-}
\ No newline at end of file
+void ushark_set_callbacks(ushark_t *sk, const ushark_data_callbacks_t *cbs) {
+ assert(sk_set_callbacks);
+
+ sk_set_callbacks(sk, cbs);
+}
+
+const char* ushark_dissect(ushark_t *sk, const unsigned char *buf, const struct pcap_pkthdr *hdr) {
+ assert(sk_dissect);
+
+ return sk_dissect(sk, buf, hdr);
+}
diff --git a/app/src/main/jni/core/ushark_dll.h b/app/src/main/jni/core/ushark_dll.h
index 5d2f2521..d625a9fe 100644
--- a/app/src/main/jni/core/ushark_dll.h
+++ b/app/src/main/jni/core/ushark_dll.h
@@ -13,7 +13,14 @@ ushark_t* ushark_new(int pcap_encap, const char *dfilter);
void ushark_destroy(ushark_t *sk);
void ushark_set_pref(const char *name, const char *val);
-typedef void (*ushark_tls_data_callback)(const unsigned char *plain_data, unsigned int data_len);
-void ushark_dissect_tls(ushark_t *sk, const unsigned char *buf, const struct pcap_pkthdr *hdr, ushark_tls_data_callback cb);
+typedef struct {
+ void (*on_http1_data)(uint32_t conversation_id, const unsigned char *plain_data, size_t data_len);
+ void (*on_http2_request)(uint32_t conversation_id, uint32_t stream_id, const unsigned char *plain_data, size_t data_len);
+ void (*on_http2_response)(uint32_t conversation_id, uint32_t stream_id, const unsigned char *plain_data, size_t data_len);
+ void (*on_http2_reset)(uint32_t conversation_id, uint32_t stream_id);
+} ushark_data_callbacks_t;
+void ushark_set_callbacks(ushark_t *sk, const ushark_data_callbacks_t *cbs);
+
+const char* ushark_dissect(ushark_t *sk, const unsigned char *buf, const struct pcap_pkthdr *hdr);
#endif
diff --git a/app/src/main/jni/tests/Makefile b/app/src/main/jni/tests/Makefile
index d03849b3..368f8905 100644
--- a/app/src/main/jni/tests/Makefile
+++ b/app/src/main/jni/tests/Makefile
@@ -6,7 +6,7 @@ clean:
run_tests:
mkdir -p build/test
cd build/test && \
- cmake -DCMAKE_C_COMPILER=clang -DCMAKE_C_FLAGS="-fsanitize=address,leak -DHAVE_GNU_STRERROR_R -fno-omit-frame-pointer -O1" ../.. && \
+ cmake -DCMAKE_C_COMPILER=clang -DCMAKE_C_FLAGS="-fsanitize=address,leak -DHAVE_GNU_STRERROR_R -fno-omit-frame-pointer -g -O0" ../.. && \
$(MAKE) run_tests
fuzz:
diff --git a/app/src/main/jni/tests/test_utils.c b/app/src/main/jni/tests/test_utils.c
index 02940fe9..aeb6011d 100644
--- a/app/src/main/jni/tests/test_utils.c
+++ b/app/src/main/jni/tests/test_utils.c
@@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with PCAPdroid. If not, see .
*
- * Copyright 2022 - Emanuele Faranda
+ * Copyright 2022-26 - Emanuele Faranda
*/
#include "test_utils.h"
@@ -37,8 +37,15 @@ static void free_payload_chunks(pcapdroid_t *pd);
/* ******************************************************* */
-static void getPcapdPath(struct pcapdroid *pd, const char *prog_name, char *buf, int bufsize) {
- snprintf(buf, bufsize, "../main/pcapd/libpcapd.so");
+static void getNativeLibPath(struct pcapdroid *pd, const char *prog_name, char *buf, int bufsize) {
+ if (strcmp(prog_name, "pcapd") == 0)
+ snprintf(buf, bufsize, "../main/pcapd/libpcapd.so");
+ else {
+ fprintf(stderr, "Unknown native library: %s", prog_name);
+
+ if (bufsize > 0)
+ buf[0] = '\0';
+ }
}
/* ******************************************************* */
@@ -89,7 +96,7 @@ pcapdroid_t* pd_init_test(const char *ifname) {
pd->pcap_file_capture = true;
pd->pcap.capture_interface = (char*) ifname;
pd->pcap.as_root = false; // don't run as root
- pd->cb.get_libprog_path = getPcapdPath;
+ pd->cb.get_libprog_path = getNativeLibPath;
pd->payload_mode = PAYLOAD_MODE_FULL;
strcpy(pd->cachedir, ".");
@@ -215,8 +222,8 @@ u_char* next_pcap_record(pcap_rec_t *rec) {
/* ******************************************************* */
/* Dumps all the payload chunks into a linked list. The linked list is accessible via
- * (payload_chunk_t*)data->payload_chunks */
-bool dump_cb_payload_chunk(pcapdroid_t *pd, const pkt_context_t *pctx, const char *dump_data, int dump_size) {
+ * (payload_chunk_t*)conn->payload_chunks */
+bool dump_cb_payload_chunk(pcapdroid_t *pd, pd_conn_t *conn, bool is_tx, uint64_t ms, uint32_t stream_id, const char *dump_data, int dump_size) {
payload_chunk_t *chunk = calloc(1, sizeof(payload_chunk_t));
assert(chunk != NULL);
chunk->payload = (u_char*)malloc(dump_size);
@@ -224,10 +231,10 @@ bool dump_cb_payload_chunk(pcapdroid_t *pd, const pkt_context_t *pctx, const cha
memcpy(chunk->payload, dump_data, dump_size);
chunk->size = dump_size;
- chunk->is_tx = pctx->is_tx;
+ chunk->is_tx = is_tx;
// append to the linked list
- payload_chunk_t *last = (payload_chunk_t*)pctx->data->payload_chunks;
+ payload_chunk_t *last = (payload_chunk_t*) conn->payload_chunks;
if(last) {
while(last->next)
last = last->next;
@@ -237,7 +244,7 @@ bool dump_cb_payload_chunk(pcapdroid_t *pd, const pkt_context_t *pctx, const cha
num_chunks_lists++;
chunks_lists_heads = realloc(chunks_lists_heads, num_chunks_lists * sizeof(void*));
chunks_lists_heads[num_chunks_lists - 1] = chunk;
- pctx->data->payload_chunks = chunk;
+ conn->payload_chunks = chunk;
}
return true;
diff --git a/app/src/main/jni/tests/test_utils.h b/app/src/main/jni/tests/test_utils.h
index 57b682b3..50ac5d31 100644
--- a/app/src/main/jni/tests/test_utils.h
+++ b/app/src/main/jni/tests/test_utils.h
@@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with PCAPdroid. If not, see .
*
- * Copyright 2022 - Emanuele Faranda
+ * Copyright 2022-26 - Emanuele Faranda
*/
#ifndef __TEST_UTILS_H__
@@ -52,7 +52,7 @@ void assert_pcap_header(pcap_hdr_t *hdr);
u_char* next_pcap_record(pcap_rec_t *rec);
// Callbacks
-bool dump_cb_payload_chunk(pcapdroid_t *pd, const pkt_context_t *pctx, const char *dump_data, int dump_size);
+bool dump_cb_payload_chunk(pcapdroid_t *pd, pd_conn_t *conn, bool is_tx, uint64_t ms, uint32_t stream_id, const char *dump_data, int dump_size);
conn_and_tuple_t* assert_conn(pcapdroid_t *pd, int ipproto, const char *dst_ip, uint16_t dst_port, const char *info);
diff --git a/submodules/PCAPdroid-ushark-bin b/submodules/PCAPdroid-ushark-bin
index 905499ae..996a45cd 160000
--- a/submodules/PCAPdroid-ushark-bin
+++ b/submodules/PCAPdroid-ushark-bin
@@ -1 +1 @@
-Subproject commit 905499ae27d8bc6e2759a1d4c558a815804d5891
+Subproject commit 996a45cdc91ea8d5c6d15cef9a964118d0cc2fd1
diff --git a/submodules/nDPI b/submodules/nDPI
index ce606bf2..20832156 160000
--- a/submodules/nDPI
+++ b/submodules/nDPI
@@ -1 +1 @@
-Subproject commit ce606bf28af1d6a4cb70707a4790e93aed231275
+Subproject commit 20832156595ed1420f574c17b24d713f8ce925bf