Skip to content
Open
36 changes: 32 additions & 4 deletions .github/actions/check-vm/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ inputs:
codecov-token:
description: 'Codecov token, if Codecov upload is desired.'
default: ''
generate-bindings:
description: 'Generate and output bindgen bindings for BSD platforms.'
default: 'false'

runs:
using: composite
Expand All @@ -21,18 +24,19 @@ runs:
WD: ${{ inputs.working-directory }}
PLATFORM: ${{ inputs.platform }}
WORKSPACE: ${{ inputs.working-directory == '.' && '--workspace' || '' }}
GENERATE_BINDINGS: ${{ inputs.generate-bindings }}
run: |
cat <<EOF > prepare.sh
# This executes as root
set -ex
pwd
case "$PLATFORM" in
freebsd) pkg install -y curl llvm nss pkgconf
freebsd) pkg install -y curl llvm nss pkgconf rust-bindgen-cli
;;
openbsd) # TODO: Is there a way to not pin the version of llvm? -z to pkg_add does not work.
pkg_add rust rust-clippy rust-rustfmt llvm-21.1.2p0 nss # rustup does not support OpenBSD at all
pkg_add rust rust-clippy rust-rustfmt rust-bindgen llvm-21.1.2p0 nss # rustup does not support OpenBSD at all
;;
netbsd) /usr/sbin/pkg_add pkgin && pkgin -y install curl clang nss pkgconf
netbsd) /usr/sbin/pkg_add pkgin && pkgin -y install curl clang nss pkgconf rust-bindgen
;;
solaris) pkg install clang-libs nss pkg-config
;;
Expand All @@ -59,6 +63,7 @@ runs:
openbsd) export LIBCLANG_PATH=/usr/local/llvm21/lib
export LLVM_COV=/usr/local/llvm21/bin/llvm-cov
export LLVM_PROFDATA=/usr/local/llvm21/bin/llvm-profdata
export PATH="\$HOME/.cargo/bin:\$PATH"
[ "$WORKSPACE" ] && EXCLUDE="--exclude fuzz" # Fuzzing not supported on OpenBSD
;;
netbsd) sh rustup.sh --default-toolchain stable --profile minimal --component clippy,llvm-tools,rustfmt -y
Expand All @@ -75,6 +80,23 @@ runs:
[ "$WORKSPACE" ] && EXCLUDE="--exclude fuzz" # Fuzzing not supported on Solaris
;;
esac
# Generate bindings first if requested (before build, so we can bootstrap)
if [ "$GENERATE_BINDINGS" = "true" ]; then
# Solaris doesn't have a system package for bindgen
[ "$PLATFORM" = "solaris" ] && cargo install bindgen-cli --locked
bindgen --allowlist-type 'rt_msghdr|rt_metrics|if_data' \
--allowlist-item 'RTAX_MAX|RTM_GET|RTM_VERSION|RTA_DST|RTA_IFP' \
--generate-cstr --explicit-padding --with-derive-default \
src/bindings/bsd.h > "$PLATFORM.rs"
# Compare generated bindings with committed bindings
# If different, exit early with success to trigger copyback
if ! diff -q "src/bindings/$PLATFORM.rs" "$PLATFORM.rs" > /dev/null 2>&1; then
echo "::warning::Bindings for $PLATFORM differ from committed version"
touch bindings-changed
exit 0
fi
fi

cargo version
cargo check --locked --all-targets $WORKSPACE \$EXCLUDE
case "$PLATFORM" in
Expand All @@ -93,6 +115,7 @@ runs:
;;
esac
cargo test --locked --no-fail-fast --release

rm -rf target # Do not sync this back to host
EOF
{
Expand All @@ -102,12 +125,13 @@ runs:
} >> "$GITHUB_OUTPUT"

curl -o "$WD/rustup.sh" --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs
echo "envs=CARGO_TERM_COLOR RUST_BACKTRACE RUST_LOG RUST_TEST_TIME_UNIT RUST_TEST_TIME_INTEGRATION RUST_TEST_TIME_DOCTEST WD" >> "$GITHUB_OUTPUT"
echo "envs=CARGO_TERM_COLOR RUST_BACKTRACE RUST_LOG RUST_TEST_TIME_UNIT RUST_TEST_TIME_INTEGRATION RUST_TEST_TIME_DOCTEST WD GENERATE_BINDINGS" >> "$GITHUB_OUTPUT"

