1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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.
|