Installation¶
Install the Automated Cryptography Discovery & Inventory (ACDI) Sniffer on a Linux host. Most operators should use the RPM package, which installs the binary, a systemd service, and a default configuration in one step. A manual install is also documented for environments where the RPM cannot be used.
Requirements¶
| Item | Requirement |
|---|---|
| Operating system | RHEL 8 or Rocky Linux 8 |
| Architecture | x86_64 or aarch64 |
| Privileges | root (or sudo) to install and to capture live traffic |
| Network access | A capture interface, or .pcap / .pcapng files to process |
The sniffer captures live traffic using the CAP_NET_RAW capability. The RPM grants it to
the systemd service rather than to the binary itself, and the manual install
does the same: it applies no file capability, and manual foreground runs use sudo.
Note
The RPM's runtime dependencies (libpcap, systemd, libcap, and the Boost
runtime libraries) are pulled from the standard RHEL/Rocky repositories during
installation. The host must be able to reach those repositories, or have the
packages already present.
OpenSSL and the PostgreSQL client library are not in this list. They are linked
statically into the binary, so no openssl-libs or postgresql-libs package is
required on the host and the versions in use are those recorded in the release SBOM,
not whatever the host happens to ship.
Install from RPM¶
This is the recommended path.
1. Obtain the package¶
You will be provided an ACDI Sniffer RPM for your architecture, named like:
pqc-sniffer-2.0.1.33.x86_64.rpm # x86_64
pqc-sniffer-2.0.1.33.aarch64.rpm # aarch64
The architecture in the filename is the same value uname -m reports on the host and the
same value rpm -qi reports for the installed package, so you can confirm you have the
right build without opening it.
Packages issued before this release
Earlier packages were named pqc-sniffer-linux-amd64-<version>.rpm, using the Debian
spelling of the architecture. The contents are unaffected --- only the filename changed.
An amd64 file you already hold is the x86_64 package.
Copy it to the target host, for example:
scp pqc-sniffer-2.0.1.33.x86_64.rpm user@host:/tmp/
2. Verify the download¶
Each RPM is published with a .sha256 file beside it in the release bundle. Check the
package before installing it, so a truncated or corrupted transfer is caught now rather
than part way through dnf.
The checksum file records the package's path within the bundle, so run the check from the directory the bundle was extracted into:
sha256sum -c rpms/pqc-sniffer-2.0.1.33.x86_64.rpm.sha256
Expect a single OK line naming the package. Anything else means the file on this host is
not the file that was published, and it should be downloaded again rather than installed.
A checksum detects corruption and interrupted transfers. It does not establish who published the package; if your deployment requires that assurance, ask your TYCHON contact.
3. Install the package¶
Use dnf so runtime dependencies resolve automatically. Point it at the local file
with a ./ (or absolute) path:
sudo dnf install ./pqc-sniffer-2.0.1.33.x86_64.rpm
Installation places the following on the system:
| Path | Purpose |
|---|---|
/usr/bin/pqc-sniffer |
The sniffer binary (on PATH) |
/etc/pqc-sniffer/config.yaml |
Active configuration (preserved on upgrade) |
/etc/pqc-sniffer/config.yaml.example |
Reference configuration |
/usr/lib/systemd/system/pqc-sniffer.service |
systemd service unit |
/var/log/pqc-sniffer/ |
Default output and log directory |
/usr/share/licenses/pqc-sniffer/THIRD-PARTY-NOTICES.txt |
Licences of the open-source libraries linked into the binary |
The notices file lists every third-party library compiled into pqc-sniffer with its
version and full licence text. It is generated from the dependency graph of the build
that produced your package, so it describes that exact binary rather than a general
list. Installed as an RPM licence file, so rpm --excludedocs does not remove it, and
rpm -qL pqc-sniffer prints its path on an installed system.
pqc-sniffer itself is proprietary; the notices cover only the open-source components it links.
The package deliberately applies no file capability to the binary. The service
captures without running as root because the systemd unit grants it CAP_NET_RAW as an
ambient capability, which applies to the service alone. A file capability
(setcap cap_net_raw+eip) would instead grant packet capture to every local account able
to execute /usr/bin/pqc-sniffer, whether or not that account can start the service.
You can confirm both:
getcap /usr/bin/pqc-sniffer # expected: no output
systemctl show pqc-sniffer -p AmbientCapabilities # expected: cap_net_raw
Running the binary directly, outside the service, therefore needs sudo.
4. Verify the install¶
pqc-sniffer --version
Then confirm the binary carries no embedded library search path:
readelf -dW /usr/bin/pqc-sniffer | grep -E 'RPATH|RUNPATH'
No output is the expected result. Any output means the binary tells the dynamic
loader to search directories of its own before the system library paths. Those
directories are build-machine paths that do not exist on your host, so whichever
account is able to create one decides which libpcap.so.1 the service loads --- and
the service holds CAP_NET_RAW. A path ending in :, or containing ::, is worse
still: an empty component means the loader searches the current working directory.
If you see output, do not run the binary. Obtain a package built from a current release; see Binary reports an embedded library search path.
Re-run this check after every install and upgrade, and keep /opt, /etc/pqc-sniffer
and /var/lib/pqc-sniffer owned by root and not writable by unprivileged users.
5. Configure¶
The installed config.yaml is a starter file. This guide runs the sniffer in
offline mode, where it writes NDJSON to local files. Replace the configuration with
the minimal offline setup below, then set your identity and capture interface:
sudo nano /etc/pqc-sniffer/config.yaml
sniffer:
customer_id: "your-customer-id"
sniffer_id: "sniffer-prod-01"
mode: "offline" # write NDJSON to file (not to a database)
capture:
interface: "eth0" # your capture interface — see `ip link`
mode: "local"
bpf_filter: "tcp or udp or proto 47 or ip6 proto 0 or ip6 proto 43 or ip6 proto 44 or ip6 proto 51 or ip6 proto 60 or ip6 proto 135"
promiscuous: true
offline_output:
output_file: "/var/log/pqc-sniffer/"
append_mode: true
buffer_size_kb: 512
rotation_enabled: true
max_size_mb: 100
max_files: 10
logging:
level: "info"
file:
path: "/var/log/pqc-sniffer/pqc-sniffer.log"
rotation_enabled: true
max_size_mb: 100
max_files: 10
database:
enabled: false # required for file-only output
monitoring:
heartbeat_interval_seconds: 60
metrics:
enabled: false
Important
sniffer.mode defaults to offline, and offline is the only supported mode in this
release. Connected (PostgreSQL) mode is not available — setting sniffer.mode: connected
causes the sniffer to refuse to start.
See the YAML Reference for every setting, and Configuration Examples for ready-to-adapt patterns. Validate your changes before starting the service:
sudo pqc-sniffer --config /etc/pqc-sniffer/config.yaml --validate-config
6. Start the service¶
sudo systemctl start pqc-sniffer # start now
sudo systemctl enable pqc-sniffer # start automatically on boot
Check status and logs:
systemctl status pqc-sniffer
sudo journalctl -u pqc-sniffer -f
Confirm output is being written:
With rotation_enabled: true, the sniffer writes indexed files such as
connections.0.ndjson, connections.1.ndjson, and so on. With
rotation_enabled: false, it writes the base file connections.ndjson.
latest_output=$(ls -t /var/log/pqc-sniffer/connections*.ndjson | head -n 1)
tail -5 "$latest_output"
The service restarts automatically on failure. See Running the Sniffer for runtime behavior and Deployment & Management for operational guidance.
Upgrade¶
Install the newer RPM the same way; dnf performs an upgrade in place:
sudo dnf install ./pqc-sniffer-<new-version>.x86_64.rpm
sudo systemctl restart pqc-sniffer
Your /etc/pqc-sniffer/config.yaml is preserved across upgrades. A new reference file
is always written to config.yaml.example so you can compare against the latest
defaults.
Uninstall¶
sudo dnf remove pqc-sniffer
Removal stops and disables the service. The log directory
/var/log/pqc-sniffer/ is cleaned up on a full uninstall (it is retained across
upgrades).
Manual install¶
Use this path only when the RPM cannot be used. It performs the same essential steps
by hand, starting from a standalone pqc-sniffer binary for your architecture.
1. Place the binary¶
sudo install -m 0755 pqc-sniffer /usr/bin/pqc-sniffer
2. Confirm runtime libraries are present¶
ldd /usr/bin/pqc-sniffer | grep "not found"
No output means every shared library is resolved. If anything is reported as
not found, install the matching package from your OS repositories:
| Missing library | Package |
|---|---|
libpcap.so.1 |
libpcap |
libboost_program_options.so.*, libboost_system.so.* |
boost-program-options, boost-system |
OpenSSL and the PostgreSQL client library are linked statically and will never appear
here, so openssl-libs and postgresql-libs are not needed.
Then confirm those libraries will be resolved from the system paths and nowhere else:
readelf -dW /usr/bin/pqc-sniffer | grep -E 'RPATH|RUNPATH'
No output is the expected result. See Verify the install above for what output would mean.
3. Confirm the binary runs¶
pqc-sniffer --version
Do not apply a file capability to the installed binary. setcap cap_net_raw+eip
grants packet capture to every local account that can execute the file, which is a
standing privilege on a shared host. Capture privilege comes from one of two places
instead:
- As a service — the systemd unit grants
CAP_NET_RAWambiently to that unit only. Step 6 covers installing the unit. - For a manual foreground run — use
sudo, as shown in step 6.
4. Create the service account¶
The service runs under a dedicated, unprivileged pqc-sniffer account rather than
root (the RPM does this for you). Create it before laying down the config and
directories so ownership can be set correctly:
sudo groupadd -r pqc-sniffer 2>/dev/null || true
sudo useradd -r -g pqc-sniffer -d /var/lib/pqc-sniffer -s /sbin/nologin \
-c "PQC Network Sniffer service account" pqc-sniffer 2>/dev/null || true
5. Create the configuration and directories¶
sudo mkdir -p /etc/pqc-sniffer /var/log/pqc-sniffer /var/lib/pqc-sniffer
sudo cp example.yaml /etc/pqc-sniffer/config.yaml
sudo nano /etc/pqc-sniffer/config.yaml
The config may hold credentials, so keep it readable by the service account but not
world-readable, and make the output/state directories owned by the account
(0750, not the umask default):
sudo chown pqc-sniffer:pqc-sniffer /etc/pqc-sniffer/config.yaml
sudo chmod 0600 /etc/pqc-sniffer/config.yaml
sudo chown pqc-sniffer:pqc-sniffer /var/log/pqc-sniffer /var/lib/pqc-sniffer
sudo chmod 0750 /var/log/pqc-sniffer /var/lib/pqc-sniffer
Who can read the output¶
Nothing the sniffer writes is world-readable. The permission model is deliberate and it is what a log collector has to work with:
| Path | Mode | Read access |
|---|---|---|
/etc/pqc-sniffer/config.yaml |
0600 pqc-sniffer:pqc-sniffer |
the service account only |
| a config the sniffer writes back itself | 0600 |
its owner only |
/var/log/pqc-sniffer/*.ndjson |
0640 |
owner, and the file's group |
/var/log/pqc-sniffer/*.log |
0640 |
owner, and the file's group |
| pre-capture PCAPs, including rotated ones | 0600 |
the service account only |
/var/lib/pqc-sniffer/buffer/*.buf |
0600 |
the service account only |
/var/log/pqc-sniffer/, /var/lib/pqc-sniffer/ |
0750 |
owner, and the directory's group |
| the pre-capture PCAP directory | 0700 |
the service account only |
PCAPs and buffer files have no group-read bit, unlike the NDJSON and the logs. They are raw packet contents and serialised connection records rather than the summarised telemetry a collector is meant to ship, so nothing outside the service has a reason to open them.
config.yaml is the one file with no group-read bit, and that is deliberate. It can hold a
plaintext database password, and the group a collector joins below is the same group the
service runs as — so while the config was 0640 root:pqc-sniffer, adding a Splunk or Elastic
account to that group also gave it the database credentials. At 0600 it does not. Nothing
but the service needs to read the config, and root can still edit it.
The group-read bit on the output is the whole mechanism by which a collector reads it. Under
the shipped unit the service runs as pqc-sniffer:pqc-sniffer, so files are group-owned by
pqc-sniffer and a collector's service account reads them by joining that group:
# Splunk Universal Forwarder — use the account splunkd actually runs as
sudo usermod -aG pqc-sniffer splunkfwd
sudo systemctl restart SplunkForwarder
# Elastic Agent
sudo usermod -aG pqc-sniffer elastic-agent
sudo systemctl restart elastic-agent
Group membership is picked up when a process starts, so the collector must be restarted
after the usermod or it keeps running with its old group set and still cannot read. Confirm
as the collector's own account rather than as root, which can read regardless:
sudo -u splunkfwd head -n 1 /var/log/pqc-sniffer/connections*.ndjson
Running the binary directly as root changes the group
Files take the group of the process that created them. Started through
systemctl, that is pqc-sniffer. Run by hand as root, the same files come out
root:root — still 0640, so still not world-readable, but the group-read bit now
grants root rather than pqc-sniffer, and a collector in the pqc-sniffer group
cannot read them no matter how the group is configured. If a collector stops seeing
data after someone ran the sniffer manually, check ls -l /var/log/pqc-sniffer for
root root and either re-run through the service or chgrp pqc-sniffer the files.
Files an older version already wrote¶
Installing or upgrading the package corrects the permissions on what is already in
/var/log/pqc-sniffer and /var/lib/pqc-sniffer, not only on what it installs. A host
first set up before these modes were pinned kept its old 0644 root:root captures and
spill files indefinitely: the fixed build wrote new files correctly and never revisited the
old ones. scripts/install.sh does the same thing for a manual install.
The correction only ever removes access. That matters because two of the modes above
are deliberately not the strictest available -- NDJSON is 0640 so a collector can read
it, and the pre-capture directory is 0700 rather than 0750 -- and a blanket chmod
would break the first and loosen the second. Anything already stricter than the table is
left alone.
You can run the same corrections by hand at any time:
for d in /var/log/pqc-sniffer /var/lib/pqc-sniffer; do
sudo chown -R --no-dereference pqc-sniffer:pqc-sniffer "$d"
sudo find "$d" -type d -exec chmod g-w,o-rwx {} +
sudo find "$d" -type f \( -name '*.pcap*' -o -name '*.buf' \) \
-exec chmod g-rwx,o-rwx {} +
sudo find "$d" -type f ! -name '*.pcap*' ! -name '*.buf' \
-exec chmod g-wx,o-rwx {} +
done
Set sniffer.customer_id, sniffer.sniffer_id, and capture.interface as shown
above, then validate:
sudo pqc-sniffer --config /etc/pqc-sniffer/config.yaml --validate-config
6. Run it¶
For a quick foreground test:
sudo pqc-sniffer --config /etc/pqc-sniffer/config.yaml --foreground
To run it as a managed service, install the systemd unit and enable it — see
Deployment & Management for the unit file and the
systemctl steps.
Next steps¶
- Quick Start — capture to NDJSON in a few commands
- Running the Sniffer — runtime behavior and CLI overrides
- Troubleshooting — no output, permission, and capture issues