From 4e25aac8e6baa582d18cd72ab95a2a9e94dfced9 Mon Sep 17 00:00:00 2001 From: emanuele-f Date: Sun, 10 Nov 2019 00:30:31 +0100 Subject: [PATCH] Show nDPI information NOTE: the code is still buggy and crashes randomly due to unaligned accesses --- README.md | 5 +- app/build.gradle | 1 + .../remote_capture/ConnDescriptor.java | 5 +- .../remote_capture/ConnectionsAdapter.java | 16 ++- .../remote_capture/MainActivity.java | 3 +- app/src/main/jni/vpnproxy-jni/CMakeLists.txt | 10 +- app/src/main/jni/vpnproxy-jni/vpnproxy.c | 125 +++++++++++++++++- app/src/main/jni/vpnproxy-jni/vpnproxy.h | 3 +- app/src/main/res/layout/connection_item.xml | 15 ++- nDPI/CMakeLists.txt | 2 +- 10 files changed, 164 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 6a3a0000..d7b235e4 100644 --- a/README.md +++ b/README.md @@ -66,5 +66,6 @@ In order to run without root, the app takes advantage of the Android VPNService 2. Clone https://github.com/emanuele-f/zdtun beside this repository 3. Clone https://github.com/ntop/nDPI beside this repository 4. Link the cmake file into nDPI: from this repo execute: `ln -s $(readlink -f nDPI/CMakeLists.txt) ../nDPI` -5. Build the `zdtun` and `ndpi` modules first -6. Then build the `app` module +4. Inside the nDPI directory, run `./autogen.sh` +6. Build the `zdtun` and `ndpi` modules first +7. Then build the `app` module diff --git a/app/build.gradle b/app/build.gradle index c7699e0a..77785fe5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,4 +36,5 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' implementation project(path: ':zdtun') implementation 'cat.ereza:customactivityoncrash:2.2.0' + implementation project(path: ':ndpi') } diff --git a/app/src/main/java/com/emanuelef/remote_capture/ConnDescriptor.java b/app/src/main/java/com/emanuelef/remote_capture/ConnDescriptor.java index f867e822..abf07119 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/ConnDescriptor.java +++ b/app/src/main/java/com/emanuelef/remote_capture/ConnDescriptor.java @@ -19,13 +19,15 @@ class ConnDescriptor implements Serializable { int sent_pkts; int rcvd_pkts; String info; + String l7proto; int uid; int incr_id; /* Invoked by native code */ public void setData(int _ipproto, String _src_ip, String _dst_ip, int _src_port, int _dst_port, long _first_seen, long _last_seen, long _sent_bytes, long _rcvd_bytes, - int _sent_pkts, int _rcvd_pkts, String _info, int _uid, int _incr_id) { + int _sent_pkts, int _rcvd_pkts, String _info, String _l7proto, int _uid, + int _incr_id) { /* Metadata */ ipproto = _ipproto; src_ip = _src_ip; @@ -41,6 +43,7 @@ class ConnDescriptor implements Serializable { sent_pkts = _sent_pkts; rcvd_pkts = _rcvd_pkts; info = _info; + l7proto = _l7proto; uid = _uid; incr_id = _incr_id; } diff --git a/app/src/main/java/com/emanuelef/remote_capture/ConnectionsAdapter.java b/app/src/main/java/com/emanuelef/remote_capture/ConnectionsAdapter.java index 240428a2..0db103bc 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/ConnectionsAdapter.java +++ b/app/src/main/java/com/emanuelef/remote_capture/ConnectionsAdapter.java @@ -43,19 +43,21 @@ public class ConnectionsAdapter extends BaseAdapter { assert conn != null; ImageView icon = convertView.findViewById(R.id.icon); TextView remote = convertView.findViewById(R.id.remote); + TextView l7proto = convertView.findViewById(R.id.l7proto); TextView traffic = convertView.findViewById(R.id.traffic); AppDescriptor app = mActivity.findAppByUid(conn.uid); Drawable appIcon; - if(app != null) - appIcon = Objects.requireNonNull(app.getIcon().getConstantState()).newDrawable(); - else - appIcon = mUnknownIcon; - + appIcon = (app != null) ? Objects.requireNonNull(app.getIcon().getConstantState()).newDrawable() : mUnknownIcon; icon.setImageDrawable(appIcon); - remote.setText(String.format(mActivity.getResources().getString(R.string.ip_and_port), - conn.dst_ip, conn.dst_port)); + if(conn.info.length() > 0) + remote.setText(conn.info); + else + remote.setText(String.format(mActivity.getResources().getString(R.string.ip_and_port), + conn.dst_ip, conn.dst_port)); + + l7proto.setText(conn.l7proto); traffic.setText(Utils.formatBytes(conn.sent_bytes + conn.rcvd_bytes)); return(convertView); diff --git a/app/src/main/java/com/emanuelef/remote_capture/MainActivity.java b/app/src/main/java/com/emanuelef/remote_capture/MainActivity.java index eaf7a244..fa566c25 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/MainActivity.java +++ b/app/src/main/java/com/emanuelef/remote_capture/MainActivity.java @@ -154,7 +154,8 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - getSupportFragmentManager().putFragment(outState, "ConnectionsFragment", mConnectionsFragment); + if(mConnectionsFragment != null) + getSupportFragmentManager().putFragment(outState, "ConnectionsFragment", mConnectionsFragment); } @Override diff --git a/app/src/main/jni/vpnproxy-jni/CMakeLists.txt b/app/src/main/jni/vpnproxy-jni/CMakeLists.txt index 3b8e99bf..6fde3452 100644 --- a/app/src/main/jni/vpnproxy-jni/CMakeLists.txt +++ b/app/src/main/jni/vpnproxy-jni/CMakeLists.txt @@ -8,7 +8,8 @@ add_library(vpnproxy-jni pcap) include_directories(../../../..) -include_directories(../../../../../../../src/zdtun) +include_directories(../../../../../../zdtun) +include_directories(../../../../../../nDPI/src/include) find_library( log-lib log) @@ -24,6 +25,13 @@ if (NOT zdtun-lib) message(FATAL_ERROR "zdtun not found!") endif() +set(ndpi-lib ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../ndpi/build/intermediates/library_and_local_jars_jni/debug/${ANDROID_ABI}/libndpi.so) + +if (NOT ndpi-lib) + message(FATAL_ERROR "nDPI not found!") +endif() + target_link_libraries( vpnproxy-jni ${zdtun-lib} + ${ndpi-lib} ${log-lib}) diff --git a/app/src/main/jni/vpnproxy-jni/vpnproxy.c b/app/src/main/jni/vpnproxy-jni/vpnproxy.c index 80d877f4..0bc435ff 100644 --- a/app/src/main/jni/vpnproxy-jni/vpnproxy.c +++ b/app/src/main/jni/vpnproxy-jni/vpnproxy.c @@ -21,10 +21,12 @@ #include #include "vpnproxy.h" #include "pcap.h" +#include "../../../../../../nDPI/src/include/ndpi_protocol_ids.h" #define VPN_TAG "VPNProxy" #define CAPTURE_STATS_UPDATE_FREQUENCY_MS 300 #define CONNECTION_DUMP_UPDATE_FREQUENCY_MS 3000 +#define MAX_DPI_PACKETS 12 /* ******************************************************* */ @@ -41,12 +43,18 @@ typedef struct dns_packet { uint16_t additional_rrs; uint8_t initial_dot; // just skip uint8_t queries[]; -} dns_packet_t __attribute__((packed)); +} __attribute__((packed)) dns_packet_t; /* ******************************************************* */ typedef struct conn_data { int incr_id; /* an incremental identifier */ + + /* nDPI */ + struct ndpi_flow_struct *ndpi_flow; + struct ndpi_id_struct *src_id, *dst_id; + ndpi_protocol l7proto; + time_t first_seen; time_t last_seen; u_int64_t sent_bytes; @@ -198,6 +206,84 @@ static char* getApplicationByUid(vpnproxy_data_t *proxy, int uid, char *buf, siz /* ******************************************************* */ +struct ndpi_detection_module_struct* init_ndpi() { + struct ndpi_detection_module_struct *ndpi = ndpi_init_detection_module(); + NDPI_PROTOCOL_BITMASK protocols; + + if(!ndpi) + return(NULL); + + // enable all the protocols + NDPI_BITMASK_SET_ALL(protocols); + + ndpi_set_protocol_detection_bitmask2(ndpi, &protocols); + ndpi_finalize_initalization(ndpi); + + return(ndpi); +} + +/* ******************************************************* */ + +void free_ndpi(conn_data_t *data) { + if(data->ndpi_flow) { + ndpi_free_flow(data->ndpi_flow); + data->ndpi_flow = NULL; + } + if(data->src_id) { + ndpi_free(data->src_id); + data->src_id = NULL; + } + if(data->dst_id) { + ndpi_free(data->dst_id); + data->dst_id = NULL; + } +} + +/* ******************************************************* */ + +const char *getL7ProtoName(struct ndpi_detection_module_struct *mod, ndpi_protocol l7proto) { + return ndpi_get_proto_name(mod, l7proto.master_protocol); +} + +/* ******************************************************* */ + +static void process_ndpi_packet(conn_data_t *data, vpnproxy_data_t *proxy, const char *packet, + ssize_t size, uint8_t from_tap) { + bool giveup = ((data->sent_pkts + data->rcvd_pkts) >= MAX_DPI_PACKETS); + + data->l7proto = ndpi_detection_process_packet(proxy->ndpi, data->ndpi_flow, packet, size, data->last_seen, + from_tap ? data->src_id : data->dst_id, + from_tap ? data->dst_id : data->src_id); + + if(giveup || ((data->l7proto.app_protocol != NDPI_PROTOCOL_UNKNOWN) && + (!ndpi_extra_dissection_possible(proxy->ndpi, data->ndpi_flow)))) { + if (data->l7proto.app_protocol == NDPI_PROTOCOL_UNKNOWN) { + uint8_t proto_guessed; + + data->l7proto = ndpi_detection_giveup(proxy->ndpi, data->ndpi_flow, 1 /* Guess */, + &proto_guessed); + } + + __android_log_print(ANDROID_LOG_DEBUG, VPN_TAG, "l7proto: app=%d, master=%d", + data->l7proto.app_protocol, data->l7proto.master_protocol); + + switch (data->l7proto.master_protocol) { + case NDPI_PROTOCOL_DNS: + case NDPI_PROTOCOL_HTTP: + case NDPI_PROTOCOL_TLS: + if (data->ndpi_flow->host_server_name[0]) { + data->info = strdup(data->ndpi_flow->host_server_name); + __android_log_print(ANDROID_LOG_DEBUG, VPN_TAG, "info: %s", data->info); + } + break; + } + + free_ndpi(data); + } +} + +/* ******************************************************* */ + static void account_packet(zdtun_t *tun, const char *packet, ssize_t size, uint8_t from_tap, const zdtun_conn_t *conn_info) { struct sockaddr_in servaddr = {0}; conn_data_t *data = (conn_data_t*)conn_info->user_data; @@ -232,6 +318,9 @@ static void account_packet(zdtun_t *tun, const char *packet, ssize_t size, uint8 data->last_seen = time(NULL); + if(data->ndpi_flow) + process_ndpi_packet(data, proxy, packet, size, from_tap); + if(((proxy->pcap_dump.uid_filter != -1) && (proxy->pcap_dump.uid_filter != uid)) && (!is_unknown_app || !proxy->pcap_dump.capture_unknown_app_traffic)) { //__android_log_print(ANDROID_LOG_DEBUG, VPN_TAG, "Discarding connection: UID=%d [filter=%d]", uid, proxy->pcap_dump.uid_filter); @@ -323,6 +412,22 @@ static int handle_new_connection(zdtun_t *tun, const zdtun_conn_t *conn_info, vo return(1); } + /* nDPI */ + if((data->ndpi_flow = ndpi_flow_malloc(SIZEOF_FLOW_STRUCT)) == NULL) { + __android_log_print(ANDROID_LOG_ERROR, VPN_TAG, "ndpi_flow_malloc failed"); + free_ndpi(data); + } + + if((data->src_id = ndpi_malloc(SIZEOF_ID_STRUCT)) == NULL) { + __android_log_print(ANDROID_LOG_ERROR, VPN_TAG, "ndpi_malloc(src_id) failed"); + free_ndpi(data); + } + + if((data->dst_id = ndpi_malloc(SIZEOF_ID_STRUCT)) == NULL) { + __android_log_print(ANDROID_LOG_ERROR, VPN_TAG, "ndpi_malloc(dst_id) failed"); + free_ndpi(data); + } + data->incr_id = proxy->incr_id++; data->first_seen = data->last_seen = time(NULL); data->uid = resolve_uid(proxy, conn_info); @@ -342,6 +447,8 @@ static void destroy_connection(zdtun_t *tun, const zdtun_conn_t *conn_info) { return; } + free_ndpi(data); + if(data->info) free(data->info); @@ -497,6 +604,7 @@ static int connection_dumper(zdtun_t *tun, const zdtun_conn_t *conn_info, void * } jobject info_string = (*env)->NewStringUTF(env, data->info ? data->info : ""); + jobject proto_string = (*env)->NewStringUTF(env, getL7ProtoName(proxy->ndpi, data->l7proto)); jobject src_string = (*env)->NewStringUTF(env, srcip); jobject dst_string = (*env)->NewStringUTF(env, dstip); jobject conn_descriptor = (*env)->NewObject(env, dump_data->conn_cls, dump_data->conn_constructor); @@ -513,7 +621,7 @@ static int connection_dumper(zdtun_t *tun, const zdtun_conn_t *conn_info, void * (*env)->CallVoidMethod(env, conn_descriptor, dump_data->conn_set_data, conn_info->ipproto, src_string, dst_string, 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, info_string, data->uid, data->incr_id); + data->sent_pkts, data->rcvd_pkts, info_string, proto_string, data->uid, data->incr_id); /* Add the connection to the array */ (*env)->SetObjectArrayElement(env, dump_data->connections, dump_data->idx++, conn_descriptor); @@ -551,7 +659,7 @@ static void sendConnectionsDump(zdtun_t *tun, vpnproxy_data_t *proxy) { /* NOTE: must match ConnDescriptor::setData */ dump_data.conn_set_data = (*env)->GetMethodID(env, dump_data.conn_cls, "setData", - "(ILjava/lang/String;Ljava/lang/String;IIJJJJIILjava/lang/String;II)V"); + "(ILjava/lang/String;Ljava/lang/String;IIJJJJIILjava/lang/String;Ljava/lang/String;II)V"); if(dump_data.conn_set_data == NULL) { __android_log_print(ANDROID_LOG_ERROR, VPN_TAG, "GetMethodID(conn_set_data) failed"); return; @@ -613,7 +721,7 @@ static int connect_dumper(vpnproxy_data_t *proxy) { servaddr.sin_port = proxy->pcap_dump.collector_port; servaddr.sin_addr.s_addr = proxy->pcap_dump.collector_addr; - if(connect(dumper_socket, &servaddr, sizeof(servaddr)) < 0) { + if(connect(dumper_socket, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) { __android_log_print(ANDROID_LOG_ERROR, VPN_TAG, "connection to the PCAP receiver failed [%d]: %s", errno, strerror(errno)); @@ -665,6 +773,14 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) { send_header = true; running = 1; + /* nDPI */ + proxy.ndpi = init_ndpi(); + + if(proxy.ndpi == NULL) { + __android_log_print(ANDROID_LOG_FATAL, VPN_TAG, "nDPI initialization failed"); + return(-1); + } + signal(SIGPIPE, SIG_IGN); // Set blocking @@ -750,6 +866,7 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) { __android_log_print(ANDROID_LOG_DEBUG, VPN_TAG, "Stopped packet loop"); ztdun_finalize(tun); + ndpi_exit_detection_module(proxy.ndpi); if(dumper_socket > 0) { close(dumper_socket); diff --git a/app/src/main/jni/vpnproxy-jni/vpnproxy.h b/app/src/main/jni/vpnproxy-jni/vpnproxy.h index d8f1ce91..3eef000e 100644 --- a/app/src/main/jni/vpnproxy-jni/vpnproxy.h +++ b/app/src/main/jni/vpnproxy-jni/vpnproxy.h @@ -20,6 +20,7 @@ #include #include #include "zdtun.h" +#include "ndpi_api.h" #ifndef REMOTE_CAPTURE_VPNPROXY_H #define REMOTE_CAPTURE_VPNPROXY_H @@ -39,12 +40,12 @@ typedef struct vpnproxy_data { int incr_id; jint sdk; JNIEnv *env; - jobject handler_cls; // TODO remove? jobject vpn_service; u_int32_t vpn_dns; u_int32_t public_dns; u_int32_t vpn_ipv4; bool dns_changed; + struct ndpi_detection_module_struct *ndpi; struct { u_int32_t collector_addr; diff --git a/app/src/main/res/layout/connection_item.xml b/app/src/main/res/layout/connection_item.xml index d49e137d..c7545d30 100644 --- a/app/src/main/res/layout/connection_item.xml +++ b/app/src/main/res/layout/connection_item.xml @@ -1,19 +1,28 @@ + + diff --git a/nDPI/CMakeLists.txt b/nDPI/CMakeLists.txt index ae41ce0b..d3c32ae9 100644 --- a/nDPI/CMakeLists.txt +++ b/nDPI/CMakeLists.txt @@ -9,9 +9,9 @@ add_definitions(-DNDPI_LIB_COMPILATION) include_directories(./src/include ./src/lib/third_party/include) +AUX_SOURCE_DIRECTORY(./src/lib AllSources) AUX_SOURCE_DIRECTORY(./src/lib/third_party/src AllSources) AUX_SOURCE_DIRECTORY(./src/lib/protocols AllSources) -AUX_SOURCE_DIRECTORY(./src/lib AllSources) # libndpi.so target ADD_LIBRARY(ndpi SHARED ${AllSources})