Compare commits

...

5 commits

Author SHA1 Message Date
Christian Hopps b4000e0ad7 rustlibd: rust daemon template
Signed-off-by: Christian Hopps <chopps@labn.net>
2025-03-03 12:14:22 +00:00
Christian Hopps 38c8603ebe lib: add FRR utility functions for rust code
Signed-off-by: Christian Hopps <chopps@labn.net>
2025-03-03 11:44:00 +00:00
Christian Hopps 6f9fced057 lib: add extern available variadic zlog function
Signed-off-by: Christian Hopps <chopps@labn.net>
2025-03-03 11:44:00 +00:00
Christian Hopps edad74e5bc rustbind: remove: rust binary based daemon skeleton code
Signed-off-by: Christian Hopps <chopps@labn.net>
2025-03-03 11:42:39 +00:00
Christian Hopps fff9e3fbcb rustbind: capture rust binary based daemon skeleton code work
Signed-off-by: Christian Hopps <chopps@labn.net>
2025-03-03 11:42:07 +00:00
21 changed files with 1535 additions and 2 deletions

View file

@ -185,6 +185,7 @@ include grpc/subdir.am
include tools/subdir.am
include mgmtd/subdir.am
include rustlibd/subdir.am
include bgpd/subdir.am
include bgpd/rfp-example/librfp/subdir.am
@ -285,6 +286,7 @@ EXTRA_DIST += \
qpb/Makefile \
ripd/Makefile \
ripngd/Makefile \
rustlibd/Makefile \
staticd/Makefile \
tests/Makefile \
tools/Makefile \

View file

@ -732,6 +732,8 @@ AC_ARG_ENABLE([mgmtd_local_validations],
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],
@ -1872,6 +1874,10 @@ AS_IF([test "$enable_ripngd" != "no"], [
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])
])
@ -2113,6 +2119,40 @@ if test "$enable_config_rollbacks" = "yes"; then
])
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 ---------------
@ -2782,6 +2822,7 @@ AM_CONDITIONAL([ENABLE_BGP_VNC], [test "$enable_bgp_vnc" != "no"])
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"])

View file

@ -23,4 +23,5 @@ FRRouting Developer's Guide
path
pceplib
link-state
rust-dev
northbound/northbound

177
doc/developer/rust-dev.rst Normal file
View file

@ -0,0 +1,177 @@
.. -*- 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.

592
lib/frrutil.rs Normal file
View file

