Metasoma is a supervised deep learning system for detecting the spreading phase of IoT botnets in a fully decentralized, collaborative fashion. Each device runs a local instance of the model, monitors its own traffic, and periodically gossips learned device embeddings to peers — achieving detection accuracy competitive with centralized approaches without any global observer.
Ahmed Lekssays, Lodovico Giaretta, Barbara Carminati, Elena Ferrari, Šarūnas Girdzijauskas
Qatar Computing Research Institute · RISE Research Institutes of Sweden · Università degli Studi dell'Insubria · KTH Royal Institute of Technology
Each IoT device maintains a memory tensor — one GRU-based embedding per peer it has observed. When a new packet arrives, the memory updater revises the embeddings of the source and destination devices. A packet classifier and a device classifier read from these embeddings to predict (i) whether the current packet is malicious, (ii) whether the source/destination is a malware-infected node, and (iii) whether the source/destination is currently executing an attack.
Periodically, each device gossips a subset of its embeddings to a randomly chosen peer. The receiver merges the incoming embeddings with its own via a trainable GRU merger, propagating knowledge about devices it has never directly communicated with.
All three components — updater, merger, classifier — are trained jointly on a single weighted binary cross-entropy loss.
metasoma/
├── training/
│ ├── train_v2.py # Main entry point: cross_dataset, in_distribution, combined
│ ├── models_v2.py # GPU-optimized model (micro-batched TBPTT)
│ ├── models.py # Base GRU components (MemoryUpdater, MemoryMerger2)
│ ├── data_loading_v2.py # Batch construction + gossip schedule pre-computation
│ ├── data_preparation.py # Feature engineering; produces Window objects
│ ├── create_splits.py # Generates temporal 80/10/10 splits → data/splits/splits.json
│ ├── preprocess_iot23.py # Parses Zeek conn.log.labeled files (IoT-23)
│ ├── preprocess_kitsune.py # Extracts features from Kitsune pcap + label alignment
│ ├── preprocess_medbiot.py # Converts MedBIoT pcap captures to CSV
│ ├── fraud_detector.py # Normalizing-flow forgery detector (security analysis)
│ └── prepare-models.py # Trains the forgery model for the security experiments
├── runs/v2/
│ ├── cross_dataset/ # Cross-dataset transfer results (Table 2 in paper)
│ ├── in_distribution/ # In-distribution holdout results (Table 3 in paper; also Table 1)
│ └── combined/ # Combined training results
├── inference/ # C++ on-device inference
├── gossip/ # Go gossip protocol implementation
├── autopeering/ # Go peer-discovery implementation
├── simulator/ # Network simulator for gossip/eclipse experiments
├── data/splits/splits.json # Fixed train/val/test indices (do not regenerate)
└── requirements.txt
Three publicly available IoT botnet datasets are used. Do not regenerate data/splits/splits.json — the archived indices are the ones used to produce all reported results.
Guerra-Manzanares et al., IFIP SEC 2020.
Download: https://cs.taltech.ee/research/data/medbiot/
Captures: Bashlite, Mirai, Torii — network of 83 devices.
Place raw pcap files under data/raw/medbiot/.
Mirsky et al., NDSS 2018.
Download: https://www.kaggle.com/datasets/ymirsky/network-attack-dataset-kitsune
Capture: Mirai — network of 12 devices.
Place the Mirai pcap + label CSV under data/raw/kitsune/.
García et al., CTU-Prague 2020.
Download: https://www.stratosphereips.org/datasets-iot23
Captures used (Zeek conn.log.labeled files):
| Capture | Family | Role |
|---|---|---|
| CTU-IoT-Malware-Capture-60-1 | Gagfyt | Test — unseen |
| CTU-IoT-Malware-Capture-8-1 | Hakai | Test — unseen |
| CTU-IoT-Malware-Capture-42-1 | Trojan | Test — unseen |
| CTU-IoT-Malware-Capture-34-1 | Mirai | Test — cross-net |
| CTU-IoT-Malware-Capture-44-1 | Mirai | Test — cross-net |
| CTU-IoT-Malware-Capture-20-1 | Torii | Test — cross-net |
| CTU-IoT-Malware-Capture-21-1 | Torii | Test — cross-net |
| CTU-Honeypot-Capture-4-1 | Benign | Test — benign |
| CTU-Honeypot-Capture-5-1 | Benign | Test — benign |
Place the labeled Zeek logs under data/raw/iot23/.
Python 3.8+ and PyTorch 1.9+ are required. A CUDA-capable GPU is strongly recommended (experiments were run on a single GPU; CPU training is supported but much slower).
pip install -r requirements.txt# IoT-23 (Zeek conn.log.labeled → CSV)
python training/preprocess_iot23.py
# Kitsune (pcap → CSV with Mirai labels)
python training/preprocess_kitsune.py
# MedBIoT (pcap → CSV)
python training/preprocess_medbiot.pyPreprocessed CSVs should end up under:
data/raw/iot23/data/raw/kitsune_csv/data/raw/medbiot_csv/
The file data/splits/splits.json contains the exact per-family train/val/test indices used in the paper. Leave it as is.
If you ever need to regenerate it from scratch (e.g., for a new dataset):
python training/create_splits.pyAll experiments share the same hyperparameters and split file.
# In-distribution holdout (Table 3 in paper)
# Train: MedBIoT + Kitsune (80%); Test: same families, held-out 10%
python training/train_v2.py in_distribution
# Cross-dataset transfer (Table 2 in paper)
# Train: MedBIoT + Kitsune; Test: IoT-23 (entire dataset, never seen during training)
python training/train_v2.py cross_dataset
# Combined training
# Train: all datasets (80%); Test: all datasets, held-out 10%
python training/train_v2.py combinedOptional environment variables:
| Variable | Default | Description |
|---|---|---|
METASOMA_EPOCHS |
5 |
Number of training epochs |
METASOMA_MAX_PKTS |
500000 |
Max packets per family (evenly-spaced subsampling) |
METASOMA_MAX_WINS |
unlimited |
Max windows per epoch |
Results are written to runs/v2/{exp_name}/results.json and logged to Weights & Biases.
| Parameter | Value | Description |
|---|---|---|
| Memory dimension |
32 | Per-device GRU hidden size |
| Window size | 5,000 packets | Truncated-BPTT sequence length |
| TBPTT interval | 50 packets | Micro-batch size (graph truncation) |
| Gossip interval | 15 s | Average time between gossip rounds |
| Memories per gossip | 8 | IP entries shared per round |
| Learning rate | 1e-3 | Adam optimizer |
| Epochs | 5 | |
| Public IP buckets | 256 | Hash buckets for non-private IPs |
| Random seed | 42 |
Train: MedBIoT + Kitsune (80%). Test: same families, last 10% by timestamp.
| Family | PKT F1 | PKT AUROC | NMAL F1 | NMAL AUROC | NATK F1 | NATK AUROC |
|---|---|---|---|---|---|---|
| Kitsune-Mirai | 0.999 | 1.000 | 0.978 | 1.000 | 0.996 | 1.000 |
| MedBIoT-Bashlite | 0.993 | 1.000 | 0.964 | 0.999 | 0.915 | 0.982 |
| MedBIoT-Mirai | 0.988 | 0.998 | 0.966 | 0.998 | 0.963 | 0.999 |
| MedBIoT-Torii | 0.999 | 1.000 | 0.984 | 1.000 | 0.994 | 1.000 |
| Macro avg. | 0.960 | 0.998 | 0.572 | 0.751 | 0.798 | 0.722 |
PKT = packet malicious detection; NMAL = node malware detection; NATK = node attack detection.
Train: MedBIoT + Kitsune. Test: IoT-23 (zero-shot — never seen during training).
AUROC is omitted (—) where the test set contains only one class.
| Family | Type | PKT F1 | PKT AUROC | NMAL F1 |
|---|---|---|---|---|
| Gagfyt | Unseen | 0.966 | 0.902 | 0.936 |
| Hakai | Unseen | 0.983 | — | 0.659 |
| Trojan | Unseen | 0.985 | — | 0.659 |
| Mirai | Cross-net | 0.983 | — | 0.661 |
| Torii | Cross-net | 0.986 | — | 0.662 |
| Benign | Benign | 0.984 | — | — |
Packet-level F1 ≥ 0.96 across all IoT-23 families, including three (Gagfyt, Hakai, Trojan) entirely absent from the training data.
| Model | D | PKT | NMAL | NATK |
|---|---|---|---|---|
| LiMNet | 32 | 99.7 | 81.2 | 82.2 |
| Metasoma | 32 | 99.2 | 80.1 | 81.6 |
| LiMNet | 64 | 99.8 | 82.0 | 82.9 |
| Metasoma | 64 | 99.2 | 81.4 | 82.4 |
Metasoma operates in a fully decentralized setting (no central observer) while matching the centralized LiMNet to within 0.5–1.1 AUROC points on packet classification.
The best checkpoints from each experiment are archived under runs/v2/:
| Path | Experiment |
|---|---|
runs/v2/in_distribution/best_model.pt |
In-distribution holdout (Table 3); also used for the LiMNet comparison (Table 1) |
runs/v2/cross_dataset/best_model.pt |
Cross-dataset transfer to IoT-23 (Table 2) |
runs/v2/combined/best_model.pt |
Combined training |
Load with:
import torch
from training.models_v2 import TrainerV2
model = torch.load('runs/v2/in_distribution/best_model.pt', map_location='cpu')The fraud_detector.py and prepare-models.py scripts implement the Normalizing Flow-based forgery detector described in Section 4 of the paper. The forgery detector estimates the density of the training embedding distribution and rejects embeddings with anomalously low likelihood. At the operating point where 0% of honest memories are rejected, only ~0.2% of adversarially forged memories are accepted.
@article{lekssays2025metasoma,
title = {Metasoma: Decentralized and Collaborative Early-Stage Detection of {IoT} Botnets},
author = {Lekssays, Ahmed and Giaretta, Lodovico and Carminati, Barbara and Ferrari, Elena and Girdzijauskas, {\v{S}}ar{\={u}}nas},
year = {2025}
}Update with the venue, volume, DOI, and pages once the paper is published.