diff options
63 files changed, 1579 insertions, 232 deletions
diff --git a/bgpd/bgp_bmp.c b/bgpd/bgp_bmp.c index 1de48a072a..5fcb8c5645 100644 --- a/bgpd/bgp_bmp.c +++ b/bgpd/bgp_bmp.c @@ -910,7 +910,8 @@ static void bmp_eor(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t flags, static struct stream *bmp_update(const struct prefix *p, struct prefix_rd *prd, struct peer *peer, struct attr *attr, - afi_t afi, safi_t safi) + afi_t afi, safi_t safi, mpls_label_t *label, + uint32_t num_labels) { struct bpacket_attr_vec_arr vecarr; struct stream *s; @@ -946,8 +947,8 @@ static struct stream *bmp_update(const struct prefix *p, struct prefix_rd *prd, mpattrlen_pos = bgp_packet_mpattr_start(s, peer, afi, safi, &vecarr, attr); - bgp_packet_mpattr_prefix(s, afi, safi, p, prd, NULL, 0, 0, 0, - attr); + bgp_packet_mpattr_prefix(s, afi, safi, p, prd, label, + num_labels, 0, 0, attr); bgp_packet_mpattr_end(s, mpattrlen_pos); total_attr_len += stream_get_endp(s) - p1; } @@ -1002,7 +1003,8 @@ static struct stream *bmp_withdraw(const struct prefix *p, static void bmp_monitor(struct bmp *bmp, struct peer *peer, uint8_t flags, uint8_t peer_type_flag, const struct prefix *p, struct prefix_rd *prd, struct attr *attr, afi_t afi, - safi_t safi, time_t uptime) + safi_t safi, time_t uptime, mpls_label_t *label, + uint32_t num_labels) { struct stream *hdr, *msg; struct timeval tv = { .tv_sec = uptime, .tv_usec = 0 }; @@ -1019,7 +1021,8 @@ static void bmp_monitor(struct bmp *bmp, struct peer *peer, uint8_t flags, monotime_to_realtime(&tv, &uptime_real); if (attr) - msg = bmp_update(p, prd, peer, attr, afi, safi); + msg = bmp_update(p, prd, peer, attr, afi, safi, label, + num_labels); else msg = bmp_withdraw(p, prd, afi, safi); @@ -1219,18 +1222,24 @@ afibreak: bmp_monitor(bmp, bpi->peer, 0, BMP_PEER_TYPE_LOC_RIB_INSTANCE, bn_p, prd, bpi->attr, afi, safi, bpi && bpi->extra ? bpi->extra->bgp_rib_uptime - : (time_t)(-1L)); + : (time_t)(-1L), + bpi->extra ? bpi->extra->label : NULL, + bpi->extra ? bpi->extra->num_labels : 0); } if (bpi && CHECK_FLAG(bpi->flags, BGP_PATH_VALID) && CHECK_FLAG(bmp->targets->afimon[afi][safi], BMP_MON_POSTPOLICY)) bmp_monitor(bmp, bpi->peer, BMP_PEER_FLAG_L, BMP_PEER_TYPE_GLOBAL_INSTANCE, bn_p, prd, bpi->attr, - afi, safi, bpi->uptime); + afi, safi, bpi->uptime, + bpi->extra ? bpi->extra->label : NULL, + bpi->extra ? bpi->extra->num_labels : 0); if (adjin) + /* TODO: set label here when adjin supports labels */ bmp_monitor(bmp, adjin->peer, 0, BMP_PEER_TYPE_GLOBAL_INSTANCE, - bn_p, prd, adjin->attr, afi, safi, adjin->uptime); + bn_p, prd, adjin->attr, afi, safi, adjin->uptime, + NULL, 0); if (bn) bgp_dest_unlock_node(bn); @@ -1343,7 +1352,9 @@ static bool bmp_wrqueue_locrib(struct bmp *bmp, struct pullwr *pullwr) bmp_monitor(bmp, peer, 0, BMP_PEER_TYPE_LOC_RIB_INSTANCE, &bqe->p, prd, bpi ? bpi->attr : NULL, afi, safi, bpi && bpi->extra ? bpi->extra->bgp_rib_uptime - : (time_t)(-1L)); + : (time_t)(-1L), + (bpi && bpi->extra) ? bpi->extra->label : NULL, + (bpi && bpi->extra) ? bpi->extra->num_labels : 0); written = true; out: @@ -1416,7 +1427,9 @@ static bool bmp_wrqueue(struct bmp *bmp, struct pullwr *pullwr) bmp_monitor(bmp, peer, BMP_PEER_FLAG_L, BMP_PEER_TYPE_GLOBAL_INSTANCE, &bqe->p, prd, bpi ? bpi->attr : NULL, afi, safi, - bpi ? bpi->uptime : monotime(NULL)); + bpi ? bpi->uptime : monotime(NULL), + (bpi && bpi->extra) ? bpi->extra->label : NULL, + (bpi && bpi->extra) ? bpi->extra->num_labels : 0); written = true; } @@ -1428,9 +1441,10 @@ static bool bmp_wrqueue(struct bmp *bmp, struct pullwr *pullwr) if (adjin->peer == peer) break; } + /* TODO: set label here when adjin supports labels */ bmp_monitor(bmp, peer, 0, BMP_PEER_TYPE_GLOBAL_INSTANCE, &bqe->p, prd, adjin ? adjin->attr : NULL, afi, safi, - adjin ? adjin->uptime : monotime(NULL)); + adjin ? adjin->uptime : monotime(NULL), NULL, 0); written = true; } diff --git a/bgpd/bgp_debug.c b/bgpd/bgp_debug.c index 3ecdc0d3cf..c1b06a0ae3 100644 --- a/bgpd/bgp_debug.c +++ b/bgpd/bgp_debug.c @@ -116,6 +116,7 @@ static const struct message bgp_notify_msg[] = { {BGP_NOTIFY_FSM_ERR, "Neighbor Events Error"}, {BGP_NOTIFY_CEASE, "Cease"}, {BGP_NOTIFY_ROUTE_REFRESH_ERR, "ROUTE-REFRESH Message Error"}, + {BGP_NOTIFY_SEND_HOLD_ERR, "Send Hold Timer Expired"}, {0}}; static const struct message bgp_notify_head_msg[] = { @@ -515,6 +516,7 @@ const char *bgp_notify_subcode_str(char code, char subcode) return lookup_msg(bgp_notify_update_msg, subcode, "Unrecognized Error Subcode"); case BGP_NOTIFY_HOLD_ERR: + case BGP_NOTIFY_SEND_HOLD_ERR: break; case BGP_NOTIFY_FSM_ERR: return lookup_msg(bgp_notify_fsm_msg, subcode, diff --git a/bgpd/bgp_evpn.c b/bgpd/bgp_evpn.c index 1a4364bb74..a846484f0e 100644 --- a/bgpd/bgp_evpn.c +++ b/bgpd/bgp_evpn.c @@ -315,18 +315,13 @@ static int is_vni_present_in_irt_vnis(struct list *vnis, struct bgpevpn *vpn) * This would be following category: * Non-imported route, * Non-EVPN imported route, - * Non Aggregate suppressed route. */ -bool is_route_injectable_into_evpn(struct bgp_path_info *pi) +bool is_route_injectable_into_evpn_non_supp(struct bgp_path_info *pi) { struct bgp_path_info *parent_pi; struct bgp_table *table; struct bgp_dest *dest; - /* do not import aggr suppressed routes */ - if (bgp_path_suppressed(pi)) - return false; - if (pi->sub_type != BGP_ROUTE_IMPORTED || !pi->extra || !pi->extra->vrfleak || !pi->extra->vrfleak->parent) return true; @@ -344,6 +339,21 @@ bool is_route_injectable_into_evpn(struct bgp_path_info *pi) return true; } +/* Flag if the route is injectable into EVPN. + * This would be following category: + * Non-imported route, + * Non-EVPN imported route, + * Non Aggregate suppressed route. + */ +bool is_route_injectable_into_evpn(struct bgp_path_info *pi) +{ + /* do not import aggr suppressed routes */ + if (bgp_path_suppressed(pi)) + return false; + + return is_route_injectable_into_evpn_non_supp(pi); +} + /* * Compare Route Targets. */ @@ -7711,3 +7721,64 @@ bool bgp_evpn_mpath_has_dvni(const struct bgp *bgp_vrf, return false; } + +/* Upon aggregate set trigger unimport suppressed routes + * from EVPN + */ +void bgp_aggr_supp_withdraw_from_evpn(struct bgp *bgp, afi_t afi, safi_t safi) +{ + struct bgp_dest *agg_dest, *dest, *top; + const struct prefix *aggr_p; + struct bgp_aggregate *bgp_aggregate; + struct bgp_table *table; + struct bgp_path_info *pi; + + if (!bgp_get_evpn() && !advertise_type5_routes(bgp, afi)) + return; + + /* Aggregate-address table walk. */ + table = bgp->rib[afi][safi]; + for (agg_dest = bgp_table_top(bgp->aggregate[afi][safi]); agg_dest; + agg_dest = bgp_route_next(agg_dest)) { + bgp_aggregate = bgp_dest_get_bgp_aggregate_info(agg_dest); + + if (bgp_aggregate == NULL) + continue; + + aggr_p = bgp_dest_get_prefix(agg_dest); + + /* Look all nodes below the aggregate prefix in + * global AFI/SAFI table (IPv4/IPv6). + * Trigger withdrawal (this will be Type-5 routes only) + * from EVPN Global table. + */ + top = bgp_node_get(table, aggr_p); + for (dest = bgp_node_get(table, aggr_p); dest; + dest = bgp_route_next_until(dest, top)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (dest_p->prefixlen <= aggr_p->prefixlen) + continue; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) { + if (pi->sub_type == BGP_ROUTE_AGGREGATE) + continue; + + /* Only Suppressed route remove from EVPN */ + if (!bgp_path_suppressed(pi)) + continue; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s aggregated %pFX remove suppressed route %pFX", + __func__, aggr_p, dest_p); + + if (!is_route_injectable_into_evpn_non_supp(pi)) + continue; + + bgp_evpn_withdraw_type5_route(bgp, dest_p, afi, + safi); + } + } + } +} diff --git a/bgpd/bgp_evpn.h b/bgpd/bgp_evpn.h index 8403897587..c641a64f62 100644 --- a/bgpd/bgp_evpn.h +++ b/bgpd/bgp_evpn.h @@ -182,5 +182,8 @@ extern vni_t bgp_evpn_path_info_get_l3vni(const struct bgp_path_info *pi); extern bool bgp_evpn_mpath_has_dvni(const struct bgp *bgp_vrf, struct bgp_path_info *mpinfo); extern bool is_route_injectable_into_evpn(struct bgp_path_info *pi); +extern bool is_route_injectable_into_evpn_non_supp(struct bgp_path_info *pi); +extern void bgp_aggr_supp_withdraw_from_evpn(struct bgp *bgp, afi_t afi, + safi_t safi); #endif /* _QUAGGA_BGP_EVPN_H */ diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c index 657c7e22d7..a2d3172882 100644 --- a/bgpd/bgp_fsm.c +++ b/bgpd/bgp_fsm.c @@ -1589,7 +1589,7 @@ bgp_stop_with_error(struct peer_connection *connection) /* something went wrong, send notify and tear down */ -static enum bgp_fsm_state_progress +enum bgp_fsm_state_progress bgp_stop_with_notify(struct peer_connection *connection, uint8_t code, uint8_t sub_code) { diff --git a/bgpd/bgp_fsm.h b/bgpd/bgp_fsm.h index 2e96ac4c10..bcdd49193f 100644 --- a/bgpd/bgp_fsm.h +++ b/bgpd/bgp_fsm.h @@ -122,6 +122,9 @@ extern void bgp_maxmed_update(struct bgp *); extern bool bgp_maxmed_onstartup_configured(struct bgp *); extern bool bgp_maxmed_onstartup_active(struct bgp *); extern int bgp_fsm_error_subcode(int status); +extern enum bgp_fsm_state_progress +bgp_stop_with_notify(struct peer_connection *connection, uint8_t code, + uint8_t sub_code); /** * Start the route advertisement timer (that honors MRAI) for all the diff --git a/bgpd/bgp_nht.c b/bgpd/bgp_nht.c index 884fabf077..786d07c5a9 100644 --- a/bgpd/bgp_nht.c +++ b/bgpd/bgp_nht.c @@ -1057,7 +1057,7 @@ static int make_prefix(int afi, struct bgp_path_info *pi, struct prefix *p) break; case AFI_IP6: p->family = AF_INET6; - if (pi->attr && pi->attr->srv6_l3vpn) { + if (pi->attr->srv6_l3vpn) { IPV6_ADDR_COPY(&(p->u.prefix6), &(pi->attr->srv6_l3vpn->sid)); p->prefixlen = IPV6_MAX_BITLEN; @@ -1068,18 +1068,19 @@ static int make_prefix(int afi, struct bgp_path_info *pi, struct prefix *p) /* If we receive MP_REACH nexthop with ::(LL) * or LL(LL), use LL address as nexthop cache. */ - if (pi->attr->mp_nexthop_len - == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL - && (IN6_IS_ADDR_UNSPECIFIED( - &pi->attr->mp_nexthop_global) - || IN6_IS_ADDR_LINKLOCAL( - &pi->attr->mp_nexthop_global))) + if (pi->attr && + pi->attr->mp_nexthop_len == + BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL && + (IN6_IS_ADDR_UNSPECIFIED( + &pi->attr->mp_nexthop_global) || + IN6_IS_ADDR_LINKLOCAL(&pi->attr->mp_nexthop_global))) p->u.prefix6 = pi->attr->mp_nexthop_local; /* If we receive MR_REACH with (GA)::(LL) * then check for route-map to choose GA or LL */ - else if (pi->attr->mp_nexthop_len - == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { + else if (pi->attr && + pi->attr->mp_nexthop_len == + BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { if (CHECK_FLAG(pi->attr->nh_flags, BGP_ATTR_NH_MP_PREFER_GLOBAL)) p->u.prefix6 = diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c index da352a8441..55d3efde23 100644 --- a/bgpd/bgp_packet.c +++ b/bgpd/bgp_packet.c @@ -148,7 +148,8 @@ static void bgp_packet_add(struct peer_connection *connection, EC_BGP_SENDQ_STUCK_PROPER, "%pBP has not made any SendQ progress for 2 holdtimes (%jds), terminating session", peer, sendholdtime); - BGP_EVENT_ADD(connection, TCP_fatal_error); + bgp_stop_with_notify(connection, + BGP_NOTIFY_SEND_HOLD_ERR, 0); } else if (delta > (intmax_t)holdtime && monotime(NULL) - peer->last_sendq_warn > 5) { flog_warn( diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index c61ffbd558..520185e60f 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -572,6 +572,11 @@ void bgp_path_info_path_with_addpath_rx_str(struct bgp_path_info *pi, char *buf, { struct peer *peer; + if (!pi) { + snprintf(buf, buf_len, "NONE"); + return; + } + if (pi->sub_type == BGP_ROUTE_IMPORTED && bgp_get_imported_bpi_ultimate(pi)) peer = bgp_get_imported_bpi_ultimate(pi)->peer; @@ -2777,41 +2782,35 @@ void bgp_best_selection(struct bgp *bgp, struct bgp_dest *dest, } new_select = pi1; - if (pi1->next) { - for (pi2 = pi1->next; pi2; pi2 = pi2->next) { - if (CHECK_FLAG(pi2->flags, - BGP_PATH_DMED_CHECK)) - continue; - if (BGP_PATH_HOLDDOWN(pi2)) - continue; - if (pi2->peer != bgp->peer_self && - !CHECK_FLAG(pi2->peer->sflags, - PEER_STATUS_NSF_WAIT) && - !peer_established( - pi2->peer->connection)) - continue; - - if (!aspath_cmp_left(pi1->attr->aspath, - pi2->attr->aspath) - && !aspath_cmp_left_confed( - pi1->attr->aspath, - pi2->attr->aspath)) - continue; + for (pi2 = pi1->next; pi2; pi2 = pi2->next) { + if (CHECK_FLAG(pi2->flags, BGP_PATH_DMED_CHECK)) + continue; + if (BGP_PATH_HOLDDOWN(pi2)) + continue; + if (pi2->peer != bgp->peer_self && + !CHECK_FLAG(pi2->peer->sflags, + PEER_STATUS_NSF_WAIT) && + !peer_established(pi2->peer->connection)) + continue; - if (bgp_path_info_cmp( - bgp, pi2, new_select, - &paths_eq, mpath_cfg, debug, - pfx_buf, afi, safi, - &dest->reason)) { - bgp_path_info_unset_flag( - dest, new_select, - BGP_PATH_DMED_SELECTED); - new_select = pi2; - } + if (!aspath_cmp_left(pi1->attr->aspath, + pi2->attr->aspath) && + !aspath_cmp_left_confed(pi1->attr->aspath, + pi2->attr->aspath)) + continue; - bgp_path_info_set_flag( - dest, pi2, BGP_PATH_DMED_CHECK); + if (bgp_path_info_cmp(bgp, pi2, new_select, + &paths_eq, mpath_cfg, + debug, pfx_buf, afi, safi, + &dest->reason)) { + bgp_path_info_unset_flag(dest, + new_select, + BGP_PATH_DMED_SELECTED); + new_select = pi2; } + + bgp_path_info_set_flag(dest, pi2, + BGP_PATH_DMED_CHECK); } bgp_path_info_set_flag(dest, new_select, BGP_PATH_DMED_CHECK); @@ -2872,9 +2871,10 @@ void bgp_best_selection(struct bgp *bgp, struct bgp_dest *dest, continue; } - if (CHECK_FLAG(bgp->flags, BGP_FLAG_DETERMINISTIC_MED) - && (!CHECK_FLAG(pi->flags, BGP_PATH_DMED_SELECTED))) { - bgp_path_info_unset_flag(dest, pi, BGP_PATH_DMED_CHECK); + bgp_path_info_unset_flag(dest, pi, BGP_PATH_DMED_CHECK); + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DETERMINISTIC_MED) && + (!CHECK_FLAG(pi->flags, BGP_PATH_DMED_SELECTED))) { if (debug) zlog_debug("%s: %pBD(%s) pi %s dmed", __func__, dest, bgp->name_pretty, @@ -2882,8 +2882,6 @@ void bgp_best_selection(struct bgp *bgp, struct bgp_dest *dest, continue; } - bgp_path_info_unset_flag(dest, pi, BGP_PATH_DMED_CHECK); - reason = dest->reason; if (bgp_path_info_cmp(bgp, pi, new_select, &paths_eq, mpath_cfg, debug, pfx_buf, afi, safi, @@ -2900,11 +2898,8 @@ void bgp_best_selection(struct bgp *bgp, struct bgp_dest *dest, * qualify as multipaths */ if (debug) { - if (new_select) - bgp_path_info_path_with_addpath_rx_str( - new_select, path_buf, sizeof(path_buf)); - else - snprintf(path_buf, sizeof(path_buf), "NONE"); + bgp_path_info_path_with_addpath_rx_str(new_select, path_buf, + sizeof(path_buf)); zlog_debug( "%pBD(%s): After path selection, newbest is %s oldbest was %s", dest, bgp->name_pretty, path_buf, @@ -2912,9 +2907,7 @@ void bgp_best_selection(struct bgp *bgp, struct bgp_dest *dest, } if (do_mpath && new_select) { - for (pi = bgp_dest_get_bgp_path_info(dest); - (pi != NULL) && (nextpi = pi->next, 1); pi = nextpi) { - + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { if (debug) bgp_path_info_path_with_addpath_rx_str( pi, path_buf, sizeof(path_buf)); @@ -7806,6 +7799,9 @@ bool bgp_aggregate_route(struct bgp *bgp, const struct prefix *p, afi_t afi, lcommunity = lcommunity_dup(aggregate->lcommunity); } + /* Unimport suppressed routes from EVPN */ + bgp_aggr_supp_withdraw_from_evpn(bgp, afi, safi); + bgp_aggregate_install(bgp, afi, safi, p, origin, aspath, community, ecommunity, lcommunity, atomic_aggregate, aggregate); diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index cf333e07cc..94bb107253 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -1983,6 +1983,7 @@ struct bgp_nlri { #define BGP_NOTIFY_FSM_ERR 5 #define BGP_NOTIFY_CEASE 6 #define BGP_NOTIFY_ROUTE_REFRESH_ERR 7 +#define BGP_NOTIFY_SEND_HOLD_ERR 8 /* Subcodes for BGP Finite State Machine Error */ #define BGP_NOTIFY_FSM_ERR_SUBCODE_UNSPECIFIC 0 diff --git a/configure.ac b/configure.ac index c73131751f..ba11e8996e 100644 --- a/configure.ac +++ b/configure.ac @@ -703,6 +703,8 @@ AC_ARG_ENABLE([mgmtd_local_validations], AS_HELP_STRING([--enable-mgmtd-local-validations], [dev: unimplemented local validation])) AC_ARG_ENABLE([mgmtd_test_be_client], AS_HELP_STRING([--enable-mgmtd-test-be-client], [build test backend client])) +AC_ARG_ENABLE([fpm_listener], + AS_HELP_STRING([--enable-fpm-listener], [build fpm listener test program])) AC_ARG_ENABLE([ripd], AS_HELP_STRING([--disable-ripd], [do not build ripd])) AC_ARG_ENABLE([ripngd], @@ -1811,6 +1813,10 @@ AS_IF([test "$enable_mgmtd_test_be_client" = "yes"], [ AC_DEFINE([HAVE_MGMTD_TESTC], [1], [mgmtd_testc]) ]) +AS_IF([test "$enable_fpm_listener" = "yes"], [ + AC_DEFINE([HAVE_FPM_LISTENER], [1], [fpm_listener]) +]) + AS_IF([test "$enable_ripd" != "no"], [ AC_DEFINE([HAVE_RIPD], [1], [ripd]) ]) @@ -2773,6 +2779,7 @@ AM_CONDITIONAL([ZEBRA], [test "$enable_zebra" != "no"]) AM_CONDITIONAL([BGPD], [test "$enable_bgpd" != "no"]) AM_CONDITIONAL([MGMTD], [test "$enable_mgmtd" != "no"]) AM_CONDITIONAL([MGMTD_TESTC], [test "$enable_mgmtd_test_be_client" = "yes"]) +AM_CONDITIONAL([FPM_LISTENER], [test "enable_fpm_listener" = "yes"]) AM_CONDITIONAL([RIPD], [test "$enable_ripd" != "no"]) AM_CONDITIONAL([OSPFD], [test "$enable_ospfd" != "no"]) AM_CONDITIONAL([LDPD], [test "$enable_ldpd" != "no"]) diff --git a/debian/control b/debian/control index b3c14f06f3..fb8c2162da 100644 --- a/debian/control +++ b/debian/control @@ -102,6 +102,14 @@ Description: FRRouting suite - BGP RPKI support (rtrlib) number. Build-Profiles: <!pkg.frr.nortrlib> +Package: frr-test-tools +Architecture: linux-any +Depends: frr (= ${binary:Version}), + ${misc:Depends}, + ${shlibs:Depends} +Description: FRRouting suite - Testing Tools + Adds FRR test tools, used in testing FRR. + Package: frr-doc Section: doc Architecture: all diff --git a/debian/frr-test-tools.install b/debian/frr-test-tools.install new file mode 100644 index 0000000000..a8ad18f2c6 --- /dev/null +++ b/debian/frr-test-tools.install @@ -0,0 +1 @@ +usr/lib/frr/fpm_listener diff --git a/doc/developer/checkpatch.rst b/doc/developer/checkpatch.rst index d8fe007c31..4ef261ba22 100644 --- a/doc/developer/checkpatch.rst +++ b/doc/developer/checkpatch.rst @@ -761,15 +761,6 @@ Indentation and Line Breaks Macros, Attributes and Symbols ------------------------------ - **ARRAY_SIZE** - The ARRAY_SIZE(foo) macro should be preferred over - sizeof(foo)/sizeof(foo[0]) for finding number of elements in an - array. - - The macro is defined in include/linux/kernel.h:: - - #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) - **AVOID_EXTERNS** Function prototypes don't need to be declared extern in .h files. It's assumed by the compiler and is unnecessary. diff --git a/doc/user/basic.rst b/doc/user/basic.rst index 7f9027679f..5fdd1887fa 100644 --- a/doc/user/basic.rst +++ b/doc/user/basic.rst @@ -206,7 +206,7 @@ Basic Config Commands enabled log destinations. The note that logging includes full command lines, including passwords. If the daemon startup option `--command-log-always` is used to start the daemon then this command is turned on by default - and cannot be turned off and the [no] form of the command is dissallowed. + and cannot be turned off and the [no] form of the command is disallowed. .. clicmd:: log filtered-file [FILENAME [LEVEL]] @@ -769,7 +769,7 @@ These options apply to all |PACKAGE_NAME| daemons. .. option:: --command-log-always Cause the daemon to always log commands entered to the specified log file. - This also makes the `no log commands` command dissallowed. Enabling this + This also makes the `no log commands` command disallowed. Enabling this is suggested if you have need to track what the operator is doing on this router. diff --git a/doc/user/installation.rst b/doc/user/installation.rst index 70f82353b7..f07bade52c 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -274,6 +274,10 @@ options from the list below. Build with FPM module support. +.. option:: --enable-fpm-listener + + Build a small fpm listener for testing. + .. option:: --with-service-timeout=X Set timeout value for FRR service. The time of restarting or reloading FRR diff --git a/doc/user/zebra.rst b/doc/user/zebra.rst index 30b0204254..72b4f20418 100644 --- a/doc/user/zebra.rst +++ b/doc/user/zebra.rst @@ -1356,6 +1356,9 @@ FPM Commands User FPM configurations: 1 User FPM disable requests: 0 +.. clicmd:: show fpm status [json] + + Show the FPM status. .. clicmd:: clear fpm counters diff --git a/lib/darr.c b/lib/darr.c index f7a64fc394..7a01274104 100644 --- a/lib/darr.c +++ b/lib/darr.c @@ -58,6 +58,7 @@ char *__darr_in_vsprintf(char **sp, bool concat, const char *fmt, va_list ap) size_t inlen = concat ? darr_strlen(*sp) : 0; size_t capcount = strlen(fmt) + MIN(inlen + 64, 128); ssize_t len; + va_list ap_copy; darr_ensure_cap(*sp, capcount); @@ -68,10 +69,12 @@ char *__darr_in_vsprintf(char **sp, bool concat, const char *fmt, va_list ap) if (darr_len(*sp) == 0) *darr_append(*sp) = 0; again: - len = vsnprintf(darr_last(*sp), darr_avail(*sp), fmt, ap); + va_copy(ap_copy, ap); + len = vsnprintf(darr_last(*sp), darr_avail(*sp) + 1, fmt, ap_copy); + va_end(ap_copy); if (len < 0) darr_in_strcat(*sp, fmt); - else if ((size_t)len < darr_avail(*sp)) + else if ((size_t)len <= darr_avail(*sp)) _darr_len(*sp) += len; else { darr_ensure_cap(*sp, darr_len(*sp) + (size_t)len); @@ -1340,6 +1340,15 @@ static void cli_show_interface_end(struct vty *vty, vty_out(vty, "exit\n"); } +static int cli_cmp_interface(const struct lyd_node *dnode1, + const struct lyd_node *dnode2) +{ + const char *ifname1 = yang_dnode_get_string(dnode1, "name"); + const char *ifname2 = yang_dnode_get_string(dnode2, "name"); + + return if_cmp_name_func(ifname1, ifname2); +} + void if_vty_config_start(struct vty *vty, struct interface *ifp) { vty_frame(vty, "!\n"); @@ -1760,6 +1769,7 @@ const struct frr_yang_module_info frr_interface_info = { .destroy = lib_interface_destroy, .cli_show = cli_show_interface, .cli_show_end = cli_show_interface_end, + .cli_cmp = cli_cmp_interface, .get_next = lib_interface_get_next, .get_keys = lib_interface_get_keys, .lookup_entry = lib_interface_lookup_entry, @@ -1842,6 +1852,7 @@ const struct frr_yang_module_info frr_interface_cli_info = { .cbs = { .cli_show = cli_show_interface, .cli_show_end = cli_show_interface_end, + .cli_cmp = cli_cmp_interface, }, }, { diff --git a/lib/northbound.c b/lib/northbound.c index 25ea658bc4..487f225913 100644 --- a/lib/northbound.c +++ b/lib/northbound.c @@ -1873,7 +1873,7 @@ static void nb_transaction_apply_finish(struct nb_transaction *transaction, dnode = lyd_parent(dnode); if (!dnode) - break; + continue; /* * The dnode from 'delete' callbacks point to elements @@ -3570,8 +3570,9 @@ static void vty_mgmt_set_config_result_notified( zlog_err("SET_CONFIG request for client 0x%" PRIx64 " failed, Error: '%s'", client_id, errmsg_if_any ? errmsg_if_any : "Unknown"); - vty_out(vty, "ERROR: SET_CONFIG request failed, Error: %s\n", - errmsg_if_any ? errmsg_if_any : "Unknown"); + vty_out(vty, "%% Configuration failed.\n\n"); + if (errmsg_if_any) + vty_out(vty, "%s\n", errmsg_if_any); } else { debug_fe_client("SET_CONFIG request for client 0x%" PRIx64 " req-id %" PRIu64 " was successfull", @@ -3602,8 +3603,9 @@ static void vty_mgmt_commit_config_result_notified( zlog_err("COMMIT_CONFIG request for client 0x%" PRIx64 " failed, Error: '%s'", client_id, errmsg_if_any ? errmsg_if_any : "Unknown"); - vty_out(vty, "ERROR: COMMIT_CONFIG request failed, Error: %s\n", - errmsg_if_any ? errmsg_if_any : "Unknown"); + vty_out(vty, "%% Configuration failed.\n\n"); + if (errmsg_if_any) + vty_out(vty, "%s\n", errmsg_if_any); } else { debug_fe_client("COMMIT_CONFIG request for client 0x%" PRIx64 " req-id %" PRIu64 " was successfull", diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c index 35d67e247e..a4e0fa2169 100644 --- a/mgmtd/mgmt_txn.c +++ b/mgmtd/mgmt_txn.c @@ -821,7 +821,6 @@ static int mgmt_txn_create_config_batches(struct mgmt_txn_req *txn_req, struct nb_config_change *chg; struct mgmt_txn_be_cfg_batch *batch; char *xpath = NULL, *value = NULL; - char err_buf[1024]; enum mgmt_be_client_id id; struct mgmt_be_client_adapter *adapter; struct mgmt_commit_cfg_req *cmtcfg_req; @@ -916,12 +915,9 @@ static int mgmt_txn_create_config_batches(struct mgmt_txn_req *txn_req, num_chgs++; } - if (!chg_clients) { - snprintf(err_buf, sizeof(err_buf), - "No validator module found for XPATH: '%s", - xpath); - __log_err("***** %s", err_buf); - } + if (!chg_clients) + __log_err("No connected daemon is interested in XPATH %s", + xpath); cmtcfg_req->clients |= chg_clients; diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c index 0e5c06bf83..2cd24719bc 100644 --- a/mgmtd/mgmt_vty.c +++ b/mgmtd/mgmt_vty.c @@ -610,13 +610,13 @@ void mgmt_vty_init(void) * here one by one. */ zebra_cli_init(); -#if HAVE_RIPD +#ifdef HAVE_RIPD rip_cli_init(); #endif -#if HAVE_RIPNGD +#ifdef HAVE_RIPNGD ripng_cli_init(); #endif -#if HAVE_STATICD +#ifdef HAVE_STATICD static_vty_init(); #endif diff --git a/ospf6d/ospf6_neighbor.c b/ospf6d/ospf6_neighbor.c index a6089b2641..8b7afd89a7 100644 --- a/ospf6d/ospf6_neighbor.c +++ b/ospf6d/ospf6_neighbor.c @@ -1004,6 +1004,8 @@ static void ospf6_neighbor_show_detail(struct vty *vty, on->ospf6_if->interface->ifindex); json_object_int_add(json_neighbor, "neighborInterfaceIndex", on->ifindex); + json_object_string_addf(json_neighbor, "localLinkLocalAddress", + "%pI6", on->ospf6_if->linklocal_addr); json_object_string_add(json_neighbor, "linkLocalAddress", linklocal_addr); json_object_string_add(json_neighbor, "neighborState", diff --git a/ospfd/ospf_asbr.c b/ospfd/ospf_asbr.c index 5baad1754d..9b62f36d7a 100644 --- a/ospfd/ospf_asbr.c +++ b/ospfd/ospf_asbr.c @@ -110,7 +110,8 @@ ospf_external_info_add(struct ospf *ospf, uint8_t type, unsigned short instance, new = rn->info; if ((new->ifindex == ifindex) && (new->nexthop.s_addr == nexthop.s_addr) - && (new->tag == tag)) { + && (new->tag == tag) + && (new->metric == metric)) { route_unlock_node(rn); return NULL; /* NULL => no LSA to refresh */ } diff --git a/redhat/frr.spec.in b/redhat/frr.spec.in index 3371a3ed4d..433aeacebb 100644 --- a/redhat/frr.spec.in +++ b/redhat/frr.spec.in @@ -677,6 +677,7 @@ fi %{_sbindir}/mgmtd_testc %endif %exclude %{_sbindir}/ssd +%exclude %{_sbindir}/fpm_listener %if %{with_watchfrr} %{_sbindir}/watchfrr %endif diff --git a/ripd/rip_cli.c b/ripd/rip_cli.c index 29db1b232d..2e967698ea 100644 --- a/ripd/rip_cli.c +++ b/ripd/rip_cli.c @@ -83,6 +83,11 @@ void cli_show_router_rip(struct vty *vty, const struct lyd_node *dnode, vty_out(vty, "\n"); } +void cli_show_end_router_rip(struct vty *vty, const struct lyd_node *dnode) +{ + vty_out(vty, "exit\n"); +} + /* * XPath: /frr-ripd:ripd/instance/allow-ecmp */ @@ -1332,6 +1337,7 @@ const struct frr_yang_module_info frr_ripd_cli_info = { { .xpath = "/frr-ripd:ripd/instance", .cbs.cli_show = cli_show_router_rip, + .cbs.cli_show_end = cli_show_end_router_rip, }, { .xpath = "/frr-ripd:ripd/instance/allow-ecmp", diff --git a/ripd/rip_nb.h b/ripd/rip_nb.h index d07273af80..ee592daf81 100644 --- a/ripd/rip_nb.h +++ b/ripd/rip_nb.h @@ -173,6 +173,7 @@ void ripd_instance_timers_apply_finish(struct nb_cb_apply_finish_args *args); /* Optional 'cli_show' callbacks. */ void cli_show_router_rip(struct vty *vty, const struct lyd_node *dnode, bool show_defaults); +void cli_show_end_router_rip(struct vty *vty, const struct lyd_node *dnode); void cli_show_rip_allow_ecmp(struct vty *vty, const struct lyd_node *dnode, bool show_defaults); void cli_show_rip_default_information_originate(struct vty *vty, diff --git a/ripngd/ripng_cli.c b/ripngd/ripng_cli.c index a4a0f5a2cb..ed460a239e 100644 --- a/ripngd/ripng_cli.c +++ b/ripngd/ripng_cli.c @@ -83,6 +83,11 @@ void cli_show_router_ripng(struct vty *vty, const struct lyd_node *dnode, vty_out(vty, "\n"); } +void cli_show_end_router_ripng(struct vty *vty, const struct lyd_node *dnode) +{ + vty_out(vty, "exit\n"); +} + /* * XPath: /frr-ripngd:ripngd/instance/allow-ecmp */ @@ -701,6 +706,7 @@ const struct frr_yang_module_info frr_ripngd_cli_info = { { .xpath = "/frr-ripngd:ripngd/instance", .cbs.cli_show = cli_show_router_ripng, + .cbs.cli_show_end = cli_show_end_router_ripng, }, { .xpath = "/frr-ripngd:ripngd/instance/allow-ecmp", diff --git a/ripngd/ripng_nb.h b/ripngd/ripng_nb.h index a6ac1fba07..790a50f628 100644 --- a/ripngd/ripng_nb.h +++ b/ripngd/ripng_nb.h @@ -107,6 +107,7 @@ void ripngd_instance_timers_apply_finish(struct nb_cb_apply_finish_args *args); /* Optional 'cli_show' callbacks. */ void cli_show_router_ripng(struct vty *vty, const struct lyd_node *dnode, bool show_defaults); +void cli_show_end_router_ripng(struct vty *vty, const struct lyd_node *dnode); void cli_show_ripng_allow_ecmp(struct vty *vty, const struct lyd_node *dnode, bool show_defaults); void cli_show_ripng_default_information_originate(struct vty *vty, diff --git a/tests/topotests/bgp_bmp/r1/bgpd.conf b/tests/topotests/bgp_bmp/r1/bgpd.conf index 69acf6e750..24505de4a8 100644 --- a/tests/topotests/bgp_bmp/r1/bgpd.conf +++ b/tests/topotests/bgp_bmp/r1/bgpd.conf @@ -6,9 +6,17 @@ router bgp 65501 neighbor 192:168::2 remote-as 65502 ! bmp targets bmp1 - bmp connect 192.0.178.10 port 1789 min-retry 100 max-retry 10000 + bmp connect 192.0.2.10 port 1789 min-retry 100 max-retry 10000 exit ! + address-family ipv4 vpn + neighbor 192.168.0.2 activate + neighbor 192.168.0.2 soft-reconfiguration inbound + exit-address-family + address-family ipv6 vpn + neighbor 192:168::2 activate + neighbor 192:168::2 soft-reconfiguration inbound + exit-address-family address-family ipv4 unicast neighbor 192.168.0.2 activate neighbor 192.168.0.2 soft-reconfiguration inbound @@ -20,3 +28,21 @@ router bgp 65501 neighbor 192:168::2 soft-reconfiguration inbound exit-address-family ! +router bgp 65502 vrf vrf1 + bgp router_id 192.168.0.1 + bgp log-neighbor-changes + address-family ipv4 unicast + label vpn export 101 + rd vpn export 444:1 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family + address-family ipv6 unicast + label vpn export 103 + rd vpn export 555:1 + rt vpn both 54:200 + export vpn + import vpn + exit-address-family +exit diff --git a/tests/topotests/bgp_bmp/r1/zebra.conf b/tests/topotests/bgp_bmp/r1/zebra.conf index 6a25a6f4c2..0b523c9e18 100644 --- a/tests/topotests/bgp_bmp/r1/zebra.conf +++ b/tests/topotests/bgp_bmp/r1/zebra.conf @@ -1,5 +1,5 @@ interface r1-eth0 - ip address 192.0.178.1/24 + ip address 192.0.2.1/24 ! interface r1-eth1 ip address 192.168.0.1/24 diff --git a/tests/topotests/bgp_bmp/r2/bgpd.conf b/tests/topotests/bgp_bmp/r2/bgpd.conf index 7c8255a175..40e2cd5bbc 100644 --- a/tests/topotests/bgp_bmp/r2/bgpd.conf +++ b/tests/topotests/bgp_bmp/r2/bgpd.conf @@ -12,8 +12,35 @@ router bgp 65502 redistribute connected exit-address-family ! + address-family ipv4 vpn + neighbor 192.168.0.1 activate + exit-address-family +! + address-family ipv6 vpn + neighbor 192:168::1 activate + exit-address-family +! address-family ipv6 unicast neighbor 192:168::1 activate redistribute connected exit-address-family ! +router bgp 65502 vrf vrf1 + bgp router-id 192.168.0.2 + bgp log-neighbor-changes + no bgp network import-check + address-family ipv4 unicast + label vpn export 102 + rd vpn export 444:2 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family + address-family ipv6 unicast + label vpn export 105 + rd vpn export 555:2 + rt vpn both 54:200 + export vpn + import vpn + exit-address-family +exit diff --git a/tests/topotests/bgp_bmp/test_bgp_bmp.py b/tests/topotests/bgp_bmp/test_bgp_bmp.py index 250f1cb90d..02de805068 100644 --- a/tests/topotests/bgp_bmp/test_bgp_bmp.py +++ b/tests/topotests/bgp_bmp/test_bgp_bmp.py @@ -55,7 +55,7 @@ LOC_RIB = "loc-rib" def build_topo(tgen): tgen.add_router("r1") tgen.add_router("r2") - tgen.add_bmp_server("bmp1", ip="192.0.178.10", defaultRoute="via 192.0.178.1") + tgen.add_bmp_server("bmp1", ip="192.0.2.10", defaultRoute="via 192.0.2.1") switch = tgen.add_switch("s1") switch.add_link(tgen.gears["r1"]) @@ -81,8 +81,8 @@ def setup_module(mod): tgen.start_router() logger.info("starting BMP servers") - for _, server in tgen.get_bmp_servers().items(): - server.start() + for bmp_name, server in tgen.get_bmp_servers().items(): + server.start(log_file=os.path.join(tgen.logdir, bmp_name, "bmp.log")) def teardown_module(_mod): @@ -105,7 +105,9 @@ def get_bmp_messages(): """ messages = [] tgen = get_topogen() - text_output = tgen.gears["bmp1"].run("cat /var/log/bmp.log") + text_output = tgen.gears["bmp1"].run( + "cat {}".format(os.path.join(tgen.logdir, "bmp1", "bmp.log")) + ) for m in text_output.splitlines(): # some output in the bash can break the message decoding @@ -121,7 +123,7 @@ def get_bmp_messages(): return messages -def check_for_prefixes(expected_prefixes, bmp_log_type, policy): +def check_for_prefixes(expected_prefixes, bmp_log_type, policy, labels=None): """ Check for the presence of the given prefixes in the BMP server logs with the given message type and the set policy. @@ -140,6 +142,12 @@ def check_for_prefixes(expected_prefixes, bmp_log_type, policy): and "bmp_log_type" in m.keys() and m["bmp_log_type"] == bmp_log_type and m["policy"] == policy + and ( + labels is None + or ( + m["ip_prefix"] in labels.keys() and m["label"] == labels[m["ip_prefix"]] + ) + ) ] # check for prefixes @@ -174,7 +182,7 @@ def configure_prefixes(tgen, node, asn, safi, prefixes, vrf=None, update=True): Configure the bgp prefixes. """ withdraw = "no " if not update else "" - vrf = " vrf {}" if vrf else "" + vrf = " vrf {}".format(vrf) if vrf else "" for p in prefixes: ip = ip_network(p) cmd = [ @@ -216,6 +224,45 @@ def unicast_prefixes(policy): assert success, "Checking the withdrawed prefixes has been failed !." +def vpn_prefixes(policy): + """ + Setup the BMP monitor policy, Add and withdraw ipv4/v6 prefixes. + Check if the previous actions are logged in the BMP server with the right + message type and the right policy. + """ + tgen = get_topogen() + set_bmp_policy(tgen, "r1", 65501, "bmp1", "vpn", policy) + + prefixes = ["172.31.10.1/32", "2001::2222/128"] + + if policy == PRE_POLICY: + # labels are not yet supported in adj-RIB-in. Do not test for the moment + labels = None + else: + # "label vpn export" value in r2/bgpd.conf + labels = { + "172.31.10.1/32": 102, + "2001::2222/128": 105, + } + + # add prefixes + configure_prefixes(tgen, "r2", 65502, "unicast", prefixes, vrf="vrf1") + + logger.info("checking for updated prefixes") + # check + test_func = partial(check_for_prefixes, prefixes, "update", policy, labels=labels) + success, _ = topotest.run_and_expect(test_func, True, wait=0.5) + assert success, "Checking the updated prefixes has been failed !." + + # withdraw prefixes + configure_prefixes(tgen, "r2", 65502, "unicast", prefixes, vrf="vrf1", update=False) + logger.info("checking for withdrawed prefixes") + # check + test_func = partial(check_for_prefixes, prefixes, "withdraw", policy) + success, _ = topotest.run_and_expect(test_func, True, wait=0.5) + assert success, "Checking the withdrawed prefixes has been failed !." + + def test_bmp_server_logging(): """ Assert the logging of the bmp server. @@ -223,7 +270,9 @@ def test_bmp_server_logging(): def check_for_log_file(): tgen = get_topogen() - output = tgen.gears["bmp1"].run("ls /var/log/") + output = tgen.gears["bmp1"].run( + "ls {}".format(os.path.join(tgen.logdir, "bmp1")) + ) if "bmp.log" not in output: return False return True @@ -244,6 +293,16 @@ def test_bmp_bgp_unicast(): unicast_prefixes(LOC_RIB) +def test_bmp_bgp_vpn(): + # check for the prefixes in the BMP server logging file + logger.info("***** VPN prefixes pre-policy logging *****") + vpn_prefixes(PRE_POLICY) + logger.info("***** VPN prefixes post-policy logging *****") + vpn_prefixes(POST_POLICY) + logger.info("***** VPN prefixes loc-rib logging *****") + vpn_prefixes(LOC_RIB) + + if __name__ == "__main__": args = ["-s"] + sys.argv[1:] sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgp.evpn.route.prefix.after.json b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgp.evpn.route.prefix.after.json new file mode 100644 index 0000000000..0b5b1a1e2b --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgp.evpn.route.prefix.after.json @@ -0,0 +1 @@ +{"27.3.0.85:3":{"rd":"27.3.0.85:3","[5]:[0]:[16]:[10.27.0.0]":{"prefix":"[5]:[0]:[16]:[10.27.0.0]","prefixLen":352,"paths":[[{"valid":true,"bestpath":true,"selectionReason":"First path received","pathFrom":"external","routeType":5,"ethTag":0,"ipLen":16,"ip":"10.27.0.0","metric":0,"weight":32768,"peerId":"(unspec)","path":"","origin":"incomplete","extendedCommunity":{"string":"ET:8 RT:65000:4000 Rmac:44:00:00:ff:ff:01"},"nexthops":[{"ip":"10.10.10.10","hostname":"PE1","afi":"ipv4","used":true}]}]]}},"30.0.0.3:2":{"rd":"30.0.0.3:2","[5]:[0]:[32]:[4.4.4.1]":{"prefix":"[5]:[0]:[32]:[4.4.4.1]","prefixLen":352,"paths":[[{"valid":true,"bestpath":true,"selectionReason":"First path received","pathFrom":"internal","routeType":5,"ethTag":0,"ipLen":32,"ip":"4.4.4.1","metric":0,"locPrf":100,"weight":0,"peerId":"10.30.30.30","path":"","origin":"incomplete","extendedCommunity":{"string":"RT:65000:300 ET:8 Rmac:44:20:00:ff:ff:01"},"nexthops":[{"ip":"10.30.30.30","hostname":"PE2","afi":"ipv4","used":true}]}]]}},"numPrefix":2,"numPaths":2} diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgp.evpn.route.prefix.before.json b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgp.evpn.route.prefix.before.json new file mode 100644 index 0000000000..67417b5986 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgp.evpn.route.prefix.before.json @@ -0,0 +1 @@ +{"27.3.0.85:3":{"rd":"27.3.0.85:3","[5]:[0]:[24]:[10.27.7.0]":{"prefix":"[5]:[0]:[24]:[10.27.7.0]","prefixLen":352,"paths":[[{"valid":true,"bestpath":true,"selectionReason":"First path received","pathFrom":"external","routeType":5,"ethTag":0,"ipLen":24,"ip":"10.27.7.0","metric":0,"weight":32768,"peerId":"(unspec)","path":"","origin":"incomplete","extendedCommunity":{"string":"ET:8 RT:65000:4000 Rmac:44:00:00:ff:ff:01"},"nexthops":[{"ip":"10.10.10.10","hostname":"PE1","afi":"ipv4","used":true}]}]]},"[5]:[0]:[30]:[10.27.7.8]":{"prefix":"[5]:[0]:[30]:[10.27.7.8]","prefixLen":352,"paths":[[{"valid":true,"bestpath":true,"selectionReason":"First path received","pathFrom":"external","routeType":5,"ethTag":0,"ipLen":30,"ip":"10.27.7.8","metric":0,"weight":32768,"peerId":"(unspec)","path":"","origin":"incomplete","extendedCommunity":{"string":"ET:8 RT:65000:4000 Rmac:44:00:00:ff:ff:01"},"nexthops":[{"ip":"10.10.10.10","hostname":"PE1","afi":"ipv4","used":true}]}]]},"[5]:[0]:[32]:[10.27.7.22]":{"prefix":"[5]:[0]:[32]:[10.27.7.22]","prefixLen":352,"paths":[[{"valid":true,"bestpath":true,"selectionReason":"First path received","pathFrom":"external","routeType":5,"ethTag":0,"ipLen":32,"ip":"10.27.7.22","metric":0,"weight":32768,"peerId":"(unspec)","path":"","origin":"incomplete","extendedCommunity":{"string":"ET:8 RT:65000:4000 Rmac:44:00:00:ff:ff:01"},"nexthops":[{"ip":"10.10.10.10","hostname":"PE1","afi":"ipv4","used":true}]}]]}},"30.0.0.3:2":{"rd":"30.0.0.3:2","[5]:[0]:[32]:[4.4.4.1]":{"prefix":"[5]:[0]:[32]:[4.4.4.1]","prefixLen":352,"paths":[[{"valid":true,"bestpath":true,"selectionReason":"First path received","pathFrom":"internal","routeType":5,"ethTag":0,"ipLen":32,"ip":"4.4.4.1","metric":0,"locPrf":100,"weight":0,"peerId":"10.30.30.30","path":"","origin":"incomplete","extendedCommunity":{"string":"RT:65000:300 ET:8 Rmac:44:20:00:ff:ff:01"},"nexthops":[{"ip":"10.30.30.30","hostname":"PE2","afi":"ipv4","used":true}]}]]}},"numPrefix":4,"numPaths":4} diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgpd.conf index 9fb2bd6835..b58219ad8e 100644 --- a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgpd.conf +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgpd.conf @@ -11,9 +11,22 @@ router bgp 65000 advertise-svi-ip ! router bgp 65000 vrf vrf-red + address-family ipv4 unicast + redistribute static + exit-address-family ! address-family l2vpn evpn route-target import *:300 route-target import auto exit-address-family ! +router bgp 65000 vrf vrf-purple + address-family ipv4 unicast + redistribute static + exit-address-family + ! + address-family l2vpn evpn + advertise ipv4 unicast + exit-address-family + +! diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/zebra.conf index 8c6cf3e6d4..cea60a8b1e 100644 --- a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/zebra.conf +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/zebra.conf @@ -5,6 +5,12 @@ vrf vrf-red vni 100 exit-vrf ! +vrf vrf-purple + vni 4000 + ip route 10.27.7.0/24 blackhole + ip route 10.27.7.22/32 blackhole + ip route 10.27.7.10/30 blackhole +exit-vrf ! interface lo ip address 10.10.10.10/32 diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/test_bgp_evpn_vxlan_svd.py b/tests/topotests/bgp_evpn_vxlan_svd_topo1/test_bgp_evpn_vxlan_svd.py index 65c0c3532a..fd75c34ebf 100755 --- a/tests/topotests/bgp_evpn_vxlan_svd_topo1/test_bgp_evpn_vxlan_svd.py +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/test_bgp_evpn_vxlan_svd.py @@ -91,6 +91,10 @@ def setup_pe_router(tgen, pe_name, tunnel_local_ip, svi_ip, intf): pe.run("ip link add name bridge type bridge stp_state 0") pe.run("ip link set dev bridge type bridge vlan_filtering 1") pe.run("bridge vlan add vid 1 dev bridge self") + if pe_name == "PE1": + pe.run("ip link set dev bridge address 44:00:00:ff:ff:01") + if pe_name == "PE2": + pe.run("ip link set dev bridge address 44:20:00:ff:ff:01") pe.run("ip link set dev bridge up") # setup svi @@ -113,6 +117,18 @@ def setup_pe_router(tgen, pe_name, tunnel_local_ip, svi_ip, intf): pe.run("bridge vlan add dev vxlan0 vid 1 tunnel_info id 101") pe.run("ip link set up dev vxlan0") + # VRF creation + pe.run("ip link add vrf-purple type vrf table 1003") + pe.run("ip link set dev vrf-purple up") + if pe_name == "PE1": + pe.run("ip link add vrf-blue type vrf table 1002") + pe.run("ip link set dev vrf-blue up") + pe.run("ip addr add 27.2.0.85/32 dev vrf-blue") + pe.run("ip addr add 27.3.0.85/32 dev vrf-purple") + if pe_name == "PE2": + pe.run("ip link add vrf-blue type vrf table 2400") + pe.run("ip link set dev vrf-blue up") + # setup PE interface pe.run("ip link set dev {0}-{1} master bridge".format(pe_name, intf)) pe.run("bridge vlan del vid 1 dev {0}-{1}".format(pe_name, intf)) @@ -120,7 +136,7 @@ def setup_pe_router(tgen, pe_name, tunnel_local_ip, svi_ip, intf): pe.run("bridge vlan add vid 1 dev {0}-{1}".format(pe_name, intf)) pe.run("bridge vlan add vid 1 pvid untagged dev {0}-{1}".format(pe_name, intf)) - # l3vni 100 + # L3VNI 100 pe.run("ip link add vrf-red type vrf table 1400") pe.run("ip link add link bridge name vlan100 type vlan id 100 protocol 802.1q") pe.run("ip link set dev vlan100 master vrf-blue") @@ -129,9 +145,16 @@ def setup_pe_router(tgen, pe_name, tunnel_local_ip, svi_ip, intf): pe.run("bridge vlan add dev vxlan0 vid 100") pe.run("bridge vlan add dev vxlan0 vid 100 tunnel_info id 100") + # L3VNI 4000 + pe.run("ip link add link bridge name vlan400 type vlan id 400 protocol 802.1q") + pe.run("ip link set dev vlan400 master vrf-purple") + pe.run("ip link set dev vlan400 up") + pe.run("bridge vlan add vid 400 dev bridge self") + pe.run("bridge vlan add dev vxlan0 vid 400") + pe.run("bridge vlan add dev vxlan0 vid 400 tunnel_info id 4000") + # add a vrf for testing DVNI if pe_name == "PE2": - pe.run("ip link add vrf-blue type vrf table 2400") pe.run("ip link add link bridge name vlan300 type vlan id 300 protocol 802.1q") pe.run("ip link set dev vlan300 master vrf-blue") pe.run("ip link set dev vlan300 up") @@ -199,6 +222,14 @@ def show_vni_json_elide_ifindex(pe, vni, expected): return topotest.json_cmp(output_json, expected) +def show_bgp_l2vpn_evpn_route_type_prefix_json(pe, expected): + output_json = pe.vtysh_cmd( + "show bgp l2vpn evpn route type prefix json", isjson=True + ) + + return topotest.json_cmp(output_json, expected) + + def check_vni_macs_present(tgen, router, vni, maclist): result = router.vtysh_cmd("show evpn mac vni {} json".format(vni), isjson=True) for rname, ifname in maclist: @@ -331,6 +362,7 @@ def mac_test_local_remote(local, remote): remote_output_json = json.loads(remote_output) local_output_vni_json = json.loads(local_output_vni) + # breakpoint() for vni in local_output_json: if vni not in remote_output_json: continue @@ -542,6 +574,42 @@ def test_dvni(): # tgen.mininet_cli() +def test_pe_advertise_aggr_evpn_route(): + "BGP advertise aggregated Type-5 prefix route" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Checking BGP EVPN route contains non-aggregate prefixes") + pe1 = tgen.gears["PE1"] + json_file = "{}/{}/bgp.evpn.route.prefix.before.json".format(CWD, pe1.name) + expected = json.loads(open(json_file).read()) + + test_func = partial(show_bgp_l2vpn_evpn_route_type_prefix_json, pe1, expected) + _, result = topotest.run_and_expect(test_func, None, count=45, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(pe1.name) + assert result is None, assertmsg + + logger.info("Configure BGP aggregate-address summary-only under ipv4-unicast") + pe1.cmd( + 'vtysh -c "config t" -c "router bgp 65000 vrf vrf-purple" ' + + ' -c "address-family ipv4 unicast" ' + + ' -c "aggregate-address 10.27.0.0/16 summary-only" ' + ) + + logger.info("Checking BGP EVPN route contains aggregated prefix") + pe1 = tgen.gears["PE1"] + json_file = "{}/{}/bgp.evpn.route.prefix.after.json".format(CWD, pe1.name) + expected = json.loads(open(json_file).read()) + + test_func = partial(show_bgp_l2vpn_evpn_route_type_prefix_json, pe1, expected) + _, result = topotest.run_and_expect(test_func, None, count=45, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(pe1.name) + assert result is None, assertmsg + + def test_memory_leak(): "Run the memory leak test and report results." tgen = get_topogen() diff --git a/tests/topotests/bgp_peer_type_multipath_relax/test_bgp_peer-type_multipath-relax.py b/tests/topotests/bgp_peer_type_multipath_relax/test_bgp_peer-type_multipath-relax.py index 9239be9221..340df71a19 100755 --- a/tests/topotests/bgp_peer_type_multipath_relax/test_bgp_peer-type_multipath-relax.py +++ b/tests/topotests/bgp_peer_type_multipath_relax/test_bgp_peer-type_multipath-relax.py @@ -103,7 +103,6 @@ def setup_module(mod): # For all registered routers, load the zebra configuration file for rname, router in tgen.routers().items(): - router.run("/bin/bash {}/setup_vrfs".format(CWD)) router.load_config( TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) ) diff --git a/tests/topotests/fpm_testing_topo1/r1/fpm_counters.json b/tests/topotests/fpm_testing_topo1/r1/fpm_counters.json new file mode 100644 index 0000000000..05a6731e13 --- /dev/null +++ b/tests/topotests/fpm_testing_topo1/r1/fpm_counters.json @@ -0,0 +1,8 @@ +{ + "connected":true, + "useNHG":true, + "useRouteReplace":true, + "disabled":false, + "address":"127.0.0.1", + "port":2620 +} diff --git a/tests/topotests/fpm_testing_topo1/r1/fpm_stub.conf b/tests/topotests/fpm_testing_topo1/r1/fpm_stub.conf new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/fpm_testing_topo1/r1/fpm_stub.conf diff --git a/tests/topotests/fpm_testing_topo1/r1/routes_summ.json b/tests/topotests/fpm_testing_topo1/r1/routes_summ.json new file mode 100644 index 0000000000..e9157bc664 --- /dev/null +++ b/tests/topotests/fpm_testing_topo1/r1/routes_summ.json @@ -0,0 +1,27 @@ +{ + "routes":[ + { + "fib":1, + "rib":1, + "fibOffLoaded":0, + "fibTrapped":0, + "type":"connected" + }, + { + "fib":1, + "rib":1, + "fibOffLoaded":0, + "fibTrapped":0, + "type":"local" + }, + { + "fib":10000, + "rib":10000, + "fibOffLoaded":0, + "fibTrapped":0, + "type":"sharp" + } + ], + "routesTotal":10002, + "routesTotalFib":10002 +} diff --git a/tests/topotests/fpm_testing_topo1/r1/routes_summ_removed.json b/tests/topotests/fpm_testing_topo1/r1/routes_summ_removed.json new file mode 100644 index 0000000000..8585b2bb6b --- /dev/null +++ b/tests/topotests/fpm_testing_topo1/r1/routes_summ_removed.json @@ -0,0 +1,20 @@ +{ + "routes":[ + { + "fib":1, + "rib":1, + "fibOffLoaded":0, + "fibTrapped":0, + "type":"connected" + }, + { + "fib":1, + "rib":1, + "fibOffLoaded":0, + "fibTrapped":0, + "type":"local" + } + ], + "routesTotal":2, + "routesTotalFib":2 +} diff --git a/tests/topotests/fpm_testing_topo1/r1/sharpd.conf b/tests/topotests/fpm_testing_topo1/r1/sharpd.conf new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/fpm_testing_topo1/r1/sharpd.conf diff --git a/tests/topotests/fpm_testing_topo1/r1/zebra.conf b/tests/topotests/fpm_testing_topo1/r1/zebra.conf new file mode 100644 index 0000000000..c7b1646dda --- /dev/null +++ b/tests/topotests/fpm_testing_topo1/r1/zebra.conf @@ -0,0 +1,5 @@ +fpm address 127.0.0.1 + +interface r1-eth0 + ip address 192.168.44.1/24 +! diff --git a/tests/topotests/fpm_testing_topo1/test_fpm_topo1.py b/tests/topotests/fpm_testing_topo1/test_fpm_topo1.py new file mode 100644 index 0000000000..bb4d02d342 --- /dev/null +++ b/tests/topotests/fpm_testing_topo1/test_fpm_topo1.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_route_scale1.py +# +# Copyright (c) 2024 by +# Nvidia, Inc. +# Donald Sharp +# + +""" +test_fpm_topo1.py: Testing FPM module + +""" +import os +import re +import sys +import pytest +import json +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 + + +pytestmark = [pytest.mark.fpm, pytest.mark.sharpd] + + +def build_topo(tgen): + "Build function" + + # Populate routers + tgen.add_router("r1") + + switch = tgen.add_switch("sw1") + switch.add_link(tgen.gears["r1"]) + + +def setup_module(module): + "Setup topology" + + # fpm_stub = os.system("which fpm-stub") + # if fpm-stub: + # pytest.skip("") + + tgen = Topogen(build_topo, module.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, + os.path.join(CWD, "{}/zebra.conf".format(rname)), + "-M dplane_fpm_nl", + ) + router.load_config( + TopoRouter.RD_SHARP, os.path.join(CWD, "{}/sharpd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_FPM_LISTENER, os.path.join(CWD, "{}/fpm_stub.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 test_fpm_connection_made(): + "Test that the fpm starts up and a connection is made" + + tgen = get_topogen() + router = tgen.gears["r1"] + + fpm_counters = "{}/r1/fpm_counters.json".format(CWD) + expected = json.loads(open(fpm_counters).read()) + + test_func = partial( + topotest.router_json_cmp, router, "show fpm status json", expected + ) + + success, result = topotest.run_and_expect(test_func, None, 30, 1) + assert success, "Unable to connect to the fpm:\n{}".format(result) + + +def test_fpm_install_routes(): + "Test that simple routes installed appears to work" + + tgen = get_topogen() + router = tgen.gears["r1"] + + # Let's install 10000 routes + router.vtysh_cmd("sharp install routes 10.0.0.0 nexthop 192.168.44.33 10000") + routes_file = "{}/r1/routes_summ.json".format(CWD) + expected = json.loads(open(routes_file).read()) + + test_func = partial( + topotest.router_json_cmp, router, "show ip route summ json", expected + ) + + success, result = topotest.run_and_expect(test_func, None, 60, 1) + assert success, "Unable to successfully install 10000 routes: {}".format(result) + + # Let's remove 10000 routes + router.vtysh_cmd("sharp remove routes 10.0.0.0 10000") + + routes_file_removed = "{}/r1/routes_summ_removed.json".format(CWD) + expected = json.loads(open(routes_file_removed).read()) + + test_func = partial( + topotest.router_json_cmp, router, "show ip route summ json", expected + ) + + success, result = topotest.run_and_expect(test_func, None, 60, 1) + assert success, "Unable to remove 10000 routes: {}".format(result) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/lib/bmp_collector/bmp.py b/tests/topotests/lib/bmp_collector/bmp.py index 57f642aa0e..237decdd5e 100644 --- a/tests/topotests/lib/bmp_collector/bmp.py +++ b/tests/topotests/lib/bmp_collector/bmp.py @@ -33,30 +33,33 @@ IS_FILTERED = 1 << 7 if not os.path.exists(LOG_DIR): os.makedirs(LOG_DIR) + def bin2str_ipaddress(ip_bytes, is_ipv6=False): if is_ipv6: return str(ipaddress.IPv6Address(ip_bytes)) return str(ipaddress.IPv4Address(ip_bytes[-4:])) -def log2file(logs): + +def log2file(logs, log_file): """ XXX: extract the useful information and save it in a flat dictionnary """ - with open(LOG_FILE, 'a') as f: + with open(log_file, "a") as f: f.write(json.dumps(logs) + "\n") -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPCodes: """ XXX: complete the list, provide RFCs. """ + VERSION = 0x3 BMP_MSG_TYPE_ROUTE_MONITORING = 0x00 BMP_MSG_TYPE_STATISTICS_REPORT = 0x01 - BMP_MSG_TYPE_PEER_DOWN_NOTIFICATION = 0x02 - BMP_MSG_TYPE_PEER_UP_NOTIFICATION = 0x03 + BMP_MSG_TYPE_PEER_DOWN_NOTIFICATION = 0x02 + BMP_MSG_TYPE_PEER_UP_NOTIFICATION = 0x03 BMP_MSG_TYPE_INITIATION = 0x04 BMP_MSG_TYPE_TERMINATION = 0x05 BMP_MSG_TYPE_ROUTE_MIRRORING = 0x06 @@ -107,15 +110,15 @@ class BMPCodes: # peer down reason code BMP_PEER_DOWN_LOCAL_NOTIFY = 0x01 - BMP_PEER_DOWN_LOCAL_NO_NOTIFY = 0X02 - BMP_PEER_DOWN_REMOTE_NOTIFY = 0X03 - BMP_PEER_DOWN_REMOTE_NO_NOTIFY = 0X04 + BMP_PEER_DOWN_LOCAL_NO_NOTIFY = 0x02 + BMP_PEER_DOWN_REMOTE_NOTIFY = 0x03 + BMP_PEER_DOWN_REMOTE_NO_NOTIFY = 0x04 BMP_PEER_DOWN_INFO_NO_LONGER = 0x05 - BMP_PEER_DOWN_SYSTEM_CLOSED = 0X06 + BMP_PEER_DOWN_SYSTEM_CLOSED = 0x06 # termincation message types BMP_TERM_TYPE_STRING = 0x00 - BMP_TERM_TYPE_REASON = 0X01 + BMP_TERM_TYPE_REASON = 0x01 # termination reason code BMP_TERM_REASON_ADMIN_CLOSE = 0x00 @@ -126,31 +129,32 @@ class BMPCodes: # policy route tlv BMP_ROUTE_POLICY_TLV_VRF = 0x00 - BMP_ROUTE_POLICY_TLV_POLICY= 0x01 + BMP_ROUTE_POLICY_TLV_POLICY = 0x01 BMP_ROUTE_POLICY_TLV_PRE_POLICY = 0x02 BMP_ROUTE_POLICY_TLV_POST_POLICY = 0x03 BMP_ROUTE_POLICY_TLV_STRING = 0x04 -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPMsg: """ XXX: should we move register_msg_type and look_msg_type to generic Type class. """ + TYPES = {} UNKNOWN_TYPE = None - HDR_STR = '!BIB' + HDR_STR = "!BIB" MIN_LEN = struct.calcsize(HDR_STR) TYPES_STR = { - BMPCodes.BMP_MSG_TYPE_INITIATION: 'initiation', - BMPCodes.BMP_MSG_TYPE_PEER_DOWN_NOTIFICATION: 'peer down notification', - BMPCodes.BMP_MSG_TYPE_PEER_UP_NOTIFICATION: 'peer up notification', - BMPCodes.BMP_MSG_TYPE_ROUTE_MONITORING: 'route monitoring', - BMPCodes.BMP_MSG_TYPE_STATISTICS_REPORT: 'statistics report', - BMPCodes.BMP_MSG_TYPE_TERMINATION: 'termination', - BMPCodes.BMP_MSG_TYPE_ROUTE_MIRRORING: 'route mirroring', - BMPCodes.BMP_MSG_TYPE_ROUTE_POLICY: 'route policy', + BMPCodes.BMP_MSG_TYPE_INITIATION: "initiation", + BMPCodes.BMP_MSG_TYPE_PEER_DOWN_NOTIFICATION: "peer down notification", + BMPCodes.BMP_MSG_TYPE_PEER_UP_NOTIFICATION: "peer up notification", + BMPCodes.BMP_MSG_TYPE_ROUTE_MONITORING: "route monitoring", + BMPCodes.BMP_MSG_TYPE_STATISTICS_REPORT: "statistics report", + BMPCodes.BMP_MSG_TYPE_TERMINATION: "termination", + BMPCodes.BMP_MSG_TYPE_ROUTE_MIRRORING: "route mirroring", + BMPCodes.BMP_MSG_TYPE_ROUTE_POLICY: "route policy", } @classmethod @@ -158,6 +162,7 @@ class BMPMsg: def _register_type(subcls): cls.TYPES[msgtype] = subcls return subcls + return _register_type @classmethod @@ -179,15 +184,15 @@ class BMPMsg: if len(data) < cls.MIN_LEN: pass else: - _version, _len, _type = struct.unpack(cls.HDR_STR, data[0:cls.MIN_LEN]) + _version, _len, _type = struct.unpack(cls.HDR_STR, data[0 : cls.MIN_LEN]) return _version, _len, _type @classmethod - def dissect(cls, data): + def dissect(cls, data, log_file=None): global SEQ version, msglen, msgtype = cls.dissect_header(data) - msg_data = data[cls.MIN_LEN:msglen] + msg_data = data[cls.MIN_LEN : msglen] data = data[msglen:] if version != BMPCodes.VERSION: @@ -202,13 +207,13 @@ class BMPMsg: msg_cls.MSG_LEN = msglen - cls.MIN_LEN logs = msg_cls.dissect(msg_data) logs["seq"] = SEQ - log2file(logs) + log2file(logs, log_file if log_file else LOG_FILE) SEQ += 1 return data -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPPerPeerMessage: """ 0 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 @@ -229,30 +234,33 @@ class BMPPerPeerMessage: | Timestamp (microseconds) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ """ - PEER_UNPACK_STR = '!BB8s16sI4sII' + + PEER_UNPACK_STR = "!BB8s16sI4sII" PEER_TYPE_STR = { - BMPCodes.BMP_PEER_GLOBAL_INSTANCE: 'global instance', - BMPCodes.BMP_PEER_RD_INSTANCE: 'route distinguisher instance', - BMPCodes.BMP_PEER_LOCAL_INSTANCE: 'local instance', - BMPCodes.BMP_PEER_LOC_RIB_INSTANCE: 'loc-rib instance', + BMPCodes.BMP_PEER_GLOBAL_INSTANCE: "global instance", + BMPCodes.BMP_PEER_RD_INSTANCE: "route distinguisher instance", + BMPCodes.BMP_PEER_LOCAL_INSTANCE: "local instance", + BMPCodes.BMP_PEER_LOC_RIB_INSTANCE: "loc-rib instance", } @classmethod def dissect(cls, data): - (peer_type, - peer_flags, - peer_distinguisher, - peer_address, - peer_asn, - peer_bgp_id, - timestamp_secs, - timestamp_microsecs) = struct.unpack_from(cls.PEER_UNPACK_STR, data) - - msg = {'peer_type': cls.PEER_TYPE_STR[peer_type]} + ( + peer_type, + peer_flags, + peer_distinguisher, + peer_address, + peer_asn, + peer_bgp_id, + timestamp_secs, + timestamp_microsecs, + ) = struct.unpack_from(cls.PEER_UNPACK_STR, data) + + msg = {"peer_type": cls.PEER_TYPE_STR[peer_type]} if peer_type == 0x03: - msg['is_filtered'] = bool(peer_flags & IS_FILTERED) - msg['policy'] = 'loc-rib' + msg["is_filtered"] = bool(peer_flags & IS_FILTERED) + msg["policy"] = "loc-rib" else: # peer_flags = 0x0000 0000 # ipv6, post-policy, as-path, adj-rib-out, reserverdx4 @@ -260,29 +268,29 @@ class BMPPerPeerMessage: is_as_path = bool(peer_flags & IS_AS_PATH) is_post_policy = bool(peer_flags & IS_POST_POLICY) is_ipv6 = bool(peer_flags & IS_IPV6) - msg['policy'] = 'post-policy' if is_post_policy else 'pre-policy' - msg['ipv6'] = is_ipv6 - msg['peer_ip'] = bin2str_ipaddress(peer_address, is_ipv6) - + msg["policy"] = "post-policy" if is_post_policy else "pre-policy" + msg["ipv6"] = is_ipv6 + msg["peer_ip"] = bin2str_ipaddress(peer_address, is_ipv6) peer_bgp_id = bin2str_ipaddress(peer_bgp_id) - timestamp = float(timestamp_secs) + timestamp_microsecs * (10 ** -6) - - data = data[struct.calcsize(cls.PEER_UNPACK_STR):] - msg.update({ - 'peer_distinguisher': str(RouteDistinguisher(peer_distinguisher)), - 'peer_asn': peer_asn, - 'peer_bgp_id': peer_bgp_id, - 'timestamp': str(datetime.datetime.fromtimestamp(timestamp)), - }) + timestamp = float(timestamp_secs) + timestamp_microsecs * (10**-6) + + data = data[struct.calcsize(cls.PEER_UNPACK_STR) :] + msg.update( + { + "peer_distinguisher": str(RouteDistinguisher(peer_distinguisher)), + "peer_asn": peer_asn, + "peer_bgp_id": peer_bgp_id, + "timestamp": str(datetime.datetime.fromtimestamp(timestamp)), + } + ) return data, msg -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ @BMPMsg.register_msg_type(BMPCodes.BMP_MSG_TYPE_ROUTE_MONITORING) class BMPRouteMonitoring(BMPPerPeerMessage): - @classmethod def dissect(cls, data): data, peer_msg = super().dissect(data) @@ -290,7 +298,7 @@ class BMPRouteMonitoring(BMPPerPeerMessage): return {**peer_msg, **update_msg} -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPStatisticsReport: """ 0 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 @@ -303,10 +311,11 @@ class BMPStatisticsReport: ~ ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ """ + pass -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPPeerDownNotification: """ 0 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 @@ -316,10 +325,11 @@ class BMPPeerDownNotification: | Data (present if Reason = 1, 2 or 3) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ """ + pass -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ @BMPMsg.register_msg_type(BMPCodes.BMP_MSG_TYPE_PEER_UP_NOTIFICATION) class BMPPeerUpNotification(BMPPerPeerMessage): """ @@ -336,7 +346,8 @@ class BMPPeerUpNotification(BMPPerPeerMessage): ~ ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ """ - UNPACK_STR = '!16sHH' + + UNPACK_STR = "!16sHH" MIN_LEN = struct.calcsize(UNPACK_STR) MSG_LEN = None @@ -344,16 +355,14 @@ class BMPPeerUpNotification(BMPPerPeerMessage): def dissect(cls, data): data, peer_msg = super().dissect(data) - (local_addr, - local_port, - remote_port) = struct.unpack_from(cls.UNPACK_STR, data) + (local_addr, local_port, remote_port) = struct.unpack_from(cls.UNPACK_STR, data) msg = { **peer_msg, **{ - 'local_ip': bin2str_ipaddress(local_addr, peer_msg.get('ipv6')), - 'local_port': int(local_port), - 'remote_port': int(remote_port), + "local_ip": bin2str_ipaddress(local_addr, peer_msg.get("ipv6")), + "local_port": int(local_port), + "remote_port": int(remote_port), }, } @@ -362,7 +371,7 @@ class BMPPeerUpNotification(BMPPerPeerMessage): return msg -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ @BMPMsg.register_msg_type(BMPCodes.BMP_MSG_TYPE_INITIATION) class BMPInitiation: """ @@ -374,30 +383,31 @@ class BMPInitiation: ~ ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ """ - TLV_STR = '!HH' + + TLV_STR = "!HH" MIN_LEN = struct.calcsize(TLV_STR) FIELD_TO_STR = { - BMPCodes.BMP_INIT_INFO_STRING: 'information', - BMPCodes.BMP_INIT_ADMIN_LABEL: 'admin_label', - BMPCodes.BMP_INIT_SYSTEM_DESCRIPTION: 'system_description', - BMPCodes.BMP_INIT_SYSTEM_NAME: 'system_name', - BMPCodes.BMP_INIT_VRF_TABLE_NAME: 'vrf_table_name', + BMPCodes.BMP_INIT_INFO_STRING: "information", + BMPCodes.BMP_INIT_ADMIN_LABEL: "admin_label", + BMPCodes.BMP_INIT_SYSTEM_DESCRIPTION: "system_description", + BMPCodes.BMP_INIT_SYSTEM_NAME: "system_name", + BMPCodes.BMP_INIT_VRF_TABLE_NAME: "vrf_table_name", } @classmethod def dissect(cls, data): msg = {} while len(data) > cls.MIN_LEN: - _type, _len = struct.unpack_from(cls.TLV_STR, data[0:cls.MIN_LEN]) - _value = data[cls.MIN_LEN: cls.MIN_LEN + _len].decode() + _type, _len = struct.unpack_from(cls.TLV_STR, data[0 : cls.MIN_LEN]) + _value = data[cls.MIN_LEN : cls.MIN_LEN + _len].decode() msg[cls.FIELD_TO_STR[_type]] = _value - data = data[cls.MIN_LEN + _len:] + data = data[cls.MIN_LEN + _len :] return msg -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPTermination: """ 0 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 @@ -408,14 +418,15 @@ class BMPTermination: ~ ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ """ + pass -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPRouteMirroring: pass -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPRoutePolicy: pass diff --git a/tests/topotests/lib/bmp_collector/bmpserver b/tests/topotests/lib/bmp_collector/bmpserver index 25b4a52c5e..5257df7530 100755 --- a/tests/topotests/lib/bmp_collector/bmpserver +++ b/tests/topotests/lib/bmp_collector/bmpserver @@ -16,10 +16,12 @@ BGP_MAX_SIZE = 4096 parser = argparse.ArgumentParser() parser.add_argument("-a", "--address", type=str, default="0.0.0.0") parser.add_argument("-p", "--port", type=int, default=1789) +parser.add_argument("-l", "--logfile", type=str, default="/var/log/bmp.log") def main(): args = parser.parse_args() ADDRESS, PORT = args.address, args.port + LOG_FILE = args.logfile with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @@ -31,7 +33,7 @@ def main(): while True: data = connection.recv(BGP_MAX_SIZE) while len(data) > BMPMsg.MIN_LEN: - data = BMPMsg.dissect(data) + data = BMPMsg.dissect(data, log_file=LOG_FILE) except Exception as e: # XXX: do something pass diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py index 155d2d0986..f49e30ea5f 100644 --- a/tests/topotests/lib/topogen.py +++ b/tests/topotests/lib/topogen.py @@ -94,7 +94,9 @@ def get_exabgp_cmd(commander=None): return False version = m.group(1) if topotest.version_cmp(version, "4.2.11") < 0: - logging.debug("found exabgp version < 4.2.11 in %s will keep looking", exacmd) + logging.debug( + "found exabgp version < 4.2.11 in %s will keep looking", exacmd + ) return False logger.info("Using ExaBGP version %s in %s", version, exacmd) return True @@ -746,6 +748,7 @@ class TopoRouter(TopoGear): RD_PIM6 = 19 RD_MGMTD = 20 RD_TRAP = 21 + RD_FPM_LISTENER = 22 RD = { RD_FRR: "frr", RD_ZEBRA: "zebra", @@ -769,6 +772,7 @@ class TopoRouter(TopoGear): RD_SNMP: "snmpd", RD_MGMTD: "mgmtd", RD_TRAP: "snmptrapd", + RD_FPM_LISTENER: "fpm_listener", } def __init__(self, tgen, cls, name, **params): @@ -845,7 +849,8 @@ class TopoRouter(TopoGear): TopoRouter.RD_RIPNG, TopoRouter.RD_OSPF, TopoRouter.RD_OSPF6, TopoRouter.RD_ISIS, TopoRouter.RD_BGP, TopoRouter.RD_LDP, TopoRouter.RD_PIM, TopoRouter.RD_PIM6, TopoRouter.RD_PBR, - TopoRouter.RD_SNMP, TopoRouter.RD_MGMTD, TopoRouter.RD_TRAP. + TopoRouter.RD_SNMP, TopoRouter.RD_MGMTD, TopoRouter.RD_TRAP, + TopoRouter.RD_FPM_LISTENER. Possible `source` values are `None` for an empty config file, a path name which is used directly, or a file name with no path components which is first looked for @@ -883,7 +888,12 @@ class TopoRouter(TopoGear): # Enable all daemon command logging, logging files # and set them to the start dir. for daemon, enabled in nrouter.daemons.items(): - if enabled and daemon != "snmpd" and daemon != "snmptrapd": + if ( + enabled + and daemon != "snmpd" + and daemon != "snmptrapd" + and daemon != "fpm_listener" + ): self.vtysh_cmd( "\n".join( [ @@ -933,7 +943,7 @@ class TopoRouter(TopoGear): # and set them to the start dir. for daemon in daemons: enabled = nrouter.daemons[daemon] - if enabled and daemon != "snmpd": + if enabled and daemon != "snmpd" and daemon != "fpm_listener": self.vtysh_cmd( "\n".join( [ @@ -1253,9 +1263,12 @@ class TopoBMPCollector(TopoHost): gear += " TopoBMPCollector<>".format() return gear - def start(self): + def start(self, log_file=None): + log_arg = "-l {}".format(log_file) if log_file else "" self.run( - "{}/bmp_collector/bmpserver -a {} -p {}&".format(CWD, self.ip, self.port), + "{}/bmp_collector/bmpserver -a {} -p {} {}&".format( + CWD, self.ip, self.port, log_arg + ), stdout=None, ) diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index 2bb892355e..0a5be970b8 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -1262,8 +1262,8 @@ def rlimit_atleast(rname, min_value, raises=False): def fix_netns_limits(ns): # Maximum read and write socket buffer sizes - sysctl_atleast(ns, "net.ipv4.tcp_rmem", [10 * 1024, 87380, 16 * 2**20]) - sysctl_atleast(ns, "net.ipv4.tcp_wmem", [10 * 1024, 87380, 16 * 2**20]) + sysctl_atleast(ns, "net.ipv4.tcp_rmem", [10 * 1024, 87380, 16 * 2 ** 20]) + sysctl_atleast(ns, "net.ipv4.tcp_wmem", [10 * 1024, 87380, 16 * 2 ** 20]) sysctl_assure(ns, "net.ipv4.conf.all.rp_filter", 0) sysctl_assure(ns, "net.ipv4.conf.default.rp_filter", 0) @@ -1322,8 +1322,8 @@ def fix_host_limits(): sysctl_atleast(None, "net.core.netdev_max_backlog", 4 * 1024) # Maximum read and write socket buffer sizes - sysctl_atleast(None, "net.core.rmem_max", 16 * 2**20) - sysctl_atleast(None, "net.core.wmem_max", 16 * 2**20) + sysctl_atleast(None, "net.core.rmem_max", 16 * 2 ** 20) + sysctl_atleast(None, "net.core.wmem_max", 16 * 2 ** 20) # Garbage Collection Settings for ARP and Neighbors sysctl_atleast(None, "net.ipv4.neigh.default.gc_thresh2", 4 * 1024) @@ -1426,6 +1426,7 @@ class Router(Node): "snmpd": 0, "mgmtd": 0, "snmptrapd": 0, + "fpm_listener": 0, } self.daemons_options = {"zebra": ""} self.reportCores = True @@ -1896,7 +1897,11 @@ class Router(Node): ) rediropt = " > {0}.out 2> {0}.err".format(daemon) - if daemon == "snmpd": + if daemon == "fpm_listener": + binary = "/usr/lib/frr/fpm_listener" + cmdenv = "" + cmdopt = "-d {}".format(daemon_opts) + elif daemon == "snmpd": binary = "/usr/sbin/snmpd" cmdenv = "" cmdopt = "{} -C -c /etc/frr/snmpd.conf -p ".format( @@ -2162,7 +2167,11 @@ class Router(Node): "%s: %s %s started with rr", self, self.routertype, daemon ) else: - if daemon != "snmpd" and daemon != "snmptrapd": + if ( + daemon != "snmpd" + and daemon != "snmptrapd" + and daemon != "fpm_listener" + ): cmdopt += " -d " cmdopt += rediropt @@ -2212,6 +2221,11 @@ class Router(Node): while "snmpd" in daemons_list: daemons_list.remove("snmpd") + if "fpm_listener" in daemons_list: + start_daemon("fpm_listener") + while "fpm_listener" in daemons_list: + daemons_list.remove("fpm_listener") + # Now start all the other daemons for daemon in daemons_list: if self.daemons[daemon] == 0: @@ -2407,6 +2421,8 @@ class Router(Node): continue if daemon == "snmptrapd": continue + if daemon == "fpm_listener": + continue if (self.daemons[daemon] == 1) and not (daemon in daemonsRunning): sys.stderr.write("%s: Daemon %s not running\n" % (self.name, daemon)) if daemon == "staticd": diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-exact.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-exact.json index f04e3a55e1..04591b6e29 100644 --- a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-exact.json +++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-exact.json @@ -14,7 +14,8 @@ "frr-zebra:zebra": { "state": { "up-count": 0, - "down-count": 0 + "down-count": 0, + "zif-type": "zif-veth" } } } diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json index 84ad82c058..c770db49b1 100644 --- a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json +++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json @@ -25,7 +25,8 @@ }, "state": { "up-count": 0, - "down-count": 0 + "down-count": 0, + "zif-type": "zif-veth" } } } diff --git a/tests/topotests/pytest.ini b/tests/topotests/pytest.ini index 98fcfbc848..db806fed39 100644 --- a/tests/topotests/pytest.ini +++ b/tests/topotests/pytest.ini @@ -43,6 +43,7 @@ markers = bfdd: Tests that run against BFDD bgpd: Tests that run against BGPD eigrpd: Tests that run against EIGRPD + fpm: Tests that run against the FPM isisd: Tests that run against ISISD ldpd: Tests that run against LDPD mgmtd: Tests that run against MGMTD diff --git a/tools/checkpatch.pl b/tools/checkpatch.pl index ecae0e92a1..f52f1a1616 100755 --- a/tools/checkpatch.pl +++ b/tools/checkpatch.pl @@ -4656,19 +4656,6 @@ sub process { $herecurr); } -# check for sizeof(foo)/sizeof(foo[0]) that could be ARRAY_SIZE(foo) - if ($line =~ m@\bsizeof\s*\(\s*($Lval)\s*\)@) { - my $array = $1; - if ($line =~ m@\b(sizeof\s*\(\s*\Q$array\E\s*\)\s*/\s*sizeof\s*\(\s*\Q$array\E\s*\[\s*0\s*\]\s*\))@) { - my $array_div = $1; - if (WARN("ARRAY_SIZE", - "Prefer ARRAY_SIZE($array)\n" . $herecurr) && - $fix) { - $fixed[$fixlinenr] =~ s/\Q$array_div\E/ARRAY_SIZE($array)/; - } - } - } - # check for function declarations without arguments like "int foo()" if ($line =~ /(\b$Type\s*$Ident)\s*\(\s*\)/) { if (ERROR("FUNCTION_WITHOUT_ARGS", diff --git a/yang/frr-zebra.yang b/yang/frr-zebra.yang index c338a23af0..f1a69068c3 100644 --- a/yang/frr-zebra.yang +++ b/yang/frr-zebra.yang @@ -145,12 +145,6 @@ module frr-zebra { "Zebra interface type bond."; } - identity zif-bond-slave { - base zebra-interface-type; - description - "Zebra interface type bond slave."; - } - identity zif-macvlan { base zebra-interface-type; description @@ -2733,6 +2727,12 @@ module frr-zebra { description "The VNI multicast group for BUM traffic."; } + + leaf bond { + type frr-interface:interface-ref; + description + "The bond interface this interface belongs to."; + } } } } diff --git a/zebra/.gitignore b/zebra/.gitignore index 41a86e7d75..f10240db43 100644 --- a/zebra/.gitignore +++ b/zebra/.gitignore @@ -1,3 +1,4 @@ zebra zebra.conf client +fpm_listener diff --git a/zebra/dplane_fpm_nl.c b/zebra/dplane_fpm_nl.c index 7ae1b2a090..245b799a91 100644 --- a/zebra/dplane_fpm_nl.c +++ b/zebra/dplane_fpm_nl.c @@ -30,6 +30,7 @@ #include "lib/network.h" #include "lib/ns.h" #include "lib/frr_pthread.h" +#include "lib/termtable.h" #include "zebra/debug.h" #include "zebra/interface.h" #include "zebra/zebra_dplane.h" @@ -44,6 +45,8 @@ #include "zebra/debug.h" #include "fpm/fpm.h" +#include "zebra/dplane_fpm_nl_clippy.c" + #define SOUTHBOUND_DEFAULT_ADDR INADDR_LOOPBACK /* @@ -322,6 +325,74 @@ DEFUN(fpm_reset_counters, fpm_reset_counters_cmd, return CMD_SUCCESS; } +DEFPY(fpm_show_status, + fpm_show_status_cmd, + "show fpm status [json]$json", + SHOW_STR FPM_STR "FPM status\n" JSON_STR) +{ + struct json_object *j; + bool connected; + uint16_t port; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + char buf[BUFSIZ]; + + connected = gfnc->socket > 0 ? true : false; + + switch (gfnc->addr.ss_family) { + case AF_INET: + sin = (struct sockaddr_in *)&gfnc->addr; + snprintfrr(buf, sizeof(buf), "%pI4", &sin->sin_addr); + port = ntohs(sin->sin_port); + break; + case AF_INET6: + sin6 = (struct sockaddr_in6 *)&gfnc->addr; + snprintfrr(buf, sizeof(buf), "%pI6", &sin6->sin6_addr); + port = ntohs(sin6->sin6_port); + break; + default: + strlcpy(buf, "Unknown", sizeof(buf)); + port = FPM_DEFAULT_PORT; + break; + } + + if (json) { + j = json_object_new_object(); + + json_object_boolean_add(j, "connected", connected); + json_object_boolean_add(j, "useNHG", gfnc->use_nhg); + json_object_boolean_add(j, "useRouteReplace", + gfnc->use_route_replace); + json_object_boolean_add(j, "disabled", gfnc->disabled); + json_object_string_add(j, "address", buf); + json_object_int_add(j, "port", port); + + vty_json(vty, j); + } else { + struct ttable *table = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + char *out; + + ttable_rowseps(table, 0, BOTTOM, true, '-'); + ttable_add_row(table, "Address to connect to|%s", buf); + ttable_add_row(table, "Port|%u", port); + ttable_add_row(table, "Connected|%s", connected ? "Yes" : "No"); + ttable_add_row(table, "Use Nexthop Groups|%s", + gfnc->use_nhg ? "Yes" : "No"); + ttable_add_row(table, "Use Route Replace Semantics|%s", + gfnc->use_route_replace ? "Yes" : "No"); + ttable_add_row(table, "Disabled|%s", + gfnc->disabled ? "Yes" : "No"); + + out = ttable_dump(table, "\n"); + vty_out(vty, "%s\n", out); + XFREE(MTYPE_TMP, out); + + ttable_del(table); + } + + return CMD_SUCCESS; +} + DEFUN(fpm_show_counters, fpm_show_counters_cmd, "show fpm counters", SHOW_STR @@ -1394,8 +1465,14 @@ static void fpm_process_queue(struct event *t) uint64_t processed_contexts = 0; while (true) { + size_t writeable_amount; + + frr_with_mutex (&fnc->obuf_mutex) { + writeable_amount = STREAM_WRITEABLE(fnc->obuf); + } + /* No space available yet. */ - if (STREAM_WRITEABLE(fnc->obuf) < NL_PKT_BUF_SIZE) { + if (writeable_amount < NL_PKT_BUF_SIZE) { no_bufs = true; break; } @@ -1665,6 +1742,7 @@ static int fpm_nl_new(struct event_loop *tm) zlog_debug("%s register status: %d", prov_name, rv); install_node(&fpm_node); + install_element(ENABLE_NODE, &fpm_show_status_cmd); install_element(ENABLE_NODE, &fpm_show_counters_cmd); install_element(ENABLE_NODE, &fpm_show_counters_json_cmd); install_element(ENABLE_NODE, &fpm_reset_counters_cmd); diff --git a/zebra/fpm_listener.c b/zebra/fpm_listener.c new file mode 100644 index 0000000000..d50e40e9d8 --- /dev/null +++ b/zebra/fpm_listener.c @@ -0,0 +1,632 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") + * + * 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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC 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. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#ifdef GNU_LINUX +#include <stdint.h> +#include <memory.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <errno.h> +#include <assert.h> +#include <err.h> +#include <sys/types.h> + +#include <linux/netlink.h> +#include <linux/rtnetlink.h> + +#include "fpm/fpm.h" +#include "lib/libfrr.h" + +struct glob { + int server_sock; + int sock; +}; + +struct glob glob_space; +struct glob *glob = &glob_space; + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +/* + * get_print_buf + */ +static char * +get_print_buf(size_t *buf_len) +{ + static char print_bufs[16][128]; + static int counter; + + counter++; + if (counter >= 16) + counter = 0; + + *buf_len = 128; + return &print_bufs[counter][0]; +} + +/* + * create_listen_sock + */ +static int create_listen_sock(int port, int *sock_p) +{ + int sock; + struct sockaddr_in addr; + int reuse; + + sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock < 0) { + fprintf(stderr, "Failed to create socket: %s\n", strerror(errno)); + return 0; + } + + reuse = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < + 0) { + fprintf(stderr, "Failed to set reuse addr option: %s\n", + strerror(errno)); + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + fprintf(stderr, "Failed to bind to port %d: %s\n", port, strerror(errno)); + close(sock); + return 0; + } + + if (listen(sock, 5)) { + fprintf(stderr, "Failed to listen on socket: %s\n", strerror(errno)); + close(sock); + return 0; + } + + *sock_p = sock; + return 1; +} + +/* + * accept_conn + */ +static int accept_conn(int listen_sock) +{ + int sock; + struct sockaddr_in client_addr = { 0 }; + unsigned int client_len; + + while (1) { + char buf[120]; + + fprintf(stdout, "Waiting for client connection...\n"); + client_len = sizeof(client_addr); + sock = accept(listen_sock, (struct sockaddr *)&client_addr, + &client_len); + + if (sock >= 0) { + fprintf(stdout, "Accepted client %s\n", + inet_ntop(AF_INET, &client_addr.sin_addr, buf, sizeof(buf))); + return sock; + } + fprintf(stderr, "Failed to accept socket: %s\n", strerror(errno)); + } +} + +/* + * read_fpm_msg + */ +static fpm_msg_hdr_t * +read_fpm_msg(char *buf, size_t buf_len) +{ + char *cur, *end; + long need_len, bytes_read, have_len; + fpm_msg_hdr_t *hdr; + int reading_full_msg; + + end = buf + buf_len; + cur = buf; + hdr = (fpm_msg_hdr_t *)buf; + + while (1) { + reading_full_msg = 0; + + have_len = cur - buf; + + if (have_len < (long)FPM_MSG_HDR_LEN) { + need_len = FPM_MSG_HDR_LEN - have_len; + } else { + need_len = fpm_msg_len(hdr) - have_len; + assert(need_len >= 0 && need_len <= (end - cur)); + + if (!need_len) + return hdr; + + reading_full_msg = 1; + } + + bytes_read = read(glob->sock, cur, need_len); + + if (bytes_read == 0) { + fprintf(stdout, + "Socket closed as that read returned 0\n"); + return NULL; + } + + if (bytes_read < 0) { + fprintf(stderr, "Error reading from socket: %s\n", + strerror(errno)); + return NULL; + } + + cur += bytes_read; + + if (bytes_read < need_len) { + fprintf(stderr, + "Read %lu bytes but expected to read %lu bytes instead\n", + bytes_read, need_len); + return NULL; + } + + if (reading_full_msg) + return hdr; + + if (!fpm_msg_ok(hdr, buf_len)) { + assert(0); + fprintf(stderr, "Malformed fpm message\n"); + return NULL; + } + } +} + +/* + * netlink_msg_type_to_s + */ +static const char * +netlink_msg_type_to_s(uint16_t type) +{ + switch (type) { + + case RTM_NEWROUTE: + return "New route"; + + case RTM_DELROUTE: + return "Del route"; + + default: + return "Unknown"; + } +} + +/* + * netlink_prot_to_s + */ +static const char * +netlink_prot_to_s(unsigned char prot) +{ + switch (prot) { + + case RTPROT_KERNEL: + return "Kernel"; + + case RTPROT_BOOT: + return "Boot"; + + case RTPROT_STATIC: + return "Static"; + + case RTPROT_ZEBRA: + return "Zebra"; + + case RTPROT_DHCP: + return "Dhcp"; + + default: + return "Unknown"; + } +} + +#define MAX_NHS 16 + +struct netlink_nh { + struct rtattr *gateway; + int if_index; +}; + +struct netlink_msg_ctx { + struct nlmsghdr *hdr; + + /* + * Stuff pertaining to route messages. + */ + struct rtmsg *rtmsg; + struct rtattr *rtattrs[RTA_MAX + 1]; + + /* + * Nexthops. + */ + struct netlink_nh nhs[MAX_NHS]; + unsigned long num_nhs; + + struct rtattr *dest; + struct rtattr *src; + int *metric; + + const char *err_msg; +}; + +/* + * netlink_msg_ctx_init + */ +static inline void netlink_msg_ctx_init(struct netlink_msg_ctx *ctx) +{ + memset(ctx, 0, sizeof(*ctx)); +} + +/* + * netlink_msg_ctx_set_err + */ +static inline void netlink_msg_ctx_set_err(struct netlink_msg_ctx *ctx, + const char *err_msg) +{ + if (ctx->err_msg) + return; + + ctx->err_msg = err_msg; +} + +/* + * parse_rtattrs_ + */ +static int parse_rtattrs_(struct rtattr *rta, size_t len, struct rtattr **rtas, + int num_rtas, const char **err_msg) +{ + memset(rtas, 0, num_rtas * sizeof(rtas[0])); + + for (; len > 0; rta = RTA_NEXT(rta, len)) { + if (!RTA_OK(rta, len)) { + *err_msg = "Malformed rta"; + return 0; + } + + if (rta->rta_type >= num_rtas) { + warn("Unknown rtattr type %d", rta->rta_type); + continue; + } + + rtas[rta->rta_type] = rta; + } + + return 1; +} + +/* + * parse_rtattrs + */ +static int parse_rtattrs(struct netlink_msg_ctx *ctx, struct rtattr *rta, + size_t len) +{ + const char *err_msg; + + err_msg = NULL; + + if (!parse_rtattrs_(rta, len, ctx->rtattrs, ARRAY_SIZE(ctx->rtattrs), + &err_msg)) { + netlink_msg_ctx_set_err(ctx, err_msg); + return 0; + } + + return 1; +} + +/* + * netlink_msg_ctx_add_nh + */ +static int netlink_msg_ctx_add_nh(struct netlink_msg_ctx *ctx, int if_index, + struct rtattr *gateway) +{ + struct netlink_nh *nh; + + if (ctx->num_nhs + 1 >= ARRAY_SIZE(ctx->nhs)) { + warn("Too many next hops"); + return 0; + } + nh = &ctx->nhs[ctx->num_nhs]; + ctx->num_nhs++; + + nh->gateway = gateway; + nh->if_index = if_index; + return 1; +} + +/* + * parse_multipath_attr + */ +static int parse_multipath_attr(struct netlink_msg_ctx *ctx, + struct rtattr *mpath_rtattr) +{ + size_t len; + struct rtnexthop *rtnh; + struct rtattr *rtattrs[RTA_MAX + 1]; + struct rtattr *gateway; + const char *err_msg; + + rtnh = RTA_DATA(mpath_rtattr); + len = RTA_PAYLOAD(mpath_rtattr); + + for (; len > 0; + len -= NLMSG_ALIGN(rtnh->rtnh_len), rtnh = RTNH_NEXT(rtnh)) { + + if (!RTNH_OK(rtnh, len)) { + netlink_msg_ctx_set_err(ctx, "Malformed nh"); + return 0; + } + + if (rtnh->rtnh_len <= sizeof(*rtnh)) { + netlink_msg_ctx_set_err(ctx, "NH len too small"); + return 0; + } + + /* + * Parse attributes included in the nexthop. + */ + err_msg = NULL; + if (!parse_rtattrs_(RTNH_DATA(rtnh), + rtnh->rtnh_len - sizeof(*rtnh), rtattrs, + ARRAY_SIZE(rtattrs), &err_msg)) { + netlink_msg_ctx_set_err(ctx, err_msg); + return 0; + } + + gateway = rtattrs[RTA_GATEWAY]; + netlink_msg_ctx_add_nh(ctx, rtnh->rtnh_ifindex, gateway); + } + + return 1; +} + +/* + * parse_route_msg + */ +static int parse_route_msg(struct netlink_msg_ctx *ctx) +{ + int len; + struct rtattr **rtattrs, *rtattr, *gateway, *oif; + int if_index; + + ctx->rtmsg = NLMSG_DATA(ctx->hdr); + + len = ctx->hdr->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg)); + if (len < 0) { + netlink_msg_ctx_set_err(ctx, "Bad message length"); + return 0; + } + + if (!parse_rtattrs(ctx, RTM_RTA(ctx->rtmsg), len)) + return 0; + + rtattrs = ctx->rtattrs; + + ctx->dest = rtattrs[RTA_DST]; + ctx->src = rtattrs[RTA_PREFSRC]; + + rtattr = rtattrs[RTA_PRIORITY]; + if (rtattr) + ctx->metric = (int *)RTA_DATA(rtattr); + + gateway = rtattrs[RTA_GATEWAY]; + oif = rtattrs[RTA_OIF]; + if (gateway || oif) { + if_index = 0; + if (oif) + if_index = *((int *)RTA_DATA(oif)); + + netlink_msg_ctx_add_nh(ctx, if_index, gateway); + } + + rtattr = rtattrs[RTA_MULTIPATH]; + if (rtattr) + parse_multipath_attr(ctx, rtattr); + + return 1; +} + +/* + * addr_to_s + */ +static const char * +addr_to_s(unsigned char family, void *addr) +{ + size_t buf_len; + char *buf; + + buf = get_print_buf(&buf_len); + + return inet_ntop(family, addr, buf, buf_len); +} + +/* + * netlink_msg_ctx_print + */ +static int netlink_msg_ctx_snprint(struct netlink_msg_ctx *ctx, char *buf, + size_t buf_len) +{ + struct nlmsghdr *hdr; + struct rtmsg *rtmsg; + struct netlink_nh *nh; + char *cur, *end; + unsigned long i; + + hdr = ctx->hdr; + rtmsg = ctx->rtmsg; + + cur = buf; + end = buf + buf_len; + + cur += snprintf(cur, end - cur, "%s %s/%d, Prot: %s", + netlink_msg_type_to_s(hdr->nlmsg_type), + addr_to_s(rtmsg->rtm_family, RTA_DATA(ctx->dest)), + rtmsg->rtm_dst_len, + netlink_prot_to_s(rtmsg->rtm_protocol)); + + if (ctx->metric) + cur += snprintf(cur, end - cur, ", Metric: %d", *ctx->metric); + + for (i = 0; i < ctx->num_nhs; i++) { + cur += snprintf(cur, end - cur, "\n "); + nh = &ctx->nhs[i]; + + if (nh->gateway) { + cur += snprintf(cur, end - cur, " %s", + addr_to_s(rtmsg->rtm_family, + RTA_DATA(nh->gateway))); + } + + if (nh->if_index) { + cur += snprintf(cur, end - cur, " via interface %d", + nh->if_index); + } + } + + return cur - buf; +} + +/* + * print_netlink_msg_ctx + */ +static void print_netlink_msg_ctx(struct netlink_msg_ctx *ctx) +{ + char buf[1024]; + + netlink_msg_ctx_snprint(ctx, buf, sizeof(buf)); + printf("%s\n", buf); +} + +/* + * parse_netlink_msg + */ +static void +parse_netlink_msg(char *buf, size_t buf_len) +{ + struct netlink_msg_ctx ctx_space, *ctx; + struct nlmsghdr *hdr; + unsigned int len; + + ctx = &ctx_space; + + hdr = (struct nlmsghdr *)buf; + len = buf_len; + for (; NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) { + + netlink_msg_ctx_init(ctx); + ctx->hdr = (struct nlmsghdr *)buf; + + switch (hdr->nlmsg_type) { + + case RTM_DELROUTE: + case RTM_NEWROUTE: + + parse_route_msg(ctx); + if (ctx->err_msg) { + fprintf(stderr, + "Error parsing route message: %s\n", + ctx->err_msg); + } + + print_netlink_msg_ctx(ctx); + break; + + default: + fprintf(stdout, "Ignoring unknown netlink message - Type: %d\n", + hdr->nlmsg_type); + } + } +} + +/* + * process_fpm_msg + */ +static void process_fpm_msg(fpm_msg_hdr_t *hdr) +{ + fprintf(stdout, "FPM message - Type: %d, Length %d\n", hdr->msg_type, + ntohs(hdr->msg_len)); + + if (hdr->msg_type != FPM_MSG_TYPE_NETLINK) { + fprintf(stderr, "Unknown fpm message type %u\n", hdr->msg_type); + return; + } + + parse_netlink_msg(fpm_msg_data(hdr), fpm_msg_data_len(hdr)); +} + +/* + * fpm_serve + */ +static void fpm_serve(void) +{ + char buf[FPM_MAX_MSG_LEN * 4]; + fpm_msg_hdr_t *hdr; + + while (1) { + + hdr = read_fpm_msg(buf, sizeof(buf)); + if (!hdr) + return; + + process_fpm_msg(hdr); + } +} + +int main(int argc, char **argv) +{ + pid_t daemon; + int d; + + d = getopt(argc, argv, "d"); + if (d == 'd') { + daemon = fork(); + + if (daemon) + exit(0); + } + + memset(glob, 0, sizeof(*glob)); + + if (!create_listen_sock(FPM_DEFAULT_PORT, &glob->server_sock)) + exit(1); + + /* + * Server forever. + */ + while (1) { + glob->sock = accept_conn(glob->server_sock); + fpm_serve(); + fprintf(stdout, "Done serving client"); + } +} +#else + +int main(int argc, char **argv) +{ + fprintf(stderr, "This program only works on linux"); + exit(-1); +} +#endif diff --git a/zebra/subdir.am b/zebra/subdir.am index d9c8d9045e..f767447366 100644 --- a/zebra/subdir.am +++ b/zebra/subdir.am @@ -19,6 +19,12 @@ if LINUX module_LTLIBRARIES += zebra/zebra_cumulus_mlag.la endif +#if FPM_LISTENER +sbin_PROGRAMS += zebra/fpm_listener +zebra_fpm_listener_SOURCES = zebra/fpm_listener.c +zebra_fpm_listener_LDADD = lib/libfrr.la +#endf + # Dataplane sample plugin if DEV_BUILD module_LTLIBRARIES += zebra/dplane_sample_plugin.la @@ -116,6 +122,7 @@ zebra_zebra_SOURCES = \ clippy_scan += \ zebra/debug.c \ + zebra/dplane_fpm_nl.c \ zebra/interface.c \ zebra/rtadv.c \ zebra/zebra_mlag_vty.c \ diff --git a/zebra/zebra_nb.c b/zebra/zebra_nb.c index e1ca5ec19b..eee9323082 100644 --- a/zebra/zebra_nb.c +++ b/zebra/zebra_nb.c @@ -803,6 +803,12 @@ const struct frr_yang_module_info frr_zebra_info = { } }, { + .xpath = "/frr-interface:lib/interface/frr-zebra:zebra/state/bond", + .cbs = { + .get_elem = lib_interface_zebra_state_bond_get_elem, + } + }, + { .xpath = "/frr-vrf:lib/vrf/frr-zebra:zebra/router-id", .cbs = { .modify = lib_vrf_zebra_router_id_modify, diff --git a/zebra/zebra_nb.h b/zebra/zebra_nb.h index d7cf5f4040..b40ed68229 100644 --- a/zebra/zebra_nb.h +++ b/zebra/zebra_nb.h @@ -285,6 +285,7 @@ struct yang_data *lib_interface_zebra_state_remote_vtep_get_elem( struct nb_cb_get_elem_args *args); struct yang_data *lib_interface_zebra_state_mcast_group_get_elem( struct nb_cb_get_elem_args *args); +struct yang_data *lib_interface_zebra_state_bond_get_elem(struct nb_cb_get_elem_args *args); int lib_vrf_zebra_router_id_modify(struct nb_cb_modify_args *args); int lib_vrf_zebra_router_id_destroy(struct nb_cb_destroy_args *args); int lib_vrf_zebra_ipv6_router_id_modify(struct nb_cb_modify_args *args); diff --git a/zebra/zebra_nb_state.c b/zebra/zebra_nb_state.c index e702080afd..46492f023a 100644 --- a/zebra/zebra_nb_state.c +++ b/zebra/zebra_nb_state.c @@ -49,8 +49,46 @@ lib_interface_zebra_state_down_count_get_elem(struct nb_cb_get_elem_args *args) struct yang_data * lib_interface_zebra_state_zif_type_get_elem(struct nb_cb_get_elem_args *args) { - /* TODO: implement me. */ - return NULL; + const struct interface *ifp = args->list_entry; + struct zebra_if *zebra_if; + const char *type = NULL; + + zebra_if = ifp->info; + + switch (zebra_if->zif_type) { + case ZEBRA_IF_OTHER: + type = "frr-zebra:zif-other"; + break; + case ZEBRA_IF_VXLAN: + type = "frr-zebra:zif-vxlan"; + break; + case ZEBRA_IF_VRF: + type = "frr-zebra:zif-vrf"; + break; + case ZEBRA_IF_BRIDGE: + type = "frr-zebra:zif-bridge"; + break; + case ZEBRA_IF_VLAN: + type = "frr-zebra:zif-vlan"; + break; + case ZEBRA_IF_MACVLAN: + type = "frr-zebra:zif-macvlan"; + break; + case ZEBRA_IF_VETH: + type = "frr-zebra:zif-veth"; + break; + case ZEBRA_IF_BOND: + type = "frr-zebra:zif-bond"; + break; + case ZEBRA_IF_GRE: + type = "frr-zebra:zif-gre"; + break; + } + + if (!type) + return NULL; + + return yang_data_new_string(args->xpath, type); } /* @@ -145,6 +183,28 @@ lib_interface_zebra_state_mcast_group_get_elem(struct nb_cb_get_elem_args *args) return yang_data_new_ipv4(args->xpath, &vni->mcast_grp); } +/* + * XPath: /frr-interface:lib/interface/frr-zebra:zebra/state/bond + */ +struct yang_data * +lib_interface_zebra_state_bond_get_elem(struct nb_cb_get_elem_args *args) +{ + const struct interface *ifp = args->list_entry; + struct zebra_if *zebra_if; + struct interface *bond; + + if (!IS_ZEBRA_IF_BOND_SLAVE(ifp)) + return NULL; + + zebra_if = ifp->info; + bond = zebra_if->bondslave_info.bond_if; + + if (!bond) + return NULL; + + return yang_data_new_string(args->xpath, bond->name); +} + const void *lib_vrf_zebra_ribs_rib_get_next(struct nb_cb_get_next_args *args) { struct vrf *vrf = (struct vrf *)args->parent_list_entry; |
