diff --git a/.ci_scripts/cross-compile/Dockerfile b/.ci_scripts/cross-compile/Dockerfile
deleted file mode 100644
index e75b90f70fc0e7859242ab766c50db41d8728bed..0000000000000000000000000000000000000000
--- a/.ci_scripts/cross-compile/Dockerfile
+++ /dev/null
@@ -1,66 +0,0 @@
-# Dockerfile describing the container used to
-# cross-compile the projet for OpenWrt,
-# in GitHub Actions.
-# The default platform is the TP-Link WDR4900.
-
-# Base image: Ubuntu 22.04 LTS
-FROM ubuntu:22.04
-
-# Set build configuration variables
-ARG VERSION=v22.03.5
-ARG ROUTER=tl-wdr4900
-ARG TOOLCHAIN_DIR=toolchain-powerpc_8540_gcc-11.2.0_musl
-ARG TARGET_DIR=target-powerpc_8540_musl
-
-# Set initial working directory
-ENV HOME=/root
-WORKDIR ${HOME}
-
-# Install dependencies
-RUN apt-get update && \
-    apt-get install -y \
-    build-essential \
-    clang \
-    flex \
-    bison \
-    g++ \
-    gawk \
-    gcc-multilib \
-    gettext \
-    git \
-    libncurses5-dev \
-    libssl-dev \
-    python3-distutils \
-    rsync \
-    unzip \
-    zlib1g-dev \
-    file \
-    wget \
-    cmake \
-    python3-pip
-
-# Clone OpenWrt repository
-ENV OPENWRT_HOME=${HOME}/openwrt
-RUN git clone https://git.openwrt.org/openwrt/openwrt.git ${OPENWRT_HOME}
-WORKDIR ${OPENWRT_HOME}
-RUN git checkout ${VERSION}
-
-# Update and install feeds
-RUN ${OPENWRT_HOME}/scripts/feeds update -a
-RUN ${OPENWRT_HOME}/scripts/feeds install -a
-
-# Configure OpenWrt toolchain
-COPY openwrt/${ROUTER}/config/config-minimal ${OPENWRT_HOME}/.config
-ENV FORCE_UNSAFE_CONFIGURE=1
-RUN make defconfig
-RUN make download
-RUN make -j $(($(nproc)+1))
-ENV STAGING_DIR=${OPENWRT_HOME}/staging_dir
-ENV TOOLCHAIN_PATH=${STAGING_DIR}/${TOOLCHAIN_DIR}
-ENV TARGET_PATH=${STAGING_DIR}/${TARGET_DIR}
-ENV C_INCLUDE_PATH=${TARGET_PATH}/usr/include
-ENV LD_LIBRARY_PATH=${TARGET_PATH}/usr/lib
-ENV PATH=${TOOLCHAIN_PATH}/bin:$PATH
-
-# Get ready for next steps
-WORKDIR ${HOME}
diff --git a/.ci_scripts/firewall-test/add_nft_rules.sh b/.ci_scripts/firewall-test/add_nft_rules.sh
index 0170f146b75c68f9a9d1496a40bffe44686209ff..f3a8adf8e1b75cd7a4acbf5c52adc44acdbe01da 100755
--- a/.ci_scripts/firewall-test/add_nft_rules.sh
+++ b/.ci_scripts/firewall-test/add_nft_rules.sh
@@ -1,6 +1,6 @@
 EXITCODE=0
 
-for nft_script in $GITHUB_WORKSPACE/devices/*/firewall*.nft
+for nft_script in devices/*/firewall*.nft
 do
     # Try adding the ruleset
     sudo nft -f "$nft_script"
diff --git a/.ci_scripts/firewall-test/run_cppcheck.sh b/.ci_scripts/firewall-test/run_cppcheck.sh
index fde11aadc5a26417254c6167b3a2ea4542421cb2..256a579436040ae51eb08d96443731b642addab5 100755
--- a/.ci_scripts/firewall-test/run_cppcheck.sh
+++ b/.ci_scripts/firewall-test/run_cppcheck.sh
@@ -1,5 +1,5 @@
 EXITCODE=0
