Merge pull request #7639 from qlyoung/frr-lua

Scripting
This commit is contained in:
Russ White 2021-01-19 07:17:03 -05:00 committed by GitHub
commit c0b6ef23f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1815 additions and 316 deletions

View File

@ -60,6 +60,7 @@
#include "bgpd/bgp_keepalives.h"
#include "bgpd/bgp_network.h"
#include "bgpd/bgp_errors.h"
#include "bgpd/bgp_script.h"
#include "lib/routing_nb.h"
#include "bgpd/bgp_nb.h"
#include "bgpd/bgp_evpn_mh.h"
@ -510,6 +511,10 @@ int main(int argc, char **argv)
/* Initializations. */
bgp_vrf_init();
#ifdef HAVE_SCRIPTING
bgp_script_init();
#endif
hook_register(routing_conf_event,
routing_control_plane_protocols_name_validate);

View File

@ -65,6 +65,7 @@
#include "bgpd/bgp_flowspec_util.h"
#include "bgpd/bgp_encap_types.h"
#include "bgpd/bgp_mpath.h"
#include "bgpd/bgp_script.h"
#ifdef ENABLE_BGP_VNC
#include "bgpd/rfapi/bgp_rfapi_cfg.h"
@ -337,99 +338,138 @@ static const struct route_map_rule_cmd route_match_peer_cmd = {
route_match_peer_free
};
#if defined(HAVE_LUA)
#ifdef HAVE_SCRIPTING
enum frrlua_rm_status {
/*
* Script function run failure. This will translate into a deny
*/
LUA_RM_FAILURE = 0,
/*
* No Match was found for the route map function
*/
LUA_RM_NOMATCH,
/*
* Match was found but no changes were made to the incoming data.
*/
LUA_RM_MATCH,
/*
* Match was found and data was modified, so figure out what changed
*/
LUA_RM_MATCH_AND_CHANGE,
};
static enum route_map_cmd_result_t
route_match_command(void *rule, const struct prefix *prefix, void *object)
route_match_script(void *rule, const struct prefix *prefix, void *object)
{
int status = RMAP_NOMATCH;
u_int32_t locpref = 0;
u_int32_t newlocpref = 0;
enum lua_rm_status lrm_status;
const char *scriptname = rule;
struct bgp_path_info *path = (struct bgp_path_info *)object;
lua_State *L = lua_initialize("/etc/frr/lua.scr");
if (L == NULL)
return status;
struct frrscript *fs = frrscript_load(scriptname, NULL);
/*
* Setup the prefix information to pass in
*/
lua_setup_prefix_table(L, prefix);
if (!fs) {
zlog_err("Issue loading script rule; defaulting to no match");
return RMAP_NOMATCH;
}
zlog_debug("Set up prefix table");
/*
* Setup the bgp_path_info information
*/
lua_newtable(L);
lua_pushinteger(L, path->attr->med);
lua_setfield(L, -2, "metric");
lua_pushinteger(L, path->attr->nh_ifindex);
lua_setfield(L, -2, "ifindex");
lua_pushstring(L, path->attr->aspath->str);
lua_setfield(L, -2, "aspath");
lua_pushinteger(L, path->attr->local_pref);
lua_setfield(L, -2, "localpref");
zlog_debug("%s %d", path->attr->aspath->str, path->attr->nh_ifindex);
lua_setglobal(L, "nexthop");
enum frrlua_rm_status status_failure = LUA_RM_FAILURE,
status_nomatch = LUA_RM_NOMATCH,
status_match = LUA_RM_MATCH,
status_match_and_change = LUA_RM_MATCH_AND_CHANGE;
zlog_debug("Set up nexthop information");
/*
* Run the rule
*/
lrm_status = lua_run_rm_rule(L, rule);
switch (lrm_status) {
/* Make result values available */
struct frrscript_env env[] = {
{"integer", "RM_FAILURE", &status_failure},
{"integer", "RM_NOMATCH", &status_nomatch},
{"integer", "RM_MATCH", &status_match},
{"integer", "RM_MATCH_AND_CHANGE", &status_match_and_change},
{"integer", "action", &status_failure},
{"prefix", "prefix", prefix},
{"attr", "attributes", path->attr},
{"peer", "peer", path->peer},
{}};
struct frrscript_env results[] = {
{"integer", "action"},
{"attr", "attributes"},
{},
};
int result = frrscript_call(fs, env);
if (result) {
zlog_err("Issue running script rule; defaulting to no match");
return RMAP_NOMATCH;
}
enum frrlua_rm_status *lrm_status =
frrscript_get_result(fs, &results[0]);
int status = RMAP_NOMATCH;
switch (*lrm_status) {
case LUA_RM_FAILURE:
zlog_debug("RM_FAILURE");
zlog_err(
"Executing route-map match script '%s' failed; defaulting to no match",
scriptname);
status = RMAP_NOMATCH;
break;
case LUA_RM_NOMATCH:
zlog_debug("RM_NOMATCH");
status = RMAP_NOMATCH;
break;
case LUA_RM_MATCH_AND_CHANGE:
zlog_debug("MATCH AND CHANGE");
lua_getglobal(L, "nexthop");
path->attr->med = get_integer(L, "metric");
/*
* This needs to be abstraced with the set function
*/
status = RMAP_MATCH;
zlog_debug("Updating attribute based on script's values");
uint32_t locpref = 0;
struct attr *newattr = frrscript_get_result(fs, &results[1]);
path->attr->med = newattr->med;
if (path->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))
locpref = path->attr->local_pref;
newlocpref = get_integer(L, "localpref");
if (newlocpref != locpref) {
path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF);
path->attr->local_pref = newlocpref;
if (locpref != newattr->local_pref) {
SET_FLAG(path->attr->flag,
ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF));
path->attr->local_pref = newattr->local_pref;
}
status = RMAP_MATCH;
aspath_free(newattr->aspath);
XFREE(MTYPE_TMP, newattr);
break;
case LUA_RM_MATCH:
zlog_debug("MATCH ONLY");
status = RMAP_MATCH;
break;
}
lua_close(L);
XFREE(MTYPE_TMP, lrm_status);
frrscript_unload(fs);
return status;
}
static void *route_match_command_compile(const char *arg)
static void *route_match_script_compile(const char *arg)
{
char *command;
char *scriptname;
command = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
return command;
scriptname = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
return scriptname;
}
static void
route_match_command_free(void *rule)
static void route_match_script_free(void *rule)
{
XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
}
static const struct route_map_rule_cmd route_match_command_cmd = {
"command",
route_match_command,
route_match_command_compile,
route_match_command_free
static const struct route_map_rule_cmd route_match_script_cmd = {
"script",
route_match_script,
route_match_script_compile,
route_match_script_free
};
#endif
#endif /* HAVE_SCRIPTING */
/* `match ip address IP_ACCESS_LIST' */
@ -4096,30 +4136,29 @@ DEFUN (no_match_peer,
RMAP_EVENT_MATCH_DELETED);
}
#if defined(HAVE_LUA)
DEFUN (match_command,
match_command_cmd,
"match command WORD",
MATCH_STR
"Run a command to match\n"
"The command to run\n")
{
return bgp_route_match_add(vty, "command", argv[2]->arg,
RMAP_EVENT_FILTER_ADDED);
}
DEFUN (no_match_command,
no_match_command_cmd,
"no match command WORD",
#ifdef HAVE_SCRIPTING
DEFUN (match_script,
match_script_cmd,
"[no] match script WORD",
NO_STR
MATCH_STR
"Run a command to match\n"
"The command to run\n")
"Execute script to determine match\n"
"The script name to run, without .lua; e.g. 'myroutemap' to run myroutemap.lua\n")
{
return bgp_route_match_delete(vty, "command", argv[3]->arg,
RMAP_EVENT_FILTER_DELETED);
bool no = strmatch(argv[0]->text, "no");
int i = 0;
argv_find(argv, argc, "WORD", &i);
const char *script = argv[i]->arg;
if (no) {
return bgp_route_match_delete(vty, "script", script,
RMAP_EVENT_FILTER_DELETED);
} else {
return bgp_route_match_add(vty, "script", script,
RMAP_EVENT_FILTER_ADDED);
}
}
#endif
#endif /* HAVE_SCRIPTING */
/* match probability */
DEFUN (match_probability,
@ -5633,8 +5672,8 @@ void bgp_route_map_init(void)
route_map_install_match(&route_match_peer_cmd);
route_map_install_match(&route_match_local_pref_cmd);
#if defined(HAVE_LUA)
route_map_install_match(&route_match_command_cmd);
#ifdef HAVE_SCRIPTING
route_map_install_match(&route_match_script_cmd);
#endif
route_map_install_match(&route_match_ip_address_cmd);
route_map_install_match(&route_match_ip_next_hop_cmd);
@ -5798,9 +5837,8 @@ void bgp_route_map_init(void)
install_element(RMAP_NODE, &no_set_ipv6_nexthop_prefer_global_cmd);
install_element(RMAP_NODE, &set_ipv6_nexthop_peer_cmd);
install_element(RMAP_NODE, &no_set_ipv6_nexthop_peer_cmd);
#if defined(HAVE_LUA)
install_element(RMAP_NODE, &match_command_cmd);
install_element(RMAP_NODE, &no_match_command_cmd);
#ifdef HAVE_SCRIPTING
install_element(RMAP_NODE, &match_script_cmd);
#endif
}