- if: ${{ inputs.platform == 'freebsd' }}
uses: vmactions/freebsd-vm@a9c0dcaf5ed572d89ea1a59fe2217d3b3da4fd23 # v1.3.7
with:
usesh: true
copyback: true
envs: ${{ steps.prep.outputs.envs }}
prepare: ${{ steps.prep.outputs.prepare }}
run: ${{ steps.prep.outputs.run }}
Expand All @@ -116,6 +140,7 @@ runs:
uses: vmactions/openbsd-vm@00753f2835e62704570734312de6f212069dfef7 # v1.3.1
with:
usesh: true
copyback: true
envs: ${{ steps.prep.outputs.envs }}
prepare: ${{ steps.prep.outputs.prepare }}
run: ${{ steps.prep.outputs.run }}
Expand All @@ -124,6 +149,7 @@ runs:
uses: vmactions/netbsd-vm@2ba9902c77c2ebdb7501e5e9308dea03f0f251c4 # v1.3.1
with:
usesh: true
copyback: true
envs: ${{ steps.prep.outputs.envs }}
prepare: ${{ steps.prep.outputs.prepare }}
run: ${{ steps.prep.outputs.run }}
Expand All @@ -133,11 +159,13 @@ runs:
with:
release: "11.4-gcc"
usesh: true
copyback: true
envs: ${{ steps.prep.outputs.envs }}
prepare: ${{ steps.prep.outputs.prepare }}
run: ${{ steps.prep.outputs.run }}

- id: check-coverage
if: ${{ always() && inputs.generate-bindings == 'true' }}
shell: bash
env:
WORKING_DIR: ${{ inputs.working-directory }}
Expand Down
115 changes: 115 additions & 0 deletions .github/workflows/check-mtu.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
name: CI MTU
on:
workflow_dispatch:
push:
branches: ["main"]
paths:
- "mtu/**"
- ".github/workflows/check-mtu.yml"
- ".github/actions/check-vm/**"
pull_request:
branches: ["main"]

Expand All @@ -12,6 +18,41 @@ permissions:
contents: read

jobs:
generate-bindings:
name: Generate ${{ matrix.os }} bindings
strategy:
fail-fast: false
matrix:
include:
- os: linux
runner: ubuntu-24.04
header: linux.h
args: --allowlist-type rtattr|rtmsg|ifinfomsg|nlmsghdr
- os: macos
runner: macos-15
header: bsd.h
args: --allowlist-type rt_msghdr|rt_metrics|if_data --allowlist-item RTAX_MAX|RTM_GET|RTM_VERSION|RTA_DST|RTA_IFP
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Install bindgen-cli
run: cargo install bindgen-cli --locked
- name: Generate bindings
env:
OS: ${{ matrix.os }}
ARGS: ${{ matrix.args }}
HEADER: ${{ matrix.header }}
run: |
# shellcheck disable=SC2086
bindgen $ARGS --generate-cstr --explicit-padding --with-derive-default "mtu/src/bindings/$HEADER" > "$OS.rs"
- name: Upload bindings
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: bindings-${{ matrix.os }}
path: ${{ matrix.os }}.rs

check-android:
name: Check Android
runs-on: ubuntu-24.04
Expand Down Expand Up @@ -44,4 +85,78 @@ jobs:
working-directory: mtu
platform: ${{ matrix.os }}
codecov-token: ${{ secrets.CODECOV_TOKEN }}
generate-bindings: true

check-bindings:
name: Check bindings
needs: [generate-bindings, check-vm]
if: always() && !cancelled()
runs-on: ubuntu-24.04
permissions:
pull-requests: write # to create PRs for binding updates
contents: write # to push branches for binding updates
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: true # zizmor: ignore[artipacked] We need to push branches.

- name: Download all binding artifacts
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
path: artifacts
pattern: bindings-*

- name: Check for binding changes
id: check
run: |
CHANGED=""
for dir in artifacts/bindings-*; do
[ -d "$dir" ] || continue
PLATFORM=$(basename "$dir" | sed 's/bindings-//')
FILE="$dir/$PLATFORM.rs"
[ -f "$FILE" ] || continue

if ! diff -q "mtu/src/bindings/$PLATFORM.rs" "$FILE" > /dev/null 2>&1; then
echo "Bindings for $PLATFORM differ:"
diff "mtu/src/bindings/$PLATFORM.rs" "$FILE" || true
cp "$FILE" "mtu/src/bindings/$PLATFORM.rs"
CHANGED="$CHANGED $PLATFORM"
fi
done

if [ -z "$CHANGED" ]; then
echo "No binding changes detected."
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "platforms=$CHANGED" >> "$GITHUB_OUTPUT"
fi