@ -0,0 +1,592 @@
// -*- coding: utf-8 -*-
// SPDX-License-Identifier: GPL-2.0-or-later
//
// January 25 2025, Christian Hopps <chopps@labn.net>
//
// Copyright (c) 2025, LabN Consulting, L.L.C.
//
use std::ffi::CStr;
use std::ffi::CString;
use std::io::Write;
use std::os::raw::{c_char, c_int, c_void};
use std::string::String;
use lazy_static::lazy_static;
use tracing::error;
use tracing_subscriber::fmt;
use tracing_subscriber::layer::Layer;
use tracing_subscriber::prelude::*;
use yang::data::{DataNodeRef, DataTreeOwningRef};
use yang::ffi;
use yang::utils::Binding;
/// Module implementing FRR rust utilities
use crate::c_shim;
use crate::c_shim::{ly_native_ctx, nb_error, nb_error_NB_ERR, nb_error_NB_OK};
// -----------------------
// Module Static Variables
// -----------------------
lazy_static! {
pub static ref FRR_YANG_CTX: yang::context::Context = {
unsafe {
let _frr_ctx = ly_native_ctx as *mut ffi::ly_ctx;
yang::context::Context::from_raw(&(), _frr_ctx)
}
};
}
// ----------------------
// Module Private Utility
// ----------------------
pub fn validate_p<T>(p: *const T) -> &'static T {
if p.is_null() {
panic!("Null pointer when needing a reference");
}
unsafe { &*p }
}
pub fn validate_mp<T>(p: *mut T) -> &'static mut T {
if p.is_null() {
panic!("Null pointer when needing a reference");
}
unsafe { &mut *p }
}
fn u8_to_char(slice: &[u8]) -> &[c_char] {
unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const c_char, slice.len()) }
}
// ==========
// Northbound
// ==========
// ----------------
// Northbound Enums
// ----------------
#[repr(u8)]
#[derive(Copy, Clone, Debug)]
pub enum NbError {
Ok = c_shim::nb_error_NB_OK as u8,
Err = c_shim::nb_error_NB_ERR as u8,
NoChanges = c_shim::nb_error_NB_ERR_NO_CHANGES as u8,
NotFound = c_shim::nb_error_NB_ERR_NOT_FOUND as u8,
Exists = c_shim::nb_error_NB_ERR_EXISTS as u8,
Locked = c_shim::nb_error_NB_ERR_LOCKED as u8,
Validation = c_shim::nb_error_NB_ERR_VALIDATION as u8,
Resource = c_shim::nb_error_NB_ERR_RESOURCE as u8,
Yield = c_shim::nb_error_NB_YIELD as u8,
}
impl std::fmt::Display for NbError {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(fmt, "{:?}", self)
}
}
impl std::error::Error for NbError {}
#[derive(Copy, Clone, Debug)]
pub enum NbEvent {
VALIDATE,
PREPARE,
ABORT(*mut c_void),
APPLY(*mut c_void),
}
impl std::fmt::Display for NbEvent {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(fmt, "{:?}", self)
}
}
pub fn nb_event_to_rust(c_event: c_shim::nb_event, arg: *mut c_void) -> NbEvent {
match c_event {
c_shim::nb_event_NB_EV_VALIDATE => NbEvent::VALIDATE,
c_shim::nb_event_NB_EV_PREPARE => NbEvent::PREPARE,
c_shim::nb_event_NB_EV_ABORT => NbEvent::ABORT(arg),
c_shim::nb_event_NB_EV_APPLY => NbEvent::APPLY(arg),
_ => panic!("invalid northbound event: {}", c_event),
}
}
// -----------------------
// Northbound API Adapters
// -----------------------
pub fn nb_notify_update(path: &str) {
let cstr = CString::new(path).expect("Failed to convert string to c-string");
unsafe { c_shim::nb_notif_add(cstr.as_ptr()) };
}
pub fn nb_notify_update_node(node: &DataNodeRef) {
let path = node.path();
nb_notify_update(&path);
}
pub fn nb_notify_delete(path: &str) {
let cstr = CString::new(path).expect("Failed to convert string to c-string");
unsafe { c_shim::nb_notif_delete(cstr.as_ptr()) };
}
pub fn nb_notify_delete_node(node: &DataNodeRef) {
let path = node.path();
nb_notify_delete(&path);
}
// -------------------------
// Northbound Callback Shims
// -------------------------
/// Obtain a temporary DataTreeOwningRef from a mutable lyd_node pointer.
///
/// Safety: The user needs to be careful if actually constructing this from a
/// const lyd_node pointer to only pass on non-mut references to the object or
/// it's noderef().
pub fn new_borrowed_node<'a>(
parent: *mut ffi::lyd_node,
) -> std::mem::ManuallyDrop<DataTreeOwningRef<'a>> {
unsafe { DataTreeOwningRef::from_raw_node(&FRR_YANG_CTX, parent) }
}
pub fn nb_create_shim(
_args: *mut c_shim::nb_cb_create_args,
create: fn(NbEvent, &DataNodeRef) -> Result<(), nb_error>,
) -> nb_error {
let args = validate_p(_args);
let resource_ptr = if args.resource.is_null() {
std::ptr::null_mut()
} else {
unsafe { validate_p(args.resource).ptr }
};
let event = nb_event_to_rust(args.event, resource_ptr);
let node = new_borrowed_node(args.dnode as *mut ffi::lyd_node);
match create(event, &node.noderef()) {
Ok(()) => nb_error_NB_OK,
Err(e) => e,
}
}
#[macro_export]
macro_rules! define_nb_create_shim {
($shim_name:ident, $native_func:expr) => {
#[no_mangle]
extern "C" fn $shim_name(
args: *mut $crate::c_shim::nb_cb_create_args,
) -> $crate::c_shim::nb_error {
$crate::frrutil::nb_create_shim(args, $native_func)
}
};
}
pub fn nb_destroy_shim(
_args: *mut c_shim::nb_cb_destroy_args,
destroy: fn(NbEvent, &DataNodeRef) -> Result<(), nb_error>,
) -> nb_error {
let args = validate_p(_args);
let event = nb_event_to_rust(args.event, std::ptr::null_mut());
let node = new_borrowed_node(args.dnode as *mut ffi::lyd_node);
match destroy(event, &node.noderef()) {
Ok(()) => nb_error_NB_OK,
Err(e) => e,
}
}
#[macro_export]
macro_rules! define_nb_destroy_shim {
($shim_name:ident, $native_func:expr) => {
#[no_mangle]
extern "C" fn $shim_name(
args: *mut $crate::c_shim::nb_cb_destroy_args,
) -> $crate::c_shim::nb_error {
$crate::frrutil::nb_destroy_shim(args, $native_func)
}
};
}
pub fn nb_modify_shim(
_args: *mut c_shim::nb_cb_modify_args,
modify: fn(NbEvent, &DataNodeRef) -> Result<(), nb_error>,
) -> nb_error {
let args = validate_p(_args);
let resource_ptr = if args.resource.is_null() {
std::ptr::null_mut()
} else {
unsafe { validate_p(args.resource).ptr }
};
let event = nb_event_to_rust(args.event, resource_ptr);
let node = new_borrowed_node(args.dnode as *mut ffi::lyd_node);
match modify(event, &node.noderef()) {
Ok(()) => nb_error_NB_OK,
Err(e) => e,
}
}
#[macro_export]
macro_rules! define_nb_modify_shim {
($shim_name:ident, $native_func:expr) => {
#[no_mangle]
extern "C" fn $shim_name(
args: *mut $crate::c_shim::nb_cb_modify_args,
) -> $crate::c_shim::nb_error {
$crate::frrutil::nb_modify_shim(args, $native_func)
}
};
}
/// Generic get_keys code to translate C callback args to and from native Rust.
pub fn nb_get_keys_shim(
_args: *mut c_shim::nb_cb_get_keys_args,
get_keys: fn(*const c_void) -> Result<Vec<String>, c_int>,
) -> c_int {
let args = validate_mp(_args);
let list_entry = args.list_entry;
let keys_ret = validate_mp(args.keys);
let keys = match get_keys(list_entry) {
Err(e) => return e as c_int,
Ok(keys) => keys,
};
let num_keys = keys.len();
if num_keys > keys_ret.key.len() {
panic!(
"Too many keys for list entry: {} > {}",
num_keys,
keys_ret.key.len()
);
}
// Walk vector of keys
keys_ret.num = num_keys as u8;
for (index, value) in keys.iter().enumerate() {
let keyref = &mut keys_ret.key[index];
let vlen = value.len();
if vlen >= keyref.len() {
error!(
"key '{}' is too long to store: {} > {}",
value,
vlen + 1,
keyref.len()
);
return nb_error_NB_ERR as c_int;
}
// Copy the key data as NUL terminated C-string.
keyref[..vlen].copy_from_slice(u8_to_char(value.as_bytes()));
keyref[vlen] = 0;
}
nb_error_NB_OK as std::os::raw::c_int
}
#[macro_export]
macro_rules! define_nb_get_keys_shim {
($shim_name:ident, $native_func:ident) => {
#[no_mangle]
pub extern "C" fn $shim_name(_args: *mut c_shim::nb_cb_get_keys_args) -> c_int {
frrutil::nb_get_keys_shim(_args, $native_func)
}
};
}
#[macro_export]
macro_rules! define_nb_get_shim {
($shim_name:ident, $native_func:expr) => {
#[no_mangle]
pub extern "C" fn $shim_name(
_nb_node: *const nb_node,
parent_list_item: *const c_void,
_parent: *mut lyd_node,
) -> nb_error {
let parent = frrutil::new_borrowed_node(_parent as *mut ffi::lyd_node);
match $native_func(parent_list_item, &mut parent.noderef()) {
Ok(e) => e,
Err(_) => nb_error_NB_ERR,
}
}
};
}
#[macro_export]
macro_rules! define_nb_get_next_shim {
($shim_name:ident, $native_func:expr) => {
#[no_mangle]
extern "C" fn $shim_name(args: *mut c_shim::nb_cb_get_next_args) -> *const c_void {
let args = frrutil::validate_p(args);
$native_func(args.parent_list_entry, args.list_entry)
}
};
}
pub fn nb_lookup_entry_shim(
_args: *mut c_shim::nb_cb_lookup_entry_args,
lookup_entry: fn(_parent_list_entry: *const c_void, _keys: &[&CStr]) -> *const c_void,
) -> *const c_void {
let args = validate_p(_args);
let c_keys = validate_p(args.keys);
let num_keys = c_keys.num as usize;
let mut keys = Vec::<&CStr>::with_capacity(num_keys);
for i in 0..num_keys {
let key = unsafe { CStr::from_ptr(c_keys.key[i].as_ptr()) };
keys.push(key);
}
lookup_entry(args.parent_list_entry, &keys)
}
#[macro_export]
macro_rules! define_nb_lookup_entry_shim {
($shim_name:ident, $native_func:expr) => {
#[no_mangle]
extern "C" fn $shim_name(_args: *mut c_shim::nb_cb_lookup_entry_args) -> *const c_void {
frrutil::nb_lookup_entry_shim(_args, $native_func)
}
};
}
// #[macro_export]
// macro_rules! define_nb_get_tree_locked {
// ($shim_name:ident, $native_func:expr) => {
// #[no_mangle]
// extern "C" fn $shim_name(xpath: *const c_char, lockptr: **c_void) -> *const c_shim::lyd_node {
// if xpath.is_null() {
// return std::ptr::null() as *const c_shim::lyd_node;
// }
// let xpath_cstr = unsafe { CStr::from_ptr(xpath) };
// let xpath = xpath_cstr.to_owned();
// let (tree_ptr, leaked_lock_ptr) = $native_func(&xpath);
// unsafe { *lockptr = leaked_lock };
// ???
// }
// };
// }
#[macro_export]
macro_rules! define_nb_get_tree_locked {
($shim_name:ident, $native_func:expr) => {
#[no_mangle]
extern "C" fn $shim_name(
xpath: *const c_char,
lockptr: *mut *const c_void,
) -> *const c_shim::lyd_node {
if xpath.is_null() || lockptr.is_null() {
return std::ptr::null();
}
let xpath_cstr = unsafe { CStr::from_ptr(xpath) };
let xpath = xpath_cstr.to_owned();
let guard = $native_func(&xpath);
let tree = (*guard).raw();
let raw_guard = Box::into_raw(Box::new(guard)) as *const c_void;
unsafe { *lockptr = raw_guard };
tree as *const c_shim::lyd_node
}
};
}
#[macro_export]
macro_rules! define_nb_unlock_tree {
($shim_name:ident, $native_func:expr) => {
#[no_mangle]
extern "C" fn $shim_name(_tree: *const c_shim::lyd_node, lock: *const c_void) {
let raw_guard =
lock as *mut tokio::sync::MutexGuard<'static, yang3::data::DataTree<'_>>;
let guard = unsafe { Box::<MutexGuard<'static, DataTree<'_>>>::from_raw(raw_guard) };
unlock_tree(*guard);
// let tree = frrutil::validate_p(tree);
// $native_func(tree)
}
};
}
// ---------------
// General Utility
// ---------------
pub fn frr_register_thread(_name: &str) {
// This is required to allow FRR zlog to work from inside external pthreads
unsafe { c_shim::rcu_thread_start(c_shim::rcu_thread_new(std::ptr::null_mut())) };
}
pub fn spawn<F, T>(name: &str, f: F) -> std::thread::JoinHandle<T>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
let capture_name = String::from(name);
std::thread::Builder::new()
.spawn(move || {
frr_register_thread(&capture_name);
f()
})
.expect("failed to spawn thread")
}
// -------
// Logging
// -------
#[macro_export]
macro_rules! zlog {
($level:expr, $fmt:tt, $($args:tt)*) => {{
c_shim::ezlog($level, $fmt.as_ptr(), $($args)*);
}};
($level:expr, $fmt:tt) => {{
c_shim::ezlog($level, $fmt.as_ptr());
}}
}
#[macro_export]
macro_rules! zlog_debug {
($($args:tt)*) => {
zlog!(c_shim::LOG_DEBUG as i32, $($args)*)
}
}
#[macro_export]
macro_rules! zlog_info {
($($args:tt)*) => {
zlog!(c_shim::LOG_INFO as i32, $($args)*)
}
}
#[macro_export]
macro_rules! zlog_warn {
($($args:tt)*) => {
zlog!(c_shim::LOG_WARNING as i32, $($args)*)
}
}
#[macro_export]
macro_rules! zlog_err {
($($args:tt)*) => {
zlog!(c_shim::LOG_ERR as i32, $($args)*)
}
}
pub struct ZlogDebug;
pub struct ZlogInfo;
pub struct ZlogWarn;
pub struct ZlogErr;
macro_rules! zlog_writer {
($name:tt, $level:expr) => {
impl Write for $name {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let len = buf.len() as c_int;
let ptr = buf.as_ptr() as *const c_char;
// We say len - 1 to eliminate the layer::fmt() added newline
unsafe { zlog!($level as i32, c"%.*s", len - 1, ptr) };
Ok(len as usize)
}
fn flush(&mut self) -> std::io::Result<()> {
unsafe { c_shim::zlog_tls_buffer_flush() };
Ok(())
}
}
};
}
zlog_writer!(ZlogDebug, c_shim::LOG_DEBUG);
zlog_writer!(ZlogInfo, c_shim::LOG_INFO);
zlog_writer!(ZlogWarn, c_shim::LOG_WARNING);
zlog_writer!(ZlogErr, c_shim::LOG_ERR);
use tracing::metadata::Metadata;
use tracing::Level;
use tracing_subscriber::layer::{Context, Filter};
pub struct DebugOnlyFilter;
impl<S> Filter<S> for DebugOnlyFilter {
fn enabled(&self, meta: &Metadata<'_>, _: &Context<'_, S>) -> bool {
meta.level() == &Level::DEBUG
}
}
pub struct InfoOnlyFilter;
impl<S> Filter<S> for InfoOnlyFilter {
fn enabled(&self, meta: &Metadata<'_>, _: &Context<'_, S>) -> bool {
meta.level() == &Level::INFO
}
}
pub struct WarnOnlyFilter;
impl<S> Filter<S> for WarnOnlyFilter {
fn enabled(&self, meta: &Metadata<'_>, _: &Context<'_, S>) -> bool {
meta.level() == &Level::WARN
}
}
pub struct ErrorOnlyFilter;
impl<S> Filter<S> for ErrorOnlyFilter {
fn enabled(&self, meta: &Metadata<'_>, _: &Context<'_, S>) -> bool {
meta.level() == &Level::ERROR
}
}
///
/// Setup logging including bridging to FRR zlog
#[no_mangle]
pub extern "C" fn bridge_rust_logging() {
/*
* Enable some logging
*/
// These are some custom filter examples
// let debug_filter = FilterFn::new(|metadata| *metadata.level() == tracing::Level::DEBUG);
// let info_filter = FilterFn::new(|metadata| *metadata.level() == tracing::Level::INFO);
// let warn_filter = FilterFn::new(|metadata| *metadata.level() == tracing::Level::WARN);
// let error_filter = FilterFn::new(|metadata| *metadata.level() == tracing::Level::ERROR);
let subscriber = tracing_subscriber::registry()
// Uncomment this to pretty print log messages to the stdout
.with(
fmt::layer()
.compact()
.with_ansi(true)
.with_filter(tracing::level_filters::LevelFilter::TRACE)
)
.with(
fmt::layer()
.with_writer(|| -> Box<dyn std::io::Write> { Box::new(ZlogDebug {}) })
.with_ansi(false)
.with_target(false)
.without_time()
.with_filter(DebugOnlyFilter),
)
.with(
fmt::layer()
.with_writer(|| -> Box<dyn std::io::Write> { Box::new(ZlogInfo {}) })
.with_ansi(false)
.with_target(false)
.without_time()
.with_filter(InfoOnlyFilter),
)
.with(
fmt::layer()
.with_writer(|| -> Box<dyn std::io::Write> { Box::new(ZlogWarn {}) })
.with_ansi(false)
.with_target(false)
.without_time()
.with_filter(WarnOnlyFilter),
)
.with(
fmt::layer()
.with_writer(|| -> Box<dyn std::io::Write> { Box::new(ZlogErr {}) })
.with_ansi(false)
.with_target(false)
.without_time()
.with_filter(ErrorOnlyFilter),
);
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
}