192
bgpd/bgp_script.c Normal file
View File

@ -0,0 +1,192 @@
/* BGP scripting foo
* Copyright (C) 2020 NVIDIA Corporation
* Quentin Young
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include <zebra.h>
#ifdef HAVE_SCRIPTING
#include "bgpd.h"
#include "bgp_script.h"
#include "bgp_debug.h"
#include "bgp_aspath.h"
#include "frratomic.h"
#include "frrscript.h"
#include "frrlua.h"
static void lua_pushpeer(lua_State *L, const struct peer *peer)
{
lua_newtable(L);
lua_pushinteger(L, peer->as);
lua_setfield(L, -2, "remote_as");
lua_pushinteger(L, peer->local_as);
lua_setfield(L, -2, "local_as");
lua_pushinaddr(L, &peer->remote_id);
lua_setfield(L, -2, "remote_id");
lua_pushinaddr(L, &peer->local_id);
lua_setfield(L, -2, "local_id");
lua_pushstring(L, lookup_msg(bgp_status_msg, peer->status, NULL));
lua_setfield(L, -2, "state");
lua_pushstring(L, peer->desc ? peer->desc : "");
lua_setfield(L, -2, "description");
lua_pushtimet(L, &peer->uptime);
lua_setfield(L, -2, "uptime");
lua_pushtimet(L, &peer->readtime);
lua_setfield(L, -2, "last_readtime");
lua_pushtimet(L, &peer->resettime);
lua_setfield(L, -2, "last_resettime");
lua_pushsockunion(L, peer->su_local);
lua_setfield(L, -2, "local_address");
lua_pushsockunion(L, peer->su_remote);
lua_setfield(L, -2, "remote_address");
lua_pushinteger(L, peer->cap);
lua_setfield(L, -2, "capabilities");
lua_pushinteger(L, peer->flags);
lua_setfield(L, -2, "flags");
lua_pushstring(L, peer->password ? peer->password : "");
lua_setfield(L, -2, "password");
/* Nested tables here */
lua_newtable(L);
{
lua_newtable(L);
{
lua_pushinteger(L, peer->holdtime);
lua_setfield(L, -2, "hold");
lua_pushinteger(L, peer->keepalive);
lua_setfield(L, -2, "keepalive");
lua_pushinteger(L, peer->connect);
lua_setfield(L, -2, "connect");
lua_pushinteger(L, peer->routeadv);
lua_setfield(L, -2, "route_advertisement");
}
lua_setfield(L, -2, "configured");
lua_newtable(L);
{
lua_pushinteger(L, peer->v_holdtime);
lua_setfield(L, -2, "hold");
lua_pushinteger(L, peer->v_keepalive);
lua_setfield(L, -2, "keepalive");
lua_pushinteger(L, peer->v_connect);
lua_setfield(L, -2, "connect");
lua_pushinteger(L, peer->v_routeadv);
lua_setfield(L, -2, "route_advertisement");
}
lua_setfield(L, -2, "negotiated");
}
lua_setfield(L, -2, "timers");
lua_newtable(L);
{
lua_pushinteger(L, atomic_load_explicit(&peer->open_in,
memory_order_relaxed));
lua_setfield(L, -2, "open_in");
lua_pushinteger(L, atomic_load_explicit(&peer->open_out,
memory_order_relaxed));
lua_setfield(L, -2, "open_out");
lua_pushinteger(L, atomic_load_explicit(&peer->update_in,
memory_order_relaxed));
lua_setfield(L, -2, "update_in");
lua_pushinteger(L, atomic_load_explicit(&peer->update_out,
memory_order_relaxed));
lua_setfield(L, -2, "update_out");
lua_pushinteger(L, atomic_load_explicit(&peer->update_time,
memory_order_relaxed));
lua_setfield(L, -2, "update_time");
lua_pushinteger(L, atomic_load_explicit(&peer->keepalive_in,
memory_order_relaxed));
lua_setfield(L, -2, "keepalive_in");
lua_pushinteger(L, atomic_load_explicit(&peer->keepalive_out,
memory_order_relaxed));
lua_setfield(L, -2, "keepalive_out");
lua_pushinteger(L, atomic_load_explicit(&peer->notify_in,
memory_order_relaxed));
lua_setfield(L, -2, "notify_in");
lua_pushinteger(L, atomic_load_explicit(&peer->notify_out,
memory_order_relaxed));
lua_setfield(L, -2, "notify_out");
lua_pushinteger(L, atomic_load_explicit(&peer->refresh_in,
memory_order_relaxed));
lua_setfield(L, -2, "refresh_in");
lua_pushinteger(L, atomic_load_explicit(&peer->refresh_out,
memory_order_relaxed));
lua_setfield(L, -2, "refresh_out");
lua_pushinteger(L, atomic_load_explicit(&peer->dynamic_cap_in,
memory_order_relaxed));
lua_setfield(L, -2, "dynamic_cap_in");
lua_pushinteger(L, atomic_load_explicit(&peer->dynamic_cap_out,
memory_order_relaxed));
lua_setfield(L, -2, "dynamic_cap_out");
lua_pushinteger(L, peer->established);
lua_setfield(L, -2, "times_established");
lua_pushinteger(L, peer->dropped);
lua_setfield(L, -2, "times_dropped");
}
lua_setfield(L, -2, "stats");
}
static void lua_pushattr(lua_State *L, const struct attr *attr)
{
lua_newtable(L);
lua_pushinteger(L, attr->med);
lua_setfield(L, -2, "metric");
lua_pushinteger(L, attr->nh_ifindex);
lua_setfield(L, -2, "ifindex");
lua_pushstring(L, attr->aspath->str);
lua_setfield(L, -2, "aspath");
lua_pushinteger(L, attr->local_pref);
lua_setfield(L, -2, "localpref");
}
static void *lua_toattr(lua_State *L, int idx)
{
struct attr *attr = XCALLOC(MTYPE_TMP, sizeof(struct attr));
lua_getfield(L, -1, "metric");
attr->med = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "ifindex");
attr->nh_ifindex = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "aspath");
attr->aspath = aspath_str2aspath(lua_tostring(L, -1));
lua_pop(L, 1);
lua_getfield(L, -1, "localpref");
attr->local_pref = lua_tointeger(L, -1);
lua_pop(L, 1);
return attr;
}
struct frrscript_codec frrscript_codecs_bgpd[] = {
{.typename = "peer",
.encoder = (encoder_func)lua_pushpeer,
.decoder = NULL},
{.typename = "attr",
.encoder = (encoder_func)lua_pushattr,
.decoder = lua_toattr},
{}};
void bgp_script_init(void)
{
frrscript_register_type_codecs(frrscript_codecs_bgpd);
}
#endif /* HAVE_SCRIPTING */

34
bgpd/bgp_script.h Normal file
View File

@ -0,0 +1,34 @@
/* BGP scripting foo
* Copyright (C) 2020 NVIDIA Corporation
* Quentin Young
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#ifndef __BGP_SCRIPT__
#define __BGP_SCRIPT__
#include <zebra.h>
#ifdef HAVE_SCRIPTING
/*
* Initialize scripting stuff.
*/
void bgp_script_init(void);
#endif /* HAVE_SCRIPTING */
#endif /* __BGP_SCRIPT__ */

View File

@ -96,6 +96,7 @@ bgpd_libbgp_a_SOURCES = \
bgpd/bgp_regex.c \
bgpd/bgp_route.c \
bgpd/bgp_routemap.c \
bgpd/bgp_script.c \
bgpd/bgp_table.c \
bgpd/bgp_updgrp.c \
bgpd/bgp_updgrp_adv.c \
@ -175,6 +176,7 @@ noinst_HEADERS += \
bgpd/bgp_rd.h \
bgpd/bgp_regex.h \
bgpd/bgp_route.h \
bgpd/bgp_script.h \
bgpd/bgp_table.h \
bgpd/bgp_updgrp.h \
bgpd/bgp_vpn.h \

View File