-PARSERS_DIR="$GITHUB_WORKSPACE/src/parsers"
+PARSERS_DIR="src/parsers"
 
 # Pattern matching on all source files
 for file in $(find "$GITHUB_WORKSPACE"/include "$GITHUB_WORKSPACE"/src "$GITHUB_WORKSPACE"/devices "$GITHUB_WORKSPACE"/test "$PARSERS_DIR"/include "$PARSERS_DIR"/src "$PARSERS_DIR"/test -name *.h -o -name *.c)
diff --git a/.ci_scripts/firewall-test/run_exec.sh b/.ci_scripts/firewall-test/run_exec.sh
index 0a93f4fbb6388917fdb308e55bd0d9455a44285b..cb4ae7d05293db5c6dc0ab2cd4607b5a5d92869d 100755
--- a/.ci_scripts/firewall-test/run_exec.sh
+++ b/.ci_scripts/firewall-test/run_exec.sh
@@ -2,7 +2,7 @@
 
 # Constants
 TIMEOUT=5  # seconds
-BIN_DIR="$GITHUB_WORKSPACE/bin"
+BIN_DIR="bin"
 
 # Ensure globbing expands to an empty list if no matches are found
 shopt -s nullglob
diff --git a/.ci_scripts/firewall-test/run_tests.sh b/.ci_scripts/firewall-test/run_tests.sh
index 14c4924a0bd5551391a6dd4809e184896d8e7369..6e0c89247adf6813f1bcbafd4230c84f79cf354d 100755
--- a/.ci_scripts/firewall-test/run_tests.sh
+++ b/.ci_scripts/firewall-test/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"
+PARSERS_DIR="src/parsers"
+VALGRIND_SUPP=".ci_scripts/firewall-test/valgrind.supp"
 
 PREFIX=""
 for file in "$GITHUB_WORKSPACE"/bin/test/* "$PARSERS_DIR"/bin/test/*
diff --git a/.ci_scripts/firewall-test/translate_profiles.sh b/.ci_scripts/firewall-test/translate_profiles.sh
index 6f39aa59a5c2f3809eeda8379e282cb8b77af1f2..53721a446e86fbb3bec5534b6a72e807de1b60b1 100755
--- a/.ci_scripts/firewall-test/translate_profiles.sh
+++ b/.ci_scripts/firewall-test/translate_profiles.sh
@@ -1,11 +1,11 @@
 # NFQueue start index
 NFQ_ID_START=0
 
-for DEVICE in $GITHUB_WORKSPACE/devices/*
+for DEVICE in devices/*
 do
     if [ -d $DEVICE ]
     then
-        python3 "$GITHUB_WORKSPACE/src/translator/translator.py" "$DEVICE/profile.yaml" $NFQ_ID_START
+        python3 "src/translator/translator.py" "$DEVICE/profile.yaml" $NFQ_ID_START
         NFQ_ID_START=$((NFQ_ID_START+1000))
     fi
 done
diff --git a/.gitignore b/.gitignore
index b2f129f9781217f2014ab5c3f95f734073e9a1c1..6e606fbe5de561247ae23ab9bb6a5f085ae2af8f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ docs
 .vscode/
 .vagrant/
 __pycache__/
+.venv
 
 # PCAP traces
 **/traces/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cfb420da1e46b008d601d88fd8ca0156a6603133
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,28 @@
+variables:
+  GIT_SUBMODULE_STRATEGY: recursive
+
+
+# Natively build and test the project
+job-native-build:
+  script:
+    - sudo .ci_scripts/native-build/install_packages.sh
+    - python3 -m venv .venv
+    - source .venv/bin/activate
+    - pip3 install -r requirements.txt
+    - .ci_scripts/firewall-test/translate_profiles.sh
+    - firewall/build.sh -C firewall
+    - .ci_scripts/native-build/run_tests.sh
+    - .ci_scripts/native-build/run_tests.sh valgrind
+    - .ci_scripts/native-build/run_cppcheck.sh
+    - .ci_scripts/native-build/add_nft_rules.sh
+    - .ci_scripts/native-build/run_exec.sh
+
+
+# Cross-compile the project for the TL-WDR4900 router
+job-cross-compilation:
+  script:
+    - python3 -m venv .venv
+    - source .venv/bin/activate
+    - pip3 install -r requirements.txt
+    - .ci_scripts/firewall-test/translate_profiles.sh
+    - docker compose run cross-compilation /home/user/iot-firewall/docker_cmd.sh tl-wdr4900 $(id -u $USER) $(id -g $USER)
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 5aac4034d9040056064388aff6256576d6a6df7a..70b0f20b48ef7ce25fe40f39029930dd9eeceb4e 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -1,5 +1,5 @@
 services:
