Skip to content

Latest commit

 

History

History
154 lines (117 loc) · 6.26 KB

File metadata and controls

154 lines (117 loc) · 6.26 KB

Developing NatStream

Building the daemon, packaging it, measuring it, and cutting a release. For installing and running it, see README.md.

Requirements

  • Linux (Netlink netfilter API required)
  • Rust 1.85+ (project uses edition 2024)
  • Docker, for building .deb packages
  • CAP_NET_ADMIN (or root) to run the daemon or the end-to-end tests

Build

cargo build             # debug
cargo build --release   # optimized release
cargo test              # unit and integration tests
cargo fmt --check       # formatting
cargo clippy            # lints

Binary output is target/debug/natstream or target/release/natstream.

Clippy's pedantic lint set lives in Cargo.toml. CI checks every target and treats warnings as errors with cargo clippy --all-targets -- -D warnings. The casts that a wire format necessarily makes are annotated where they are, with the bound that makes each one safe; the few places where the layout of the source is doing the explaining carry #[rustfmt::skip].

Packaging

./build.sh produces a .deb for a target distribution, inside a Docker container based on that distribution. No build tooling is needed on the host beyond Docker. This is the same script the release workflow runs, so a locally built package is the packaged one.

./build.sh trixie      # Debian 13        -> dist/natstream_*~deb13_*.deb
./build.sh 24.04       # Ubuntu 24.04 LTS -> dist/natstream_*~ubuntu24.04_*.deb
./build.sh 26.04       # Ubuntu 26.04 LTS -> dist/natstream_*~ubuntu26.04_*.deb
./build.sh all         # all three

The Rust toolchain is pinned (RUST_VERSION, default 1.97.1) and installed in the container, because the distros' own rustc is not usable across all targets — Ubuntu 24.04 ships 1.75 and this crate needs 1.85+ for edition 2024. Each target links its own distro's glibc and gets a libc6 dependency derived from the binary with dpkg-shlibdeps, rather than a guess. In practice the binary currently only needs GLIBC_2.34, so the three are interchangeable today; the per-distro build is what keeps that true if it ever stops being.

Override the packaging metadata with MAINTAINER=, DEB_REVISION= and RUST_VERSION= in the environment. ./build.sh --help lists everything.

Benchmarks

cargo bench                  # everything
cargo bench -- decode        # one group

The benchmarks measure the two steps that cost CPU — decoding a netlink datagram into conntrack events, and encoding those into export messages — separately and together, across every protocol, profile and counter width. Decoding is measured with all events translated, with one in four translated, and with none, because rejecting an event the exporter does not care about is the common case and the cheapest path through the parser.

They deliberately do not measure the recvmmsg receive itself: its cost includes the kernel and cannot be reproduced without a live conntrack.

Checking a change for regressions

Criterion baselines make the before/after comparison a two-step affair. The gate is on the lower bound of the confidence interval, so a regression has to be one the statistics are confident about before it fails:

./benches/compare.sh --save before      # on the unchanged tree
                                        # ...make the change...
./benches/compare.sh --against before   # exits non-zero if anything got slower

--threshold PCT sets what counts as a regression (default 5%), --filter EXPR narrows it to some of the benchmarks, and --list shows the saved baselines. Results are only as steady as the machine underneath them; for numbers worth arguing over, pin the CPU governor to performance.

What it can take from a real kernel

The microbenchmarks say how fast the code is, not whether the daemon keeps up with a kernel that is actually producing events. That needs the real thing:

cargo build --release
./tests/e2e/run-netns.sh --throughput

It creates tens of thousands of NAT sessions in a namespace, tears them all down, and reports what was offered, what the kernel had to drop because the exporter did not drain its socket in time, and what the collector received. The netlink drop count is the one that answers the question — records received is a floor, since a Python collector on loopback is the slower end.

Treat it as a sizing exercise rather than a regression gate: the load is an instantaneous burst, harsher than real traffic, and the result moves with the machine. Build for release first, or the number says more about -O0 than about the exporter.

Releasing

A release is a tag. .github/workflows/release.yml builds one .deb per target with ./build.sh, then publishes them to a GitHub release:

# Cargo.toml's version must already say 0.2.0 on the commit being tagged.
git tag -a 0.2.0 -m 'NatStream 0.2.0'
git push origin 0.2.0

0.2.0 and v0.2.0 both trigger it, since the repository has tags of each kind. The workflow refuses to build if the tag and Cargo.toml disagree, so a mismatch costs a failed job rather than a wrong package. A version with a suffix (0.2.0-rc1) is published as a pre-release.

Before publishing, the Ubuntu 24.04 package is installed and purged on the runner, which is the same distribution: a maintainer script that fails, a binary that will not run, or an install that enables the service fails the build. The release carries the packages and a SHA256SUMS over them.

Running the workflow by hand from the Actions tab builds all three packages and leaves them as workflow artifacts without publishing anything, which is how to exercise the packaging path without spending a tag. Giving it a tag instead builds that tag and attaches the packages to its release, which is how a tag pushed before this workflow existed gets its packages:

gh workflow run release.yml --ref master -f tag=0.1.0

An existing release keeps its notes; only the packages are attached.

Set the DEB_MAINTAINER repository variable to a real Name <email>; without it the packages carry the placeholder maintainer from packaging/build-deb.sh.

License

Copyright 2026 FastNetMon LTD.

Licensed under the Apache License, Version 2.0. See LICENSE for the full text and NOTICE for the copyright notice, or https://www.apache.org/licenses/LICENSE-2.0.