@ -138,6 +138,12 @@ AC_ARG_WITH([moduledir], [AS_HELP_STRING([--with-moduledir=DIR], [module directo
])
AC_SUBST([moduledir], [$moduledir])
AC_ARG_WITH([scriptdir], [AS_HELP_STRING([--with-scriptdir=DIR], [script directory (${sysconfdir}/scripts)])], [
scriptdir="$withval"
], [
scriptdir="\${sysconfdir}/scripts"
])
AC_SUBST([scriptdir], [$scriptdir])
AC_ARG_WITH([yangmodelsdir], [AS_HELP_STRING([--with-yangmodelsdir=DIR], [yang models directory (${datarootdir}/yang)])], [
yangmodelsdir="$withval"
@ -274,24 +280,22 @@ if test "$enable_clang_coverage" = "yes"; then
])
fi
if test "$enable_scripting" = "yes"; then
AX_PROG_LUA([5.3])
AX_LUA_HEADERS
AX_LUA_LIBS([
AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
LIBS="$LIBS $LUA_LIB"
])
fi
if test "$enable_dev_build" = "yes"; then
AC_DEFINE([DEV_BUILD], [1], [Build for development])
if test "$orig_cflags" = ""; then
AC_C_FLAG([-g3])
AC_C_FLAG([-O0])
fi
if test "$enable_lua" = "yes"; then
AX_PROG_LUA([5.3])
AX_LUA_HEADERS
AX_LUA_LIBS([
AC_DEFINE([HAVE_LUA], [1], [Have support for Lua interpreter])
LIBS="$LIBS $LUA_LIB"
])
fi
else
if test "$enable_lua" = "yes"; then
AC_MSG_ERROR([Lua is not meant to be built/used outside of development at this time])
fi
if test "$orig_cflags" = ""; then
AC_C_FLAG([-g])
AC_C_FLAG([-O2])
@ -697,8 +701,8 @@ fi
AC_ARG_ENABLE([dev_build],
AS_HELP_STRING([--enable-dev-build], [build for development]))
AC_ARG_ENABLE([lua],
AS_HELP_STRING([--enable-lua], [Build Lua scripting]))
AC_ARG_ENABLE([scripting],
AS_HELP_STRING([--enable-scripting], [Build with scripting support]))
AC_ARG_ENABLE([netlink-debug],
AS_HELP_STRING([--disable-netlink-debug], [pretty print netlink debug messages]))
@ -2446,19 +2450,23 @@ CFG_SBIN="$sbindir"
CFG_STATE="$frr_statedir"
CFG_MODULE="$moduledir"
CFG_YANGMODELS="$yangmodelsdir"
CFG_SCRIPT="$scriptdir"
for I in 1 2 3 4 5 6 7 8 9 10; do
eval CFG_SYSCONF="\"$CFG_SYSCONF\""
eval CFG_SBIN="\"$CFG_SBIN\""
eval CFG_STATE="\"$CFG_STATE\""
eval CFG_MODULE="\"$CFG_MODULE\""
eval CFG_YANGMODELS="\"$CFG_YANGMODELS\""
eval CFG_SCRIPT="\"$CFG_SCRIPT\""
done
AC_SUBST([CFG_SYSCONF])
AC_SUBST([CFG_SBIN])
AC_SUBST([CFG_STATE])
AC_SUBST([CFG_MODULE])
AC_SUBST([CFG_SCRIPT])
AC_SUBST([CFG_YANGMODELS])
AC_DEFINE_UNQUOTED([MODULE_PATH], ["$CFG_MODULE"], [path to modules])
AC_DEFINE_UNQUOTED([SCRIPT_PATH], ["$CFG_SCRIPT"], [path to scripts])
AC_DEFINE_UNQUOTED([YANG_MODELS_PATH], ["$CFG_YANGMODELS"], [path to YANG data models])
AC_DEFINE_UNQUOTED([WATCHFRR_SH_PATH], ["${CFG_SBIN%/}/watchfrr.sh"], [path to watchfrr.sh])
@ -2582,6 +2590,7 @@ state file directory : ${frr_statedir}
config file directory : `eval echo \`echo ${sysconfdir}\``
example directory : `eval echo \`echo ${exampledir}\``
module directory : ${CFG_MODULE}
script directory : ${CFG_SCRIPT}
user to run as : ${enable_user}
group to run as : ${enable_group}
group for vty sockets : ${enable_vty_group}

3
debian/control vendored
View File

@ -29,7 +29,8 @@ Build-Depends: bison,
python3-dev,
python3-pytest <!nocheck>,
python3-sphinx,
texinfo (>= 4.7)
texinfo (>= 4.7),
liblua5.3-dev <pkg.frr.lua>
Standards-Version: 4.5.0.3
Homepage: https://www.frrouting.org/
Vcs-Browser: https://github.com/FRRouting/frr/tree/debian/master

7
debian/rules vendored
View File

@ -29,6 +29,12 @@ else
CONF_SYSTEMD=--enable-systemd=no
endif
ifeq ($(filter pkg.frr.lua,$(DEB_BUILD_PROFILES)),)
CONF_LUA=--disable-scripting
else
CONF_LUA=--enable-scripting
endif
export PYTHON=python3
%:
@ -49,6 +55,7 @@ override_dh_auto_configure:
\
$(CONF_SYSTEMD) \
$(CONF_RPKI) \
$(CONF_LUA) \
--with-libpam \
--enable-doc \
--enable-doc-html \

View File

@ -15,6 +15,6 @@ Library Facilities (libfrr)
hooks
cli
modules
lua
scripting

View File

@ -1,65 +0,0 @@
.. _lua:
Lua
===
Lua is currently experimental within FRR and has very limited
support. If you would like to compile FRR with Lua you must
follow these steps:
1. Installation of Relevant Libraries
.. code-block:: shell
apt-get install lua5.3 liblua5-3 liblua5.3-dev
These are the Debian libraries that are needed. There should
be equivalent RPM's that can be found
2. Compilation
Configure needs these options
.. code-block:: shell
./configure --enable-dev-build --enable-lua <all other interesting options>
Typically you just include the two new enable lines to build with it.
3. Using Lua
* Copy tools/lua.scr into /etc/frr
* Create a route-map match command
.. code-block:: console
!
router bgp 55
neighbor 10.50.11.116 remote-as external
address-family ipv4 unicast
neighbor 10.50.11.116 route-map TEST in
exit-address-family
!
route-map TEST permit 10
match command mooey
!
* In the lua.scr file make sure that you have a function named 'mooey'
.. code-block:: console
function mooey ()
zlog_debug(string.format("afi: %d: %s %d ifdx: %d aspath: %s localpref: %d",
prefix.family, prefix.route, nexthop.metric,
nexthop.ifindex, nexthop.aspath, nexthop.localpref))
nexthop.metric = 33
nexthop.localpref = 13
return 3
end
4. General Comments
Please be aware that this is extremely experimental and needs a ton of work
to get this up into a state that is usable.

433
doc/developer/scripting.rst Normal file
View File

@ -0,0 +1,433 @@
.. _scripting:
Scripting
=========
.. seealso:: User docs for scripting
Overview
--------
FRR has the ability to call Lua scripts to perform calculations, make
decisions, or otherwise extend builtin behavior with arbitrary user code. This
is implemented using the standard Lua C bindings. The supported version of Lua
is 5.3.
C objects may be passed into Lua and Lua objects may be retrieved by C code via
a marshalling system. In this way, arbitrary data from FRR may be passed to
scripts. It is possible to pass C functions as well.
The Lua environment is isolated from the C environment; user scripts cannot
access FRR's address space unless explicitly allowed by FRR.
For general information on how Lua is used to extend C, refer to Part IV of
"Programming in Lua".
https://www.lua.org/pil/contents.html#24
Design
------
Why Lua
^^^^^^^
Lua is designed to be embedded in C applications. It is very small; the
standard library is 220K. It is relatively fast. It has a simple, minimal
syntax that is relatively easy to learn and can be understood by someone with
little to no programming experience. Moreover it is widely used to add
scripting capabilities to applications. In short it is designed for this task.
Reasons against supporting multiple scripting languages:
- Each language would require different FFI methods, and specifically
different object encoders; a lot of code
- Languages have different capabilities that would have to be brought to
parity with each other; a lot of work
- Languages have vastly different performance characteristics; this would
create alot of basically unfixable issues, and result in a single de facto
standard scripting language (the fastest)
- Each language would need a dedicated maintainer for the above reasons;
this is pragmatically difficult
- Supporting multiple languages fractures the community and limits the audience
with which a given script can be shared
General
^^^^^^^
FRR's concept of a script is somewhat abstracted away from the fact that it is
Lua underneath. A script in has two things:
- name
- state
In code:
.. code-block:: c
struct frrscript {
/* Script name */
char *name;
/* Lua state */
struct lua_State *L;
};
``name`` is simply a string. Everything else is in ``state``, which is itself a
Lua library object (``lua_State``). This is an opaque struct that is
manipulated using ``lua_*`` functions. The basic ones are imported from
``lua.h`` and the rest are implemented within FRR to fill our use cases. The
thing to remember is that all operations beyond the initial loading the script
take place on this opaque state object.
There are four basic actions that can be done on a script:
- load
- execute
- query state
- unload
They are typically done in this order.
Loading
^^^^^^^
A snippet of Lua code is referred to as a "chunk". These are simply text. FRR
presently assumes chunks are located in individual files specific to one task.
These files are stored in the scripts directory and must end in ``.lua``.
A script object is created by loading a script. This is done with
``frrscript_load()``. This function takes the name of the script and an
optional callback function. The string ".lua" is appended to the script name,
and the resultant filename is looked for in the scripts directory.
For example, to load ``/etc/frr/scripts/bingus.lua``:
.. code-block:: c
struct frrscript *fs = frrscript_load("bingus", NULL);
During loading the script is validated for syntax and its initial environment
is setup. By default this does not include the Lua standard library; there are
security issues to consider, though for practical purposes untrusted users
should not be able to write the scripts directory anyway. If desired the Lua
standard library may be added to the script environment using
``luaL_openlibs(fs->L)`` after loading the script. Further information on
setting up the script environment is in the Lua manual.
Executing
^^^^^^^^^
After loading, scripts may be executed. A script may take input in the form of
variable bindings set in its environment prior to being run, and may provide
results by setting the value of variables. Arbitrary C values may be
transferred into the script environment, including functions.
A typical execution call looks something like this:
.. code-block:: c
struct frrscript *fs = frrscript_load(...);
int status_ok = 0, status_fail = 1;
struct prefix p = ...;
struct frrscript_env env[] = {
{"integer", "STATUS_FAIL", &status_fail},
{"integer", "STATUS_OK", &status_ok},
{"prefix", "myprefix", &p},
{}};
int result = frrscript_call(fs, env);
To execute a loaded script, we need to define the inputs. These inputs are
passed by binding values to variable names that will be accessible within the
Lua environment. Basically, all communication with the script takes place via
global variables within the script, and to provide inputs we predefine globals
before the script runs. This is done by passing ``frrscript_call()`` an array
of ``struct frrscript_env``. Each struct has three fields. The first identifies
the type of the value being passed; more on this later. The second defines the
name of the global variable within the script environment to bind the third
argument (the value) to.
The script is then executed and returns a general status code. In the success
case this will be 0, otherwise it will be nonzero. The script itself does not
determine this code, it is provided by the Lua interpreter.
Querying State
^^^^^^^^^^^^^^
When a chunk is executed, its state at exit is preserved and can be inspected.
After running a script, results may be retrieved by querying the script's
state. Again this is done by retrieving the values of global variables, which
are known to the script author to be "output" variables.
A result is retrieved like so:
.. code-block:: c
struct frrscript_env myresult = {"string", "myresult"};
char *myresult = frrscript_get_result(fs, &myresult);
... do something ...
XFREE(MTYPE_TMP, myresult);
As with arguments, results are retrieved by providing a ``struct
frrscript_env`` specifying a type and a global name. No value is necessary, nor
is it modified by ``frrscript_get_result()``. That function simply extracts the
requested value from the script state and returns it.
In most cases the returned value will be allocated with ``MTYPE_TMP`` and will
need to be freed after use.
Unloading
^^^^^^^^^
To destroy a script and its associated state:
.. code-block:: c
frrscript_unload(fs);
Values returned by ``frrscript_get_result`` are still valid after the script
they were retrieved from is unloaded.
Note that you must unload and then load the script if you want to reset its
state, for example to run it again with different inputs. Otherwise the state
from the previous run carries over into subsequent runs.
.. _marshalling:
Marshalling
^^^^^^^^^^^
Earlier sections glossed over the meaning of the type name field in ``struct
frrscript_env`` and how data is passed between C and Lua. Lua, as a dynamically
typed, garbage collected language, cannot directly use C values without some
kind of marshalling / unmarshalling system to translate types between the two
runtimes.
Lua communicates with C code using a stack. C code wishing to provide data to
Lua scripts must provide a function that marshalls the C data into a Lua
representation and pushes it on the stack. C code wishing to retrieve data from
Lua must provide a corresponding unmarshalling function that retrieves a Lua
value from the stack and converts it to the corresponding C type. These two
functions, together with a chosen name of the type they operate on, are
referred to as ``codecs`` in FRR.
A codec is defined as:
.. code-block:: c
typedef void (*encoder_func)(lua_State *, const void *);
typedef void *(*decoder_func)(lua_State *, int);
struct frrscript_codec {
const char *typename;
encoder_func encoder;
decoder_func decoder;
};
A typename string and two function pointers.
``typename`` can be anything you want. For example, for the combined types of
``struct prefix`` and its equivalent in Lua I have chosen the name ``prefix``.
There is no restriction on naming here, it is just a human name used as a key
and specified when passing and retrieving values.
``encoder`` is a function that takes a ``lua_State *`` and a C type and pushes
onto the Lua stack a value representing the C type. For C structs, the usual
case, this will typically be a Lua table (tables are the only datastructure Lua
has). For example, here is the encoder function for ``struct prefix``:
.. code-block:: c
void lua_pushprefix(lua_State *L, const struct prefix *prefix)
{
char buffer[PREFIX_STRLEN];
zlog_debug("frrlua: pushing prefix table");
lua_newtable(L);
lua_pushstring(L, prefix2str(prefix, buffer, PREFIX_STRLEN));
lua_setfield(L, -2, "network");
lua_pushinteger(L, prefix->prefixlen);
lua_setfield(L, -2, "length");
lua_pushinteger(L, prefix->family);
lua_setfield(L, -2, "family");
}
This function pushes a single value onto the Lua stack. It is a table whose equivalent in Lua is:
.. code-block::
{ ["network"] = "1.2.3.4/24", ["prefixlen"] = 24, ["family"] = 2 }
``decoder`` does the reverse; it takes a ``lua_State *`` and an index into the
stack, and unmarshalls a Lua value there into the corresponding C type. Again
for ``struct prefix``:
.. code-block:: c
void *lua_toprefix(lua_State *L, int idx)
{
struct prefix *p = XCALLOC(MTYPE_TMP, sizeof(struct prefix));
lua_getfield(L, idx, "network");
str2prefix(lua_tostring(L, -1), p);
lua_pop(L, 1);
return p;
}
By convention these functions should be called ``lua_to*``, as this is the
naming convention used by the Lua C library for the basic types e.g.
``lua_tointeger`` and ``lua_tostring``.
The returned data must always be copied off the stack and the copy must be
allocated with ``MTYPE_TMP``. This way it is possible to unload the script
(destroy the state) without invalidating any references to values stored in it.
To register a new type with its corresponding encoding functions:
.. code-block:: c
struct frrscript_codec frrscript_codecs_lib[] = {
{.typename = "prefix",
.encoder = (encoder_func)lua_pushprefix,
.decoder = lua_toprefix},
{.typename = "sockunion",
.encoder = (encoder_func)lua_pushsockunion,
.decoder = lua_tosockunion},
...
{}};
frrscript_register_type_codecs(frrscript_codecs_lib);
From this point on the type names are available to be used when calling any
script and getting its results.
.. note::
Marshalled types are not restricted to simple values like integers, strings
and tables. It is possible to marshall a type such that the resultant object
in Lua is an actual object-oriented object, complete with methods that call
back into defined C functions. See the Lua manual for how to do this; for a
code example, look at how zlog is exported into the script environment.
Script Environment
------------------
Logging
^^^^^^^
For convenience, script environments are populated by default with a ``log``
object which contains methods corresponding to each of the ``zlog`` levels:
.. code-block:: lua
log.info("info")
log.warn("warn")
log.error("error")
log.notice("notice")
log.debug("debug")
The log messages will show up in the daemon's log output.
Examples
--------
For a complete code example involving passing custom types, retrieving results,
and doing complex calculations in Lua, look at the implementation of the
``match script SCRIPT`` command for BGP routemaps. This example calls into a
script with a route prefix and attributes received from a peer and expects the
script to return a match / no match / match and update result.
An example script to use with this follows. This script matches, does not match
or updates a route depending on how many BGP UPDATE messages the peer has
received when the script is called, simply as a demonstration of what can be
accomplished with scripting.
.. code-block:: lua
-- Example route map matching
-- author: qlyoung
--
-- The following variables are available to us:
-- log
-- logging library, with the usual functions
-- prefix
-- the route under consideration
-- attributes
-- the route's attributes
-- peer
-- the peer which received this route
-- RM_FAILURE
-- status code in case of failure
-- RM_NOMATCH
-- status code for no match
-- RM_MATCH
-- status code for match
-- RM_MATCH_AND_CHANGE
-- status code for match-and-set
--
-- We need to set the following out values:
-- action
-- Set to the appropriate status code to indicate what we did
-- attributes
-- Setting fields on here will propagate them back up to the caller if
-- 'action' is set to RM_MATCH_AND_CHANGE.
log.info("Evaluating route " .. prefix.network .. " from peer " .. peer.remote_id.string)
function on_match (prefix, attrs)
log.info("Match")
action = RM_MATCH
end
function on_nomatch (prefix, attrs)
log.info("No match")
action = RM_NOMATCH
end
function on_match_and_change (prefix, attrs)
action = RM_MATCH_AND_CHANGE
log.info("Match and change")
attrs["metric"] = attrs["metric"] + 7
end
special_routes = {
["172.16.10.4/24"] = on_match,
["172.16.13.1/8"] = on_nomatch,
["192.168.0.24/8"] = on_match_and_change,
}
if special_routes[prefix.network] then
special_routes[prefix.network](prefix, attributes)
elseif peer.stats.update_in % 3 == 0 then
on_match(prefix, attributes)
elseif peer.stats.update_in % 2 == 0 then
on_nomatch(prefix, attributes)
else
on_match_and_change(prefix, attributes)
end

View File

@ -37,7 +37,6 @@ dev_RSTFILES = \
doc/developer/lists.rst \
doc/developer/locking.rst \
doc/developer/logging.rst \
doc/developer/lua.rst \
doc/developer/memtypes.rst \
doc/developer/modules.rst \
doc/developer/next-hop-tracking.rst \
@ -52,6 +51,7 @@ dev_RSTFILES = \
doc/developer/path-internals.rst \
doc/developer/path.rst \
doc/developer/rcu.rst \
doc/developer/scripting.rst \
doc/developer/static-linking.rst \
doc/developer/tracing.rst \
doc/developer/testing.rst \

View File

@ -29,6 +29,7 @@ Basics
ipv6
kernel
snmp
scripting
.. modules
#########

View File

@ -362,6 +362,10 @@ options from the list below.
Set hardcoded rpaths in the executable [default=yes].
.. option:: --enable-scripting
Enable Lua scripting [default=no].
You may specify any combination of the above options to the configure
script. By default, the executables are placed in :file:`/usr/local/sbin`
and the configuration files in :file:`/usr/local/etc`. The :file:`/usr/local/`
@ -382,6 +386,10 @@ options to the configuration script.
Configure zebra to use `dir` for local state files, such as pid files and
unix sockets.
.. option:: --with-scriptdir <dir>
Look for Lua scripts in ``dir`` [``prefix``/etc/frr/scripts].
.. option:: --with-yangmodelsdir <dir>
Look for YANG modules in `dir` [`prefix`/share/yang]. Note that the FRR

28
doc/user/scripting.rst Normal file
View File

@ -0,0 +1,28 @@
.. _scripting:
*********
Scripting
*********
The behavior of FRR may be extended or customized using its built-in scripting
capabilities.
Some configuration commands accept the name of a Lua script to call to perform
some task or make some decision. These scripts have their environments
populated with some set of inputs, and are expected to populate some set of
output variables, which are read by FRR after the script completes. The names
and expected contents of these scripts are documented alongside the commands
that support them.
These scripts live in :file:`/etc/frr/scripts/` by default. This is
configurable at compile time via ``--with-scriptdir``. It may be
overriden at runtime with the ``--scriptdir`` daemon option.
In order to use scripting, FRR must be built with ``--enable-scripting``.
.. note::
Scripts are typically loaded just-in-time. This means you can change the
contents of a script that is in use without restarting FRR. Not all
scripting locations may behave this way; refer to the documentation for the
particular location.

View File

@ -35,6 +35,7 @@ user_RSTFILES = \
doc/user/routemap.rst \
doc/user/routeserver.rst \
doc/user/rpki.rst \
doc/user/scripting.rst \
doc/user/setup.rst \
doc/user/sharp.rst \
doc/user/snmp.rst \

View File

@ -49,6 +49,8 @@
#include "northbound_cli.h"
#include "network.h"
#include "frrscript.h"
DEFINE_MTYPE_STATIC(LIB, HOST, "Host config")
DEFINE_MTYPE(LIB, COMPLETION, "Completion item")
@ -2303,6 +2305,30 @@ done:
return CMD_SUCCESS;
}
#if defined(DEV_BUILD) && defined(HAVE_SCRIPTING)
DEFUN(script,
script_cmd,
"script SCRIPT",
"Test command - execute a script\n"
"Script name (same as filename in /etc/frr/scripts/\n")
{
struct prefix p;
str2prefix("1.2.3.4/24", &p);
struct frrscript *fs = frrscript_load(argv[1]->arg, NULL);
if (fs == NULL) {
vty_out(vty, "Script '/etc/frr/scripts/%s.lua' not found\n",
argv[1]->arg);
} else {
int ret = frrscript_call(fs, NULL);
vty_out(vty, "Script result: %d\n", ret);
}
return CMD_SUCCESS;
}
#endif
/* Set config filename. Called from vty.c */
void host_config_set(const char *filename)
{
@ -2397,6 +2423,10 @@ void cmd_init(int terminal)
install_element(VIEW_NODE, &echo_cmd);
install_element(VIEW_NODE, &autocomplete_cmd);
install_element(VIEW_NODE, &find_cmd);
#if defined(DEV_BUILD) && defined(HAVE_SCRIPTING)
install_element(VIEW_NODE, &script_cmd);
#endif
install_element(ENABLE_NODE, &config_end_cmd);
install_element(ENABLE_NODE, &config_disable_cmd);

View File

@ -279,6 +279,29 @@ extern "C" {
#define array_size(ar) (sizeof(ar) / sizeof(ar[0]))
/* Some insane macros to count number of varargs to a functionlike macro */
#define PP_ARG_N( \
_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, N, ...) N
#define PP_RSEQ_N() \
62, 61, 60, \
59, 58, 57, 56, 55, 54, 53, 52, 51, 50, \
49, 48, 47, 46, 45, 44, 43, 42, 41, 40, \
39, 38, 37, 36, 35, 34, 33, 32, 31, 30, \
29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \
19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \
9, 8, 7, 6, 5, 4, 3, 2, 1, 0
#define PP_NARG_(...) PP_ARG_N(__VA_ARGS__)
#define PP_NARG(...) PP_NARG_(_, ##__VA_ARGS__, PP_RSEQ_N())
/* sigh. this is so ugly, it overflows and wraps to being nice again.
*
* printfrr() supports "%Ld" for <int64_t>, whatever that is typedef'd to.

View File

@ -2,57 +2,43 @@
* This file defines the lua interface into
* FRRouting.
*
* Copyright (C) 2016 Cumulus Networks, Inc.
* Donald Sharp
* Copyright (C) 2016-2019 Cumulus Networks, Inc.
* Donald Sharp, Quentin Young
*
* This file is part of FRRouting (FRR).
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* FRR is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2, or (at your option) any later version.
*
* FRR is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with FRR; see the file COPYING. If not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* with this program; see the file COPYING; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <zebra.h>
#if defined(HAVE_LUA)
#ifdef HAVE_SCRIPTING
#include "prefix.h"
#include "frrlua.h"
#include "log.h"
#include "buffer.h"
static int lua_zlog_debug(lua_State *L)
{
int debug_lua = 1;
const char *string = lua_tostring(L, 1);
/* Lua stuff */
if (debug_lua)
zlog_debug("%s", string);
/*
* FRR convenience functions.
*
* This section has convenience functions used to make interacting with the Lua
* stack easier.
*/
return 0;
}
const char *get_string(lua_State *L, const char *key)
{
const char *str;
lua_pushstring(L, key);
lua_gettable(L, -2);
str = (const char *)lua_tostring(L, -1);
lua_pop(L, 1);
return str;
}
int get_integer(lua_State *L, const char *key)
int frrlua_table_get_integer(lua_State *L, const char *key)
{
int result;
@ -65,65 +51,316 @@ int get_integer(lua_State *L, const char *key)
return result;
}
static void *lua_alloc(void *ud, void *ptr, size_t osize,
size_t nsize)
/*
* Encoders.
*
* This section has functions that convert internal FRR datatypes into Lua
* datatypes.
*/
void lua_pushprefix(lua_State *L, const struct prefix *prefix)
{
(void)ud; (void)osize; /* not used */
if (nsize == 0) {
free(ptr);
return NULL;
} else
return realloc(ptr, nsize);
}
lua_State *lua_initialize(const char *file)
{
int status;
lua_State *L = lua_newstate(lua_alloc, NULL);
zlog_debug("Newstate: %p", L);
luaL_openlibs(L);
zlog_debug("Opened lib");
status = luaL_loadfile(L, file);
if (status) {
zlog_debug("Failure to open %s %d", file, status);
lua_close(L);
return NULL;
}
lua_pcall(L, 0, LUA_MULTRET, 0);
zlog_debug("Setting global function");
lua_pushcfunction(L, lua_zlog_debug);
lua_setglobal(L, "zlog_debug");
return L;
}
void lua_setup_prefix_table(lua_State *L, const struct prefix *prefix)
{
char buffer[100];
char buffer[PREFIX_STRLEN];
lua_newtable(L);
lua_pushstring(L, prefix2str(prefix, buffer, 100));
lua_setfield(L, -2, "route");
lua_pushstring(L, prefix2str(prefix, buffer, PREFIX_STRLEN));
lua_setfield(L, -2, "network");
lua_pushinteger(L, prefix->prefixlen);
lua_setfield(L, -2, "length");
lua_pushinteger(L, prefix->family);
lua_setfield(L, -2, "family");
lua_setglobal(L, "prefix");
}
enum lua_rm_status lua_run_rm_rule(lua_State *L, const char *rule)
void *lua_toprefix(lua_State *L, int idx)
{
int status;
struct prefix *p = XCALLOC(MTYPE_TMP, sizeof(struct prefix));
lua_getglobal(L, rule);
status = lua_pcall(L, 0, 1, 0);
if (status) {
zlog_debug("Executing Failure with function: %s: %d",
rule, status);
return LUA_RM_FAILURE;
lua_getfield(L, idx, "network");
str2prefix(lua_tostring(L, -1), p);
lua_pop(L, 1);
return p;
}
void lua_pushinterface(lua_State *L, const struct interface *ifp)
{
lua_newtable(L);
lua_pushstring(L, ifp->name);
lua_setfield(L, -2, "name");
lua_pushinteger(L, ifp->ifindex);
lua_setfield(L, -2, "ifindex");
lua_pushinteger(L, ifp->status);
lua_setfield(L, -2, "status");
lua_pushinteger(L, ifp->flags);
lua_setfield(L, -2, "flags");
lua_pushinteger(L, ifp->metric);
lua_setfield(L, -2, "metric");
lua_pushinteger(L, ifp->speed);
lua_setfield(L, -2, "speed");
lua_pushinteger(L, ifp->mtu);
lua_setfield(L, -2, "mtu");
lua_pushinteger(L, ifp->mtu6);
lua_setfield(L, -2, "mtu6");
lua_pushinteger(L, ifp->bandwidth);
lua_setfield(L, -2, "bandwidth");
lua_pushinteger(L, ifp->link_ifindex);
lua_setfield(L, -2, "link_ifindex");
lua_pushinteger(L, ifp->ll_type);
lua_setfield(L, -2, "linklayer_type");
}
void *lua_tointerface(lua_State *L, int idx)
{
struct interface *ifp = XCALLOC(MTYPE_TMP, sizeof(struct interface));
lua_getfield(L, idx, "name");
strlcpy(ifp->name, lua_tostring(L, -1), sizeof(ifp->name));
lua_pop(L, 1);
lua_getfield(L, idx, "ifindex");
ifp->ifindex = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, idx, "status");
ifp->status = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, idx, "flags");
ifp->flags = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, idx, "metric");
ifp->metric = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, idx, "speed");
ifp->speed = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, idx, "mtu");
ifp->mtu = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, idx, "mtu6");
ifp->mtu6 = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, idx, "bandwidth");
ifp->bandwidth = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, idx, "link_ifindex");
ifp->link_ifindex = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, idx, "linklayer_type");
ifp->ll_type = lua_tointeger(L, -1);
lua_pop(L, 1);
return ifp;
}
void lua_pushinaddr(lua_State *L, const struct in_addr *addr)
{
char buf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, addr, buf, sizeof(buf));
lua_newtable(L);
lua_pushinteger(L, addr->s_addr);
lua_setfield(L, -2, "value");
lua_pushstring(L, buf);
lua_setfield(L, -2, "string");
}
void *lua_toinaddr(lua_State *L, int idx)
{
struct in_addr *inaddr = XCALLOC(MTYPE_TMP, sizeof(struct in_addr));
lua_getfield(L, idx, "value");
inaddr->s_addr = lua_tointeger(L, -1);
lua_pop(L, 1);
return inaddr;
}
void lua_pushin6addr(lua_State *L, const struct in6_addr *addr)
{
char buf[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, addr, buf, sizeof(buf));
lua_newtable(L);
lua_pushlstring(L, (const char *)addr->s6_addr, 16);
lua_setfield(L, -2, "value");
lua_pushstring(L, buf);
lua_setfield(L, -2, "string");
}
void *lua_toin6addr(lua_State *L, int idx)
{
struct in6_addr *in6addr = XCALLOC(MTYPE_TMP, sizeof(struct in6_addr));
lua_getfield(L, idx, "string");
inet_pton(AF_INET6, lua_tostring(L, -1), in6addr);
lua_pop(L, 1);
return in6addr;
}
void lua_pushsockunion(lua_State *L, const union sockunion *su)
{
char buf[SU_ADDRSTRLEN];
sockunion2str(su, buf, sizeof(buf));
lua_newtable(L);
lua_pushlstring(L, (const char *)sockunion_get_addr(su),
sockunion_get_addrlen(su));
lua_setfield(L, -2, "value");
lua_pushstring(L, buf);
lua_setfield(L, -2, "string");
}
void *lua_tosockunion(lua_State *L, int idx)
{
union sockunion *su = XCALLOC(MTYPE_TMP, sizeof(union sockunion));
lua_getfield(L, idx, "string");
str2sockunion(lua_tostring(L, -1), su);
return su;
}
void lua_pushtimet(lua_State *L, const time_t *time)
{
lua_pushinteger(L, *time);
}
void *lua_totimet(lua_State *L, int idx)
{
time_t *t = XCALLOC(MTYPE_TMP, sizeof(time_t));
*t = lua_tointeger(L, idx);
return t;
}
void lua_pushintegerp(lua_State *L, const long long *num)
{
lua_pushinteger(L, *num);
}
void *lua_tointegerp(lua_State *L, int idx)
{
int isnum;
long long *num = XCALLOC(MTYPE_TMP, sizeof(long long));
*num = lua_tonumberx(L, idx, &isnum);
assert(isnum);
return num;
}
void *lua_tostringp(lua_State *L, int idx)
{
char *string = XSTRDUP(MTYPE_TMP, lua_tostring(L, idx));
return string;
}
/*
* Logging.
*
* Lua-compatible wrappers for FRR logging functions.
*/
static const char *frrlua_log_thunk(lua_State *L)
{
int nargs;
nargs = lua_gettop(L);
assert(nargs == 1);
return lua_tostring(L, 1);
}
static int frrlua_log_debug(lua_State *L)
{
zlog_debug("%s", frrlua_log_thunk(L));
return 0;
}
static int frrlua_log_info(lua_State *L)
{
zlog_info("%s", frrlua_log_thunk(L));
return 0;
}
static int frrlua_log_notice(lua_State *L)
{
zlog_notice("%s", frrlua_log_thunk(L));
return 0;
}
static int frrlua_log_warn(lua_State *L)
{
zlog_warn("%s", frrlua_log_thunk(L));
return 0;
}
static int frrlua_log_error(lua_State *L)
{
zlog_err("%s", frrlua_log_thunk(L));
return 0;
}
static const luaL_Reg log_funcs[] = {
{"debug", frrlua_log_debug},
{"info", frrlua_log_info},
{"notice", frrlua_log_notice},
{"warn", frrlua_log_warn},
{"error", frrlua_log_error},
{},
};
void frrlua_export_logging(lua_State *L)
{
lua_newtable(L);
luaL_setfuncs(L, log_funcs, 0);
lua_setglobal(L, "log");
}
/*
* Debugging.
*/
char *frrlua_stackdump(lua_State *L)
{
int top = lua_gettop(L);
char tmpbuf[64];
struct buffer *buf = buffer_new(4098);
for (int i = 1; i <= top; i++) {
int t = lua_type(L, i);
switch (t) {
case LUA_TSTRING: /* strings */
snprintf(tmpbuf, sizeof(tmpbuf), "\"%s\"\n",
lua_tostring(L, i));
buffer_putstr(buf, tmpbuf);
break;
case LUA_TBOOLEAN: /* booleans */
snprintf(tmpbuf, sizeof(tmpbuf), "%s\n",
lua_toboolean(L, i) ? "true" : "false");
buffer_putstr(buf, tmpbuf);
break;
case LUA_TNUMBER: /* numbers */
snprintf(tmpbuf, sizeof(tmpbuf), "%g\n",
lua_tonumber(L, i));
buffer_putstr(buf, tmpbuf);
break;
default: /* other values */
snprintf(tmpbuf, sizeof(tmpbuf), "%s\n",
lua_typename(L, t));
buffer_putstr(buf, tmpbuf);
break;
}
}
status = lua_tonumber(L, -1);
return status;
char *result = XSTRDUP(MTYPE_TMP, buffer_getstr(buf));
buffer_free(buf);
return result;
}
#endif
#endif /* HAVE_SCRIPTING */

