name: Build iperf3 Static Binaries on: workflow_dispatch: schedule: # check upstream for a new iperf3 release once a day - cron: '47 5 * * *' permissions: contents: write jobs: check-release: runs-on: ubuntu-latest outputs: latest-version: ${{ steps.get-version.outputs.version }} should-build: ${{ steps.check-version.outputs.should-build }} steps: - name: Get latest iperf3 release id: get-version run: | # Get the latest release tag from GitHub API (e.g. "3.19") LATEST_VERSION=$(curl -4 --retry 5 --retry-delay 2 --connect-timeout 15 -s https://api.github.com/repos/esnet/iperf/releases/latest | jq -r '.tag_name') echo "Latest iperf3 version: $LATEST_VERSION" if [ -z "$LATEST_VERSION" ] || [ "$LATEST_VERSION" = "null" ]; then echo "Failed to determine latest iperf3 version" exit 1 fi echo "version=$LATEST_VERSION" >> $GITHUB_OUTPUT - name: Check if version exists in releases id: check-version run: | VERSION="${{ steps.get-version.outputs.version }}" # Check if this version already exists in our releases if gh release view "iperf3-$VERSION" --repo ${{ github.repository }} >/dev/null 2>&1; then echo "Version $VERSION already exists in releases" echo "should-build=false" >> $GITHUB_OUTPUT else echo "Version $VERSION does not exist, should build" echo "should-build=true" >> $GITHUB_OUTPUT fi env: GH_TOKEN: ${{ github.token }} build: needs: check-release if: needs.check-release.outputs.should-build == 'true' runs-on: ubuntu-latest # allow the ARM 32-bit leg to fail without failing the workflow (iperf3 arm32 # builds are historically flaky; kept as a safety net for future versions) continue-on-error: ${{ matrix.arch == 'arm' }} strategy: fail-fast: false matrix: include: - arch: x64 cross: x86_64-linux-musl host: x86_64-linux-musl - arch: x86 cross: i686-linux-musl host: i686-linux-musl - arch: aarch64 cross: aarch64-linux-musl host: aarch64-linux-gnu - arch: arm cross: arm-linux-musleabihf host: arm-linux-gnueabihf steps: - name: Create compilation script run: | # Single script for all architectures using musl cross-compilation cat > compile-iperf3.sh << 'EOF' #!/bin/bash set -e # Activate Holy Build Box lib compilation environment source /hbb/activate set -x # remove obsolete CentOS repos cd /etc/yum.repos.d/ rm -f CentOS-Base.repo CentOS-SCLo-scl-rh.repo CentOS-SCLo-scl.repo CentOS-fasttrack.repo CentOS-x86_64-kernel.repo yum install -y yum-plugin-ovl # fix for docker overlay fs yum install -y xz # download musl cross compilation toolchain - mirrored as a release asset on # this repo since musl.cc blocks GitHub Actions IP ranges (musl.cc fallback # kept for local runs) cd ~ curl -fL -4 --retry 5 --retry-delay 2 --connect-timeout 15 "https://github.com/masonr/yet-another-bench-script/releases/download/toolchains/$CROSS-cross.tgz" -o "$CROSS-cross.tgz" \ || curl -fL -4 --retry 5 --retry-delay 2 --connect-timeout 15 "https://musl.cc/$CROSS-cross.tgz" -o "$CROSS-cross.tgz" tar xf "$CROSS-cross.tgz" # Activate Holy Build Box exe compilation environment source /hbb_exe/activate # download and compile iperf3 cd ~ curl -L -4 --retry 5 --retry-delay 2 --connect-timeout 15 "https://github.com/esnet/iperf/archive/$VERSION.tar.gz" -o "iperf.tar.gz" tar xf iperf.tar.gz cd iperf-* CC=/root/$CROSS-cross/bin/$CROSS-gcc ./configure --disable-shared --disable-profiling --build x86_64-pc-linux-gnu --host "$HOST" --with-openssl=no --enable-static-bin # remove libatomic.la so libtool links the static libatomic.a rather than # attempting to link libatomic.so into the static binary (breaks arm32 builds) rm -f /root/$CROSS-cross/$CROSS/lib/libatomic.la make # verify no external shared library links libcheck src/iperf3 # copy iperf3 binary to mounted dir cp src/iperf3 "/io/iperf3_$ARCH" EOF chmod +x compile-iperf3.sh - name: Compile iperf3 binary run: | # Use musl cross-compilation for all architectures docker run --rm -v $(pwd):/io --env ARCH=${{ matrix.arch }} --env CROSS=${{ matrix.cross }} --env HOST=${{ matrix.host }} --env VERSION="${{ needs.check-release.outputs.latest-version }}" phusion/holy-build-box-64:latest bash /io/compile-iperf3.sh - name: Verify binary run: | ls -la iperf3_${{ matrix.arch }} file iperf3_${{ matrix.arch }} - name: Upload binary as artifact uses: actions/upload-artifact@v4 with: name: iperf3_${{ matrix.arch }} path: iperf3_${{ matrix.arch }} retention-days: 1 virustotal-scan: needs: [check-release, build] if: needs.check-release.outputs.should-build == 'true' runs-on: ubuntu-latest outputs: scan-results: ${{ steps.scan-summary.outputs.results }} all-clean: ${{ steps.scan-summary.outputs.all-clean }} steps: - name: Download all artifacts uses: actions/download-artifact@v4 with: path: artifacts continue-on-error: true - name: Prepare binaries for scanning run: | mkdir -p scan-binaries find artifacts -name "iperf3_*" -type f -exec cp {} scan-binaries/ \; || true ls -la scan-binaries/ # Check if we have at least the x64 binary if [ ! -f "scan-binaries/iperf3_x64" ]; then echo "Error: No iperf3_x64 binary found" exit 1 fi - name: Upload binaries to VirusTotal and scan id: virustotal-upload run: | echo "## VirusTotal Scan Results" > scan_results.md echo "| Binary | Status | Malicious | Suspicious | Undetected | VirusTotal URL |" >> scan_results.md echo "|--------|--------|-----------|------------|------------|----------------|" >> scan_results.md ALL_CLEAN=true for binary in scan-binaries/iperf3_*; do filename=$(basename "$binary") echo "Uploading $filename to VirusTotal..." # Upload to VirusTotal upload_response=$(curl -4 --retry 5 --retry-delay 2 --connect-timeout 15 -s --request POST \ --url https://www.virustotal.com/api/v3/files \ --header 'accept: application/json' \ --header 'content-type: multipart/form-data' \ --header "x-apikey: ${{ secrets.VIRUSTOTAL_API_KEY }}" \ --form "file=@$binary") analysis_id=$(echo "$upload_response" | jq -r '.data.id') echo "Analysis ID for $filename: $analysis_id" if [ "$analysis_id" = "null" ] || [ -z "$analysis_id" ]; then echo "Failed to upload $filename to VirusTotal" echo "| $filename | Upload Failed | N/A | N/A | N/A | N/A |" >> scan_results.md ALL_CLEAN=false continue fi # Store analysis ID for later retrieval echo "${filename}:${analysis_id}" >> analysis_ids.txt done - name: Retrieve VirusTotal scan results id: get-results run: | ALL_CLEAN=true # Poll each analysis until it completes; VirusTotal scans can take a few # minutes after upload, so allow up to ~5 minutes per binary (10 polls, # 30s apart) before giving up. Files are processed sequentially, so by # the time the first completes the rest are usually already done. while IFS=':' read -r filename analysis_id; do echo "Retrieving results for $filename (ID: $analysis_id)..." status="queued" for ((attempts=1; attempts<=10; attempts++)); do # Get scan results result_response=$(curl -4 --retry 5 --retry-delay 2 --connect-timeout 15 -s --request GET \ --url "https://www.virustotal.com/api/v3/analyses/$analysis_id" \ --header 'accept: application/json' \ --header "x-apikey: ${{ secrets.VIRUSTOTAL_API_KEY }}") status=$(echo "$result_response" | jq -r '.data.attributes.status // "unknown"') [ "$status" = "completed" ] && break echo "$filename still scanning (attempt $attempts of 10), waiting 30s..." sleep 30 done if [ "$status" = "completed" ]; then malicious=$(echo "$result_response" | jq -r '.data.attributes.stats.malicious // 0') suspicious=$(echo "$result_response" | jq -r '.data.attributes.stats.suspicious // 0') undetected=$(echo "$result_response" | jq -r '.data.attributes.stats.undetected // 0') # Get file hash from the item link item_link=$(echo "$result_response" | jq -r '.data.links.item // ""') file_hash=$(echo "$item_link" | sed 's/.*files\///') vt_url="https://www.virustotal.com/gui/file/$file_hash" if [ "$malicious" -gt 0 ] || [ "$suspicious" -gt 0 ]; then status_text="⚠️ FLAGGED" ALL_CLEAN=false else status_text="✅ Clean" fi echo "| $filename | $status_text | $malicious | $suspicious | $undetected | [$file_hash]($vt_url) |" >> scan_results.md else echo "| $filename | ⏳ Timed out | N/A | N/A | N/A | Scan did not complete in ~5 min |" >> scan_results.md ALL_CLEAN=false fi done < analysis_ids.txt echo "all_clean=$ALL_CLEAN" >> $GITHUB_OUTPUT # Add summary to GitHub step summary cat scan_results.md >> $GITHUB_STEP_SUMMARY - name: Create scan summary id: scan-summary run: | ALL_CLEAN="${{ steps.get-results.outputs.all_clean }}" # Pass the scan results through as a multi-line output (heredoc delimiter # syntax) so the markdown table renders correctly in the release body { echo "results<> $GITHUB_OUTPUT echo "all-clean=$ALL_CLEAN" >> $GITHUB_OUTPUT - name: Upload scan results as artifact uses: actions/upload-artifact@v4 with: name: virustotal-scan-results path: | scan_results.md analysis_ids.txt retention-days: 30 create-release: needs: [check-release, build, virustotal-scan] if: needs.check-release.outputs.should-build == 'true' && needs.virustotal-scan.outputs.all-clean == 'true' runs-on: ubuntu-latest steps: - name: Download all artifacts uses: actions/download-artifact@v4 with: path: artifacts # Continue even if some artifacts (like arm) failed continue-on-error: true - name: Prepare release assets run: | mkdir -p release-assets # Move all binaries to release-assets directory find artifacts -name "iperf3_*" -type f -exec cp {} release-assets/ \; || true ls -la release-assets/ # Check if we have at least the x64 binary if [ ! -f "release-assets/iperf3_x64" ]; then echo "Error: No iperf3_x64 binary found" exit 1 fi - name: Download upstream source tarball run: | # Attach the exact upstream source these binaries were built from # (iperf3 is BSD-3-Clause; retain the copyright notice with distribution) curl -L --retry 5 --retry-delay 2 --connect-timeout 15 "https://github.com/esnet/iperf/archive/${{ needs.check-release.outputs.latest-version }}.tar.gz" -o "release-assets/iperf-${{ needs.check-release.outputs.latest-version }}.tar.gz" ls -la release-assets/ - name: Generate SHA256 checksums run: | cd release-assets if ls iperf3_* 1> /dev/null 2>&1; then sha256sum iperf3_* > iperf3-checksums.sha256 cat iperf3-checksums.sha256 else echo "No iperf3 binaries found to checksum" exit 1 fi - name: Check for ARM binary and add note id: check-arm run: | if [ -f "release-assets/iperf3_arm" ]; then echo "arm-note=" >> $GITHUB_OUTPUT else echo "arm-note=**Note:** ARM 32-bit binary compilation failed (known issue with iperf3 versions >3.15). Other architectures compiled successfully." >> $GITHUB_OUTPUT fi - name: Download VirusTotal scan results uses: actions/download-artifact@v4 with: name: virustotal-scan-results path: vt-results - name: Create Release uses: softprops/action-gh-release@v2 with: tag_name: iperf3-${{ needs.check-release.outputs.latest-version }} name: iperf3 ${{ needs.check-release.outputs.latest-version }} Static Binaries body: | Static binaries for iperf3 ${{ needs.check-release.outputs.latest-version }} Built using musl toolchains for maximum compatibility. **Architectures:** - `iperf3_x64` - x86_64 (64-bit) - `iperf3_x86` - i686 (32-bit) - `iperf3_aarch64` - ARM 64-bit - `iperf3_arm` - ARM 32-bit (if available) ${{ steps.check-arm.outputs.arm-note }} **Security Verification:** All binaries have been scanned by VirusTotal and verified clean. ${{ needs.virustotal-scan.outputs.scan-results }} **Checksum Verification:** ```bash # Verify checksums sha256sum -c iperf3-checksums.sha256 ``` **License & Source:** iperf3 is licensed under [BSD-3-Clause](https://github.com/esnet/iperf/blob/master/LICENSE) (© ESnet / LBL). These are unofficial static builds produced by CI for use by [YABS](https://github.com/masonr/yet-another-bench-script); the exact upstream source is attached as `iperf-${{ needs.check-release.outputs.latest-version }}.tar.gz`. files: | release-assets/iperf3_* release-assets/iperf3-checksums.sha256 release-assets/*.tar.gz draft: false prerelease: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} notify-failure: needs: [check-release, build, virustotal-scan] if: always() && needs.check-release.outputs.should-build == 'true' && (failure() || needs.virustotal-scan.outputs.all-clean == 'false') runs-on: ubuntu-latest steps: - name: Send failure notification run: | echo "## ⚠️ iperf3 Build Failed" >> $GITHUB_STEP_SUMMARY echo "Version: ${{ needs.check-release.outputs.latest-version }}" >> $GITHUB_STEP_SUMMARY if [ "${{ needs.virustotal-scan.outputs.all-clean }}" == "false" ]; then echo "**Reason:** VirusTotal scan detected issues with one or more binaries" >> $GITHUB_STEP_SUMMARY echo "**Scan Results:**" >> $GITHUB_STEP_SUMMARY echo "${{ needs.virustotal-scan.outputs.scan-results }}" >> $GITHUB_STEP_SUMMARY else echo "**Reason:** Build compilation failed" >> $GITHUB_STEP_SUMMARY fi echo "Check the workflow logs for details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> $GITHUB_STEP_SUMMARY