vlan-probe 🛡️
VLAN Isolation & Network Permission Probe Tool — verify your firewall rules by probing target VLAN subnets, IPs, and ports from the host, and get alerted to any unauthorized inter-VLAN access.
✨ Features
- Multi-protocol probing —
tcp,udp(DNS-aware on port 53, NTP-aware on port 123),icmp(ping), andsctp. - Policy-first configuration — declare what should be blocked vs. reachable; every deviation is reported as a violation 🔴.
- Concurrent execution — multi-threaded worker pool (
-j/--concurrency) to probe dozens of targets simultaneously. - Flexible output — human-friendly
table, structuredjson, streamingndjson, orprometheusexposition format. - Alerting & observability built-in —
--strictexit codes for automation, scheduled MQTT reporting, and Prometheusnode_exportertextfile export (--prometheus) viasystemdtimers 📡. - Boring & reliable — typed Python 3.10+, zero long-running daemons, 100% test coverage.
📑 Table of contents
Requirements
- Python 3.10 or newer
- Linux (uses the
ipandpingutilities)
Installation
Install with pipx (recommended)
vlan-probe is a command-line tool — pipx runs it in
an isolated environment while keeping the vlan-probe command on your PATH.
pipx install vlan-probe
Upgrade to a newer release:
pipx upgrade vlan-probe
Don't have pipx yet? Install it first:
brew install pipx && pipx ensurepath # macOS/Homebrew
apt install pipx && pipx ensurepath # Debian/Ubuntu
pacman -S python-pipx && pipx ensurepath # Arch
Install from source
pipx install . # via pipx
uv tool install . # via uv
pip install . # into the active environment
Quick start
# 1. copy the example config and edit it to match your network
cp vlan_probe.toml.example /etc/vlan_probe.toml
# 2. probe everything and show a human-friendly table
vlan-probe -f table
# 3. or go headless: JSON to stdout, exit 1 on any violation
vlan-probe -f json -s
Configuration
Configuration lives in a single TOML file (or JSON file) —
readable, diff-able, and easy to keep in git. By default it is read from
/etc/vlan_probe.toml (or /etc/vlan_probe.json); use -c <path> for another
location.
[[targets]]
name = "Internal - Device A SSH"
vlan = "Internal"
ip = "10.10.1.10"
port = 22
protocol = "tcp"
expected_blocked = true
[[targets]]
name = "External - Public DNS"
vlan = "External"
ip = "8.8.8.8"
port = 53
protocol = "udp"
expected_blocked = false
[[targets]]
name = "Internal - Gateway Ping"
vlan = "Internal"
ip = "10.10.1.1"
port = 0
protocol = "icmp"
expected_blocked = true
[[targets]]
name = "Internal - Diameter Signalling"
vlan = "Internal"
ip = "10.10.1.20"
port = 3868
protocol = "sctp"
expected_blocked = false
Each target declares the firewall policy it expects:
expected_blocked |
Policy | Reported when… |
|---|---|---|
true |
deny | target is reachable → violation 🔴 |
false |
allow | target is unreachable → failure 🔴 |
Supported protocols: tcp · udp (probed with DNS query on port 53, NTP
request on port 123, or null byte; ICMP port-unreachable is detected) · icmp
(via ping) · sctp.
Note on UDP probing: UDP is connectionless. For DNS (port 53) and NTP (port 123),
vlan-probesends valid protocol request packets and awaits responses. For other UDP ports, it sends a probe byte and waits for a response or an ICMP Port Unreachable message. Silent UDP endpoints that drop unsolicited packets will time out.
Environment variables
Every setting that isn't a probe target can come from an environment
variable instead of a flag or the config file. CLI flags win over env vars;
env vars win over the [mqtt] config section. This keeps secrets out of the
config file and makes containerized/systemd deployments trivial.
CLI defaults:
| Env var | Controls | Default |
|---|---|---|
VLAN_PROBE_CONFIG |
config file path | /etc/vlan_probe.toml |
VLAN_PROBE_TIMEOUT |
probe timeout (seconds) | 2.0 |
VLAN_PROBE_FORMAT |
output format | ndjson |
VLAN_PROBE_STRICT |
strict mode (exit 1) | false |
VLAN_PROBE_CONCURRENCY |
worker thread pool size | 10 |
VLAN_PROBE_PROMETHEUS_TEXTFILE |
Prometheus .prom textfile path |
/var/lib/prometheus/node-exporter/vlan_probe.prom |
MQTT ([mqtt] section, overridable per-key):
| Env var | [mqtt] key |
|---|---|
VLAN_PROBE_MQTT_HOST |
host |
VLAN_PROBE_MQTT_PORT |
port |
VLAN_PROBE_MQTT_USERNAME |
username |
VLAN_PROBE_MQTT_PASSWORD |
password |
VLAN_PROBE_MQTT_TLS |
tls |
VLAN_PROBE_MQTT_CA_CERTS |
ca_certs |
VLAN_PROBE_MQTT_INSECURE |
insecure |
VLAN_PROBE_MQTT_TOPIC_PREFIX |
topic_prefix |
VLAN_PROBE_MQTT_RETAIN |
retain |
VLAN_PROBE_MQTT_QOS |
qos |
VLAN_PROBE_MQTT_CONNECT_TIMEOUT |
connect_timeout |
Booleans accept 1/true/yes/on. A VLAN_PROBE_MQTT_HOST alone is
enough to enable MQTT — no [mqtt] section needed:
export VLAN_PROBE_MQTT_HOST=mqtt.example.com
export VLAN_PROBE_MQTT_USERNAME=vlan-probe
export VLAN_PROBE_MQTT_PASSWORD='s3cret'
export VLAN_PROBE_MQTT_TLS=true
vlan-probe --mqtt
Usage
-v, --version Show program's version number and exit
-c, --config PATH Path to config TOML/JSON file (default: /etc/vlan_probe.toml)
-f, --format FORMAT Output format: ndjson, json, table, or prometheus (default: ndjson)
-t, --timeout SECONDS Socket connection timeout (default: 2.0)
-j, --concurrency INT Number of concurrent worker threads (default: 10)
-s, --strict Exit with code 1 if any violations occur
--mqtt Publish results to MQTT (requires [mqtt] section or VLAN_PROBE_MQTT_HOST)
--prometheus Write Prometheus metrics to node_exporter textfile collector
--prometheus-textfile PATH Custom path for Prometheus .prom textfile (implies --prometheus)
--color [auto|always|never] Colorize output (default: auto)
# table output
vlan-probe -c ./vlan_probe.toml -f table
# json output
vlan-probe -f json
# strict mode with a custom timeout and concurrency
vlan-probe -s -t 5.0 -j 20
# no color
vlan-probe -f table --color never
Exit codes
| Code | Meaning |
|---|---|
0 |
all checks passed |
1 |
violations detected (with --strict) |
2 |
configuration error or MQTT delivery failure |
Output formats
Table (IPs are fictional):
VLAN TARGET ENDPOINT STATUS DETAILS
------------------------------------------------------------------------------------------
Internal Internal - Device A SSH 10.10.1.10:22 (tcp) PASS OK
Internal Internal - DNS Server 10.10.1.1:53 (udp) PASS OK
IoT IoT - Device B 10.10.2.50: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)
External External - Public DNS 8.8.8.8:53 (udp) PASS OK
JSON — a run summary with totals and violations:
{
"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)"
}
]
}
NDJSON — one object per probed target, ideal for streaming/ingestion:
{"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)
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. A
systemd timer wakes a short-lived process that probes, publishes, and exits —
no daemon, no state to babysit.
Add an [mqtt] section to the config:
[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
…and run with --mqtt:
vlan-probe --mqtt
Topics published (all retained at QoS 1):
vlan-probe/<hostname>/summary— run summary (totals + violations)vlan-probe/<hostname>/targets/<vlan>/<target>— one message per target
A failed MQTT delivery is fatal (exit code 2); probe violations in strict mode still exit 1.
--mqttwithout an[mqtt]section orVLAN_PROBE_MQTT_HOSTis 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 tips:
journalctl -u vlan-probe— probe + delivery logs per runsystemctl list-timers vlan-probe— next scheduled run- Broker credentials: the
[mqtt]config section (chmod 600) or theVLAN_PROBE_MQTT_*environment variables in a systemdEnvironmentFile
Development 🧑💻
The repository ships a Makefile wrapping uv — that's the
supported way to work in this project.
| Command | What it does |
|---|---|
make venv |
create the virtualenv, install dev deps, and set hooks |
make hooks |
configure git pre-commit hook to enforce lint & tests |
make lint |
ruff lint and format check |
make format |
auto-format the code with ruff |
make test |
mypy type-check + pytest with 100% coverage gate |
make run |
run the CLI, e.g. make run ARGS="-f table" |
make build |
build sdist + wheel |
make publish |
build and publish to PyPI |
make install |
install the package into a uv tool environment |
make clean |
remove the venv and build/cache artifacts |
make venv # one-time setup (also configures git hooks)
make lint # before pushing
make test # before pushing
Git hooks 🪝
Git hooks are provided in .githooks/ to ensure code quality and conventional commit standards before committing:
pre-commitruns lockfile checks (uv lock --check), linting (ruff check), formatting (ruff format --check), type checking (mypy), and test suite (pytestwith 100% coverage).commit-msgvalidates that commit messages adhere to Conventional Commits format (feat:,fix:,chore:, etc.).
To install / enable the hooks:
make hooks
# or directly with git:
git config core.hooksPath .githooks
Test coverage 🛡️
Every code path is covered by a meaningful test — real loopback sockets and
real config files, mocking only system boundaries. pytest fails if coverage
drops below 100% (--cov-fail-under=100). Run the suite with a coverage
report:
make test # mypy + pytest with coverage
make run ARGS="-f table" # try it out
License
Metadata
Release files for vlan-probe 0.8.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| vlan_probe-0.8.0.tar.gz | 81.4 kB | With provenance | Details for vlan_probe-0.8.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| vlan_probe-0.8.0-py3-none-any.whl | Python 3 | none | any | Details for vlan_probe-0.8.0-py3-none-any.whl |
Total release size: 102.6 kB
Release files / vlan_probe-0.8.0.tar.gz
| Download URL | vlan_probe-0.8.0.tar.gz |
|---|---|
| Size | 81.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
536a698e4be58fec698ba9fb5a06f4e1e9790fa158f6e5e0f2d3b7f4be6fb694
|
|
BLAKE2b-256 checksum How to use checksums |
7129d608fd35e03e9cf29087534b019d3635017e2dcc0de80cfcc7f84b4106a4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logRelease files / vlan_probe-0.8.0-py3-none-any.whl
| Download URL | vlan_probe-0.8.0-py3-none-any.whl |
|---|---|
| Size | 21.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b639d98dfa2c35672942f3dc044c71098a7edb9ac83896583161f54db87953e9
|
|
BLAKE2b-256 checksum How to use checksums |
728bedf1c8d6ca61e1cafc63c54f5e7c24453aabff72ac376b9359b8beb8cde2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logSecurity for vlan-probe 0.8.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for vlan_probe-0.8.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.