mirror of
https://github.com/domcyrus/rustnet.git
synced 2026-01-05 21:40:01 -06:00
feat: add static musl binary builds for Linux (#103)
This commit is contained in:
56
.github/workflows/release.yml
vendored
56
.github/workflows/release.yml
vendored
@@ -185,10 +185,64 @@ jobs:
|
||||
path: rustnet-${{ github.ref_name }}-x86_64-unknown-freebsd.tar.gz
|
||||
if-no-files-found: error
|
||||
|
||||
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: Configure static zstd linking
|
||||
run: |
|
||||
# Fix for elfutils 0.189+ undeclared zstd dependency
|
||||
# See: https://github.com/libbpf/bpftool/issues/152
|
||||
mkdir -p .cargo
|
||||
printf '[target.x86_64-unknown-linux-musl]\nrustflags = ["-C", "link-arg=-l:libzstd.a"]\n' > .cargo/config.toml
|
||||
|
||||
- name: Build static binary
|
||||
env:
|
||||
RUSTFLAGS: "-C strip=symbols"
|
||||
run: cargo build --release
|
||||
|
||||
- name: Verify static linking
|
||||
run: |
|
||||
file target/release/rustnet
|
||||
ldd target/release/rustnet 2>&1 | grep -q "statically 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"
|
||||
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 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
|
||||
if-no-files-found: error
|
||||
|
||||
create-release:
|
||||
name: create-release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-release, build-freebsd]
|
||||
needs: [build-release, build-freebsd, build-static]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
86
.github/workflows/test-static-build.yml
vendored
Normal file
86
.github/workflows/test-static-build.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
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: Configure static zstd linking
|
||||
run: |
|
||||
# Fix for elfutils 0.189+ undeclared zstd dependency
|
||||
# See: https://github.com/libbpf/bpftool/issues/152
|
||||
mkdir -p .cargo
|
||||
printf '[target.x86_64-unknown-linux-musl]\nrustflags = ["-C", "link-arg=-l:libzstd.a"]\n' > .cargo/config.toml
|
||||
|
||||
- name: Build static binary
|
||||
env:
|
||||
RUSTFLAGS: "-C strip=symbols"
|
||||
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
|
||||
if ldd target/release/rustnet 2>&1 | grep -q "statically 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
|
||||
61
Dockerfile.static
Normal file
61
Dockerfile.static
Normal file
@@ -0,0 +1,61 @@
|
||||
# Dockerfile for building static musl-linked RustNet binary
|
||||
#
|
||||
# Usage (with eBPF - default, recommended):
|
||||
# docker build -f Dockerfile.static -t rustnet-static .
|
||||
# docker run --rm -v $(pwd)/dist:/dist rustnet-static cp /build/target/release/rustnet /dist/
|
||||
#
|
||||
# Usage (without eBPF - smaller binary, ~5.2MB vs ~6.5MB):
|
||||
# docker build -f Dockerfile.static --build-arg FEATURES="--no-default-features" -t rustnet-static .
|
||||
|
||||
FROM rust:alpine
|
||||
|
||||
ARG FEATURES=""
|
||||
|
||||
# Install build dependencies
|
||||
# - musl-dev: musl C library headers
|
||||
# - libpcap-dev: libpcap headers and static library (/usr/lib/libpcap.a)
|
||||
# - pkgconfig: for finding library paths
|
||||
# - build-base: basic build tools (make, gcc, etc.)
|
||||
# - perl: required by some build scripts (ring crate)
|
||||
# - elfutils-dev: libelf for eBPF (includes static library)
|
||||
# - zlib-dev/zlib-static: compression library
|
||||
# - zstd-dev/zstd-static: Zstandard compression (required by elfutils 0.189+)
|
||||
# - clang/llvm: for eBPF compilation
|
||||
# - linux-headers: kernel headers for eBPF
|
||||
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
|
||||
|
||||
# Add rustfmt for eBPF skeleton generation
|
||||
RUN rustup component add rustfmt
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Configure static linking for zstd
|
||||
# This fixes the elfutils 0.189+ undeclared dependency on zstd
|
||||
# See: https://github.com/libbpf/bpftool/issues/152
|
||||
RUN mkdir -p .cargo && printf '[target.x86_64-unknown-linux-musl]\nrustflags = ["-C", "link-arg=-l:libzstd.a"]\n' > .cargo/config.toml
|
||||
|
||||
# Build configuration:
|
||||
# - In Alpine/musl, binaries are statically linked by default
|
||||
# - FEATURES arg controls whether eBPF is included (default) or disabled
|
||||
# - --release: Optimized build
|
||||
RUN cargo build --release ${FEATURES}
|
||||
|
||||
# Verify the binary is statically linked
|
||||
RUN file target/release/rustnet && \
|
||||
ldd target/release/rustnet 2>&1 || echo "Binary is statically linked"
|
||||
169
MUSL_BUILD.md
169
MUSL_BUILD.md
@@ -1,66 +1,133 @@
|
||||
# musl Static Build Challenges
|
||||
# musl Static Build Guide
|
||||
|
||||
This document explains why RustNet currently does not provide musl static builds and the technical challenges encountered during implementation attempts.
|
||||
This document explains how to build fully static RustNet binaries using musl.
|
||||
|
||||
## Background
|
||||
## Quick Start
|
||||
|
||||
musl is a lightweight C standard library designed for static linking. It would allow RustNet to produce fully static binaries that work on any Linux distribution regardless of GLIBC version.
|
||||
|
||||
## Why We Attempted musl Builds
|
||||
|
||||
GitHub issue #40 reported that pre-built packages required GLIBC 2.38/2.39, which wasn't available on PopOS 22.04 (GLIBC 2.35). musl builds would theoretically solve this by creating fully static binaries.
|
||||
|
||||
## Challenges Encountered
|
||||
|
||||
### libpcap Linking Issues
|
||||
|
||||
The primary challenge appears to be related to **libpcap** static linking with musl:
|
||||
|
||||
- Installing `libpcap-dev` in Ubuntu-based cross-rs containers provides glibc-linked libraries
|
||||
- Attempting to statically link these with musl resulted in linker errors
|
||||
- Errors included undefined references to pthread, math (exp), and dynamic loading functions (dladdr)
|
||||
|
||||
It's unclear whether this is due to:
|
||||
- Fundamental glibc/musl incompatibility when statically linking
|
||||
- Missing library specifications in the linker flags
|
||||
- Issues with how cross-rs musl images are configured
|
||||
- Something specific to our build configuration
|
||||
|
||||
### eBPF Complications
|
||||
|
||||
We initially attempted to include eBPF support, which required vendoring libelf and zlib. This was abandoned to simplify the problem, but even without eBPF the libpcap linking issues persisted.
|
||||
|
||||
## Current Solution
|
||||
|
||||
**We solved the original issue by pinning builds to ubuntu-22.04** (GLIBC 2.35), which ensures compatibility with PopOS 22.04 and similar distributions.
|
||||
|
||||
For users on older distributions, the `cargo install` workaround is documented:
|
||||
```bash
|
||||
cargo install rustnet-monitor
|
||||
sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' ~/.cargo/bin/rustnet
|
||||
# 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
|
||||
```
|
||||
|
||||
## Potential Future Approaches
|
||||
## Binary Characteristics
|
||||
|
||||
If someone wants to tackle musl builds in the future, areas to investigate:
|
||||
| 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 |
|
||||
|
||||
1. **Building libpcap from source** targeting musl in the pre-build step
|
||||
2. **Using Alpine Linux-based images** which have native musl packages
|
||||
3. **Custom linker flags** to properly link required libraries
|
||||
4. **Alternative pure-Rust packet capture** libraries (if they exist)
|
||||
## How It Works
|
||||
|
||||
We're uncertain which approach would work best, or if there are other issues we haven't discovered yet.
|
||||
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
|
||||
|
||||
## Why We're Not Pursuing This Now
|
||||
### The zstd Fix
|
||||
|
||||
- The ubuntu-22.04 solution already addresses the reported issue
|
||||
- The complexity-to-benefit ratio seems high
|
||||
- `cargo install` provides a universal fallback for edge cases
|
||||
- More investigation would be needed to understand the root causes
|
||||
Alpine's elfutils 0.189+ has an undeclared dependency on zstd for ELF section compression. The Dockerfile includes a workaround:
|
||||
|
||||
If you have experience with musl static linking and want to contribute, we'd welcome the help!
|
||||
```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-10-09*
|
||||
*Status: Not currently pursuing due to linking complexity*
|
||||
*Last updated: 2025-12-26*
|
||||
*Status: Fully working with eBPF support*
|
||||
|
||||
Reference in New Issue
Block a user