A self-contained 802.11 WiFi frame injection tool with a browser-based control panel. Runs on a Raspberry Pi (or any Linux SBC) inside Docker. Supports multiple named injection tasks running simultaneously across a pool of USB WiFi adapters.
- How It Works
- Hardware Requirements
- Quick Start
- Configuration
- Web UI
- REST API Reference
- Project Structure
- Architecture
- Adding a New Packet Type
- Adding a New Task Type
- Dependencies
On startup the system:
- Scans
/sys/class/netfor all USB wireless adapters (built-in Pi WiFi is intentionally skipped). - Puts every detected adapter into monitor mode and adds it to the interface pool.
- Loads tasks from
config/config.tomland starts any tasks withenabled = true. - Splits the pool evenly across enabled tasks — each task gets
floor(N/T)adapters. - Starts a FastAPI web server on port
8080for live monitoring and control.
All configuration is persisted to config/config.toml. Changes made through the web UI are written back to disk immediately.
| Component | Notes |
|---|---|
| Raspberry Pi (any model with USB) | Tested target. Any Linux SBC works. |
| USB WiFi adapter(s) that support monitor mode and packet injection | e.g. Alfa AWUS036ACH, AWUS036ACS, cards based on RT8812AU, AR9271, MT7612U. The Pi's built-in BCM chip is not used — it does not support injection reliably. |
| MicroSD + power | Standard Pi setup. |
Important: Plug in USB WiFi adapters before starting the container. Adapters are detected once at startup with a 10-second retry watchdog. If a dongle is plugged in after startup the watchdog will find it automatically.
# Clone / copy the project onto the Pi, then:
./host.sh # build the Docker image and start (foreground)
./host.sh -d # start detached (background)Then open a browser on any device on the same network:
http://<pi-ip>:8080
host.sh installs Docker automatically if it isn't present.
The container is configured with restart: unless-stopped in docker-compose.yml, so it comes back automatically after a reboot. Tasks with enabled = true in config begin transmitting immediately on container start.
[api]
enabled = true
host = "0.0.0.0"
port = 8080
[[tasks]]
type = "standard"
id = "demo0001"
name = "Deauth Flood"
enabled = false
channel = 6
packets_per_second = 10
packet = {type = "deauth", source_mac = "aa:bb:cc:dd:ee:ff", dest_mac = "ff:ff:ff:ff:ff:ff", bssid = "aa:bb:cc:dd:ee:ff", reason = 7}Multiple [[tasks]] blocks are supported. The web UI reads and writes this file at runtime.
Injects a single 802.11 frame type at a fixed rate on a fixed channel. One worker thread per allocated interface.
| Field | Type | Description |
|---|---|---|
channel |
int | 802.11 channel (1–165) |
packets_per_second |
int | Injection rate (1–1000) |
packet |
PacketConfig | Frame type and parameters (see below) |
Each allocated interface cycles through a list of channels with a configurable dwell time. Interfaces are staggered — interface i starts at channels[i % len(channels)] — giving simultaneous multi-channel coverage.
| Field | Type | Description |
|---|---|---|
channels |
list[int] | Channel list to cycle through, e.g. [1, 6, 11] |
dwell_ms |
int | Milliseconds to stay on each channel before hopping |
packets_per_second |
int | Injection rate per interface per channel |
packet |
PacketConfig | Frame type and parameters |
Broadcasts a rotating sequence of beacon frames with SSIDs following the pattern {task_name}-{seq_num}-{pos}. The sequencer advances through positions 1 → sequence_length, then increments seq_num and repeats. Each SSID is broadcast for one second.
| Field | Type | Description |
|---|---|---|
task_name |
str | SSID prefix (e.g. "ap" → "ap-1-1", "ap-1-2", …) |
sequence_length |
int | Number of positions before seq_num increments |
channel |
int | 802.11 channel to transmit on |
packets_per_second |
int | Injection rate per interface |
source_mac |
MAC | Spoofed AP source address |
bssid |
MAC | BSS identifier |
Used by the standard and span task types.
| Field | Type | Description |
|---|---|---|
source_mac |
MAC | Spoofed source address (typically the AP's MAC) |
dest_mac |
MAC | Target client, or ff:ff:ff:ff:ff:ff to broadcast |
bssid |
MAC | BSS identifier |
reason |
int | Reason code (7 = most common) |
| Field | Type | Description |
|---|---|---|
ssid |
str | Network name to advertise |
source_mac |
MAC | Spoofed AP source address |
bssid |
MAC | BSS identifier |
| Field | Type | Description |
|---|---|---|
source_mac |
MAC | Spoofed source address |
ssid |
str | Target SSID, or empty string for wildcard |
| Field | Type | Description |
|---|---|---|
source_mac |
MAC | Spoofed source address |
dest_mac |
MAC | Target client, or broadcast |
bssid |
MAC | BSS identifier |
reason |
int | Reason code |
| Field | Type | Description |
|---|---|---|
source_mac |
MAC | Spoofed source address |
dest_mac |
MAC | Target AP |
bssid |
MAC | BSS identifier |
algo |
int | 0 = Open System, 1 = Shared Key |
seq |
int | Sequence number (1–4) |
Accessible at http://<pi-ip>:8080.
At the top of every page, shows each detected USB adapter:
- Green dot — adapter in monitor mode, ready for injection. Shows interface name and current channel.
- Red error — no adapters found. Plug one in; the watchdog retries every 10 seconds.
Each configured task appears as a card showing:
| Field | Description |
|---|---|
| Name | Task name with type badge (Standard / Span / Beacon Seq) |
| Status | RUNNING (animated green) or STOPPED |
| Packets Sent | Total frames injected this session |
| Session Time | Time since this task started |
| Channel / Rate | Task-specific summary |
Buttons per card: Start, Stop, Edit, Delete.
Click + Add Task or Edit on a card to open the configuration modal:
- Task Name and Task Type selector at the top.
- Type-specific fields render dynamically below.
- For
standardandspan, a Packet Config section appears with a Frame Type selector and per-type fields. - MAC address fields have a Random button generating a locally-administered unicast MAC.
| Method | Path | Description |
|---|---|---|
GET |
/api/health |
Liveness probe — returns {"status": "ok"} |
GET |
/api/status |
System uptime |
| Method | Path | Description |
|---|---|---|
GET |
/api/pool |
Pool status — adapter list, count, readiness |
| Method | Path | Description |
|---|---|---|
GET |
/api/tasks |
List all tasks with runtime status |
POST |
/api/tasks |
Create a new task |
GET |
/api/tasks/{id} |
Get one task's status |
PUT |
/api/tasks/{id} |
Replace a task's configuration |
DELETE |
/api/tasks/{id} |
Remove a task |
POST |
/api/tasks/{id}/start |
Enable and start a task |
POST |
/api/tasks/{id}/stop |
Disable and stop a task |
curl -X POST http://pi-ip:8080/api/tasks \
-H 'Content-Type: application/json' \
-d '{
"type": "span",
"name": "Channel Sweep",
"channels": [1, 6, 11],
"dwell_ms": 500,
"packets_per_second": 20,
"packet": {"type": "deauth", "source_mac": "de:ad:be:ef:00:01",
"dest_mac": "ff:ff:ff:ff:ff:ff", "bssid": "de:ad:be:ef:00:01", "reason": 7}
}'curl -X POST http://pi-ip:8080/api/tasks/abc12345/startpacket-spammer/
├── run.py # Entry point
├── pyproject.toml # Python dependencies
├── Dockerfile
├── docker-compose.yml # Single service, privileged, host network
├── host.sh # Build + start script
│
├── config/
│ └── config.toml # Runtime configuration (written by web UI)
│
├── app/
│ ├── models/
│ │ └── config.py # Pydantic config models (all task + packet types)
│ ├── sender/
│ │ ├── utils.py # Shared subprocess helpers (iw, ip link)
│ │ ├── pool.py # InterfacePool — manages all USB adapters
│ │ └── tasks/
│ │ ├── base.py # BaseTaskEngine abstract class
│ │ ├── standard.py # StandardTaskEngine — fixed channel
│ │ ├── span.py # SpanTaskEngine — staggered multi-channel
│ │ ├── beacon_seq.py # BeaconSequenceEngine — SSID rotation
│ │ └── manager.py # TaskManager — allocates pool across tasks
│ ├── host/
│ │ └── runtime.py # Top-level coordinator
│ └── api/
│ └── routes.py # FastAPI route handlers
│
└── base/ # Reusable infrastructure
├── api/
│ ├── server.py # FastAPI + Uvicorn lifecycle wrapper
│ ├── routes.py # /api/health and /api/status
│ └── static/
│ └── index.html # Web UI
└── config/
├── models.py # APIConfig, BaseHostConfig
└── loader.py # TOML load/save via Pydantic
┌─────────────────────────────────────────────────┐
│ Docker container (privileged, host network) │
│ │
│ HostRuntime │
│ ├─ InterfacePool │
│ │ scan /sys/class/net for ALL USB WiFi │
│ │ iw dev <iface> set type monitor │
│ │ watchdog: retry every 10s if pool empty │
│ │ │
│ ├─ TaskManager │
│ │ allocate(N_adapters / N_active_tasks) │
│ │ rebalance on add / remove / toggle │
│ │ │
│ │ StandardTaskEngine │
│ │ ├─ _WorkerThread(iface0) │
│ │ └─ _WorkerThread(iface1) │
│ │ fixed channel, fixed frame, N pps │
│ │ │
│ │ SpanTaskEngine │
│ │ ├─ _SpanWorker(iface0, offset=0) │
│ │ └─ _SpanWorker(iface1, offset=1) │
│ │ staggered ch cycling, dwell_ms │
│ │ │
│ │ BeaconSequenceEngine │
│ │ ├─ _Sequencer → updates shared frame │
│ │ ├─ _Worker(iface0) │
│ │ └─ _Worker(iface1) │
│ │ all interfaces, rotating SSID │
│ │ │
│ └─ APIServer (FastAPI + Uvicorn :8080) │
│ /api/pool │
│ /api/tasks (CRUD + start/stop) │
│ / → index.html (web UI) │
└─────────────────────────────────────────────────┘
│ host network namespace
▼
USB WiFi dongle pool (wlan1, wlan2, …)
in monitor mode
│
▼
802.11 frames injected over the air
When a task is started or stopped the pool is reallocated:
floor(N / T)interfaces per task (T = active task count)- First
N % Ttasks each get one extra interface - If a task gets zero interfaces it logs a warning and does not start
Worker i in a SpanTask starts at channel index i % len(channels). With 3 interfaces and channels [1, 6, 11] the interface layout is:
iface0 → starts at ch1 → hops to ch6 → ch11 → ch1 …
iface1 → starts at ch6 → hops to ch11 → ch1 → ch6 …
iface2 → starts at ch11 → hops to ch1 → ch6 → ch11 …
All three channels are covered simultaneously.
- Add a config model in app/models/config.py:
class MyPacketConfig(BaseModel):
type: Literal["my_type"] = "my_type"
some_field: str = "value"-
Add it to
PacketConfigin the same file. -
Create a builder at
app/sender/packets/my_type.py. -
Register it in the factory (app/sender/packets/factory.py).
-
Add it to the web UI in base/api/static/index.html — add to
PACKET_SCHEMAS.
-
Add a config model in app/models/config.py and add it to
TaskConfig. -
Create a task engine in
app/sender/tasks/my_task.pyextendingBaseTaskEngine. -
Register it in
_make_engine()in app/sender/tasks/manager.py. -
Add it to the web UI in base/api/static/index.html — add to
TASK_SCHEMASand the<select>element.
| Package | Purpose |
|---|---|
scapy |
802.11 frame construction and raw socket injection |
fastapi |
REST API framework |
uvicorn[standard] |
ASGI server for FastAPI |
pydantic |
Config validation and serialisation |
tomli-w |
TOML serialisation (writing config back to disk) |
System packages installed in the Docker image: iw, iproute2, net-tools.
Python 3.13+ required.