diff --git a/app/src/main/jni/core/blacklist.c b/app/src/main/jni/core/blacklist.c index ea501af4..068bc56c 100644 --- a/app/src/main/jni/core/blacklist.c +++ b/app/src/main/jni/core/blacklist.c @@ -254,18 +254,13 @@ bool blacklist_match_ip(blacklist_t *bl, const zdtun_ip_t *ip, int ipver) { /* ******************************************************* */ bool blacklist_match_ipstr(blacklist_t *bl, const char *ip_str) { - ndpi_ip_addr_t addr; - int ipver = ndpi_parse_ip_string(ip_str, &addr); - zdtun_ip_t ip; + zdtun_ip_t parsed; - if(ipver == 4) - ip.ip4 = addr.ipv4; - else if(ipver == 6) - memcpy(&ip.ip6, &addr.ipv6, 16); - else + int ipver = zdtun_parse_ip(ip_str, &parsed); + if(ipver < 0) return false; - return blacklist_match_ip(bl, &ip, ipver); + return blacklist_match_ip(bl, &parsed, ipver); } /* ******************************************************* */ diff --git a/app/src/main/jni/tests/CMakeLists.txt b/app/src/main/jni/tests/CMakeLists.txt index 31c3b4cd..1e9d9fa5 100644 --- a/app/src/main/jni/tests/CMakeLists.txt +++ b/app/src/main/jni/tests/CMakeLists.txt @@ -14,10 +14,9 @@ include_directories(${ROOTDIR}/submodules/zdtun) include_directories(${ROOTDIR}/submodules/nDPI/src/include) include(CTest) -list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") # Target to run tests and build them if necessary -add_custom_target(run_tests COMMAND ${CMAKE_CTEST_COMMAND}) +add_custom_target(run_tests COMMAND CTEST_OUTPUT_ON_FAILURE=1 ${CMAKE_CTEST_COMMAND}) # build_tests(target) macro(build_tests) @@ -30,5 +29,9 @@ endmacro() build_tests(pcap_reader) +add_test(NAME dpi_extract COMMAND ./dpi extract) +build_tests(dpi) + add_test(NAME blacklist_match COMMAND ./blacklist match) +add_test(NAME blacklist_detection COMMAND ./blacklist detection) build_tests(blacklist) diff --git a/app/src/main/jni/tests/blacklist.c b/app/src/main/jni/tests/blacklist.c index 12350249..03295002 100644 --- a/app/src/main/jni/tests/blacklist.c +++ b/app/src/main/jni/tests/blacklist.c @@ -51,9 +51,73 @@ static void test_match() { /* ******************************************************* */ +static void detection_cb(pcapdroid_t *pd) { + conn_and_tuple_t *conn; + + // IP blacklist + conn = assert_conn(pd, IPPROTO_ICMP, "1.1.1.1", 0, NULL); + assert1(conn->data->blacklisted_ip); + conn = assert_conn(pd, IPPROTO_TCP, "216.58.208.164", 80, NULL); + assert1(conn->data->blacklisted_ip); + conn = assert_conn(pd, IPPROTO_TCP, "2c9b:a9b9:83dd:d9d1::2003", 443, NULL); + assert1(conn->data->blacklisted_ip); + + // Host blacklist + conn = assert_conn(pd, IPPROTO_UDP, "8.8.8.8", 53, "www.google.it"); + assert0(conn->data->blacklisted_ip); + assert1(conn->data->blacklisted_domain); + conn = assert_conn(pd, IPPROTO_TCP, "146.112.255.155", 80, "www.internetbadguys.com"); + assert1(conn->data->blacklisted_domain); + conn = assert_conn(pd, IPPROTO_TCP, "3a5d:15fe:e3cb:9c5f::2003", 443, "www.google.it"); + assert0(conn->data->blacklisted_ip); + assert1(conn->data->blacklisted_domain); + + // Whitelist + conn = assert_conn(pd, IPPROTO_TCP, "149.202.95.241", 80, "f-droid.org"); + assert0(conn->data->blacklisted_domain); + assert0(conn->data->blacklisted_ip); + conn = assert_conn(pd, IPPROTO_TCP, "2ed5:9050:81e9:4b68:248:1893:25c8:1946", 443, "example.org"); + assert0(conn->data->blacklisted_domain); + assert0(conn->data->blacklisted_ip); +} + +static void test_detection() { + pcapdroid_t *pd = pd_init(PCAP_PATH "/metadata.pcap"); + + blacklist_t *bl = blacklist_init(); + assert(bl != NULL); + blacklist_t *wl = blacklist_init(); + assert(wl != NULL); + pd->malware_detection.enabled = true; + pd->malware_detection.bl = bl; + pd->malware_detection.whitelist = wl; + + // Load blacklist + blacklist_add_ipstr(bl, "1.1.1.1"); + blacklist_add_ipstr(bl, "216.58.208.164"); + blacklist_add_ipstr(bl, "2c9b:a9b9:83dd:d9d1::2003"); + blacklist_add_ipstr(bl, "149.202.95.241"); + blacklist_add_domain(bl, "google.it"); + blacklist_add_domain(bl, "www.internetbadguys.com"); + blacklist_add_domain(bl, "example.org"); + + // Load whitelist + blacklist_add_ipstr(wl, "149.202.95.241"); + blacklist_add_domain(wl, "example.org"); + + // Run + pd->cb.send_connections_dump = detection_cb; + pd_run(pd); + + pd_free(pd); +} + +/* ******************************************************* */ + int main(int argc, char **argv) { add_test("match", test_match); - run_test(argc, argv); + add_test("detection", test_detection); + run_test(argc, argv); return 0; } diff --git a/app/src/main/jni/tests/dpi.c b/app/src/main/jni/tests/dpi.c new file mode 100644 index 00000000..ce8e8276 --- /dev/null +++ b/app/src/main/jni/tests/dpi.c @@ -0,0 +1,70 @@ +/* + * This file is part of PCAPdroid. + * + * PCAPdroid is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PCAPdroid is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PCAPdroid. If not, see . + * + * Copyright 2022 - Emanuele Faranda + */ + +#include "test_utils.h" + +/* ******************************************************* */ + +// Called on send_connections_dump. pd->new_conns contains the dumped +// connections. Ensures that metadata is correctly extracted from +// network traffic. +static void extract_metadata_cb(pcapdroid_t *pd) { + conn_and_tuple_t *conn; + + // DNS request without reply + conn = assert_conn(pd, IPPROTO_UDP, "8.8.8.8", 53, "example.org"); + assert(conn->tuple.src_port == htons(48037)); + + // DNS (TCP) + assert_conn(pd, IPPROTO_TCP, "8.8.8.8", 53, "f-droid.org"); + + // Guess host name from previous DNS request + assert_conn(pd, IPPROTO_TCP, "149.202.95.241", 80, "f-droid.org"); + + // HTTP + conn = assert_conn(pd, IPPROTO_TCP, "216.58.208.164", 80, "www.google.com"); + assert(!strcmp(conn->data->url, "www.google.com/imghp?test=1&v2=2")); + conn = assert_conn(pd, IPPROTO_TCP, "385d:1ee:e3c9:9c5f::2004", 80, "www.google.com"); + assert(!strcmp(conn->data->url, "www.google.com/imghp?test=1&v2=2")); + + // TLS + conn = assert_conn(pd, IPPROTO_TCP, "142.250.180.131", 443, "google.it"); + assert(conn->data->l7proto == NDPI_PROTOCOL_TLS); + conn = assert_conn(pd, IPPROTO_TCP, "2ed5:9050:81e9:4b68:248:1893:25c8:1946", 443, "example.org"); + assert(conn->data->l7proto == NDPI_PROTOCOL_TLS); +} + +static void test_metadata_extraction() { + conn_and_tuple_t *conn; + pcapdroid_t *pd = pd_init(PCAP_PATH "/metadata.pcap"); + + pd->cb.send_connections_dump = extract_metadata_cb; + pd_run(pd); + + pd_free(pd); +} + +/* ******************************************************* */ + +int main(int argc, char **argv) { + add_test("extract", test_metadata_extraction); + + run_test(argc, argv); + return 0; +} diff --git a/app/src/main/jni/tests/pcap/README.md b/app/src/main/jni/tests/pcap/README.md new file mode 100644 index 00000000..e46a462b --- /dev/null +++ b/app/src/main/jni/tests/pcap/README.md @@ -0,0 +1,27 @@ +## metadata.pcap + +Contains HTTP, TLS and DNS connections for both IPv4 and IPv6, suitable to test DPI. + +Connections: + +``` +[UDP4] 192.168.1.10:48037 -> 8.8.8.8:53 [example.org] +[UDP4] 192.168.1.10:38793 -> 8.8.8.8:53 [www.google.com] +[TCP4] 192.168.1.10:36922 -> 216.58.208.164:80 [www.google.com] +[UDP4] 192.168.1.10:48772 -> 8.8.8.8:53 [www.google.com] +[TCP6] 2001:db8:1234::1:49936 -> 385d:1ee:e3c9:9c5f::2004:80 [www.google.com] +[UDP4] 192.168.1.10:51080 -> 8.8.8.8:53 [google.it] +[TCP6] 2001:db8:1234::1:44904 -> 2c9b:a9b9:83dd:d9d1::2003:443 [] +[TCP4] 192.168.1.10:51588 -> 142.250.180.131:443 [google.it] +[UDP4] 192.168.1.10:42218 -> 8.8.8.8:53 [www.google.it] +[TCP6] 2001:db8:1234::1:59424 -> 3a5d:15fe:e3cb:9c5f::2003:443 [www.google.it] +[ICMP4] 192.168.1.10:4 -> 1.1.1.1:0 [] +[UDP4] 192.168.1.10:47987 -> 8.8.8.8:53 [www.internetbadguys.com] +[TCP4] 192.168.1.10:46312 -> 146.112.255.155:80 [www.internetbadguys.com] +[UDP4] 192.168.1.10:51165 -> 8.8.8.8:53 [www.internetbadguys.com] +[UDP4] 192.168.1.10:52176 -> 8.8.8.8:53 [example.org] +[TCP6] 2001:db8:1234::1:45226 -> 2ed5:9050:81e9:4b68:248:1893:25c8:1946:443 [example.org] +[TCP4] 192.168.1.10:43453 -> 8.8.8.8:53 [f-droid.org] +[UDP4] 192.168.1.10:41011 -> 8.8.8.8:53 [f-droid.org] +[TCP4] 192.168.1.10:52782 -> 149.202.95.241:80 [f-droid.org] +``` diff --git a/app/src/main/jni/tests/pcap/metadata.pcap b/app/src/main/jni/tests/pcap/metadata.pcap new file mode 100644 index 00000000..382484d3 Binary files /dev/null and b/app/src/main/jni/tests/pcap/metadata.pcap differ diff --git a/app/src/main/jni/tests/pcap_reader.c b/app/src/main/jni/tests/pcap_reader.c index 1c9b4dfc..2fab184c 100644 --- a/app/src/main/jni/tests/pcap_reader.c +++ b/app/src/main/jni/tests/pcap_reader.c @@ -98,7 +98,7 @@ int main(int argc, char *argv[]) { log_i("Capturing packets from %s", ifname); pd_run(pd); - log_i("Terminated"); + log_i("Cleanup..."); pd_free(pd); free(ifname); } diff --git a/app/src/main/jni/tests/test_utils.c b/app/src/main/jni/tests/test_utils.c index 14ae2fac..3bb19ee3 100644 --- a/app/src/main/jni/tests/test_utils.c +++ b/app/src/main/jni/tests/test_utils.c @@ -88,3 +88,36 @@ pcapdroid_t* pd_init(const char *ifname) { return pd; } + +/* ******************************************************* */ + +/* To be called during send_connections_dump. Looks up a connection + * matching the specified protocol, IP port and info (if not NULL). + * If no connection is found, abort is called. Only the first match is + * returned. + */ +conn_and_tuple_t* assert_conn(pcapdroid_t *pd, int ipproto, const char *dst_ip, + uint16_t dst_port, const char *info) { + conn_and_tuple_t *found = NULL; + zdtun_ip_t ip; + dst_port = htons(dst_port); + + int ipver = zdtun_parse_ip(dst_ip, &ip); + assert((ipver == 4) || (ipver == 6)); + + for(int i=0; i < pd->new_conns.cur_items; i++) { + conn_and_tuple_t *conn = &pd->new_conns.items[i]; + + if((conn->tuple.ipproto == ipproto) && + (conn->tuple.dst_port == dst_port) && + (conn->tuple.ipver == ipver) && + (!zdtun_cmp_ip(ipver, &conn->tuple.dst_ip, &ip)) && + ((info == NULL) || ((conn->data->info != NULL) && !strcmp(info, conn->data->info)))) { + found = conn; + break; + } + } + + assert(found); + return found; +} diff --git a/app/src/main/jni/tests/test_utils.h b/app/src/main/jni/tests/test_utils.h index 85708ffd..3487fdae 100644 --- a/app/src/main/jni/tests/test_utils.h +++ b/app/src/main/jni/tests/test_utils.h @@ -26,13 +26,14 @@ #define assert0(x) assert((x) == 0) #define assert1(x) assert((x) == 1) +#define PCAP_PATH "../pcap" + void add_test(const char *name, void (*test_cb)()); void run_test(int argc, char **argv); pcapdroid_t* pd_init(const char *ifname); +static inline void pd_free(pcapdroid_t *pd) { free(pd); } -static inline void pd_free(pcapdroid_t *pd) { - free(pd); -} +conn_and_tuple_t* assert_conn(pcapdroid_t *pd, int ipproto, const char *dst_ip, uint16_t dst_port, const char *info); #endif diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 00000000..fc20aeb3 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,23 @@ +Tests in PCAPdroid can be split in the following categories: + +- [Java tests](https://github.com/emanuele-f/PCAPdroid/tree/dev/app/src/test/java): + they can be run via `./gradlew test`. They use the + [robolectric framework](https://github.com/robolectric/robolectric) + to mock the Android API, allowing them to be run locally (without an emulator) + +- [Native tests](https://github.com/emanuele-f/PCAPdroid/tree/dev/app/src/main/jni/tests): + they can be run with `./run_tests.sh` on a linux host. They are built + with the [AddressSanitizer](https://clang.llvm.org/docs/AddressSanitizer.html) + to detect memory issues and leaks + +The tests are executed on every push via the +[Github workflows](https://github.com/emanuele-f/PCAPdroid/tree/dev/.github/workflows) + +Apart from automatic tests, the following manual tests should be performed +before every release: + +- Test on devices matching the `minSdkVersion` (currently Android SDK 21) +- Test on devices matching the `targetSdkVersion` (currently Android SDK 31) +- Rotate the device, put activity in background, clear from recent activities +- Java memory consumption tests via the [Memory Profiler](https://developer.android.com/studio/profile/memory-profiler) +- Manual malware detection test against `internetbadguys.com` and `0.0.0.1` diff --git a/submodules/zdtun b/submodules/zdtun index e536c593..240ae645 160000 --- a/submodules/zdtun +++ b/submodules/zdtun @@ -1 +1 @@ -Subproject commit e536c593d1fe49c67617911b18bda23a13138e5d +Subproject commit 240ae6459c9ef55c90ab8fd4fea1e60d8b5ad8b2