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
|
.. _modules:
Modules
=======
FRR has facilities to load DSOs at startup via ``dlopen()``. These are used to
implement modules, such as SNMP and FPM.
Limitations
-----------
- can't load, unload, or reload during runtime. This just needs some
work and can probably be done in the future.
- doesn't fix any of the "things need to be changed in the code in the
library" issues. Most prominently, you can't add a CLI node because
CLI nodes are listed in the library...
- if your module crashes, the daemon crashes. Should be obvious.
- **does not provide a stable API or ABI**. Your module must match a
version of FRR and you may have to update it frequently to match
changes.
- **does not create a license boundary**. Your module will need to link
libzebra and include header files from the daemons, meaning it will
be GPL-encumbered.
Installation
------------
Look for ``moduledir`` in ``configure.ac``, default is normally
``/usr/lib64/frr/modules`` but depends on ``--libdir`` / ``--prefix``.
The daemon's name is prepended when looking for a module, e.g. "snmp"
tries to find "zebra\_snmp" first when used in zebra. This is just to
make it nicer for the user, with the snmp module having the same name
everywhere.
Modules can be packaged separately from FRR. The SNMP and FPM modules
are good candidates for this because they have dependencies (net-snmp /
protobuf) that are not FRR dependencies. However, any distro packages
should have an "exact-match" dependency onto the FRR package. Using a
module from a different FRR version will probably blow up nicely.
For snapcraft (and during development), modules can be loaded with full
path (e.g. -M ``$SNAP/lib/frr/modules/zebra_snmp.so``). Note that
libtool puts output files in the .libs directory, so during development
you have to use ``./zebra -M .libs/zebra_snmp.so``.
Creating a module
-----------------
... best to look at the existing SNMP or FPM modules.
Basic boilerplate:
::
#include "hook.h"
#include "module.h"
#include "libfrr.h"
#include "frrevent.h"
static int module_late_init(struct event_loop *master)
{
/* Do initialization stuff here */
return 0;
}
static int
module_init (void)
{
hook_register(frr_late_init, module_late_init);
return 0;
}
FRR_MODULE_SETUP(
.name = "my module",
.version = "0.0",
.description = "my module",
.init = module_init,
);
The ``frr_late_init`` hook will be called after the daemon has finished
its other startup and is about to enter the main event loop; this is the
best place for most initialisation.
Compiler & Linker magic
-----------------------
There's a ``THIS_MODULE`` (like in the Linux kernel), which uses
``visibility`` attributes to restrict it to the current module. If you
get a linker error with ``_frrmod_this_module``, there is some linker
SNAFU. This shouldn't be possible, though one way to get it would be to
not include libzebra (which provides a fallback definition for the
symbol).
libzebra and the daemons each have their own ``THIS_MODULE``, as do all
loadable modules. In any other libraries (e.g. ``libfrrsnmp``),
``THIS_MODULE`` will use the definition in libzebra; same applies if the
main executable doesn't use ``FRR_DAEMON_INFO`` (e.g. all testcases).
The deciding factor here is "what dynamic linker unit are you using the
symbol from." If you're in a library function and want to know who
called you, you can't use ``THIS_MODULE`` (because that'll just tell you
you're in the library). Put a macro around your function that adds
``THIS_MODULE`` in the *caller's code calling your function*.
The idea is to use this in the future for module unloading. Hooks
already remember which module they were installed by, as groundwork for
a function that removes all of a module's installed hooks.
There's also the ``frr_module`` symbol in modules, pretty much a
standard entry point for loadable modules.
Command line parameters
-----------------------
Command line parameters can be passed directly to a module by appending a
colon to the module name when loading it, e.g. ``-M mymodule:myparameter``.
The text after the colon will be accessible in the module's code through
``THIS_MODULE->load_args``. For example, see how the format parameter is
configured in the ``zfpm_init()`` function inside ``zebra_fpm.c``.
Hooks
-----
Hooks are just points in the code where you can register your callback
to be called. The parameter list is specific to the hook point. Since
there is no stable API, the hook code has some extra type safety checks
making sure you get a compiler warning when the hook parameter list
doesn't match your callback. Don't ignore these warnings.
Relation to MTYPE macros
------------------------
The MTYPE macros, while primarily designed to decouple MTYPEs from the
library and beautify the code, also work very nicely with loadable
modules -- both constructors and destructors are executed when
loading/unloading modules.
This means there is absolutely no change required to MTYPEs, you can
just use them in a module and they will even clean up themselves when we
implement module unloading and an unload happens. In fact, it's
impossible to create a bug where unloading fails to de-register a MTYPE.
|