vlan-probe 🛡️
VLAN Isolation & Network Permission Probe Tool — verify your firewall rules by probing target VLAN subnets, IPs, and ports from the host and reporting any unauthorized inter-VLAN access.
vlan-probe is a command-line tool. Install it with pipx
so it runs in an isolated environment and the vlan-probe command is available
on your PATH.
Requirements
- Python 3.10 or newer
Installation
Install with pipx (recommended)
pipx install vlan-probe
Upgrade to a newer release:
pipx upgrade vlan-probe
If you do not already have pipx, install it first (macOS/Homebrew, Debian/Ubuntu or Arch examples; see the pipx docs for other systems):
brew install pipx && pipx ensurepath
# or
apt install pipx && pipx ensurepath
# or
pacman -S python-pipx && pipx ensurepath
Install from source
# with pipx
pipx install .
# with uv (alternative)
uv tool install .
# or install directly into your environment
pip install .
Usage 📡
- Install or copy to a host.
- Create a TOML config at
/etc/vlan_probe.tomlor pass-cto point to a different file. - Run
vlan-probe -f tableorpython -m vlan_probe -f table.
Configuration
Configuration uses TOML format for easy human readability and simplicity:
[[targets]]
name = "Internal - Device A SSH"
vlan = "Internal"
ip = "10.10.1.10"
port = 22
protocol = "tcp"
expected_blocked = true
[[targets]]
name = "Internal - Gateway HTTP"
vlan = "Internal"
ip = "10.10.1.1"
port = 80
protocol = "tcp"
expected_blocked = true
[[targets]]
name = "External - Public DNS"
vlan = "External"
ip = "8.8.8.8"
port = 53
protocol = "udp"
expected_blocked = false
For each target:
expected_blocked = true— the probe expects the firewall to deny access; a reachable target is reported as a violation 🔴.expected_blocked = false— the probe expects the firewall to allow access; an unreachable target is reported as a failure 🔴.
Supported protocols: tcp, udp (with DNS probing on port 53), icmp (ping),
and sctp.
Options
-c, --config PATH Path to config TOML file (default: /etc/vlan_probe.toml)
-f, --format FORMAT Output format: ndjson, json, or table (default: ndjson)
-t, --timeout SECONDS Socket connection timeout (default: 2.0)
-s, --strict Exit with code 1 if any violations occur
--mqtt Publish results to MQTT (requires [mqtt] config section)
--color [auto|always|never] Colorize output (default: auto)
Scheduled MQTT reporting ⏱️
Run the probe on a schedule and publish results to an MQTT broker so dashboards
and automations can track VLAN isolation state over time. Scheduling is handled
by a systemd timer — each run is a short-lived process that probes, publishes,
and exits.
Add an [mqtt] section to the config file:
[mqtt]
host = "mqtt.example.com"
port = 8883
username = "vlan-probe" # optional
password = "s3cret" # optional
tls = true # optional, default false
ca_certs = "/etc/ssl/certs/ca-certificates.crt" # optional
insecure = false # optional
topic_prefix = "vlan-probe" # optional, default "vlan-probe"
retain = true # optional, default true
qos = 1 # optional, default 1
connect_timeout = 5.0 # optional, default 5.0
Then run with --mqtt:
vlan-probe --mqtt
Topics published (all retained at QoS 1):
vlan-probe/<hostname>/summary— run summary JSON (totals + violations)vlan-probe/<hostname>/targets/<vlan>/<target>— one message per probed target
A failed MQTT delivery is fatal (exit code 2); probe violations in strict mode
still exit 1. --mqtt without an [mqtt] section is a config error.
systemd timer
Ship the example units and enable the timer (adjust the interval in
vlan-probe.timer, default every 5 minutes):
# system-wide (root)
sudo cp deploy/systemd/vlan-probe.service deploy/systemd/vlan-probe.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now vlan-probe.timer
# user (no root)
mkdir -p ~/.config/systemd/user
cp deploy/systemd/user/vlan-probe.service deploy/systemd/user/vlan-probe.timer ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now vlan-probe.timer
Ops:
journalctl -u vlan-probe— probe + delivery logs per run.systemctl list-timers vlan-probe— next scheduled run.- Broker credentials:
[mqtt]in the config (chmod 600) or a systemdEnvironmentFile.
Examples
# Display as table
vlan-probe -c ./vlan_probe.toml -f table
# Output JSON
vlan-probe -f json
# Strict mode with custom timeout
vlan-probe -s -t 5.0
# No color output
vlan-probe -f table --color never
Example runs
Table output (IPs are fictional):
VLAN TARGET ENDPOINT STATUS DETAILS
------------------------------------------------------------------------------------------
Internal Internal - Device A SSH 10.10.1.10:22 (tcp) PASS OK
Internal Internal - Device A HTTP 10.10.1.10:8080 (tcp) PASS OK
Internal Internal - DNS Server 10.10.1.1:53 (udp) PASS OK
Internal Internal - Gateway SSH 10.10.1.1:22 (tcp) PASS OK
Internal Internal - Gateway HTTPS 10.10.1.1:443 (tcp) PASS OK
Internal Internal - Gateway HTTP 10.10.1.1:80 (tcp) PASS OK
IoT IoT - Device B 10.10.2.50:80 (tcp) PASS OK
IoT IoT - Device C 10.10.2.100:80 (tcp) PASS OK
Guest Guest - Gateway 10.10.3.1:80 (tcp) PASS OK
DMZ DMZ - Web Server 10.10.4.50:80 (tcp) FAIL EXPECTED_CONNECTIVITY_FAILED: Failed to connect to DMZ - Web Server (10.10.4.50:80)
DMZ DMZ - Web Server HTTPS 10.10.4.50:443 (tcp) FAIL EXPECTED_CONNECTIVITY_FAILED: Failed to connect to DMZ - Web Server HTTPS (10.10.4.50:443)
External External - Public DNS 8.8.8.8:53 (udp) PASS OK
JSON output:
{
"timestamp": "2026-08-15T07:56:17.397044+00:00",
"total_probed": 12,
"passed": 10,
"failed": 2,
"violations": [
{
"vlan": "DMZ",
"target": "DMZ - Web Server",
"ip": "10.10.4.50",
"port": 80,
"error": "EXPECTED_CONNECTIVITY_FAILED: Failed to connect to DMZ - Web Server (10.10.4.50:80)"
},
{
"vlan": "DMZ",
"target": "DMZ - Web Server HTTPS",
"ip": "10.10.4.50",
"port": 443,
"error": "EXPECTED_CONNECTIVITY_FAILED: Failed to connect to DMZ - Web Server HTTPS (10.10.4.50:443)"
}
]
}
NDJSON output (one JSON object per probed target):
{"timestamp": "2026-08-15T07:55:15.797713+00:00", "target_name": "Internal - Device A SSH", "target_vlan": "Internal", "target_ip": "10.10.1.10", "port": 22, "protocol": "tcp", "reachable": false, "expected_blocked": true, "status": "PASS", "latency_ms": 1001.0, "error": null}
Strict mode (-s) writes failures to stderr and exits with code 1:
🚨 2 unauthorized connection(s) detected!
- EXPECTED_CONNECTIVITY_FAILED: Failed to connect to DMZ - Web Server (10.10.4.50:80)
- EXPECTED_CONNECTIVITY_FAILED: Failed to connect to DMZ - Web Server HTTPS (10.10.4.50:443)
Development 🧑💻
uv sync # install dev dependencies
uv run pytest # run tests
uv run ruff check . && uv run ruff format --check . # lint
uv run mypy src # type check
Test coverage 🛡️
Every code path is covered by a test; pytest fails if coverage drops below
100% (--cov-fail-under=100). Run the suite with coverage:
uv run pytest --cov=src/vlan_probe --cov-report=term-missing
License
MIT (see LICENSE)
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vlan_probe-0.3.0.tar.gz.
File metadata
- Download URL: vlan_probe-0.3.0.tar.gz
- Upload date:
- Size: 67.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
17531c0c75bdd6b056ed5df42ff49aff52bc3c5a9400f5364fa5a02245cba6af
|
|
| MD5 |
5f1792b22836d33669bf3a372f9dbd29
|
|
| BLAKE2b-256 |
19879c9909bf70950eb33a6927d90d49b95de4308df5367065f4dbb326b39ee5
|
Provenance
The following attestation bundles were made for vlan_probe-0.3.0.tar.gz:
Publisher:
ci.yml on hellqvio86/vlan-probe
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vlan_probe-0.3.0.tar.gz -
Subject digest:
17531c0c75bdd6b056ed5df42ff49aff52bc3c5a9400f5364fa5a02245cba6af - Sigstore transparency entry: 2474313188
- Sigstore integration time:
-
Permalink:
hellqvio86/vlan-probe@4a605bd18d1ef4b3858faaeb7a60ce00222e6aba -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/hellqvio86
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@4a605bd18d1ef4b3858faaeb7a60ce00222e6aba -
Trigger Event:
release
-
Statement type:
File details
Details for the file vlan_probe-0.3.0-py3-none-any.whl.
File metadata
- Download URL: vlan_probe-0.3.0-py3-none-any.whl
- Upload date:
- Size: 13.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b51c49db3849054d5f3e5e9427b2f919072efa44291667a03c148f1bec741398
|
|
| MD5 |
068bafc49b72f1aab746581f08f891f0
|
|
| BLAKE2b-256 |
ed3bb55675b2feefd169bd35bb1d2ef096a0cb0b946453a52482988b03eddf47
|
Provenance
The following attestation bundles were made for vlan_probe-0.3.0-py3-none-any.whl:
Publisher:
ci.yml on hellqvio86/vlan-probe
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vlan_probe-0.3.0-py3-none-any.whl -
Subject digest:
b51c49db3849054d5f3e5e9427b2f919072efa44291667a03c148f1bec741398 - Sigstore transparency entry: 2474313217
- Sigstore integration time:
-
Permalink:
hellqvio86/vlan-probe@4a605bd18d1ef4b3858faaeb7a60ce00222e6aba -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/hellqvio86
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@4a605bd18d1ef4b3858faaeb7a60ce00222e6aba -
Trigger Event:
release
-
Statement type: