diff options
81 files changed, 3313 insertions, 464 deletions
diff --git a/bfdd/bfd.c b/bfdd/bfd.c index 9667ba8708..ca4bf94955 100644 --- a/bfdd/bfd.c +++ b/bfdd/bfd.c @@ -313,6 +313,13 @@ int bfd_session_enable(struct bfd_session *bs) } } + if (!vrf_is_backend_netns() && vrf && vrf->vrf_id != VRF_DEFAULT + && !if_lookup_by_name(vrf->name, vrf->vrf_id)) { + zlog_err("session-enable: vrf interface %s not available yet", + vrf->name); + return 0; + } + if (bs->key.ifname[0]) { if (vrf) ifp = if_lookup_by_name(bs->key.ifname, vrf->vrf_id); @@ -320,14 +327,16 @@ int bfd_session_enable(struct bfd_session *bs) ifp = if_lookup_by_name_all_vrf(bs->key.ifname); if (ifp == NULL) { zlog_err( - "session-enable: specified interface doesn't exists."); + "session-enable: specified interface %s (VRF %s) doesn't exist.", + bs->key.ifname, vrf ? vrf->name : "<all>"); return 0; } if (bs->key.ifname[0] && !vrf) { vrf = vrf_lookup_by_id(ifp->vrf_id); if (vrf == NULL) { zlog_err( - "session-enable: specified VRF doesn't exists."); + "session-enable: specified VRF %u doesn't exist.", + ifp->vrf_id); return 0; } } diff --git a/bfdd/bfd_packet.c b/bfdd/bfd_packet.c index 0a71c18a42..076318e6ca 100644 --- a/bfdd/bfd_packet.c +++ b/bfdd/bfd_packet.c @@ -543,6 +543,7 @@ int bfd_recv_cb(struct thread *t) ifindex_t ifindex = IFINDEX_INTERNAL; struct sockaddr_any local, peer; uint8_t msgbuf[1516]; + struct interface *ifp = NULL; struct bfd_vrf_global *bvrf = THREAD_ARG(t); vrfid = bvrf->vrf->vrf_id; @@ -572,6 +573,15 @@ int bfd_recv_cb(struct thread *t) &local, &peer); } + /* update vrf-id because when in vrf-lite mode, + * the socket is on default namespace + */ + if (ifindex) { + ifp = if_lookup_by_index(ifindex, vrfid); + if (ifp) + vrfid = ifp->vrf_id; + } + /* Implement RFC 5880 6.8.6 */ if (mlen < BFD_PKT_LEN) { cp_debug(is_mhop, &peer, &local, ifindex, vrfid, @@ -951,8 +961,9 @@ int bp_peer_socket(const struct bfd_session *bs) if (bs->key.ifname[0]) device_to_bind = (const char *)bs->key.ifname; - else if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) - && bs->key.vrfname[0]) + else if ((!vrf_is_backend_netns() && bs->vrf->vrf_id != VRF_DEFAULT) + || ((CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) + && bs->key.vrfname[0]))) device_to_bind = (const char *)bs->key.vrfname; frr_with_privs(&bglobal.bfdd_privs) { @@ -1018,8 +1029,9 @@ int bp_peer_socketv6(const struct bfd_session *bs) if (bs->key.ifname[0]) device_to_bind = (const char *)bs->key.ifname; - else if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) - && bs->key.vrfname[0]) + else if ((!vrf_is_backend_netns() && bs->vrf->vrf_id != VRF_DEFAULT) + || ((CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) + && bs->key.vrfname[0]))) device_to_bind = (const char *)bs->key.vrfname; frr_with_privs(&bglobal.bfdd_privs) { diff --git a/bfdd/ptm_adapter.c b/bfdd/ptm_adapter.c index 44519c47b5..57fb81aa27 100644 --- a/bfdd/ptm_adapter.c +++ b/bfdd/ptm_adapter.c @@ -669,17 +669,24 @@ static void bfdd_sessions_enable_interface(struct interface *ifp) struct bfd_session *bs; struct vrf *vrf; + vrf = vrf_lookup_by_id(ifp->vrf_id); + if (!vrf) + return; + TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { bs = bso->bso_bs; - /* Interface name mismatch. */ - if (strcmp(ifp->name, bs->key.ifname)) - continue; - vrf = vrf_lookup_by_id(ifp->vrf_id); - if (!vrf) - continue; + /* check vrf name */ if (bs->key.vrfname[0] && strcmp(vrf->name, bs->key.vrfname)) continue; + + /* If Interface matches vrfname, then bypass iface check */ + if (vrf_is_backend_netns() || strcmp(ifp->name, vrf->name)) { + /* Interface name mismatch. */ + if (strcmp(ifp->name, bs->key.ifname)) + continue; + } + /* Skip enabled sessions. */ if (bs->sock != -1) continue; @@ -759,7 +766,8 @@ void bfdd_sessions_disable_vrf(struct vrf *vrf) static int bfd_ifp_destroy(struct interface *ifp) { if (bglobal.debug_zebra) - zlog_debug("zclient: delete interface %s", ifp->name); + zlog_debug("zclient: delete interface %s (VRF %u)", ifp->name, + ifp->vrf_id); bfdd_sessions_disable_interface(ifp); @@ -812,10 +820,10 @@ static int bfdd_interface_address_update(ZAPI_CALLBACK_ARGS) return 0; if (bglobal.debug_zebra) - zlog_debug("zclient: %s local address %pFX", + zlog_debug("zclient: %s local address %pFX (VRF %u)", cmd == ZEBRA_INTERFACE_ADDRESS_ADD ? "add" : "delete", - ifc->address); + ifc->address, vrf_id); if (cmd == ZEBRA_INTERFACE_ADDRESS_ADD) bfdd_sessions_enable_address(ifc); @@ -828,8 +836,8 @@ static int bfdd_interface_address_update(ZAPI_CALLBACK_ARGS) static int bfd_ifp_create(struct interface *ifp) { if (bglobal.debug_zebra) - zlog_debug("zclient: add interface %s", ifp->name); - + zlog_debug("zclient: add interface %s (VRF %u)", ifp->name, + ifp->vrf_id); bfdd_sessions_enable_interface(ifp); return 0; diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c index ce22e8404d..c25d0e269a 100644 --- a/bgpd/bgp_attr.c +++ b/bgpd/bgp_attr.c @@ -3395,7 +3395,8 @@ void bgp_attr_extcom_tunnel_type(struct attr *attr, bgp_encap_types *tunnel_type) { struct ecommunity *ecom; - int i; + uint32_t i; + if (!attr) return; @@ -4021,7 +4022,7 @@ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, uint8_t *pnt; int tbit; int ecom_tr_size = 0; - int i; + uint32_t i; for (i = 0; i < attr->ecommunity->size; i++) { pnt = attr->ecommunity->val + (i * 8); diff --git a/bgpd/bgp_attr_evpn.c b/bgpd/bgp_attr_evpn.c index 7cc9ecd79e..1df646c346 100644 --- a/bgpd/bgp_attr_evpn.c +++ b/bgpd/bgp_attr_evpn.c @@ -89,7 +89,7 @@ char *ecom_mac2str(char *ecom_mac) /* Fetch router-mac from extended community */ bool bgp_attr_rmac(struct attr *attr, struct ethaddr *rmac) { - int i = 0; + uint32_t i = 0; struct ecommunity *ecom; ecom = attr->ecommunity; @@ -122,7 +122,7 @@ bool bgp_attr_rmac(struct attr *attr, struct ethaddr *rmac) uint8_t bgp_attr_default_gw(struct attr *attr) { struct ecommunity *ecom; - int i; + uint32_t i; ecom = attr->ecommunity; if (!ecom || !ecom->size) @@ -153,7 +153,7 @@ uint8_t bgp_attr_default_gw(struct attr *attr) uint16_t bgp_attr_df_pref_from_ec(struct attr *attr, uint8_t *alg) { struct ecommunity *ecom; - int i; + uint32_t i; uint16_t df_pref = 0; *alg = EVPN_MH_DF_ALG_SERVICE_CARVING; @@ -190,7 +190,7 @@ uint16_t bgp_attr_df_pref_from_ec(struct attr *attr, uint8_t *alg) uint32_t bgp_attr_mac_mobility_seqnum(struct attr *attr, uint8_t *sticky) { struct ecommunity *ecom; - int i; + uint32_t i; uint8_t flags = 0; ecom = attr->ecommunity; @@ -237,7 +237,7 @@ void bgp_attr_evpn_na_flag(struct attr *attr, uint8_t *router_flag, bool *proxy) { struct ecommunity *ecom; - int i; + uint32_t i; uint8_t val; ecom = attr->ecommunity; diff --git a/bgpd/bgp_ecommunity.c b/bgpd/bgp_ecommunity.c index 43bfb3e2bc..c358d4203e 100644 --- a/bgpd/bgp_ecommunity.c +++ b/bgpd/bgp_ecommunity.c @@ -95,7 +95,7 @@ static bool ecommunity_add_val_internal(struct ecommunity *ecom, bool unique, bool overwrite, uint8_t ecom_size) { - int c, ins_idx; + uint32_t c, ins_idx; const struct ecommunity_val *eval4 = (struct ecommunity_val *)eval; const struct ecommunity_val_ipv6 *eval6 = (struct ecommunity_val_ipv6 *)eval; @@ -113,7 +113,7 @@ static bool ecommunity_add_val_internal(struct ecommunity *ecom, /* check also if the extended community itself exists. */ c = 0; - ins_idx = -1; + ins_idx = UINT32_MAX; for (uint8_t *p = ecom->val; c < ecom->size; p += ecom_size, c++) { if (unique) { @@ -145,12 +145,12 @@ static bool ecommunity_add_val_internal(struct ecommunity *ecom, if (ret > 0) { if (!unique) break; - if (ins_idx == -1) + if (ins_idx == UINT32_MAX) ins_idx = c; } } - if (ins_idx == -1) + if (ins_idx == UINT32_MAX) ins_idx = c; /* Add the value to the structure with numerical sorting. */ @@ -193,7 +193,7 @@ static struct ecommunity * ecommunity_uniq_sort_internal(struct ecommunity *ecom, unsigned short ecom_size) { - int i; + uint32_t i; struct ecommunity *new; const void *eval; @@ -895,7 +895,7 @@ static int ecommunity_lb_str(char *buf, size_t bufsz, const uint8_t *pnt) */ char *ecommunity_ecom2str(struct ecommunity *ecom, int format, int filter) { - int i; + uint32_t i; uint8_t *pnt; uint8_t type = 0; uint8_t sub_type = 0; @@ -1176,8 +1176,8 @@ char *ecommunity_ecom2str(struct ecommunity *ecom, int format, int filter) bool ecommunity_match(const struct ecommunity *ecom1, const struct ecommunity *ecom2) { - int i = 0; - int j = 0; + uint32_t i = 0; + uint32_t j = 0; if (ecom1 == NULL && ecom2 == NULL) return true; @@ -1209,7 +1209,7 @@ extern struct ecommunity_val *ecommunity_lookup(const struct ecommunity *ecom, uint8_t type, uint8_t subtype) { uint8_t *p; - int c; + uint32_t c; /* If the value already exists in the structure return 0. */ c = 0; @@ -1230,7 +1230,7 @@ bool ecommunity_strip(struct ecommunity *ecom, uint8_t type, uint8_t subtype) { uint8_t *p, *q, *new; - int c, found = 0; + uint32_t c, found = 0; /* When this is fist value, just add it. */ if (ecom == NULL || ecom->val == NULL) return false; @@ -1278,7 +1278,7 @@ bool ecommunity_strip(struct ecommunity *ecom, uint8_t type, bool ecommunity_del_val(struct ecommunity *ecom, struct ecommunity_val *eval) { uint8_t *p; - int c, found = 0; + uint32_t c, found = 0; /* Make sure specified value exists. */ if (ecom == NULL || ecom->val == NULL) @@ -1512,7 +1512,7 @@ void bgp_remove_ecomm_from_aggregate_hash(struct bgp_aggregate *aggregate, const uint8_t *ecommunity_linkbw_present(struct ecommunity *ecom, uint32_t *bw) { const uint8_t *eval; - int i; + uint32_t i; if (bw) *bw = 0; diff --git a/bgpd/bgp_ecommunity.h b/bgpd/bgp_ecommunity.h index 6318e7edb1..6d0275a0c3 100644 --- a/bgpd/bgp_ecommunity.h +++ b/bgpd/bgp_ecommunity.h @@ -114,7 +114,7 @@ struct ecommunity { uint8_t unit_size; /* Size of Extended Communities attribute. */ - int size; + uint32_t size; /* Extended Communities value. */ uint8_t *val; diff --git a/bgpd/bgp_evpn.c b/bgpd/bgp_evpn.c index 96f4b0aa78..88747b14b1 100644 --- a/bgpd/bgp_evpn.c +++ b/bgpd/bgp_evpn.c @@ -870,7 +870,7 @@ static void add_mac_mobility_to_attr(uint32_t seq_num, struct attr *attr) struct ecommunity ecom_tmp; struct ecommunity_val eval; uint8_t *ecom_val_ptr; - int i; + uint32_t i; uint8_t *pnt; int type = 0; int sub_type = 0; @@ -2710,7 +2710,7 @@ static int is_route_matching_for_vrf(struct bgp *bgp_vrf, { struct attr *attr = pi->attr; struct ecommunity *ecom; - int i; + uint32_t i; assert(attr); /* Route should have valid RT to be even considered. */ @@ -2777,7 +2777,7 @@ static int is_route_matching_for_vni(struct bgp *bgp, struct bgpevpn *vpn, { struct attr *attr = pi->attr; struct ecommunity *ecom; - int i; + uint32_t i; assert(attr); /* Route should have valid RT to be even considered. */ @@ -3260,7 +3260,7 @@ static int bgp_evpn_install_uninstall_table(struct bgp *bgp, afi_t afi, struct prefix_evpn *evp = (struct prefix_evpn *)p; struct attr *attr = pi->attr; struct ecommunity *ecom; - int i; + uint32_t i; struct prefix_evpn ad_evp; assert(attr); @@ -4906,7 +4906,7 @@ int bgp_nlri_parse_evpn(struct peer *peer, struct attr *attr, */ void bgp_evpn_map_vrf_to_its_rts(struct bgp *bgp_vrf) { - int i = 0; + uint32_t i = 0; struct ecommunity_val *eval = NULL; struct listnode *node = NULL, *nnode = NULL; struct ecommunity *ecom = NULL; @@ -4926,7 +4926,7 @@ void bgp_evpn_map_vrf_to_its_rts(struct bgp *bgp_vrf) */ void bgp_evpn_unmap_vrf_from_its_rts(struct bgp *bgp_vrf) { - int i; + uint32_t i; struct ecommunity_val *eval; struct listnode *node, *nnode; struct ecommunity *ecom; @@ -4963,7 +4963,7 @@ void bgp_evpn_unmap_vrf_from_its_rts(struct bgp *bgp_vrf) */ void bgp_evpn_map_vni_to_its_rts(struct bgp *bgp, struct bgpevpn *vpn) { - int i; + uint32_t i; struct ecommunity_val *eval; struct listnode *node, *nnode; struct ecommunity *ecom; @@ -4983,7 +4983,7 @@ void bgp_evpn_map_vni_to_its_rts(struct bgp *bgp, struct bgpevpn *vpn) */ void bgp_evpn_unmap_vni_from_its_rts(struct bgp *bgp, struct bgpevpn *vpn) { - int i; + uint32_t i; struct ecommunity_val *eval; struct listnode *node, *nnode; struct ecommunity *ecom; diff --git a/bgpd/bgp_main.c b/bgpd/bgp_main.c index 287555b1fc..3cb3d06217 100644 --- a/bgpd/bgp_main.c +++ b/bgpd/bgp_main.c @@ -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); diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c index 3bc4c03233..1d66d75288 100644 --- a/bgpd/bgp_mplsvpn.c +++ b/bgpd/bgp_mplsvpn.c @@ -419,8 +419,7 @@ int vpn_leak_label_callback( static bool ecom_intersect(struct ecommunity *e1, struct ecommunity *e2) { - int i; - int j; + uint32_t i, j; if (!e1 || !e2) return false; diff --git a/bgpd/bgp_pbr.c b/bgpd/bgp_pbr.c index a3f1eb8401..4f22f5bcfe 100644 --- a/bgpd/bgp_pbr.c +++ b/bgpd/bgp_pbr.c @@ -754,7 +754,7 @@ int bgp_pbr_build_and_validate_entry(const struct prefix *p, struct bgp_pbr_entry_main *api) { int ret; - int i, action_count = 0; + uint32_t i, action_count = 0; struct ecommunity *ecom; struct ecommunity_val *ecom_eval; struct bgp_pbr_entry_action *api_action; diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c index 0f4f26e3ee..ceaf8c0963 100644 --- a/bgpd/bgp_routemap.c +++ b/bgpd/bgp_routemap.c @@ -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) -static enum route_map_cmd_result_t -route_match_command(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; - struct bgp_path_info *path = (struct bgp_path_info *)object; - lua_State *L = lua_initialize("/etc/frr/lua.scr"); - - if (L == NULL) - return status; +#ifdef HAVE_SCRIPTING +enum frrlua_rm_status { /* - * Setup the prefix information to pass in + * Script function run failure. This will translate into a deny */ - lua_setup_prefix_table(L, prefix); - - zlog_debug("Set up prefix table"); + LUA_RM_FAILURE = 0, /* - * Setup the bgp_path_info information + * No Match was found for the route map function */ - 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"); - - zlog_debug("Set up nexthop information"); + LUA_RM_NOMATCH, /* - * Run the rule + * Match was found but no changes were made to the incoming data. */ - lrm_status = lua_run_rm_rule(L, rule); - switch (lrm_status) { + 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_script(void *rule, const struct prefix *prefix, void *object) +{ + const char *scriptname = rule; + struct bgp_path_info *path = (struct bgp_path_info *)object; + + struct frrscript *fs = frrscript_load(scriptname, NULL); + + if (!fs) { + zlog_err("Issue loading script rule; defaulting to no match"); + return RMAP_NOMATCH; + } + + 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; + + /* 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; + + scriptname = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); - command = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); - return command; + 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 } diff --git a/bgpd/bgp_script.c b/bgpd/bgp_script.c new file mode 100644 index 0000000000..0cda1927f8 --- /dev/null +++ b/bgpd/bgp_script.c @@ -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 */ diff --git a/bgpd/bgp_script.h b/bgpd/bgp_script.h new file mode 100644 index 0000000000..6682c2eebd --- /dev/null +++ b/bgpd/bgp_script.h @@ -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__ */ diff --git a/bgpd/bgp_updgrp_adv.c b/bgpd/bgp_updgrp_adv.c index b1ff9ac251..30babb7b76 100644 --- a/bgpd/bgp_updgrp_adv.c +++ b/bgpd/bgp_updgrp_adv.c @@ -518,10 +518,7 @@ void bgp_adj_out_set_subgroup(struct bgp_dest *dest, /* bgp_path_info adj_out reference */ adv->pathi = bgp_path_info_lock(path); - if (attr) - adv->baa = bgp_advertise_intern(subgrp->hash, attr); - else - adv->baa = baa_new(); + adv->baa = bgp_advertise_intern(subgrp->hash, attr); adv->adj = adj; adj->attr_hash = attr_hash; diff --git a/bgpd/rfapi/rfapi_import.c b/bgpd/rfapi/rfapi_import.c index 3d87b63542..b2732a40b4 100644 --- a/bgpd/rfapi/rfapi_import.c +++ b/bgpd/rfapi/rfapi_import.c @@ -984,7 +984,7 @@ static int rfapiEcommunitiesMatchBeec(struct ecommunity *ecom, int rfapiEcommunitiesIntersect(struct ecommunity *e1, struct ecommunity *e2) { - int i, j; + uint32_t i, j; if (!e1 || !e2) return 0; @@ -1014,7 +1014,8 @@ int rfapiEcommunitiesIntersect(struct ecommunity *e1, struct ecommunity *e2) int rfapiEcommunityGetLNI(struct ecommunity *ecom, uint32_t *lni) { if (ecom) { - int i; + uint32_t i; + for (i = 0; i < ecom->size; ++i) { uint8_t *p = ecom->val + (i * ECOMMUNITY_SIZE); @@ -1034,7 +1035,8 @@ int rfapiEcommunityGetEthernetTag(struct ecommunity *ecom, uint16_t *tag_id) struct bgp *bgp = bgp_get_default(); *tag_id = 0; /* default to untagged */ if (ecom) { - int i; + uint32_t i; + for (i = 0; i < ecom->size; ++i) { as_t as = 0; int encode = 0; diff --git a/bgpd/rfapi/vnc_export_bgp.c b/bgpd/rfapi/vnc_export_bgp.c index 762cd2596f..bc29f05aeb 100644 --- a/bgpd/rfapi/vnc_export_bgp.c +++ b/bgpd/rfapi/vnc_export_bgp.c @@ -134,7 +134,7 @@ static void encap_attr_export_ce(struct attr *new, struct attr *orig, static int getce(struct bgp *bgp, struct attr *attr, struct prefix *pfx_ce) { uint8_t *ecp; - int i; + uint32_t i; uint16_t localadmin = bgp->rfapi_cfg->resolve_nve_roo_local_admin; for (ecp = attr->ecommunity->val, i = 0; i < attr->ecommunity->size; diff --git a/bgpd/subdir.am b/bgpd/subdir.am index ac84f4b9e4..df1555c32a 100644 --- a/bgpd/subdir.am +++ b/bgpd/subdir.am @@ -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 \ diff --git a/configure.ac b/configure.ac index 495019ee14..8f9517763d 100755 --- a/configure.ac +++ b/configure.ac @@ -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} diff --git a/debian/control b/debian/control index 4aaa9f21bf..b9e96b55d0 100644 --- a/debian/control +++ b/debian/control @@ -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 diff --git a/debian/rules b/debian/rules index 6cc03c378a..25ae04261d 100755 --- a/debian/rules +++ b/debian/rules @@ -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 \ diff --git a/doc/developer/library.rst b/doc/developer/library.rst index 3d5c6a2a15..1bfe5df2f0 100644 --- a/doc/developer/library.rst +++ b/doc/developer/library.rst @@ -15,6 +15,6 @@ Library Facilities (libfrr) hooks cli modules - lua + scripting diff --git a/doc/developer/lua.rst b/doc/developer/lua.rst deleted file mode 100644 index 3315c31ad7..0000000000 --- a/doc/developer/lua.rst +++ /dev/null @@ -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. diff --git a/doc/developer/scripting.rst b/doc/developer/scripting.rst new file mode 100644 index 0000000000..b0413619ab --- /dev/null +++ b/doc/developer/scripting.rst @@ -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 + diff --git a/doc/developer/subdir.am b/doc/developer/subdir.am index 0129be6bf1..07a25886d0 100644 --- a/doc/developer/subdir.am +++ b/doc/developer/subdir.am @@ -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 \ diff --git a/doc/user/index.rst b/doc/user/index.rst index 993acf3b4c..7b9464668b 100644 --- a/doc/user/index.rst +++ b/doc/user/index.rst @@ -29,6 +29,7 @@ Basics ipv6 kernel snmp + scripting .. modules ######### diff --git a/doc/user/installation.rst b/doc/user/installation.rst index 382d71b71f..a13e6ce43b 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -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 diff --git a/doc/user/scripting.rst b/doc/user/scripting.rst new file mode 100644 index 0000000000..b0295e5706 --- /dev/null +++ b/doc/user/scripting.rst @@ -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. diff --git a/doc/user/subdir.am b/doc/user/subdir.am index a78d261863..3585245e85 100644 --- a/doc/user/subdir.am +++ b/doc/user/subdir.am @@ -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 \ diff --git a/lib/command.c b/lib/command.c index f40fe6e2c5..b9d607a101 100644 --- a/lib/command.c +++ b/lib/command.c @@ -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); diff --git a/lib/compiler.h b/lib/compiler.h index 217a60d888..70ef8e9bc8 100644 --- a/lib/compiler.h +++ b/lib/compiler.h @@ -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. diff --git a/lib/frrlua.c b/lib/frrlua.c index 9f9cf8c1f6..3c270b2340 100644 --- a/lib/frrlua.c +++ b/lib/frrlua.c @@ -2,128 +2,365 @@ * 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" + +/* Lua stuff */ -static int lua_zlog_debug(lua_State *L) +/* + * FRR convenience functions. + * + * This section has convenience functions used to make interacting with the Lua + * stack easier. + */ + +int frrlua_table_get_integer(lua_State *L, const char *key) { - int debug_lua = 1; - const char *string = lua_tostring(L, 1); + int result; - if (debug_lua) - zlog_debug("%s", string); + lua_pushstring(L, key); + lua_gettable(L, -2); - return 0; + result = lua_tointeger(L, -1); + lua_pop(L, 1); + + return result; } -const char *get_string(lua_State *L, const char *key) +/* + * Encoders. + * + * This section has functions that convert internal FRR datatypes into Lua + * datatypes. + */ + +void lua_pushprefix(lua_State *L, const struct prefix *prefix) { - const char *str; + char buffer[PREFIX_STRLEN]; - lua_pushstring(L, key); - lua_gettable(L, -2); + 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"); +} - str = (const char *)lua_tostring(L, -1); +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 str; + return p; } -int get_integer(lua_State *L, const char *key) +void lua_pushinterface(lua_State *L, const struct interface *ifp) { - int result; + 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"); +} - lua_pushstring(L, key); - lua_gettable(L, -2); +void *lua_tointerface(lua_State *L, int idx) +{ + struct interface *ifp = XCALLOC(MTYPE_TMP, sizeof(struct interface)); - result = lua_tointeger(L, -1); + 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 result; + return ifp; } -static void *lua_alloc(void *ud, void *ptr, size_t osize, - size_t nsize) +void lua_pushinaddr(lua_State *L, const struct in_addr *addr) { - (void)ud; (void)osize; /* not used */ - if (nsize == 0) { - free(ptr); - return NULL; - } else - return realloc(ptr, nsize); + 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"); } -lua_State *lua_initialize(const char *file) +void *lua_toinaddr(lua_State *L, int idx) { - int status; - lua_State *L = lua_newstate(lua_alloc, NULL); + struct in_addr *inaddr = XCALLOC(MTYPE_TMP, sizeof(struct in_addr)); - 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_getfield(L, idx, "value"); + inaddr->s_addr = lua_tointeger(L, -1); + lua_pop(L, 1); - lua_pcall(L, 0, LUA_MULTRET, 0); - zlog_debug("Setting global function"); - lua_pushcfunction(L, lua_zlog_debug); - lua_setglobal(L, "zlog_debug"); + 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 L; + return in6addr; } -void lua_setup_prefix_table(lua_State *L, const struct prefix *prefix) +void lua_pushsockunion(lua_State *L, const union sockunion *su) { - char buffer[100]; + char buf[SU_ADDRSTRLEN]; + sockunion2str(su, buf, sizeof(buf)); lua_newtable(L); - lua_pushstring(L, prefix2str(prefix, buffer, 100)); - lua_setfield(L, -2, "route"); - lua_pushinteger(L, prefix->family); - lua_setfield(L, -2, "family"); - lua_setglobal(L, "prefix"); + 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"); } -enum lua_rm_status lua_run_rm_rule(lua_State *L, const char *rule) +void *lua_tosockunion(lua_State *L, int idx) { - int status; + union sockunion *su = XCALLOC(MTYPE_TMP, sizeof(union sockunion)); + + lua_getfield(L, idx, "string"); + str2sockunion(lua_tostring(L, -1), su); + + return su; +} - 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; +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 */ diff --git a/lib/frrlua.h b/lib/frrlua.h index 40c7a67b89..8e52931e50 100644 --- a/lib/frrlua.h +++ b/lib/frrlua.h @@ -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__ + +#include <zebra.h> + +#ifdef HAVE_SCRIPTING -#if defined(HAVE_LUA) +#include <lua.h> +#include <lualib.h> +#include <lauxlib.h> -#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. + */ +void lua_pushprefix(lua_State *L, const struct prefix *prefix); + +/* + * Converts the Lua value at idx to a prefix. + * + * Returns: + * struct prefix allocated with MTYPE_TMP + */ +void *lua_toprefix(lua_State *L, int idx); + +/* + * Converts an interface to a Lua value and pushes it on the stack. + */ +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. */ -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_pushin6addr(lua_State *L, const struct in6_addr *addr); /* - * Open up the lua.scr file and parse - * initial global values, if any. + * Converts the Lua value at idx to an in6_addr. + * + * Returns: + * struct in6_addr allocated with MTYPE_TMP. */ -lua_State *lua_initialize(const char *file); +void *lua_toin6addr(lua_State *L, int idx); -void lua_setup_prefix_table(lua_State *L, const struct prefix *prefix); +/* + * Converts a time_t to a Lua value and pushes it on the stack. + */ +void lua_pushtimet(lua_State *L, const time_t *time); -enum lua_rm_status lua_run_rm_rule(lua_State *L, const char *rule); +/* + * 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); /* - * Get particular string/integer information - * from a table. It is *assumed* that - * the table has already been selected + * Converts a sockunion 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_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__ */ diff --git a/lib/frrscript.c b/lib/frrscript.c new file mode 100644 index 0000000000..a3de474a4e --- /dev/null +++ b/lib/frrscript.c @@ -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 */ diff --git a/lib/frrscript.h b/lib/frrscript.h new file mode 100644 index 0000000000..f4057f531b --- /dev/null +++ b/lib/frrscript.h @@ -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__ */ diff --git a/lib/libfrr.c b/lib/libfrr.c index 8e7777a1a9..b83883779c 100644 --- a/lib/libfrr.c +++ b/lib/libfrr.c @@ -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(); diff --git a/lib/libfrr.h b/lib/libfrr.h index 2e4dcbe093..c446931468 100644 --- a/lib/libfrr.h +++ b/lib/libfrr.h @@ -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[]; diff --git a/lib/northbound_cli.c b/lib/northbound_cli.c index 7048df99fb..853f643472 100644 --- a/lib/northbound_cli.c +++ b/lib/northbound_cli.c @@ -693,6 +693,12 @@ static int nb_write_config(struct nb_config *config, enum nb_cfg_format format, __func__, safe_strerror(errno)); return -1; } + if (fchmod(fd, CONFIGFILE_MASK) != 0) { + flog_warn(EC_LIB_SYSTEM_CALL, + "%s: fchmod() failed: %s(%d):", __func__, + safe_strerror(errno), errno); + return -1; + } /* Make vty for configuration file. */ file_vty = vty_new(); diff --git a/lib/subdir.am b/lib/subdir.am index ee9e827ee8..570e0c3d28 100644 --- a/lib/subdir.am +++ b/lib/subdir.am @@ -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 \ diff --git a/pbrd/pbr_nht.c b/pbrd/pbr_nht.c index dbe5de724c..f99971ab7b 100644 --- a/pbrd/pbr_nht.c +++ b/pbrd/pbr_nht.c @@ -770,18 +770,20 @@ pbr_nht_individual_nexthop_gw_update(struct pbr_nexthop_cache *pnhc, goto done; } - switch (pnhi->nhr->prefix.family) { - case AF_INET: - if (pnhc->nexthop.gate.ipv4.s_addr - != pnhi->nhr->prefix.u.prefix4.s_addr) - goto done; /* Unrelated change */ - break; - case AF_INET6: - if (memcmp(&pnhc->nexthop.gate.ipv6, - &pnhi->nhr->prefix.u.prefix6, 16) - != 0) - goto done; /* Unrelated change */ - break; + if (pnhi->nhr) { + switch (pnhi->nhr->prefix.family) { + case AF_INET: + if (pnhc->nexthop.gate.ipv4.s_addr + != pnhi->nhr->prefix.u.prefix4.s_addr) + goto done; /* Unrelated change */ + break; + case AF_INET6: + if (memcmp(&pnhc->nexthop.gate.ipv6, + &pnhi->nhr->prefix.u.prefix6, 16) + != 0) + goto done; /* Unrelated change */ + break; + } } pnhi->nhr_matched = true; diff --git a/staticd/static_nb.c b/staticd/static_nb.c index 2fdd0d2989..a2a14751cf 100644 --- a/staticd/static_nb.c +++ b/staticd/static_nb.c @@ -66,7 +66,6 @@ const struct frr_yang_module_info frr_staticd_info = { .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/onlink", .cbs = { .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_onlink_modify, - .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_onlink_destroy, } }, { @@ -145,7 +144,6 @@ const struct frr_yang_module_info frr_staticd_info = { .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/onlink", .cbs = { .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_onlink_modify, - .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_onlink_destroy, } }, { diff --git a/staticd/static_nb.h b/staticd/static_nb.h index b293224dd1..e85e1d0e9f 100644 --- a/staticd/static_nb.h +++ b/staticd/static_nb.h @@ -41,8 +41,6 @@ int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_pa struct nb_cb_destroy_args *args); int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_onlink_modify( struct nb_cb_modify_args *args); -int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_onlink_destroy( - struct nb_cb_destroy_args *args); int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_color_modify( struct nb_cb_modify_args *args); int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_color_destroy( @@ -83,8 +81,6 @@ int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_sr struct nb_cb_destroy_args *args); int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_onlink_modify( struct nb_cb_modify_args *args); -int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_onlink_destroy( - struct nb_cb_destroy_args *args); int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_color_modify( struct nb_cb_modify_args *args); int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_color_destroy( diff --git a/staticd/static_nb_config.c b/staticd/static_nb_config.c index 3778960cd1..bf669957bf 100644 --- a/staticd/static_nb_config.c +++ b/staticd/static_nb_config.c @@ -287,9 +287,27 @@ static int static_nexthop_mpls_label_modify(struct nb_cb_modify_args *args) static int static_nexthop_onlink_modify(struct nb_cb_modify_args *args) { struct static_nexthop *nh; + static_types nh_type; - nh = nb_running_get_entry(args->dnode, NULL, true); - nh->onlink = yang_dnode_get_bool(args->dnode, NULL); + switch (args->event) { + case NB_EV_VALIDATE: + nh_type = yang_dnode_get_enum(args->dnode, "../nh-type"); + if ((nh_type != STATIC_IPV4_GATEWAY_IFNAME) + && (nh_type != STATIC_IPV6_GATEWAY_IFNAME)) { + snprintf( + args->errmsg, args->errmsg_len, + "nexthop type is not the ipv4 or ipv6 interface type"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + nh = nb_running_get_entry(args->dnode, NULL, true); + nh->onlink = yang_dnode_get_bool(args->dnode, NULL); + break; + } return NB_OK; } @@ -317,9 +335,25 @@ static int static_nexthop_color_destroy(struct nb_cb_destroy_args *args) static int static_nexthop_bh_type_modify(struct nb_cb_modify_args *args) { struct static_nexthop *nh; + static_types nh_type; - nh = nb_running_get_entry(args->dnode, NULL, true); - nh->bh_type = yang_dnode_get_enum(args->dnode, NULL); + switch (args->event) { + case NB_EV_VALIDATE: + nh_type = yang_dnode_get_enum(args->dnode, "../nh-type"); + if (nh_type != STATIC_BLACKHOLE) { + snprintf(args->errmsg, args->errmsg_len, + "nexthop type is not the blackhole type"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + nh = nb_running_get_entry(args->dnode, NULL, true); + nh->bh_type = yang_dnode_get_enum(args->dnode, NULL); + break; + } return NB_OK; } @@ -589,7 +623,7 @@ int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_pa info = route_table_get_info(rn->table); if (static_nexthop_create(args, rn_dnode, info) != NB_OK) - return NB_ERR_VALIDATION; + return NB_ERR_INCONSISTENCY; break; } return NB_OK; @@ -626,17 +660,7 @@ int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_pa int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_bh_type_modify( struct nb_cb_modify_args *args) { - switch (args->event) { - case NB_EV_VALIDATE: - case NB_EV_PREPARE: - case NB_EV_ABORT: - break; - case NB_EV_APPLY: - if (static_nexthop_bh_type_modify(args) != NB_OK) - return NB_ERR; - break; - } - return NB_OK; + return static_nexthop_bh_type_modify(args); } int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_bh_type_destroy( @@ -662,33 +686,7 @@ int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_pa int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_onlink_modify( struct nb_cb_modify_args *args) { - switch (args->event) { - case NB_EV_VALIDATE: - case NB_EV_PREPARE: - case NB_EV_ABORT: - break; - case NB_EV_APPLY: - if (static_nexthop_onlink_modify(args) != NB_OK) - return NB_ERR; - - break; - } - return NB_OK; -} -int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_onlink_destroy( - struct nb_cb_destroy_args *args) -{ - /* onlink has a boolean type with default value, - * so no need to do any operations in destroy callback - */ - switch (args->event) { - case NB_EV_VALIDATE: - case NB_EV_PREPARE: - case NB_EV_ABORT: - case NB_EV_APPLY: - break; - } - return NB_OK; + return static_nexthop_onlink_modify(args); } /* @@ -1042,17 +1040,7 @@ int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_sr int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_bh_type_modify( struct nb_cb_modify_args *args) { - switch (args->event) { - case NB_EV_VALIDATE: - case NB_EV_PREPARE: - case NB_EV_ABORT: - break; - case NB_EV_APPLY: - if (static_nexthop_bh_type_modify(args) != NB_OK) - return NB_ERR; - break; - } - return NB_OK; + return static_nexthop_bh_type_modify(args); } int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_bh_type_destroy( @@ -1079,35 +1067,7 @@ int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_sr int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_onlink_modify( struct nb_cb_modify_args *args) { - switch (args->event) { - case NB_EV_VALIDATE: - case NB_EV_PREPARE: - case NB_EV_ABORT: - break; - case NB_EV_APPLY: - if (static_nexthop_onlink_modify(args) != NB_OK) - return NB_ERR; - - break; - } - return NB_OK; -} - - -int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_onlink_destroy( - struct nb_cb_destroy_args *args) -{ - /* onlink has a boolean type with default value, - * so no need to do any operations in destroy callback - */ - switch (args->event) { - case NB_EV_VALIDATE: - case NB_EV_PREPARE: - case NB_EV_ABORT: - case NB_EV_APPLY: - break; - } - return NB_OK; + return static_nexthop_onlink_modify(args); } /* diff --git a/tests/topotests/bfd-ospf-topo1/__init__.py b/tests/topotests/bfd-ospf-topo1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/__init__.py diff --git a/tests/topotests/bfd-ospf-topo1/rt1/bfdd.conf b/tests/topotests/bfd-ospf-topo1/rt1/bfdd.conf new file mode 100644 index 0000000000..610a20f88a --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/bfdd.conf @@ -0,0 +1,9 @@ +log file bfdd.log +log timestamp precision 3 +! +debug bfd network +debug bfd peer +debug bfd zebra +! +bfd +! diff --git a/tests/topotests/bfd-ospf-topo1/rt1/ospf6d.conf b/tests/topotests/bfd-ospf-topo1/rt1/ospf6d.conf new file mode 100644 index 0000000000..18def599b4 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/ospf6d.conf @@ -0,0 +1,21 @@ +log file ospf6d.log +log timestamp precision 3 +! +hostname rt1 +! +password 1 +! +interface eth-rt2 + ipv6 ospf6 network broadcast + ipv6 ospf6 bfd +! +interface eth-rt3 + ipv6 ospf6 network broadcast + ipv6 ospf6 bfd +! +router ospf6 + ospf6 router-id 1.1.1.1 + interface eth-rt2 area 0.0.0.0 + interface eth-rt3 area 0.0.0.0 + redistribute connected +! diff --git a/tests/topotests/bfd-ospf-topo1/rt1/ospfd.conf b/tests/topotests/bfd-ospf-topo1/rt1/ospfd.conf new file mode 100644 index 0000000000..07b42f9885 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/ospfd.conf @@ -0,0 +1,26 @@ +log file ospfd.log +log timestamp precision 3 +! +hostname rt1 +! +password 1 +! +debug ospf event +debug ospf zebra +! +interface lo + ip ospf area 0.0.0.0 +! +interface eth-rt2 + ip ospf area 0.0.0.0 + ip ospf bfd +! +interface eth-rt3 + ip ospf area 0.0.0.0 + ip ospf bfd +! +router ospf + ospf router-id 1.1.1.1 + passive interface lo + router-info area 0.0.0.0 +! diff --git a/tests/topotests/bfd-ospf-topo1/rt1/step1/show_ip_route.ref b/tests/topotests/bfd-ospf-topo1/rt1/step1/show_ip_route.ref new file mode 100644 index 0000000000..f354eff697 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/step1/show_ip_route.ref @@ -0,0 +1,74 @@ +{ + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "5.5.5.5\/32":[ + { + "prefix":"5.5.5.5\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd-ospf-topo1/rt1/step1/show_ipv6_route.ref b/tests/topotests/bfd-ospf-topo1/rt1/step1/show_ipv6_route.ref new file mode 100644 index 0000000000..6465efb8b5 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/step1/show_ipv6_route.ref @@ -0,0 +1,70 @@ +{ + "::ffff:202:202\/128":[ + { + "prefix":"::ffff:202:202\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "::ffff:303:303\/128":[ + { + "prefix":"::ffff:303:303\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:404:404\/128":[ + { + "prefix":"::ffff:404:404\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:505:505\/128":[ + { + "prefix":"::ffff:505:505\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd-ospf-topo1/rt1/step2/show_bfd_peers.ref b/tests/topotests/bfd-ospf-topo1/rt1/step2/show_bfd_peers.ref new file mode 100644 index 0000000000..63f0d50784 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/step2/show_bfd_peers.ref @@ -0,0 +1,26 @@ +[ + { + "interface": "eth-rt3", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt2", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt3", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt2", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd-ospf-topo1/rt1/step3/show_bfd_peers_healthy.ref b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_bfd_peers_healthy.ref new file mode 100644 index 0000000000..42051f9582 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_bfd_peers_healthy.ref @@ -0,0 +1,28 @@ +[ + { + "peer": "10.0.2.2", + "interface": "eth-rt3", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "peer": "10.0.1.2", + "interface": "eth-rt2", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt3", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt2", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd-ospf-topo1/rt1/step3/show_bfd_peers_rt2_down.ref b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_bfd_peers_rt2_down.ref new file mode 100644 index 0000000000..d844ee6813 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_bfd_peers_rt2_down.ref @@ -0,0 +1,15 @@ +[ + { + "peer": "10.0.2.2", + "interface": "eth-rt3", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt3", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd-ospf-topo1/rt1/step3/show_bfd_peers_rt3_down.ref b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_bfd_peers_rt3_down.ref new file mode 100644 index 0000000000..32799084fb --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_bfd_peers_rt3_down.ref @@ -0,0 +1,15 @@ +[ + { + "peer": "10.0.1.2", + "interface": "eth-rt2", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt2", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ip_route_healthy.ref b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ip_route_healthy.ref new file mode 100644 index 0000000000..f354eff697 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ip_route_healthy.ref @@ -0,0 +1,74 @@ +{ + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "5.5.5.5\/32":[ + { + "prefix":"5.5.5.5\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ip_route_rt2_down.ref b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ip_route_rt2_down.ref new file mode 100644 index 0000000000..43eecd0b7a --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ip_route_rt2_down.ref @@ -0,0 +1,74 @@ +{ + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "5.5.5.5\/32":[ + { + "prefix":"5.5.5.5\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ip_route_rt3_down.ref b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ip_route_rt3_down.ref new file mode 100644 index 0000000000..409af6308b --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ip_route_rt3_down.ref @@ -0,0 +1,74 @@ +{ + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "5.5.5.5\/32":[ + { + "prefix":"5.5.5.5\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ipv6_route_healthy.ref b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ipv6_route_healthy.ref new file mode 100644 index 0000000000..6465efb8b5 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ipv6_route_healthy.ref @@ -0,0 +1,70 @@ +{ + "::ffff:202:202\/128":[ + { + "prefix":"::ffff:202:202\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "::ffff:303:303\/128":[ + { + "prefix":"::ffff:303:303\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:404:404\/128":[ + { + "prefix":"::ffff:404:404\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:505:505\/128":[ + { + "prefix":"::ffff:505:505\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ipv6_route_rt2_down.ref b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ipv6_route_rt2_down.ref new file mode 100644 index 0000000000..cfb1ef1bb6 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ipv6_route_rt2_down.ref @@ -0,0 +1,70 @@ +{ + "::ffff:202:202\/128":[ + { + "prefix":"::ffff:202:202\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:303:303\/128":[ + { + "prefix":"::ffff:303:303\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:404:404\/128":[ + { + "prefix":"::ffff:404:404\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:505:505\/128":[ + { + "prefix":"::ffff:505:505\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ipv6_route_rt3_down.ref b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ipv6_route_rt3_down.ref new file mode 100644 index 0000000000..58b44da5c2 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/step3/show_ipv6_route_rt3_down.ref @@ -0,0 +1,70 @@ +{ + "::ffff:202:202\/128":[ + { + "prefix":"::ffff:202:202\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "::ffff:303:303\/128":[ + { + "prefix":"::ffff:303:303\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "::ffff:404:404\/128":[ + { + "prefix":"::ffff:404:404\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "::ffff:505:505\/128":[ + { + "prefix":"::ffff:505:505\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd-ospf-topo1/rt1/zebra.conf b/tests/topotests/bfd-ospf-topo1/rt1/zebra.conf new file mode 100644 index 0000000000..6003125b6b --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt1/zebra.conf @@ -0,0 +1,25 @@ +log file zebra.log +log timestamp precision 3 +! +hostname rt1 +! +debug zebra kernel +debug zebra packet +debug zebra events +debug zebra rib +! +interface lo + ip address 1.1.1.1/32 + ipv6 address ::ffff:0101:0101/128 +! +interface eth-rt2 + ip address 10.0.1.1/24 +! +interface eth-rt3 + ip address 10.0.2.1/24 +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bfd-ospf-topo1/rt2/bfdd.conf b/tests/topotests/bfd-ospf-topo1/rt2/bfdd.conf new file mode 100644 index 0000000000..437f063d8f --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt2/bfdd.conf @@ -0,0 +1,7 @@ +! +debug bfd network +debug bfd peer +debug bfd zebra +! +bfd +! diff --git a/tests/topotests/bfd-ospf-topo1/rt2/ospf6d.conf b/tests/topotests/bfd-ospf-topo1/rt2/ospf6d.conf new file mode 100644 index 0000000000..2f35099564 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt2/ospf6d.conf @@ -0,0 +1,19 @@ +log file ospf6d.log +! +hostname rt2 +! +password 1 +! +interface eth-rt1 + ipv6 ospf6 network broadcast + ipv6 ospf6 bfd +! +interface eth-rt5 + ipv6 ospf6 network broadcast +! +router ospf6 + ospf6 router-id 2.2.2.2 + interface eth-rt1 area 0.0.0.0 + interface eth-rt5 area 0.0.0.0 + redistribute connected +! diff --git a/tests/topotests/bfd-ospf-topo1/rt2/ospfd.conf b/tests/topotests/bfd-ospf-topo1/rt2/ospfd.conf new file mode 100644 index 0000000000..a05d8b58c8 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt2/ospfd.conf @@ -0,0 +1,24 @@ +log file ospfd.log +! +hostname rt2 +! +password 1 +! +debug ospf event +debug ospf zebra +! +interface lo + ip ospf area 0.0.0.0 +! +interface eth-rt1 + ip ospf area 0.0.0.0 + ip ospf bfd +! +interface eth-rt5 + ip ospf area 0.0.0.0 +! +router ospf + ospf router-id 2.2.2.2 + passive interface lo + router-info area 0.0.0.0 +! diff --git a/tests/topotests/bfd-ospf-topo1/rt2/step2/show_bfd_peers.ref b/tests/topotests/bfd-ospf-topo1/rt2/step2/show_bfd_peers.ref new file mode 100644 index 0000000000..d6df1ebfb2 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt2/step2/show_bfd_peers.ref @@ -0,0 +1,14 @@ +[ + { + "interface": "eth-rt1", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt1", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd-ospf-topo1/rt2/zebra.conf b/tests/topotests/bfd-ospf-topo1/rt2/zebra.conf new file mode 100644 index 0000000000..5fc7fc5b28 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt2/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname rt2 +! +debug zebra kernel +debug zebra packet +! +interface lo + ip address 2.2.2.2/32 + ipv6 address ::ffff:0202:0202/128 +! +interface eth-rt1 + ip address 10.0.1.2/24 +! +interface eth-rt5 + ip address 10.0.3.1/24 +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bfd-ospf-topo1/rt3/bfdd.conf b/tests/topotests/bfd-ospf-topo1/rt3/bfdd.conf new file mode 100644 index 0000000000..437f063d8f --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt3/bfdd.conf @@ -0,0 +1,7 @@ +! +debug bfd network +debug bfd peer +debug bfd zebra +! +bfd +! diff --git a/tests/topotests/bfd-ospf-topo1/rt3/ospf6d.conf b/tests/topotests/bfd-ospf-topo1/rt3/ospf6d.conf new file mode 100644 index 0000000000..3e8777019e --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt3/ospf6d.conf @@ -0,0 +1,19 @@ +log file ospf6d.log +! +hostname rt3 +! +password 1 +! +interface eth-rt1 + ipv6 ospf6 network broadcast + ipv6 ospf6 bfd +! +interface eth-rt4 + ipv6 ospf6 network broadcast +! +router ospf6 + ospf6 router-id 3.3.3.3 + interface eth-rt1 area 0.0.0.0 + interface eth-rt4 area 0.0.0.0 + redistribute connected +! diff --git a/tests/topotests/bfd-ospf-topo1/rt3/ospfd.conf b/tests/topotests/bfd-ospf-topo1/rt3/ospfd.conf new file mode 100644 index 0000000000..1196e6d189 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt3/ospfd.conf @@ -0,0 +1,24 @@ +log file ospfd.log +! +hostname rt3 +! +password 1 +! +debug ospf event +debug ospf zebra +! +interface lo + ip ospf area 0.0.0.0 +! +interface eth-rt1 + ip ospf area 0.0.0.0 + ip ospf bfd +! +interface eth-rt4 + ip ospf area 0.0.0.0 +! +router ospf + ospf router-id 3.3.3.3 + passive interface lo + router-info area 0.0.0.0 +! diff --git a/tests/topotests/bfd-ospf-topo1/rt3/step2/show_bfd_peers.ref b/tests/topotests/bfd-ospf-topo1/rt3/step2/show_bfd_peers.ref new file mode 100644 index 0000000000..d6df1ebfb2 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt3/step2/show_bfd_peers.ref @@ -0,0 +1,14 @@ +[ + { + "interface": "eth-rt1", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt1", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd-ospf-topo1/rt3/zebra.conf b/tests/topotests/bfd-ospf-topo1/rt3/zebra.conf new file mode 100644 index 0000000000..d368de9bbe --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt3/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname rt3 +! +debug zebra kernel +debug zebra packet +! +interface lo + ip address 3.3.3.3/32 + ipv6 address ::ffff:0303:0303/128 +! +interface eth-rt1 + ip address 10.0.2.2/24 +! +interface eth-rt4 + ip address 10.0.4.1/24 +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bfd-ospf-topo1/rt4/bfdd.conf b/tests/topotests/bfd-ospf-topo1/rt4/bfdd.conf new file mode 100644 index 0000000000..f35e772790 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt4/bfdd.conf @@ -0,0 +1,5 @@ +! +debug bfd network +debug bfd peer +debug bfd zebra +! diff --git a/tests/topotests/bfd-ospf-topo1/rt4/ospf6d.conf b/tests/topotests/bfd-ospf-topo1/rt4/ospf6d.conf new file mode 100644 index 0000000000..bccd1e75bd --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt4/ospf6d.conf @@ -0,0 +1,18 @@ +log file ospf6d.log +! +hostname rt4 +! +password 1 +! +interface eth-rt3 + ipv6 ospf6 network broadcast +! +interface eth-rt5 + ipv6 ospf6 network broadcast +! +router ospf6 + ospf6 router-id 4.4.4.4 + interface eth-rt3 area 0.0.0.0 + interface eth-rt5 area 0.0.0.0 + redistribute connected +! diff --git a/tests/topotests/bfd-ospf-topo1/rt4/ospfd.conf b/tests/topotests/bfd-ospf-topo1/rt4/ospfd.conf new file mode 100644 index 0000000000..3a2568b4ab --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt4/ospfd.conf @@ -0,0 +1,23 @@ +log file ospfd.log +! +hostname rt4 +! +password 1 +! +debug ospf event +debug ospf zebra +! +interface lo + ip ospf area 0.0.0.0 +! +interface eth-rt3 + ip ospf area 0.0.0.0 +! +interface eth-rt5 + ip ospf area 0.0.0.0 +! +router ospf + ospf router-id 4.4.4.4 + passive interface lo + router-info area 0.0.0.0 +! diff --git a/tests/topotests/bfd-ospf-topo1/rt4/zebra.conf b/tests/topotests/bfd-ospf-topo1/rt4/zebra.conf new file mode 100644 index 0000000000..7b053bac35 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt4/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname rt4 +! +debug zebra kernel +debug zebra packet +! +interface lo + ip address 4.4.4.4/32 + ipv6 address ::ffff:0404:0404/128 +! +interface eth-rt3 + ip address 10.0.4.2/24 +! +interface eth-rt5 + ip address 10.0.5.1/24 +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bfd-ospf-topo1/rt5/bfdd.conf b/tests/topotests/bfd-ospf-topo1/rt5/bfdd.conf new file mode 100644 index 0000000000..f35e772790 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt5/bfdd.conf @@ -0,0 +1,5 @@ +! +debug bfd network +debug bfd peer +debug bfd zebra +! diff --git a/tests/topotests/bfd-ospf-topo1/rt5/ospf6d.conf b/tests/topotests/bfd-ospf-topo1/rt5/ospf6d.conf new file mode 100644 index 0000000000..766862276c --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt5/ospf6d.conf @@ -0,0 +1,18 @@ +log file ospf6d.log +! +hostname rt5 +! +password 1 +! +interface eth-rt2 + ipv6 ospf6 network broadcast +! +interface eth-rt4 + ipv6 ospf6 network broadcast +! +router ospf6 + ospf6 router-id 5.5.5.5 + interface eth-rt2 area 0.0.0.0 + interface eth-rt4 area 0.0.0.0 + redistribute connected +! diff --git a/tests/topotests/bfd-ospf-topo1/rt5/ospfd.conf b/tests/topotests/bfd-ospf-topo1/rt5/ospfd.conf new file mode 100644 index 0000000000..a35de5f45f --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt5/ospfd.conf @@ -0,0 +1,23 @@ +log file ospfd.log +! +hostname rt5 +! +password 1 +! +debug ospf event +debug ospf zebra +! +interface lo + ip ospf area 0.0.0.0 +! +interface eth-rt2 + ip ospf area 0.0.0.0 +! +interface eth-rt4 + ip ospf area 0.0.0.0 +! +router ospf + ospf router-id 5.5.5.5 + passive interface lo + router-info area 0.0.0.0 +! diff --git a/tests/topotests/bfd-ospf-topo1/rt5/zebra.conf b/tests/topotests/bfd-ospf-topo1/rt5/zebra.conf new file mode 100644 index 0000000000..0b7c9e02f3 --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/rt5/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname rt5 +! +debug zebra kernel +debug zebra packet +! +interface lo + ip address 5.5.5.5/32 + ipv6 address ::ffff:0505:0505/128 +! +interface eth-rt2 + ip address 10.0.3.2/24 +! +interface eth-rt4 + ip address 10.0.5.2/24 +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bfd-ospf-topo1/test_bfd_ospf_topo1.py b/tests/topotests/bfd-ospf-topo1/test_bfd_ospf_topo1.py new file mode 100755 index 0000000000..1cec62789b --- /dev/null +++ b/tests/topotests/bfd-ospf-topo1/test_bfd_ospf_topo1.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python + +# +# test_bfd_ospf_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +test_bfd_ospf_topo1.py: + + +---------+ + | | + eth-rt2 (.1) | RT1 | eth-rt3 (.1) + +----------+ 1.1.1.1 +----------+ + | | | | + | +---------+ | + | | + | 10.0.2.0/24 | + | | + | eth-rt1 | (.2) + | 10.0.1.0/24 +----+----+ + | | | + | | RT3 | + | | 3.3.3.3 | + | | | + (.2) | eth-rt1 +----+----+ + +----+----+ eth-rt4 | (.1) + | | | + | RT2 | | + | 2.2.2.2 | 10.0.4.0/24 | + | | | + +----+----+ | + (.1) | eth-rt5 eth-rt3 | (.2) + | +----+----+ + | | | + | | RT4 | + | | 4.4.4.4 | + | | | + | +----+----+ + | 10.0.3.0/24 eth-rt5 | (.1) + | | + | | + | 10.0.5.0/24 | + | | + | +---------+ | + | | | | + +----------+ RT5 +----------+ + eth-rt2 (.2) | 5.5.5.5 | eth-rt4 (.2) + | | + +---------+ + +""" + +import os +import sys +import pytest +import json +import re +from time import sleep +from time import time +from functools import partial + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. +from mininet.topo import Topo + + +class TemplateTopo(Topo): + "Test topology builder" + + def build(self, *_args, **_opts): + "Build function" + tgen = get_topogen(self) + + # + # Define FRR Routers + # + for router in ["rt1", "rt2", "rt3", "rt4", "rt5"]: + tgen.add_router(router) + + # + # Define connections + # + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["rt1"], nodeif="eth-rt2") + switch.add_link(tgen.gears["rt2"], nodeif="eth-rt1") + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["rt1"], nodeif="eth-rt3") + switch.add_link(tgen.gears["rt3"], nodeif="eth-rt1") + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["rt2"], nodeif="eth-rt5") + switch.add_link(tgen.gears["rt5"], nodeif="eth-rt2") + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["rt3"], nodeif="eth-rt4") + switch.add_link(tgen.gears["rt4"], nodeif="eth-rt3") + + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["rt4"], nodeif="eth-rt5") + switch.add_link(tgen.gears["rt5"], nodeif="eth-rt4") + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(TemplateTopo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + # For all registered routers, load the zebra configuration file + for rname, router in router_list.iteritems(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BFD, os.path.join(CWD, "{}/bfdd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_OSPF6, os.path.join(CWD, "{}/ospf6d.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def print_cmd_result(rname, command): + print(get_topogen().gears[rname].vtysh_cmd(command, isjson=False)) + + +def router_compare_json_output(rname, command, reference, count=120, wait=0.5): + "Compare router JSON output" + + logger.info('Comparing router "%s" "%s" output', rname, command) + + tgen = get_topogen() + filename = "{}/{}/{}".format(CWD, rname, reference) + expected = json.loads(open(filename).read()) + + # Run test function until we get an result. Wait at most 60 seconds. + test_func = partial(topotest.router_json_cmp, tgen.gears[rname], command, expected) + _, diff = topotest.run_and_expect(test_func, None, count=count, wait=wait) + assertmsg = '"{}" JSON output mismatches the expected result'.format(rname) + assert diff is None, assertmsg + + +## TEST STEPS + + +def test_rib_ospf_step1(): + logger.info("Test (step 1): verify RIB for OSPF") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router_compare_json_output( + "rt1", "show ip route ospf json", "step1/show_ip_route.ref" + ) + router_compare_json_output( + "rt1", "show ipv6 route ospf json", "step1/show_ipv6_route.ref" + ) + + +def test_bfd_ospf_sessions_step2(): + logger.info("Test (step 2): verify BFD peers for OSPF") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # BFD is just used on three routers + for rt in ["rt1", "rt2", "rt3"]: + router_compare_json_output( + rt, "show bfd peers json", "step2/show_bfd_peers.ref" + ) + + +def test_bfd_ospf_interface_failure_rt2_step3(): + logger.info("Test (step 3): Check failover handling with RT2 down") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Let's kill the interface on rt2 and see what happens with the RIB and BFD on rt1 + tgen.gears["rt2"].link_enable("eth-rt1", enabled=False) + + # By default BFD provides a recovery time of 900ms plus jitter, so let's wait + # initial 2 seconds to let the CI not suffer. + # TODO: add check for array size + sleep(2) + router_compare_json_output( + "rt1", "show ip route ospf json", "step3/show_ip_route_rt2_down.ref", 1, 0 + ) + router_compare_json_output( + "rt1", "show ipv6 route ospf json", "step3/show_ipv6_route_rt2_down.ref", 1, 0 + ) + router_compare_json_output( + "rt1", "show bfd peers json", "step3/show_bfd_peers_rt2_down.ref", 1, 0 + ) + + # Check recovery, this can take some time + tgen.gears["rt2"].link_enable("eth-rt1", enabled=True) + + router_compare_json_output( + "rt1", "show ip route ospf json", "step3/show_ip_route_healthy.ref" + ) + router_compare_json_output( + "rt1", "show ipv6 route ospf json", "step3/show_ipv6_route_healthy.ref" + ) + router_compare_json_output( + "rt1", "show bfd peers json", "step3/show_bfd_peers_healthy.ref" + ) + + +def test_bfd_ospf_interface_failure_rt3_step3(): + logger.info("Test (step 3): Check failover handling with RT3 down") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Let's kill the interface on rt3 and see what happens with the RIB and BFD on rt1 + tgen.gears["rt3"].link_enable("eth-rt1", enabled=False) + + # By default BFD provides a recovery time of 900ms plus jitter, so let's wait + # initial 2 seconds to let the CI not suffer. + # TODO: add check for array size + sleep(2) + router_compare_json_output( + "rt1", "show ip route ospf json", "step3/show_ip_route_rt3_down.ref", 1, 0 + ) + router_compare_json_output( + "rt1", "show ipv6 route ospf json", "step3/show_ipv6_route_rt3_down.ref", 1, 0 + ) + router_compare_json_output( + "rt1", "show bfd peers json", "step3/show_bfd_peers_rt3_down.ref", 1, 0 + ) + + # Check recovery, this can take some time + tgen.gears["rt3"].link_enable("eth-rt1", enabled=True) + + router_compare_json_output( + "rt1", "show ip route ospf json", "step3/show_ip_route_healthy.ref" + ) + router_compare_json_output( + "rt1", "show ipv6 route ospf json", "step3/show_ipv6_route_healthy.ref" + ) + router_compare_json_output( + "rt1", "show bfd peers json", "step3/show_bfd_peers_healthy.ref" + ) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/yang/frr-nexthop.yang b/yang/frr-nexthop.yang index 619514de7d..2df2e2958e 100644 --- a/yang/frr-nexthop.yang +++ b/yang/frr-nexthop.yang @@ -61,7 +61,7 @@ module frr-nexthop { type union { type inet:ip-address; type string { - pattern ''; + pattern ""; } } } @@ -160,6 +160,7 @@ module frr-nexthop { description "The nexthop vrf name, if different from the route."; } + leaf gateway { type frr-nexthop:optional-ip-address; description @@ -173,15 +174,12 @@ module frr-nexthop { } leaf bh-type { - when "../nh-type = 'blackhole'"; type blackhole-type; description "A blackhole sub-type, if the nexthop is a blackhole type."; } leaf onlink { - when "../nh-type = 'ip4-ifindex' or - ../nh-type = 'ip6-ifindex'"; type boolean; default "false"; description diff --git a/zebra/dplane_fpm_nl.c b/zebra/dplane_fpm_nl.c index 51ce59c477..bf733e38f7 100644 --- a/zebra/dplane_fpm_nl.c +++ b/zebra/dplane_fpm_nl.c @@ -1236,7 +1236,13 @@ static int fpm_process_queue(struct thread *t) if (ctx == NULL) break; - fpm_nl_enqueue(fnc, ctx); + /* + * Intentionally ignoring the return value + * as that we are ensuring that we can write to + * the output data in the STREAM_WRITEABLE + * check above, so we can ignore the return + */ + (void)fpm_nl_enqueue(fnc, ctx); /* Account the processed entries. */ processed_contexts++; |
