doxctl is a diagnostic CLI tool that endusers can use to triage connectivity problems stemming from their VPN & DNS setups on their laptops. It can help with the following areas:
| Command | Description |
|---|---|
| dns | Verify DNS resolver configuration, connectivity, and query responses |
| vpn | Check VPN connection status, network interfaces, and route configuration |
| svrs | Test connectivity to well-known servers through VPN |
| svcs | Multi-datacenter service-level health checks for critical endpoints |
| net | Network performance testing and SLO validation (latency, jitter, packet loss) |
- Human-readable table output - Easy to read diagnostic results
- Machine-readable formats - JSON and YAML output for automation, CI/CD pipelines, and monitoring systems
- VPN diagnostics - Check VPN connectivity, routes, and interface status
- DNS diagnostics - Verify DNS resolver configuration and connectivity
- Server reachability - Test connectivity to well-known servers
For details on using JSON/YAML output for automation, see docs/OUTPUT_FORMATS.md.
For building from source or development:
- Go 1.25.0+ is required (as specified in go.mod)
Tree - CLICK ME
$ brew install slmingol/tap/doxctl
$ brew install slmingol/tap/doxctl
==> Installing doxctl from slmingol/tap
==> Downloading https://ghcr.io/v2/homebrew/core/go/manifests/1.25.0
######################################################################## 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/go/blobs/sha256:...
######################################################################## 100.0%
==> Downloading https://github.com/slmingol/doxctl/releases/download/1.0.1/doxctl_1.0.1_Darwin_x86_64.tar.gz
######################################################################## 100.0%
==> Installing dependencies for slmingol/tap/doxctl: go
==> Installing slmingol/tap/doxctl dependency: go
==> Pouring go--1.25.0.arm64_sequoia.bottle.tar.gz
πΊ /opt/homebrew/Cellar/go/1.25.0: 9,956 files, 503.6MB
==> Installing slmingol/tap/doxctl
πΊ /opt/homebrew/Cellar/doxctl/1.0.1: 5 files, 9.1MB, built in 4 seconds
$ brew search doxctl
==> Formulae
slmingol/tap/doxctl β
$ brew update
Updated 2 taps (homebrew/cask and slmingol/tap).
==> Updated Formulae
slmingol/tap/doxctl β
You have 96 outdated formulae and 10 outdated casks installed.
You can upgrade them with brew upgrade
or list them with brew outdated.
$ brew upgrade doxctl
==> Upgrading 1 outdated package:
slmingol/tap/doxctl 1.0.0 -> 1.0.1
==> Upgrading slmingol/tap/doxctl 1.0.0 -> 1.0.1
==> Downloading https://github.com/slmingol/doxctl/releases/download/1.0.1/doxctl_1.0.1_Darwin_x86_64.tar.gz
######################################################################## 100.0%
πΊ /opt/homebrew/Cellar/doxctl/1.0.1: 5 files, 9.1MB, built in 4 seconds
Removing: /opt/homebrew/Cellar/doxctl/1.0.0... (5 files, 9.1MB)
Removing: /Users/smingolelli/Library/Caches/Homebrew/doxctl--1.0.0.tar.gz... (3.4MB)
$ brew uninstall doxctl
Uninstalling /opt/homebrew/Cellar/doxctl/1.0.1... (5 files, 9.1MB)
Docker images are available for both amd64 and arm64 architectures, making doxctl compatible with:
- x86_64 systems (traditional servers, Intel-based laptops)
- ARM-based systems (Apple M1/M2, Raspberry Pi, AWS Graviton)
Images are published to both GitHub Container Registry (GHCR) and Docker Hub:
ghcr.io/slmingol/doxctl:latest(multi-arch)slmingol/doxctl:latest(multi-arch)- Tagged versions:
ghcr.io/slmingol/doxctl:1.0.1orslmingol/doxctl:1.0.1
# Pull and run the latest version
docker run --rm ghcr.io/slmingol/doxctl:latest --help
# Run DNS diagnostics
docker run --rm ghcr.io/slmingol/doxctl:latest dns --help
# Run VPN diagnostics
docker run --rm ghcr.io/slmingol/doxctl:latest vpn --help
# Run server connectivity checks
docker run --rm ghcr.io/slmingol/doxctl:latest svrs --help# Create a config file
cat > doxctl.yaml <<EOF
# Your configuration here
EOF
# Mount and use the config file
docker run --rm \
-v $(pwd)/doxctl.yaml:/root/.doxctl.yaml \
ghcr.io/slmingol/doxctl:latest dns -aThe Docker image is based on Alpine Linux, which includes a shell for debugging:
# Start an interactive shell
docker run --rm -it --entrypoint /bin/sh ghcr.io/slmingol/doxctl:latest
# Inside the container, you can run doxctl commands
/usr/bin/doxctl --helpFor network diagnostics, you may need host network mode:
# Run with host network access
docker run --rm --network host ghcr.io/slmingol/doxctl:latest dns -pWhile the multi-arch manifest automatically selects the correct image for your platform, you can explicitly pull architecture-specific images:
# AMD64 (x86_64)
docker pull ghcr.io/slmingol/doxctl:latest-amd64
# ARM64 (Apple Silicon, Graviton, etc.)
docker pull ghcr.io/slmingol/doxctl:latest-arm64When running doxctl in containers on macOS, VPN DNS settings require special handling because containers run in a Linux VM that cannot access macOS network APIs. We provide a wrapper script that automatically extracts your macOS VPN DNS configuration and passes it to the container.
For detailed instructions, see MACOS.md.
# Quick start with the wrapper
./doxctl-container dns -adoxctl uses a YAML configuration file located at $HOME/.doxctl.yaml by default. You can specify a different location using the -c or --config flag.
The config command helps you manage your configuration file:
# Create an example configuration file
doxctl config init
# Display current configuration
doxctl config show
# Validate configuration file
doxctl config validate# Minimum number of VPN routes required (for vpnRoutesChk)
minVpnRoutes: 5# Domain name checks (regex patterns)
domNameChk: "bandwidth.local"
domSearchChk: "[0-1].*bandwidth"
domAddrChk: "[0-1].*10.5"
# DNS dig probe servers
domainName: "bandwidthclec.local"
digProbeServerA: "idm-01a"
digProbeServerB: "idm-01b"# List of sites for multi-datacenter health checks
sites:
- lab1
- rdu1
- atl1
- dfw1
- lax2
- jfk1
- lhr1
- fra1Define services and their server endpoints for connectivity and health checks:
wellKnownSvcs:
- svc: openshift
svrs:
- ocp-master-01{a,b,c}.{lab1,rdu1,dfw1}.bandwidthclec.local
- svc: idm
svrs:
- idm-01{a,b}.{lab1,rdu1,dfw1,lax2,jfk1}.bandwidthclec.local# Ping timeout in milliseconds
pingTimeout: 250
# DNS lookup timeout in milliseconds
dnsLookupTimeout: 100
# Failure threshold for connectivity checks
failThreshold: 5See .doxctl.yaml.example in the repository for a complete example configuration file.
$ doxctl -h
'doxctl' is a collection of tools which can be used to diagnose & triage problems
stemming from the following areas with a laptop or desktop system:
- DNS, specifically with the configuration of resolvers
- VPN configuration and network connectivity over it
- General access to well-known servers
- ... or general network connectivity issues
Usage:
doxctl [command]
Available Commands:
dns Run diagnostics related to DNS servers (aka. resolvers) configurations
help Help about any command
net Network performance testing and SLO validation
svcs Service-level health checks for multi-datacenter endpoints
svrs Run diagnostics verifying connectivity to well known servers thru a VPN connection
vpn Run diagnostics related to VPN connections, network interfaces & configurations
Flags:
-c, --config string config file (default is $HOME/.doxctl.yaml)
-h, --help help for doxctl
-o, --output string Output format: table, json, yaml (default "table")
-v, --verbose Enable verbose output of commands
Use "doxctl [command] --help" for more information about a command.
$ doxctl net -h
Test network connectivity and performance against defined SLO thresholds.
This command measures:
- Average, minimum, and maximum latency
- Jitter (latency variance)
- Packet loss percentage
- SLO compliance (latency threshold)
Examples:
# Test network performance to configured targets
doxctl net --perf
# Set custom SLO threshold (default: 50ms)
doxctl net --perf --slo 100
# Specify number of packets to send (default: 10)
doxctl net --perf --packets 20
Usage:
doxctl net [flags]
Flags:
-h, --help help for net
-n, --packets int Number of packets to send (default 10)
-p, --perf Run network performance tests
-s, --slo float SLO threshold in milliseconds (default 50)
Global Flags:
-c, --config string config file (default is $HOME/.doxctl.yaml)
-o, --output string Output format: table, json, yaml (default "table")
-v, --verbose Enable verbose output of commands
Tree - CLICK ME
$ doxctl net --perf
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Network Performance & SLO Validation β
βββββββββββββββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ¬ββββββββββββββ¬βββββββββ¬ββββββββββ¬βββββββββ€
β TARGET β AVG (MS) β MIN (MS) β MAX (MS) β JITTER (MS) β LOSS % β SLO β STATUS β
βββββββββββββββββββββββββΌβββββββββββΌβββββββββββΌβββββββββββΌββββββββββββββΌβββββββββΌββββββββββΌβββββββββ€
β lab1.example.com β 12.50 β 10.20 β 15.80 β 1.20 β 0.0 β 50 ms β β PASS β
β rdu1.example.com β 18.30 β 15.10 β 22.50 β 2.10 β 0.0 β 50 ms β β PASS β
β dfw1.example.com β 45.20 β 42.00 β 48.90 β 1.80 β 0.0 β 50 ms β β PASS β
βββββββββββββββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ΄ββββββββββββββ΄βββββββββ΄ββββββββββ΄βββββββββ
Summary: 3/3 targets meeting SLO (100.0% success rate)
$ doxctl net --perf
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Network Performance & SLO Validation β
βββββββββββββββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ¬ββββββββββββββ¬βββββββββ¬ββββββββββ¬βββββββββ€
β TARGET β AVG (MS) β MIN (MS) β MAX (MS) β JITTER (MS) β LOSS % β SLO β STATUS β
βββββββββββββββββββββββββΌβββββββββββΌβββββββββββΌβββββββββββΌββββββββββββββΌβββββββββΌββββββββββΌβββββββββ€
β lab1.example.com β 12.50 β 10.20 β 15.80 β 1.20 β 0.0 β 50 ms β β PASS β
β rdu1.example.com β 85.40 β 78.10 β 95.20 β 5.30 β 2.5 β 50 ms β β FAIL β
β dfw1.example.com β 45.20 β 42.00 β 48.90 β 1.80 β 8.0 β 50 ms β β FAIL β
βββββββββββββββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ΄ββββββββββββββ΄βββββββββ΄ββββββββββ΄βββββββββ
Summary: 1/3 targets meeting SLO (33.3% success rate)
$ doxctl net --perf -o json
{
"timestamp": "2026-03-02T10:30:00Z",
"results": [
{
"timestamp": "2026-03-02T10:30:00Z",
"target": "lab1.example.com",
"avgLatencyMs": 12.5,
"minLatencyMs": 10.2,
"maxLatencyMs": 15.8,
"jitterMs": 1.2,
"packetLoss": 0.0,
"meetsSLO": true,
"sloThreshold": 50.0
}
],
"summary": {
"totalTargets": 1,
"passing": 1,
"failing": 0
}
}
$ doxctl svcs -h
Check the health and availability of services across multiple datacenters.
This command performs HTTP/HTTPS health checks on service endpoints and measures:
- Response time
- HTTP status codes
- Service availability
- Multi-datacenter service health
Examples:
# Check health of all configured services
doxctl svcs --health
# Set custom timeout (default: 5 seconds)
doxctl svcs --health --timeout 10
# Skip TLS verification for self-signed certificates
doxctl svcs --health --insecure
Usage:
doxctl svcs [flags]
Flags:
-H, --health Run service health checks
-h, --help help for svcs
-k, --insecure Skip TLS certificate verification
-t, --timeout int HTTP request timeout in seconds (default 5)
Global Flags:
-c, --config string config file (default is $HOME/.doxctl.yaml)
-o, --output string Output format: table, json, yaml (default "table")
-v, --verbose Enable verbose output of commands
Tree - CLICK ME
$ doxctl svcs --health
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Service Health Checks β
βββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββ¬βββββββββββββββ¬ββββββββββββ¬ββββββββ€
β SERVICE β ENDPOINT β STATUS CODE β RESPONSE (MS)β STATUS β ERROR β
βββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββΌβββββββββββββββΌββββββββββββΌββββββββ€
β openshift β https://ocp-master-01a.lab1.example.com:6443 β 200 β 45.23 β β Healthy β β
β openshift β https://ocp-master-01b.rdu1.example.com:6443 β 200 β 32.18 β β Healthy β β
β elastic β https://es-master-01a.lab1.example.com:6443 β 200 β 28.45 β β Healthy β β
β elastic β https://es-master-01b.rdu1.example.com:6443 β 200 β 22.91 β β Healthy β β
βββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββ΄ββββββββββββββ΄βββββββββββββββ΄ββββββββββββ΄ββββββββ
Summary: 4/4 services healthy (100.0% availability)
$ doxctl svcs --health
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Service Health Checks β
βββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββ¬βββββββββββββββ¬βββββββββββ¬βββββββββββββββββββββββ€
β SERVICE β ENDPOINT β STATUS CODE β RESPONSE (MS)β STATUS β ERROR β
βββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββΌβββββββββββββββΌβββββββββββΌβββββββββββββββββββββββ€
β openshift β https://ocp-master-01a.lab1.example.com:6443 β 200 β 45.23 β β Healthyβ β
β openshift β https://ocp-master-01b.rdu1.example.com:6443 β - β - β β Failed β connection refused β
β elastic β https://es-master-01a.lab1.example.com:6443 β 503 β 125.40 β β Failed β β
β elastic β https://es-master-01b.rdu1.example.com:6443 β 200 β 22.91 β β Healthyβ β
βββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββ΄ββββββββββββββ΄βββββββββββββββ΄βββββββββββ΄βββββββββββββββββββββββ
Summary: 2/4 services healthy (50.0% availability)
$ doxctl svcs --health -o yaml
timestamp: 2026-03-02T10:35:00Z
results:
- timestamp: 2026-03-02T10:35:00Z
service: openshift
endpoint: https://ocp-master-01a.lab1.example.com:6443/healthz
responseTimeMs: 45.23
statusCode: 200
healthy: true
error: ""
summary:
total: 1
healthy: 1
failed: 0
$ doxctl dns -h
doxctl's 'dns' subcommand can help triage DNS resovler configuration issues,
general access to DNS resolvers and name resolution against DNS resolvers.
Usage:
doxctl dns [flags]
Flags:
-a, --allChk Run all the checks in this subcommand module
-d, --digChk Check if VPN defined resolvers respond with well-known servers in DCs
-h, --help help for dns
-p, --pingChk Check if VPN defined resolvers are pingable & reachable
-r, --resolverChk Check if VPN designated DNS resolvers are configured
Global Flags:
-c, --config string config file (default is $HOME/.doxctl.yaml)
-v, --verbose Enable verbose output of commands
Tree - CLICK ME
$ doxctl dns -r
**NOTE:** Using config file: /Users/smingolelli/.doxctl.yaml
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VPN defined DNS Resolver Checks β
ββββββββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ€
β PROPERTY DESCRIPTION β VALUE β
ββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββ€
β DomainName defined? β unset β
β SearchDomains defined? β unset β
β ServerAddresses defined? β unset β
ββββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββ
INFO: Any values of unset indicate that the VPN client is not defining DNS resolver(s) properly!
$ doxctl dns -r
**NOTE:** Using config file: /Users/smingolelli/.doxctl.yaml
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VPN defined DNS Resolver Checks β
ββββββββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ€
β PROPERTY DESCRIPTION β VALUE β
ββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββ€
β DomainName defined? β set β
β SearchDomains defined? β set β
β ServerAddresses defined? β set β
ββββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββ
INFO: Any values of unset indicate that the VPN client is not defining DNS resolver(s) properly!
Tree - CLICK ME
$ doxctl dns -p
**NOTE:** Using config file: /Users/smingolelli/.doxctl.yaml
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VPN defined DNS Resolver Connectivity Checks β
ββββββββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββββ¬ββββββββββββββββ¬ββββββββ€
β PROPERTY DESCRIPTION β IP β NET I/F β VALUE β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββΌββββββββββββββββΌββββββββ€
ββββββββββββββββββββββββββββββββββββββββββββ΄ββββββββββββββββ΄ββββββββββββββββ΄ββββββββ
WARNING:
Your VPN client does not appear to be defining any DNS resolver(s) properly,
you're either not connected via VPN or it's misconfigured!
$ doxctl dns -p
**NOTE:** Using config file: /Users/smingolelli/.doxctl.yaml
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VPN defined DNS Resolver Connectivity Checks β
ββββββββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββββ¬ββββββββββββββββ¬ββββββββ€
β PROPERTY DESCRIPTION β IP β NET I/F β VALUE β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββΌββββββββββββββββΌββββββββ€
β Resovler is pingable? β 10.5.0.18 β utun2 β true β
β Reachable via TCP? β 10.5.0.18 β utun2 β true β
β Reachable via UDP? β 10.5.0.18 β utun2 β true β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββΌββββββββββββββββΌββββββββ€
β Resovler is pingable? β 10.5.0.19 β utun2 β true β
β Reachable via TCP? β 10.5.0.19 β utun2 β true β
β Reachable via UDP? β 10.5.0.19 β utun2 β true β
ββββββββββββββββββββββββββββββββββββββββββββ΄ββββββββββββββββ΄ββββββββββββββββ΄ββββββββ
Tree - CLICK ME
$ doxctl dns -d
**NOTE:** Using config file: /Users/smingolelli/.doxctl.yaml
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Dig Check against VPN defined DNS Resolvers β
ββββββββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββββββ¬ββββββββββββββββββ€
β HOSTNAME TO 'DIG' β RESOLVER IP β IS RESOLVABLE? β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββ€
β idm-01a.lab1.somedom.local β β false β
β idm-01b.lab1.somedom.local β β false β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββ€
β idm-01a.rdu1.somedom.local β β false β
β idm-01b.rdu1.somedom.local β β false β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββ€
β idm-01a.atl1.somedom.local β β false β
β idm-01b.atl1.somedom.local β β false β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββ€
β idm-01a.dfw1.somedom.local β β false β
β idm-01b.dfw1.somedom.local β β false β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββ€
β idm-01a.lax2.somedom.local β β false β
β idm-01b.lax2.somedom.local β β false β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββ€
β idm-01a.jfk1.somedom.local β β false β
β idm-01b.jfk1.somedom.local β β false β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββ€
β SUCCESSESFUL QUERIES β RESOLVER #1: 0 β β
β β RESOLVER #2: 0 β β
ββββββββββββββββββββββββββββββββββββββββββββ΄ββββββββββββββββββ΄ββββββββββββββββββ
WARNING:
Your VPN client does not appear to be defining any DNS resolver(s) properly,
you're either not connected via VPN or it's misconfigured!
$ doxctl dns -d
**NOTE:** Using config file: /Users/smingolelli/.doxctl.yaml
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Dig Check against VPN defined DNS Resolvers β
ββββββββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββββββ¬ββββββββββββββββββ€
β HOSTNAME TO 'DIG' β RESOLVER IP β IS RESOLVABLE? β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββ€
β idm-01a.lab1.somedom.local β 10.5.0.18 β true β
β idm-01b.lab1.somedom.local β 10.5.0.18 β true β
β idm-01a.lab1.somedom.local β 10.5.0.19 β true β
β idm-01b.lab1.somedom.local β 10.5.0.19 β true β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββ€
β idm-01a.rdu1.somedom.local β 10.5.0.18 β true β
β idm-01b.rdu1.somedom.local β 10.5.0.18 β true β
β idm-01a.rdu1.somedom.local β 10.5.0.19 β true β
β idm-01b.rdu1.somedom.local β 10.5.0.19 β true β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββ€
β idm-01a.atl1.somedom.local β 10.5.0.18 β true β
β idm-01b.atl1.somedom.local β 10.5.0.18 β true β
β idm-01a.atl1.somedom.local β 10.5.0.19 β true β
β idm-01b.atl1.somedom.local β 10.5.0.19 β true β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββ€
β idm-01a.dfw1.somedom.local β 10.5.0.18 β true β
β idm-01b.dfw1.somedom.local β 10.5.0.18 β true β
β idm-01a.dfw1.somedom.local β 10.5.0.19 β true β
β idm-01b.dfw1.somedom.local β 10.5.0.19 β true β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββ€
β idm-01a.lax2.somedom.local β 10.5.0.18 β true β
β idm-01b.lax2.somedom.local β 10.5.0.18 β true β
β idm-01a.lax2.somedom.local β 10.5.0.19 β true β
β idm-01b.lax2.somedom.local β 10.5.0.19 β true β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββ€
β idm-01a.jfk1.somedom.local β 10.5.0.18 β true β
β idm-01b.jfk1.somedom.local β 10.5.0.18 β true β
β idm-01a.jfk1.somedom.local β 10.5.0.19 β true β
β idm-01b.jfk1.somedom.local β 10.5.0.19 β true β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββ€
β SUCCESSESFUL QUERIES β RESOLVER #1: 12 β β
β β RESOLVER #2: 12 β β
ββββββββββββββββββββββββββββββββββββββββββββ΄ββββββββββββββββββ΄ββββββββββββββββββ
$ doxctl vpn -h
doxctl's 'vpn' subcommand can help triage VPN related configuration issues,
& routes related to a VPN connection.
Usage:
doxctl vpn [flags]
Flags:
-a, --allChk Run all the checks in this subcommand module
-h, --help help for vpn
-i, --ifReachableChk Check if network interfaces are reachable
-r, --vpnRoutesChk Check if >5 VPN routes are defined
-s, --vpnStatusChk Check if VPN client's status reports as 'Connected'
Global Flags:
-c, --config string config file (default is $HOME/.doxctl.yaml)
-v, --verbose Enable verbose output of commands
Tree - CLICK ME
$ doxctl vpn -i
NOTE: Using config file: /Users/smingolelli/.doxctl.yaml
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Interfaces Reachable Checks β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ¬ββββββββ€
β PROPERTY DESCRIPTION β VALUE β NOTES β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββΌββββββββ€
β How many network interfaces found? β 1 β [en0] β
β At least 1 interface's a utun device? β false β [] β
β All active interfaces are reporting as reachable? β true β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββ΄ββββββββ
WARNING:
Your VPN client does not appear to be defining a TUN interface properly,
your VPN is either not connected or it's misconfigured!
$ doxctl vpn -i
NOTE: Using config file: /Users/smingolelli/.doxctl.yaml
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Interfaces Reachable Checks β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ¬ββββββββββββββ€
β PROPERTY DESCRIPTION β VALUE β NOTES β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββΌββββββββββββββ€
β How many network interfaces found? β 2 β [en0 utun2] β
β At least 1 interface's a utun device? β true β [utun2] β
β All active interfaces are reporting as reachable? β true β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββ΄ββββββββββββββ
Tree - CLICK ME
$ doxctl vpn -r
NOTE: Using config file: /Users/smingolelli/.doxctl.yaml
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VPN Interface Route Checks β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ¬ββββββββ€
β PROPERTY DESCRIPTION β VALUE β NOTES β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββΌββββββββ€
β At least [5] routes using interface [NIL]? β false β 0 β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββ΄ββββββββ
WARNING:
Your VPN client does not appear to be defining a TUN interface properly,
it's either not connected or it's misconfigured!
$ doxctl vpn -r
NOTE: Using config file: /Users/smingolelli/.doxctl.yaml
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VPN Interface Route Checks β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ¬ββββββββ€
β PROPERTY DESCRIPTION β VALUE β NOTES β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββΌββββββββ€
β At least [5] routes using interface [utun2]? β true β 148 β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββ΄ββββββββ
Tree - CLICK ME
$ doxctl vpn -s
NOTE: Using config file: /Users/smingolelli/.doxctl.yaml
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VPN Connection Status Checks β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ¬ββββββββ€
β PROPERTY DESCRIPTION β VALUE β NOTES β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββΌββββββββ€
β VPN Client reports connection status as 'Connected'? β false β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββ΄ββββββββ
WARNING:
Your VPN client's does not appear to be a state of 'connected',
it's either down or misconfigured!"
$ doxctl vpn -s
NOTE: Using config file: /Users/smingolelli/.doxctl.yaml
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VPN Connection Status Checks β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ¬ββββββββ€
β PROPERTY DESCRIPTION β VALUE β NOTES β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββΌββββββββ€
β VPN Client reports connection status as 'Connected'? β true β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββ΄ββββββββ
$ doxctl svrs -h
doxctl's 'svrs' subcommand can help triage & test connectivity to 'well known servers'
thru a VPN connection to servers which have been defined in your '.doxctl.yaml'
configuration file.
Usage:
doxctl svrs [flags]
Flags:
-a, --allChk Run all the checks in this subcommand module
-h, --help help for svrs
-s, --svrsReachableChk Check if well known servers are reachable
Global Flags:
-c, --config string config file (default is $HOME/.doxctl.yaml)
-v, --verbose Enable verbose output of commands
Tree - CLICK ME
$ doxctl svrs -s
NOTE: Using config file: /Users/smingolelli/.doxctl.yaml
INFO: Attempting to ping all well known servers, this may take a few...
--- Working through svc: openshift
--- Working through svc: elastic
--- Working through svc: idm
...one sec, preparing `ping` results...
WARNING: More than 5 hosts appear to be unreachable, aborting remainder....
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Well known Servers Reachable Checks β
ββββββββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββ¬βββββββββββββ¬βββββββββββββββββββ€
β HOST β SERVICE β REACHABLE? β PING PERFORMANCE β
ββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββΌβββββββββββββΌβββββββββββββββββββ€
β ocp-master-01a.lab1.somedom.local β openshift β false β N/A β
β ocp-master-01a.rdu1.somedom.local β openshift β false β N/A β
β ocp-master-01a.dfw1.somedom.local β openshift β false β N/A β
β ocp-master-01a.lax2.somedom.local β openshift β false β N/A β
β ocp-master-01a.jfk1.somedom.local β openshift β false β N/A β
β ocp-master-01b.lab1.somedom.local β openshift β false β N/A β
β ocp-master-01b.rdu1.somedom.local β openshift β false β N/A β
β ocp-master-01b.dfw1.somedom.local β openshift β false β N/A β
β ocp-master-01b.lax2.somedom.local β openshift β false β N/A β
β ocp-master-01b.jfk1.somedom.local β openshift β false β N/A β
β ocp-master-01c.lab1.somedom.local β openshift β false β N/A β
β ocp-master-01c.rdu1.somedom.local β openshift β false β N/A β
β ocp-master-01c.dfw1.somedom.local β openshift β false β N/A β
β ocp-master-01c.lax2.somedom.local β openshift β false β N/A β
β ocp-master-01c.jfk1.somedom.local β openshift β false β N/A β
β ocp-master-01a.lhr1.somedom.us β openshift β false β N/A β
β ocp-master-01a.fra1.somedom.us β openshift β false β N/A β
β ocp-master-01b.lhr1.somedom.us β openshift β false β N/A β
β ocp-master-01b.fra1.somedom.us β openshift β false β N/A β
β ocp-master-01c.lhr1.somedom.us β openshift β false β N/A β
β ocp-master-01c.fra1.somedom.us β openshift β false β N/A β
ββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββΌβββββββββββββΌβββββββββββββββββββ€
β es-master-01a.lab1.somedom.local β elastic β false β N/A β
β es-master-01a.rdu1.somedom.local β elastic β false β N/A β
β es-master-01b.lab1.somedom.local β elastic β false β N/A β
β es-master-01b.rdu1.somedom.local β elastic β false β N/A β
β es-master-01c.lab1.somedom.local β elastic β false β N/A β
β es-master-01c.rdu1.somedom.local β elastic β false β N/A β
ββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββΌβββββββββββββΌβββββββββββββββββββ€
β idm-01a.lab1.somedom.local β idm β false β N/A β
β idm-01a.rdu1.somedom.local β idm β false β N/A β
β idm-01a.dfw1.somedom.local β idm β false β N/A β
β idm-01a.lax2.somedom.local β idm β false β N/A β
β idm-01a.jfk1.somedom.local β idm β false β N/A β
β idm-01b.lab1.somedom.local β idm β false β N/A β
β idm-01b.rdu1.somedom.local β idm β false β N/A β
β idm-01b.dfw1.somedom.local β idm β false β N/A β
β idm-01b.lax2.somedom.local β idm β false β N/A β
β idm-01b.jfk1.somedom.local β idm β false β N/A β
β idm-01a.lhr1.somedom.us β idm β false β N/A β
β idm-01a.fra1.somedom.us β idm β false β N/A β
β idm-01b.lhr1.somedom.us β idm β false β N/A β
β idm-01b.fra1.somedom.us β idm β false β N/A β
ββββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββ΄βββββββββββββ΄βββββββββββββββββββ
WARNING:
Your VPN client does not appear to be functioning properly, it's likely one or more of the following:
- Well known servers are unreachable via ping --- try running 'doxctl vpn -h'
- Servers are unresovlable in DNS --- try running 'doxctl dns -h'
- VPN client is otherwise misconfigured!
doxctl svrs -s
NOTE: Using config file: /Users/smingolelli/.doxctl.yaml
INFO: Attempting to ping all well known servers, this may take a few...
--- Working through svc: openshift
--- Working through svc: elastic
--- Working through svc: idm
...one sec, preparing `ping` results...
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Well known Servers Reachable Checks β
ββββββββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββ¬βββββββββββββ¬ββββββββββββββββββββββββββ€
β HOST β SERVICE β REACHABLE? β PING PERFORMANCE β
ββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββΌβββββββββββββΌββββββββββββββββββββββββββ€
β ocp-master-01a.lab1.somedom.local β openshift β true β rnd-trp avg = 44.525ms β
β ocp-master-01a.rdu1.somedom.local β openshift β true β rnd-trp avg = 24.337ms β
β ocp-master-01a.dfw1.somedom.local β openshift β true β rnd-trp avg = 55.118ms β
β ocp-master-01a.lax2.somedom.local β openshift β true β rnd-trp avg = 97.183ms β
β ocp-master-01a.jfk1.somedom.local β openshift β true β rnd-trp avg = 36.187ms β
β ocp-master-01b.lab1.somedom.local β openshift β true β rnd-trp avg = 44.237ms β
β ocp-master-01b.rdu1.somedom.local β openshift β true β rnd-trp avg = 17.678ms β
β ocp-master-01b.dfw1.somedom.local β openshift β true β rnd-trp avg = 56.559ms β
β ocp-master-01b.lax2.somedom.local β openshift β true β rnd-trp avg = 96.493ms β
β ocp-master-01b.jfk1.somedom.local β openshift β true β rnd-trp avg = 43.273ms β
β ocp-master-01c.lab1.somedom.local β openshift β true β rnd-trp avg = 41.358ms β
β ocp-master-01c.rdu1.somedom.local β openshift β true β rnd-trp avg = 31.427ms β
β ocp-master-01c.dfw1.somedom.local β openshift β true β rnd-trp avg = 55.095ms β
β ocp-master-01c.lax2.somedom.local β openshift β true β rnd-trp avg = 103.423ms β
β ocp-master-01c.jfk1.somedom.local β openshift β true β rnd-trp avg = 37.22ms β
β ocp-master-01a.lhr1.somedom.us β openshift β true β rnd-trp avg = 133.023ms β
β ocp-master-01a.fra1.somedom.us β openshift β true β rnd-trp avg = 136.647ms β
β ocp-master-01b.lhr1.somedom.us β openshift β true β rnd-trp avg = 127.451ms β
β ocp-master-01b.fra1.somedom.us β openshift β true β rnd-trp avg = 139.85ms β
β ocp-master-01c.lhr1.somedom.us β openshift β true β rnd-trp avg = 132.362ms β
β ocp-master-01c.fra1.somedom.us β openshift β true β rnd-trp avg = 137.558ms β
ββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββΌβββββββββββββΌββββββββββββββββββββββββββ€
β es-master-01a.lab1.somedom.local β elastic β true β rnd-trp avg = 44.029ms β
β es-master-01a.rdu1.somedom.local β elastic β true β rnd-trp avg = 32.187ms β
β es-master-01b.lab1.somedom.local β elastic β true β rnd-trp avg = 48.833ms β
β es-master-01b.rdu1.somedom.local β elastic β true β rnd-trp avg = 22.477ms β
β es-master-01c.lab1.somedom.local β elastic β true β rnd-trp avg = 55.587ms β
β es-master-01c.rdu1.somedom.local β elastic β true β rnd-trp avg = 25.79ms β
ββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββΌβββββββββββββΌββββββββββββββββββββββββββ€
β idm-01a.lab1.somedom.local β idm β true β rnd-trp avg = 47.484ms β
β idm-01a.rdu1.somedom.local β idm β true β rnd-trp avg = 22.766ms β
β idm-01a.dfw1.somedom.local β idm β true β rnd-trp avg = 54.07ms β
β idm-01a.lax2.somedom.local β idm β true β rnd-trp avg = 94.755ms β
β idm-01a.jfk1.somedom.local β idm β true β rnd-trp avg = 36.26ms β
β idm-01b.lab1.somedom.local β idm β true β rnd-trp avg = 41.171ms β
β idm-01b.rdu1.somedom.local β idm β true β rnd-trp avg = 27.097ms β
β idm-01b.dfw1.somedom.local β idm β true β rnd-trp avg = 51.547ms β
β idm-01b.lax2.somedom.local β idm β true β rnd-trp avg = 94.203ms β
β idm-01b.jfk1.somedom.local β idm β true β rnd-trp avg = 36.956ms β
β idm-01a.lhr1.somedom.us β idm β true β rnd-trp avg = 145.853ms β
β idm-01a.fra1.somedom.us β idm β true β rnd-trp avg = 146.425ms β
β idm-01b.lhr1.somedom.us β idm β true β rnd-trp avg = 127.987ms β
β idm-01b.fra1.somedom.us β idm β true β rnd-trp avg = 135.593ms β
ββββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββ΄βββββββββββββ΄ββββββββββββββββββββββββββ
All the CLI subcommands can make use of either the -v or the --verbose switch to gather further diagnostics which can be helpful when triaging connectivity issues.
Tree - CLICK ME
For example:
$ doxctl dns -r -v
+ printf '\n\nDNS Resolver Checks\n===================\n\n\n'
DNS Resolver Checks
===================
++ printf 'get State:/Network/Service/com.cisco.anyconnect/DNS\nd.show\n'
++ scutil
+ vpn_resolvers='<dictionary> {
DomainName : somedom.local
SearchDomains : <array> {
0 : somedom.local
}
SearchOrder : 1
ServerAddresses : <array> {
0 : 10.5.0.18
1 : 10.5.0.19
2 : 192.168.7.85
}
SupplementalMatchDomains : <array> {
0 :
1 : somedom.local
}
}'
+ column -t
+ echo '<dictionary> {
DomainName : somedom.local
SearchDomains : <array> {
0 : somedom.local
}
SearchOrder : 1
ServerAddresses : <array> {
0 : 10.5.0.18
1 : 10.5.0.19
2 : 192.168.7.85
}
SupplementalMatchDomains : <array> {
0 :
1 : somedom.local
}
}'
+ grep -q 'DomainName.*somedom.local'
+ echo 'DomainName set'
+ echo '<dictionary> {
DomainName : somedom.local
SearchDomains : <array> {
0 : somedom.local
}
SearchOrder : 1
ServerAddresses : <array> {
0 : 10.5.0.18
1 : 10.5.0.19
2 : 192.168.7.85
}
SupplementalMatchDomains : <array> {
0 :
1 : somedom.local
}
}'
+ grep -A1 SearchDomains
+ grep -qE '[0-1].*somedom'
+ echo 'SearchDomains set'
+ echo '<dictionary> {
DomainName : somedom.local
SearchDomains : <array> {
0 : somedom.local
}
SearchOrder : 1
ServerAddresses : <array> {
0 : 10.5.0.18
1 : 10.5.0.19
2 : 192.168.7.85
}
SupplementalMatchDomains : <array> {
0 :
1 : somedom.local
}
}'
+ grep -A3 ServerAddresses
+ grep -qE '[0-1].*10.5'
+ echo 'ServerAddresses set'
DomainName set
SearchDomains set
ServerAddresses set
+ [[ 1 -eq 1 ]]
+ set +x
- Go 1.25.0+ (as specified in go.mod)
- Git for version control
# Clone the repository
git clone https://github.com/slmingol/doxctl.git
cd doxctl
# Build the binary
go build -o doxctl .
# Run the binary
./doxctl --help# Run all tests
go test ./...
# Run tests with coverage
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.outThe project uses golangci-lint for code quality enforcement:
# Install golangci-lint (if not already installed)
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# Run linter
golangci-lint run
# Run with auto-fix for some issues
golangci-lint run --fix- Make changes to the code
- Run tests:
go test ./... - Run linter:
golangci-lint run - Build:
go build -o doxctl . - Test locally:
./doxctl [command]
This project implements multiple layers of security and code quality checks:
- CodeQL Analysis: Runs on every push and pull request to identify security vulnerabilities
- Security Scan Workflow: Dedicated security scanning workflow runs on all commits
- Dependabot: Automatically monitors dependencies and creates PRs for security updates
- Checks Go modules weekly
- Checks GitHub Actions weekly
- golangci-lint: Comprehensive linting with multiple analyzers:
errcheck: Ensures error handlinggosec: Security-focused static analysisstaticcheck: Advanced Go static analysisgofmt: Code formattingmisspell: Spelling checks- And many more (see
.golangci.yml)
Please see SECURITY.md for our security policy and instructions on reporting vulnerabilities.
macOS: Fully supported
- Uses native
scutilcommand for DNS configuration - Tested on Intel and Apple Silicon (M1/M2)
Linux: Partial support
- Multi-arch builds available (amd64, arm64)
- Some DNS features rely on macOS-specific
scutilcommand - VPN and server connectivity checks work across platforms
Docker images support both architectures:
- amd64: Traditional x86_64 systems
- arm64: Apple M1/M2, AWS Graviton, Raspberry Pi
See the Docker section for usage examples.
Contributions are welcome! Please see CONTRIBUTING.md for:
- Code of conduct
- Development guidelines
- Pull request process
- Coding standards
For security-related contributions, please review SECURITY.md first.
