A DNS query and a username, stitched back together.
OpenVPN sees IPs. It never tells you who ran a query. This installs three small
services that capture DNS on the tunnel, track which user held which IP and when,
and give you one command — vpn-lookup — to answer
"who visited this, and when."
Works the same either way
User attribution needs a source of truth for "who held this IP, when." Community
Edition and Access Server expose that completely differently — dns-status-poller detects which one you're running and handles both.
Community Edition
Free, self-configured, server.conf you edit by hand.
- Reads
status.log— on by default in nearly every install script - Tunnel interface is usually
tun0 - No config edits or restart required
Access Server
Commercial product, managed through sacli and a web admin UI — including AWS Marketplace images.
- Reads live state via
sacli VPNStatusinstead - Tunnel interfaces are named
as0t0,as0t1, … — nottun0 - Field mapping comes straight from AS's own header data — no guessing
Three things to check first
In order — each one avoids a dead end further down.
-
You're root on the OpenVPN server itself. Not a client, not a jump box — the box actually running
openvpnor Access Server. Every install and service step needssudoor a root shell. -
OpenVPN is already installed and has active connections. This instruments an existing server — it doesn't set one up. Interface auto-detection also works better with at least one client connected during install.
-
tshark can be installed.
install.shinstalls it viaaptautomatically if missing — just confirm the box has outbound access to your package mirror.
Two ways in
Both end up running the same install.sh — pick whichever fits how you work.
curl -fsSL https://raw.githubusercontent.com/yousafkhamza/openvpn-dns-tools/main/bootstrap.sh | sudo bash
main, e.g. /raw/v1.0.0/bootstrap.sh.cd openvpn-dns-tools
sudo ./install.sh
install.sh before running it, or you're on a box with no outbound access to raw.githubusercontent.com.
install.sh
auto-detects your tunnel interface, your attribution backend, and installs everything — including short commands on your $PATH. Enable the services when it tells you to:
sudo systemctl enable --now openvpn-dns-status-poller.service
sudo systemctl enable --now openvpn-dns-cleanup.timer
Every log, one table
Nothing hidden — these are the only paths this tool writes to.
| Path | What's in it |
|---|---|
| /var/log/openvpn-dns/openvpn-dns-YYYY-MM-DD.log | One file per day. Every DNS query seen on the tunnel: timestamp, client IP, domain, record type. Deleted automatically after 2 days. |
| /var/log/openvpn-dns.log | Symlink to today's file above — a fixed path for anything that expects one. |
| /var/log/openvpn-dns/openvpn-sessions.log | Who held which VPN IP, and when — connect/disconnect events from dns-status-poller (or the optional hooks). This is what makes vpn-lookup able to name a user, not just an IP. Rotated monthly, kept 12 months. |
| /etc/systemd/system/openvpn-dns-* | The three services/timer: capture, status-poller, cleanup. |
| /etc/logrotate.d/openvpn-dns-sessions | Rotation rule for the session log — installed automatically. |
Commands, from anywhere
Installed to /usr/local/bin — no path to type, no .py. Run the ones marked root as sudo — anything else just tells you it needs root rather than failing cryptically.
| Command | Does | Needs root? |
|---|---|---|
| vpn-lookup --domain loadway | Search by domain, IP, user, or time range | usually not |
| dns-capture -i tun0 | Runs as the systemd service — sniffs DNS on the tunnel | root |
| dns-status-poller | Runs as the systemd service — tracks IP↔user over time | root |
| dns-cleanup | Deletes DNS logs older than 2 days | root |