View file

@ -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 RUSTLIBD_VTY_PORT 2635 /* Move this value not actual daemons if needed */
/* Registry of daemons' port defaults */
enum frr_cli_mode {

View file

@ -685,8 +685,17 @@ static void zlog_backtrace_msg(const struct xref_logmsg *xref, int prio)
tc->xref->xref.line);
}
void vzlogx(const struct xref_logmsg *xref, int prio,
const char *fmt, va_list ap)
PRINTFRR(2, 3)
void ezlog(int prio, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vzlog(prio, fmt, ap);
va_end(ap);
}
void vzlogx(const struct xref_logmsg *xref, int prio, const char *fmt, va_list ap)
{
struct zlog_tls *zlog_tls = zlog_tls_get();

View file

@ -64,6 +64,8 @@ extern void vzlogx(const struct xref_logmsg *xref, int prio, const char *fmt,
va_list ap) PRINTFRR(3, 0);
#define vzlog(prio, ...) vzlogx(NULL, prio, __VA_ARGS__)
extern void ezlog(int prio, const char *fmt, ...) PRINTFRR(2, 3);
PRINTFRR(2, 3)
static inline void zlog(int prio, const char *fmt, ...)
{

6
rustlibd/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
/.cargo
/build.rs
/target
/wrapper.h
/Cargo.lock
/Cargo.toml

30
rustlibd/Cargo.toml.in Normal file
View file

@ -0,0 +1,30 @@
[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"

10
rustlibd/Makefile Normal file
View file

@ -0,0 +1,10 @@
all: ALWAYS
@$(MAKE) -s -C .. rustlibd/rustlibd
%: ALWAYS
@$(MAKE) -s -C .. rustlibd/$@
Makefile:
#nothing
ALWAYS:
.PHONY: ALWAYS makefiles
.SUFFIXES:

21
rustlibd/README.org Normal file
View file

@ -0,0 +1,21 @@
* 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

94
rustlibd/build.rs.in Normal file
View file

@ -0,0 +1,94 @@
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");
}

52
rustlibd/c_shim.rs Normal file
View file

@ -0,0 +1,52 @@
// 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);
}

1
rustlibd/frrutil.rs Symbolic link
View file

@ -0,0 +1 @@
../lib/frrutil.rs

View file

@ -0,0 +1,80 @@
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

131
rustlibd/rustlib_lib.rs Normal file
View file

@ -0,0 +1,131 @@
// 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;
}
}
}
}

156
rustlibd/rustlib_main.c Normal file
View file

@ -0,0 +1,156 @@
// 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;
}

29
rustlibd/sandbox.rs Normal file
View file

@ -0,0 +1,29 @@
// -*- 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();
}

74
rustlibd/subdir.am Normal file
View file

@ -0,0 +1,74 @@
#
# 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

24
rustlibd/wrapper.h.in Normal file
View file

@ -0,0 +1,24 @@
// 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>