summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bgpd/bgp_bmp.c36
-rw-r--r--bgpd/bgp_debug.c2
-rw-r--r--bgpd/bgp_evpn.c83
-rw-r--r--bgpd/bgp_evpn.h3
-rw-r--r--bgpd/bgp_fsm.c2
-rw-r--r--bgpd/bgp_fsm.h3
-rw-r--r--bgpd/bgp_nht.c19
-rw-r--r--bgpd/bgp_packet.c3
-rw-r--r--bgpd/bgp_route.c86
-rw-r--r--bgpd/bgpd.h1
-rw-r--r--configure.ac7
-rw-r--r--debian/control8
-rw-r--r--debian/frr-test-tools.install1
-rw-r--r--doc/developer/checkpatch.rst9
-rw-r--r--doc/user/basic.rst4
-rw-r--r--doc/user/installation.rst4
-rw-r--r--doc/user/zebra.rst3
-rw-r--r--lib/darr.c7
-rw-r--r--lib/if.c11
-rw-r--r--lib/northbound.c2
-rw-r--r--lib/vty.c10
-rw-r--r--mgmtd/mgmt_txn.c10
-rw-r--r--mgmtd/mgmt_vty.c6
-rw-r--r--ospf6d/ospf6_neighbor.c2
-rw-r--r--ospfd/ospf_asbr.c3
-rw-r--r--redhat/frr.spec.in1
-rw-r--r--ripd/rip_cli.c6
-rw-r--r--ripd/rip_nb.h1
-rw-r--r--ripngd/ripng_cli.c6
-rw-r--r--ripngd/ripng_nb.h1
-rw-r--r--tests/topotests/bgp_bmp/r1/bgpd.conf28
-rw-r--r--tests/topotests/bgp_bmp/r1/zebra.conf2
-rw-r--r--tests/topotests/bgp_bmp/r2/bgpd.conf27
-rw-r--r--tests/topotests/bgp_bmp/test_bgp_bmp.py73
-rw-r--r--tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgp.evpn.route.prefix.after.json1
-rw-r--r--tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgp.evpn.route.prefix.before.json1
-rw-r--r--tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgpd.conf13
-rw-r--r--tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/zebra.conf6
-rwxr-xr-xtests/topotests/bgp_evpn_vxlan_svd_topo1/test_bgp_evpn_vxlan_svd.py72
-rwxr-xr-xtests/topotests/bgp_peer_type_multipath_relax/test_bgp_peer-type_multipath-relax.py1
-rw-r--r--tests/topotests/fpm_testing_topo1/r1/fpm_counters.json8
-rw-r--r--tests/topotests/fpm_testing_topo1/r1/fpm_stub.conf0
-rw-r--r--tests/topotests/fpm_testing_topo1/r1/routes_summ.json27
-rw-r--r--tests/topotests/fpm_testing_topo1/r1/routes_summ_removed.json20
-rw-r--r--tests/topotests/fpm_testing_topo1/r1/sharpd.conf0
-rw-r--r--tests/topotests/fpm_testing_topo1/r1/zebra.conf5
-rw-r--r--tests/topotests/fpm_testing_topo1/test_fpm_topo1.py134
-rw-r--r--tests/topotests/lib/bmp_collector/bmp.py173
-rwxr-xr-xtests/topotests/lib/bmp_collector/bmpserver4
-rw-r--r--tests/topotests/lib/topogen.py25
-rw-r--r--tests/topotests/lib/topotest.py28
-rw-r--r--tests/topotests/mgmt_oper/simple-results/result-intf-eth0-exact.json3
-rw-r--r--tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json3
-rw-r--r--tests/topotests/pytest.ini1
-rwxr-xr-xtools/checkpatch.pl13
-rw-r--r--yang/frr-zebra.yang12
-rw-r--r--zebra/.gitignore1
-rw-r--r--zebra/dplane_fpm_nl.c80
-rw-r--r--zebra/fpm_listener.c632
-rw-r--r--zebra/subdir.am7
-rw-r--r--zebra/zebra_nb.c6
-rw-r--r--zebra/zebra_nb.h1
-rw-r--r--zebra/zebra_nb_state.c64
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);
diff --git a/lib/if.c b/lib/if.c
index a344c2b865..a8ceac7243 100644
--- a/lib/if.c
+++ b/lib/if.c
@@ -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
diff --git a/lib/vty.c b/lib/vty.c
index 1c9cff478d..912c893556 100644
--- a/lib/vty.c
+++ b/lib/vty.c
@@ -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;