Skip to main content

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.

PyPI - Version PyPI - Python Versions PyPI - License CI/CD Coverage


✨ Features

  • Multi-protocol probing — tcp, udp (DNS-aware on port 53, NTP-aware on port 123), icmp (ping), and sctp.
  • 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, structured json, streaming ndjson, or prometheus exposition format.
  • Alerting & observability built-in — --strict exit codes for automation, scheduled MQTT reporting, and Prometheus node_exporter textfile export (--prometheus) via systemd timers 📡.
  • 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 ip and ping utilities)

Installation

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-probe sends 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. --mqtt without an [mqtt] section or VLAN_PROBE_MQTT_HOST 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 tips:

  • journalctl -u vlan-probe — probe + delivery logs per run
  • systemctl list-timers vlan-probe — next scheduled run
  • Broker credentials: the [mqtt] config section (chmod 600) or the VLAN_PROBE_MQTT_* environment variables in a systemd EnvironmentFile

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-commit runs lockfile checks (uv lock --check), linting (ruff check), formatting (ruff format --check), type checking (mypy), and test suite (pytest with 100% coverage).
  • commit-msg validates 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

MIT

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)

Source distribution for vlan-probe 0.8.0
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)

Table of built distributions (wheels) for vlan-probe 0.8.0
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page