diff --git a/.ci_scripts/firewall-test/translate_profiles.sh b/.ci_scripts/firewall-test/translate_profiles.sh deleted file mode 100755 index f7e34aa1b413c1863c31399ddcf16caab8e78c9a..0000000000000000000000000000000000000000 --- a/.ci_scripts/firewall-test/translate_profiles.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -## CONSTANTS -DEVICES_DIR="$GITHUB_WORKSPACE/test/translator/devices" -TRANSLATOR_PATH="$GITHUB_WORKSPACE/src/translator/translator.py" - -# Ensure globbing expands to an empty list if no matches are found -shopt -s nullglob - -# Loop over devices -NFQ_BASE_ID=0 -for DEVICE in "$DEVICES_DIR"/*/; do - # Call translator over device profile - # Arguments $1 & $2 represent the verdict mode - python3 $TRANSLATOR_PATH "$DEVICE"profile.yaml $NFQ_BASE_ID $1 $2 - ((NFQ_BASE_ID=NFQ_BASE_ID+100)) -done diff --git a/.ci_scripts/firewall-test/add_nft_rules.sh b/.ci_scripts/native-build/add_nft_rules.sh similarity index 81% rename from .ci_scripts/firewall-test/add_nft_rules.sh rename to .ci_scripts/native-build/add_nft_rules.sh index 3bd0603e0c246a8f1e8bfb474c6e9a017a933840..498850ce9d7e1e1f626ad5d1c399a112218f7ee6 100755 --- a/.ci_scripts/firewall-test/add_nft_rules.sh +++ b/.ci_scripts/native-build/add_nft_rules.sh @@ -1,6 +1,6 @@ EXITCODE=0 -for nft_script in $GITHUB_WORKSPACE/test/translator/devices/*/firewall.nft +for nft_script in $GITHUB_WORKSPACE/test/device/firewall.nft do # Flush the ruleset before next device sudo nft flush ruleset diff --git a/.ci_scripts/firewall-test/install_packages.sh b/.ci_scripts/native-build/install_packages.sh similarity index 100% rename from .ci_scripts/firewall-test/install_packages.sh rename to .ci_scripts/native-build/install_packages.sh diff --git a/.ci_scripts/firewall-test/run_cppcheck.sh b/.ci_scripts/native-build/run_cppcheck.sh similarity index 100% rename from .ci_scripts/firewall-test/run_cppcheck.sh rename to .ci_scripts/native-build/run_cppcheck.sh diff --git a/.ci_scripts/firewall-test/run_exec.sh b/.ci_scripts/native-build/run_exec.sh similarity index 100% rename from .ci_scripts/firewall-test/run_exec.sh rename to .ci_scripts/native-build/run_exec.sh diff --git a/.ci_scripts/firewall-test/run_tests.sh b/.ci_scripts/native-build/run_tests.sh similarity index 88% rename from .ci_scripts/firewall-test/run_tests.sh rename to .ci_scripts/native-build/run_tests.sh index 14c4924a0bd5551391a6dd4809e184896d8e7369..e911e16892d3b50bf2536c4edf9f2c1a50b90ef4 100755 --- a/.ci_scripts/firewall-test/run_tests.sh +++ b/.ci_scripts/native-build/run_tests.sh @@ -1,6 +1,6 @@ EXITCODE=0 PARSERS_DIR="$GITHUB_WORKSPACE/src/parsers" -VALGRIND_SUPP="$GITHUB_WORKSPACE/.ci_scripts/firewall-test/valgrind.supp" +VALGRIND_SUPP="$GITHUB_WORKSPACE/.ci_scripts/native-build/valgrind.supp" PREFIX="" for file in "$GITHUB_WORKSPACE"/bin/test/* "$PARSERS_DIR"/bin/test/* diff --git a/.ci_scripts/firewall-test/valgrind.supp b/.ci_scripts/native-build/valgrind.supp similarity index 100% rename from .ci_scripts/firewall-test/valgrind.supp rename to .ci_scripts/native-build/valgrind.supp diff --git a/.github/workflows/cross-compile.yml b/.github/workflows/cross-compile.yml deleted file mode 100644 index 7fbcf5b0d08735edb03652be7718ea9802894999..0000000000000000000000000000000000000000 --- a/.github/workflows/cross-compile.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Verify cross-compilation on OpenWrt environment -on: [push] - - -jobs: - - binary-verdict: - runs-on: ubuntu-latest - container: fdekeers/openwrt_tl-wdr4900_gha - - steps: - - - name: Checkout repository - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Install Python packages - run: pip install -r $GITHUB_WORKSPACE/requirements.txt - - - name: Translate profiles - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/translate_profiles.sh - - - name: Run cross-compilation - run: $GITHUB_WORKSPACE/build.sh -d $GITHUB_WORKSPACE -t $GITHUB_WORKSPACE/openwrt/tl-wdr4900.cmake - - - rate-limit-verdict: - runs-on: ubuntu-latest - container: fdekeers/openwrt_tl-wdr4900_gha - - steps: - - - name: Checkout repository - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Translate profiles - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/translate_profiles.sh -r 50 - - - name: Run cross-compilation - run: $GITHUB_WORKSPACE/build.sh -d $GITHUB_WORKSPACE -t $GITHUB_WORKSPACE/openwrt/tl-wdr4900.cmake - - - random-verdict: - runs-on: ubuntu-latest - container: fdekeers/openwrt_tl-wdr4900_gha - - steps: - - - name: Checkout repository - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Translate profiles - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/translate_profiles.sh -p 0.5 - - - name: Run cross-compilation - run: $GITHUB_WORKSPACE/build.sh -d $GITHUB_WORKSPACE -t $GITHUB_WORKSPACE/openwrt/tl-wdr4900.cmake diff --git a/.github/workflows/full-test.yaml b/.github/workflows/full-test.yaml new file mode 100644 index 0000000000000000000000000000000000000000..db778462c32421af23ab2d0b733c04067c54a9fe --- /dev/null +++ b/.github/workflows/full-test.yaml @@ -0,0 +1,50 @@ +name: full-test +on: [push] + + +jobs: + + native-build: + runs-on: ubuntu-latest + steps: + + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install required packages + run: sudo $GITHUB_WORKSPACE/.ci_scripts/native-build/install_packages.sh + + - name: Build project with CMake + run: $GITHUB_WORKSPACE/build.sh -d $GITHUB_WORKSPACE + + - name: Run CUnit tests + run: $GITHUB_WORKSPACE/.ci_scripts/native-build/run_tests.sh + + - name: Run Valgrind on CUnit tests + run: $GITHUB_WORKSPACE/.ci_scripts/native-build/run_tests.sh valgrind + + - name: Run cppcheck on source files + run: $GITHUB_WORKSPACE/.ci_scripts/native-build/run_cppcheck.sh + + - name: Add NFTables rules + run: $GITHUB_WORKSPACE/.ci_scripts/native-build/add_nft_rules.sh + + - name: Run NFQueue executables + run: $GITHUB_WORKSPACE/.ci_scripts/native-build/run_exec.sh + + + cross-compile: + runs-on: ubuntu-latest + container: fdekeers/openwrt_tl-wdr4900_gha + + steps: + + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Run cross-compilation + run: $GITHUB_WORKSPACE/build.sh -d $GITHUB_WORKSPACE -t $GITHUB_WORKSPACE/openwrt/tl-wdr4900.cmake diff --git a/.github/workflows/test-firewall.yaml b/.github/workflows/test-firewall.yaml deleted file mode 100644 index ac35b5360d81542a99ff9ea9d01fa56aa7b28b00..0000000000000000000000000000000000000000 --- a/.github/workflows/test-firewall.yaml +++ /dev/null @@ -1,115 +0,0 @@ -name: Test the whole system -on: [push] - - -jobs: - - binary-verdict: - runs-on: ubuntu-latest - steps: - - - name: Checkout repository - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Install required packages - run: sudo $GITHUB_WORKSPACE/.ci_scripts/firewall-test/install_packages.sh - - - name: Install Python packages - run: pip install -r $GITHUB_WORKSPACE/requirements.txt - - - name: Translate profiles - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/translate_profiles.sh - - - name: Build project with CMake - run: $GITHUB_WORKSPACE/build.sh -d $GITHUB_WORKSPACE - - - name: Run CUnit tests - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/run_tests.sh - - - name: Run Valgrind on CUnit tests - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/run_tests.sh valgrind - - - name: Run cppcheck on source files - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/run_cppcheck.sh - - - name: Add NFTables rules - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/add_nft_rules.sh - - - name: Run NFQueue executables - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/run_exec.sh - - - rate-limit-verdict: - runs-on: ubuntu-latest - steps: - - - name: Checkout repository - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Install required packages - run: sudo $GITHUB_WORKSPACE/.ci_scripts/firewall-test/install_packages.sh - - - name: Install Python packages - run: pip install -r $GITHUB_WORKSPACE/requirements.txt - - - name: Translate profiles - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/translate_profiles.sh -r 50 - - - name: Build project with CMake - run: $GITHUB_WORKSPACE/build.sh -d $GITHUB_WORKSPACE - - - name: Run CUnit tests - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/run_tests.sh - - - name: Run Valgrind on CUnit tests - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/run_tests.sh valgrind - - - name: Run cppcheck on source files - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/run_cppcheck.sh - - - name: Add nftables rules - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/add_nft_rules.sh - - - name: Run NFQueue executables - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/run_exec.sh - - - random-verdict: - runs-on: ubuntu-latest - steps: - - - name: Checkout repository - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Install required packages - run: sudo $GITHUB_WORKSPACE/.ci_scripts/firewall-test/install_packages.sh - - - name: Install Python packages - run: pip install -r $GITHUB_WORKSPACE/requirements.txt - - - name: Translate profiles - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/translate_profiles.sh -p 0.5 - - - name: Build project with CMake - run: $GITHUB_WORKSPACE/build.sh -d $GITHUB_WORKSPACE - - - name: Run CUnit tests - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/run_tests.sh - - - name: Run Valgrind on CUnit tests - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/run_tests.sh valgrind - - - name: Run cppcheck on source files - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/run_cppcheck.sh - - - name: Add nftables rules - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/add_nft_rules.sh - - - name: Run NFQueue executables - run: $GITHUB_WORKSPACE/.ci_scripts/firewall-test/run_exec.sh diff --git a/.gitignore b/.gitignore index d1eba1603ad6581ddc3bd48665ac40b764f8c128..f571c4ae5e4e662955b0010a28d8cb9474bde435 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,3 @@ # Build directories build bin - -# Python cache -__pycache__ diff --git a/CMakeLists.txt b/CMakeLists.txt index 50f80e22b91c9eb913bbf5663e252003e4d891c8..fe86854956bcd25540fd5d99a810fa582851aa24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,4 @@ set(PARSERS header dns dhcp http igmp ssdp coap) # Subdirectories containing code add_subdirectory(src) -IF( NOT OPENWRT_CROSSCOMPILING ) - add_subdirectory(test) -ENDIF() +add_subdirectory(test) diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c6140dc44c19208e12cb9bba3e7de1b4e60ce3bf..0000000000000000000000000000000000000000 --- a/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Libs -PyYAML -Jinja2 -# Custom -pyyaml-loaders diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5f70a077925c655ea0887499ba65c012d2412121..b5fefe49f575073dfb834a11f13c2a0536246891 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,8 +3,8 @@ cmake_minimum_required(VERSION 3.20) ## Test subdirectories # Sample profiles -add_subdirectory(translator) -# Unit tests for runtime code +add_subdirectory(device) +# Unit tests IF( NOT OPENWRT_CROSSCOMPILING ) - add_subdirectory(runtime) + add_subdirectory(unit) ENDIF() diff --git a/test/device/CMakeLists.txt b/test/device/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..f608138a5a094317c357514a547b4941411c7449 --- /dev/null +++ b/test/device/CMakeLists.txt @@ -0,0 +1,15 @@ +# Minimum required CMake version +cmake_minimum_required(VERSION 3.20) + +set(EXECUTABLE_OUTPUT_PATH ${BIN_DIR}) + +# Nfqueue C file for device tplink-plug +add_executable(tplink-plug nfqueues.c) +target_link_libraries(tplink-plug pthread) +IF( OPENWRT_CROSSCOMPILING ) +target_link_libraries(tplink-plug jansson mnl nfnetlink nftnl nftables netfilter_queue netfilter_log) +ENDIF() +target_link_libraries(tplink-plug nfqueue packet_utils rule_utils) +target_link_libraries(tplink-plug header dns) +target_include_directories(tplink-plug PRIVATE ${INCLUDE_DIR} ${INCLUDE_PARSERS_DIR}) +install(TARGETS tplink-plug DESTINATION ${EXECUTABLE_OUTPUT_PATH}) \ No newline at end of file diff --git a/test/device/firewall.nft b/test/device/firewall.nft new file mode 100644 index 0000000000000000000000000000000000000000..b47709ad82dc8d94c207266e5b87a4f13d5c7ddb --- /dev/null +++ b/test/device/firewall.nft @@ -0,0 +1,39 @@ +#!/usr/sbin/nft -f + +table bridge tplink-plug { + + # Chain PREROUTING, entry point for all traffic + chain prerouting { + + # Base chain, need configuration + # Default policy is ACCEPT + type filter hook prerouting priority 0; policy accept; + + # NFQueue lan-tcp-to-phone + meta l4proto tcp tcp sport 9999 ip saddr 192.168.1.135 ip daddr 192.168.1.222 drop + + # NFQueue lan-tcp-to-phone-backward + meta l4proto tcp tcp dport 9999 ip daddr 192.168.1.135 ip saddr 192.168.1.222 drop + + # NFQueue lan-udp-to-phone + meta l4proto udp udp sport 9999 ip saddr 192.168.1.135 ip daddr 192.168.1.222 drop + + # NFQueue lan-udp-to-phone-backward + meta l4proto udp udp dport 9999 ip daddr 192.168.1.135 ip saddr 192.168.1.222 drop + + # NFQueue dns-query-tplinkapi + meta l4proto udp udp dport 53 ip saddr 192.168.1.135 ip daddr 192.168.1.1 queue num 0 + + # NFQueue dns-query-tplinkapi-backward + meta l4proto udp udp sport 53 ip daddr 192.168.1.135 ip saddr 192.168.1.1 queue num 1 + + # NFQueue wan-https-to-domain-tplinkapi + meta l4proto tcp tcp dport 443 ip saddr 192.168.1.135 queue num 10 + + # NFQueue wan-https-to-domain-tplinkapi-backward + meta l4proto tcp tcp sport 443 ip daddr 192.168.1.135 queue num 11 + + + } + +} diff --git a/test/device/nfqueues.c b/test/device/nfqueues.c new file mode 100644 index 0000000000000000000000000000000000000000..fe67279287aafe6fa7b8edf8a6e3a711dd5f7ec8 --- /dev/null +++ b/test/device/nfqueues.c @@ -0,0 +1,689 @@ +// THIS FILE HAS BEEN AUTOGENERATED. DO NOT EDIT. + +/** + * Nefilter queue for device tplink-plug + */ + +// Standard libraries +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <string.h> +#include <pthread.h> +#include <assert.h> +#include <signal.h> +#include <sys/time.h> +// Custom libraries +#include "nfqueue.h" +#include "packet_utils.h" +#include "rule_utils.h" +// Parsers +#include "header.h" +#include "dns.h" + + +/* CONSTANTS */ + +float DROP_PROBA = 1.0; // Drop probability for random drop verdict mode + +#define NUM_THREADS 4 + +/** + * Thread-specific data. + */ +typedef struct { + uint8_t id; // Thread ID + uint32_t seed; // Thread-specific seed for random number generation + pthread_t thread; // The thread itself +} thread_data_t; + +thread_data_t thread_data[NUM_THREADS]; + +dns_map_t *dns_map; // Domain name to IP address mapping + +#ifdef DEBUG +uint16_t dropped_packets = 0; +#endif /* DEBUG */ + + +/** + * @brief dns-query-tplinkapi callback function, called when a packet enters the queue. + * + * @param pkt_id packet ID for netfilter queue + * @param hash packet payload SHA256 hash (only present if LOG is defined) + * @param timestamp packet timestamp (only present if LOG is defined) + * @param pkt_len packet length, in bytes + * @param payload pointer to the packet payload + * @param arg pointer to the argument passed to the callback function + * @return the verdict for the packet + */ +#ifdef LOG +uint32_t callback_dns_query_tplinkapi(int pkt_id, uint8_t *hash, struct timeval timestamp, int pkt_len, uint8_t *payload, void *arg) +#else +uint32_t callback_dns_query_tplinkapi(int pkt_id, int pkt_len, uint8_t *payload, void *arg) +#endif /* LOG */ +{ + #ifdef DEBUG + printf("Received packet from nfqueue 0\n"); + #endif + + // Skip layer 3 and 4 headers + size_t skipped = get_headers_length(payload); + + // Parse payload as DNS message + dns_message_t dns_message = dns_parse_message(payload + skipped); + #ifdef DEBUG + dns_print_message(dns_message); + #endif + uint32_t verdict = NF_ACCEPT; // Packet verdict: ACCEPT or DROP + + /* Policy dns-query-tplinkapi */ + if ( + dns_message.header.qr == 0 + && + ( dns_message.header.qdcount > 0 && dns_message.questions->qtype == A ) + && + dns_contains_full_domain_name(dns_message.questions, dns_message.header.qdcount, "use1-api.tplinkra.com") + ) { + + + + uint32_t old_verdict = verdict; + + // Binary DROP + verdict = NF_DROP; + + #if defined LOG || defined DEBUG + if (verdict == NF_DROP) { + #ifdef LOG + print_hash(hash); + printf(",%ld.%06ld,dns-query-tplinkapi,,DROP\n", (long int)timestamp.tv_sec, (long int)timestamp.tv_usec); + #endif /* LOG */ + #ifdef DEBUG + printf("DROP - Policy: dns-query-tplinkapi\n"); + if (old_verdict != NF_DROP) { + dropped_packets++; + printf("Dropped packets: %hu\n", dropped_packets); + } + #endif /* DEBUG */ + } + #endif /* LOG || DEBUG */ + + } + /* Policy dns-query-tplinkcloud */ + if ( + dns_message.header.qr == 0 + && + ( dns_message.header.qdcount > 0 && dns_message.questions->qtype == A ) + && + dns_contains_full_domain_name(dns_message.questions, dns_message.header.qdcount, "n-devs.tplinkcloud.com") + ) { + + + + uint32_t old_verdict = verdict; + + // Binary DROP + verdict = NF_DROP; + + #if defined LOG || defined DEBUG + if (verdict == NF_DROP) { + #ifdef LOG + print_hash(hash); + printf(",%ld.%06ld,dns-query-tplinkcloud,,DROP\n", (long int)timestamp.tv_sec, (long int)timestamp.tv_usec); + #endif /* LOG */ + #ifdef DEBUG + printf("DROP - Policy: dns-query-tplinkcloud\n"); + if (old_verdict != NF_DROP) { + dropped_packets++; + printf("Dropped packets: %hu\n", dropped_packets); + } + #endif /* DEBUG */ + } + #endif /* LOG || DEBUG */ + + } + + // Free memory allocated for parsed messages + dns_free_message(dns_message); + + #ifdef LOG + if (verdict != NF_DROP) { + // Log packet as accepted + print_hash(hash); + printf(",%ld.%06ld,dns-query-tplinkapi,,ACCEPT\n", (long int)timestamp.tv_sec, (long int)timestamp.tv_usec); + } + free(hash); + #endif /* LOG */ + + return verdict; +} + + + + +/** + * @brief dns-query-tplinkapi-backward callback function, called when a packet enters the queue. + * + * @param pkt_id packet ID for netfilter queue + * @param hash packet payload SHA256 hash (only present if LOG is defined) + * @param timestamp packet timestamp (only present if LOG is defined) + * @param pkt_len packet length, in bytes + * @param payload pointer to the packet payload + * @param arg pointer to the argument passed to the callback function + * @return the verdict for the packet + */ +#ifdef LOG +uint32_t callback_dns_query_tplinkapi_backward(int pkt_id, uint8_t *hash, struct timeval timestamp, int pkt_len, uint8_t *payload, void *arg) +#else +uint32_t callback_dns_query_tplinkapi_backward(int pkt_id, int pkt_len, uint8_t *payload, void *arg) +#endif /* LOG */ +{ + #ifdef DEBUG + printf("Received packet from nfqueue 1\n"); + #endif + + // Skip layer 3 and 4 headers + size_t skipped = get_headers_length(payload); + + // Parse payload as DNS message + dns_message_t dns_message = dns_parse_message(payload + skipped); + #ifdef DEBUG + dns_print_message(dns_message); + #endif + uint32_t verdict = NF_ACCEPT; // Packet verdict: ACCEPT or DROP + + /* Policy dns-query-tplinkapi-backward */ + if ( + dns_message.header.qr == 1 + && + ( dns_message.header.qdcount > 0 && dns_message.questions->qtype == A ) + && + dns_contains_full_domain_name(dns_message.questions, dns_message.header.qdcount, "use1-api.tplinkra.com") + ) { + + // Retrieve IP addresses corresponding to the given domain name from the DNS response + char *domain_name = NULL; + ip_list_t ip_list = ip_list_init(); + domain_name = "use1-api.tplinkra.com"; + ip_list = dns_get_ip_from_name(dns_message.answers, dns_message.header.ancount, domain_name); + + if (ip_list.ip_count > 0) { + // Add IP addresses to DNS map + dns_map_add(dns_map, domain_name, ip_list); + } + + uint32_t old_verdict = verdict; + + // Binary DROP + verdict = NF_DROP; + + #if defined LOG || defined DEBUG + if (verdict == NF_DROP) { + #ifdef LOG + print_hash(hash); + printf(",%ld.%06ld,dns-query-tplinkapi-backward,,DROP\n", (long int)timestamp.tv_sec, (long int)timestamp.tv_usec); + #endif /* LOG */ + #ifdef DEBUG + printf("DROP - Policy: dns-query-tplinkapi-backward\n"); + if (old_verdict != NF_DROP) { + dropped_packets++; + printf("Dropped packets: %hu\n", dropped_packets); + } + #endif /* DEBUG */ + } + #endif /* LOG || DEBUG */ + + } + /* Policy dns-query-tplinkcloud-backward */ + if ( + dns_message.header.qr == 1 + && + ( dns_message.header.qdcount > 0 && dns_message.questions->qtype == A ) + && + dns_contains_full_domain_name(dns_message.questions, dns_message.header.qdcount, "n-devs.tplinkcloud.com") + ) { + + // Retrieve IP addresses corresponding to the given domain name from the DNS response + char *domain_name = NULL; + ip_list_t ip_list = ip_list_init(); + domain_name = "n-devs.tplinkcloud.com"; + ip_list = dns_get_ip_from_name(dns_message.answers, dns_message.header.ancount, domain_name); + + if (ip_list.ip_count > 0) { + // Add IP addresses to DNS map + dns_map_add(dns_map, domain_name, ip_list); + } + + uint32_t old_verdict = verdict; + + // Binary DROP + verdict = NF_DROP; + + #if defined LOG || defined DEBUG + if (verdict == NF_DROP) { + #ifdef LOG + print_hash(hash); + printf(",%ld.%06ld,dns-query-tplinkcloud-backward,,DROP\n", (long int)timestamp.tv_sec, (long int)timestamp.tv_usec); + #endif /* LOG */ + #ifdef DEBUG + printf("DROP - Policy: dns-query-tplinkcloud-backward\n"); + if (old_verdict != NF_DROP) { + dropped_packets++; + printf("Dropped packets: %hu\n", dropped_packets); + } + #endif /* DEBUG */ + } + #endif /* LOG || DEBUG */ + + } + + // Free memory allocated for parsed messages + dns_free_message(dns_message); + + #ifdef LOG + if (verdict != NF_DROP) { + // Log packet as accepted + print_hash(hash); + printf(",%ld.%06ld,dns-query-tplinkapi-backward,,ACCEPT\n", (long int)timestamp.tv_sec, (long int)timestamp.tv_usec); + } + free(hash); + #endif /* LOG */ + + return verdict; +} + + + + +/** + * @brief wan-https-to-domain-tplinkapi callback function, called when a packet enters the queue. + * + * @param pkt_id packet ID for netfilter queue + * @param hash packet payload SHA256 hash (only present if LOG is defined) + * @param timestamp packet timestamp (only present if LOG is defined) + * @param pkt_len packet length, in bytes + * @param payload pointer to the packet payload + * @param arg pointer to the argument passed to the callback function + * @return the verdict for the packet + */ +#ifdef LOG +uint32_t callback_wan_https_to_domain_tplinkapi(int pkt_id, uint8_t *hash, struct timeval timestamp, int pkt_len, uint8_t *payload, void *arg) +#else +uint32_t callback_wan_https_to_domain_tplinkapi(int pkt_id, int pkt_len, uint8_t *payload, void *arg) +#endif /* LOG */ +{ + #ifdef DEBUG + printf("Received packet from nfqueue 10\n"); + #endif + + uint32_t dst_addr = get_ipv4_dst_addr(payload); // IPv4 destination address, in network byte order + uint32_t verdict = NF_ACCEPT; // Packet verdict: ACCEPT or DROP + + /* Policy wan-https-to-domain-tplinkapi */ + if ( + ( dns_entry_contains(dns_map_get(dns_map, "use1-api.tplinkra.com"), (ip_addr_t) {.version = 4, .value.ipv4 = dst_addr}) ) + ) { + + + + uint32_t old_verdict = verdict; + + // Binary DROP + verdict = NF_DROP; + + #if defined LOG || defined DEBUG + if (verdict == NF_DROP) { + #ifdef LOG + print_hash(hash); + printf(",%ld.%06ld,wan-https-to-domain-tplinkapi,,DROP\n", (long int)timestamp.tv_sec, (long int)timestamp.tv_usec); + #endif /* LOG */ + #ifdef DEBUG + printf("DROP - Policy: wan-https-to-domain-tplinkapi\n"); + if (old_verdict != NF_DROP) { + dropped_packets++; + printf("Dropped packets: %hu\n", dropped_packets); + } + #endif /* DEBUG */ + } + #endif /* LOG || DEBUG */ + + } + /* Policy wan-https-to-domain-tplinkcloud */ + if ( + ( dns_entry_contains(dns_map_get(dns_map, "n-devs.tplinkcloud.com"), (ip_addr_t) {.version = 4, .value.ipv4 = dst_addr}) ) + ) { + + + + uint32_t old_verdict = verdict; + + // Binary DROP + verdict = NF_DROP; + + #if defined LOG || defined DEBUG + if (verdict == NF_DROP) { + #ifdef LOG + print_hash(hash); + printf(",%ld.%06ld,wan-https-to-domain-tplinkcloud,,DROP\n", (long int)timestamp.tv_sec, (long int)timestamp.tv_usec); + #endif /* LOG */ + #ifdef DEBUG + printf("DROP - Policy: wan-https-to-domain-tplinkcloud\n"); + if (old_verdict != NF_DROP) { + dropped_packets++; + printf("Dropped packets: %hu\n", dropped_packets); + } + #endif /* DEBUG */ + } + #endif /* LOG || DEBUG */ + + } + + + #ifdef LOG + if (verdict != NF_DROP) { + // Log packet as accepted + print_hash(hash); + printf(",%ld.%06ld,wan-https-to-domain-tplinkapi,,ACCEPT\n", (long int)timestamp.tv_sec, (long int)timestamp.tv_usec); + } + free(hash); + #endif /* LOG */ + + return verdict; +} + + + + +/** + * @brief wan-https-to-domain-tplinkapi-backward callback function, called when a packet enters the queue. + * + * @param pkt_id packet ID for netfilter queue + * @param hash packet payload SHA256 hash (only present if LOG is defined) + * @param timestamp packet timestamp (only present if LOG is defined) + * @param pkt_len packet length, in bytes + * @param payload pointer to the packet payload + * @param arg pointer to the argument passed to the callback function + * @return the verdict for the packet + */ +#ifdef LOG +uint32_t callback_wan_https_to_domain_tplinkapi_backward(int pkt_id, uint8_t *hash, struct timeval timestamp, int pkt_len, uint8_t *payload, void *arg) +#else +uint32_t callback_wan_https_to_domain_tplinkapi_backward(int pkt_id, int pkt_len, uint8_t *payload, void *arg) +#endif /* LOG */ +{ + #ifdef DEBUG + printf("Received packet from nfqueue 11\n"); + #endif + + uint32_t src_addr = get_ipv4_src_addr(payload); // IPv4 source address, in network byte order + uint32_t verdict = NF_ACCEPT; // Packet verdict: ACCEPT or DROP + + /* Policy wan-https-to-domain-tplinkapi-backward */ + if ( + ( dns_entry_contains(dns_map_get(dns_map, "use1-api.tplinkra.com"), (ip_addr_t) {.version = 4, .value.ipv4 = src_addr}) ) + ) { + + + + uint32_t old_verdict = verdict; + + // Binary DROP + verdict = NF_DROP; + + #if defined LOG || defined DEBUG + if (verdict == NF_DROP) { + #ifdef LOG + print_hash(hash); + printf(",%ld.%06ld,wan-https-to-domain-tplinkapi-backward,,DROP\n", (long int)timestamp.tv_sec, (long int)timestamp.tv_usec); + #endif /* LOG */ + #ifdef DEBUG + printf("DROP - Policy: wan-https-to-domain-tplinkapi-backward\n"); + if (old_verdict != NF_DROP) { + dropped_packets++; + printf("Dropped packets: %hu\n", dropped_packets); + } + #endif /* DEBUG */ + } + #endif /* LOG || DEBUG */ + + } + /* Policy wan-https-to-domain-tplinkcloud-backward */ + if ( + ( dns_entry_contains(dns_map_get(dns_map, "n-devs.tplinkcloud.com"), (ip_addr_t) {.version = 4, .value.ipv4 = src_addr}) ) + ) { + + + + uint32_t old_verdict = verdict; + + // Binary DROP + verdict = NF_DROP; + + #if defined LOG || defined DEBUG + if (verdict == NF_DROP) { + #ifdef LOG + print_hash(hash); + printf(",%ld.%06ld,wan-https-to-domain-tplinkcloud-backward,,DROP\n", (long int)timestamp.tv_sec, (long int)timestamp.tv_usec); + #endif /* LOG */ + #ifdef DEBUG + printf("DROP - Policy: wan-https-to-domain-tplinkcloud-backward\n"); + if (old_verdict != NF_DROP) { + dropped_packets++; + printf("Dropped packets: %hu\n", dropped_packets); + } + #endif /* DEBUG */ + } + #endif /* LOG || DEBUG */ + + } + + + #ifdef LOG + if (verdict != NF_DROP) { + // Log packet as accepted + print_hash(hash); + printf(",%ld.%06ld,wan-https-to-domain-tplinkapi-backward,,ACCEPT\n", (long int)timestamp.tv_sec, (long int)timestamp.tv_usec); + } + free(hash); + #endif /* LOG */ + + return verdict; +} + + + +/** + * @brief SIGINT handler, flush stdout and exit. + * + * @param arg unused + */ +void sigint_handler(int arg) { + fflush(stdout); + exit(0); +} + + +/** + * @brief Print program usage. + * + * @param prog program name + */ +void usage(char* prog) { + fprintf(stderr, "Usage: %s [-s DNS_SERVER_IP] [-p DROP_PROBA]\n", prog); +} + + +/** + * @brief Program entry point + * + * @param argc number of command line arguments + * @param argv list of command line arguments + * @return exit code, 0 if success + */ +int main(int argc, char *argv[]) { + + // Initialize variables + int ret; + char *dns_server_ip = "8.8.8.8"; // Default DNS server: Google Quad8 + + // Setup SIGINT handler + signal(SIGINT, sigint_handler); + + + /* COMMAND LINE ARGUMENTS */ + int opt; + while ((opt = getopt(argc, argv, "hp:s:")) != -1) + { + switch (opt) + { + case 'h': + /* Help */ + usage(argv[0]); + exit(EXIT_SUCCESS); + case 'p': + /* Random verdict mode: drop probability (float between 0 and 1) */ + DROP_PROBA = atof(optarg); + break; + case 's': + /* IP address of the network gateway */ + dns_server_ip = optarg; + break; + default: + usage(argv[0]); + exit(EXIT_FAILURE); + } + } + #ifdef DEBUG + printf("Drop probability for random verdict mode: %f\n", DROP_PROBA); + #endif /* DEBUG */ + + + #ifdef LOG + // CSV log file header + printf("hash,timestamp,policy,state,verdict\n"); + #endif /* LOG */ + + + /* GLOBAL STRUCTURES INITIALIZATION */ + + // Initialize variables for DNS + dns_map = dns_map_create(); + dns_message_t dns_response; + ip_list_t ip_list; + dns_entry_t *dns_entry; + + // Open socket for DNS + int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sockfd < 0) { + perror("Socket creation failed"); + exit(EXIT_FAILURE); + } + + // Server address: network gateway + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(53); + server_addr.sin_addr.s_addr = inet_addr(dns_server_ip); + + // Add addresses for domain use1-api.tplinkra.com to DNS map + ret = dns_send_query("use1-api.tplinkra.com", sockfd, &server_addr); + if (ret == 0) { + ret = dns_receive_response(sockfd, &server_addr, &dns_response); + if (ret == 0) { + ip_list = dns_get_ip_from_name(dns_response.answers, dns_response.header.ancount, "use1-api.tplinkra.com"); + dns_map_add(dns_map, "use1-api.tplinkra.com", ip_list); + #ifdef DEBUG + // Check DNS map has been correctly updated + dns_entry = dns_map_get(dns_map, "use1-api.tplinkra.com"); + dns_entry_print(dns_entry); + #endif /* DEBUG */ + } + } + + // Add addresses for domain n-devs.tplinkcloud.com to DNS map + ret = dns_send_query("n-devs.tplinkcloud.com", sockfd, &server_addr); + if (ret == 0) { + ret = dns_receive_response(sockfd, &server_addr, &dns_response); + if (ret == 0) { + ip_list = dns_get_ip_from_name(dns_response.answers, dns_response.header.ancount, "n-devs.tplinkcloud.com"); + dns_map_add(dns_map, "n-devs.tplinkcloud.com", ip_list); + #ifdef DEBUG + // Check DNS map has been correctly updated + dns_entry = dns_map_get(dns_map, "n-devs.tplinkcloud.com"); + dns_entry_print(dns_entry); + #endif /* DEBUG */ + } + } + + + + /* NFQUEUE THREADS LAUNCH */ + + // Create threads + uint8_t i = 0; + + /* dns-query-tplinkapi */ + // Setup thread-specific data + thread_data[i].id = i; + thread_data[i].seed = time(NULL) + i; + thread_arg_t thread_arg_dns_query_tplinkapi = { + .queue_id = 0, + .func = &callback_dns_query_tplinkapi, + .arg = &(thread_data[i].id) + }; + ret = pthread_create(&(thread_data[i++].thread), NULL, nfqueue_thread, (void *) &thread_arg_dns_query_tplinkapi); + assert(ret == 0); + + /* dns-query-tplinkapi-backward */ + // Setup thread-specific data + thread_data[i].id = i; + thread_data[i].seed = time(NULL) + i; + thread_arg_t thread_arg_dns_query_tplinkapi_backward = { + .queue_id = 1, + .func = &callback_dns_query_tplinkapi_backward, + .arg = &(thread_data[i].id) + }; + ret = pthread_create(&(thread_data[i++].thread), NULL, nfqueue_thread, (void *) &thread_arg_dns_query_tplinkapi_backward); + assert(ret == 0); + + /* wan-https-to-domain-tplinkapi */ + // Setup thread-specific data + thread_data[i].id = i; + thread_data[i].seed = time(NULL) + i; + thread_arg_t thread_arg_wan_https_to_domain_tplinkapi = { + .queue_id = 10, + .func = &callback_wan_https_to_domain_tplinkapi, + .arg = &(thread_data[i].id) + }; + ret = pthread_create(&(thread_data[i++].thread), NULL, nfqueue_thread, (void *) &thread_arg_wan_https_to_domain_tplinkapi); + assert(ret == 0); + + /* wan-https-to-domain-tplinkapi-backward */ + // Setup thread-specific data + thread_data[i].id = i; + thread_data[i].seed = time(NULL) + i; + thread_arg_t thread_arg_wan_https_to_domain_tplinkapi_backward = { + .queue_id = 11, + .func = &callback_wan_https_to_domain_tplinkapi_backward, + .arg = &(thread_data[i].id) + }; + ret = pthread_create(&(thread_data[i++].thread), NULL, nfqueue_thread, (void *) &thread_arg_wan_https_to_domain_tplinkapi_backward); + assert(ret == 0); + + // Wait forever for threads + for (i = 0; i < NUM_THREADS; i++) { + pthread_join(thread_data[i++].thread, NULL); + } + + + /* FREE MEMORY */ + + // Free DNS map + dns_map_free(dns_map); + + return 0; +} diff --git a/test/translator/CMakeLists.txt b/test/translator/CMakeLists.txt deleted file mode 100644 index 1caaa531f77e8ce1e53881e6a70e0caa8fb4eb32..0000000000000000000000000000000000000000 --- a/test/translator/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -# Minimum required CMake version -cmake_minimum_required(VERSION 3.20) - -add_subdirectory(devices) diff --git a/test/translator/devices/.gitignore b/test/translator/devices/.gitignore deleted file mode 100644 index e4b444ff32febd567ed6167b42f1e6f50fdc4d1e..0000000000000000000000000000000000000000 --- a/test/translator/devices/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Translator's output files -firewall.nft -nfqueues.c -*/CMakeLists.txt diff --git a/test/translator/devices/CMakeLists.txt b/test/translator/devices/CMakeLists.txt deleted file mode 100644 index 8a80a67822c4385661fe5c4721238f103ece8718..0000000000000000000000000000000000000000 --- a/test/translator/devices/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Minimum required CMake version -cmake_minimum_required(VERSION 3.20) - -# Devices -add_subdirectory(tplink-plug) diff --git a/test/translator/devices/tplink-plug/profile.yaml b/test/translator/devices/tplink-plug/profile.yaml deleted file mode 100644 index 69f51f1fa4fbe2326cce7ae03705482d2a2fe72d..0000000000000000000000000000000000000000 --- a/test/translator/devices/tplink-plug/profile.yaml +++ /dev/null @@ -1,94 +0,0 @@ -# Sample profile for the TP-Link smart plug. - ---- -device-info: - name: tplink-plug - mac: 50:c7:bf:ed:0a:54 - ipv4: 192.168.1.135 - network: wireless - - -single-policies: - - ### LAN ### - - ## TCP with phone - - lan-tcp-to-phone: - protocols: - tcp: - src-port: 9999 - ipv4: - src: self - dst: 192.168.1.222 - bidirectional: true - - - ## UDP with phone - - lan-udp-to-phone: - protocols: - udp: - src-port: 9999 - ipv4: - src: self - dst: 192.168.1.222 - bidirectional: true - - - ## DNS for domain name use1-api.tplinkra.com - - dns-query-tplinkapi: - protocols: - dns: - qtype: A - domain-name: use1-api.tplinkra.com - udp: - dst-port: 53 - ipv4: - src: self - dst: gateway - bidirectional: true - - - ## DNS for domain name n-devs.tplinkcloud.com - - dns-query-tplinkcloud: - protocols: - dns: - qtype: A - domain-name: n-devs.tplinkcloud.com - udp: - dst-port: 53 - ipv4: - src: self - dst: gateway - bidirectional: true - - - ### WAN ### - - ## HTTPS with domain use1-api.tplinkra.com - - wan-https-to-domain-tplinkapi: - protocols: - tcp: - dst-port: 443 - ipv4: - src: self - dst: use1-api.tplinkra.com - bidirectional: true - - - ## HTTPS with domain n-devs.tplinkcloud.com - - wan-https-to-domain-tplinkcloud: - protocols: - tcp: - dst-port: 443 - ipv4: - src: self - dst: n-devs.tplinkcloud.com - bidirectional: true - -... \ No newline at end of file diff --git a/test/translator/translate.sh b/test/translator/translate.sh deleted file mode 100755 index d72d97ae492b542f019c787a69396e1be1ba25c5..0000000000000000000000000000000000000000 --- a/test/translator/translate.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -## CONSTANTS -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) # This script's path -DEVICES_DIR="$SCRIPT_DIR/devices" -TRANSLATOR_PATH="$SCRIPT_DIR/../../src/translator/translator.py" - -# Ensure globbing expands to an empty list if no matches are found -shopt -s nullglob - -# Loop over devices -NFQ_BASE_ID=0 -for DEVICE in "$DEVICES_DIR"/*/; do - python3 "$TRANSLATOR_PATH" "$DEVICE"profile.yaml $NFQ_BASE_ID - ((NFQ_BASE_ID=NFQ_BASE_ID+100)) -done diff --git a/test/runtime/CMakeLists.txt b/test/unit/CMakeLists.txt similarity index 100% rename from test/runtime/CMakeLists.txt rename to test/unit/CMakeLists.txt diff --git a/test/runtime/Vagrantfile b/test/unit/Vagrantfile similarity index 94% rename from test/runtime/Vagrantfile rename to test/unit/Vagrantfile index 6faa57f898eeb0de78715523c85968aebca05ab5..e019740d88484503a43ebd240a29f1e9644e38fd 100644 --- a/test/runtime/Vagrantfile +++ b/test/unit/Vagrantfile @@ -11,7 +11,7 @@ VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| # https://vagrantcloud.com/ubuntu config.vm.box = "ubuntu/jammy64" # Ubuntu 22.04 - config.vm.hostname = "firewall-test" + config.vm.hostname = "native-build" config.vm.network "private_network", type: "dhcp", name: "vboxnet0" config.vm.provider "virtualbox" do |vb| @@ -32,7 +32,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.provision "shell", inline: <<-SHELL sudo apt-get update sudo apt-get upgrade -y - sudo DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential cmake net-tools tshark nftables libnetfilter-queue-dev libnetfilter-log-dev python3-pip + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential cmake net-tools tshark nftables libnetfilter-queue-dev libnetfilter-log-dev sudo pip3 install scapy SHELL diff --git a/test/runtime/rule_utils.c b/test/unit/rule_utils.c similarity index 100% rename from test/runtime/rule_utils.c rename to test/unit/rule_utils.c