Skip to content

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_RAW ambiently 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