include tools/subdir.am
include mgmtd/subdir.am
+include rustlibd/subdir.am
include bgpd/subdir.am
include bgpd/rfp-example/librfp/subdir.am
qpb/Makefile \
ripd/Makefile \
ripngd/Makefile \
+ rustlibd/Makefile \
staticd/Makefile \
tests/Makefile \
tools/Makefile \
AS_HELP_STRING([--enable-mgmtd-local-validations], [dev: unimplemented local validation]))
AC_ARG_ENABLE([mgmtd_test_be_client],
AS_HELP_STRING([--enable-mgmtd-test-be-client], [build test backend client]))
+AC_ARG_ENABLE([rustlibd],
+ AS_HELP_STRING([--enable-rustlibd], [enable rust library based daemon template]))
AC_ARG_ENABLE([fpm_listener],
AS_HELP_STRING([--enable-fpm-listener], [build fpm listener test program]))
AC_ARG_ENABLE([ripd],
AC_DEFINE([HAVE_RIPNGD], [1], [ripngd])
])
+AS_IF([test "$enable_rustlibd" != "no"], [
+ AC_DEFINE([HAVE_RUSTLIBD], [1], [rustlibd])
+])
+
AS_IF([test "$enable_ospfd" != "no"], [
AC_DEFINE([HAVE_OSPFD], [1], [ospfd])
])
])
fi
+dnl ------------------------------------------------------
+dnl rust general (add to conditional any new rust daemons)
+dnl ------------------------------------------------------
+if test "$enable_rustlibd" = "yes"; then
+ AC_PATH_PROG([CARGO], [cargo], [notfound])
+ AS_IF([test "$CARGO" = "notfound"], [AC_MSG_ERROR([cargo is required])])
+
+ AC_PATH_PROG([RUSTC], [rustc], [notfound])
+ AS_IF([test "$RUSTC" = "notfound"], [AC_MSG_ERROR([rustc is required])])
+
+ if test "$enable_dev_build" = "yes"; then
+ CARGO_TARGET_DIR=debug
+ else
+ CARGO_TARGET_DIR=release
+ fi
+ AC_SUBST(CARGO_TARGET_DIR)
+fi
+
+dnl ---------------
+dnl rustlibd
+dnl ---------------
+if test "$enable_rustlibd" = "yes"; then
+ AC_CONFIG_FILES([rustlibd/build.rs rustlibd/wrapper.h rustlibd/Cargo.toml])
+
+ AC_CONFIG_COMMANDS([gen-dot-cargo-config], [
+ if test "$ac_abs_top_builddir" != "$ac_abs_top_srcdir"; then
+ mkdir -p ${srcdir}/rustlibd/.cargo
+ if ! test -e "${srcdir}/rustlibd/.cargo/config.toml"; then
+ printf '[[build]]\ntarget-dir = "%s"\n' "${ac_abs_top_builddir}/rustlibd/target" > "${srcdir}/rustlibd/.cargo/config.toml"
+ fi
+ fi]
+ )
+fi
+
dnl ---------------
dnl sysrepo
dnl ---------------
AM_CONDITIONAL([BGP_BMP], [$bgpd_bmp])
dnl northbound
AM_CONDITIONAL([SQLITE3], [$SQLITE3])
+AM_CONDITIONAL([RUSTLIBD], [test "$enable_rustlibd" = "yes"])
AM_CONDITIONAL([SYSREPO], [test "$enable_sysrepo" = "yes"])
AM_CONDITIONAL([GRPC], [test "$enable_grpc" = "yes"])
AM_CONDITIONAL([ZEROMQ], [test "$ZEROMQ" = "true"])
path
pceplib
link-state
+ rust-dev
northbound/northbound
--- /dev/null
+.. -*- coding: utf-8 -*-
+..
+.. SPDX-License-Identifier: GPL-2.0-or-later
+..
+.. February 26 2025, Christian Hopps <chopps@labn.net>
+..
+.. Copyright (c) 2025, LabN Consulting, L.L.C.
+..
+
+.. _rust_dev:
+
+Rust Development
+================
+
+Overview
+--------
+
+The FRR project has started adding support for daemons written in rust. The
+following sections document the infrastructure to support to-date. This is the
+initial approach of rust integration, we expect changes as best-practices within
+the community evolve.
+
+General Structure
+-----------------
+
+An example template of the general structure of a rust based daemon can be found
+in ``rustlib/`` sub-directory. The recommended structure so far is to use a C
+main file and function to drive initialization of the daemon calling out to rust
+at 3 critical points. The Rust code is then built as a static library and linked
+into the daemon. Rust bindings are built for ``libfrr`` and accessed through a
+c_shim sub-module. Here's the files and as of the time of this writing:
+
+.. code-block:: make
+
+rustlibd/
+ .gitignore
+ Cargo.toml.in
+ Makefile
+ README.org
+ build.rs.in
+ c_shim.rs
+ frrutil.rs (symlink)
+ rustlib_lib.rs
+ rustlib_main.c
+ sandbox.rs
+ subdir.am
+ wrapper.h.in
+
+:file:`frrutil.rs` is a symlink to :file:`../lib/frrutil.rs` kept here to keep
+various rust tools happy about files being inside or below the main source
+directory.
+
+
+NOTE: if you use a separate build dir (named `build` in the below example) and
+you want to have your development environment proper analyze code (e.g.,
+vs-code/emacs LSP mode) you should create an additional 2 symlinks and create a
+local :file:`Cargo.toml` file like so:
+
+.. code-block:: sh
+
+ cd frr/rustlibd
+ sed -e 's,@srcdir@/,,g' < Cargo.toml.in > Cargo.toml
+ ln -s ../build/rustlibd/build.rs .
+ ln -s ../build/rustlibd/wrapper.h .
+
+Logging
+-------
+
+FRR logging is transparently supported using some bridging code that connects
+the native rust ``tracing`` calls directly to the ``zlog`` functionality in FRR.
+The only thing you have to do is call the function :func:`bridge_rust_logging`
+at startup. This is already done for you in the `rustlibd` template :func:`main`
+if you started with that code.
+
+.. code-block:: rust
+
+ use tracing::{debug, info};
+
+ fn myrustfunc(sval: &str, uval: u32) {
+ debug!("Some DEBUG level output of str value: {}", sval);
+ info!("Some INFO level output of uint value: {}", uval);
+ }
+
+Northbound Integration
+----------------------
+
+Support for the FRR northbound callback system is handled through rust macros.
+These rust macros define C shims which then call your rust functions which will
+use natural rust types. The rust macros hide the unsafe and tricky conversion
+code. You put pointers to the generated C shim functions into the
+:struct:`frr_yang_module_info` structure.
+
+NOTE: Locking will probably be important as your callbacks will be called in the
+FRR event loop main thread and your rust code is probably running in it's own
+different thread (perhaps using the tokio async runtime as setup in the
+:file:`rustlibd` template).
+
+Here's an example of defining a handler for a config leave value `enable`:
+
+.. code-block:: C
+
+ const struct frr_yang_module_info frr_my_module_nb_info = {
+ .name = "frr-my-module",
+ .nodes = {
+ {
+ .xpath = "/frr-my-module:lib/bvalue",
+ .cbs = {
+ .modify = my_module_bvalue_modify_shim,
+ .destroy = my_module_bvalue_destroy_shim
+ }
+ },
+ ...
+
+.. code-block:: rust
+
+ use crate::{define_nb_destroy_shim, define_nb_modify_shim};
+
+ pub(crate) fn my_module_bvalue_modify(
+ event: NbEvent,
+ _node: &DataNodeRef,
+ ) -> Result<(), nb_error> {
+ debug!("RUST: bvalue modify: {}", event);
+ match event {
+ NbEvent::APPLY(_) => {
+ // handle the change to the `bvalue` leaf.
+ Ok(())
+ },
+ _ => Ok(()), // All other events just return Ok.
+ }
+ }
+
+ pub(crate) fn my_module_bvalue_destroy(
+ event: NbEvent,
+ _node: &DataNodeRef,
+ ) -> Result<(), nb_error> {
+ // handle the removal of the `bvalue` leaf.
+ // ...
+ }
+
+ define_nb_modify_shim!(
+ my_module_bvalue_modify_shim,
+ my_module_bvalue_modify);
+
+ define_nb_destroy_shim!(
+ my_module_bvalue_destroy_shim,
+ my_module_bvalue_destroy);
+
+
+CLI commands
+~~~~~~~~~~~~
+
+For CLI commands you should continue to write the DEFPY_YANG() calls in C which
+simply set your YANG config data base on the args to DEFPY_YANG(). The actual
+configuration will be handled in your rust based callbacks you defined for your
+YANG model that are describe above.
+
+Operational State
+~~~~~~~~~~~~~~~~~
+
+You have 2 choices with operation state. You can implement the operation state
+callbacks in rust and use the rust macros to bridge these to the
+:struct:`frr_yang_module_info` definition as you did with your config handlers, or you
+can keep your operational state in a ``yang-rs`` (i.e., ``libyang``) based tree.
+Here's an example of using the macros:
+
+If you choose to do the latter and save all your operational state in a
+``libyang`` :struct:`DataTree`, you only need to define 2 callback functions, a
+:func:`get_tree_locked()` function which returns the :struct:`DataTree` in a
+:struct:`MutexGuard` (i.e., a held lock), and an :func:`unlock_tree()` function
+which is passed back the :struct:`MutexGuard` object for unlocking. You use 2
+macros: :func:`define_nb_get_tree_locked`, and :func:`define_nb_unlock_tree` to
+create the C based shims to plug into your :struct:`frr_yang_module_info`
+structure.
+
+NOTE: As with config, locking will probably be important as your callbacks will
+be called in the FRR event loop main thread and your rust code is probably
+running in it's own different thread.
#define PATH_VTY_PORT 2621
#define PIM6D_VTY_PORT 2622
#define MGMTD_VTY_PORT 2623
+#define RUSTLIBD_VTY_PORT 2635 /* Move this value not actual daemons if needed */
/* Registry of daemons' port defaults */
enum frr_cli_mode {
--- /dev/null
+/.cargo
+/build.rs
+/target
+/wrapper.h
+/Cargo.lock
+/Cargo.toml
--- /dev/null
+[package]
+name = "rustlibd"
+version = "0.1.0"
+edition = "2021"
+default-run = "sandbox"
+
+[[bin]]
+name = "sandbox"
+path = "@srcdir@/sandbox.rs"
+test = false
+bench = false
+
+[lib]
+name = "rustlibd"
+path = "@srcdir@/rustlib_lib.rs"
+crate-type = ["staticlib"]
+test = true
+bench = false
+
+[dependencies]
+lazy_static = "1.5.0"
+libc = "0.2.169"
+libyang = { package = "libyang3-sys", version = "0.2.0" }
+tokio = { version = "1.38.0", features = ["full"]}
+tracing = "0.1.40"
+tracing-subscriber = { version = "0.3.18", features = ["env-filter"]}
+yang = { package = "yang3", version = "0.13.0" }
+
+[build-dependencies]
+bindgen = "0.71.1"
\ No newline at end of file
--- /dev/null
+all: ALWAYS
+ @$(MAKE) -s -C .. rustlibd/rustlibd
+%: ALWAYS
+ @$(MAKE) -s -C .. rustlibd/$@
+
+Makefile:
+ #nothing
+ALWAYS:
+.PHONY: ALWAYS makefiles
+.SUFFIXES:
--- /dev/null
+* Rust Library Based Daemon Skeleton
+
+This is the skeleton for a rust library based FRR daemon.
+
+** 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/rustlibd~ for
+ `build.rs` and `wrapper.h` e.g. if your build dir is in `frr/build`, and
+ create a `Cargo.toml` in the source directory that points to itself. Example
+ follows:
+
+ #+begin_src bash
+ cd frr/rustlibd
+ ln -s ../build/rustlibd/build.rs .
+ ln -s ../build/rustlibd/wrapper.h .
+ sed -e 's,@srcdir@/,,g' < Cargo.toml.in > Cargo.toml
+ #+end_src
--- /dev/null
+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<RwLock<HashSet<String>>>,
+}
+
+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@/rustlib_main.c")
+ // .compile("c_main_code");
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// September 22 2024, Christian Hopps <chopps@labn.net>
+//
+// 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::c_void;
+
+struct ShimGlobals {
+ arg: *mut c_void,
+ runtime: tokio::runtime::Runtime,
+}
+
+#[no_mangle]
+pub extern "C" fn _rust_preinit(daemon: *mut frr_daemon_info) -> *mut c_void {
+ crate::rust_preinit(daemon)
+}
+
+#[no_mangle]
+pub extern "C" fn _rust_init(_master: *mut event_loop, arg: *mut c_void) -> *mut c_void {
+ crate::rust_init(arg)
+}
+
+#[no_mangle]
+pub extern "C" fn _rust_run(_master: *mut event_loop, arg: *mut c_void) -> *mut c_void {
+ let runtime = crate::get_runtime();
+
+ let mut globals = Box::new(ShimGlobals {
+ arg: crate::rust_init(arg),
+ runtime,
+ });
+
+ globals.arg = crate::rust_run(&mut globals.runtime, globals.arg);
+
+ Box::into_raw(globals) as *mut c_void
+}
+
+#[no_mangle]
+pub extern "C" fn _rust_fini(_master: *mut event_loop, arg: *mut c_void) {
+ let globals = unsafe { Box::from_raw(arg as *mut ShimGlobals) };
+
+ crate::rust_fini(globals.arg);
+
+ // Make this explicit, not really needed but makes it clear
+ drop(globals.runtime);
+}
--- /dev/null
+../lib/frrutil.rs
\ No newline at end of file
--- /dev/null
+array_width = 60
+attr_fn_like_width = 70
+binop_separator = "Front"
+blank_lines_lower_bound = 0
+blank_lines_upper_bound = 1
+brace_style = "SameLineWhere"
+chain_width = 60
+color = "Auto"
+combine_control_expr = true
+comment_width = 80
+condense_wildcard_suffixes = false
+control_brace_style = "AlwaysSameLine"
+disable_all_formatting = false
+doc_comment_code_block_width = 100
+edition = "2015"
+emit_mode = "Files"
+empty_item_single_line = true
+enum_discrim_align_threshold = 0
+error_on_line_overflow = false
+error_on_unformatted = false
+fn_call_width = 60
+fn_params_layout = "Tall"
+fn_single_line = false
+force_explicit_abi = true
+force_multiline_blocks = false
+format_code_in_doc_comments = false
+format_generated_files = true
+format_macro_bodies = true
+format_macro_matchers = false
+format_strings = false
+generated_marker_line_search_limit = 5
+group_imports = "Preserve"
+hard_tabs = false
+hex_literal_case = "Preserve"
+ignore = []
+imports_granularity = "Preserve"
+imports_indent = "Block"
+imports_layout = "Mixed"
+indent_style = "Block"
+inline_attribute_width = 0
+make_backup = false
+match_arm_blocks = true
+match_arm_leading_pipes = "Never"
+match_block_trailing_comma = false
+max_width = 100
+merge_derives = true
+newline_style = "Auto"
+normalize_comments = false
+normalize_doc_attributes = false
+overflow_delimited_expr = false
+remove_nested_parens = true
+reorder_impl_items = false
+reorder_imports = true
+reorder_modules = true
+required_version = "1.8.0"
+short_array_element_width_threshold = 10
+show_parse_errors = true
+single_line_if_else_max_width = 50
+single_line_let_else_max_width = 50
+skip_children = false
+skip_macro_invocations = []
+space_after_colon = true
+space_before_colon = false
+spaces_around_ranges = false
+struct_field_align_threshold = 0
+struct_lit_single_line = true
+struct_lit_width = 18
+struct_variant_width = 35
+style_edition = "2015"
+tab_spaces = 4
+trailing_comma = "Vertical"
+trailing_semicolon = true
+type_punctuation_density = "Wide"
+unstable_features = false
+use_field_init_shorthand = false
+use_small_heuristics = "Default"
+use_try_shorthand = false
+version = "One"
+where_single_line = false
+wrap_comments = false
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// September 9 2024, Christian Hopps <chopps@labn.net>
+//
+// Copyright (C) 2024 LabN Consulting, L.L.C.
+//
+
+pub mod c_shim;
+#[macro_use]
+pub mod frrutil;
+
+use std::ffi::c_void;
+use std::io::Result;
+use std::sync::OnceLock;
+
+use tokio::sync::mpsc::{channel, Receiver, Sender};
+use tracing::{debug, error};
+
+// -------
+// Globals
+// -------
+
+enum Command {
+ Quit,
+}
+
+static TX: OnceLock<Sender<Command>> = OnceLock::new();
+static RX: OnceLock<std::sync::Mutex<Receiver<Result<()>>>> = OnceLock::new();
+
+////
+/// Get an tokio runtime for async execution
+///
+/// This function should create a runtime for the daemon. The generic rust infra
+/// code will call it to create the runtime for the daemon. The runtime will be
+/// passed to `rust_run()` so that a main task (or whatever) can be started.
+fn get_runtime() -> tokio::runtime::Runtime {
+ tokio::runtime::Builder::new_multi_thread()
+ .on_thread_start(|| {
+ frrutil::frr_register_thread("tokio-worker-thread");
+ debug!("tokio thread started")
+ })
+ .on_thread_stop(|| debug!("tokio thread stop"))
+ .enable_all()
+ .build()
+ .unwrap()
+}
+
+// ===================================================
+// Initialization/Teardown Callbacks (from xxx_main.c)
+// ===================================================
+
+///
+/// Pre-init (called after frr_preinit()), returned value is passed to rust_init()
+fn rust_preinit(_daemon: *mut c_shim::frr_daemon_info) -> *mut c_void {
+ debug!("in rust_preinit");
+ std::ptr::null_mut()
+}
+
+///
+/// Pre-daemonize (called after frr_init()), returned value is passed to rust_run()
+fn rust_init(_preinit_val: *mut c_void) -> *mut c_void {
+ debug!("in rust_init");
+ std::ptr::null_mut()
+}
+
+///
+/// Called prior to entering the FRR event loop (frr_run())
+///
+/// This function should spawn a task into the tokio runtime to run the daemon
+fn rust_run(runtime: &mut tokio::runtime::Runtime, _init_val: *mut c_void) -> *mut c_void {
+ debug!("in rust_run");
+
+ //
+ // Setup command channel with async_main thread
+ //
+
+ let (tx, rx) = channel::<Command>(10);
+ let (result_tx, result_rx) = channel::<Result<()>>(10);
+ TX.set(tx.clone()).expect("only set once");
+ RX.set(std::sync::Mutex::new(result_rx))
+ .expect("only set once");
+
+ //
+ // Start running our main routine
+ //
+ runtime.spawn(async { async_main(rx, result_tx).await });
+ std::ptr::null_mut()
+}
+
+///
+/// FRR exiting callback
+///
+/// Do any cleanup prior to the rust runtime being dropped and all spawned tasks
+/// being joined/canceled.
+pub fn rust_fini(_run_val: *mut c_void) {
+ debug!("rust_fini: sending quit command to async_main");
+ if TX.get().unwrap().blocking_send(Command::Quit).is_err() {
+ return;
+ }
+
+ debug!("rust_fini: waiting on exit result from async_main");
+ if let Some(result) = RX.get().unwrap().lock().unwrap().blocking_recv() {
+ match result {
+ Ok(()) => debug!("Runtime quit successfully"),
+ Err(x) => error!("Failed to quit runtime: {}", x),
+ }
+ }
+}
+
+// ============================
+// Main Rust Execution Function
+// ============================
+
+///
+/// Main loop -- process commands from FRR
+async fn async_main(mut cmd_rx: Receiver<Command>, result_tx: Sender<Result<()>>) {
+ loop {
+ debug!("Waiting on cmd channel");
+ let cmd = match cmd_rx.recv().await {
+ None => continue,
+ Some(cmd) => cmd,
+ };
+ match cmd {
+ Command::Quit => {
+ debug!("Quit command");
+ let _ = result_tx.send(Ok(())).await;
+ return;
+ }
+ }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * September 9 2024, Christian Hopps <chopps@labn.net>
+ *
+ * Copyright (c) 2024, LabN Consulting, L.L.C.
+ */
+
+#include <lib/libfrr.h>
+#include <lib/zebra.h>
+#include <lib/privs.h>
+#include <lib/version.h>
+#include "mgmt_be_client.h"
+
+extern void *_rust_preinit(struct frr_daemon_info *daemon);
+extern void *_rust_init(struct event_loop *master, void *arg);
+extern void *_rust_run(struct event_loop *master, void *arg);
+extern void _rust_fini(struct event_loop *master, void *arg);
+extern void bridge_rust_logging(void);
+extern struct frr_daemon_info *rust_get_daemon_info(void);
+static void sighup(void);
+static void sigint(void);
+static void sigusr1(void);
+
+struct event_loop *master;
+struct mgmt_be_client *mgmt_be_client;
+void *rust_fini_arg;
+
+static struct option __longopts[] = { { 0 } };
+
+static zebra_capabilities_t __caps_p[] = {ZCAP_NET_RAW, ZCAP_BIND, ZCAP_SYS_ADMIN};
+
+static struct zebra_privs_t __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 struct frr_signal_t __signals[] = {
+ {
+ .signal = SIGHUP,
+ .handler = &sighup,
+ },
+ {
+ .signal = SIGUSR1,
+ .handler = &sigusr1,
+ },
+ {
+ .signal = SIGINT,
+ .handler = &sigint,
+ },
+ {
+ .signal = SIGTERM,
+ .handler = &sigint,
+ },
+};
+
+static const struct frr_yang_module_info *const __yang_modules[] = {};
+
+/* clang-format off */
+FRR_DAEMON_INFO(rustlibd, RUST,
+ .vty_port = RUSTLIBD_VTY_PORT,
+ .proghelp = "Implementation of the RUST daemon template.",
+
+ .signals = __signals,
+ .n_signals = array_size(__signals),
+
+ .privs = &__privs,
+
+ .yang_modules = __yang_modules,
+ .n_yang_modules = array_size(__yang_modules),
+
+ /* mgmtd will load the per-daemon config file now */
+ .flags = FRR_NO_SPLIT_CONFIG,
+ );
+/* clang-format on */
+
+struct frr_daemon_info *rust_get_daemon_info(void)
+{
+ return &rustlibd_di;
+}
+
+static void sighup(void)
+{
+ zlog_info("SIGHUP received and ignored");
+}
+
+static void sigint(void)
+{
+ zlog_notice("Terminating on signal");
+
+ nb_oper_cancel_all_walks();
+
+ _rust_fini(master, rust_fini_arg);
+
+ mgmt_be_client_destroy(mgmt_be_client);
+ mgmt_be_client = NULL;
+ frr_fini();
+ exit(0);
+}
+
+static void sigusr1(void)
+{
+ zlog_rotate();
+}
+
+/* Main routine of ripd. */
+int main(int argc, char **argv)
+{
+ void *rust_arg;
+
+ frr_preinit(&rustlibd_di, argc, argv);
+
+ bridge_rust_logging();
+
+ rust_arg = _rust_preinit(&rustlibd_di);
+
+ frr_opt_add("", __longopts, "");
+
+ /* Command line option parse. */
+ while (1) {
+ int opt;
+
+ opt = frr_getopt(argc, argv, NULL);
+ if (opt == EOF)
+ break;
+
+ switch (opt) {
+ case 0:
+ break;
+ default:
+ frr_help_exit(1);
+ }
+ }
+
+ /* Prepare master thread. */
+ master = frr_init();
+ rust_arg = _rust_init(master, rust_arg);
+ mgmt_be_client = mgmt_be_client_create("rustlibd", NULL, 0, master);
+
+ frr_config_fork();
+
+ rust_fini_arg = _rust_run(master, rust_arg);
+ frr_run(master);
+
+ /* Not reached. */
+ return 0;
+}
--- /dev/null
+// -*- coding: utf-8 -*-
+//
+// February 26 2025, Christian Hopps <chopps@labn.net>
+//
+// Copyright (c) 2025, LabN Consulting, L.L.C.
+//
+#![allow(clippy::disallowed_names)]
+
+// =======
+// HashMap
+// =======
+
+fn test_hashmap() {
+ let v: Vec<&str> = "foobar=1baz&&bf%2Clag".split('&').collect();
+ let v: Vec<&str> = v.into_iter().filter(|&x| !x.is_empty()).collect();
+ println!("HASHMAP: split: {:?}", v);
+
+ let qmap: HashMap<_, _> = v
+ .into_iter()
+ .map(|x| x.split_once('=').unwrap_or((x, "")))
+ .map(|(x, y)| (_percent_decode(x), _percent_decode(y)))
+ .map(|(x, y)| (String::from(x), String::from(y)))
+ .collect();
+ println!("HASHMAP: qmap: {:?}", qmap);
+}
+
+fn main() {
+ test_hashmap();
+}
--- /dev/null
+#
+# rustlibd -- RUSTLIBD Daemon
+#
+
+if RUSTLIBD
+noinst_LIBRARIES += rustlibd/librustlibd.a
+sbin_PROGRAMS += rustlibd/rustlibd
+# vtysh_daeons += rustlibd
+
+
+#
+# Files for copying as template:
+#
+# - xxx_lib.c :: Main rust functions for the daemon - replace these with your own
+# - xxx_nb.c :: Create from `tools/gen_northbound_calllbacks` function/struct defines
+# - xxx_nb.h :: Create from `tools/gen_northbound_calllbacks` prototype declares
+# - xxx_nb_{config,state}.rs :: Use `define_nb_*_shim()` rust macros to bridge
+# your rust northbound callbacks from the C prototypes created by
+# gen_northbound_calllbacks - remove the C versions of these functions
+# from xxx_nb.c
+#
+# - build.rs :: Build script the rust daemon, only modify if you have build
+# issues with C symbols.
+# - c_shim.rs :: no modify needed - exports FRR/C bindings and calls your rust
+# functions.
+# - restonf_main.c :: C main(), requires minimal modifications (name, yang modules)
+# - wrapper.h.in :: Include C header files to create bindings under c_shim for your
+# rust use, add/remove as needed.
+
+#
+# C build rules
+#
+rustlibd_rustlibd_SOURCES = rustlibd/rustlib_main.c # rustlibd/rustlib_nb.c
+rustlibd_rustlibd_LDADD = rustlibd/librustlibd.a lib/libfrr.la $(LIBCAP) -lm
+# noinst_HEADERS += rustlibd/rustlib_nb.h
+
+# Add any used YANG modules here to embed.
+# nodist_restconfd_restconfd_SOURCES = \
+# yang/frr-rustlib.yang.c
+
+# clippy_scan += rustlibd/rustlib_cli.c
+
+#
+# rustlibd rust library build rules
+#
+EXTRA_DIST += rustlibd/Cargo.toml
+rustlibd_librustlibd_a_SOURCES = rustlibd/c_shim.rs \
+ lib/frrutil.rs \
+ rustlibd/rustlib_lib.rs
+
+if DEV_BUILD
+RUSTLIBD_BUILD_FLAG =
+RUSTLIBD_TARGET_DIR = debug
+else
+RUSTLIBD_BUILD_FLAG = --release
+RUSTLIBD_TARGET_DIR = release
+endif
+
+rustlibd/Cargo.lock: rustlibd/Cargo.toml
+ (cd rustlibd && cargo update $(RUSTLIBD_BUILD_FLAG))
+
+rustlibd/librustlibd.a: rustlibd/target/$(RUSTLIBD_TARGET_DIR)/librustlibd.a
+ @printf " CP rustlib/librustlibd.a\n"
+ @cp $< $@
+
+rustlibd/target/$(RUSTLIBD_TARGET_DIR)/librustlibd.a: $(rustlibd_librustlibd_a_SOURCES) rustlibd/Cargo.toml
+ @printf " CARGO $@\n"
+ (cd rustlibd && cargo +stable -q build --lib $(RUSTLIBD_BUILD_FLAG))
+
+clean-rustlibd:
+ (cd rustlibd && cargo clean)
+else
+clean-rustlibd:
+endif
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * September 9 2024, Christian Hopps <chopps@labn.net>
+ *
+ * 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 <lib/zebra.h>
+
+#include <lib/frr_pthread.h>
+#include <lib/libfrr.h>
+#include <lib/log.h>
+#include <lib/mgmt_msg_native.h>