fix: release workflow (#17)

* Remove musl targets to simplify Linux builds and fix cross-compilation issues
* Enable eBPF by default on Linux via linux-default feature for better packet capture
* Add macOS code signing and notarization support with graceful fallback for unsigned builds
* Fix Windows MSI packaging with improved WiX configuration and Npcap library linking
* Auto-extract changelog content from CHANGELOG.md into GitHub release notes
* Fix ARM cross-compilation (aarch64, armv7) with proper library paths and eBPF support
* Add comprehensive installation documentation for DMG, MSI, DEB, and RPM packages
* Allow re-running releases with --clobber flag for artifact uploads
This commit is contained in:
Marco Cadetg
2025-09-30 09:39:26 +02:00
committed by GitHub
parent d9798f9605
commit 42db7f5614
9 changed files with 429 additions and 120 deletions

View File

@@ -1,5 +1,24 @@
name: Release
# This workflow builds and packages Rustnet for multiple platforms.
#
# For macOS code signing and notarization, configure these GitHub repository secrets:
# - APPLE_DEVELOPER_CERTIFICATE_P12: Base64-encoded .p12 certificate file
# (Export from Keychain: Developer ID Application certificate → Export as .p12 → base64 encode)
# - APPLE_DEVELOPER_CERTIFICATE_PASSWORD: Password for the .p12 file
# - APPLE_ID_ISSUER_ID: App Store Connect API issuer ID (from App Store Connect → Users and Access → Keys)
# - APPLE_ID_KEY_ID: App Store Connect API key ID
# - APPLE_ID_KEY: App Store Connect API key content (.p8 file contents)
#
# Without these secrets, the DMG will be unsigned and users must use "Open Anyway" in
# System Settings → Privacy & Security to bypass Gatekeeper protection.
#
# To obtain Apple Developer credentials:
# 1. Join the Apple Developer Program ($99/year): https://developer.apple.com/programs/
# 2. Create a Developer ID Application certificate in your Apple Developer account
# 3. Create an App Store Connect API key with "Developer" role
# 4. Export certificate as .p12, encode with: base64 -i certificate.p12 | pbcopy
on:
push:
tags:
@@ -18,11 +37,8 @@ jobs:
matrix:
build:
- linux-aarch64-gnu
- linux-aarch64-musl
- linux-armv7-gnueabihf
- linux-armv7-musleabihf
- linux-x64-gnu
- linux-x64-musl
- macos-aarch64
- macos-x64
- windows-x64-msvc
@@ -33,19 +49,11 @@ jobs:
- build: linux-aarch64-gnu
target: aarch64-unknown-linux-gnu
cargo: cross
- build: linux-aarch64-musl
target: aarch64-unknown-linux-musl
cargo: cross
- build: linux-armv7-gnueabihf
target: armv7-unknown-linux-gnueabihf
cargo: cross
- build: linux-armv7-musleabihf
target: armv7-unknown-linux-musleabihf
cargo: cross
- build: linux-x64-gnu
target: x86_64-unknown-linux-gnu
- build: linux-x64-musl
target: x86_64-unknown-linux-musl
- build: macos-aarch64
os: macos-14
target: aarch64-apple-darwin
@@ -67,21 +75,20 @@ jobs:
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update -y
sudo apt-get install -y libpcap-dev libelf-dev clang llvm
sudo apt-get install -y libpcap-dev libelf-dev zlib1g-dev clang llvm pkg-config
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: ${{ matrix.target }}
components: rustfmt
- name: Install cross
if: matrix.cargo == 'cross'
uses: taiki-e/cache-cargo-install-action@v2
with:
tool: cross
git: https://github.com/cross-rs/cross.git
rev: 085092c
tool: cross@0.2.5
- name: Build release binary
shell: bash
@@ -90,7 +97,12 @@ jobs:
run: |
mkdir -p "$RUSTNET_ASSET_DIR"
# build.rs will handle Npcap SDK download on Windows
${{ matrix.cargo }} build --verbose --release --target ${{ matrix.target }}
# Use linux-default feature for all Linux targets to enable eBPF
if [[ "${{ matrix.target }}" == *"linux"* ]]; then
${{ matrix.cargo }} build --verbose --release --target ${{ matrix.target }} --features "linux-default"
else
${{ matrix.cargo }} build --verbose --release --target ${{ matrix.target }}
fi
- name: Create release archive
shell: bash
@@ -103,9 +115,10 @@ jobs:
# Copy binary
cp "target/${{ matrix.target }}/release/$RUSTNET_BIN" "$staging/"
# Copy assets if they exist
if [ -d "$RUSTNET_ASSET_DIR" ]; then
cp -r "$RUSTNET_ASSET_DIR" "$staging/"
# Copy services file if it exists
if [ -f "$RUSTNET_ASSET_DIR/services" ]; then
mkdir -p "$staging/assets"
cp "$RUSTNET_ASSET_DIR/services" "$staging/assets/"
fi
# Copy documentation
@@ -141,19 +154,59 @@ jobs:
with:
path: artifacts
- name: Extract changelog for release
run: |
# Extract the version-specific section from CHANGELOG.md
VERSION="${{ github.ref_name }}"
# Remove 'v' prefix if present for matching changelog headers
VERSION_NUMBER="${VERSION#v}"
# Extract content between version headers
awk -v version="$VERSION_NUMBER" '
/^## \[/ {
if (found) exit
if ($0 ~ "\\[" version "\\]") {
found=1
next
}
}
found { print }
' CHANGELOG.md > release_notes.md
# Check if we extracted any content
if [ ! -s release_notes.md ]; then
echo "⚠️ No changelog entry found for $VERSION, will use auto-generated notes"
echo "USE_GENERATED_NOTES=true" >> $GITHUB_ENV
else
echo "✅ Extracted changelog content for $VERSION"
cat release_notes.md
fi
- name: Create Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Create the release
gh release create ${{ github.ref_name }} \
--title "Release ${{ github.ref_name }}" \
--draft \
--generate-notes
# Create the release if it doesn't exist
if ! gh release view ${{ github.ref_name }} 2>/dev/null; then
if [ "$USE_GENERATED_NOTES" = "true" ]; then
# Fallback to auto-generated notes if changelog extraction failed
gh release create ${{ github.ref_name }} \
--title "Release ${{ github.ref_name }}" \
--draft \
--generate-notes
else
# Use extracted changelog content
gh release create ${{ github.ref_name }} \
--title "Release ${{ github.ref_name }}" \
--draft \
--notes-file release_notes.md
fi
fi
# Upload all build artifacts
for artifact in artifacts/build-*/rustnet-*; do
gh release upload ${{ github.ref_name }} "$artifact"
gh release upload ${{ github.ref_name }} "$artifact" --clobber
done
package-installers:
@@ -172,12 +225,13 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update -y
sudo apt-get install -y libpcap-dev libelf-dev clang llvm
sudo apt-get install -y libpcap-dev libelf-dev zlib1g-dev clang llvm pkg-config
- name: Install Rust and tools
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu,armv7-unknown-linux-gnueabihf
components: rustfmt
- name: Install packaging tools
run: |
@@ -193,11 +247,17 @@ jobs:
armhf) target="armv7-unknown-linux-gnueabihf" ;;
esac
# Extract binary from artifact
# Extract binary and assets from artifact
tar -xzf "artifacts/build-$target/rustnet-${{ github.ref_name }}-$target.tar.gz"
mkdir -p "target/$target/release"
cp "rustnet-${{ github.ref_name }}-$target/rustnet" "target/$target/release/"
# Ensure services file exists for packaging
mkdir -p assets
if [ -f "rustnet-${{ github.ref_name }}-$target/assets/services" ]; then
cp "rustnet-${{ github.ref_name }}-$target/assets/services" assets/
fi
# Create deb package
cargo deb --no-build --no-strip --target $target
mv "target/$target/debian"/*.deb "installers/Rustnet_LinuxDEB_$arch.deb"
@@ -211,11 +271,17 @@ jobs:
for arch in x86_64 aarch64; do
target="$arch-unknown-linux-gnu"
# Extract binary from artifact
# Extract binary and assets from artifact
tar -xzf "artifacts/build-$target/rustnet-${{ github.ref_name }}-$target.tar.gz"
mkdir -p "target/$target/release"
cp "rustnet-${{ github.ref_name }}-$target/rustnet" "target/$target/release/"
# Ensure services file exists for packaging
mkdir -p assets
if [ -f "rustnet-${{ github.ref_name }}-$target/assets/services" ]; then
cp "rustnet-${{ github.ref_name }}-$target/assets/services" assets/
fi
# Fix library linking if needed
patchelf --replace-needed libpcap.so.0.8 libpcap.so.1 "target/$target/release/rustnet" 2>/dev/null || true
@@ -233,7 +299,7 @@ jobs:
run: |
# Upload all installer packages
for installer in installers/*; do
gh release upload ${{ github.ref_name }} "$installer"
gh release upload ${{ github.ref_name }} "$installer" --clobber
done
package-macos:
@@ -254,6 +320,7 @@ jobs:
- name: Install packaging tools
run: |
cargo install toml-cli
cargo install apple-codesign
brew install create-dmg
- name: Download build artifact
@@ -272,14 +339,66 @@ jobs:
sed -i'.bak' -e "s/0\.0\.0/${VERSION}/g" -e "s/fffffff/${GITHUB_SHA:0:7}/g" resources/packaging/macos/Info.plist
# Create app bundle
mkdir -p "Rustnet.app/Contents/"{MacOS,Resources}
mkdir -p "Rustnet.app/Contents/"{MacOS,Resources/assets}
cp resources/packaging/macos/Info.plist "Rustnet.app/Contents/"
cp resources/packaging/macos/graphics/rustnet.icns "Rustnet.app/Contents/Resources/"
cp "rustnet-${{ github.ref_name }}-${{ matrix.target }}/rustnet" "Rustnet.app/Contents/MacOS/"
cp resources/packaging/macos/wrapper.sh "Rustnet.app/Contents/MacOS/"
cp "rustnet-${{ github.ref_name }}-${{ matrix.target }}/assets/services" "Rustnet.app/Contents/Resources/assets/"
chmod +x "Rustnet.app/Contents/MacOS/"{rustnet,wrapper.sh}
# Create DMG
- name: Code sign and notarize app bundle
env:
APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
APPLE_API_ISSUER: ${{ secrets.APPLE_ID_ISSUER_ID }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_ID_KEY_ID }}
APPLE_API_KEY: ${{ secrets.APPLE_ID_KEY }}
run: |
# Skip signing if secrets are not configured
if [ -z "$APPLE_CERTIFICATE_P12" ]; then
echo "⚠️ Code signing secrets not configured. DMG will be unsigned."
echo "⚠️ Users will need to use 'Open Anyway' in System Settings > Privacy & Security"
echo "SKIP_SIGNING=true" >> $GITHUB_ENV
exit 0
fi
# Decode certificate and save to file
echo "$APPLE_CERTIFICATE_P12" | base64 --decode > certificate.p12
# Create temporary JSON for API credentials
cat > api-key.json <<EOF
{
"issuer": "$APPLE_API_ISSUER",
"key_id": "$APPLE_API_KEY_ID",
"private_key": "$APPLE_API_KEY"
}
EOF
# Sign the app bundle with hardened runtime
rcodesign sign \
--p12-file certificate.p12 \
--p12-password "$APPLE_CERTIFICATE_PASSWORD" \
--code-signature-flags runtime \
Rustnet.app
# Create zip for notarization (required format)
ditto -c -k --keepParent Rustnet.app Rustnet.zip
# Submit for notarization and wait for result
rcodesign notary-submit \
--api-key-path api-key.json \
--wait \
Rustnet.zip
# Staple the notarization ticket to the app
rcodesign staple Rustnet.app
# Cleanup sensitive files
rm -f certificate.p12 api-key.json Rustnet.zip
- name: Create DMG
run: |
create-dmg \
--volname "Rustnet Installer" \
--background "resources/packaging/macos/graphics/dmg_bg.png" \
@@ -296,7 +415,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload ${{ github.ref_name }} "Rustnet_macOS_${{ matrix.arch }}.dmg"
gh release upload ${{ github.ref_name }} "Rustnet_macOS_${{ matrix.arch }}.dmg" --clobber
package-windows:
name: package-windows
@@ -322,12 +441,12 @@ jobs:
-OutFile "$env:TEMP\wix-binaries.zip" -Verbose
Expand-Archive -LiteralPath "$env:TEMP\wix-binaries.zip" -DestinationPath "$env:TEMP\wix" -Verbose
# Add WiX to PATH for subsequent steps
# Add WiX to PATH for all subsequent steps
"$env:TEMP\wix" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
# Verify WiX installation
Write-Host "Verifying WiX installation..."
Get-ChildItem "$env:TEMP\wix" | Select-Object -First 5
Write-Host "WiX tools installed:"
Get-ChildItem "$env:TEMP\wix\*.exe" | Select-Object Name
Write-Host "::endgroup::"
- name: Install Rust and tools
@@ -339,8 +458,7 @@ jobs:
shell: bash
run: |
cargo install cargo-wix
echo "cargo-wix installed successfully"
cargo wix --version || echo "Warning: cargo-wix version check failed"
cargo wix --version
- name: Download build artifact
uses: actions/download-artifact@v4
@@ -352,40 +470,50 @@ jobs:
shell: powershell
run: |
# Extract binary and assets
Expand-Archive -LiteralPath "artifacts\rustnet-${{ github.ref_name }}-${{ matrix.target }}.zip" -DestinationPath .
Write-Host "Extracting artifact..."
Expand-Archive -LiteralPath "artifacts\rustnet-${{ github.ref_name }}-${{ matrix.target }}.zip" -DestinationPath . -Force
# Create target directory structure
New-Item -ItemType Directory -Path "target\${{ matrix.target }}\release" -Force
# Copy binary
Copy-Item -Path "rustnet-${{ github.ref_name }}-${{ matrix.target }}\rustnet.exe" -Destination "target\${{ matrix.target }}\release\" -Force
Write-Host "Binary copied to target directory"
# Copy assets if they exist
if (Test-Path "rustnet-${{ github.ref_name }}-${{ matrix.target }}\assets") {
Copy-Item -Path "rustnet-${{ github.ref_name }}-${{ matrix.target }}\assets" -Destination "." -Recurse -Force
Write-Host "Assets copied successfully"
# Copy services file if it exists
New-Item -ItemType Directory -Path "assets" -Force
if (Test-Path "rustnet-${{ github.ref_name }}-${{ matrix.target }}\assets\services") {
Copy-Item -Path "rustnet-${{ github.ref_name }}-${{ matrix.target }}\assets\services" -Destination "assets\" -Force
Write-Host "Services file copied successfully"
} else {
Write-Host "Warning: No assets directory found in artifact"
# Create empty assets directory to prevent cargo-wix from failing
New-Item -ItemType Directory -Path "assets" -Force
Write-Host "Warning: No services file found"
}
# Verify directory structure
Write-Host "Directory structure:"
Get-ChildItem -Path "target\${{ matrix.target }}\release" -Recurse | Select-Object FullName
# Setup WiX configuration (cargo-wix expects main.wxs in wix/ directory)
Write-Host "Setting up WiX configuration..."
New-Item -ItemType Directory -Path "wix" -Force
Copy-Item -Path "resources\packaging\windows\wix\main.wxs" -Destination "wix\main.wxs" -Force
Write-Host "WiX configuration copied"
# Verify candle.exe and light.exe are in PATH
Write-Host "Checking WiX tools..."
Get-Command candle.exe -ErrorAction SilentlyContinue | Select-Object Source
Get-Command light.exe -ErrorAction SilentlyContinue | Select-Object Source
# Create MSI package
Write-Host "Creating MSI package..."
cargo wix --no-build --nocapture --target ${{ matrix.target }}
# Find and rename the MSI file
$msiFile = Get-ChildItem -Path "target\wix" -Filter "*.msi" | Select-Object -First 1
if ($msiFile) {
$msiFiles = Get-ChildItem -Path "target\wix" -Filter "*.msi" -ErrorAction SilentlyContinue
if ($msiFiles) {
$msiFile = $msiFiles | Select-Object -First 1
Move-Item -Path $msiFile.FullName -Destination "Rustnet_Windows_${{ matrix.arch }}.msi" -Force
Write-Host "MSI package created: Rustnet_Windows_${{ matrix.arch }}.msi"
} else {
Write-Error "MSI file not found in target\wix directory"
Get-ChildItem -Path "target" -Recurse | Select-Object FullName
Write-Error "No MSI file found in target\wix\"
Write-Host "Contents of target directory:"
Get-ChildItem -Path "target" -Recurse -ErrorAction SilentlyContinue | Select-Object FullName
exit 1
}
@@ -393,4 +521,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload ${{ github.ref_name }} "Rustnet_Windows_${{ matrix.arch }}.msi"
gh release upload ${{ github.ref_name }} "Rustnet_Windows_${{ matrix.arch }}.msi" --clobber

7
Cargo.lock generated
View File

@@ -1721,7 +1721,6 @@ dependencies = [
"http_req",
"libbpf-cargo",
"libbpf-rs",
"libbpf-sys",
"libc",
"log",
"num_cpus",
@@ -1732,6 +1731,7 @@ dependencies = [
"ring",
"simple-logging",
"simplelog",
"vmlinux",
"zip",
]
@@ -2198,6 +2198,11 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vmlinux"
version = "0.0.0"
source = "git+https://github.com/libbpf/vmlinux.h.git?rev=83a228cf37fc65f2d14e4896a04922b5ee531a94#83a228cf37fc65f2d14e4896a04922b5ee531a94"
[[package]]
name = "vsprintf"
version = "2.0.0"

View File

@@ -44,7 +44,6 @@ aes = "0.8"
[target.'cfg(target_os = "linux")'.dependencies]
procfs = "0.16"
libbpf-rs = { version = "0.25", optional = true }
libbpf-sys = { version = "1.4", optional = true }
bytes = { version = "1.5", optional = true }
libc = { version = "0.2", optional = true }
@@ -60,10 +59,20 @@ zip = "2.1"
[target.'cfg(target_os = "linux")'.build-dependencies]
libbpf-cargo = "0.25"
vmlinux = { version = "0.0", git = "https://github.com/libbpf/vmlinux.h.git", rev = "83a228cf37fc65f2d14e4896a04922b5ee531a94" }
[features]
default = []
ebpf = ["libbpf-rs", "libbpf-sys", "bytes", "libc"]
linux-default = ["ebpf"]
ebpf = ["libbpf-rs", "bytes", "libc"]
# Minimal cross configuration to override dependency conflicts
[workspace.metadata.cross.build.env]
passthrough = [
"CARGO_INCREMENTAL",
"CARGO_NET_RETRY",
"CARGO_NET_TIMEOUT",
]
[package.metadata.deb]
maintainer = "domcyrus <domcyrus@example.com>"
@@ -124,3 +133,9 @@ libpcap = "*"
libelf = "*"
clang = "*"
llvm = "*"
[package.metadata.wix]
upgrade-guid = "455c823b-9665-43e0-baa4-bd0fcb762463"
path-guid = "d3a2452e-f04f-4d4f-becf-c3580f49f8fc"
license = false
eula = false

16
Cross.toml Normal file
View File

@@ -0,0 +1,16 @@
[target.aarch64-unknown-linux-gnu]
image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu:edge"
pre-build = [
"dpkg --add-architecture $CROSS_DEB_ARCH",
"apt-get update -y",
"apt-get install -y libelf-dev:arm64 zlib1g-dev:arm64 libpcap-dev:arm64 gcc-aarch64-linux-gnu protobuf-compiler libseccomp-dev:arm64 libbpf-dev clang llvm"
]
[target.armv7-unknown-linux-gnueabihf]
image = "ghcr.io/cross-rs/armv7-unknown-linux-gnueabihf:edge"
pre-build = [
"dpkg --add-architecture $CROSS_DEB_ARCH",
"apt-get update -y",
"apt-get install -y libelf-dev:armhf zlib1g-dev:armhf libpcap-dev:armhf gcc-arm-linux-gnueabihf protobuf-compiler libseccomp-dev:armhf libbpf-dev clang llvm"
]

100
README.md
View File

@@ -63,6 +63,106 @@ When built with the `ebpf` feature on Linux, RustNet uses kernel eBPF programs f
## Installation
### Installing from Release Packages
Pre-built packages are available for each release on the [GitHub Releases](https://github.com/domcyrus/rustnet/releases) page.
#### macOS DMG Installation
> **💡 Prefer Homebrew?** If you have Homebrew installed, using `brew install` is easier and avoids Gatekeeper bypass steps. See [Homebrew Installation](#option-3-homebrew-installation) for instructions.
1. **Download** the appropriate DMG for your architecture:
- `Rustnet_macOS_AppleSilicon.dmg` for Apple Silicon Macs (M1/M2/M3)
- `Rustnet_macOS_Intel.dmg` for Intel-based Macs
2. **Open the DMG** and drag Rustnet.app to your Applications folder
3. **Bypass Gatekeeper** (for unsigned builds):
- When you first try to open Rustnet, macOS will block it because the app is not signed
- Go to **System Settings → Privacy & Security**
- Scroll down to find the message about Rustnet being blocked
- Click **"Open Anyway"** to allow the application to run
- You may need to confirm this choice when launching the app again
4. **Run Rustnet**:
- Double-click Rustnet.app to launch it in a Terminal window with sudo
- Or run from command line: `sudo /Applications/Rustnet.app/Contents/MacOS/rustnet`
5. **Optional: Create a symlink for shell access**:
```bash
# Create a symlink so you can run 'rustnet' from anywhere
sudo ln -s /Applications/Rustnet.app/Contents/MacOS/rustnet /usr/local/bin/rustnet
# Now you can run from any terminal:
sudo rustnet
```
6. **Optional: Setup BPF permissions** (to avoid needing sudo):
- Install Wireshark's BPF permission helper: `brew install --cask wireshark-chmodbpf`
- Log out and back in for group changes to take effect
- See the [Permissions](#permissions) section for detailed setup instructions
#### Windows MSI Installation
1. **Install Npcap Runtime** (required for packet capture):
- Download from https://npcap.com/dist/
- Run the installer and select **"WinPcap API compatible mode"**
2. **Download and install** the appropriate MSI package:
- `Rustnet_Windows_64-bit.msi` for 64-bit Windows
- `Rustnet_Windows_32-bit.msi` for 32-bit Windows
3. **Run the installer** and follow the installation wizard
4. **Run Rustnet**:
- Open Command Prompt or PowerShell
- Run: `rustnet.exe`
- Note: Depending on your Npcap installation settings, you may or may not need Administrator privileges
#### Linux Package Installation
**Debian/Ubuntu (.deb packages):**
```bash
# Download the appropriate package for your architecture:
# - Rustnet_LinuxDEB_amd64.deb (x86_64)
# - Rustnet_LinuxDEB_arm64.deb (ARM64)
# - Rustnet_LinuxDEB_armhf.deb (ARMv7)
# Install the package
sudo dpkg -i Rustnet_LinuxDEB_amd64.deb
# Install dependencies if needed
sudo apt-get install -f
# Run with sudo
sudo rustnet
# Optional: Grant capabilities to run without sudo
sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/rustnet
rustnet
```
**RedHat/Fedora/CentOS (.rpm packages):**
```bash
# Download the appropriate package for your architecture:
# - Rustnet_LinuxRPM_x86_64.rpm
# - Rustnet_LinuxRPM_aarch64.rpm
# Install the package
sudo rpm -i Rustnet_LinuxRPM_x86_64.rpm
# Or with dnf/yum:
sudo dnf install Rustnet_LinuxRPM_x86_64.rpm
# Run with sudo
sudo rustnet
# Optional: Grant capabilities to run without sudo
sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/rustnet
rustnet
```
### Prerequisites
- Rust 2024 edition or later (install from [rustup.rs](https://rustup.rs/))

117
build.rs
View File

@@ -5,7 +5,10 @@ fn main() -> Result<()> {
// Generate shell completions and manpage
generate_assets()?;
// Only compile eBPF programs on Linux when the feature is enabled
// Add library search paths for cross-compilation
setup_cross_compilation_libs();
// Compile eBPF programs on Linux when the feature is enabled
if cfg!(target_os = "linux") && env::var("CARGO_FEATURE_EBPF").is_ok() {
compile_ebpf_programs();
}
@@ -21,6 +24,26 @@ fn main() -> Result<()> {
include!("src/cli.rs");
fn setup_cross_compilation_libs() {
let target = env::var("TARGET").unwrap_or_default();
match target.as_str() {
"aarch64-unknown-linux-gnu" => {
println!("cargo:rustc-link-search=native=/usr/lib/aarch64-linux-gnu");
println!("cargo:rustc-link-lib=elf");
println!("cargo:rustc-link-lib=z");
}
"armv7-unknown-linux-gnueabihf" => {
println!("cargo:rustc-link-search=native=/usr/lib/arm-linux-gnueabihf");
println!("cargo:rustc-link-lib=elf");
println!("cargo:rustc-link-lib=z");
}
_ => {
// For other targets, including native builds, let pkg-config handle it
}
}
}
fn generate_assets() -> Result<()> {
use clap::ValueEnum;
use clap_complete::Shell;
@@ -86,25 +109,34 @@ fn download_windows_npcap_sdk() -> Result<()> {
}
};
// extract DLL
let lib_path = if cfg!(target_arch = "aarch64") {
"Lib/ARM64/Packet.lib"
} else if cfg!(target_arch = "x86_64") {
"Lib/x64/Packet.lib"
} else if cfg!(target_arch = "x86") {
"Lib/Packet.lib"
// extract libraries based on target architecture
let target = env::var("TARGET").unwrap_or_else(|_| "unknown".to_string());
let (packet_lib_path, wpcap_lib_path) = if target.contains("aarch64") {
("Lib/ARM64/Packet.lib", "Lib/ARM64/wpcap.lib")
} else if target.contains("x86_64") {
("Lib/x64/Packet.lib", "Lib/x64/wpcap.lib")
} else if target.contains("i686") || target.contains("i586") {
("Lib/Packet.lib", "Lib/wpcap.lib")
} else {
panic!("Unsupported target!")
panic!("Unsupported target: {}", target)
};
let mut archive = zip::ZipArchive::new(io::Cursor::new(npcap_zip))?;
let mut npcap_lib = archive.by_name(lib_path)?;
// write DLL
let mut archive = zip::ZipArchive::new(io::Cursor::new(npcap_zip))?;
// Extract Packet.lib
let mut packet_lib = archive.by_name(packet_lib_path)?;
let lib_dir = PathBuf::from(env::var("OUT_DIR")?).join("npcap_sdk");
let lib_path = lib_dir.join("Packet.lib");
fs::create_dir_all(&lib_dir)?;
let mut lib_file = fs::File::create(lib_path)?;
io::copy(&mut npcap_lib, &mut lib_file)?;
let packet_lib_dest = lib_dir.join("Packet.lib");
let mut packet_file = fs::File::create(packet_lib_dest)?;
io::copy(&mut packet_lib, &mut packet_file)?;
drop(packet_lib);
// Extract wpcap.lib
let mut wpcap_lib = archive.by_name(wpcap_lib_path)?;
let wpcap_lib_dest = lib_dir.join("wpcap.lib");
let mut wpcap_file = fs::File::create(wpcap_lib_dest)?;
io::copy(&mut wpcap_lib, &mut wpcap_file)?;
println!(
"cargo:rustc-link-search=native={}",
@@ -119,6 +151,7 @@ fn download_windows_npcap_sdk() -> Result<()> {
#[cfg(all(target_os = "linux", feature = "ebpf"))]
fn compile_ebpf_programs() {
use libbpf_cargo::SkeletonBuilder;
use std::ffi::OsStr;
use std::path::PathBuf;
let mut out = PathBuf::from(env::var("OUT_DIR").unwrap());
@@ -128,46 +161,27 @@ fn compile_ebpf_programs() {
println!("cargo:warning=Building eBPF program using libbpf-cargo");
match SkeletonBuilder::new()
// Get target architecture for cross-compilation
let arch = env::var("CARGO_CFG_TARGET_ARCH")
.expect("CARGO_CFG_TARGET_ARCH must be set in build script");
// Map Rust arch names to eBPF target arch defines
let target_arch_define = match arch.as_str() {
"x86_64" => "-D__TARGET_ARCH_x86",
"aarch64" => "-D__TARGET_ARCH_arm64",
"arm" => "-D__TARGET_ARCH_arm",
_ => "-D__TARGET_ARCH_x86", // fallback
};
SkeletonBuilder::new()
.source(src)
.clang_args([
"-I/usr/include",
"-I/usr/include/linux",
"-I/usr/include/x86_64-linux-gnu",
"-D__TARGET_ARCH_x86",
OsStr::new("-I"),
vmlinux::include_path_root().join(&arch).as_os_str(),
OsStr::new(target_arch_define),
])
.build_and_generate(&out)
{
Ok(_) => {
println!("cargo:warning=eBPF skeleton generated successfully");
}
Err(e) => {
println!("cargo:warning=Failed to build eBPF program: {}", e);
// Create a placeholder skeleton file that compiles but returns None
let placeholder_skeleton = r#"
// Placeholder skeleton for failed compilation
#[allow(dead_code)]
pub mod socket_tracker {
use anyhow::Result;
pub struct SocketTrackerSkel;
impl SocketTrackerSkel {
pub fn open() -> Result<Self> {
Err(anyhow::anyhow!("eBPF compilation failed"))
}
}
}
"#;
std::fs::write(&out, placeholder_skeleton).unwrap_or_else(|e| {
println!(
"cargo:warning=Failed to create placeholder skeleton file: {}",
e
);
});
}
}
.unwrap();
println!("cargo:rerun-if-changed={}", src);
}
@@ -176,3 +190,4 @@ pub mod socket_tracker {
fn compile_ebpf_programs() {
// No-op when not on Linux or eBPF feature is not enabled
}

View File

@@ -2,6 +2,7 @@
# Wrapper script for Rustnet macOS app
# This script launches the Rustnet binary in a terminal
# Note: Rustnet requires sudo for packet capture. Run: sudo rustnet
# Get the directory where the app bundle is located
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
@@ -11,7 +12,7 @@ if [[ "$TERM_PROGRAM" == "Apple_Terminal" ]] || [[ "$TERM_PROGRAM" == "iTerm.app
# Already in terminal, just run the binary
exec "$DIR/rustnet" "$@"
else
# Not in terminal, open Terminal and run the command
# This will open a new Terminal window with rustnet running
osascript -e "tell application \"Terminal\" to do script \"cd '$DIR' && ./rustnet; exit\""
# Not in terminal, open Terminal and prompt for sudo
# This will open a new Terminal window with rustnet running with sudo
osascript -e "tell application \"Terminal\" to do script \"cd '$DIR' && sudo ./rustnet; exit\""
fi

View File

@@ -1,9 +1,19 @@
<?xml version='1.0' encoding='windows-1252'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
<!-- Define platform-specific variables based on target architecture -->
<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>
<Product
Id='*'
Name='Rustnet'
UpgradeCode='12345678-1234-1234-1234-123456789012'
UpgradeCode='455c823b-9665-43e0-baa4-bd0fcb762463'
Manufacturer='domcyrus'
Language='1033'
Codepage='1252'
@@ -13,37 +23,52 @@
Keywords='Installer'
Description='Rustnet Network Monitor Installer'
Manufacturer='domcyrus'
InstallerVersion='100'
InstallerVersion='500'
Languages='1033'
Compressed='yes'
SummaryCodepage='1252'
Platform='$(var.Platform)'
/>
<Media Id='1' Cabinet='Sample.cab' EmbedCab='yes' DiskPrompt="CD-ROM #1" />
<MajorUpgrade
DowngradeErrorMessage="A newer version of [ProductName] is already installed."
Schedule="afterInstallInitialize"
AllowSameVersionUpgrades="yes" />
<Media Id='1' Cabinet='rustnet.cab' EmbedCab='yes' DiskPrompt="CD-ROM #1" />
<Property Id='DiskPrompt' Value="Rustnet Installation [1]" />
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='ProgramFilesFolder'>
<Directory Id='$(var.PlatformProgramFilesFolder)'>
<Directory Id='APPLICATIONROOTDIRECTORY' Name='Rustnet'>
<Component Id='MainExecutable' Guid='*'>
<Component Id='MainExecutable' Guid='*' Win64='$(var.Win64)'>
<File Id='RustnetExe'
Name='rustnet.exe'
DiskId='1'
Source='$(var.CargoTargetBinDir)\rustnet.exe'
KeyPath='yes'/>
<Environment Id='PATH' Name='PATH' Value='[APPLICATIONROOTDIRECTORY]'
Permanent='no' Part='last' Action='set' System='yes' />
</Component>
<Component Id='AppIcon' Guid='*'>
<Component Id='AppIcon' Guid='*' Win64='$(var.Win64)'>
<File Id='RustnetIco'
Name='rustnet.ico'
DiskId='1'
Source='resources\packaging\windows\graphics\rustnet.ico'/>
</Component>
<Component Id='AssetsDir' Guid='*' Win64='$(var.Win64)'>
<File Id='ServicesFile'
Name='services'
DiskId='1'
Source='assets\services'
KeyPath='yes'/>
</Component>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ApplicationProgramsFolder" Name="Rustnet">
<Component Id="ApplicationShortcut" Guid="*">
<Component Id="ApplicationShortcut" Guid="*" Win64="$(var.Win64)">
<Shortcut Id="ApplicationStartMenuShortcut"
Name="Rustnet"
Description="Cross-platform network monitoring tool"
@@ -51,7 +76,7 @@
WorkingDirectory="APPLICATIONROOTDIRECTORY"
Icon="rustnet.ico"/>
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
<RegistryValue Root="HKCU" Key="Software\Microsoft\Rustnet" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
<RegistryValue Root="HKCU" Key="Software\domcyrus\Rustnet" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
</Component>
</Directory>
</Directory>
@@ -61,9 +86,13 @@
<Feature
Id='Complete'
Level='1'>
Title='Rustnet'
Description='Complete installation of Rustnet Network Monitor'
Level='1'
ConfigurableDirectory='APPLICATIONROOTDIRECTORY'>
<ComponentRef Id='MainExecutable' />
<ComponentRef Id='AppIcon' />
<ComponentRef Id='AssetsDir' />
<ComponentRef Id='ApplicationShortcut' />
</Feature>

View File

@@ -1,7 +1,7 @@
// Socket tracker eBPF program
// CO-RE (Compile Once - Run Everywhere) version using BTF
#include "vmlinux_min.h"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>