View File

@ -1,88 +1,173 @@
/*
* This file defines the lua interface into
* FRRouting.
* Copyright (C) 2016-2019 Cumulus Networks, Inc.
* Donald Sharp, Quentin Young
*
* Copyright (C) 2016 Cumulus Networks, Inc.
* Donald Sharp
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This file is part of FRRouting (FRR).
*
* FRR is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2, or (at your option) any later version.
*
* FRR is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with FRR; see the file COPYING. If not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* with this program; see the file COPYING; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef __LUA_H__
#define __LUA_H__
#ifndef __FRRLUA_H__
#define __FRRLUA_H__
#if defined(HAVE_LUA)
#include <zebra.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#ifdef HAVE_SCRIPTING
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include "prefix.h"
#include "frrscript.h"
#ifdef __cplusplus
extern "C" {
#endif
/*
* These functions are helper functions that
* try to glom some of the lua_XXX functionality
* into what we actually need, instead of having
* to make multiple calls to set up what
* we want
* Converts a prefix to a Lua value and pushes it on the stack.
*/
enum lua_rm_status {
/*
* Script function run failure. This will translate into a
* deny
*/
LUA_RM_FAILURE = 0,
/*
* No Match was found for the route map function
*/
LUA_RM_NOMATCH,
/*
* Match was found but no changes were made to the
* incoming data.
*/
LUA_RM_MATCH,
/*
* Match was found and data was modified, so
* figure out what changed
*/
LUA_RM_MATCH_AND_CHANGE,
};
void lua_pushprefix(lua_State *L, const struct prefix *prefix);
/*
* Open up the lua.scr file and parse
* initial global values, if any.
* Converts the Lua value at idx to a prefix.
*
* Returns:
* struct prefix allocated with MTYPE_TMP
*/
lua_State *lua_initialize(const char *file);
void lua_setup_prefix_table(lua_State *L, const struct prefix *prefix);
enum lua_rm_status lua_run_rm_rule(lua_State *L, const char *rule);
void *lua_toprefix(lua_State *L, int idx);
/*
* Get particular string/integer information
* from a table. It is *assumed* that
* the table has already been selected
* Converts an interface to a Lua value and pushes it on the stack.
*/
const char *get_string(lua_State *L, const char *key);
int get_integer(lua_State *L, const char *key);
void lua_pushinterface(lua_State *L, const struct interface *ifp);
/*
* Converts the Lua value at idx to an interface.
*
* Returns:
* struct interface allocated with MTYPE_TMP. This interface is not hooked
* to anything, nor is it inserted in the global interface tree.
*/
void *lua_tointerface(lua_State *L, int idx);
/*
* Converts an in_addr to a Lua value and pushes it on the stack.
*/
void lua_pushinaddr(lua_State *L, const struct in_addr *addr);
/*
* Converts the Lua value at idx to an in_addr.
*
* Returns:
* struct in_addr allocated with MTYPE_TMP.
*/
void *lua_toinaddr(lua_State *L, int idx);
/*
* Converts an in6_addr to a Lua value and pushes it on the stack.
*/
void lua_pushin6addr(lua_State *L, const struct in6_addr *addr);
/*
* Converts the Lua value at idx to an in6_addr.
*
* Returns:
* struct in6_addr allocated with MTYPE_TMP.
*/
void *lua_toin6addr(lua_State *L, int idx);
/*
* Converts a time_t to a Lua value and pushes it on the stack.
*/
void lua_pushtimet(lua_State *L, const time_t *time);
/*
* Converts the Lua value at idx to a time_t.
*
* Returns:
* time_t allocated with MTYPE_TMP.
*/
void *lua_totimet(lua_State *L, int idx);
/*
* Converts a sockunion to a Lua value and pushes it on the stack.
*/
void lua_pushsockunion(lua_State *L, const union sockunion *su);
/*
* Converts the Lua value at idx to a sockunion.
*
* Returns:
* sockunion allocated with MTYPE_TMP.
*/
void *lua_tosockunion(lua_State *L, int idx);
/*
* Converts an int to a Lua value and pushes it on the stack.
*/
void lua_pushintegerp(lua_State *L, const long long *num);
/*
* Converts the Lua value at idx to an int.
*
* Returns:
* int allocated with MTYPE_TMP.
*/
void *lua_tointegerp(lua_State *L, int idx);
/*
* Pop string.
*
* Sets *string to a copy of the string at the top of the stack. The copy is
* allocated with MTYPE_TMP and the caller is responsible for freeing it.
*/
void *lua_tostringp(lua_State *L, int idx);
/*
* Retrieve an integer from table on the top of the stack.
*
* key
* Key of string value in table
*/
int frrlua_table_get_integer(lua_State *L, const char *key);
/*
* Exports a new table containing bindings to FRR zlog functions into the
* global namespace.
*
* From Lua, these functions may be accessed as:
*
* - log.debug()
* - log.info()
* - log.warn()
* - log.error()
*
* They take a single string argument.
*/
void frrlua_export_logging(lua_State *L);
/*
* Dump Lua stack to a string.
*
* Return value must be freed with XFREE(MTYPE_TMP, ...);
*/
char *frrlua_stackdump(lua_State *L);
#ifdef __cplusplus
}
#endif
#endif
#endif
#endif /* HAVE_SCRIPTING */
#endif /* __FRRLUA_H__ */

