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