From ebc04fd09c6cdb3c37f2d4b6cb4e0d0e384388ac Mon Sep 17 00:00:00 2001 From: Marco Cadetg Date: Fri, 26 Dec 2025 22:09:32 +0100 Subject: [PATCH] feat: add aarch64 static builds, clean up docs --- .github/workflows/release.yml | 21 ++-- .github/workflows/test-static-build.yml | 82 --------------- INSTALL.md | 28 +++++ MUSL_BUILD.md | 133 ------------------------ 4 files changed, 43 insertions(+), 221 deletions(-) delete mode 100644 .github/workflows/test-static-build.yml delete mode 100644 MUSL_BUILD.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0ccf033..66bd4c9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -186,8 +186,17 @@ jobs: if-no-files-found: error build-static: - name: build-static-musl - runs-on: ubuntu-latest + name: build-static-${{ matrix.arch }} + runs-on: ${{ matrix.runner }} + strategy: + matrix: + include: + - arch: x86_64 + runner: ubuntu-latest + target: x86_64-unknown-linux-musl + - arch: aarch64 + runner: ubuntu-24.04-arm + target: aarch64-unknown-linux-musl container: image: rust:alpine steps: @@ -213,12 +222,12 @@ jobs: run: | file target/release/rustnet # Use file command to verify (ldd behaves differently inside Alpine) - file target/release/rustnet | grep -q "static-pie linked" || \ + file target/release/rustnet | grep -q "static.* linked" || \ (echo "ERROR: Binary is not statically linked" && exit 1) - name: Create release archive run: | - staging="rustnet-${{ github.ref_name }}-x86_64-unknown-linux-musl" + staging="rustnet-${{ github.ref_name }}-${{ matrix.target }}" mkdir -p "$staging/assets" cp target/release/rustnet "$staging/" @@ -231,8 +240,8 @@ jobs: - name: Upload static build artifact uses: actions/upload-artifact@v6 with: - name: build-x86_64-unknown-linux-musl - path: rustnet-${{ github.ref_name }}-x86_64-unknown-linux-musl.tar.gz + name: build-${{ matrix.target }} + path: rustnet-${{ github.ref_name }}-${{ matrix.target }}.tar.gz if-no-files-found: error create-release: diff --git a/.github/workflows/test-static-build.yml b/.github/workflows/test-static-build.yml deleted file mode 100644 index 2f2afae..0000000 --- a/.github/workflows/test-static-build.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: Test Static Build - -on: - pull_request: - paths: - - 'Dockerfile.static' - - '.github/workflows/test-static-build.yml' - - '.github/workflows/release.yml' - - 'MUSL_BUILD.md' - workflow_dispatch: - -permissions: - contents: read - -jobs: - build-static: - name: build-static-musl - runs-on: ubuntu-latest - container: - image: rust:alpine - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Install dependencies - run: | - apk add --no-cache \ - musl-dev libpcap-dev pkgconfig build-base perl \ - elfutils-dev zlib-dev zlib-static zstd-dev zstd-static \ - clang llvm linux-headers git - rustup component add rustfmt - - - name: Build static binary - env: - # -C strip=symbols: Strip debug symbols for smaller binary - # -C link-arg=-l:libzstd.a: Fix elfutils 0.189+ zstd dependency (libbpf/bpftool#152) - RUSTFLAGS: "-C strip=symbols -C link-arg=-l:libzstd.a" - run: cargo build --release - - - name: Verify static linking - run: | - echo "=== File info ===" - file target/release/rustnet - echo "" - echo "=== ldd output ===" - ldd target/release/rustnet 2>&1 || true - echo "" - echo "=== Size ===" - ls -lh target/release/rustnet - echo "" - # Verify it's actually static using file command - # (ldd behaves differently inside Alpine vs glibc systems) - if file target/release/rustnet | grep -q "static-pie linked"; then - echo "✅ Binary is statically linked!" - else - echo "❌ ERROR: Binary is NOT statically linked" - exit 1 - fi - - - name: Test binary runs - run: | - ./target/release/rustnet --version - ./target/release/rustnet --help | head -20 - - - name: Create release archive - run: | - staging="rustnet-static-x86_64-unknown-linux-musl" - mkdir -p "$staging/assets" - - cp target/release/rustnet "$staging/" - cp assets/services "$staging/assets/" 2>/dev/null || true - cp README.md "$staging/" - cp LICENSE "$staging/" 2>/dev/null || true - - tar czf "$staging.tar.gz" "$staging" - - - name: Upload static binary - uses: actions/upload-artifact@v4 - with: - name: rustnet-static-musl - path: rustnet-static-x86_64-unknown-linux-musl.tar.gz - retention-days: 7 diff --git a/INSTALL.md b/INSTALL.md index 855269f..4531e92 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -216,6 +216,34 @@ sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' $(brew --prefix)/bin/rustnet rustnet ``` +#### Static Binary (Portable - Any Linux Distribution) + +For maximum portability, static binaries are available that work on **any Linux distribution** regardless of GLIBC version. These are fully self-contained and require no system dependencies. + +```bash +# Download the static binary for your architecture: +# - rustnet-vX.Y.Z-x86_64-unknown-linux-musl.tar.gz (x86_64) +# - rustnet-vX.Y.Z-aarch64-unknown-linux-musl.tar.gz (ARM64) + +# Extract the archive +tar xzf rustnet-vX.Y.Z-x86_64-unknown-linux-musl.tar.gz + +# Move binary to PATH +sudo mv rustnet-vX.Y.Z-x86_64-unknown-linux-musl/rustnet /usr/local/bin/ + +# Grant capabilities (modern kernel 5.8+) +sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' /usr/local/bin/rustnet + +# Run without sudo +rustnet +``` + +**When to use static binaries:** +- Older distributions with outdated GLIBC (e.g., CentOS 7, older Ubuntu) +- Minimal/containerized environments +- Air-gapped systems where installing dependencies is difficult +- When you want a single portable binary + ### FreeBSD Installation FreeBSD support is available starting from version 0.15.0. diff --git a/MUSL_BUILD.md b/MUSL_BUILD.md deleted file mode 100644 index ab85da4..0000000 --- a/MUSL_BUILD.md +++ /dev/null @@ -1,133 +0,0 @@ -# musl Static Build Guide - -This document explains how to build fully static RustNet binaries using musl. - -## Quick Start - -```bash -# Build static binary with eBPF support (default, ~6.5MB) -docker build -f Dockerfile.static -t rustnet-static . - -# Or build without eBPF (smaller, ~5.2MB) -docker build -f Dockerfile.static --build-arg FEATURES="--no-default-features" -t rustnet-static . - -# Extract the binary -mkdir -p dist -docker run --rm -v $(pwd)/dist:/out rustnet-static cp /build/target/release/rustnet /out/ - -# Verify it's static -file dist/rustnet -# Output: ELF 64-bit LSB pie executable, x86-64, ..., static-pie linked - -ldd dist/rustnet -# Output: statically linked -``` - -## Binary Characteristics - -| Build | Size | eBPF | Process Detection | Compatibility | -|-------|------|------|-------------------|---------------| -| With eBPF | ~6.5MB | Yes | eBPF + procfs fallback | Any Linux | -| Without eBPF | ~5.2MB | No | procfs only | Any Linux | - -## How It Works - -The `Dockerfile.static` uses `rust:alpine` which provides: -- Native musl toolchain (no glibc/musl mixing issues) -- `libpcap-dev` package with static library (`/usr/lib/libpcap.a`) -- `elfutils-dev` with static libelf for eBPF -- All dependencies compiled against musl - -### The zstd Fix - -Alpine's elfutils 0.189+ has an undeclared dependency on zstd for ELF section compression. The Dockerfile includes a workaround: - -```toml -# .cargo/config.toml -[target.x86_64-unknown-linux-musl] -rustflags = ["-C", "link-arg=-l:libzstd.a"] -``` - -This explicitly links the static zstd library, fixing the link order issue. See [libbpf/bpftool#152](https://github.com/libbpf/bpftool/issues/152) for details. - -## Running the Static Binary - -```bash -# Set capabilities for packet capture -sudo setcap 'cap_net_raw=eip' dist/rustnet - -# For eBPF support (Linux 5.8+) -sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' dist/rustnet - -# Run -./dist/rustnet -``` - -## CI Integration - -For GitHub Actions, use the native container approach (faster than Docker build): - -```yaml -build-static: - name: build-static-musl - runs-on: ubuntu-latest - container: - image: rust:alpine - steps: - - uses: actions/checkout@v6 - - - name: Install dependencies - run: | - apk add --no-cache \ - musl-dev libpcap-dev pkgconfig build-base perl \ - elfutils-dev zlib-dev zlib-static zstd-dev zstd-static \ - clang llvm linux-headers git - rustup component add rustfmt - - - name: Configure static zstd linking - run: | - mkdir -p .cargo - printf '[target.x86_64-unknown-linux-musl]\nrustflags = ["-C", "link-arg=-l:libzstd.a"]\n' > .cargo/config.toml - - - name: Build - run: cargo build --release - - - name: Verify static linking - run: | - file target/release/rustnet - ldd target/release/rustnet 2>&1 | grep -q "statically linked" -``` - -## Historical Context - -### Previous Challenges (Resolved) - -Earlier attempts using cross-rs with Ubuntu-based containers failed: -- Installing `libpcap-dev` in Ubuntu provides glibc-linked libraries -- Mixing glibc libraries with musl linking caused undefined references -- Errors included pthread, math (exp), and dladdr symbols - -### Why Alpine Works - -Alpine Linux uses musl as its system C library: -- All packages are compiled against musl -- Static libraries (`*.a`) are musl-compatible -- No glibc/musl mixing occurs - -### The eBPF Challenge (Resolved) - -Static eBPF builds initially failed due to elfutils → zstd dependency chain: -- elfutils 0.189+ added ZSTD compression for ELF sections -- libbpf-sys didn't propagate the zstd link dependency -- Fixed by explicitly linking `-l:libzstd.a` via cargo config - -## References - -- [libbpf/bpftool#152](https://github.com/libbpf/bpftool/issues/152) - zstd link fix -- [arachsys/libelf](https://github.com/arachsys/libelf) - Standalone libelf (alternative) -- [Alpine Static Linking](https://build-your-own.org/blog/20221229_alpine/) - General guidance - ---- - -*Last updated: 2025-12-26* -*Status: Fully working with eBPF support*