272
lib/frrscript.c Normal file
View File

@ -0,0 +1,272 @@
/* Scripting foo
* Copyright (C) 2020 NVIDIA Corporation
* Quentin Young
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; see the file COPYING; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <zebra.h>
#ifdef HAVE_SCRIPTING
#include <stdarg.h>
#include <lua.h>
#include "frrscript.h"
#include "frrlua.h"
#include "memory.h"
#include "hash.h"
#include "log.h"
DEFINE_MTYPE_STATIC(LIB, SCRIPT, "Scripting");
/* Codecs */
struct frrscript_codec frrscript_codecs_lib[] = {
{.typename = "integer",
.encoder = (encoder_func)lua_pushintegerp,
.decoder = lua_tointegerp},
{.typename = "string",
.encoder = (encoder_func)lua_pushstring,
.decoder = lua_tostringp},
{.typename = "prefix",
.encoder = (encoder_func)lua_pushprefix,
.decoder = lua_toprefix},
{.typename = "interface",
.encoder = (encoder_func)lua_pushinterface,
.decoder = lua_tointerface},
{.typename = "in_addr",
.encoder = (encoder_func)lua_pushinaddr,
.decoder = lua_toinaddr},
{.typename = "in6_addr",
.encoder = (encoder_func)lua_pushin6addr,
.decoder = lua_toin6addr},
{.typename = "sockunion",
.encoder = (encoder_func)lua_pushsockunion,
.decoder = lua_tosockunion},
{.typename = "time_t",
.encoder = (encoder_func)lua_pushtimet,
.decoder = lua_totimet},
{}};
/* Type codecs */
struct hash *codec_hash;
char scriptdir[MAXPATHLEN];
static unsigned int codec_hash_key(const void *data)
{
const struct frrscript_codec *c = data;
return string_hash_make(c->typename);
}
static bool codec_hash_cmp(const void *d1, const void *d2)
{
const struct frrscript_codec *e1 = d1;
const struct frrscript_codec *e2 = d2;
return strmatch(e1->typename, e2->typename);
}
static void *codec_alloc(void *arg)
{
struct frrscript_codec *tmp = arg;
struct frrscript_codec *e =
XCALLOC(MTYPE_SCRIPT, sizeof(struct frrscript_codec));
e->typename = XSTRDUP(MTYPE_SCRIPT, tmp->typename);
e->encoder = tmp->encoder;
e->decoder = tmp->decoder;
return e;
}
#if 0
static void codec_free(struct codec *c)
{
XFREE(MTYPE_TMP, c->typename);
XFREE(MTYPE_TMP, c);
}
#endif
/* Generic script APIs */
int frrscript_call(struct frrscript *fs, struct frrscript_env *env)
{
struct frrscript_codec c = {};
const void *arg;
const char *bindname;
/* Encode script arguments */
for (int i = 0; env && env[i].val != NULL; i++) {
bindname = env[i].name;
c.typename = env[i].typename;
arg = env[i].val;
struct frrscript_codec *codec = hash_lookup(codec_hash, &c);
assert(codec && "No encoder for type");
codec->encoder(fs->L, arg);
lua_setglobal(fs->L, bindname);
}
int ret = lua_pcall(fs->L, 0, 0, 0);
switch (ret) {
case LUA_OK:
break;
case LUA_ERRRUN:
zlog_err("Script '%s' runtime error: %s", fs->name,
lua_tostring(fs->L, -1));
break;
case LUA_ERRMEM:
zlog_err("Script '%s' memory error: %s", fs->name,
lua_tostring(fs->L, -1));
break;
case LUA_ERRERR:
zlog_err("Script '%s' error handler error: %s", fs->name,
lua_tostring(fs->L, -1));
break;
case LUA_ERRGCMM:
zlog_err("Script '%s' garbage collector error: %s", fs->name,
lua_tostring(fs->L, -1));
break;
default:
zlog_err("Script '%s' unknown error: %s", fs->name,
lua_tostring(fs->L, -1));
break;
}
if (ret != LUA_OK) {
lua_pop(fs->L, 1);
goto done;
}
done:
/* LUA_OK is 0, so we can just return lua_pcall's result directly */
return ret;
}
void *frrscript_get_result(struct frrscript *fs,
const struct frrscript_env *result)
{
void *r;
struct frrscript_codec c = {.typename = result->typename};
struct frrscript_codec *codec = hash_lookup(codec_hash, &c);
assert(codec && "No encoder for type");
if (!codec->decoder) {
zlog_err("No script decoder for type '%s'", result->typename);
return NULL;
}
lua_getglobal(fs->L, result->name);
r = codec->decoder(fs->L, -1);
lua_pop(fs->L, 1);
return r;
}
void frrscript_register_type_codec(struct frrscript_codec *codec)
{
struct frrscript_codec c = *codec;
if (hash_lookup(codec_hash, &c)) {
zlog_backtrace(LOG_ERR);
assert(!"Type codec double-registered.");
}
assert(hash_get(codec_hash, &c, codec_alloc));
}
void frrscript_register_type_codecs(struct frrscript_codec *codecs)
{
for (int i = 0; codecs[i].typename != NULL; i++)
frrscript_register_type_codec(&codecs[i]);
}
struct frrscript *frrscript_load(const char *name,
int (*load_cb)(struct frrscript *))
{
struct frrscript *fs = XCALLOC(MTYPE_SCRIPT, sizeof(struct frrscript));
fs->name = XSTRDUP(MTYPE_SCRIPT, name);
fs->L = luaL_newstate();
frrlua_export_logging(fs->L);
char fname[MAXPATHLEN];
snprintf(fname, sizeof(fname), "%s/%s.lua", scriptdir, fs->name);
int ret = luaL_loadfile(fs->L, fname);
switch (ret) {
case LUA_OK:
break;
case LUA_ERRSYNTAX:
zlog_err("Failed loading script '%s': syntax error: %s", fname,
lua_tostring(fs->L, -1));
break;
case LUA_ERRMEM:
zlog_err("Failed loading script '%s': out-of-memory error: %s",
fname, lua_tostring(fs->L, -1));
break;
case LUA_ERRGCMM:
zlog_err(
"Failed loading script '%s': garbage collector error: %s",
fname, lua_tostring(fs->L, -1));
break;
case LUA_ERRFILE:
zlog_err("Failed loading script '%s': file read error: %s",
fname, lua_tostring(fs->L, -1));
break;
default:
zlog_err("Failed loading script '%s': unknown error: %s", fname,
lua_tostring(fs->L, -1));
break;
}
if (ret != LUA_OK)
goto fail;
if (load_cb && (*load_cb)(fs) != 0)
goto fail;
return fs;
fail:
frrscript_unload(fs);
return NULL;
}
void frrscript_unload(struct frrscript *fs)
{
lua_close(fs->L);
XFREE(MTYPE_SCRIPT, fs->name);
XFREE(MTYPE_SCRIPT, fs);
}
void frrscript_init(const char *sd)
{
codec_hash = hash_create(codec_hash_key, codec_hash_cmp,
"Lua type encoders");
strlcpy(scriptdir, sd, sizeof(scriptdir));
/* Register core library types */
frrscript_register_type_codecs(frrscript_codecs_lib);
}
#endif /* HAVE_SCRIPTING */