-  openwrt:
+  cross-compilation:
     image: fdekeers/openwrt_tl-wdr4900
     #image: fdekeers/openwrt_linksys-wrt1200ac
     container_name: openwrt-firewall
@@ -8,6 +8,6 @@ services:
       #- ROUTER=linksys-wrt1200ac
     volumes:
       - .:/home/user/iot-firewall
-    command: ["/home/user/iot-firewall/build.sh", "-t", "/home/user/iot-firewall/openwrt/tl-wdr4900/tl-wdr4900.cmake"]
-    #command: ["/home/user/iot-firewall/build.sh", "-t", "/home/user/iot-firewall/openwrt/linksys-wrt1200ac/linksys-wrt1200ac.cmake"]
+    command: ["/home/user/iot-firewall/docker_cmd.sh", "tl-wdr4900", "1000", "1000"]
+    #command: ["/home/user/iot-firewall/docker_cmd.sh", "linksys-wrt1200ac", "1000", "1000"]
     restart: no
diff --git a/docker_cmd.sh b/docker_cmd.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ed43159fab1ad2389b1a5fdb20d08cc8a2cf7c36
--- /dev/null
+++ b/docker_cmd.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+# Script to run inside the cross-compilation Docker container.
+
+
+# Base directory
+BASE_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
+
+
+### ARGUMENTS ###
+
+# Print usage information
+usage() {
+    echo "Usage: $0 ROUTER NEW_ID NEW_GID" 1>&2
+    exit 1
+}
+
+# Verify number of arguments
+if [[ $# -ne 2 ]] && [[ $# -ne 3 ]]; then
+    usage
+fi
+
+## Get command line arguments
+ROUTER=$1
+NEW_UID=$2
+# GID (optional)
+# If not provided, equal to UID
+if [[ $# -eq 2 ]]; then
+    NEW_GID=$NEW_UID
+elif [[ $# -eq 3 ]]; then
+    NEW_GID=$3
+fi
+
+
+### MAIN ###
+
+# Cross-compile sources
+"$BASE_DIR"/build.sh -C "$BASE_DIR" -t "$BASE_DIR"/firewall/openwrt/$ROUTER/$ROUTER.cmake
+
+# Change perimissions
+ROOT_UID=0
+for DIR in build bin; do
+    DIR="$BASE_DIR"/$DIR
+    find $DIR -uid $ROOT_UID -exec chown -h $NEW_UID {} \;
+    find $DIR -gid $ROOT_UID -exec chgrp -h $NEW_GID {} \;
+done
diff --git a/src/parsers b/src/parsers
index 746030e98cab5d64007c71b21aa113db77175959..d052494c48b97becbd08b114bafa58f59471f567 160000
--- a/src/parsers
+++ b/src/parsers
@@ -1 +1 @@
-Subproject commit 746030e98cab5d64007c71b21aa113db77175959
+Subproject commit d052494c48b97becbd08b114bafa58f59471f567