This repository contains all artifacts, i.e. code, tools, data, and visualizations for the paper “Secrets Best Not Shared: DNS Privacy Enhancements for the Constrained IoT” presented at the 11th IEEE European Symposium on Security and Privacy (EuroS&P 2026).
- M. S. Lenders, T. C. Schmidt, and M. Wählisch, “Secrets Best Not Shared: DNS Privacy Enhancements for the Constrained IoT,” in IEEE EuroS&P 2026, July 2026, pp. 1476–1495. https://doi.org/10.1109/EuroSP68448.2026.00094
Attackers often identify DNS traffic to disrupt or compromise Internet services. While prior work has focused on encrypting queries using DNS over TLS, HTTPS, or QUIC to counter such attacks, we consider IETF protocols designed for resource-constrained IoT devices and empirically analyze the potential of obfuscating DNS traffic in addition to encryption. We create a dataset of machine-to-machine-compatible data objects along with the corresponding DNS resolution processes, evaluating 296 deployment scenarios of resolving host names, including DNS over the Constrained Application Layer Protocol (CoAP) and an onion routing flavor of CoAP under varying link-layer conditions. We compare them to DNS over HTTPS. Using Random Forest and a header field analysis, we identify fields that leak most information. Our findings show that DNS over CoAP with equalized packet lengths, block-wise transfer, and header compression reduces the accuracy of identifying DNS frames to 86% and further to 77% with payload compression. Our approach outperforms DNS over HTTPS, where classifiers always identify DNS frames based on IP addresses. The dataset is publicly available.
To use, first clone this repository including submodules.
git clone --recurse-submodules https://github.com/netd-tud/artifacts-eurosp26-doc-privacy.git
cd artifacts-eurosp26-doc-privacyOr download the artifacts-eurosp26-doc-privacy-v1.1.0.zip ZIP archive from Zenodo and unzip it in the git repository.
For most sections there are one or more Jupyter notebooks containing documentation and the code to create the output we describe in that section. The first number XX indicates which section it belongs to, for XX_Y the Y notates a subsection. Some of these notebooks have a corresponding directory which contain further code.
- 1. Introduction does not have any code.
- 2. Background does not have any code.
- 3. Creating a Dataset for Privacy Analysis of DNS on the Internet of Things
- 3.1. Thread Model does not have any code.
- 3.2. Data Collection, see
03_2_data_collection.ipynb. - 3.3. Traffic Generation, see
03_3_traffic_generation.ipynb.
- 4. Overview of Data Corpus, see
04_data_corpus_overview.ipynb. - 5. Machine Learning to Identify DNS Traffic, see
05_ml_to_identify_dns_traffic.ipynb. Since these are also based in cross validation Figures 13 and 14 of Section 6 are also generated in this notebook. - 6. Evaluation, see
06_evaluation.ipynb. - 7. Related Work does not have any code.
- 8. Discussion does not have any code. The numbers we discuss in this section we took from
05_ml_to_identify_dns_traffic.ipynband06_evaluation.ipynb. - 9. Conclusion does not have any code.
Additionally, there are two directories that will contain data if you run the notebooks or already contain data generated by us:
input_dataset/will contain the data collected in section 3.2 Data Collection.output_dataset/contains all data generated from section 3.3. Traffic Generation onwards.plots/contains all plots we generated from the data as PDF, ready for import into LaTex, as well as treated base data as CSV to directly generate the plots.
To run the Jupyter notebooks, you can easer run them in a Docker container (recommended usage) or natively on your host system using UV.
This setup was only tested in Linux (Ubuntu 24.04) but it should work on any platform that supports Docker. Installation steps might differ and you might need to adapt some paths in the docker compose files though.
This repository can be used using docker compose. You can find installation instructions for your OS on the Docker website. Also be sure to make sure make docker commands available for non-users, our traffic generation step will make use of that. To that end, we also expose the Docker Daemon socket of the host to the main Docker container. This gives the main Docker container a certain degree of control to the containers running on your host. Make sure, this is okay for you.
Since the experiments in each chapter can run for several weeks, we recommend running this on a dedicated machine, so you can keep running them in the background. If you do the appropriate permissions on your machine, consider running in a virtual machine. Otherwise, see "Using UV" below. Once docker compose is installed, run docker compose up from a command-line in the same directory you find this README in.
docker compose upOnce it is done fetching or building the image, Jupyter Lab will start and a URL to open in your browser will be shown, e.g.,
jupyter-1 | To access the server, open this file in a browser:
jupyter-1 | file:/home/user/.local/share/jupyter/runtime/jpserver-12-open.html
jupyter-1 | Or copy and paste one of these URLs:
jupyter-1 | http://localhost:8888/lab?token=f63eeb3d8158079dfea465051cbb4598fbe5575f96a7ffdb
jupyter-1 | http://127.0.0.1:8888/lab?token=f63eeb3d8158079dfea465051cbb4598fbe5575f96a7ffdb
Alternatively, you can get the URL using the following command
$ docker compose exec jupyter su - user -c "jupyter server list"
Currently running servers:
http://localhost:8888/?token=f63eeb3d8158079dfea465051cbb4598fbe5575f96a7ffdb :: /appIn the Jupyter Lab, following the link provided by the command above, start with the notebook of your choice.
We provide the docker images needed for the artifacts at several container registries. You can find the image names for each repository in the following table.
Sadly, support to configure these easily is not provided when running docker compose. As such, the easiest way to use the repositories is to search and replace the image: key within the docker compose files. E.g., to use the Codeberg Packages registry, use the following
find . -name *.yaml | xargs grep -l 'image: *docker\.io/miri64' | xargs sed -i 's#image: *docker\.io/miri64#image: codeberg.org/miri64'If port 8888 is already in use on your system, you can also pick another by setting the JUPYTER_PORT environment variable:
JUPYTER_PORT=8889 docker compose upIf your host user has a different UID or GID than 1000, this also can be configured:
HOST_UID="$(id -u)" HOST_GID="$(id -g)" docker compose upWe do not recommend this method, since updates during the years since we published this repository might lead to incompatibilities. However, you might need to use it, if you do not have access to Docker or a virtual machine where you can run Docker on your machine.
First, install the package and project manager for Python UV (you might need to use flags like --user or --system to install this on your specific system, see pip documentation):
pip install uvUV has some advantages over the classic pip package manager: First, it is much faster. Second, it allows for a hassle-free deployment of python versions that are not pre-installed on your system.
We tested the code of our repository for Python 3.12 so we recommend installing that if available.
uv python install cpython-3.12Additional dependencies from the system might be needed. Please take a look at the apt-get install (the Debian package manager command used to install dependencies there) line from our Dockerfile for a (Debian-13-based) listing of the dependencies.
Now create and step into a virtual environment for this repository.
uv venv --python python3.12 .env
. .env/bin/activateLast, install the dependencies:
uv pip install -r requirements.txtYou now can start Jupyter Lab by running the following command (you might want to use the --port argument to change the port).
jupyter labIn the Jupyter Lab, following the link automatically opened in your browser, start with the notebook of your choice.