- name: Create PR for binding updates
if: steps.check.outputs.changed == 'true' && github.event_name == 'push'
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SHA: ${{ github.sha }}
PLATFORMS: ${{ steps.check.outputs.platforms }}
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
echo "$TOKEN" | gh auth login --with-token
SHA_SHORT=$(echo "$SHA" | cut -c1-7)
BRANCH="chore-update-mtu-bindings-$SHA_SHORT"
MESSAGE="chore: Update MTU bindings

Automated update of platform-specific bindings generated by bindgen.

Updated platforms:$PLATFORMS"
git checkout -b "$BRANCH"
git add mtu/src/bindings
git commit -m "$MESSAGE"
git push --set-upstream origin "$BRANCH"
gh pr create --fill-verbose

- name: Fail if bindings changed on PR
if: steps.check.outputs.changed == 'true' && github.event_name == 'pull_request'
run: |
echo "::error::Generated bindings differ from committed versions. See job output for details."
exit 1
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions mtu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@ windows = { workspace = true, features = [
[build-dependencies]
cfg_aliases = { version = "0.2", default-features = false }
mozbuild = { version = "0.1", default-features = false, optional = true }
bindgen = { version = "0.72", default-features = false, features = ["runtime"] }

[package.metadata.cargo-machete]
ignored = ["bindgen", "cfg_aliases"]
ignored = ["cfg_aliases"]

[features]
gecko = ["dep:mozbuild"]
Expand Down
87 changes: 0 additions & 87 deletions mtu/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,92 +4,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![expect(clippy::unwrap_used, reason = "OK in build scripts.")]

use std::env;

const BINDINGS: &str = "bindings.rs";

#[cfg(feature = "gecko")]
fn clang_args() -> Vec<String> {
use mozbuild::{TOPOBJDIR, config::BINDGEN_SYSTEM_FLAGS};

let mut flags: Vec<String> = BINDGEN_SYSTEM_FLAGS.iter().map(|s| s.to_string()).collect();

flags.push(String::from("-include"));
flags.push(
TOPOBJDIR
.join("dist")
.join("include")
.join("mozilla-config.h")
.to_str()
.unwrap()
.to_string(),
);
flags
}

#[cfg(not(feature = "gecko"))]
const fn clang_args() -> Vec<String> {
Vec::new()
}

fn bindgen() {
let target_os = env::var("CARGO_CFG_TARGET_OS").expect("CARGO_CFG_TARGET_OS was not set");

// Platforms currently not supported.
//
// See <https://github.com/mozilla/mtu/issues/82>.
if matches!(target_os.as_str(), "ios" | "tvos" | "visionos") {
return;
}

if target_os == "windows" {
return;
}

let bindings = if matches!(target_os.as_str(), "linux" | "android") {
bindgen::Builder::default()
.header_contents("rtnetlink.h", "#include <linux/rtnetlink.h>")
// Only generate bindings for the following types
.allowlist_type("rtattr|rtmsg|ifinfomsg|nlmsghdr")
} else {
bindgen::Builder::default()
.header_contents(
"route.h",
"#include <sys/types.h>\n#include <sys/socket.h>\n#include <net/route.h>\n#include <net/if.h>",
)
// Only generate bindings for the following types and items
.allowlist_type("rt_msghdr|rt_metrics|if_data")
.allowlist_item("RTAX_MAX|RTM_GET|RTM_VERSION|RTA_DST|RTA_IFP")
};

let bindings = bindings
.clang_args(clang_args())
// Tell cargo to invalidate the built crate whenever any of the
// included header files changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
// Constants should be generated as &CStr instead of &[u8].
.generate_cstr(true)
// Always emit explicit padding fields.
.explicit_padding(true)
// Default trait should be derived when possible
.derive_default(true)
// Finish the builder and generate the bindings.
.generate()
// Unwrap the Result and panic on failure.
.expect("Unable to generate bindings");

// Write the bindings to the $OUT_DIR/$BINDINGS file.
let out_path = std::path::PathBuf::from(env::var("OUT_DIR").unwrap()).join(BINDINGS);
bindings
.write_to_file(out_path.clone())
.expect("Couldn't write bindings!");
println!("cargo:rustc-env=BINDINGS={}", out_path.display());
}

fn main() {
// Setup cfg aliases
cfg_aliases::cfg_aliases! {
bsd: {
any(
Expand All @@ -100,6 +15,4 @@ fn main() {
)
}
}

bindgen();
}
9 changes: 9 additions & 0 deletions mtu/src/bindings/bsd.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <net/route.h>
#include <net/if.h>

/* Force bindgen to generate these types */
struct rt_msghdr __rt_msghdr;
struct rt_metrics __rt_metrics;
struct if_data __if_data;
Loading
Loading