138
lib/frrscript.h Normal file
View File

@ -0,0 +1,138 @@
/* Scripting foo
* Copyright (C) 2020 NVIDIA Corporation
* Quentin Young
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; see the file COPYING; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef __FRRSCRIPT_H__
#define __FRRSCRIPT_H__
#include <zebra.h>
#ifdef HAVE_SCRIPTING
#include <lua.h>
#include "frrlua.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*encoder_func)(lua_State *, const void *);
typedef void *(*decoder_func)(lua_State *, int);
struct frrscript_codec {
const char *typename;
encoder_func encoder;
decoder_func decoder;
};
struct frrscript {
/* Script name */
char *name;
/* Lua state */
struct lua_State *L;
};
struct frrscript_env {
/* Value type */
const char *typename;
/* Binding name */
const char *name;
/* Value */
const void *val;
};
/*
* Create new FRR script.
*/
struct frrscript *frrscript_load(const char *name,
int (*load_cb)(struct frrscript *));
/*
* Destroy FRR script.
*/
void frrscript_unload(struct frrscript *fs);
/*
* Register a Lua codec for a type.
*
* tname
* Name of type; e.g., "peer", "ospf_interface", etc. Chosen at will.
*
* codec(s)
* Function pointer to codec struct. Encoder function should push a Lua
* table representing the passed argument - which will have the C type
* associated with the chosen 'tname' to the provided stack. The decoder
* function should pop a value from the top of the stack and return a heap
* chunk containing that value. Allocations should be made with MTYPE_TMP.
*
* If using the plural function variant, pass a NULL-terminated array.
*
*/
void frrscript_register_type_codec(struct frrscript_codec *codec);
void frrscript_register_type_codecs(struct frrscript_codec *codecs);
/*
* Initialize scripting subsystem. Call this before anything else.
*
* scriptdir
* Directory in which to look for scripts
*/
void frrscript_init(const char *scriptdir);
/*
* Call script.
*
* fs
* The script to call; this is obtained from frrscript_load().
*
* env
* The script's environment. Specify this as an array of frrscript_env.
*
* Returns:
* 0 if the script ran successfully, nonzero otherwise.
*/
int frrscript_call(struct frrscript *fs, struct frrscript_env *env);
/*
* Get result from finished script.
*
* fs
* The script. This script must have been run already.
*
* result
* The result to extract from the script.
* This reuses the frrscript_env type, but only the typename and name fields
* need to be set. The value is returned directly.
*
* Returns:
* The script result of the specified name and type, or NULL.
*/
void *frrscript_get_result(struct frrscript *fs,
const struct frrscript_env *result);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* HAVE_SCRIPTING */
#endif /* __FRRSCRIPT_H__ */

