From fff9e3fbcb856b198a61a62e5f91ea08ee60b213 Mon Sep 17 00:00:00 2001 From: Christian Hopps Date: Sun, 22 Sep 2024 00:46:48 -0400 Subject: [PATCH] rustbind: capture rust binary based daemon skeleton code work Signed-off-by: Christian Hopps --- lib/libfrr.h | 1 + rustbind/.cargo/config.toml | 2 + rustbind/.gitignore | 1 + rustbind/Cargo.toml.in | 19 ++++++++ rustbind/Makefile | 10 ++++ rustbind/README.org | 22 +++++++++ rustbind/build.rs.in | 95 +++++++++++++++++++++++++++++++++++++ rustbind/rustbin_init.c | 53 +++++++++++++++++++++ rustbind/rustbin_main.rs | 79 ++++++++++++++++++++++++++++++ rustbind/subdir.am | 34 +++++++++++++ rustbind/wrapper.h.in | 21 ++++++++ 11 files changed, 337 insertions(+) create mode 100644 rustbind/.cargo/config.toml create mode 100644 rustbind/.gitignore create mode 100644 rustbind/Cargo.toml.in create mode 100644 rustbind/Makefile create mode 100644 rustbind/README.org create mode 100644 rustbind/build.rs.in create mode 100644 rustbind/rustbin_init.c create mode 100644 rustbind/rustbin_main.rs create mode 100644 rustbind/subdir.am create mode 100644 rustbind/wrapper.h.in diff --git a/lib/libfrr.h b/lib/libfrr.h index df537e2e3b..c08374811c 100644 --- a/lib/libfrr.h +++ b/lib/libfrr.h @@ -101,6 +101,7 @@ DECLARE_DLIST(log_args, struct log_arg, itm); #define PATH_VTY_PORT 2621 #define PIM6D_VTY_PORT 2622 #define MGMTD_VTY_PORT 2623 +#define RUSTBIND_VTY_PORT 2624 /* Registry of daemons' port defaults */ enum frr_cli_mode { diff --git a/rustbind/.cargo/config.toml b/rustbind/.cargo/config.toml new file mode 100644 index 0000000000..0ac4faf01e --- /dev/null +++ b/rustbind/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target-dir = "../build/rustd/target" diff --git a/rustbind/.gitignore b/rustbind/.gitignore new file mode 100644 index 0000000000..5a44eef09a --- /dev/null +++ b/rustbind/.gitignore @@ -0,0 +1 @@ +/Cargo.lock diff --git a/rustbind/Cargo.toml.in b/rustbind/Cargo.toml.in new file mode 100644 index 0000000000..dfa4defb0f --- /dev/null +++ b/rustbind/Cargo.toml.in @@ -0,0 +1,19 @@ +[package] +name = "rustbind" +version = "0.1.0" +edition = "2021" +default-run = "rustbind" + +[[bin]] +name = "rustbind" +test = false +bench = false +path = "@srcdir@/rustbin_main.rs" + +[dependencies] +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"]} + +[build-dependencies] +bindgen = "0.70.1" +cc = "1.1.18" diff --git a/rustbind/Makefile b/rustbind/Makefile new file mode 100644 index 0000000000..dce0d2daaf --- /dev/null +++ b/rustbind/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. rustbind/rustbind +%: ALWAYS + @$(MAKE) -s -C .. rustbind/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/rustbind/README.org b/rustbind/README.org new file mode 100644 index 0000000000..b41c0c6ff0 --- /dev/null +++ b/rustbind/README.org @@ -0,0 +1,22 @@ +* Rust Binary Daemon Skeleton + +This is the skeleton for a native rust based FRR daemon. The daemon is built as +a rust binary with bindgen access to FRR infra. + +** Development +*** Editor/LSP support + +In order for LSP support to work you will likely need to do a couple things if +you use a build directory. + +- Create 2 symlinks from the generated files in ~BUILDDIR/rustbind~ for + `build.rs` and `wrapper.h` e.g. if your build dir is in `frr/build`: + + #+begin_src bash + cd frr/rustbind + ln -s ../build/rustbind/build.rs . + ln -s ../build/rustbind/wrapper.h . + #+end_src + +- Copy the created `Cargo.toml` from BUILDDIR and modify the path value to be + `rustbin_main.rs` (i.e., no path prefix). diff --git a/rustbind/build.rs.in b/rustbind/build.rs.in new file mode 100644 index 0000000000..cf2222e08a --- /dev/null +++ b/rustbind/build.rs.in @@ -0,0 +1,95 @@ +extern crate bindgen; +// extern crate gcc; + +use std::env; +use std::path::PathBuf; + +use bindgen::callbacks::{MacroParsingBehavior, ParseCallbacks}; +use std::sync::{Arc, RwLock}; + +use std::collections::HashSet; + +// +// This complexity is need to ignore a #define in netdb.h that is shadowing an +// anonymous enum in netinet/in.h +// +#[derive(Debug)] +struct MacroCallback { + macros: Arc>>, +} + +impl ParseCallbacks for MacroCallback { + fn will_parse_macro(&self, name: &str) -> MacroParsingBehavior { + self.macros.write().unwrap().insert(name.into()); + + if name == "IPPORT_RESERVED" { + return MacroParsingBehavior::Ignore; + } + + MacroParsingBehavior::Default + } +} + +fn main() { + // Tell cargo to look for shared libraries in the specified directory + println!("cargo:rustc-link-search=@abs_top_builddir@/lib/.libs/libfrr.so"); + + // Tell cargo to tell rustc to link libfrr shared library. + println!("cargo:rustc-link-lib=frr"); + + // Tell cargo to invalidate the built crate whenever the wrapper changes + // println!("cargo:rerun-if-changed=@abs_srcdir@/wrapper.h"); + println!("cargo:rerun-if-changed=wrapper.h"); + + let macros = Arc::new(RwLock::new(HashSet::new())); + + // The bindgen::Builder is the main entry point + // to bindgen, and lets you build up options for + // the resulting bindings. + let bindings = bindgen::Builder::default() + .clang_args(&[ + "-I@abs_top_builddir@", // need to get this dynamically + "-I@abs_top_srcdir@", + "-I@abs_top_srcdir@/lib", + "-DHAVE_CONFIG_H", + "-D_ATOMIC_WANT_TYPEDEFS", + ]) + // The input header we would like to generate + // bindings for. + // .header("@abs_srcdir@/wrapper.h") + .header("wrapper.h") + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .parse_callbacks(Box::new(MacroCallback { + macros: macros.clone(), + })) + // Finish the builder and generate the bindings. + .blocklist_type("IPPORT_.*") + .blocklist_type("IPPORT_RESERVED") + // avoid creating bindings for things with u128 which doesn't yet have a + // stable FFI, for now. + + .blocklist_function("lyd_eval_xpath4") + .blocklist_function("q[efg]cvt.*") + .blocklist_function("strto.*") + .blocklist_function("strfrom.*") + + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); + + cc::Build::new() + .include("@abs_top_builddir@") + .include("@abs_top_srcdir@") + .include("@abs_top_srcdir@/lib") + .define("HAVE_CONFIG_H", None) + .file("@abs_srcdir@/rustbin_init.c") + .compile("c_init_code"); +} diff --git a/rustbind/rustbin_init.c b/rustbind/rustbin_init.c new file mode 100644 index 0000000000..fa1dc74e32 --- /dev/null +++ b/rustbind/rustbin_init.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * September 9 2024, Christian Hopps + * + * Copyright (c) 2024, LabN Consulting, L.L.C. + */ + +#include +#include +#include +#include + +zebra_capabilities_t _caps_p[] = {ZCAP_NET_RAW, ZCAP_BIND, ZCAP_SYS_ADMIN}; + +struct zebra_privs_t rustbind_privs = { +#if defined(FRR_USER) + .user = FRR_USER, +#endif +#if defined FRR_GROUP + .group = FRR_GROUP, +#endif +#ifdef VTY_GROUP + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0 +}; + + +static const struct frr_yang_module_info *const rustbind_yang_modules[] = {}; + +/* clang-format off */ +FRR_DAEMON_INFO(rustbind, RUST, + .vty_port = RUSTBIND_VTY_PORT, + .proghelp = "Implementation of the RUST daemon template.", + + .privs = &rustbind_privs, + + .yang_modules = rustbind_yang_modules, + .n_yang_modules = array_size(rustbind_yang_modules), + + /* mgmtd will load the per-daemon config file now */ + .flags = FRR_NO_SPLIT_CONFIG, + ); +/* clang-format on */ + +extern struct frr_daemon_info *rust_get_daemon_info(void); + +struct frr_daemon_info *rust_get_daemon_info(void) +{ + return &rustbind_di; +} diff --git a/rustbind/rustbin_main.rs b/rustbind/rustbin_main.rs new file mode 100644 index 0000000000..acbe930e65 --- /dev/null +++ b/rustbind/rustbin_main.rs @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// September 9 2024, Christian Hopps +// +// Copyright (C) 2024 LabN Consulting, L.L.C. +// + +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; + +// #[path = "rust_server.rs"] +// pub mod rust; +// Need to figure best way to do shared source files +// #[path = "../lib/rust_msg.rs"] +// pub mod msg; + +use tracing::debug; + +extern "C" { + // This is our C init function we use to handle the complex compiler tricks + // FRR uses for initializing the daemon info structure. + fn rust_get_daemon_info() -> *mut frr_daemon_info; +} + +/// +/// Setup the trace logging. +/// +fn setup_logging() { + /* + * Enable some logging + */ + let subscriber = tracing_subscriber::FmtSubscriber::builder() + .with_max_level(tracing::Level::TRACE) + .finish(); + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); +} + +/// +/// Main Function :) +/// +fn main() { + setup_logging(); + + // Initialize FRR. + + // create a vector of zero terminated CLI arg strings + let args = std::env::args() + .map(|arg| CString::new(arg).unwrap()) + .collect::>(); + + // convert the strings to raw pointers + let c_args = args + .iter() + .map(|arg| arg.as_ptr()) + .collect::>(); + + // Get frr_daemon_info from our C init module + let di = unsafe { rust_get_daemon_info() }; + + // Initialize FRR + unsafe { frr_preinit(di, c_args.len() as i32, c_args.as_ptr() as *mut *mut i8) } + + debug!("daemon name is {:?}", unsafe { + CStr::from_ptr((*di).progname) + }); + + let master = unsafe { frr_init() }; + debug!("master thread: {:?}", master); + + // Run! + debug!("Running main FRR loop"); + unsafe { frr_run(master) }; +} diff --git a/rustbind/subdir.am b/rustbind/subdir.am new file mode 100644 index 0000000000..299645c4ea --- /dev/null +++ b/rustbind/subdir.am @@ -0,0 +1,34 @@ +# +# rustbind -- RUSTBIN Daemon +# + +if RUSTBIND + +EXTRA_DIST += rustbind/Cargo.toml +sbin_PROGRAMS += rustbind/rustbind +rustbind_rustbind_SOURCES = rustbind/rustbin_main.rs + +if DEV_BUILD +RUSTBIND_BUILD_FLAG = +RUSTBIND_TARGET_DIR = debug +else +RUSTBIND_BUILD_FLAG = --release +RUSTBIND_TARGET_DIR = release +endif + +rustbind/Cargo.lock: rustbind/Cargo.toml + (cd rustbind && cargo update $(RUSTBIND_BUILD_FLAG)) + +rustbind/rustbind: rustbind/target/$(RUSTBIND_TARGET_DIR)/rustbind$(EXEEXT) + @printf " CP rustbind/rustbind\n" + @cp $< $@ + +rustbind/target/$(RUSTBIND_TARGET_DIR)/rustbind$(EXEEXT): $(rustbind_rustbind_SOURCES) rustbind/Cargo.toml + @printf " CARGO $@\n" + @(cd rustbind && cargo -q build $(RUSTBIND_BUILD_FLAG)) + +clean-rustbind: + (cd rustbind && cargo clean) +else +clean-rustbind: +endif diff --git a/rustbind/wrapper.h.in b/rustbind/wrapper.h.in new file mode 100644 index 0000000000..a818311ed7 --- /dev/null +++ b/rustbind/wrapper.h.in @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * September 9 2024, Christian Hopps + * + * Copyright (C) 2024 LabN Consulting, L.L.C. + */ +#include "config.h" +/* #include "typesafe.h" */ +/* #include "frratomic.h" */ +/* #include "sigevent.h" */ +/* #include "privs.h" */ + +/* #include "log.h" */ +/* #include "getopt.h" */ +/* #include "module.h" */ +/* #include "hook.h" */ +/* #include "northbound.h" */ + +#include +#include +#include -- 2.39.5