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 reporting any unauthorized inter-VLAN access.

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

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 📡

  1. Install or copy to a host.
  2. Create a TOML config at /etc/vlan_probe.toml or pass -c to point to a different file.
  3. Run vlan-probe -f table or python -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 🔴.

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 systemd EnvironmentFile.

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

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

vlan_probe-0.2.0.tar.gz (60.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vlan_probe-0.2.0-py3-none-any.whl (12.7 kB view details)

Uploaded Python 3

File details

Details for the file vlan_probe-0.2.0.tar.gz.

File metadata

  • Download URL: vlan_probe-0.2.0.tar.gz
  • Upload date:
  • Size: 60.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for vlan_probe-0.2.0.tar.gz
Algorithm Hash digest
SHA256 c3021d2e8a695fc6b933dca79c8c856746d50a66cda2b419e4dc26031cb3bd0b
MD5 34c4fa9b5066cf3b438026f09d5ff814
BLAKE2b-256 5be8972569ef5c365e342268141ffe1438e1fbf60016d24491035c51fd0c3d02

See more details on using hashes here.

Provenance

The following attestation bundles were made for vlan_probe-0.2.0.tar.gz:

Publisher: ci.yml on hellqvio86/vlan-probe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vlan_probe-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: vlan_probe-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 12.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for vlan_probe-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fe058c009e1626c7704f7e2f73c65bc3cbd225453ca7897621258611b56608d2
MD5 059a90f3181ed814f14023c6829c483d
BLAKE2b-256 985891a2593a23799d72e9eb47ad8ad7261204acc0fd07168603200783760e22

See more details on using hashes here.

Provenance

The following attestation bundles were made for vlan_probe-0.2.0-py3-none-any.whl:

Publisher: ci.yml on hellqvio86/vlan-probe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.6.0

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

This release

0.2.0 This release

2 files

0.1.0

2 files

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