View File

@ -43,6 +43,7 @@
#include "frrcu.h"
#include "frr_pthread.h"
#include "defaults.h"
#include "frrscript.h"
DEFINE_HOOK(frr_late_init, (struct thread_master * tm), (tm))
DEFINE_HOOK(frr_very_late_init, (struct thread_master * tm), (tm))
@ -55,6 +56,7 @@ char frr_vtydir[256];
const char frr_dbdir[] = DAEMON_DB_DIR;
#endif
const char frr_moduledir[] = MODULE_PATH;
const char frr_scriptdir[] = SCRIPT_PATH;
char frr_protoname[256] = "NONE";
char frr_protonameinst[256] = "NONE";
@ -100,6 +102,7 @@ static void opt_extend(const struct optspec *os)
#define OPTION_DB_FILE 1006
#define OPTION_LOGGING 1007
#define OPTION_LIMIT_FDS 1008
#define OPTION_SCRIPTDIR 1009
static const struct option lo_always[] = {
{"help", no_argument, NULL, 'h'},
@ -110,6 +113,7 @@ static const struct option lo_always[] = {
{"pathspace", required_argument, NULL, 'N'},
{"vty_socket", required_argument, NULL, OPTION_VTYSOCK},
{"moduledir", required_argument, NULL, OPTION_MODULEDIR},
{"scriptdir", required_argument, NULL, OPTION_SCRIPTDIR},
{"log", required_argument, NULL, OPTION_LOG},
{"log-level", required_argument, NULL, OPTION_LOGLEVEL},
{"tcli", no_argument, NULL, OPTION_TCLI},
@ -126,6 +130,7 @@ static const struct optspec os_always = {
" -N, --pathspace Insert prefix into config & socket paths\n"
" --vty_socket Override vty socket path\n"
" --moduledir Override modules directory\n"
" --scriptdir Override scripts directory\n"
" --log Set Logging to stdout, syslog, or file:<name>\n"
" --log-level Set Logging Level to use, debug, info, warn, etc\n"
" --tcli Use transaction-based CLI\n"
@ -533,6 +538,14 @@ static int frr_opt(int opt)
}
di->module_path = optarg;
break;
case OPTION_SCRIPTDIR:
if (di->script_path) {
fprintf(stderr, "--scriptdir option specified more than once!\n");
errors++;
break;
}
di->script_path = optarg;
break;
case OPTION_TCLI:
di->cli_mode = FRR_CLI_TRANSACTIONAL;
break;
@ -717,6 +730,9 @@ struct thread_master *frr_init(void)
lib_cmd_init();
frr_pthread_init();
#ifdef HAVE_SCRIPTING
frrscript_init(di->script_path ? di->script_path : frr_scriptdir);
#endif
log_ref_init();
log_ref_vty_init();

View File

@ -81,6 +81,7 @@ struct frr_daemon_info {
#endif
const char *vty_path;
const char *module_path;
const char *script_path;
const char *pathspace;
bool zpathspace;
@ -162,6 +163,7 @@ extern char frr_zclientpath[256];
extern const char frr_sysconfdir[];
extern char frr_vtydir[256];
extern const char frr_moduledir[];
extern const char frr_scriptdir[];
extern char frr_protoname[];
extern char frr_protonameinst[];

View File

@ -26,6 +26,7 @@ lib_libfrr_la_SOURCES = \
lib/filter_nb.c \
lib/frrcu.c \
lib/frrlua.c \
lib/frrscript.c \
lib/frr_pthread.c \
lib/frrstr.c \
lib/getopt.c \
@ -185,6 +186,7 @@ pkginclude_HEADERS += \
lib/filter.h \
lib/freebsd-queue.h \
lib/frrlua.h \
lib/frrscript.h \
lib/frr_pthread.h \
lib/frratomic.h \
lib/frrcu.h \