diff options
29 files changed, 2547 insertions, 68 deletions
diff --git a/.gitignore b/.gitignore index 4e120dae85..33d03296db 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ /libtool.orig /changelog-auto /test-driver +/test-suite.log /Makefile /Makefile.in @@ -57,6 +58,7 @@ *.pb.cc *_clippy.c *.bc +*.ll *.cg.json *.cg.dot *.cg.svg diff --git a/configure.ac b/configure.ac index 560cd73860..96bacc9ed4 100644 --- a/configure.ac +++ b/configure.ac @@ -1924,7 +1924,7 @@ dnl --------------- dnl gRPC dnl --------------- if test "$enable_grpc" = "yes"; then - AC_LANG([C++]) + AC_LANG_PUSH([C++]) AX_CXX_COMPILE_STDCXX([11], [ext]) PKG_CHECK_MODULES([GRPC], [grpc >= 6.0.0 grpc++ >= 1.16.1 protobuf >= 3.6.1 ], [ AC_CHECK_PROGS([PROTOC], [protoc], [/bin/false]) @@ -1938,6 +1938,7 @@ if test "$enable_grpc" = "yes"; then GRPC=false AC_MSG_ERROR([grpc/grpc++ were not found on your system.]) ]) + AC_LANG_POP([C++]) fi dnl ----- diff --git a/doc/user/zebra.rst b/doc/user/zebra.rst index ac1686dd27..1539f9a9d1 100644 --- a/doc/user/zebra.rst +++ b/doc/user/zebra.rst @@ -1058,6 +1058,11 @@ zebra Terminal Mode Commands nexthop groups that do have an afi. [type] allows you to filter those only coming from a specific NHG type (protocol). +.. clicmd:: show <ip|ipv6> zebra route dump [<vrf> VRFNAME] + + It dumps all the routes from RIB with detailed information including + internal flags, status etc. This is defined as a hidden command. + Router-id ========= diff --git a/isisd/isis_bfd.c b/isisd/isis_bfd.c index ed4d2c6539..7510e310f7 100644 --- a/isisd/isis_bfd.c +++ b/isisd/isis_bfd.c @@ -132,6 +132,7 @@ static void bfd_handle_adj_up(struct isis_adjacency *adj) bfd_sess_set_ipv6_addrs(adj->bfd_session, &src_ip.ipv6, &dst_ip.ipv6); bfd_sess_set_interface(adj->bfd_session, adj->circuit->interface->name); + bfd_sess_set_vrf(adj->bfd_session, adj->circuit->interface->vrf_id); bfd_sess_set_profile(adj->bfd_session, circuit->bfd_config.profile); bfd_sess_install(adj->bfd_session); return; diff --git a/isisd/isis_circuit.c b/isisd/isis_circuit.c index 2a197ab2b7..4fa28a4ad9 100644 --- a/isisd/isis_circuit.c +++ b/isisd/isis_circuit.c @@ -497,7 +497,7 @@ void isis_circuit_if_add(struct isis_circuit *circuit, struct interface *ifp) circuit->circ_type = CIRCUIT_T_BROADCAST; } else if (if_is_pointopoint(ifp)) { circuit->circ_type = CIRCUIT_T_P2P; - } else if (if_is_loopback(ifp)) { + } else if (if_is_loopback_or_vrf(ifp)) { circuit->circ_type = CIRCUIT_T_LOOPBACK; circuit->is_passive = 1; } else { @@ -1350,7 +1350,7 @@ ferr_r isis_circuit_passive_set(struct isis_circuit *circuit, bool passive) if (circuit->is_passive == passive) return ferr_ok(); - if (if_is_loopback(circuit->interface) && !passive) + if (if_is_loopback_or_vrf(circuit->interface) && !passive) return ferr_cfg_invalid("loopback is always passive"); if (circuit->state != C_STATE_UP) { diff --git a/isisd/isis_cli.c b/isisd/isis_cli.c index 5aea9f25d9..415c25fbf9 100644 --- a/isisd/isis_cli.c +++ b/isisd/isis_cli.c @@ -195,7 +195,7 @@ DEFPY_YANG(ip_router_isis, ip_router_isis_cmd, /* check if the interface is a loopback and if so set it as passive */ ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false); - if (ifp && if_is_loopback(ifp)) + if (ifp && if_is_loopback_or_vrf(ifp)) nb_cli_enqueue_change(vty, "./frr-isisd:isis/passive", NB_OP_MODIFY, "true"); @@ -252,7 +252,7 @@ DEFPY_YANG(ip6_router_isis, ip6_router_isis_cmd, /* check if the interface is a loopback and if so set it as passive */ ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false); - if (ifp && if_is_loopback(ifp)) + if (ifp && if_is_loopback_or_vrf(ifp)) nb_cli_enqueue_change(vty, "./frr-isisd:isis/passive", NB_OP_MODIFY, "true"); diff --git a/isisd/isis_nb_config.c b/isisd/isis_nb_config.c index a00c86eeaf..2622c5b51b 100644 --- a/isisd/isis_nb_config.c +++ b/isisd/isis_nb_config.c @@ -2966,7 +2966,7 @@ int lib_interface_isis_passive_modify(struct nb_cb_modify_args *args) ifp = circuit->interface; if (!ifp) return NB_OK; - if (if_is_loopback(ifp)) { + if (if_is_loopback_or_vrf(ifp)) { snprintf(args->errmsg, args->errmsg_len, "Loopback is always passive"); return NB_ERR_VALIDATION; diff --git a/lib/compiler.h b/lib/compiler.h index 86cf347e01..bbfe01b569 100644 --- a/lib/compiler.h +++ b/lib/compiler.h @@ -140,21 +140,6 @@ extern "C" { #define MACRO_REQUIRE_SEMICOLON() \ _Static_assert(1, "please add a semicolon after this macro") -#if CONFDATE < 20210601 -#ifdef ENABLE_BGP_VNC -/* temporarily disabled for transition for LabN CI - * NB: it's not possible to generate a deprecation warning for this, hence - * the shortened transition period (since otherwise new uses of the old syntax - * may creep in without errors) - */ -#undef MACRO_REQUIRE_SEMICOLON -#define MACRO_REQUIRE_SEMICOLON() \ - /* nothing */ -#endif /* ENABLE_BGP_VNC */ -#else /* CONFDATE >= 20210601 */ -CPP_NOTICE("time to remove this CONFDATE block") -#endif - /* variadic macros, use like: * #define V_0() ... * #define V_1(x) ... @@ -331,7 +316,7 @@ CPP_NOTICE("time to remove this CONFDATE block") 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_ARG_N(__VA_ARGS__) #define PP_NARG(...) PP_NARG_(_, ##__VA_ARGS__, PP_RSEQ_N()) diff --git a/lib/frr_zmq.c b/lib/frr_zmq.c index 05f0fce5fc..ce52848a25 100644 --- a/lib/frr_zmq.c +++ b/lib/frr_zmq.c @@ -135,8 +135,8 @@ static int frrzmq_read_msg(struct thread *t) if (read) frrzmq_check_events(cbp, &cb->write, ZMQ_POLLOUT); - _thread_add_read_write(t->xref, t->master, frrzmq_read_msg, cbp, - cb->fd, &cb->read.thread); + thread_add_read(t->master, frrzmq_read_msg, cbp, + cb->fd, &cb->read.thread); return 0; out_err: @@ -191,11 +191,11 @@ int _frrzmq_thread_add_read(const struct xref_threadsched *xref, if (events & ZMQ_POLLIN) { thread_cancel(&cb->read.thread); - _thread_add_event(xref, master, frrzmq_read_msg, cbp, fd, + thread_add_event(master, frrzmq_read_msg, cbp, fd, &cb->read.thread); } else - _thread_add_read_write(xref, master, frrzmq_read_msg, cbp, fd, - &cb->read.thread); + thread_add_read(master, frrzmq_read_msg, cbp, fd, + &cb->read.thread); return 0; } @@ -241,8 +241,8 @@ static int frrzmq_write_msg(struct thread *t) if (written) frrzmq_check_events(cbp, &cb->read, ZMQ_POLLIN); - _thread_add_read_write(t->xref, t->master, frrzmq_write_msg, cbp, - cb->fd, &cb->write.thread); + thread_add_write(t->master, frrzmq_write_msg, cbp, + cb->fd, &cb->write.thread); return 0; out_err: @@ -297,8 +297,8 @@ int _frrzmq_thread_add_write(const struct xref_threadsched *xref, _thread_add_event(xref, master, frrzmq_write_msg, cbp, fd, &cb->write.thread); } else - _thread_add_read_write(xref, master, frrzmq_write_msg, cbp, fd, - &cb->write.thread); + thread_add_write(master, frrzmq_write_msg, cbp, fd, + &cb->write.thread); return 0; } @@ -310,7 +310,7 @@ void frrzmq_thread_cancel(struct frrzmq_cb **cb, struct cb_core *core) thread_cancel(&core->thread); if ((*cb)->read.cancelled && !(*cb)->read.thread - && (*cb)->write.cancelled && (*cb)->write.thread) + && (*cb)->write.cancelled && !(*cb)->write.thread) XFREE(MTYPE_ZEROMQ_CB, *cb); } @@ -330,12 +330,16 @@ void frrzmq_check_events(struct frrzmq_cb **cbp, struct cb_core *core, len = sizeof(events); if (zmq_getsockopt(cb->zmqsock, ZMQ_EVENTS, &events, &len)) return; - if (events & event && core->thread && !core->cancelled) { + if ((events & event) && core->thread && !core->cancelled) { struct thread_master *tm = core->thread->master; + thread_cancel(&core->thread); - thread_add_event(tm, (event == ZMQ_POLLIN ? frrzmq_read_msg - : frrzmq_write_msg), - cbp, cb->fd, &core->thread); + if (event == ZMQ_POLLIN) + thread_add_event(tm, frrzmq_read_msg, + cbp, cb->fd, &core->thread); + else + thread_add_event(tm, frrzmq_write_msg, + cbp, cb->fd, &core->thread); } } diff --git a/lib/prefix.h b/lib/prefix.h index d7ee1b8e4c..217a23d561 100644 --- a/lib/prefix.h +++ b/lib/prefix.h @@ -315,10 +315,12 @@ struct prefix_sg { #ifndef __cplusplus #define prefixtype(uname, typename, fieldname) \ typename *fieldname; +#define TRANSPARENT_UNION __attribute__((transparent_union)) #else #define prefixtype(uname, typename, fieldname) \ typename *fieldname; \ uname(typename *x) { this->fieldname = x; } +#define TRANSPARENT_UNION #endif union prefixptr { @@ -328,7 +330,7 @@ union prefixptr { prefixtype(prefixptr, struct prefix_evpn, evp) prefixtype(prefixptr, struct prefix_fs, fs) prefixtype(prefixptr, struct prefix_rd, rd) -} __attribute__((transparent_union)); +} TRANSPARENT_UNION; union prefixconstptr { prefixtype(prefixconstptr, const struct prefix, p) @@ -337,7 +339,10 @@ union prefixconstptr { prefixtype(prefixconstptr, const struct prefix_evpn, evp) prefixtype(prefixconstptr, const struct prefix_fs, fs) prefixtype(prefixconstptr, const struct prefix_rd, rd) -} __attribute__((transparent_union)); +} TRANSPARENT_UNION; + +#undef prefixtype +#undef TRANSPARENT_UNION #ifndef INET_ADDRSTRLEN #define INET_ADDRSTRLEN 16 diff --git a/ospf6d/ospf6_abr.c b/ospf6d/ospf6_abr.c index 286e642781..1af8aed1a9 100644 --- a/ospf6d/ospf6_abr.c +++ b/ospf6d/ospf6_abr.c @@ -1224,8 +1224,6 @@ void ospf6_abr_examin_summary(struct ospf6_lsa *lsa, struct ospf6_area *oa) if (table->hook_add) (*table->hook_add)(old_route); - /* Delete new route */ - ospf6_route_delete(route); break; } @@ -1253,7 +1251,9 @@ void ospf6_abr_examin_summary(struct ospf6_lsa *lsa, struct ospf6_area *oa) listnode_add_sort(route->paths, path); /* ospf6_ia_add_nw_route (table, &prefix, route); */ ospf6_route_add(route, table); - } + } else + /* if we did not add the route remove it */ + ospf6_route_delete(route); } void ospf6_abr_examin_brouter(uint32_t router_id, struct ospf6_route *route, diff --git a/ospf6d/ospf6_bfd.c b/ospf6d/ospf6_bfd.c index ba8c398dee..fc1e718540 100644 --- a/ospf6d/ospf6_bfd.c +++ b/ospf6d/ospf6_bfd.c @@ -143,6 +143,7 @@ void ospf6_bfd_info_nbr_create(struct ospf6_interface *oi, bfd_sess_set_ipv6_addrs(on->bfd_session, on->ospf6_if->linklocal_addr, &on->linklocal_addr); bfd_sess_set_interface(on->bfd_session, oi->interface->name); + bfd_sess_set_vrf(on->bfd_session, oi->interface->vrf_id); bfd_sess_set_profile(on->bfd_session, oi->bfd_config.profile); } diff --git a/ospf6d/ospf6_route.c b/ospf6d/ospf6_route.c index 2daf119c52..908011c946 100644 --- a/ospf6d/ospf6_route.c +++ b/ospf6d/ospf6_route.c @@ -39,6 +39,7 @@ #include "ospf6_zebra.h" DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_ROUTE, "OSPF6 route"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_ROUTE_TABLE, "OSPF6 route table"); DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_NEXTHOP, "OSPF6 nexthop"); DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_PATH, "OSPF6 Path"); @@ -1021,7 +1022,8 @@ void ospf6_route_remove_all(struct ospf6_route_table *table) struct ospf6_route_table *ospf6_route_table_create(int s, int t) { struct ospf6_route_table *new; - new = XCALLOC(MTYPE_OSPF6_ROUTE, sizeof(struct ospf6_route_table)); + new = XCALLOC(MTYPE_OSPF6_ROUTE_TABLE, + sizeof(struct ospf6_route_table)); new->table = route_table_init(); new->scope_type = s; new->table_type = t; @@ -1033,7 +1035,7 @@ void ospf6_route_table_delete(struct ospf6_route_table *table) ospf6_route_remove_all(table); bf_free(table->idspace); route_table_finish(table->table); - XFREE(MTYPE_OSPF6_ROUTE, table); + XFREE(MTYPE_OSPF6_ROUTE_TABLE, table); } diff --git a/ospf6d/ospf6_top.c b/ospf6d/ospf6_top.c index 2376409ccd..42405ca35e 100644 --- a/ospf6d/ospf6_top.c +++ b/ospf6d/ospf6_top.c @@ -413,6 +413,7 @@ void ospf6_delete(struct ospf6 *o) { struct listnode *node, *nnode; struct ospf6_area *oa; + struct vrf *vrf; QOBJ_UNREG(o); @@ -442,6 +443,12 @@ void ospf6_delete(struct ospf6 *o) ospf6_distance_reset(o); route_table_finish(o->distance_table); + if (o->vrf_id != VRF_UNKNOWN) { + vrf = vrf_lookup_by_id(o->vrf_id); + if (vrf) + ospf6_vrf_unlink(o, vrf); + } + XFREE(MTYPE_OSPF6_TOP, o->name); XFREE(MTYPE_OSPF6_TOP, o); } diff --git a/pceplib/pcep_session_logic_loop.c b/pceplib/pcep_session_logic_loop.c index 4b855c06cd..77392fb6a9 100644 --- a/pceplib/pcep_session_logic_loop.c +++ b/pceplib/pcep_session_logic_loop.c @@ -332,7 +332,7 @@ void *session_logic_loop(void *data) session_logic_handle ->session_event_queue); pthread_mutex_unlock( - &(session_logic_handle_ + &(session_logic_handle ->session_list_mutex)); continue; } @@ -350,7 +350,7 @@ void *session_logic_loop(void *data) session_logic_handle->session_event_queue); pthread_mutex_unlock( - &(session_logic_handle_->session_list_mutex)); + &(session_logic_handle->session_list_mutex)); } session_logic_handle->session_logic_condition = false; diff --git a/pimd/pim_bfd.c b/pimd/pim_bfd.c index bc518391a5..dfe2d5f2fa 100644 --- a/pimd/pim_bfd.c +++ b/pimd/pim_bfd.c @@ -96,6 +96,7 @@ void pim_bfd_info_nbr_create(struct pim_interface *pim_ifp, pim_ifp->bfd_config.min_rx, pim_ifp->bfd_config.min_tx); bfd_sess_set_ipv4_addrs(neigh->bfd_session, NULL, &neigh->source_addr); bfd_sess_set_interface(neigh->bfd_session, neigh->interface->name); + bfd_sess_set_vrf(neigh->bfd_session, neigh->interface->vrf_id); bfd_sess_set_profile(neigh->bfd_session, pim_ifp->bfd_config.profile); bfd_sess_install(neigh->bfd_session); } diff --git a/staticd/static_vty.c b/staticd/static_vty.c index 3bdd4a673c..ea09054a23 100644 --- a/staticd/static_vty.c +++ b/staticd/static_vty.c @@ -325,9 +325,8 @@ static int static_route_leak(struct vty *vty, const char *svrf, dnode = yang_dnode_get(vty->candidate_config->dnode, ab_xpath); if (!dnode) { - vty_out(vty, - "%% Refusing to remove a non-existent route\n"); - return ret; + /* Silently return */ + return CMD_SUCCESS; } dnode = yang_get_subtree_with_no_sibling(dnode); diff --git a/tests/topotests/bgp_gshut_topo1/ebgp_gshut_topo1.json b/tests/topotests/bgp_gshut_topo1/ebgp_gshut_topo1.json new file mode 100644 index 0000000000..dcd5af83f6 --- /dev/null +++ b/tests/topotests/bgp_gshut_topo1/ebgp_gshut_topo1.json @@ -0,0 +1,221 @@ +{ + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.1.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + "static_routes":[ + { + "network":"100.0.10.1/32", + "no_of_ip":5, + "next_hop":"Null0" + }, + { + "network":"1::1/128", + "no_of_ip":5, + "next_hop":"Null0" + }] + }, + "r2": { + "links": { + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r4": { + "dest_link": { + "r2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r4": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + } + }, + "r3": { + "links": { + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + }, + "r4": { + "links": { + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": {} + } + }, + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": {} + } + }, + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + } + } + } + } + } +}
\ No newline at end of file diff --git a/tests/topotests/bgp_gshut_topo1/ibgp_gshut_topo1.json b/tests/topotests/bgp_gshut_topo1/ibgp_gshut_topo1.json new file mode 100644 index 0000000000..464e362465 --- /dev/null +++ b/tests/topotests/bgp_gshut_topo1/ibgp_gshut_topo1.json @@ -0,0 +1,221 @@ +{
+ "ipv4base": "10.0.0.0",
+ "ipv4mask": 24,
+ "ipv6base": "fd00::",
+ "ipv6mask": 64,
+ "link_ip_start": {
+ "ipv4": "10.0.1.0",
+ "v4mask": 24,
+ "ipv6": "fd00::",
+ "v6mask": 64
+ },
+ "lo_prefix": {
+ "ipv4": "1.0.",
+ "v4mask": 32,
+ "ipv6": "2001:DB8:F::",
+ "v6mask": 128
+ },
+ "routers": {
+ "r1": {
+ "links": {
+ "r2": {"ipv4": "auto", "ipv6": "auto"},
+ "r3": {"ipv4": "auto", "ipv6": "auto"}
+ },
+ "bgp": {
+ "local_as": "100",
+ "address_family": {
+ "ipv4": {
+ "unicast": {
+ "redistribute": [
+ {"redist_type": "static"}
+ ],
+ "neighbor": {
+ "r2": {
+ "dest_link": {
+ "r1": {
+ "keepalivetimer": 1,
+ "holddowntimer": 3
+ }
+ }
+ },
+ "r3": {
+ "dest_link": {
+ "r1": {
+ "keepalivetimer": 1,
+ "holddowntimer": 3
+ }
+ }
+ }
+ }
+ }
+ },
+ "ipv6": {
+ "unicast": {
+ "redistribute": [
+ {"redist_type": "static"}
+ ],
+ "neighbor": {
+ "r2": {
+ "dest_link": {
+ "r1": {
+ "keepalivetimer": 1,
+ "holddowntimer": 3
+ }
+ }
+ },
+ "r3": {
+ "dest_link": {
+ "r1": {
+ "keepalivetimer": 1,
+ "holddowntimer": 3
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "static_routes":[
+ {
+ "network":"100.0.10.1/32",
+ "no_of_ip":5,
+ "next_hop":"Null0"
+ },
+ {
+ "network":"1::1/128",
+ "no_of_ip":5,
+ "next_hop":"Null0"
+ }]
+ },
+ "r2": {
+ "links": {
+ "r1": {"ipv4": "auto", "ipv6": "auto"},
+ "r4": {"ipv4": "auto", "ipv6": "auto"}
+ },
+ "bgp": {
+ "local_as": "100",
+ "address_family": {
+ "ipv4": {
+ "unicast": {
+ "neighbor": {
+ "r1": {
+ "dest_link": {
+ "r2": {}
+ }
+ },
+ "r4": {
+ "dest_link": {
+ "r2": {}
+ }
+ }
+ }
+ }
+ },
+ "ipv6": {
+ "unicast": {
+ "neighbor": {
+ "r1": {
+ "dest_link": {
+ "r2": {}
+ }
+ },
+ "r4": {
+ "dest_link": {
+ "r2": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "r3": {
+ "links": {
+ "r1": {"ipv4": "auto", "ipv6": "auto"},
+ "r4": {"ipv4": "auto", "ipv6": "auto"}
+ },
+ "bgp": {
+ "local_as": "100",
+ "address_family": {
+ "ipv4": {
+ "unicast": {
+ "neighbor": {
+ "r1": {
+ "dest_link": {
+ "r3": {}
+ }
+ },
+ "r4": {
+ "dest_link": {
+ "r3": {}
+ }
+ }
+ }
+ }
+ },
+ "ipv6": {
+ "unicast": {
+ "neighbor": {
+ "r1": {
+ "dest_link": {
+ "r3": {}
+ }
+ },
+ "r4": {
+ "dest_link": {
+ "r3": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "r4": {
+ "links": {
+ "r2": {"ipv4": "auto", "ipv6": "auto"},
+ "r3": {"ipv4": "auto", "ipv6": "auto"}
+ },
+ "bgp": {
+ "local_as": "200",
+ "address_family": {
+ "ipv4": {
+ "unicast": {
+ "neighbor": {
+ "r2": {
+ "dest_link": {
+ "r4": {}
+ }
+ },
+ "r3": {
+ "dest_link": {
+ "r4": {}
+ }
+ }
+ }
+ }
+ },
+ "ipv6": {
+ "unicast": {
+ "neighbor": {
+ "r2": {
+ "dest_link": {
+ "r4": {}
+ }
+ },
+ "r3": {
+ "dest_link": {
+ "r4": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/tests/topotests/bgp_gshut_topo1/test_ebgp_gshut_topo1.py b/tests/topotests/bgp_gshut_topo1/test_ebgp_gshut_topo1.py new file mode 100644 index 0000000000..868aec9f3e --- /dev/null +++ b/tests/topotests/bgp_gshut_topo1/test_ebgp_gshut_topo1.py @@ -0,0 +1,630 @@ +#!/usr/bin/env python + +# +# Copyright (c) 2021 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. +# ("NetDEF") in this file. +# +# 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 VMWARE DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VMWARE 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. +# + + +""" +Following tests are covered to test ecmp functionality on BGP GSHUT. +1. Verify graceful-shutdown functionality with eBGP peers +2. Verify graceful-shutdown functionality when daemons + bgpd/zebra/staticd and frr services are restarted with eBGP peers +""" + +import os +import sys +import time +import json +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from mininet.topo import Topo +from time import sleep + +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + verify_rib, + create_static_routes, + check_address_types, + interface_status, + reset_config_on_routers, + step, + get_frr_ipv6_linklocal, + kill_router_daemons, + start_router_daemons, + stop_router, + start_router, + create_route_maps, + create_bgp_community_lists, + delete_route_maps, + required_linux_kernel_version, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + clear_bgp, + verify_bgp_rib, + verify_bgp_attributes, +) +from lib.topojson import build_topo_from_json, build_config_from_json + +# Reading the data from JSON File for topology and configuration creation +jsonFile = "{}/ebgp_gshut_topo1.json".format(CWD) +try: + with open(jsonFile, "r") as topoJson: + topo = json.load(topoJson) +except IOError: + logger.info("Could not read file:", jsonFile) + +# Global variables +NETWORK = {"ipv4": "100.0.10.1/32", "ipv6": "1::1/128"} +NEXT_HOP_IP_1 = {"ipv4": "10.0.2.1", "ipv6": "fd00:0:0:1::1"} +NEXT_HOP_IP_2 = {"ipv4": "10.0.4.2", "ipv6": "fd00:0:0:3::2"} +PREFERRED_NEXT_HOP = "link_local" +BGP_CONVERGENCE = False + + +class GenerateTopo(Topo): + """ + Test topology builder + + * `Topo`: Topology object + """ + + def build(self, *_args, **_opts): + "Build function" + tgen = get_topogen(self) + + # This function only purpose is to create topology + # as defined in input json file. + # + # Create topology (setup module) + # Creating 2 routers topology, r1, r2in IBGP + # Bring up topology + + # Building topology from json file + build_topo_from_json(tgen, topo) + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + global ADDR_TYPES + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.16") + if result is not True: + pytest.skip("Kernel requirements are not met") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + tgen = Topogen(GenerateTopo, mod.__name__) + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start deamons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """ + Teardown the pytest environment. + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + +########################### +# Local APIs +########################### + + +def next_hop_per_address_family( + tgen, dut, peer, addr_type, next_hop_dict, preferred_next_hop=PREFERRED_NEXT_HOP +): + """ + This function returns link_local or global next_hop per address-family + """ + + intferface = topo["routers"][peer]["links"]["{}".format(dut)]["interface"] + if addr_type == "ipv6" and "link_local" in preferred_next_hop: + next_hop = get_frr_ipv6_linklocal(tgen, peer, intf=intferface) + else: + next_hop = next_hop_dict[addr_type] + + return next_hop + + +########################### +# TESTCASES +########################### + + +def test_verify_graceful_shutdown_functionality_with_eBGP_peers_p0(request): + """ + Verify graceful-shutdown functionality with eBGP peers + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + reset_config_on_routers(tgen) + + step("Done in base config: Configure base config as per the topology") + step("Base config should be up, verify using BGP convergence") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Done in base config: Advertise prefixes from R1") + step("Verify BGP routes are received at R3 with best path from R3 to R1") + + for addr_type in ADDR_TYPES: + dut = "r3" + next_hop1 = next_hop_per_address_family( + tgen, "r3", "r1", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r3", "r4", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop1) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("On R1 configure:") + step("Create standard bgp community-list to permit graceful-shutdown:") + input_dict_1 = { + "r1": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "GSHUT", + "value": "graceful-shutdown", + } + ] + } + } + + result = create_bgp_community_lists(tgen, input_dict_1) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route-map to set community GSHUT in OUT direction") + + input_dict_2 = { + "r1": { + "route_maps": { + "GSHUT-OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": {"community": {"num": "graceful-shutdown"}}, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "FRR is setting local-pref to 0 by-default on receiver GSHUT community, " + "below step is not needed, but keeping for reference" + ) + step( + "On R3, apply route-map IN direction to match GSHUT community " + "and set local-preference to 0." + ) + + step( + "Verify BGP convergence on R3 and ensure all the neighbours state " + "is established" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP routes on R3:") + step("local pref for routes coming from R1 is set to 0.") + + for addr_type in ADDR_TYPES: + rmap_dict = {"r1": {"route_maps": {"GSHUT-OUT": [{"set": {"locPrf": 0}}],}}} + + static_routes = [NETWORK[addr_type]] + result = verify_bgp_attributes( + tgen, addr_type, dut, static_routes, "GSHUT-OUT", rmap_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Ensure that best path is selected from R4 to R3.") + + for addr_type in ADDR_TYPES: + dut = "r3" + next_hop1 = next_hop_per_address_family( + tgen, "r3", "r1", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r3", "r4", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop2) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_restarting_zebra_bgpd_staticd_frr_with_eBGP_peers_p0(request): + """ + Verify graceful-shutdown functionality when daemons bgpd/zebra/staticd and + frr services are restarted with eBGP peers + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + reset_config_on_routers(tgen) + + step("Done in base config: Configure base config as per the topology") + step("Base config should be up, verify using BGP convergence") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Done in base config: Advertise prefixes from R1") + step("Verify BGP routes are received at R3 with best path from R3 to R1") + + for addr_type in ADDR_TYPES: + dut = "r3" + next_hop1 = next_hop_per_address_family( + tgen, "r3", "r1", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r3", "r4", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop1) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("On R1 configure:") + step("Create standard bgp community-list to permit graceful-shutdown:") + input_dict_1 = { + "r1": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "GSHUT", + "value": "graceful-shutdown", + } + ] + } + } + + result = create_bgp_community_lists(tgen, input_dict_1) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route-map to set community GSHUT in OUT direction") + + input_dict_2 = { + "r1": { + "route_maps": { + "GSHUT-OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": {"community": {"num": "graceful-shutdown"}}, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "FRR is setting local-pref to 0 by-default on receiver GSHUT community, " + "below step is not needed, but keeping for reference" + ) + step( + "On R3, apply route-map IN direction to match GSHUT community " + "and set local-preference to 0." + ) + + step( + "Verify BGP convergence on R3 and ensure all the neighbours state " + "is established" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP routes on R3:") + step("local pref for routes coming from R1 is set to 0.") + + for addr_type in ADDR_TYPES: + rmap_dict = {"r1": {"route_maps": {"GSHUT-OUT": [{"set": {"locPrf": 0}}]}}} + + static_routes = [NETWORK[addr_type]] + result = verify_bgp_attributes( + tgen, addr_type, dut, static_routes, "GSHUT-OUT", rmap_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Ensure that best path is selected from R4 to R3.") + + for addr_type in ADDR_TYPES: + dut = "r3" + next_hop1 = next_hop_per_address_family( + tgen, "r3", "r1", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r3", "r4", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop2) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Restart daemons and frr services") + + for daemon in ["bgpd", "zebra", "staticd", "frr"]: + if daemon != "frr": + kill_router_daemons(tgen, "r3", ["staticd"]) + start_router_daemons(tgen, "r3", ["staticd"]) + else: + stop_router(tgen, "r3") + start_router(tgen, "r3") + + step( + "Verify BGP convergence on R3 and ensure all the neighbours state " + "is established" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify BGP routes on R3:") + step("local pref for routes coming from R1 is set to 0.") + + for addr_type in ADDR_TYPES: + rmap_dict = {"r1": {"route_maps": {"GSHUT-OUT": [{"set": {"locPrf": 0}}]}}} + + static_routes = [NETWORK[addr_type]] + result = verify_bgp_attributes( + tgen, addr_type, dut, static_routes, "GSHUT-OUT", rmap_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Ensure that best path is selected from R4 to R3.") + + for addr_type in ADDR_TYPES: + dut = "r3" + next_hop1 = next_hop_per_address_family( + tgen, "r3", "r1", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r3", "r4", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop2) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_gshut_topo1/test_ibgp_gshut_topo1.py b/tests/topotests/bgp_gshut_topo1/test_ibgp_gshut_topo1.py new file mode 100644 index 0000000000..69f4916374 --- /dev/null +++ b/tests/topotests/bgp_gshut_topo1/test_ibgp_gshut_topo1.py @@ -0,0 +1,676 @@ +#!/usr/bin/env python + +# +# Copyright (c) 2021 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. +# ("NetDEF") in this file. +# +# 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 VMWARE DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VMWARE 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. +# + + +""" +Following tests are covered to test ecmp functionality on BGP GSHUT. +1. Verify graceful-shutdown functionality with iBGP peers +2. Verify graceful-shutdown functionality after + deleting/re-adding route-map with iBGP peers +""" + +import os +import sys +import time +import json +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from mininet.topo import Topo +from time import sleep + +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + verify_rib, + create_static_routes, + check_address_types, + interface_status, + reset_config_on_routers, + step, + get_frr_ipv6_linklocal, + kill_router_daemons, + start_router_daemons, + stop_router, + start_router, + create_route_maps, + create_bgp_community_lists, + delete_route_maps, + required_linux_kernel_version, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + clear_bgp, + verify_bgp_rib, + verify_bgp_attributes, +) +from lib.topojson import build_topo_from_json, build_config_from_json + +# Reading the data from JSON File for topology and configuration creation +jsonFile = "{}/ibgp_gshut_topo1.json".format(CWD) +try: + with open(jsonFile, "r") as topoJson: + topo = json.load(topoJson) +except IOError: + logger.info("Could not read file:", jsonFile) + +# Global variables +NETWORK = {"ipv4": "100.0.10.1/32", "ipv6": "1::1/128"} +NEXT_HOP_IP_1 = {"ipv4": "10.0.3.1", "ipv6": "fd00:0:0:3::1"} +NEXT_HOP_IP_2 = {"ipv4": "10.0.4.1", "ipv6": "fd00:0:0:2::1"} +PREFERRED_NEXT_HOP = "link_local" +BGP_CONVERGENCE = False + + +class GenerateTopo(Topo): + """ + Test topology builder + + * `Topo`: Topology object + """ + + def build(self, *_args, **_opts): + "Build function" + tgen = get_topogen(self) + + # This function only purpose is to create topology + # as defined in input json file. + # + # Create topology (setup module) + # Creating 2 routers topology, r1, r2in IBGP + # Bring up topology + + # Building topology from json file + build_topo_from_json(tgen, topo) + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + global ADDR_TYPES + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.16") + if result is not True: + pytest.skip("Kernel requirements are not met") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + tgen = Topogen(GenerateTopo, mod.__name__) + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start deamons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """ + Teardown the pytest environment. + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + +########################### +# Local APIs +########################### + + +def next_hop_per_address_family( + tgen, dut, peer, addr_type, next_hop_dict, preferred_next_hop=PREFERRED_NEXT_HOP +): + """ + This function returns link_local or global next_hop per address-family + """ + + intferface = topo["routers"][peer]["links"]["{}".format(dut)]["interface"] + if addr_type == "ipv6" and "link_local" in preferred_next_hop: + next_hop = get_frr_ipv6_linklocal(tgen, peer, intf=intferface) + else: + next_hop = next_hop_dict[addr_type] + + return next_hop + + +########################### +# TESTCASES +########################### + + +def test_verify_graceful_shutdown_functionality_with_iBGP_peers_p0(request): + """ + Verify graceful-shutdown functionality with iBGP peers + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + reset_config_on_routers(tgen) + + step("Done in base config: Configure base config as per the topology") + step("Base config should be up, verify using BGP convergence") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Done in base config: Advertise prefixes from R1") + step("Verify BGP routes are received at R4 with best path from R3 to R1") + + for addr_type in ADDR_TYPES: + dut = "r4" + next_hop1 = next_hop_per_address_family( + tgen, "r4", "r2", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r4", "r3", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("On R1 configure:") + step("Create standard bgp community-list to permit graceful-shutdown:") + input_dict_1 = { + "r1": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "GSHUT", + "value": "graceful-shutdown", + } + ] + } + } + + result = create_bgp_community_lists(tgen, input_dict_1) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route-map to set community GSHUT in OUT direction") + + input_dict_2 = { + "r1": { + "route_maps": { + "GSHUT-OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": {"community": {"num": "graceful-shutdown"}}, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "FRR is setting local-pref to 0 by-default on receiver GSHUT community, " + "below step is not needed, but keeping for reference" + ) + step( + "On R3, apply route-map IN direction to match GSHUT community " + "and set local-preference to 0." + ) + + step( + "Verify BGP convergence on R4 and ensure all the neighbours state " + "is established" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP routes on R4:") + step("local pref for routes coming from R1 is set to 0.") + + for addr_type in ADDR_TYPES: + rmap_dict = {"r1": {"route_maps": {"GSHUT-OUT": [{"set": {"locPrf": 0}}],}}} + + static_routes = [NETWORK[addr_type]] + result = verify_bgp_attributes( + tgen, addr_type, dut, static_routes, "GSHUT-OUT", rmap_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Ensure that best path is selected from R4 to R3.") + + for addr_type in ADDR_TYPES: + dut = "r4" + next_hop1 = next_hop_per_address_family( + tgen, "r4", "r2", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r4", "r3", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop1) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_deleting_re_adding_route_map_with_iBGP_peers_p0(request): + """ + Verify graceful-shutdown functionality after deleting/re-adding route-map + with iBGP peers + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + reset_config_on_routers(tgen) + + step("Done in base config: Configure base config as per the topology") + step("Base config should be up, verify using BGP convergence") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Done in base config: Advertise prefixes from R1") + step("Verify BGP routes are received at R4 with best path from R3 to R1") + + for addr_type in ADDR_TYPES: + dut = "r4" + next_hop1 = next_hop_per_address_family( + tgen, "r4", "r2", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r4", "r3", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("On R1 configure:") + step("Create standard bgp community-list to permit graceful-shutdown:") + input_dict_1 = { + "r1": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "GSHUT", + "value": "graceful-shutdown", + } + ] + } + } + + result = create_bgp_community_lists(tgen, input_dict_1) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route-map to set community GSHUT in OUT direction") + + input_dict_2 = { + "r1": { + "route_maps": { + "GSHUT-OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": {"community": {"num": "graceful-shutdown"}}, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "FRR is setting local-pref to 0 by-default on receiver GSHUT community, " + "below step is not needed, but keeping for reference" + ) + step( + "On R3, apply route-map IN direction to match GSHUT community " + "and set local-preference to 0." + ) + + step( + "Verify BGP convergence on R4 and ensure all the neighbours state " + "is established" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP routes on R4:") + step("local pref for routes coming from R1 is set to 0.") + + for addr_type in ADDR_TYPES: + rmap_dict = {"r1": {"route_maps": {"GSHUT-OUT": [{"set": {"locPrf": 0}}],}}} + + static_routes = [NETWORK[addr_type]] + result = verify_bgp_attributes( + tgen, addr_type, dut, static_routes, "GSHUT-OUT", rmap_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Ensure that best path is selected from R4 to R3.") + + for addr_type in ADDR_TYPES: + dut = "r4" + next_hop1 = next_hop_per_address_family( + tgen, "r4", "r2", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r4", "r3", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop1) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Delete route-map from R1") + del_rmap_dict = { + "r1": { + "route_maps": { + "GSHUT-OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "community": {"num": "graceful-shutdown", "delete": True} + }, + } + ] + } + } + } + + result = create_route_maps(tgen, del_rmap_dict) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify BGP convergence on R3 and ensure that all neighbor state " + "is established" + ) + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP routes on R4:") + step("Ensure that best path is selected from R1->R3") + + for addr_type in ADDR_TYPES: + dut = "r4" + next_hop1 = next_hop_per_address_family( + tgen, "r4", "r2", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r4", "r3", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop=[next_hop1]) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=[next_hop1]) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Re-add route-map in R1") + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify BGP convergence on R3 and ensure all the neighbours state " + "is established" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP routes on R4:") + step("local pref for routes coming from R1 is set to 0.") + + for addr_type in ADDR_TYPES: + rmap_dict = {"r1": {"route_maps": {"GSHUT-OUT": [{"set": {"locPrf": 0}}]}}} + + static_routes = [NETWORK[addr_type]] + result = verify_bgp_attributes( + tgen, addr_type, dut, static_routes, "GSHUT-OUT", rmap_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Ensure that best path is selected from R4 to R3.") + + for addr_type in ADDR_TYPES: + dut = "r4" + next_hop1 = next_hop_per_address_family( + tgen, "r4", "r2", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r4", "r3", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop1) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/multicast_pim_sm_topo2/test_multicast_pim_sm_topo2.py b/tests/topotests/multicast_pim_sm_topo2/test_multicast_pim_sm_topo2.py index f1f130826f..ad3b77b843 100755 --- a/tests/topotests/multicast_pim_sm_topo2/test_multicast_pim_sm_topo2.py +++ b/tests/topotests/multicast_pim_sm_topo2/test_multicast_pim_sm_topo2.py @@ -519,8 +519,11 @@ def test_verify_mroute_and_traffic_when_pimd_restarted_p2(request): data["oil"], expected=False, ) - assert result is not True, "Testcase {} : Failed \n mroutes are" - " still present \n Error: {}".format(tc_name, result) + assert ( + result is not True + ), "Testcase {} : Failed \n mroutes are still present \n Error: {}".format( + tc_name, result + ) logger.info("Expected Behavior: {}".format(result)) write_test_footer(tc_name) @@ -727,8 +730,11 @@ def test_verify_mroute_and_traffic_when_frr_restarted_p2(request): data["oil"], expected=False, ) - assert result is not True, "Testcase {} : Failed \n mroutes are" - " still present \n Error: {}".format(tc_name, result) + assert ( + result is not True + ), "Testcase {} : Failed \n mroutes are still present \n Error: {}".format( + tc_name, result + ) logger.info("Expected Behavior: {}".format(result)) write_test_footer(tc_name) @@ -802,8 +808,9 @@ def test_verify_SPT_switchover_when_RPT_and_SPT_path_is_different_p0(request): state_before = verify_pim_interface_traffic(tgen, state_dict) assert isinstance( state_before, dict - ), "Testcase {} : Failed \n state_before is not dictionary \n " - "Error: {}".format(tc_name, result) + ), "Testcase {} : Failed \n state_before is not dictionary \nError: {}".format( + tc_name, result + ) result = iperfSendTraffic(tgen, "i2", _IGMP_JOIN_RANGE, 32, 2500) assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) @@ -885,8 +892,9 @@ def test_verify_SPT_switchover_when_RPT_and_SPT_path_is_different_p0(request): state_after = verify_pim_interface_traffic(tgen, state_dict) assert isinstance( state_after, dict - ), "Testcase {} : Failed \n state_before is not dictionary \n " - "Error: {}".format(tc_name, result) + ), "Testcase {} : Failed \n state_before is not dictionary \nError: {}".format( + tc_name, result + ) result = verify_state_incremented(state_before, state_after) assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) @@ -1167,8 +1175,11 @@ def test_verify_mroute_after_shut_noshut_of_upstream_interface_p1(request): data["oil"], expected=False, ) - assert result is not True, "Testcase {} : Failed \n mroutes are " - "still present \n Error: {}".format(tc_name, result) + assert ( + result is not True + ), "Testcase {} : Failed \n mroutes are still present \n Error: {}".format( + tc_name, result + ) logger.info("Expected Behavior: {}".format(result)) write_test_footer(tc_name) @@ -1718,8 +1729,11 @@ def test_verify_mroute_when_5_different_receiver_joining_same_sources_p0(request data["oil"], expected=False, ) - assert result is not True, "Testcase {} : Failed \n mroutes are" - " still present \n Error: {}".format(tc_name, result) + assert ( + result is not True + ), "Testcase {} : Failed \n mroutes are still present \n Error: {}".format( + tc_name, result + ) logger.info("Expected Behavior: {}".format(result)) step( diff --git a/tools/.gitignore b/tools/.gitignore index 63a5b61c35..1cc343a11a 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -8,3 +8,4 @@ /frrcommon.sh /frr.service /frr@.service +/frr-llvm-cg diff --git a/tools/etc/frr/support_bundle_commands.conf b/tools/etc/frr/support_bundle_commands.conf index f902d3dd21..0d64320488 100644 --- a/tools/etc/frr/support_bundle_commands.conf +++ b/tools/etc/frr/support_bundle_commands.conf @@ -40,6 +40,8 @@ CMD_LIST_START show zebra show zebra client summary show zebra router table summary +show ip zebra route dump +show ipv6 zebra route dump show ip nht vrf all show ipv6 nht vrf all show ip route vrf all diff --git a/tools/frr-llvm-cg.c b/tools/frr-llvm-cg.c index 84a756a376..fbcf9222d2 100644 --- a/tools/frr-llvm-cg.c +++ b/tools/frr-llvm-cg.c @@ -50,11 +50,15 @@ #include <json-c/json.h> +#include "frr-llvm-debuginfo.h" + /* if you want to use this without the special FRRouting defines, * remove the following #define */ #define FRR_SPECIFIC +static struct dbginfo *dbginfo; + static void dbgloc_add(struct json_object *jsobj, LLVMValueRef obj) { unsigned file_len = 0; @@ -85,6 +89,70 @@ static struct json_object *js_get_or_make(struct json_object *parent, return ret; } +static bool try_struct_fptr(struct json_object *js_call, LLVMValueRef gep, + const char *prefix) +{ + unsigned long long val = 0; + bool ret = false; + LLVMTypeRef ptrtype = LLVMTypeOf(LLVMGetOperand(gep, 0)); + LLVMValueRef idx; + + /* middle steps like struct a -> struct b a_member; -> fptr */ + for (int i = 1; ptrtype && i < LLVMGetNumOperands(gep) - 1; i++) { + if (LLVMGetTypeKind(ptrtype) == LLVMPointerTypeKind + || LLVMGetTypeKind(ptrtype) == LLVMArrayTypeKind + || LLVMGetTypeKind(ptrtype) == LLVMVectorTypeKind) { + ptrtype = LLVMGetElementType(ptrtype); + continue; + } + + if (LLVMGetTypeKind(ptrtype) != LLVMStructTypeKind) + return false; + + idx = LLVMGetOperand(gep, i); + if (!LLVMIsConstant(idx)) + return false; + val = LLVMConstIntGetZExtValue(idx); + + unsigned n = LLVMGetNumContainedTypes(ptrtype); + LLVMTypeRef arr[n]; + + if (val > n) + return false; + + LLVMGetSubtypes(ptrtype, arr); + ptrtype = arr[val]; + } + + if (!ptrtype) + return false; + + idx = LLVMGetOperand(gep, LLVMGetNumOperands(gep) - 1); + if (!LLVMIsConstant(idx)) + return false; + + val = LLVMConstIntGetZExtValue(idx); + + char *sname = NULL, *mname = NULL; + + if (dbginfo_struct_member(dbginfo, ptrtype, val, &sname, &mname)) { + fprintf(stderr, "%s: call to struct %s->%s\n", prefix, sname, + mname); + + json_object_object_add(js_call, "type", + json_object_new_string("struct_memb")); + json_object_object_add(js_call, "struct", + json_object_new_string(sname)); + json_object_object_add(js_call, "member", + json_object_new_string(mname)); + ret = true; + } + free(sname); + free(mname); + + return ret; +} + static bool details_fptr_vars = false; static bool details_fptr_consts = true; @@ -175,6 +243,34 @@ static void walk_const_fptrs(struct json_object *js_call, LLVMValueRef value, prefix, hdr_written); return; + case LLVMConstantExprValueKind: + switch (LLVMGetConstOpcode(value)) { + case LLVMGetElementPtr: + if (try_struct_fptr(js_call, value, prefix)) { + *hdr_written = true; + return; + } + + fprintf(stderr, + "%s: calls function pointer from unhandled const GEP\n", + prefix); + *hdr_written = true; + /* fallthru */ + default: + /* to help the user / development */ + if (!*hdr_written) { + fprintf(stderr, + "%s: calls function pointer from constexpr\n", + prefix); + *hdr_written = true; + } + dump = LLVMPrintValueToString(value); + fprintf(stderr, "%s- [opcode=%d] %s\n", prefix, + LLVMGetConstOpcode(value), dump); + LLVMDisposeMessage(dump); + } + return; + default: /* to help the user / development */ if (!*hdr_written) { @@ -197,7 +293,7 @@ static void walk_const_fptrs(struct json_object *js_call, LLVMValueRef value, #ifdef FRR_SPECIFIC static bool is_thread_sched(const char *name, size_t len) { -#define thread_prefix "funcname_" +#define thread_prefix "_" static const char *const names[] = { thread_prefix "thread_add_read_write", thread_prefix "thread_add_timer", @@ -218,6 +314,225 @@ static bool is_thread_sched(const char *name, size_t len) } #endif +static bool _check_val(bool cond, const char *text, LLVMValueRef dumpval) +{ + if (cond) + return true; + + char *dump = LLVMPrintValueToString(dumpval); + fprintf(stderr, "check failed: %s\ndump:\n\t%s\n", text, dump); + LLVMDisposeMessage(dump); + return false; +} + +#define check_val(cond, dump) \ + if (!_check_val(cond, #cond, dump)) \ + return; + +static char *get_string(LLVMValueRef value) +{ + if (!LLVMIsAConstant(value)) + return strdup("!NOT-A-CONST"); + + if (LLVMGetValueKind(value) == LLVMConstantExprValueKind + && LLVMGetConstOpcode(value) == LLVMGetElementPtr) { + value = LLVMGetOperand(value, 0); + + if (!LLVMIsAConstant(value)) + return strdup("!NOT-A-CONST-2"); + } + + if (LLVMIsAGlobalVariable(value)) + value = LLVMGetInitializer(value); + + size_t len = 0; + const char *sval = LLVMGetAsString(value, &len); + + return strndup(sval, len); +} + +static void handle_yang_module(struct json_object *js_special, + LLVMValueRef yang_mod) +{ + check_val(LLVMIsAGlobalVariable(yang_mod), yang_mod); + + LLVMValueRef value; + + value = LLVMGetInitializer(yang_mod); + LLVMValueKind kind = LLVMGetValueKind(value); + + check_val(kind == LLVMConstantStructValueKind, value); + + size_t var_len = 0; + const char *var_name = LLVMGetValueName2(yang_mod, &var_len); + char buf_name[var_len + 1]; + + memcpy(buf_name, var_name, var_len); + buf_name[var_len] = '\0'; + + struct json_object *js_yang, *js_yangmod, *js_items; + + js_yang = js_get_or_make(js_special, "yang", json_object_new_object); + js_yangmod = js_get_or_make(js_yang, buf_name, json_object_new_object); + js_items = js_get_or_make(js_yangmod, "items", json_object_new_array); + + char *mod_name = get_string(LLVMGetOperand(value, 0)); + json_object_object_add(js_yangmod, "name", + json_object_new_string(mod_name)); + free(mod_name); + + value = LLVMGetOperand(value, 1); + kind = LLVMGetValueKind(value); + check_val(kind == LLVMConstantArrayValueKind, value); + + unsigned len = LLVMGetArrayLength(LLVMTypeOf(value)); + + for (unsigned i = 0; i < len - 1; i++) { + struct json_object *js_item, *js_cbs; + LLVMValueRef item = LLVMGetOperand(value, i); + char *xpath = get_string(LLVMGetOperand(item, 0)); + + js_item = json_object_new_object(); + json_object_array_add(js_items, js_item); + + json_object_object_add(js_item, "xpath", + json_object_new_string(xpath)); + js_cbs = js_get_or_make(js_item, "cbs", json_object_new_object); + + free(xpath); + + LLVMValueRef cbs = LLVMGetOperand(item, 1); + + check_val(LLVMGetValueKind(cbs) == LLVMConstantStructValueKind, + value); + + LLVMTypeRef cbs_type = LLVMTypeOf(cbs); + unsigned cblen = LLVMCountStructElementTypes(cbs_type); + + for (unsigned i = 0; i < cblen; i++) { + LLVMValueRef cb = LLVMGetOperand(cbs, i); + + char *sname = NULL; + char *mname = NULL; + + if (dbginfo_struct_member(dbginfo, cbs_type, i, &sname, + &mname)) { + (void)0; + } + + if (LLVMIsAFunction(cb)) { + size_t fn_len; + const char *fn_name; + + fn_name = LLVMGetValueName2(cb, &fn_len); + + json_object_object_add( + js_cbs, mname, + json_object_new_string_len(fn_name, + fn_len)); + } + + free(sname); + free(mname); + } + } +} + +static void handle_daemoninfo(struct json_object *js_special, + LLVMValueRef daemoninfo) +{ + check_val(LLVMIsAGlobalVariable(daemoninfo), daemoninfo); + + LLVMTypeRef type; + LLVMValueRef value; + unsigned len; + + type = LLVMGlobalGetValueType(daemoninfo); + value = LLVMGetInitializer(daemoninfo); + LLVMValueKind kind = LLVMGetValueKind(value); + + check_val(kind == LLVMConstantStructValueKind, value); + + int yang_idx = -1; + + len = LLVMCountStructElementTypes(type); + + LLVMTypeRef fieldtypes[len]; + LLVMGetSubtypes(type, fieldtypes); + + for (unsigned i = 0; i < len; i++) { + LLVMTypeRef t = fieldtypes[i]; + + if (LLVMGetTypeKind(t) != LLVMPointerTypeKind) + continue; + t = LLVMGetElementType(t); + if (LLVMGetTypeKind(t) != LLVMPointerTypeKind) + continue; + t = LLVMGetElementType(t); + if (LLVMGetTypeKind(t) != LLVMStructTypeKind) + continue; + + const char *name = LLVMGetStructName(t); + if (!strcmp(name, "struct.frr_yang_module_info")) + yang_idx = i; + } + + if (yang_idx == -1) + return; + + LLVMValueRef yang_mods = LLVMGetOperand(value, yang_idx); + LLVMValueRef yang_size = LLVMGetOperand(value, yang_idx + 1); + + check_val(LLVMIsConstant(yang_size), yang_size); + + unsigned long long ival = LLVMConstIntGetZExtValue(yang_size); + + check_val(LLVMGetValueKind(yang_mods) == LLVMConstantExprValueKind + && LLVMGetConstOpcode(yang_mods) == LLVMGetElementPtr, + yang_mods); + + yang_mods = LLVMGetOperand(yang_mods, 0); + + check_val(LLVMIsAGlobalVariable(yang_mods), yang_mods); + + yang_mods = LLVMGetInitializer(yang_mods); + + check_val(LLVMGetValueKind(yang_mods) == LLVMConstantArrayValueKind, + yang_mods); + + len = LLVMGetArrayLength(LLVMTypeOf(yang_mods)); + + if (len != ival) + fprintf(stderr, "length mismatch - %llu vs. %u\n", ival, len); + + for (unsigned i = 0; i < len; i++) { + char *dump; + + LLVMValueRef item = LLVMGetOperand(yang_mods, i); + LLVMValueKind kind = LLVMGetValueKind(item); + + check_val(kind == LLVMGlobalVariableValueKind + || kind == LLVMConstantExprValueKind, + item); + + if (kind == LLVMGlobalVariableValueKind) + continue; + + LLVMOpcode opcode = LLVMGetConstOpcode(item); + switch (opcode) { + case LLVMBitCast: + item = LLVMGetOperand(item, 0); + handle_yang_module(js_special, item); + break; + + default: + dump = LLVMPrintValueToString(item); + printf("[%u] = [opcode=%u] %s\n", i, opcode, dump); + LLVMDisposeMessage(dump); + } + } +} + static void process_call(struct json_object *js_calls, struct json_object *js_special, LLVMValueRef instr, @@ -227,6 +542,9 @@ static void process_call(struct json_object *js_calls, LLVMValueRef called = LLVMGetCalledValue(instr); + if (LLVMIsAInlineAsm(called)) + return; + if (LLVMIsAConstantExpr(called)) { LLVMOpcode opcode = LLVMGetConstOpcode(called); @@ -277,6 +595,12 @@ static void process_call(struct json_object *js_calls, snprintf(prefix, sizeof(prefix), "%.*s:%d:%.*s()", (int)file_len, file, line, (int)name_len, name_c); + if (LLVMIsALoadInst(called) + && LLVMIsAGetElementPtrInst(LLVMGetOperand(called, 0)) + && try_struct_fptr(js_call, LLVMGetOperand(called, 0), + prefix)) + goto out_struct_fptr; + while (LLVMIsALoadInst(last) || LLVMIsAGetElementPtrInst(last)) /* skipping over details for GEP here, but meh. */ last = LLVMGetOperand(last, 0); @@ -324,12 +648,11 @@ static void process_call(struct json_object *js_calls, prefix); } else { char *dump = LLVMPrintValueToString(called); - printf("\t%s\n", dump); + fprintf(stderr, "%s: ??? %s\n", prefix, dump); LLVMDisposeMessage(dump); } - return; #ifdef FRR_SPECIFIC - } else if (!strcmp(called_name, "install_element")) { + } else if (!strcmp(called_name, "_install_element")) { called_type = FN_INSTALL_ELEMENT; LLVMValueRef param0 = LLVMGetOperand(instr, 0); @@ -380,10 +703,7 @@ static void process_call(struct json_object *js_calls, json_object_new_string_len(called_name, called_len)); LLVMValueRef fparam; - if (strstr(called_name, "_read_")) - fparam = LLVMGetOperand(instr, 2); - else - fparam = LLVMGetOperand(instr, 1); + fparam = LLVMGetOperand(instr, 2); assert(fparam); size_t target_len = 0; @@ -434,12 +754,21 @@ static void process_call(struct json_object *js_calls, * - zclient->* ? */ #endif /* FRR_SPECIFIC */ + } else if (!strcmp(called_name, "frr_preinit")) { + LLVMValueRef daemoninfo = LLVMGetOperand(instr, 0); + + handle_daemoninfo(js_special, daemoninfo); + + json_object_object_add( + js_call, "target", + json_object_new_string_len(called_name, called_len)); } else { json_object_object_add( js_call, "target", json_object_new_string_len(called_name, called_len)); } +out_struct_fptr: for (unsigned argno = 0; argno < n_args; argno++) { LLVMValueRef param = LLVMGetOperand(instr, argno); size_t target_len; @@ -597,6 +926,8 @@ int main(int argc, char **argv) // done with the memory buffer now, so dispose of it LLVMDisposeMemoryBuffer(memoryBuffer); + dbginfo = dbginfo_load(module); + struct json_object *js_root, *js_funcs, *js_special; js_root = json_object_new_object(); diff --git a/tools/frr-llvm-debuginfo.cpp b/tools/frr-llvm-debuginfo.cpp new file mode 100644 index 0000000000..ed3ad956b8 --- /dev/null +++ b/tools/frr-llvm-debuginfo.cpp @@ -0,0 +1,112 @@ +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to <http://unlicense.org/> + +#include <llvm-c/BitReader.h> +#include <llvm-c/BitWriter.h> +#include <llvm-c/Core.h> +#include <llvm-c/DebugInfo.h> + +#include <llvm/IR/Module.h> +#include <llvm/IR/Value.h> +#include <llvm/IR/Type.h> +#include <llvm/IR/DebugInfo.h> +#include <llvm/IR/DebugInfoMetadata.h> +#include <llvm/Support/raw_ostream.h> + +#include <map> + +#include "frr-llvm-debuginfo.h" + +/* llvm::DebugInfoFinder is unfortunately not exposed in the llvm-c API... */ + +struct dbginfo { + llvm::DebugInfoFinder finder; + std::map<std::string, llvm::DICompositeType *> tab; +}; + +struct dbginfo *dbginfo_load(LLVMModuleRef _mod) +{ + llvm::Module *mod = llvm::unwrap(_mod); + struct dbginfo *info = new dbginfo(); + + info->finder.processModule(*mod); + + for (auto ty : info->finder.types()) { + if (ty->getMetadataID() != llvm::Metadata::DICompositeTypeKind) + continue; + + llvm::DICompositeType *cty = (llvm::DICompositeType *)ty; + /* empty forward declarations aka "struct foobar;" */ + if (cty->getElements().size() == 0) + continue; + + info->tab.emplace(std::move(ty->getName().str()), cty); + } + + return info; +} + +bool dbginfo_struct_member(struct dbginfo *info, LLVMTypeRef _typ, + unsigned long long idx, char **struct_name, + char **member_name) +{ + *struct_name = NULL; + *member_name = NULL; + + llvm::Type *typ = llvm::unwrap(_typ); + + if (!typ->isStructTy()) + return false; + + llvm::StructType *styp = (llvm::StructType *)typ; + auto sname = styp->getStructName(); + + if (!sname.startswith("struct.")) + return false; + sname = sname.drop_front(7); + + size_t dot = sname.find_last_of("."); + if (dot != sname.npos) + sname = sname.take_front(dot); + + auto item = info->tab.find(sname.str()); + if (item == info->tab.end()) + return false; + + auto elements = item->second->getElements(); + if (idx >= elements.size()) + return false; + + auto elem = elements[idx]; + + if (elem->getMetadataID() != llvm::Metadata::DIDerivedTypeKind) + return false; + + llvm::DIDerivedType *dtyp = (llvm::DIDerivedType *)elem; + + *struct_name = strdup(sname.str().c_str()); + *member_name = strdup(dtyp->getName().str().c_str()); + return true; +} diff --git a/tools/frr-llvm-debuginfo.h b/tools/frr-llvm-debuginfo.h new file mode 100644 index 0000000000..fca4bc1f97 --- /dev/null +++ b/tools/frr-llvm-debuginfo.h @@ -0,0 +1,47 @@ +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to <http://unlicense.org/> + +#ifndef _FRR_LLVM_DEBUGINFO_H +#define _FRR_LLVM_DEBUGINFO_H + +#include <stdbool.h> +#include <llvm-c/Core.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct dbginfo; + +extern struct dbginfo *dbginfo_load(LLVMModuleRef mod); +extern bool dbginfo_struct_member(struct dbginfo *di, LLVMTypeRef typ, + unsigned long long idx, char **struct_name, + char **member_name); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_LLVM_DEBUGINFO_H */ diff --git a/tools/subdir.am b/tools/subdir.am index 6a03a23baa..e4b9ecd84f 100644 --- a/tools/subdir.am +++ b/tools/subdir.am @@ -40,9 +40,15 @@ tools_ssd_CPPFLAGS = llvm_version = $(shell echo __clang_major__ | $(CC) -xc -P -E -) tools_frr_llvm_cg_CPPFLAGS = $(CPPFLAGS_BASE) tools_frr_llvm_cg_CFLAGS = $(AM_CFLAGS) `llvm-config-$(llvm_version) --cflags` +tools_frr_llvm_cg_CXXFLAGS = $(AM_CXXFLAGS) -O0 -ggdb3 `llvm-config-$(llvm_version) --cxxflags` tools_frr_llvm_cg_LDFLAGS = `llvm-config-$(llvm_version) --ldflags --libs` tools_frr_llvm_cg_SOURCES = \ tools/frr-llvm-cg.c \ + tools/frr-llvm-debuginfo.cpp \ + # end + +noinst_HEADERS += \ + tools/frr-llvm-debuginfo.h \ # end EXTRA_DIST += \ diff --git a/zebra/zebra_vty.c b/zebra/zebra_vty.c index 8567edbf96..2c2c75c419 100644 --- a/zebra/zebra_vty.c +++ b/zebra/zebra_vty.c @@ -88,6 +88,9 @@ static void show_nexthop_detail_helper(struct vty *vty, const struct nexthop *nexthop, bool is_backup); +static void show_ip_route_dump_vty(struct vty *vty, struct route_table *table); +static void show_ip_route_nht_dump(struct vty *vty, struct nexthop *nexthop, + struct route_entry *re, unsigned int num); DEFUN (ip_multicast_mode, ip_multicast_mode_cmd, @@ -2084,6 +2087,207 @@ DEFPY (show_route_summary, return CMD_SUCCESS; } +DEFUN_HIDDEN (show_route_zebra_dump, + show_route_zebra_dump_cmd, + "show <ip|ipv6> zebra route dump [vrf VRFNAME]", + SHOW_STR + IP_STR + IP6_STR + "Zebra daemon\n" + "Routing table\n" + "All information\n" + VRF_CMD_HELP_STR) +{ + afi_t afi = AFI_IP; + struct route_table *table; + const char *vrf_name = NULL; + int idx = 0; + + afi = strmatch(argv[1]->text, "ipv6") ? AFI_IP6 : AFI_IP; + + if (argv_find(argv, argc, "vrf", &idx)) + vrf_name = argv[++idx]->arg; + + if (!vrf_name) { + struct vrf *vrf; + struct zebra_vrf *zvrf; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + zvrf = vrf->info; + if ((zvrf == NULL) + || (zvrf->table[afi][SAFI_UNICAST] == NULL)) + continue; + + table = zvrf->table[afi][SAFI_UNICAST]; + show_ip_route_dump_vty(vty, table); + } + } else { + vrf_id_t vrf_id = VRF_DEFAULT; + + VRF_GET_ID(vrf_id, vrf_name, true); + + table = zebra_vrf_table(afi, SAFI_UNICAST, vrf_id); + if (!table) + return CMD_SUCCESS; + + show_ip_route_dump_vty(vty, table); + } + + return CMD_SUCCESS; +} + +static void show_ip_route_nht_dump(struct vty *vty, struct nexthop *nexthop, + struct route_entry *re, unsigned int num) +{ + + char buf[SRCDEST2STR_BUFFER]; + + vty_out(vty, " Nexthop %u:\n", num); + vty_out(vty, " type: %u\n", nexthop->type); + vty_out(vty, " flags: %u\n", nexthop->flags); + switch (nexthop->type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + vty_out(vty, " ip address: %s\n", + inet_ntop(AF_INET, &nexthop->gate.ipv4, buf, + sizeof(buf))); + vty_out(vty, " afi: ipv4\n"); + + if (nexthop->ifindex) { + vty_out(vty, " interface index: %d\n", + nexthop->ifindex); + vty_out(vty, " interface name: %s\n", + ifindex2ifname(nexthop->ifindex, + nexthop->vrf_id)); + } + + if (nexthop->src.ipv4.s_addr + && (inet_ntop(AF_INET, &nexthop->src.ipv4, buf, + sizeof(buf)))) + vty_out(vty, " source: %s\n", buf); + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + vty_out(vty, " ip: %s\n", + inet_ntop(AF_INET6, &nexthop->gate.ipv6, buf, + sizeof(buf))); + vty_out(vty, " afi: ipv6\n"); + + if (nexthop->ifindex) { + vty_out(vty, " interface index: %d\n", + nexthop->ifindex); + vty_out(vty, " interface name: %s\n", + ifindex2ifname(nexthop->ifindex, + nexthop->vrf_id)); + } + + if (!IPV6_ADDR_SAME(&nexthop->src.ipv6, &in6addr_any)) { + if (inet_ntop(AF_INET6, &nexthop->src.ipv6, buf, + sizeof(buf))) + vty_out(vty, " source: %s\n", buf); + } + break; + case NEXTHOP_TYPE_IFINDEX: + vty_out(vty, + " Nexthop is an interface (directly connected).\n"); + vty_out(vty, " interface index: %d\n", nexthop->ifindex); + vty_out(vty, " interface name: %s\n", + ifindex2ifname(nexthop->ifindex, nexthop->vrf_id)); + break; + case NEXTHOP_TYPE_BLACKHOLE: + vty_out(vty, " Nexthop type is blackhole.\n"); + + switch (nexthop->bh_type) { + case BLACKHOLE_REJECT: + vty_out(vty, " Blackhole type: reject\n"); + break; + case BLACKHOLE_ADMINPROHIB: + vty_out(vty, + " Blackhole type: admin-prohibited\n"); + break; + case BLACKHOLE_NULL: + vty_out(vty, " Blackhole type: NULL0\n"); + break; + case BLACKHOLE_UNSPEC: + break; + } + break; + default: + break; + } +} + +static void show_ip_route_dump_vty(struct vty *vty, struct route_table *table) +{ + struct route_node *rn; + struct route_entry *re; + char buf[SRCDEST2STR_BUFFER]; + char time[20]; + time_t uptime; + struct tm tm; + struct timeval tv; + struct nexthop *nexthop = NULL; + int nexthop_num = 0; + + vty_out(vty, "\nIPv4/IPv6 Routing table dump\n"); + vty_out(vty, "----------------------------\n"); + + for (rn = route_top(table); rn; rn = route_next(rn)) { + RNODE_FOREACH_RE (rn, re) { + vty_out(vty, "Route: %s\n", + srcdest_rnode2str(rn, buf, sizeof(buf))); + vty_out(vty, " protocol: %s\n", + zebra_route_string(re->type)); + vty_out(vty, " instance: %u\n", re->instance); + vty_out(vty, " VRF ID: %u\n", re->vrf_id); + vty_out(vty, " VRF name: %s\n", + vrf_id_to_name(re->vrf_id)); + vty_out(vty, " flags: %u\n", re->flags); + + if (re->type != ZEBRA_ROUTE_CONNECT) { + vty_out(vty, " distance: %u\n", re->distance); + vty_out(vty, " metric: %u\n", re->metric); + } + + vty_out(vty, " tag: %u\n", re->tag); + + uptime = monotime(&tv); + uptime -= re->uptime; + gmtime_r(&uptime, &tm); + + if (uptime < ONE_DAY_SECOND) + snprintf(time, sizeof(time), "%02d:%02d:%02d", + tm.tm_hour, tm.tm_min, tm.tm_sec); + else if (uptime < ONE_WEEK_SECOND) + snprintf(time, sizeof(time), "%dd%02dh%02dm", + tm.tm_yday, tm.tm_hour, tm.tm_min); + else + snprintf(time, sizeof(time), "%02dw%dd%02dh", + tm.tm_yday / 7, + tm.tm_yday - ((tm.tm_yday / 7) * 7), + tm.tm_hour); + + vty_out(vty, " status: %u\n", re->status); + vty_out(vty, " nexthop_num: %u\n", + nexthop_group_nexthop_num(&(re->nhe->nhg))); + vty_out(vty, " nexthop_active_num: %u\n", + nexthop_group_active_nexthop_num( + &(re->nhe->nhg))); + vty_out(vty, " table: %u\n", re->table); + vty_out(vty, " uptime: %s\n", time); + + for (ALL_NEXTHOPS_PTR(&(re->nhe->nhg), nexthop)) { + nexthop_num++; + show_ip_route_nht_dump(vty, nexthop, re, + nexthop_num); + } + + nexthop_num = 0; + vty_out(vty, "\n"); + } + } +} + static void vty_show_ip_route_summary(struct vty *vty, struct route_table *table, bool use_json) { @@ -4191,6 +4395,7 @@ void zebra_vty_init(void) install_element(VIEW_NODE, &show_pbr_ipset_cmd); install_element(VIEW_NODE, &show_pbr_iptable_cmd); + install_element(VIEW_NODE, &show_route_zebra_dump_cmd); install_element(CONFIG_NODE, &evpn_mh_mac_holdtime_cmd); install_element(CONFIG_NODE, &evpn_mh_neigh_holdtime_cmd); |
