forked from Mirror/frr
Compare commits
5 commits
master
...
dev/rust-s
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b4000e0ad7 | ||
![]() |
38c8603ebe | ||
![]() |
6f9fced057 | ||
![]() |
edad74e5bc | ||
![]() |
fff9e3fbcb |
|
@ -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 \
|
||||
|
|
41
configure.ac
41
configure.ac
|
@ -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"])
|
||||
|
|
|
@ -23,4 +23,5 @@ FRRouting Developer's Guide
|
|||
path
|
||||
pceplib
|
||||
link-state
|
||||
rust-dev
|
||||
northbound/northbound
|
||||
|
|
177
doc/developer/rust-dev.rst
Normal file
177
doc/developer/rust-dev.rst
Normal 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
592
lib/frrutil.rs
Normal 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");
|
||||
}
|
|
@ -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 {
|
||||
|
|
13
lib/zlog.c
13
lib/zlog.c
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
6
rustlibd/.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
/.cargo
|
||||
/build.rs
|
||||
/target
|
||||
/wrapper.h
|
||||
/Cargo.lock
|
||||
/Cargo.toml
|
30
rustlibd/Cargo.toml.in
Normal file
30
rustlibd/Cargo.toml.in
Normal 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
10
rustlibd/Makefile
Normal 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
21
rustlibd/README.org
Normal 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
94
rustlibd/build.rs.in
Normal 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
52
rustlibd/c_shim.rs
Normal 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
1
rustlibd/frrutil.rs
Symbolic link
|
@ -0,0 +1 @@
|
|||
../lib/frrutil.rs
|
80
rustlibd/rustfmt.toml.default
Normal file
80
rustlibd/rustfmt.toml.default
Normal 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
131
rustlibd/rustlib_lib.rs
Normal 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
156
rustlibd/rustlib_main.c
Normal 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
29
rustlibd/sandbox.rs
Normal 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
74
rustlibd/subdir.am
Normal 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
24
rustlibd/wrapper.h.in
Normal 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>
|
Loading…
Reference in a new issue