summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bgpd/bgp_bmp.c801
-rw-r--r--bgpd/bgp_bmp.h20
-rw-r--r--bgpd/bgp_trace.h3
-rw-r--r--doc/developer/building-frr-for-ubuntu2004.rst161
-rw-r--r--doc/developer/building-frr-for-ubuntu2204.rst162
-rw-r--r--doc/developer/building-frr-for-ubuntu2404.rst4
-rw-r--r--doc/developer/building-frr-for-ubuntu2x04.rst162
-rw-r--r--doc/user/bmp.rst5
-rw-r--r--docker/ubuntu-ci/Dockerfile6
-rw-r--r--docker/ubuntu22-ci/README.md2
-rw-r--r--docker/ubuntu24-ci/README.md66
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step1.json34
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step2.json34
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step1.json36
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step2.json36
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step1.json36
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step2.json36
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step1.json28
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step2.json34
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-post-policy-step1.json30
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-pre-policy-step1.json30
-rw-r--r--tests/topotests/bgp_bmp/r1import/frr.conf73
-rw-r--r--tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-update-step1.json21
-rw-r--r--tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-withdraw-step1.json6
-rw-r--r--tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-update-step1.json27
-rw-r--r--tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-withdraw-step1.json6
-rw-r--r--tests/topotests/bgp_bmp/r3/frr.conf18
-rw-r--r--tests/topotests/bgp_bmp/test_bgp_bmp_3.py567
-rw-r--r--tests/topotests/munet/base.py28
-rw-r--r--tests/topotests/munet/munet-schema.json22
-rw-r--r--tests/topotests/munet/mutest/userapi.py2
-rw-r--r--tests/topotests/munet/native.py218
-rw-r--r--tests/topotests/munet/testing/util.py97
-rw-r--r--tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py16
-rwxr-xr-xtools/frr-reload.py6
-rw-r--r--zebra/zebra_nhg.c4
36 files changed, 2289 insertions, 548 deletions
diff --git a/bgpd/bgp_bmp.c b/bgpd/bgp_bmp.c
index 036bece359..680eaae47a 100644
--- a/bgpd/bgp_bmp.c
+++ b/bgpd/bgp_bmp.c
@@ -50,6 +50,12 @@ static struct bmp_bgp_peer *bmp_bgp_peer_find(uint64_t peerid);
static struct bmp_bgp_peer *bmp_bgp_peer_get(struct peer *peer);
static void bmp_active_disconnected(struct bmp_active *ba);
static void bmp_active_put(struct bmp_active *ba);
+static int bmp_route_update_bgpbmp(struct bmp_targets *bt, afi_t afi, safi_t safi,
+ struct bgp_dest *bn, struct bgp_path_info *old_route,
+ struct bgp_path_info *new_route);
+static void bmp_send_all_bgp(struct peer *peer, bool down);
+static struct bmp_imported_bgp *bmp_imported_bgp_find(struct bmp_targets *bt, char *name);
+static void bmp_stats_per_instance(struct bgp *bgp, struct bmp_targets *bt);
DEFINE_MGROUP(BMP, "BMP (BGP Monitoring Protocol)");
@@ -64,6 +70,7 @@ DEFINE_MTYPE_STATIC(BMP, BMP, "BMP instance state");
DEFINE_MTYPE_STATIC(BMP, BMP_MIRRORQ, "BMP route mirroring buffer");
DEFINE_MTYPE_STATIC(BMP, BMP_PEER, "BMP per BGP peer data");
DEFINE_MTYPE_STATIC(BMP, BMP_OPEN, "BMP stored BGP OPEN message");
+DEFINE_MTYPE_STATIC(BMP, BMP_IMPORTED_BGP, "BMP imported BGP instance");
DEFINE_QOBJ_TYPE(bmp_targets);
@@ -141,6 +148,17 @@ static int bmp_targets_cmp(const struct bmp_targets *a,
DECLARE_SORTLIST_UNIQ(bmp_targets, struct bmp_targets, bti, bmp_targets_cmp);
+static int bmp_imported_bgps_cmp(const struct bmp_imported_bgp *a, const struct bmp_imported_bgp *b)
+{
+ if (a->name == NULL && b->name == NULL)
+ return 0;
+ if (a->name == NULL || b->name == NULL)
+ return 1;
+ return strcmp(a->name, b->name);
+}
+
+DECLARE_SORTLIST_UNIQ(bmp_imported_bgps, struct bmp_imported_bgp, bib, bmp_imported_bgps_cmp);
+
DECLARE_LIST(bmp_session, struct bmp, bsi);
DECLARE_DLIST(bmp_qlist, struct bmp_queue_entry, bli);
@@ -228,6 +246,7 @@ static struct bmp *bmp_new(struct bmp_targets *bt, int bmp_sock)
new->targets = bt;
new->socket = bmp_sock;
new->syncafi = AFI_MAX;
+ new->sync_bgp = NULL;
FOREACH_AFI_SAFI (afi, safi) {
new->afistate[afi][safi] = bt->afimon[afi][safi]
@@ -399,14 +418,17 @@ static void bmp_put_info_tlv(struct stream *s, uint16_t type,
/* put the vrf table name of the bgp instance bmp is bound to in a tlv on the
* stream
*/
-static void bmp_put_vrftablename_info_tlv(struct stream *s, struct bgp *bgp)
+static void bmp_put_vrftablename_info_tlv(struct stream *s, struct peer *peer)
{
const char *vrftablename = "global";
+ struct vrf *vrf;
#define BMP_INFO_TYPE_VRFTABLENAME 3
- if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT)
- vrftablename = bgp->name;
+ if (peer->bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT) {
+ vrf = vrf_lookup_by_id(peer->bgp->vrf_id);
+ vrftablename = vrf ? vrf->name : NULL;
+ }
if (vrftablename != NULL)
bmp_put_info_tlv(s, BMP_INFO_TYPE_VRFTABLENAME, vrftablename);
@@ -590,63 +612,123 @@ static struct stream *bmp_peerstate(struct peer *peer, bool down)
}
if (is_locrib)
- bmp_put_vrftablename_info_tlv(s, peer->bgp);
+ bmp_put_vrftablename_info_tlv(s, peer);
len = stream_get_endp(s);
stream_putl_at(s, BMP_LENGTH_POS, len); /* message length is set. */
return s;
}
-
-static int bmp_send_peerup(struct bmp *bmp)
+static int bmp_send_peerup_per_instance(struct bmp *bmp, struct bgp *bgp)
{
struct peer *peer;
struct listnode *node;
struct stream *s;
/* Walk down all peers */
- for (ALL_LIST_ELEMENTS_RO(bmp->targets->bgp->peer, node, peer)) {
+ for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) {
s = bmp_peerstate(peer, false);
if (s) {
pullwr_write_stream(bmp->pullwr, s);
stream_free(s);
}
}
+ return 0;
+}
+
+static int bmp_send_peerup(struct bmp *bmp)
+{
+ struct bmp_imported_bgp *bib;
+ struct bgp *bgp;
+
+ bmp_send_peerup_per_instance(bmp, bmp->targets->bgp);
+ frr_each (bmp_imported_bgps, &bmp->targets->imported_bgps, bib) {
+ bgp = bgp_lookup_by_name(bib->name);
+ if (bgp)
+ bmp_send_peerup_per_instance(bmp, bgp);
+ }
return 0;
}
-static int bmp_send_peerup_vrf(struct bmp *bmp)
+static void bmp_send_peerup_vrf_per_instance(struct bmp *bmp, enum bmp_vrf_state *vrf_state,
+ struct bgp *bgp)
{
- struct bmp_bgp *bmpbgp = bmp->targets->bmpbgp;
struct stream *s;
/* send unconditionally because state may has been set before the
* session was up. and in this case the peer up has not been sent.
*/
- bmp_bgp_update_vrf_status(bmpbgp, vrf_state_unknown);
+ bmp_bgp_update_vrf_status(vrf_state, bgp, vrf_state_unknown);
- s = bmp_peerstate(bmpbgp->bgp->peer_self, bmpbgp->vrf_state == vrf_state_down);
+ s = bmp_peerstate(bgp->peer_self, *vrf_state == vrf_state_down);
if (s) {
pullwr_write_stream(bmp->pullwr, s);
stream_free(s);
}
+}
+
+static int bmp_send_peerup_vrf(struct bmp *bmp)
+{
+ struct bgp *bgp;
+ struct bmp_imported_bgp *bib;
+ struct bmp_bgp *bmpbgp = bmp->targets->bmpbgp;
+ struct bmp_targets *bt;
+
+ bmp_send_peerup_vrf_per_instance(bmp, &bmpbgp->vrf_state, bmpbgp->bgp);
+
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ frr_each (bmp_imported_bgps, &bt->imported_bgps, bib) {
+ bgp = bgp_lookup_by_name(bib->name);
+ if (!bgp)
+ continue;
+ bmp_send_peerup_vrf_per_instance(bmp, &bib->vrf_state, bgp);
+ }
+ }
return 0;
}
+static void bmp_send_bt(struct bmp_targets *bt, struct stream *s)
+{
+ struct bmp *bmp;
+
+ frr_each (bmp_session, &bt->sessions, bmp)
+ pullwr_write_stream(bmp->pullwr, s);
+}
+
+static void bmp_send_bt_safe(struct bmp_targets *bt, struct stream *s)
+{
+ if (!s)
+ return;
+
+ bmp_send_bt(bt, s);
+
+ stream_free(s);
+}
+
+static void bmp_send_peerdown_vrf_per_instance(struct bmp_targets *bt, struct bgp *bgp)
+{
+ struct stream *s;
+
+ s = bmp_peerstate(bgp->peer_self, true);
+ if (!s)
+ return;
+ bmp_send_bt(bt, s);
+ stream_free(s);
+}
+
/* send a stream to all bmp sessions configured in a bgp instance */
/* XXX: kludge - filling the pullwr's buffer */
static void bmp_send_all(struct bmp_bgp *bmpbgp, struct stream *s)
{
struct bmp_targets *bt;
- struct bmp *bmp;
if (!s)
return;
frr_each(bmp_targets, &bmpbgp->targets, bt)
- frr_each(bmp_session, &bt->sessions, bmp)
- pullwr_write_stream(bmp->pullwr, s);
+ bmp_send_bt(bt, s);
+
stream_free(s);
}
@@ -725,11 +807,13 @@ static void bmp_mirror_cull(struct bmp_bgp *bmpbgp)
static int bmp_mirror_packet(struct peer *peer, uint8_t type, bgp_size_t size,
struct stream *packet)
{
- struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp);
+ struct bmp_bgp *bmpbgp;
struct timeval tv;
- struct bmp_mirrorq *qitem;
+ struct bmp_mirrorq *qitem = NULL;
struct bmp_targets *bt;
struct bmp *bmp;
+ struct bgp *bgp_vrf;
+ struct listnode *node;
frrtrace(3, frr_bgp, bmp_mirror_packet, peer, type, packet);
@@ -745,8 +829,6 @@ static int bmp_mirror_packet(struct peer *peer, uint8_t type, bgp_size_t size,
memcpy(bbpeer->open_rx, packet->data, size);
}
- if (!bmpbgp)
- return 0;
qitem = XCALLOC(MTYPE_BMP_MIRRORQ, sizeof(*qitem) + size);
qitem->peerid = peer->qobj_node.nid;
@@ -754,27 +836,41 @@ static int bmp_mirror_packet(struct peer *peer, uint8_t type, bgp_size_t size,
qitem->len = size;
memcpy(qitem->data, packet->data, size);
- frr_each(bmp_targets, &bmpbgp->targets, bt) {
- if (!bt->mirror)
+ for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) {
+ bmpbgp = bmp_bgp_find(bgp_vrf);
+ if (!bmpbgp)
continue;
- frr_each(bmp_session, &bt->sessions, bmp) {
- qitem->refcount++;
- if (!bmp->mirrorpos)
- bmp->mirrorpos = qitem;
- pullwr_bump(bmp->pullwr);
- }
- }
- if (qitem->refcount == 0)
- XFREE(MTYPE_BMP_MIRRORQ, qitem);
- else {
- bmpbgp->mirror_qsize += sizeof(*qitem) + size;
- bmp_mirrorq_add_tail(&bmpbgp->mirrorq, qitem);
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ if (!bt->mirror)
+ continue;
+
+ if (bgp_vrf != peer->bgp && !bmp_imported_bgp_find(bt, peer->bgp->name))
+ continue;
+
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ if (!qitem) {
+ qitem = XCALLOC(MTYPE_BMP_MIRRORQ, sizeof(*qitem) + size);
+ qitem->peerid = peer->qobj_node.nid;
+ qitem->tv = tv;
+ qitem->len = size;
+ memcpy(qitem->data, packet->data, size);
+ }
+
+ qitem->refcount++;
+ if (!bmp->mirrorpos)
+ bmp->mirrorpos = qitem;
+ pullwr_bump(bmp->pullwr);
+ }
+ bmpbgp->mirror_qsize += sizeof(*qitem) + size;
+ bmp_mirrorq_add_tail(&bmpbgp->mirrorq, qitem);
- bmp_mirror_cull(bmpbgp);
+ bmp_mirror_cull(bmpbgp);
- bmpbgp->mirror_qsizemax = MAX(bmpbgp->mirror_qsizemax,
- bmpbgp->mirror_qsize);
+ bmpbgp->mirror_qsizemax = MAX(bmpbgp->mirror_qsizemax, bmpbgp->mirror_qsize);
+ }
}
+ if (qitem && qitem->refcount == 0)
+ XFREE(MTYPE_BMP_MIRRORQ, qitem);
return 0;
}
@@ -847,8 +943,7 @@ static bool bmp_wrmirror(struct bmp *bmp, struct pullwr *pullwr)
s = stream_new(BGP_MAX_PACKET_SIZE);
bmp_common_hdr(s, BMP_VERSION_3, BMP_TYPE_ROUTE_MIRRORING);
- bmp_per_peer_hdr(s, bmp->targets->bgp, peer, 0, peer_type_flag, peer_distinguisher,
- &bmq->tv);
+ bmp_per_peer_hdr(s, peer->bgp, peer, 0, peer_type_flag, peer_distinguisher, &bmq->tv);
/* BMP Mirror TLV. */
stream_putw(s, BMP_MIRROR_TLV_TYPE_BGP_MESSAGE);
@@ -887,14 +982,10 @@ static int bmp_outgoing_packet(struct peer *peer, uint8_t type, bgp_size_t size,
static int bmp_peer_status_changed(struct peer *peer)
{
- struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp);
struct bmp_bgp_peer *bbpeer, *bbdopp;
frrtrace(1, frr_bgp, bmp_peer_status_changed, peer);
- if (!bmpbgp)
- return 0;
-
if (peer->connection->status == Deleted) {
bbpeer = bmp_bgp_peer_find(peer->qobj_node.nid);
if (bbpeer) {
@@ -929,20 +1020,16 @@ static int bmp_peer_status_changed(struct peer *peer)
}
}
- bmp_send_all_safe(bmpbgp, bmp_peerstate(peer, false));
+ bmp_send_all_bgp(peer, false);
return 0;
}
static int bmp_peer_backward(struct peer *peer)
{
- struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp);
struct bmp_bgp_peer *bbpeer;
frrtrace(1, frr_bgp, bmp_peer_backward_transition, peer);
- if (!bmpbgp)
- return 0;
-
bbpeer = bmp_bgp_peer_find(peer->qobj_node.nid);
if (bbpeer) {
XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx);
@@ -951,12 +1038,12 @@ static int bmp_peer_backward(struct peer *peer)
bbpeer->open_rx_len = 0;
}
- bmp_send_all_safe(bmpbgp, bmp_peerstate(peer, true));
+ bmp_send_all_bgp(peer, true);
return 0;
}
-static void bmp_eor(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t flags,
- uint8_t peer_type_flag)
+static void bmp_eor(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t flags, uint8_t peer_type_flag,
+ struct bgp *bgp)
{
struct peer *peer;
struct listnode *node;
@@ -964,7 +1051,7 @@ static void bmp_eor(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t flags,
iana_afi_t pkt_afi = IANA_AFI_IPV4;
iana_safi_t pkt_safi = IANA_SAFI_UNICAST;
- frrtrace(4, frr_bgp, bmp_eor, afi, safi, flags, peer_type_flag);
+ frrtrace(5, frr_bgp, bmp_eor, afi, safi, flags, peer_type_flag, bgp);
s = stream_new(BGP_MAX_PACKET_SIZE);
@@ -992,7 +1079,7 @@ static void bmp_eor(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t flags,
bgp_packet_set_size(s);
- for (ALL_LIST_ELEMENTS_RO(bmp->targets->bgp->peer, node, peer)) {
+ for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) {
if (!peer->afc_nego[afi][safi])
continue;
@@ -1009,8 +1096,7 @@ static void bmp_eor(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t flags,
bmp_common_hdr(s2, BMP_VERSION_3,
BMP_TYPE_ROUTE_MONITORING);
- bmp_per_peer_hdr(s2, bmp->targets->bgp, peer, flags,
- peer_type_flag, peer_distinguisher, NULL);
+ bmp_per_peer_hdr(s2, bgp, peer, flags, peer_type_flag, peer_distinguisher, NULL);
stream_putl_at(s2, BMP_LENGTH_POS,
stream_get_endp(s) + stream_get_endp(s2));
@@ -1141,8 +1227,7 @@ static void bmp_monitor(struct bmp *bmp, struct peer *peer, uint8_t flags,
hdr = stream_new(BGP_MAX_PACKET_SIZE);
bmp_common_hdr(hdr, BMP_VERSION_3, BMP_TYPE_ROUTE_MONITORING);
- bmp_per_peer_hdr(hdr, bmp->targets->bgp, peer, flags, peer_type_flag,
- peer_distinguisher,
+ bmp_per_peer_hdr(hdr, peer->bgp, peer, flags, peer_type_flag, peer_distinguisher,
uptime == (time_t)(-1L) ? NULL : &uptime_real);
stream_putl_at(hdr, BMP_LENGTH_POS,
@@ -1155,6 +1240,93 @@ static void bmp_monitor(struct bmp *bmp, struct peer *peer, uint8_t flags,
stream_free(msg);
}
+static struct bgp *bmp_get_next_bgp(struct bmp_targets *bt, struct bgp *bgp, afi_t afi, safi_t safi)
+{
+ struct bmp_imported_bgp *bib;
+ struct bgp *bgp_inst;
+ bool get_first = false;
+
+ if (bgp == NULL && bt->bgp_request_sync[afi][safi])
+ return bt->bgp;
+ if (bgp == NULL)
+ get_first = true;
+ frr_each (bmp_imported_bgps, &bt->imported_bgps, bib) {
+ bgp_inst = bgp_lookup_by_name(bib->name);
+ if (get_first && bgp_inst && bib->bgp_request_sync[afi][safi])
+ return bgp_inst;
+ if (bgp_inst == bgp)
+ get_first = true;
+ }
+ return NULL;
+}
+
+static void bmp_update_syncro(struct bmp *bmp, afi_t afi, safi_t safi, struct bgp *bgp)
+{
+ struct bmp_imported_bgp *bib;
+
+ if (bmp->syncafi == afi && bmp->syncsafi == safi) {
+ bmp->syncafi = AFI_MAX;
+ bmp->syncsafi = SAFI_MAX;
+ bmp->sync_bgp = NULL;
+ }
+
+ if (!bmp->targets->afimon[afi][safi]) {
+ bmp->afistate[afi][safi] = BMP_AFI_INACTIVE;
+ return;
+ }
+
+ bmp->afistate[afi][safi] = BMP_AFI_NEEDSYNC;
+
+ if (bgp == NULL || bmp->targets->bgp == bgp)
+ bmp->targets->bgp_request_sync[afi][safi] = true;
+
+ frr_each (bmp_imported_bgps, &bmp->targets->imported_bgps, bib) {
+ if (bgp != NULL && bgp_lookup_by_name(bib->name) != bgp)
+ continue;
+ bib->bgp_request_sync[afi][safi] = true;
+ }
+}
+
+static void bmp_update_syncro_set(struct bmp *bmp, afi_t afi, safi_t safi, struct bgp *bgp,
+ enum bmp_afi_state state)
+{
+ struct bmp_imported_bgp *bib;
+
+ bmp->afistate[afi][safi] = state;
+ bmp->syncafi = AFI_MAX;
+ bmp->syncsafi = SAFI_MAX;
+ if (bgp == NULL || bmp->targets->bgp == bmp->sync_bgp)
+ bmp->targets->bgp_request_sync[afi][safi] = false;
+
+ frr_each (bmp_imported_bgps, &bmp->targets->imported_bgps, bib) {
+ if (bgp == NULL || bgp_lookup_by_name(bib->name) != bmp->sync_bgp)
+ continue;
+ bib->bgp_request_sync[afi][safi] = false;
+ }
+}
+
+static void bmp_eor_afi_safi(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t peer_type_flag)
+{
+ struct bgp *sync_bgp;
+
+ zlog_info("bmp[%s] %s %s table completed (EoR) (BGP %s)", bmp->remote, afi2str(afi),
+ safi2str(safi), bmp->sync_bgp->name_pretty);
+
+ bmp_eor(bmp, afi, safi, BMP_PEER_FLAG_L, peer_type_flag, bmp->sync_bgp);
+ bmp_eor(bmp, afi, safi, 0, peer_type_flag, bmp->sync_bgp);
+ bmp_eor(bmp, afi, safi, 0, BMP_PEER_TYPE_LOC_RIB_INSTANCE, bmp->sync_bgp);
+
+ sync_bgp = bmp_get_next_bgp(bmp->targets, bmp->sync_bgp, afi, safi);
+ if (sync_bgp) {
+ memset(&bmp->syncpos, 0, sizeof(bmp->syncpos));
+ bmp->syncpos.family = afi2family(afi);
+ bmp->syncrdpos = NULL;
+ bmp->syncpeerid = 0;
+ } else
+ bmp_update_syncro_set(bmp, afi, safi, bmp->sync_bgp, BMP_AFI_LIVE);
+ bmp->sync_bgp = sync_bgp;
+}
+
static bool bmp_wrsync(struct bmp *bmp, struct pullwr *pullwr)
{
uint8_t bpi_num_labels, adjin_num_labels;
@@ -1175,10 +1347,13 @@ static bool bmp_wrsync(struct bmp *bmp, struct pullwr *pullwr)
memset(&bmp->syncpos, 0, sizeof(bmp->syncpos));
bmp->syncpos.family = afi2family(afi);
bmp->syncrdpos = NULL;
- zlog_info("bmp[%s] %s %s sending table",
- bmp->remote,
- afi2str(bmp->syncafi),
- safi2str(bmp->syncsafi));
+ bmp->sync_bgp = bmp_get_next_bgp(bmp->targets, NULL, afi, safi);
+ if (bmp->sync_bgp == NULL)
+ /* all BGP instances already synced*/
+ return true;
+ zlog_info("bmp[%s] %s %s sending table (BGP %s)", bmp->remote,
+ afi2str(bmp->syncafi), safi2str(bmp->syncsafi),
+ bmp->sync_bgp->name_pretty);
/* break does not work here, 2 loops... */
goto afibreak;
}
@@ -1192,18 +1367,22 @@ afibreak:
if (!bmp->targets->afimon[afi][safi]) {
/* shouldn't happen */
- bmp->afistate[afi][safi] = BMP_AFI_INACTIVE;
- bmp->syncafi = AFI_MAX;
- bmp->syncsafi = SAFI_MAX;
+ bmp_update_syncro_set(bmp, afi, safi, bmp->sync_bgp, BMP_AFI_INACTIVE);
+ bmp->sync_bgp = NULL;
return true;
}
+ if (bmp->sync_bgp == NULL) {
+ bmp->sync_bgp = bmp_get_next_bgp(bmp->targets, NULL, afi, safi);
+ if (bmp->sync_bgp == NULL)
+ return true;
+ }
- struct bgp_table *table = bmp->targets->bgp->rib[afi][safi];
+ struct bgp_table *table = bmp->sync_bgp->rib[afi][safi];
struct bgp_dest *bn = NULL;
struct bgp_path_info *bpi = NULL, *bpiter;
struct bgp_adj_in *adjin = NULL, *adjiter;
- peer_type_flag = bmp_get_peer_type_vrf(bmp->targets->bgp->vrf_id);
+ peer_type_flag = bmp_get_peer_type_vrf(bmp->sync_bgp->vrf_id);
if ((afi == AFI_L2VPN && safi == SAFI_EVPN) ||
(safi == SAFI_MPLS_VPN)) {
@@ -1255,19 +1434,9 @@ afibreak:
return true;
}
eor:
- zlog_info("bmp[%s] %s %s table completed (EoR)",
- bmp->remote, afi2str(afi),
- safi2str(safi));
-
- bmp_eor(bmp, afi, safi, BMP_PEER_FLAG_L, peer_type_flag);
- bmp_eor(bmp, afi, safi, 0, peer_type_flag);
- bmp_eor(bmp, afi, safi, 0,
- BMP_PEER_TYPE_LOC_RIB_INSTANCE);
-
- bmp->afistate[afi][safi] = BMP_AFI_LIVE;
- bmp->syncafi = AFI_MAX;
- bmp->syncsafi = SAFI_MAX;
- return true;
+ bmp_eor_afi_safi(bmp, afi, safi,
+ peer_type_flag);
+ return true;
}
bmp->syncpeerid = 0;
prefix_copy(&bmp->syncpos, bgp_dest_get_prefix(bn));
@@ -1446,7 +1615,7 @@ static bool bmp_wrqueue_locrib(struct bmp *bmp, struct pullwr *pullwr)
*/
goto out;
}
- if (peer != bmp->targets->bgp->peer_self && !peer_established(peer->connection)) {
+ if (peer != peer->bgp->peer_self && !peer_established(peer->connection)) {
/* peer is neither self, nor established
*/
goto out;
@@ -1457,8 +1626,7 @@ static bool bmp_wrqueue_locrib(struct bmp *bmp, struct pullwr *pullwr)
struct prefix_rd *prd = is_vpn ? &bqe->rd : NULL;
- bn = bgp_safi_node_lookup(bmp->targets->bgp->rib[afi][safi], safi,
- &bqe->p, prd);
+ bn = bgp_safi_node_lookup(peer->bgp->rib[afi][safi], safi, &bqe->p, prd);
struct bgp_path_info *bpi;
@@ -1534,8 +1702,7 @@ static bool bmp_wrqueue(struct bmp *bmp, struct pullwr *pullwr)
(bqe->safi == SAFI_MPLS_VPN);
struct prefix_rd *prd = is_vpn ? &bqe->rd : NULL;
- bn = bgp_safi_node_lookup(bmp->targets->bgp->rib[afi][safi], safi,
- &bqe->p, prd);
+ bn = bgp_safi_node_lookup(peer->bgp->rib[afi][safi], safi, &bqe->p, prd);
peer_type_flag = bmp_get_peer_type(peer);
@@ -1585,11 +1752,16 @@ out:
static void bmp_wrfill(struct bmp *bmp, struct pullwr *pullwr)
{
+ afi_t afi;
+ safi_t safi;
+
switch(bmp->state) {
case BMP_PeerUp:
bmp_send_peerup_vrf(bmp);
bmp_send_peerup(bmp);
bmp->state = BMP_Run;
+ FOREACH_AFI_SAFI (afi, safi)
+ bmp_update_syncro(bmp, afi, safi, NULL);
break;
case BMP_Run:
@@ -1667,9 +1839,12 @@ bmp_process_one(struct bmp_targets *bt, struct bmp_qhash_head *updhash,
static int bmp_process(struct bgp *bgp, afi_t afi, safi_t safi,
struct bgp_dest *bn, struct peer *peer, bool withdraw)
{
- struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp);
+ struct bmp_bgp *bmpbgp;
struct bmp_targets *bt;
struct bmp *bmp;
+ struct bgp *bgp_vrf;
+ struct listnode *node;
+ struct bmp_queue_entry *last_item;
if (frrtrace_enabled(frr_bgp, bmp_process)) {
char pfxprint[PREFIX2STR_BUFFER];
@@ -1679,31 +1854,34 @@ static int bmp_process(struct bgp *bgp, afi_t afi, safi_t safi,
withdraw);
}
- if (!bmpbgp)
- return 0;
-
- frr_each(bmp_targets, &bmpbgp->targets, bt) {
- /* check if any monitoring is enabled (ignoring loc-rib since it
- * uses another hook & queue
- */
- if (!CHECK_FLAG(bt->afimon[afi][safi], ~BMP_MON_LOC_RIB))
+ for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) {
+ bmpbgp = bmp_bgp_find(bgp_vrf);
+ if (!bmpbgp)
continue;
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ /* check if any monitoring is enabled (ignoring loc-rib since it
+ * uses another hook & queue
+ */
+ if (!CHECK_FLAG(bt->afimon[afi][safi], ~BMP_MON_LOC_RIB))
+ continue;
- struct bmp_queue_entry *last_item =
- bmp_process_one(bt, &bt->updhash, &bt->updlist, bgp,
- afi, safi, bn, peer);
+ if (bgp_vrf != peer->bgp && !bmp_imported_bgp_find(bt, peer->bgp->name))
+ continue;
- /* if bmp_process_one returns NULL
- * we don't have anything to do next
- */
- if (!last_item)
- continue;
+ last_item = bmp_process_one(bt, &bt->updhash, &bt->updlist, bgp, afi, safi,
+ bn, peer);
+ /* if bmp_process_one returns NULL
+ * we don't have anything to do next
+ */
+ if (!last_item)
+ continue;
- frr_each(bmp_session, &bt->sessions, bmp) {
- if (!bmp->queuepos)
- bmp->queuepos = last_item;
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ if (!bmp->queuepos)
+ bmp->queuepos = last_item;
- pullwr_bump(bmp->pullwr);
+ pullwr_bump(bmp->pullwr);
+ }
}
}
return 0;
@@ -1750,6 +1928,22 @@ static void bmp_stat_put_u64(struct stream *s, size_t *cnt, uint16_t type,
static void bmp_stats(struct event *thread)
{
struct bmp_targets *bt = EVENT_ARG(thread);
+ struct bmp_imported_bgp *bib;
+ struct bgp *bgp;
+
+ if (bt->stat_msec)
+ event_add_timer_msec(bm->master, bmp_stats, bt, bt->stat_msec, &bt->t_stats);
+
+ bmp_stats_per_instance(bt->bgp, bt);
+ frr_each (bmp_imported_bgps, &bt->imported_bgps, bib) {
+ bgp = bgp_lookup_by_name(bib->name);
+ if (bgp)
+ bmp_stats_per_instance(bgp, bt);
+ }
+}
+
+static void bmp_stats_per_instance(struct bgp *bgp, struct bmp_targets *bt)
+{
struct stream *s;
struct peer *peer;
struct listnode *node;
@@ -1757,14 +1951,10 @@ static void bmp_stats(struct event *thread)
uint8_t peer_type_flag;
uint64_t peer_distinguisher = 0;
- if (bt->stat_msec)
- event_add_timer_msec(bm->master, bmp_stats, bt, bt->stat_msec,
- &bt->t_stats);
-
gettimeofday(&tv, NULL);
/* Walk down all peers */
- for (ALL_LIST_ELEMENTS_RO(bt->bgp->peer, node, peer)) {
+ for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) {
size_t count = 0, count_pos, len;
if (!peer_established(peer->connection))
@@ -1980,6 +2170,7 @@ static struct bmp_bgp *bmp_bgp_get(struct bgp *bgp)
bmpbgp->bgp = bgp;
bmpbgp->vrf_state = vrf_state_unknown;
bmpbgp->mirror_qsizelimit = ~0UL;
+ bmp_targets_init(&bmpbgp->targets);
bmp_mirrorq_init(&bmpbgp->mirrorq);
bmp_bgph_add(&bmp_bgph, bmpbgp);
@@ -2054,30 +2245,29 @@ static void bmp_bgp_peer_vrf(struct bmp_bgp_peer *bbpeer, struct bgp *bgp)
*
* returns true if state has changed
*/
-bool bmp_bgp_update_vrf_status(struct bmp_bgp *bmpbgp, enum bmp_vrf_state force)
+bool bmp_bgp_update_vrf_status(enum bmp_vrf_state *vrf_state, struct bgp *bgp,
+ enum bmp_vrf_state force)
{
enum bmp_vrf_state old_state;
struct bmp_bgp_peer *bbpeer;
struct peer *peer;
struct vrf *vrf;
- struct bgp *bgp;
bool changed;
- if (!bmpbgp || !bmpbgp->bgp)
+ if (!vrf_state || !bgp)
return false;
- bgp = bmpbgp->bgp;
- old_state = bmpbgp->vrf_state;
+ old_state = *vrf_state;
vrf = bgp_vrf_lookup_by_instance_type(bgp);
- bmpbgp->vrf_state = force != vrf_state_unknown ? force
- : vrf_is_enabled(vrf) ? vrf_state_up
- : vrf_state_down;
+ *vrf_state = force != vrf_state_unknown ? force
+ : vrf_is_enabled(vrf) ? vrf_state_up
+ : vrf_state_down;
- changed = old_state != bmpbgp->vrf_state;
+ changed = old_state != *vrf_state;
if (changed) {
- peer = bmpbgp->bgp->peer_self;
- if (bmpbgp->vrf_state == vrf_state_up) {
+ peer = bgp->peer_self;
+ if (*vrf_state == vrf_state_up) {
bbpeer = bmp_bgp_peer_get(peer);
bmp_bgp_peer_vrf(bbpeer, bgp);
} else {
@@ -2085,6 +2275,7 @@ bool bmp_bgp_update_vrf_status(struct bmp_bgp *bmpbgp, enum bmp_vrf_state force)
if (bbpeer) {
XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx);
XFREE(MTYPE_BMP_OPEN, bbpeer->open_rx);
+ XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx);
bmp_peerh_del(&bmp_peerh, bbpeer);
XFREE(MTYPE_BMP_PEER, bbpeer);
}
@@ -2129,6 +2320,8 @@ static struct bmp_targets *bmp_targets_find1(struct bgp *bgp, const char *name)
static struct bmp_targets *bmp_targets_get(struct bgp *bgp, const char *name)
{
struct bmp_targets *bt;
+ afi_t afi;
+ safi_t safi;
bt = bmp_targets_find1(bgp, name);
if (bt)
@@ -2139,6 +2332,8 @@ static struct bmp_targets *bmp_targets_get(struct bgp *bgp, const char *name)
bt->bgp = bgp;
bt->bmpbgp = bmp_bgp_get(bgp);
bt->stats_send_experimental = true;
+ FOREACH_AFI_SAFI (afi, safi)
+ bt->bgp_request_sync[afi][safi] = false;
bmp_session_init(&bt->sessions);
bmp_qhash_init(&bt->updhash);
bmp_qlist_init(&bt->updlist);
@@ -2146,16 +2341,25 @@ static struct bmp_targets *bmp_targets_get(struct bgp *bgp, const char *name)
bmp_qlist_init(&bt->locupdlist);
bmp_actives_init(&bt->actives);
bmp_listeners_init(&bt->listeners);
+ bmp_imported_bgps_init(&bt->imported_bgps);
QOBJ_REG(bt, bmp_targets);
bmp_targets_add(&bt->bmpbgp->targets, bt);
return bt;
}
+static void bmp_imported_bgp_free(struct bmp_imported_bgp *bib)
+{
+ if (bib->name)
+ XFREE(MTYPE_BMP_IMPORTED_BGP, bib->name);
+ XFREE(MTYPE_BMP_IMPORTED_BGP, bib);
+}
+
static void bmp_targets_put(struct bmp_targets *bt)
{
struct bmp *bmp;
struct bmp_active *ba;
+ struct bmp_imported_bgp *bib;
EVENT_OFF(bt->t_stats);
@@ -2170,6 +2374,10 @@ static void bmp_targets_put(struct bmp_targets *bt)
bmp_targets_del(&bt->bmpbgp->targets, bt);
QOBJ_UNREG(bt);
+ frr_each_safe (bmp_imported_bgps, &bt->imported_bgps, bib)
+ bmp_imported_bgp_free(bib);
+
+ bmp_imported_bgps_fini(&bt->imported_bgps);
bmp_listeners_fini(&bt->listeners);
bmp_actives_fini(&bt->actives);
bmp_qhash_fini(&bt->updhash);
@@ -2214,6 +2422,71 @@ static struct bmp_listener *bmp_listener_get(struct bmp_targets *bt,
return bl;
}
+static struct bmp_imported_bgp *bmp_imported_bgp_find(struct bmp_targets *bt, char *name)
+{
+ struct bmp_imported_bgp dummy;
+
+ dummy.name = name;
+ return bmp_imported_bgps_find(&bt->imported_bgps, &dummy);
+}
+
+static void bmp_send_all_bgp(struct peer *peer, bool down)
+{
+ struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp);
+ struct bgp *bgp_vrf;
+ struct listnode *node;
+ struct stream *s = NULL;
+ struct bmp_targets *bt;
+
+ s = bmp_peerstate(peer, down);
+ if (!s)
+ return;
+
+ if (bmpbgp) {
+ frr_each (bmp_targets, &bmpbgp->targets, bt)
+ bmp_send_bt(bt, s);
+ }
+ for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) {
+ bmpbgp = bmp_bgp_find(bgp_vrf);
+ if (!bmpbgp)
+ continue;
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ if (bgp_vrf != peer->bgp && !bmp_imported_bgp_find(bt, peer->bgp->name))
+ continue;
+ bmp_send_bt(bt, s);
+ }
+ }
+ stream_free(s);
+}
+
+static void bmp_imported_bgp_put(struct bmp_targets *bt, struct bmp_imported_bgp *bib)
+{
+ bmp_imported_bgps_del(&bt->imported_bgps, bib);
+ bmp_imported_bgp_free(bib);
+}
+
+static struct bmp_imported_bgp *bmp_imported_bgp_get(struct bmp_targets *bt, char *name)
+{
+ struct bmp_imported_bgp *bib = bmp_imported_bgp_find(bt, name);
+ afi_t afi;
+ safi_t safi;
+
+ if (bib)
+ return bib;
+
+ bib = XCALLOC(MTYPE_BMP_IMPORTED_BGP, sizeof(*bib));
+ if (name)
+ bib->name = XSTRDUP(MTYPE_BMP_IMPORTED_BGP, name);
+ bib->vrf_state = vrf_state_unknown;
+ FOREACH_AFI_SAFI (afi, safi)
+ bib->bgp_request_sync[afi][safi] = false;
+
+ bib->targets = bt;
+ bmp_imported_bgps_add(&bt->imported_bgps, bib);
+
+ return bib;
+}
+
static void bmp_listener_start(struct bmp_listener *bl)
{
int sock, ret;
@@ -2574,6 +2847,63 @@ DEFPY(no_bmp_targets_main,
return CMD_SUCCESS;
}
+DEFPY(bmp_import_vrf,
+ bmp_import_vrf_cmd,
+ "[no] bmp import-vrf-view VRFNAME$vrfname",
+ NO_STR
+ BMP_STR
+ "Import BMP information from another VRF\n"
+ "Specify the VRF or view instance name\n")
+{
+ VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt);
+ struct bmp_imported_bgp *bib;
+ struct bgp *bgp;
+ struct bmp *bmp;
+ afi_t afi;
+ safi_t safi;
+
+ if (!bt->bgp) {
+ vty_out(vty, "%% BMP target, BGP instance not found\n");
+ return CMD_WARNING;
+ }
+ if ((bt->bgp->name == NULL && vrfname == NULL) ||
+ (bt->bgp->name && vrfname && strmatch(vrfname, bt->bgp->name))) {
+ vty_out(vty, "%% BMP target, can not import our own BGP instance\n");
+ return CMD_WARNING;
+ }
+ if (no) {
+ bib = bmp_imported_bgp_find(bt, (char *)vrfname);
+ if (!bib) {
+ vty_out(vty, "%% BMP imported BGP instance not found\n");
+ return CMD_WARNING;
+ }
+ bgp = bgp_lookup_by_name(bib->name);
+ if (!bgp)
+ return CMD_WARNING;
+ bmp_send_peerdown_vrf_per_instance(bt, bgp);
+ bmp_imported_bgp_put(bt, bib);
+ return CMD_SUCCESS;
+ }
+ bib = bmp_imported_bgp_find(bt, (char *)vrfname);
+ if (bib)
+ return CMD_SUCCESS;
+
+ bib = bmp_imported_bgp_get(bt, (char *)vrfname);
+ bgp = bgp_lookup_by_name(bib->name);
+ if (!bgp)
+ return CMD_SUCCESS;
+
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ if (bmp->state != BMP_PeerUp && bmp->state != BMP_Run)
+ continue;
+ bmp_send_peerup_per_instance(bmp, bgp);
+ bmp_send_peerup_vrf_per_instance(bmp, &bib->vrf_state, bgp);
+ FOREACH_AFI_SAFI (afi, safi)
+ bmp_update_syncro(bmp, afi, safi, bgp);
+ }
+ return CMD_SUCCESS;
+}
+
DEFPY(bmp_listener_main,
bmp_listener_cmd,
"bmp listener <X:X::X:X|A.B.C.D> port (1-65535)",
@@ -2776,19 +3106,8 @@ DEFPY(bmp_monitor_cfg, bmp_monitor_cmd,
if (prev == bt->afimon[afi][safi])
return CMD_SUCCESS;
- frr_each (bmp_session, &bt->sessions, bmp) {
- if (bmp->syncafi == afi && bmp->syncsafi == safi) {
- bmp->syncafi = AFI_MAX;
- bmp->syncsafi = SAFI_MAX;
- }
-
- if (!bt->afimon[afi][safi]) {
- bmp->afistate[afi][safi] = BMP_AFI_INACTIVE;
- continue;
- }
-
- bmp->afistate[afi][safi] = BMP_AFI_NEEDSYNC;
- }
+ frr_each (bmp_session, &bt->sessions, bmp)
+ bmp_update_syncro(bmp, afi, safi, NULL);
return CMD_SUCCESS;
}
@@ -3011,6 +3330,7 @@ static int bmp_config_write(struct bgp *bgp, struct vty *vty)
struct bmp_targets *bt;
struct bmp_listener *bl;
struct bmp_active *ba;
+ struct bmp_imported_bgp *bib;
afi_t afi;
safi_t safi;
@@ -3053,6 +3373,11 @@ static int bmp_config_write(struct bgp *bgp, struct vty *vty)
vty_out(vty, " bmp monitor %s %s loc-rib\n",
afi2str_lower(afi), safi2str(safi));
}
+
+ frr_each (bmp_imported_bgps, &bt->imported_bgps, bib)
+ vty_out(vty, " bmp import-vrf-view %s\n",
+ bib->name ? bib->name : VRF_DEFAULT_NAME);
+
frr_each (bmp_listeners, &bt->listeners, bl)
vty_out(vty, " bmp listener %pSU port %d\n", &bl->addr, bl->port);
@@ -3090,6 +3415,7 @@ static int bgp_bmp_init(struct event_loop *tm)
install_element(BMP_NODE, &bmp_stats_cmd);
install_element(BMP_NODE, &bmp_monitor_cmd);
install_element(BMP_NODE, &bmp_mirror_cmd);
+ install_element(BMP_NODE, &bmp_import_vrf_cmd);
install_element(BGP_NODE, &bmp_mirror_limit_cmd);
install_element(BGP_NODE, &no_bmp_mirror_limit_cmd);
@@ -3105,11 +3431,14 @@ static int bmp_route_update(struct bgp *bgp, afi_t afi, safi_t safi,
struct bgp_path_info *old_route,
struct bgp_path_info *new_route)
{
- bool is_locribmon_enabled = false;
bool is_withdraw = old_route && !new_route;
struct bgp_path_info *updated_route =
is_withdraw ? old_route : new_route;
-
+ struct bmp_bgp *bmpbgp;
+ struct bmp_targets *bt;
+ int ret = 0;
+ struct bgp *bgp_vrf;
+ struct listnode *node;
/* this should never happen */
if (!updated_route) {
@@ -3117,23 +3446,30 @@ static int bmp_route_update(struct bgp *bgp, afi_t afi, safi_t safi,
return 0;
}
- struct bmp_bgp *bmpbgp = bmp_bgp_find(bgp);
- struct peer *peer = updated_route->peer;
- struct bmp_targets *bt;
- struct bmp *bmp;
-
- if (!bmpbgp)
- return 0;
-
- frr_each (bmp_targets, &bmpbgp->targets, bt) {
- if (CHECK_FLAG(bt->afimon[afi][safi], BMP_MON_LOC_RIB)) {
- is_locribmon_enabled = true;
- break;
+ for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) {
+ bmpbgp = bmp_bgp_find(bgp_vrf);
+ if (!bmpbgp)
+ continue;
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ if (!CHECK_FLAG(bt->afimon[afi][safi], BMP_MON_LOC_RIB))
+ continue;
+ if (bgp_vrf != bgp && !bmp_imported_bgp_find(bt, bgp->name))
+ continue;
+ ret = bmp_route_update_bgpbmp(bt, afi, safi, bn, old_route, new_route);
}
}
+ return ret;
+}
- if (!is_locribmon_enabled)
- return 0;
+static int bmp_route_update_bgpbmp(struct bmp_targets *bt, afi_t afi, safi_t safi,
+ struct bgp_dest *bn, struct bgp_path_info *old_route,
+ struct bgp_path_info *new_route)
+{
+ bool is_withdraw = old_route && !new_route;
+ struct bgp_path_info *updated_route = is_withdraw ? old_route : new_route;
+ struct peer *peer = updated_route->peer;
+ struct bmp *bmp;
+ struct bmp_queue_entry *last_item;
/* route is not installed in locrib anymore and rib uptime was saved */
if (old_route && old_route->extra)
@@ -3147,26 +3483,20 @@ static int bmp_route_update(struct bgp *bgp, afi_t afi, safi_t safi,
bgp_path_info_extra_get(new_route)->bgp_rib_uptime =
monotime(NULL);
- frr_each (bmp_targets, &bmpbgp->targets, bt) {
- if (CHECK_FLAG(bt->afimon[afi][safi], BMP_MON_LOC_RIB)) {
-
- struct bmp_queue_entry *last_item = bmp_process_one(
- bt, &bt->locupdhash, &bt->locupdlist, bgp, afi,
- safi, bn, peer);
+ last_item = bmp_process_one(bt, &bt->locupdhash, &bt->locupdlist, bt->bgp, afi, safi, bn,
+ peer);
- /* if bmp_process_one returns NULL
- * we don't have anything to do next
- */
- if (!last_item)
- continue;
+ /* if bmp_process_one returns NULL
+ * we don't have anything to do next
+ */
+ if (!last_item)
+ return 0;
- frr_each (bmp_session, &bt->sessions, bmp) {
- if (!bmp->locrib_queuepos)
- bmp->locrib_queuepos = last_item;
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ if (!bmp->locrib_queuepos)
+ bmp->locrib_queuepos = last_item;
- pullwr_bump(bmp->pullwr);
- };
- }
+ pullwr_bump(bmp->pullwr);
};
return 0;
@@ -3179,24 +3509,76 @@ static int bgp_bmp_early_fini(void)
return 0;
}
+static int bmp_bgp_attribute_updated_instance(struct bmp_targets *bt, enum bmp_vrf_state *vrf_state,
+ struct bgp *bgp, bool withdraw, struct stream *s)
+{
+ bmp_bgp_update_vrf_status(vrf_state, bgp, vrf_state_unknown);
+ if (*vrf_state == vrf_state_down)
+ /* do not send peer events, router id will not be enough to set state to up
+ */
+ return 0;
+
+ /* vrf_state is up: trigger a peer event
+ */
+ bmp_send_bt(bt, s);
+ return 1;
+}
+
/* called when the routerid of an instance changes */
static int bmp_bgp_attribute_updated(struct bgp *bgp, bool withdraw)
{
struct bmp_bgp *bmpbgp = bmp_bgp_find(bgp);
+ struct bgp *bgp_vrf;
+ struct bmp_targets *bt;
+ struct listnode *node;
+ struct bmp_imported_bgp *bib;
+ int ret = 0;
+ struct stream *s = bmp_peerstate(bgp->peer_self, withdraw);
+ struct bmp *bmp;
+ afi_t afi;
+ safi_t safi;
- if (!bmpbgp)
+ if (!s)
return 0;
- bmp_bgp_update_vrf_status(bmpbgp, vrf_state_unknown);
-
- if (bmpbgp->vrf_state == vrf_state_down)
- /* do not send peer events, router id will not be enough to set state to up
- */
- return 0;
+ if (bmpbgp) {
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ ret = bmp_bgp_attribute_updated_instance(bt, &bmpbgp->vrf_state, bgp,
+ withdraw, s);
+ if (withdraw)
+ continue;
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ bmp_send_peerup_per_instance(bmp, bgp);
+ FOREACH_AFI_SAFI (afi, safi)
+ bmp_update_syncro(bmp, afi, safi, bgp);
+ }
+ }
+ }
- /* vrf_state is up: trigger a peer event
- */
- bmp_send_all_safe(bmpbgp, bmp_peerstate(bgp->peer_self, withdraw));
+ for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) {
+ if (bgp == bgp_vrf)
+ continue;
+ bmpbgp = bmp_bgp_find(bgp_vrf);
+ if (!bmpbgp)
+ continue;
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ frr_each (bmp_imported_bgps, &bt->imported_bgps, bib) {
+ if (bgp_lookup_by_name(bib->name) != bgp)
+ continue;
+ ret += bmp_bgp_attribute_updated_instance(bt, &bib->vrf_state, bgp,
+ withdraw, s);
+ if (withdraw)
+ continue;
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ bmp_send_peerup_per_instance(bmp, bgp);
+ FOREACH_AFI_SAFI (afi, safi) {
+ bmp_update_syncro(bmp, afi, safi, bgp);
+ }
+ }
+ }
+ }
+ }
+ stream_free(s);
return 1;
}
@@ -3210,19 +3592,67 @@ static int bmp_route_distinguisher_update(struct bgp *bgp, afi_t afi, bool preco
return bmp_bgp_attribute_updated(bgp, preconfig);
}
-/* called when a bgp instance goes up/down, implying that the underlying VRF
- * has been created or deleted in zebra
- */
-static int bmp_vrf_state_changed(struct bgp *bgp)
+static void _bmp_vrf_state_changed_internal(struct bgp *bgp, enum bmp_vrf_state vrf_state)
{
struct bmp_bgp *bmpbgp = bmp_bgp_find(bgp);
+ struct bgp *bgp_vrf;
+ struct bmp_targets *bt;
+ struct listnode *node;
+ struct bmp_imported_bgp *bib;
+ struct bmp *bmp;
+ afi_t afi;
+ safi_t safi;
- if (!bmp_bgp_update_vrf_status(bmpbgp, vrf_state_unknown))
- return 1;
+ if (bmpbgp && bmp_bgp_update_vrf_status(&bmpbgp->vrf_state, bgp, vrf_state)) {
+ bmp_send_all_safe(bmpbgp, bmp_peerstate(bgp->peer_self,
+ bmpbgp->vrf_state == vrf_state_down));
+ if (vrf_state == vrf_state_up && bmpbgp->vrf_state == vrf_state_up) {
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ bmp_send_peerup_per_instance(bmp, bgp);
+ FOREACH_AFI_SAFI (afi, safi)
+ bmp_update_syncro(bmp, afi, safi, bgp);
+ }
+ }
+ }
+ }
- bmp_send_all_safe(bmpbgp,
- bmp_peerstate(bgp->peer_self, bmpbgp->vrf_state == vrf_state_down));
+ for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) {
+ bmpbgp = bmp_bgp_find(bgp_vrf);
+ if (!bmpbgp)
+ continue;
+ if (bgp_vrf == bgp)
+ continue;
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ frr_each (bmp_imported_bgps, &bt->imported_bgps, bib) {
+ if (bgp_lookup_by_name(bib->name) != bgp)
+ continue;
+ if (bmp_bgp_update_vrf_status(&bib->vrf_state, bgp, vrf_state)) {
+ bmp_send_bt_safe(bt, bmp_peerstate(bgp->peer_self,
+ bib->vrf_state ==
+ vrf_state_down));
+ if (vrf_state == vrf_state_up &&
+ bib->vrf_state == vrf_state_up) {
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ bmp_send_peerup_per_instance(bmp, bgp);
+ FOREACH_AFI_SAFI (afi, safi)
+ bmp_update_syncro(bmp, afi, safi,
+ bgp);
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+}
+/* called when a bgp instance goes up/down, implying that the underlying VRF
+ * has been created or deleted in zebra
+ */
+static int bmp_vrf_state_changed(struct bgp *bgp)
+{
+ _bmp_vrf_state_changed_internal(bgp, vrf_state_unknown);
return 0;
}
@@ -3231,7 +3661,6 @@ static int bmp_vrf_state_changed(struct bgp *bgp)
*/
static int bmp_vrf_itf_state_changed(struct bgp *bgp, struct interface *itf)
{
- struct bmp_bgp *bmpbgp;
enum bmp_vrf_state new_state;
/* if the update is not about the vrf device double-check
@@ -3240,10 +3669,8 @@ static int bmp_vrf_itf_state_changed(struct bgp *bgp, struct interface *itf)
if (!itf || !if_is_vrf(itf))
return bmp_vrf_state_changed(bgp);
- bmpbgp = bmp_bgp_find(bgp);
new_state = if_is_up(itf) ? vrf_state_up : vrf_state_down;
- if (bmp_bgp_update_vrf_status(bmpbgp, new_state))
- bmp_send_all(bmpbgp, bmp_peerstate(bgp->peer_self, new_state == vrf_state_down));
+ _bmp_vrf_state_changed_internal(bgp, new_state);
return 0;
}
diff --git a/bgpd/bgp_bmp.h b/bgpd/bgp_bmp.h
index d45a4278f6..d81b8f9b05 100644
--- a/bgpd/bgp_bmp.h
+++ b/bgpd/bgp_bmp.h
@@ -92,7 +92,7 @@ struct bmp_mirrorq {
uint8_t data[0];
};
-enum {
+enum bmp_afi_state {
BMP_AFI_INACTIVE = 0,
BMP_AFI_NEEDSYNC,
BMP_AFI_SYNC,
@@ -148,6 +148,7 @@ struct bmp {
uint64_t syncpeerid;
afi_t syncafi;
safi_t syncsafi;
+ struct bgp *sync_bgp;
};
/* config & state for an active outbound connection. When the connection
@@ -195,6 +196,9 @@ struct bmp_listener {
int sock;
};
+/* config for imported bgp instances */
+PREDECL_SORTLIST_UNIQ(bmp_imported_bgps);
+
/* bmp_targets - plural since it may contain multiple bmp_listener &
* bmp_active items. If they have the same config, BMP session should be
* put in the same targets since that's a bit more effective.
@@ -206,6 +210,7 @@ struct bmp_targets {
struct bmp_bgp *bmpbgp;
struct bgp *bgp;
+ bool bgp_request_sync[AFI_MAX][SAFI_MAX];
char *name;
struct bmp_listeners_head listeners;
@@ -238,6 +243,8 @@ struct bmp_targets {
struct bmp_qhash_head locupdhash;
struct bmp_qlist_head locupdlist;
+ struct bmp_imported_bgps_head imported_bgps;
+
uint64_t cnt_accept, cnt_aclrefused;
bool stats_send_experimental;
@@ -274,6 +281,14 @@ enum bmp_vrf_state {
vrf_state_up = 1,
};
+struct bmp_imported_bgp {
+ struct bmp_imported_bgps_item bib;
+ struct bmp_targets *targets;
+ char *name;
+ enum bmp_vrf_state vrf_state;
+ bool bgp_request_sync[AFI_MAX][SAFI_MAX];
+};
+
struct bmp_bgp {
struct bmp_bgph_item bbi;
@@ -289,7 +304,8 @@ struct bmp_bgp {
size_t mirror_qsizelimit;
};
-extern bool bmp_bgp_update_vrf_status(struct bmp_bgp *bmpbgp, enum bmp_vrf_state force);
+extern bool bmp_bgp_update_vrf_status(enum bmp_vrf_state *vrf_state, struct bgp *bgp,
+ enum bmp_vrf_state force);
enum {
/* RFC7854 - 10.8 */
diff --git a/bgpd/bgp_trace.h b/bgpd/bgp_trace.h
index 43bc7a5a17..ce86920634 100644
--- a/bgpd/bgp_trace.h
+++ b/bgpd/bgp_trace.h
@@ -135,12 +135,13 @@ TRACEPOINT_LOGLEVEL(frr_bgp, bmp_mirror_packet, TRACE_INFO)
TRACEPOINT_EVENT(
frr_bgp,
bmp_eor,
- TP_ARGS(afi_t, afi, safi_t, safi, uint8_t, flags, uint8_t, peer_type_flag),
+ TP_ARGS(afi_t, afi, safi_t, safi, uint8_t, flags, uint8_t, peer_type_flag, bgp),
TP_FIELDS(
ctf_integer(afi_t, afi, afi)
ctf_integer(safi_t, safi, safi)
ctf_integer(uint8_t, flags, flags)
ctf_integer(uint8_t, peer_type_flag, peer_type_flag)
+ ctf_string(bgp, bgp->name_pretty)
)
)
diff --git a/doc/developer/building-frr-for-ubuntu2004.rst b/doc/developer/building-frr-for-ubuntu2004.rst
index 3db97c4b2d..19353e317d 100644
--- a/doc/developer/building-frr-for-ubuntu2004.rst
+++ b/doc/developer/building-frr-for-ubuntu2004.rst
@@ -1,163 +1,4 @@
Ubuntu 20.04 LTS
================
-This document describes installation from source. If you want to build a
-``deb``, see :ref:`packaging-debian`.
-
-Installing Dependencies
------------------------
-
-.. code-block:: console
-
- sudo apt update
- sudo apt-get install \
- git autoconf automake libtool make libreadline-dev texinfo \
- pkg-config libpam0g-dev libjson-c-dev bison flex \
- libc-ares-dev python3-dev python3-sphinx \
- install-info build-essential libsnmp-dev perl \
- protobuf-c-compiler libprotobuf-c-dev \
- libcap-dev libelf-dev libunwind-dev
-
-.. include:: building-libunwind-note.rst
-
-.. include:: building-libyang.rst
-
-GRPC
-^^^^
-If GRPC is enabled using ``--enable-grpc`` the following packages should be
-installed.
-
-.. code-block:: console
-
- sudo apt-get install libgrpc++-dev protobuf-compiler-grpc
-
-
-Config Rollbacks
-^^^^^^^^^^^^^^^^
-
-If config rollbacks are enabled using ``--enable-config-rollbacks``
-the sqlite3 developer package also should be installed.
-
-.. code-block:: console
-
- sudo apt install libsqlite3-dev
-
-
-ZeroMQ
-^^^^^^
-
-.. code-block:: console
-
- sudo apt-get install libzmq5 libzmq3-dev
-
-Building & Installing FRR
--------------------------
-
-Add FRR user and groups
-^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: console
-
- sudo groupadd -r -g 92 frr
- sudo groupadd -r -g 85 frrvty
- sudo adduser --system --ingroup frr --home /var/run/frr/ \
- --gecos "FRR suite" --shell /sbin/nologin frr
- sudo usermod -a -G frrvty frr
-
-Compile
-^^^^^^^
-
-.. include:: include-compile.rst
-
-Install FRR configuration files
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: console
-
- sudo install -m 775 -o frr -g frr -d /var/log/frr
- sudo install -m 775 -o frr -g frrvty -d /etc/frr
- sudo install -m 640 -o frr -g frrvty tools/etc/frr/vtysh.conf /etc/frr/vtysh.conf
- sudo install -m 640 -o frr -g frr tools/etc/frr/frr.conf /etc/frr/frr.conf
- sudo install -m 640 -o frr -g frr tools/etc/frr/daemons.conf /etc/frr/daemons.conf
- sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons
-
-Tweak sysctls
-^^^^^^^^^^^^^
-
-Some sysctls need to be changed in order to enable IPv4/IPv6 forwarding and
-MPLS (if supported by your platform). If your platform does not support MPLS,
-skip the MPLS related configuration in this section.
-
-Edit :file:`/etc/sysctl.conf` and uncomment the following values (ignore the
-other settings):
-
-::
-
- # Uncomment the next line to enable packet forwarding for IPv4
- net.ipv4.ip_forward=1
-
- # Uncomment the next line to enable packet forwarding for IPv6
- # Enabling this option disables Stateless Address Autoconfiguration
- # based on Router Advertisements for this host
- net.ipv6.conf.all.forwarding=1
-
-Reboot or use ``sysctl -p`` to apply the same config to the running system.
-
-Add MPLS kernel modules
-"""""""""""""""""""""""
-
-Ubuntu 20.04 ships with kernel 5.4; MPLS modules are present by default. To
-enable, add the following lines to :file:`/etc/modules-load.d/modules.conf`:
-
-::
-
- # Load MPLS Kernel Modules
- mpls_router
- mpls_iptunnel
-
-
-And load the kernel modules on the running system:
-
-.. code-block:: console
-
- sudo modprobe mpls-router mpls-iptunnel
-
-If the above command returns an error, you may need to install the appropriate
-or latest linux-modules-extra-<kernel-version>-generic package. For example
-``apt-get install linux-modules-extra-`uname -r`-generic``
-
-Enable MPLS Forwarding
-""""""""""""""""""""""
-
-Edit :file:`/etc/sysctl.conf` and the following lines. Make sure to add a line
-equal to :file:`net.mpls.conf.eth0.input` for each interface used with MPLS.
-
-::
-
- # Enable MPLS Label processing on all interfaces
- net.mpls.conf.eth0.input=1
- net.mpls.conf.eth1.input=1
- net.mpls.conf.eth2.input=1
- net.mpls.platform_labels=100000
-
-Install service files
-^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: console
-
- sudo install -m 644 tools/frr.service /etc/systemd/system/frr.service
- sudo systemctl enable frr
-
-Enable daemons
-^^^^^^^^^^^^^^
-
-Open :file:`/etc/frr/daemons` with your text editor of choice. Look for the
-section with ``watchfrr_enable=...`` and ``zebra=...`` etc. Enable the daemons
-as required by changing the value to ``yes``.
-
-Start FRR
-^^^^^^^^^
-
-.. code-block:: shell
-
- systemctl start frr
+.. include:: building-frr-for-ubuntu2x04.rst
diff --git a/doc/developer/building-frr-for-ubuntu2204.rst b/doc/developer/building-frr-for-ubuntu2204.rst
index c898c3cd2c..726cf0a911 100644
--- a/doc/developer/building-frr-for-ubuntu2204.rst
+++ b/doc/developer/building-frr-for-ubuntu2204.rst
@@ -1,164 +1,4 @@
Ubuntu 22.04 LTS
================
-This document describes installation from source. If you want to build a
-``deb``, see :ref:`packaging-debian`.
-
-Installing Dependencies
------------------------
-
-.. code-block:: console
-
- sudo apt update
- sudo apt-get install \
- git autoconf automake libtool make libreadline-dev texinfo \
- pkg-config libpam0g-dev libjson-c-dev bison flex \
- libc-ares-dev python3-dev python3-sphinx \
- install-info build-essential libsnmp-dev perl \
- libcap-dev libelf-dev libunwind-dev \
- protobuf-c-compiler libprotobuf-c-dev
-
-.. include:: building-libunwind-note.rst
-
-.. include:: building-libyang.rst
-
-GRPC
-^^^^
-If GRPC is enabled using ``--enable-grpc`` the following packages should be
-installed.
-
-.. code-block:: console
-
- sudo apt-get install libgrpc++-dev protobuf-compiler-grpc
-
-
-Config Rollbacks
-^^^^^^^^^^^^^^^^
-
-If config rollbacks are enabled using ``--enable-config-rollbacks``
-the sqlite3 developer package also should be installed.
-
-.. code-block:: console
-
- sudo apt install libsqlite3-dev
-
-
-ZeroMQ
-^^^^^^
-This is optional
-
-.. code-block:: console
-
- sudo apt-get install libzmq5 libzmq3-dev
-
-Building & Installing FRR
--------------------------
-
-Add FRR user and groups
-^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: console
-
- sudo groupadd -r -g 92 frr
- sudo groupadd -r -g 85 frrvty
- sudo adduser --system --ingroup frr --home /var/run/frr/ \
- --gecos "FRR suite" --shell /sbin/nologin frr
- sudo usermod -a -G frrvty frr
-
-Compile
-^^^^^^^
-
-.. include:: include-compile.rst
-
-Install FRR configuration files
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: console
-
- sudo install -m 775 -o frr -g frr -d /var/log/frr
- sudo install -m 775 -o frr -g frrvty -d /etc/frr
- sudo install -m 640 -o frr -g frrvty tools/etc/frr/vtysh.conf /etc/frr/vtysh.conf
- sudo install -m 640 -o frr -g frr tools/etc/frr/frr.conf /etc/frr/frr.conf
- sudo install -m 640 -o frr -g frr tools/etc/frr/daemons.conf /etc/frr/daemons.conf
- sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons
-
-Tweak sysctls
-^^^^^^^^^^^^^
-
-Some sysctls need to be changed in order to enable IPv4/IPv6 forwarding and
-MPLS (if supported by your platform). If your platform does not support MPLS,
-skip the MPLS related configuration in this section.
-
-Edit :file:`/etc/sysctl.conf` and uncomment the following values (ignore the
-other settings):
-
-::
-
- # Uncomment the next line to enable packet forwarding for IPv4
- net.ipv4.ip_forward=1
-
- # Uncomment the next line to enable packet forwarding for IPv6
- # Enabling this option disables Stateless Address Autoconfiguration
- # based on Router Advertisements for this host
- net.ipv6.conf.all.forwarding=1
-
-Reboot or use ``sysctl -p`` to apply the same config to the running system.
-
-Add MPLS kernel modules
-"""""""""""""""""""""""
-
-Ubuntu 20.04 ships with kernel 5.4; MPLS modules are present by default. To
-enable, add the following lines to :file:`/etc/modules-load.d/modules.conf`:
-
-::
-
- # Load MPLS Kernel Modules
- mpls_router
- mpls_iptunnel
-
-
-And load the kernel modules on the running system:
-
-.. code-block:: console
-
- sudo modprobe mpls-router mpls-iptunnel
-
-If the above command returns an error, you may need to install the appropriate
-or latest linux-modules-extra-<kernel-version>-generic package. For example
-``apt-get install linux-modules-extra-`uname -r`-generic``
-
-Enable MPLS Forwarding
-""""""""""""""""""""""
-
-Edit :file:`/etc/sysctl.conf` and the following lines. Make sure to add a line
-equal to :file:`net.mpls.conf.eth0.input` for each interface used with MPLS.
-
-::
-
- # Enable MPLS Label processing on all interfaces
- net.mpls.conf.eth0.input=1
- net.mpls.conf.eth1.input=1
- net.mpls.conf.eth2.input=1
- net.mpls.platform_labels=100000
-
-Install service files
-^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: console
-
- sudo install -m 644 tools/frr.service /etc/systemd/system/frr.service
- sudo systemctl enable frr
-
-Enable daemons
-^^^^^^^^^^^^^^
-
-Open :file:`/etc/frr/daemons` with your text editor of choice. Look for the
-section with ``watchfrr_enable=...`` and ``zebra=...`` etc. Enable the daemons
-as required by changing the value to ``yes``.
-
-Start FRR
-^^^^^^^^^
-
-.. code-block:: shell
-
- systemctl start frr
+.. include:: building-frr-for-ubuntu2x04.rst
diff --git a/doc/developer/building-frr-for-ubuntu2404.rst b/doc/developer/building-frr-for-ubuntu2404.rst
new file mode 100644
index 0000000000..e6b264993c
--- /dev/null
+++ b/doc/developer/building-frr-for-ubuntu2404.rst
@@ -0,0 +1,4 @@
+Ubuntu 24.04 LTS
+================
+
+.. include:: building-frr-for-ubuntu2x04.rst
diff --git a/doc/developer/building-frr-for-ubuntu2x04.rst b/doc/developer/building-frr-for-ubuntu2x04.rst
new file mode 100644
index 0000000000..78b45e141e
--- /dev/null
+++ b/doc/developer/building-frr-for-ubuntu2x04.rst
@@ -0,0 +1,162 @@
+
+This document describes installation from source. If you want to build a
+``deb``, see :ref:`packaging-debian`.
+
+Installing Dependencies
+-----------------------
+
+.. code-block:: console
+
+ sudo apt update
+ sudo apt-get install \
+ git autoconf automake libtool make libreadline-dev texinfo \
+ pkg-config libpam0g-dev libjson-c-dev bison flex \
+ libc-ares-dev python3-dev python3-sphinx \
+ install-info build-essential libsnmp-dev perl \
+ libcap-dev libelf-dev libunwind-dev \
+ protobuf-c-compiler libprotobuf-c-dev
+
+.. include:: building-libunwind-note.rst
+
+.. include:: building-libyang.rst
+
+GRPC
+^^^^
+If GRPC is enabled using ``--enable-grpc`` the following packages should be
+installed.
+
+.. code-block:: console
+
+ sudo apt-get install libgrpc++-dev protobuf-compiler-grpc
+
+
+Config Rollbacks
+^^^^^^^^^^^^^^^^
+
+If config rollbacks are enabled using ``--enable-config-rollbacks``
+the sqlite3 developer package also should be installed.
+
+.. code-block:: console
+
+ sudo apt install libsqlite3-dev
+
+
+ZeroMQ
+^^^^^^
+This is optional
+
+.. code-block:: console
+
+ sudo apt-get install libzmq5 libzmq3-dev
+
+Building & Installing FRR
+-------------------------
+
+Add FRR user and groups
+^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: console
+
+ sudo groupadd -r -g 92 frr
+ sudo groupadd -r -g 85 frrvty
+ sudo adduser --system --ingroup frr --home /var/run/frr/ \
+ --gecos "FRR suite" --shell /sbin/nologin frr
+ sudo usermod -a -G frrvty frr
+
+Compile
+^^^^^^^
+
+.. include:: include-compile.rst
+
+Install FRR configuration files
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: console
+
+ sudo install -m 775 -o frr -g frr -d /var/log/frr
+ sudo install -m 775 -o frr -g frrvty -d /etc/frr
+ sudo install -m 640 -o frr -g frrvty tools/etc/frr/vtysh.conf /etc/frr/vtysh.conf
+ sudo install -m 640 -o frr -g frr tools/etc/frr/frr.conf /etc/frr/frr.conf
+ sudo install -m 640 -o frr -g frr tools/etc/frr/daemons.conf /etc/frr/daemons.conf
+ sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons
+
+Tweak sysctls
+^^^^^^^^^^^^^
+
+Some sysctls need to be changed in order to enable IPv4/IPv6 forwarding and
+MPLS (if supported by your platform). If your platform does not support MPLS,
+skip the MPLS related configuration in this section.
+
+Edit :file:`/etc/sysctl.conf` and uncomment the following values (ignore the
+other settings):
+
+::
+
+ # Uncomment the next line to enable packet forwarding for IPv4
+ net.ipv4.ip_forward=1
+
+ # Uncomment the next line to enable packet forwarding for IPv6
+ # Enabling this option disables Stateless Address Autoconfiguration
+ # based on Router Advertisements for this host
+ net.ipv6.conf.all.forwarding=1
+
+Reboot or use ``sysctl -p`` to apply the same config to the running system.
+
+Add MPLS kernel modules
+"""""""""""""""""""""""
+
+Ubuntu 20.04 ships with kernel 5.4; MPLS modules are present by default. To
+enable, add the following lines to :file:`/etc/modules-load.d/modules.conf`:
+
+::
+
+ # Load MPLS Kernel Modules
+ mpls_router
+ mpls_iptunnel
+
+
+And load the kernel modules on the running system:
+
+.. code-block:: console
+
+ sudo modprobe mpls-router mpls-iptunnel
+
+If the above command returns an error, you may need to install the appropriate
+or latest linux-modules-extra-<kernel-version>-generic package. For example
+``apt-get install linux-modules-extra-`uname -r`-generic``
+
+Enable MPLS Forwarding
+""""""""""""""""""""""
+
+Edit :file:`/etc/sysctl.conf` and the following lines. Make sure to add a line
+equal to :file:`net.mpls.conf.eth0.input` for each interface used with MPLS.
+
+::
+
+ # Enable MPLS Label processing on all interfaces
+ net.mpls.conf.eth0.input=1
+ net.mpls.conf.eth1.input=1
+ net.mpls.conf.eth2.input=1
+ net.mpls.platform_labels=100000
+
+Install service files
+^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: console
+
+ sudo install -m 644 tools/frr.service /etc/systemd/system/frr.service
+ sudo systemctl enable frr
+
+Enable daemons
+^^^^^^^^^^^^^^
+
+Open :file:`/etc/frr/daemons` with your text editor of choice. Look for the
+section with ``watchfrr_enable=...`` and ``zebra=...`` etc. Enable the daemons
+as required by changing the value to ``yes``.
+
+Start FRR
+^^^^^^^^^
+
+.. code-block:: shell
+
+ systemctl start frr
diff --git a/doc/user/bmp.rst b/doc/user/bmp.rst
index 14d0849b34..07c3c1c8bd 100644
--- a/doc/user/bmp.rst
+++ b/doc/user/bmp.rst
@@ -171,3 +171,8 @@ associated with a particular ``bmp targets``:
All BGP neighbors are included in Route Mirroring. Options to select
a subset of BGP sessions may be added in the future.
+
+.. clicmd:: bmp import-vrf-view VRF_OR_VIEW_NAME
+
+ Perform Route Mirroring and Route Monitoring from an other BGP
+ instance.
diff --git a/docker/ubuntu-ci/Dockerfile b/docker/ubuntu-ci/Dockerfile
index aaad3bc172..0bfcb51878 100644
--- a/docker/ubuntu-ci/Dockerfile
+++ b/docker/ubuntu-ci/Dockerfile
@@ -2,7 +2,6 @@ ARG UBUNTU_VERSION=22.04
FROM ubuntu:$UBUNTU_VERSION
ARG DEBIAN_FRONTEND=noninteractive
-ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# Update and install build requirements.
RUN apt update && apt upgrade -y && \
@@ -77,14 +76,15 @@ RUN apt update && apt upgrade -y && \
wget https://raw.githubusercontent.com/FRRouting/frr-mibs/main/iana/IANA-IPPM-METRICS-REGISTRY-MIB -O /usr/share/snmp/mibs/iana/IANA-IPPM-METRICS-REGISTRY-MIB && \
wget https://raw.githubusercontent.com/FRRouting/frr-mibs/main/ietf/SNMPv2-PDU -O /usr/share/snmp/mibs/ietf/SNMPv2-PDU && \
wget https://raw.githubusercontent.com/FRRouting/frr-mibs/main/ietf/IPATM-IPMC-MIB -O /usr/share/snmp/mibs/ietf/IPATM-IPMC-MIB && \
+ rm -f /usr/lib/python3.*/EXTERNALLY-MANAGED && \
python3 -m pip install wheel && \
- python3 -m pip install 'protobuf<4' grpcio grpcio-tools && \
+ python3 -m pip install protobuf grpcio grpcio-tools && \
python3 -m pip install 'pytest>=6.2.4' 'pytest-xdist>=2.3.0' && \
python3 -m pip install 'scapy>=2.4.5' && \
python3 -m pip install xmltodict && \
python3 -m pip install git+https://github.com/Exa-Networks/exabgp@0659057837cd6c6351579e9f0fa47e9fb7de7311
-ARG UID=1000
+ARG UID=1010
RUN groupadd -r -g 92 frr && \
groupadd -r -g 85 frrvty && \
adduser --system --ingroup frr --home /home/frr \
diff --git a/docker/ubuntu22-ci/README.md b/docker/ubuntu22-ci/README.md
index 617192eb71..116b3c0e4a 100644
--- a/docker/ubuntu22-ci/README.md
+++ b/docker/ubuntu22-ci/README.md
@@ -5,7 +5,7 @@ This builds an ubuntu 22.04 container for dev / test
# Build
```
-docker build -t frr-ubuntu22:latest -f docker/ubuntu-ci/Dockerfile .
+docker build -t frr-ubuntu22:latest --build-arg=UBUNTU_VERSION=22.04 -f docker/ubuntu-ci/Dockerfile .
```
# Run
diff --git a/docker/ubuntu24-ci/README.md b/docker/ubuntu24-ci/README.md
new file mode 100644
index 0000000000..38ba0ee171
--- /dev/null
+++ b/docker/ubuntu24-ci/README.md
@@ -0,0 +1,66 @@
+# Ubuntu 24.04
+
+This builds an ubuntu 24.04 container for dev / test
+
+# Build
+
+```
+docker build -t frr-ubuntu24:latest --build-arg=UBUNTU_VERSION=24.04 -f docker/ubuntu-ci/Dockerfile .
+```
+
+# Run
+
+```
+docker run -d --init --privileged --name frr-ubuntu24 --mount type=bind,source=/lib/modules,target=/lib/modules frr-ubuntu24:latest
+```
+
+# Running full topotest (container stops at end)
+
+```
+docker run --init -it --privileged --name frr-ubuntu24 \
+ -v /lib/modules:/lib/modules frr-ubuntu24:latest \
+ bash -c 'cd /home/frr/frr/tests/topotests; sudo pytest -nauto --dist=loadfile'
+```
+
+# Extract results from the above run into `run-results` dir and analyze
+
+```
+tests/topotests/analyze.py -C frr-ubuntu24 -Ar run-results
+```
+
+# Extract coverage from a stopped container into host FRR source tree
+
+```
+docker export frr-ubuntu24 | tar --strip=3 --wildcards -vx '*.gc??'
+lcov -b $(pwd) --capture --directory . --output-file=coverage.info
+```
+
+# make check
+
+```
+docker exec frr-ubuntu24 bash -c 'cd ~/frr ; make check'
+```
+
+# interactive bash
+
+```
+docker exec -it frr-ubuntu24 bash
+```
+
+# Run a specific topotest
+
+```
+docker exec frr-ubuntu24 bash -c 'cd ~/frr/tests/topotests ; sudo pytest ospf_topo1/test_ospf_topo1.py'
+```
+
+# stop & remove container
+
+```
+docker stop frr-ubuntu24 ; docker rm frr-ubuntu24
+```
+
+# remove image
+
+```
+docker rmi frr-ubuntu24:latest
+```
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step1.json
new file mode 100644
index 0000000000..3542f4e495
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step1.json
@@ -0,0 +1,34 @@
+{
+ "loc-rib": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "is_filtered": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "444:1",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "is_filtered": false,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "555:1",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step2.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step2.json
new file mode 100644
index 0000000000..60066d502c
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step2.json
@@ -0,0 +1,34 @@
+{
+ "loc-rib": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "is_filtered": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "666:22",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "is_filtered": false,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "666:22",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step1.json
new file mode 100644
index 0000000000..cf71f20485
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step1.json
@@ -0,0 +1,36 @@
+{
+ "post-policy": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "444:1",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "555:1",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step2.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step2.json
new file mode 100644
index 0000000000..b555c2a371
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step2.json
@@ -0,0 +1,36 @@
+{
+ "post-policy": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "666:22",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "666:22",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step1.json
new file mode 100644
index 0000000000..43273cc93a
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step1.json
@@ -0,0 +1,36 @@
+{
+ "pre-policy": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "444:1",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "555:1",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step2.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step2.json
new file mode 100644
index 0000000000..20549926d5
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step2.json
@@ -0,0 +1,36 @@
+{
+ "pre-policy": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "666:22",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "666:22",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step1.json
new file mode 100644
index 0000000000..fcf518390d
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step1.json
@@ -0,0 +1,28 @@
+{
+ "loc-rib": {
+ "withdraw": {
+ "172.31.0.77/32": {
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "172.31.0.77/32",
+ "is_filtered": false,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "444:1",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "2001::1125/128",
+ "is_filtered": false,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "555:1",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step2.json b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step2.json
new file mode 100644
index 0000000000..1e5040ba60
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step2.json
@@ -0,0 +1,34 @@
+{
+ "loc-rib": {
+ "withdraw": {
+ "172.31.0.15/32": {
+ "afi": 1,
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "172.31.0.15/32",
+ "is_filtered": false,
+ "label": 0,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "0:0",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib",
+ "rd": "444:2",
+ "safi": 128
+ },
+ "2001::1111/128": {
+ "afi": 2,
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "2001::1111/128",
+ "is_filtered": false,
+ "label": 0,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "0:0",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib",
+ "rd": "555:2",
+ "safi": 128
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-post-policy-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-post-policy-step1.json
new file mode 100644
index 0000000000..6626e91361
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-post-policy-step1.json
@@ -0,0 +1,30 @@
+{
+ "post-policy": {
+ "withdraw": {
+ "172.31.0.77/32": {
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "444:1",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "555:1",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-pre-policy-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-pre-policy-step1.json
new file mode 100644
index 0000000000..d3fb1b7ba1
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-pre-policy-step1.json
@@ -0,0 +1,30 @@
+{
+ "pre-policy": {
+ "withdraw": {
+ "172.31.0.77/32": {
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "444:1",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "555:1",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/r1import/frr.conf b/tests/topotests/bgp_bmp/r1import/frr.conf
new file mode 100644
index 0000000000..bec4eb01c7
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r1import/frr.conf
@@ -0,0 +1,73 @@
+interface r1import-eth0
+ ip address 192.0.2.1/24
+!
+interface r1import-eth1
+ ip address 192.168.0.1/24
+ ipv6 address 192:168::1/64
+!
+interface r1import-eth2
+ ip address 192.168.1.1/24
+ ipv6 address 192:167::1/64
+!
+router bgp 65501
+ bgp router-id 192.168.0.1
+ bgp log-neighbor-changes
+ no bgp ebgp-requires-policy
+ neighbor 192.168.0.2 remote-as 65502
+ neighbor 192:168::2 remote-as 65502
+!
+ bmp targets bmp1
+ bmp connect 192.0.2.10 port 1789 min-retry 100 max-retry 10000
+ bmp monitor ipv4 unicast pre-policy
+ bmp monitor ipv6 unicast pre-policy
+ bmp monitor ipv4 unicast post-policy
+ bmp monitor ipv6 unicast post-policy
+ bmp monitor ipv4 unicast loc-rib
+ bmp monitor ipv6 unicast loc-rib
+ bmp import-vrf-view vrf1
+ 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
+ no neighbor 192:168::2 activate
+ exit-address-family
+!
+ address-family ipv6 unicast
+ neighbor 192:168::2 activate
+ neighbor 192:168::2 soft-reconfiguration inbound
+ exit-address-family
+!
+router bgp 65501 vrf vrf1
+ bgp router-id 192.168.0.1
+ bgp log-neighbor-changes
+ neighbor 192.168.1.3 remote-as 65501
+ neighbor 192:167::3 remote-as 65501
+ address-family ipv4 unicast
+ neighbor 192.168.1.3 activate
+ neighbor 192.168.1.3 soft-reconfiguration inbound
+ no neighbor 192:167::3 activate
+ 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
+ neighbor 192:167::3 activate
+ neighbor 192:167::3 soft-reconfiguration inbound
+ 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/r1import/show-bgp-vrf1-ipv4-update-step1.json b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-update-step1.json
new file mode 100644
index 0000000000..c21a586c3b
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-update-step1.json
@@ -0,0 +1,21 @@
+{
+ "routes": {
+ "172.31.0.77/32": [
+ {
+ "bestpath": true,
+ "pathFrom": "internal",
+ "path": "",
+ "origin": "IGP",
+ "nexthops": [
+ {
+ "ip": "192.168.1.3",
+ "hostname": "r3",
+ "afi": "ipv4",
+ "used": true
+ }
+ ]
+ }
+ ]
+ }
+}
+
diff --git a/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-withdraw-step1.json b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-withdraw-step1.json
new file mode 100644
index 0000000000..154bef7995
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-withdraw-step1.json
@@ -0,0 +1,6 @@
+{
+ "routes": {
+ "172.31.0.77/32": null
+ }
+}
+
diff --git a/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-update-step1.json b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-update-step1.json
new file mode 100644
index 0000000000..14df5ec931
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-update-step1.json
@@ -0,0 +1,27 @@
+{
+ "routes": {
+ "2001::1125/128": [
+ {
+ "bestpath": true,
+ "pathFrom": "internal",
+ "path": "",
+ "origin": "IGP",
+ "nexthops": [
+ {
+ "ip": "192:167::3",
+ "hostname": "r3",
+ "afi": "ipv6",
+ "scope": "global"
+ },
+ {
+ "hostname": "r3",
+ "afi": "ipv6",
+ "scope": "link-local",
+ "used": true
+ }
+ ]
+ }
+ ]
+ }
+}
+
diff --git a/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-withdraw-step1.json b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-withdraw-step1.json
new file mode 100644
index 0000000000..7c7a95e33e
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-withdraw-step1.json
@@ -0,0 +1,6 @@
+{
+ "routes": {
+ "2001::1125/128": null
+ }
+}
+
diff --git a/tests/topotests/bgp_bmp/r3/frr.conf b/tests/topotests/bgp_bmp/r3/frr.conf
new file mode 100644
index 0000000000..145e156b11
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r3/frr.conf
@@ -0,0 +1,18 @@
+interface r3-eth0
+ ip address 192.168.1.3/24
+ ipv6 address 192:167::3/64
+!
+router bgp 65501
+ bgp router-id 192.168.1.3
+ bgp log-neighbor-changes
+ no bgp network import-check
+ neighbor 192.168.1.1 remote-as 65501
+ neighbor 192:167::1 remote-as 65501
+ address-family ipv4 unicast
+ neighbor 192.168.1.1 activate
+ no neighbor 192:167::1 activate
+ exit-address-family
+ address-family ipv6 unicast
+ neighbor 192:167::1 activate
+ exit-address-family
+exit
diff --git a/tests/topotests/bgp_bmp/test_bgp_bmp_3.py b/tests/topotests/bgp_bmp/test_bgp_bmp_3.py
new file mode 100644
index 0000000000..212cf9e696
--- /dev/null
+++ b/tests/topotests/bgp_bmp/test_bgp_bmp_3.py
@@ -0,0 +1,567 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+# Copyright 2024 6WIND S.A.
+#
+
+"""
+test_bgp_bmp.py_3: Test BGP BMP functionalities
+
+ +------+ +------+ +------+
+ | | | | | |
+ | BMP1 |------------| R1 |---------------| R2 |
+ | | | | | |
+ +------+ +--+---+ +------+
+ |
+ +--+---+
+ | |
+ | R3 |
+ | |
+ +------+
+
+Setup two routers R1 and R2 with one link configured with IPv4 and
+IPv6 addresses.
+Configure BGP in R1 and R2 to exchange prefixes from
+the latter to the first router.
+Setup a link between R1 and the BMP server, activate the BMP feature in R1
+and ensure the monitored BGP sessions logs are well present on the BMP server.
+"""
+
+from functools import partial
+import json
+import os
+import pytest
+import sys
+
+# Save the Current Working Directory to find configuration files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join("../"))
+sys.path.append(os.path.join("../lib/"))
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from lib import topotest
+from lib.bgp import verify_bgp_convergence_from_running_config
+from lib.bgp import bgp_configure_prefixes
+from .bgpbmp import (
+ bmp_check_for_prefixes,
+ bmp_check_for_peer_message,
+ bmp_update_seq,
+ bmp_reset_seq,
+)
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+pytestmark = [pytest.mark.bgpd]
+
+PRE_POLICY = "pre-policy"
+POST_POLICY = "post-policy"
+LOC_RIB = "loc-rib"
+
+UPDATE_EXPECTED_JSON = False
+DEBUG_PCAP = False
+
+
+def build_topo(tgen):
+ tgen.add_router("r1import")
+ tgen.add_router("r2")
+ tgen.add_router("r3") # CPE behind r1
+
+ tgen.add_bmp_server("bmp1import", ip="192.0.2.10", defaultRoute="via 192.0.2.1")
+
+ switch = tgen.add_switch("s1")
+ switch.add_link(tgen.gears["r1import"])
+ switch.add_link(tgen.gears["bmp1import"])
+
+ tgen.add_link(tgen.gears["r1import"], tgen.gears["r2"], "r1import-eth1", "r2-eth0")
+ tgen.add_link(tgen.gears["r1import"], tgen.gears["r3"], "r1import-eth2", "r3-eth0")
+
+
+def setup_module(mod):
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ tgen.net["r1import"].cmd(
+ """
+ip link add vrf1 type vrf table 10
+ip link set vrf1 up
+ip link set r1import-eth2 master vrf1
+ """
+ )
+
+ bmp_reset_seq()
+ if DEBUG_PCAP:
+ tgen.gears["r1import"].run("rm /tmp/bmp.pcap")
+ tgen.gears["r1import"].run(
+ "tcpdump -nni r1import-eth0 -s 0 -w /tmp/bmp.pcap &", stdout=None
+ )
+
+ for rname, router in tgen.routers().items():
+ logger.info("Loading router %s" % rname)
+ router.load_frr_config(
+ os.path.join(CWD, "{}/frr.conf".format(rname)),
+ [(TopoRouter.RD_ZEBRA, None), (TopoRouter.RD_BGP, "-M bmp")],
+ )
+
+ tgen.start_router()
+
+ logger.info("starting BMP servers")
+ 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):
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_bgp_convergence():
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ result = verify_bgp_convergence_from_running_config(tgen, dut="r1import")
+ assert result is True, "BGP is not converging"
+
+
+def _test_prefixes_syncro(policy, vrf=None, step=1):
+ """
+ Check that the given policy has syncronised the previously received BGP
+ updates.
+ """
+ tgen = get_topogen()
+
+ prefixes = ["172.31.0.77/32", "2001::1125/128"]
+ # check
+ test_func = partial(
+ bmp_check_for_prefixes,
+ prefixes,
+ "update",
+ policy,
+ step,
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import"),
+ tgen.gears["r1import"],
+ f"{CWD}/bmp1import",
+ UPDATE_EXPECTED_JSON,
+ LOC_RIB,
+ )
+ success, res = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert success, "Checking the updated prefixes has failed ! %s" % res
+
+
+def _test_prefixes(policy, vrf=None, step=0):
+ """
+ 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()
+
+ safi = "vpn" if vrf else "unicast"
+
+ prefixes = ["172.31.0.77/32", "2001::1125/128"]
+
+ for type in ("update", "withdraw"):
+ bmp_update_seq(
+ tgen.gears["bmp1import"], os.path.join(tgen.logdir, "bmp1import", "bmp.log")
+ )
+
+ bgp_configure_prefixes(
+ tgen.gears["r3"],
+ 65501,
+ "unicast",
+ prefixes,
+ vrf=None,
+ update=(type == "update"),
+ )
+
+ logger.info(f"checking for prefixes {type}")
+
+ for ipver in [4, 6]:
+ if UPDATE_EXPECTED_JSON:
+ continue
+ ref_file = "{}/r1import/show-bgp-{}-ipv{}-{}-step{}.json".format(
+ CWD, vrf, ipver, type, step
+ )
+ expected = json.loads(open(ref_file).read())
+
+ test_func = partial(
+ topotest.router_json_cmp,
+ tgen.gears["r1import"],
+ f"show bgp vrf {vrf} ipv{ipver} json",
+ expected,
+ )
+ _, res = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assertmsg = f"r1: BGP IPv{ipver} convergence failed"
+ assert res is None, assertmsg
+
+ # check
+ test_func = partial(
+ bmp_check_for_prefixes,
+ prefixes,
+ type,
+ policy,
+ step,
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import"),
+ tgen.gears["r1import"],
+ f"{CWD}/bmp1import",
+ UPDATE_EXPECTED_JSON,
+ LOC_RIB,
+ )
+ success, res = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert success, "Checking the updated prefixes has failed ! %s" % res
+
+
+def _test_peer_up(check_locrib=True):
+ """
+ Checking for BMP peers up messages
+ """
+
+ tgen = get_topogen()
+ if check_locrib:
+ peers = ["0.0.0.0", "192.168.1.3", "192:167::3"]
+ else:
+ peers = ["192.168.1.3", "192:167::3"]
+
+ logger.info("checking for BMP peers up messages")
+
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer up",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert success, "Checking the updated prefixes has been failed !."
+
+
+def test_bmp_server_logging():
+ """
+ Assert the logging of the bmp server.
+ """
+
+ def check_for_log_file():
+ tgen = get_topogen()
+ output = tgen.gears["bmp1import"].run(
+ "ls {}".format(os.path.join(tgen.logdir, "bmp1import"))
+ )
+ if "bmp.log" not in output:
+ return False
+ return True
+
+ success, _ = topotest.run_and_expect(check_for_log_file, True, count=30, wait=1)
+ assert success, "The BMP server is not logging"
+
+
+def test_bmp_peer_up_start():
+ _test_peer_up()
+
+
+def test_bmp_bgp_unicast():
+ """
+ Add/withdraw bgp unicast prefixes and check the bmp logs.
+ """
+ logger.info("*** Unicast prefixes pre-policy logging ***")
+ _test_prefixes(PRE_POLICY, vrf="vrf1", step=1)
+ logger.info("*** Unicast prefixes post-policy logging ***")
+ _test_prefixes(POST_POLICY, vrf="vrf1", step=1)
+ logger.info("*** Unicast prefixes loc-rib logging ***")
+ _test_prefixes(LOC_RIB, vrf="vrf1", step=1)
+
+
+def test_peer_down():
+ """
+ Checking for BMP peers down messages
+ """
+ tgen = get_topogen()
+
+ tgen.gears["r3"].vtysh_cmd("clear bgp *")
+
+ peers = ["192.168.1.3", "192:167::3"]
+
+ logger.info("checking for BMP peers down messages")
+
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer down",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert success, "Checking the updated prefixes has been failed !."
+
+
+def test_reconfigure_prefixes():
+ """
+ Reconfigured BGP networks from R3. Check for BGP VRF update messages
+ """
+
+ tgen = get_topogen()
+
+ prefixes = ["172.31.0.77/32", "2001::1125/128"]
+ bgp_configure_prefixes(
+ tgen.gears["r3"],
+ 65501,
+ "unicast",
+ prefixes,
+ vrf=None,
+ update=True,
+ )
+
+ for ipver in [4, 6]:
+ ref_file = "{}/r1import/show-bgp-{}-ipv{}-{}-step{}.json".format(
+ CWD, "vrf1", ipver, "update", 1
+ )
+ expected = json.loads(open(ref_file).read())
+
+ test_func = partial(
+ topotest.router_json_cmp,
+ tgen.gears["r1import"],
+ f"show bgp vrf vrf1 ipv{ipver} json",
+ expected,
+ )
+ _, res = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assertmsg = f"r1: BGP IPv{ipver} convergence failed"
+ assert res is None, assertmsg
+
+
+def test_monitor_syncro():
+ """
+ Checking for BMP peers down messages
+ """
+ tgen = get_topogen()
+
+ tgen.gears["r1import"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65501
+ bmp targets bmp1
+ bmp import-vrf-view vrf1
+ """
+ )
+
+ logger.info("*** Unicast prefixes pre-policy logging ***")
+ _test_prefixes_syncro(PRE_POLICY, vrf="vrf1")
+ logger.info("*** Unicast prefixes post-policy logging ***")
+ _test_prefixes_syncro(POST_POLICY, vrf="vrf1")
+ logger.info("*** Unicast prefixes loc-rib logging ***")
+ _test_prefixes_syncro(LOC_RIB, vrf="vrf1")
+
+
+def test_reconfigure_route_distinguisher_vrf1():
+ """
+ Checking for BMP peers down messages
+ """
+ tgen = get_topogen()
+
+ bmp_update_seq(
+ tgen.gears["bmp1import"], os.path.join(tgen.logdir, "bmp1import", "bmp.log")
+ )
+ peers = ["0.0.0.0"]
+
+ tgen.gears["r1import"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65501 vrf vrf1
+ address-family ipv4 unicast
+ rd vpn export 666:22
+ exit-address-family
+ address-family ipv6 unicast
+ rd vpn export 666:22
+ """
+ )
+ logger.info(
+ "Checking for BMP peer down LOC-RIB message with route-distinguisher set to 444:1"
+ )
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer down",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ peer_distinguisher="444:1",
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert (
+ success
+ ), "Checking the BMP peer down LOC-RIB message with route-distinguisher set to 444:1 failed !."
+
+ logger.info(
+ "Checking for BMP peer up LOC-RIB messages with route-distinguisher set to 666:22"
+ )
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer up",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ peer_distinguisher="666:22",
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert (
+ success
+ ), "Checking the BMP peer up LOC-RIB message with route-distinguisher set to 666:22 failed !."
+
+ logger.info(
+ "Checking for BMP peer up messages with route-distinguisher set to 666:22"
+ )
+ peers = ["192.168.1.3", "192:167::3"]
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer up",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ peer_distinguisher="666:22",
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert (
+ success
+ ), "Checking the BMP peer up messages with route-distinguisher set to 666:22 failed !."
+
+ logger.info("*** Unicast prefixes pre-policy logging ***")
+ _test_prefixes_syncro(PRE_POLICY, vrf="vrf1", step=2)
+ logger.info("*** Unicast prefixes post-policy logging ***")
+ _test_prefixes_syncro(POST_POLICY, vrf="vrf1", step=2)
+ logger.info("*** Unicast prefixes loc-rib logging ***")
+ _test_prefixes_syncro(LOC_RIB, vrf="vrf1", step=2)
+
+
+def test_bgp_routerid_changed():
+ """
+ Checking for BGP loc-rib up messages with new router-id
+ """
+ tgen = get_topogen()
+
+ tgen.gears["r1import"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65501 vrf vrf1
+ bgp router-id 192.168.1.77
+ """
+ )
+
+ peers = ["0.0.0.0"]
+
+ logger.info(
+ "checking for BMP peer down LOC-RIB message with router-id set to 192.168.0.1."
+ )
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer down",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ peer_bgp_id="192.168.0.1",
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert (
+ success
+ ), "Checking the BMP peer down LOC-RIB message with router-id set to 192.168.0.1 failed !."
+
+ logger.info(
+ "checking for BMP peer up LOC-RIB message with router-id set to 192.168.1.77."
+ )
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer up",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ peer_bgp_id="192.168.1.77",
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert (
+ success
+ ), "Checking the BMP peer up LOC-RIB message with router-id set to 192.168.1.77 failed !."
+
+
+def test_bgp_instance_flapping():
+ """
+ Checking for BGP loc-rib up messages
+ """
+ tgen = get_topogen()
+
+ # create flapping at BMP
+ # note: only peer up are handled at BMP level today
+ tgen.net["r1import"].cmd("ip link set dev vrf1 down")
+
+ peers = ["0.0.0.0"]
+
+ logger.info("checking for BMP peer down LOC-RIB message.")
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer down",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert success, "Checking the BMP peer down LOC-RIB message failed !."
+
+ tgen.net["r1import"].cmd("ip link set dev vrf1 up")
+
+ logger.info("checking for BMP peer up LOC-RIB message.")
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer up",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert success, "Checking the BMP peer up LOC-RIB message failed !."
+
+
+def test_peer_up_after_flush():
+ """
+ Checking for BMP peers down messages
+ """
+ _test_peer_up(check_locrib=False)
+
+
+def test_peer_down_locrib():
+ """
+ Checking for BMP peers down loc-rib messages
+ """
+ tgen = get_topogen()
+
+ tgen.gears["r1import"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65501
+ bmp targets bmp1
+ no bmp import-vrf-view vrf1
+ """
+ )
+
+ peers = ["0.0.0.0"]
+
+ logger.info("checking for BMP peers down messages")
+
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer down",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert success, "Checking the BMP peer down message has failed !."
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/munet/base.py b/tests/topotests/munet/base.py
index e77eb15dc8..e9410d442d 100644
--- a/tests/topotests/munet/base.py
+++ b/tests/topotests/munet/base.py
@@ -332,6 +332,10 @@ class Commander: # pylint: disable=R0904
self.last = None
self.exec_paths = {}
+ # For running commands one time only (deals with asyncio)
+ self.cmd_once_done = {}
+ self.cmd_once_locks = {}
+
if not logger:
logname = f"munet.{self.__class__.__name__.lower()}.{name}"
self.logger = logging.getLogger(logname)
@@ -1189,7 +1193,7 @@ class Commander: # pylint: disable=R0904
return stdout
# Run a command in a new window (gnome-terminal, screen, tmux, xterm)
- def run_in_window(
+ def run_in_window( # pylint: disable=too-many-positional-arguments
self,
cmd,
wait_for=False,
@@ -1205,7 +1209,7 @@ class Commander: # pylint: disable=R0904
Args:
cmd: string to execute.
- wait_for: True to wait for exit from command or `str` as channel neme to
+ wait_for: True to wait for exit from command or `str` as channel name to
signal on exit, otherwise False
background: Do not change focus to new window.
title: Title for new pane (tmux) or window (xterm).
@@ -1405,6 +1409,26 @@ class Commander: # pylint: disable=R0904
return pane_info
+ async def async_cmd_raises_once(self, cmd, **kwargs):
+ if cmd in self.cmd_once_done:
+ return self.cmd_once_done[cmd]
+
+ if cmd not in self.cmd_once_locks:
+ self.cmd_once_locks[cmd] = asyncio.Lock()
+
+ async with self.cmd_once_locks[cmd]:
+ if cmd not in self.cmd_once_done:
+ self.logger.info("Running command once: %s", cmd)
+ self.cmd_once_done[cmd] = await commander.async_cmd_raises(
+ cmd, **kwargs
+ )
+ return self.cmd_once_done[cmd]
+
+ def cmd_raises_once(self, cmd, **kwargs):
+ if cmd not in self.cmd_once_done:
+ self.cmd_once_done[cmd] = commander.cmd_raises(cmd, **kwargs)
+ return self.cmd_once_done[cmd]
+
def delete(self):
"""Calls self.async_delete within an exec loop."""
asyncio.run(self.async_delete())
diff --git a/tests/topotests/munet/munet-schema.json b/tests/topotests/munet/munet-schema.json
index 6ebc368dcb..44453cb44f 100644
--- a/tests/topotests/munet/munet-schema.json
+++ b/tests/topotests/munet/munet-schema.json
@@ -117,6 +117,12 @@
"bios": {
"type": "string"
},
+ "cloud-init": {
+ "type": "boolean"
+ },
+ "cloud-init-disk": {
+ "type": "string"
+ },
"disk": {
"type": "string"
},
@@ -129,7 +135,7 @@
"initial-cmd": {
"type": "string"
},
- "kerenel": {
+ "kernel": {
"type": "string"
},
"initrd": {
@@ -373,6 +379,12 @@
"networks-autonumber": {
"type": "boolean"
},
+ "initial-setup-cmd": {
+ "type": "string"
+ },
+ "initial-setup-host-cmd": {
+ "type": "string"
+ },
"networks": {
"type": "array",
"items": {
@@ -452,6 +464,12 @@
"bios": {
"type": "string"
},
+ "cloud-init": {
+ "type": "boolean"
+ },
+ "cloud-init-disk": {
+ "type": "string"
+ },
"disk": {
"type": "string"
},
@@ -464,7 +482,7 @@
"initial-cmd": {
"type": "string"
},
- "kerenel": {
+ "kernel": {
"type": "string"
},
"initrd": {
diff --git a/tests/topotests/munet/mutest/userapi.py b/tests/topotests/munet/mutest/userapi.py
index abc63af365..e367e65a15 100644
--- a/tests/topotests/munet/mutest/userapi.py
+++ b/tests/topotests/munet/mutest/userapi.py
@@ -180,7 +180,7 @@ class TestCase:
# sum_hfmt = "{:5.5s} {:4.4s} {:>6.6s} {}"
# sum_dfmt = "{:5s} {:4.4s} {:^6.6s} {}"
- sum_fmt = "%-8.8s %4.4s %{}s %6s %s"
+ sum_fmt = "%-10s %4.4s %{}s %6s %s"
def __init__(
self,
diff --git a/tests/topotests/munet/native.py b/tests/topotests/munet/native.py
index e3b782396e..4e29fe91b1 100644
--- a/tests/topotests/munet/native.py
+++ b/tests/topotests/munet/native.py
@@ -24,6 +24,13 @@ import time
from pathlib import Path
+
+try:
+ # We only want to require yaml for the gen cloud image feature
+ import yaml
+except ImportError:
+ pass
+
from . import cli
from .base import BaseMunet
from .base import Bridge
@@ -749,9 +756,11 @@ class L3NodeMixin(NodeMixin):
# Disable IPv6
self.cmd_raises("sysctl -w net.ipv6.conf.all.autoconf=0")
self.cmd_raises("sysctl -w net.ipv6.conf.all.disable_ipv6=1")
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.forwarding=0")
else:
self.cmd_raises("sysctl -w net.ipv6.conf.all.autoconf=1")
self.cmd_raises("sysctl -w net.ipv6.conf.all.disable_ipv6=0")
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.forwarding=1")
self.next_p2p_network = ipaddress.ip_network(f"10.254.{self.id}.0/31")
self.next_p2p_network6 = ipaddress.ip_network(f"fcff:ffff:{self.id:02x}::/127")
@@ -2265,6 +2274,164 @@ class L3QemuVM(L3NodeMixin, LinuxNamespace):
tid = self.cpu_thread_map[i]
self.cmd_raises_nsonly(f"taskset -cp {aff} {tid}")
+ def _gen_network_config(self):
+ intfs = sorted(self.intfs)
+ if not intfs:
+ return ""
+
+ self.logger.debug("Generating cloud-init interface config")
+ config = {}
+ config["version"] = 2
+ enets = config["ethernets"] = {}
+
+ for ifname in sorted(self.intfs):
+ self.logger.debug("Interface %s", ifname)
+ conn = find_with_kv(self.config["connections"], "name", ifname)
+
+ index = self.config["connections"].index(conn)
+ to = conn["to"]
+ switch = self.unet.switches.get(to)
+ mtu = conn.get("mtu")
+ if not mtu and switch:
+ mtu = switch.config.get("mtu")
+
+ devaddr = conn.get("physical", "")
+ # Eventually we should get the MAC from /sys
+ if not devaddr:
+ mac = self.tapmacs.get(ifname, f"02:aa:aa:aa:{index:02x}:{self.id:02x}")
+ nic = {
+ "match": {"macaddress": str(mac)},
+ "set-name": ifname,
+ }
+ if mtu:
+ nic["mtu"] = str(mtu)
+ enets[f"nic-{ifname}"] = nic
+
+ ifaddr4 = self.get_intf_addr(ifname, ipv6=False)
+ ifaddr6 = self.get_intf_addr(ifname, ipv6=True)
+ if not ifaddr4 and not ifaddr6:
+ continue
+ net = {
+ "dhcp4": False,
+ "dhcp6": False,
+ "accept-ra": False,
+ "addresses": [],
+ }
+ if ifaddr4:
+ net["addresses"].append(str(ifaddr4))
+ if ifaddr6:
+ net["addresses"].append(str(ifaddr6))
+ if switch and hasattr(switch, "is_nat") and switch.is_nat:
+ net["nameservers"] = {"addresses": []}
+ nameservers = net["nameservers"]["addresses"]
+ if hasattr(switch, "ip6_address"):
+ net["gateway6"] = str(switch.ip6_address)
+ nameservers.append("2001:4860:4860::8888")
+ if switch.ip_address:
+ net["gateway4"] = str(switch.ip_address)
+ nameservers.append("8.8.8.8")
+ enets[ifname] = net
+
+ return yaml.safe_dump(config)
+
+ def _gen_cloud_init(self):
+ qc = self.qemu_config
+ cc = qc.get("console", {})
+ cipath = self.rundir.joinpath("cloud-init.img")
+
+ geniso = get_exec_path_host("genisoimage")
+ if not geniso:
+ mfbin = get_exec_path_host("mkfs.vfat")
+ mcbin = get_exec_path_host("mcopy")
+ assert (
+ mfbin and mcbin
+ ), "genisoimage or mkfs.vfat,mcopy needed to gen cloud-init disk"
+
+ #
+ # cloud-init: meta-data
+ #
+ mdata = f"""
+instance-id: "munet-{self.id}"
+local-hostname: "{self.name}"
+"""
+ #
+ # cloud-init: user-data
+ #
+ ssh_auth_s = ""
+ if bool(self.ssh_keyfile):
+ pubkey = commander.cmd_raises(f"ssh-keygen -y -f {self.ssh_keyfile}")
+ assert pubkey, f"Can't extract public key from {self.ssh_keyfile}"
+ pubkey = pubkey.strip()
+ ssh_auth_s = f'ssh_authorized_keys: ["{pubkey}"]'
+
+ user = cc.get("user", "root")
+ password = cc.get("password", "admin")
+ if user != "root":
+ root_password = "admin"
+ else:
+ root_password = password
+
+ udata = f"""#cloud-config
+disable_root: 0
+ssh_pwauth: 1
+hostname: {self.name}
+runcmd:
+ - systemctl enable serial-getty@ttyS1.service
+ - systemctl start serial-getty@ttyS1.service
+ - systemctl enable serial-getty@ttyS2.service
+ - systemctl start serial-getty@ttyS2.service
+ - systemctl enable serial-getty@hvc0.service
+ - systemctl start serial-getty@hvc0.service
+ - systemctl enable serial-getty@hvc1.service
+ - systemctl start serial-getty@hvc1.service
+users:
+ - name: root
+ lock_passwd: false
+ plain_text_passwd: "{root_password}"
+ {ssh_auth_s}
+"""
+ if user != "root":
+ udata += """
+ - name: {user}
+ lock_passwd: false
+ plain_text_passwd: "{password}"
+ {ssh_auth_s}
+"""
+ #
+ # cloud-init: network-config
+ #
+ ndata = self._gen_network_config()
+
+ #
+ # Generate cloud-init files
+ #
+ cidir = self.rundir.joinpath("ci-data")
+ commander.cmd_raises(f"mkdir -p {cidir}")
+
+ with open(cidir.joinpath("meta-data"), "w+", encoding="utf-8") as f:
+ f.write(mdata)
+ with open(cidir.joinpath("user-data"), "w+", encoding="utf-8") as f:
+ f.write(udata)
+ files = "meta-data user-data"
+ if ndata:
+ files += " network-config"
+ with open(cidir.joinpath("network-config"), "w+", encoding="utf-8") as f:
+ f.write(ndata)
+ if geniso:
+ commander.cmd_raises(
+ f"cd {cidir} && "
+ f'genisoimage -output "{cipath}" -volid cidata'
+ f" -joliet -rock {files}"
+ )
+ else:
+ commander.cmd_raises(f'cd {cidir} && mkfs.vfat -n cidata "{cipath}"')
+ commander.cmd_raises(f'cd {cidir} && mcopy -oi "{cipath}" {files}')
+
+ #
+ # Generate cloud-init disk
+ #
+ return cipath
+
async def launch(self):
"""Launch qemu."""
self.logger.info("%s: Launch Qemu", self)
@@ -2367,11 +2534,21 @@ class L3QemuVM(L3NodeMixin, LinuxNamespace):
diskpath = os.path.join(self.unet.config_dirname, diskpath)
if dtpl and (not disk or not os.path.exists(diskpath)):
+ basename = os.path.basename(dtpl)
+ confdir = self.unet.config_dirname
+ if re.match("(https|http|ftp|tftp):.*", dtpl):
+ await self.unet.async_cmd_raises_once(
+ f"cd {confdir} && (test -e {basename} || curl -fLO {dtpl})"
+ )
+ dtplpath = os.path.join(confdir, basename)
+
if not disk:
- disk = qc["disk"] = f"{self.name}-{os.path.basename(dtpl)}"
+ disk = qc["disk"] = f"{self.name}-{basename}"
diskpath = os.path.join(self.rundir, disk)
+
if self.path_exists(diskpath):
logging.debug("Disk '%s' file exists, using.", diskpath)
+
else:
if dtplpath[0] != "/":
dtplpath = os.path.join(self.unet.config_dirname, dtpl)
@@ -2392,11 +2569,15 @@ class L3QemuVM(L3NodeMixin, LinuxNamespace):
args.extend(["-device", "ahci,id=ahci"])
args.extend(["-device", "ide-hd,bus=ahci.0,drive=sata-disk0"])
- cidiskpath = qc.get("cloud-init-disk")
- if cidiskpath:
- if cidiskpath[0] != "/":
- cidiskpath = os.path.join(self.unet.config_dirname, cidiskpath)
- args.extend(["-drive", f"file={cidiskpath},if=virtio,format=qcow2"])
+ if qc.get("cloud-init"):
+ cidiskpath = qc.get("cloud-init-disk")
+ if cidiskpath:
+ if cidiskpath[0] != "/":
+ cidiskpath = os.path.join(self.unet.config_dirname, cidiskpath)
+ else:
+ cidiskpath = self._gen_cloud_init()
+ diskfmt = "qcow2" if str(cidiskpath).endswith("qcow2") else "raw"
+ args.extend(["-drive", f"file={cidiskpath},if=virtio,format={diskfmt}"])
# args.extend(["-display", "vnc=0.0.0.0:40"])
@@ -2488,7 +2669,7 @@ class L3QemuVM(L3NodeMixin, LinuxNamespace):
if use_cmdcon:
confiles.append("_cmdcon")
- password = cc.get("password", "")
+ password = cc.get("password", "admin")
if self.disk_created:
password = cc.get("initial-password", password)
@@ -2764,9 +2945,11 @@ ff02::2\tip6-allrouters
# Disable IPv6
self.cmd_raises("sysctl -w net.ipv6.conf.all.autoconf=0")
self.cmd_raises("sysctl -w net.ipv6.conf.all.disable_ipv6=1")
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.forwarding=0")
else:
self.cmd_raises("sysctl -w net.ipv6.conf.all.autoconf=1")
self.cmd_raises("sysctl -w net.ipv6.conf.all.disable_ipv6=0")
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.forwarding=1")
# we really need overlay, but overlay-layers (used by overlay-images)
# counts on things being present in overlay so this temp stuff doesn't work.
@@ -2774,6 +2957,24 @@ ff02::2\tip6-allrouters
# # Let's hide podman details
# self.tmpfs_mount("/var/lib/containers/storage/overlay-containers")
+ def run_init_cmds(unet, key, on_host):
+ cmds = unet.topoconf.get(key, "")
+ cmds = cmds.replace("%CONFIGDIR%", str(unet.config_dirname))
+ cmds = cmds.replace("%RUNDIR%", str(unet.rundir))
+ cmds = cmds.strip()
+ if not cmds:
+ return
+
+ cmds += "\n"
+ c = commander if on_host else unet
+ o = c.cmd_raises(cmds)
+ self.logger.debug(
+ "run_init_cmds (on-host: %s): %s", on_host, cmd_error(0, o, "")
+ )
+
+ run_init_cmds(self, "initial-setup-host-cmd", True)
+ run_init_cmds(self, "initial-setup-cmd", False)
+
shellopt = self.cfgopt.getoption("--shell")
shellopt = shellopt if shellopt else ""
if shellopt == "all" or "." in shellopt.split(","):
@@ -3061,7 +3262,8 @@ done"""
if not rc:
continue
logging.info("Pulling missing image %s", image)
- aw = self.rootcmd.async_cmd_raises(f"podman pull {image}")
+
+ aw = self.rootcmd.async_cmd_raises_once(f"podman pull {image}")
tasks.append(asyncio.create_task(aw))
if not tasks:
return
diff --git a/tests/topotests/munet/testing/util.py b/tests/topotests/munet/testing/util.py
index 99687c0a83..02ff9bd69e 100644
--- a/tests/topotests/munet/testing/util.py
+++ b/tests/topotests/munet/testing/util.py
@@ -8,12 +8,17 @@
"""Utility functions useful when using munet testing functionailty in pytest."""
import asyncio
import datetime
+import fcntl
import functools
import logging
+import os
+import re
+import select
import sys
import time
from ..base import BaseMunet
+from ..base import Timeout
from ..cli import async_cli
@@ -23,6 +28,7 @@ from ..cli import async_cli
async def async_pause_test(desc=""):
+ """Pause the running of a test offering options for CLI or PDB."""
isatty = sys.stdout.isatty()
if not isatty:
desc = f" for {desc}" if desc else ""
@@ -49,11 +55,12 @@ async def async_pause_test(desc=""):
def pause_test(desc=""):
+ """Pause the running of a test offering options for CLI or PDB."""
asyncio.run(async_pause_test(desc))
def retry(retry_timeout, initial_wait=0, retry_sleep=2, expected=True):
- """decorator: retry while functions return is not None or raises an exception.
+ """Retry decorated function until it returns None, raises an exception, or timeout.
* `retry_timeout`: Retry for at least this many seconds; after waiting
initial_wait seconds
@@ -116,3 +123,91 @@ def retry(retry_timeout, initial_wait=0, retry_sleep=2, expected=True):
return func_retry
return _retry
+
+
+def readline(f, timeout=None):
+ """Read a line or timeout.
+
+ This function will take over the file object, the file object should not be used
+ outside of calling this function once you begin.
+
+ Return: A line, remaining buffer if EOF (subsequent calls will return ""), or None
+ for timeout.
+ """
+ fd = f.fileno()
+ if not hasattr(f, "munet_non_block_set"):
+ flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+ f.munet_non_block_set = True
+ f.munet_lines = []
+ f.munet_buf = ""
+
+ if f.munet_lines:
+ return f.munet_lines.pop(0)
+
+ timeout = Timeout(timeout)
+ remaining = timeout.remaining()
+ while remaining > 0:
+ ready, _, _ = select.select([fd], [], [], remaining)
+ if not ready:
+ return None
+
+ c = f.read()
+ if c is None:
+ logging.error("munet readline: unexpected None during read")
+ return None
+
+ if not c:
+ logging.debug("munet readline: got eof")
+ c = f.munet_buf
+ f.munet_buf = ""
+ return c
+
+ f.munet_buf += c
+ while "\n" in f.munet_buf:
+ a, f.munet_buf = f.munet_buf.split("\n", 1)
+ f.munet_lines.append(a + "\n")
+
+ if f.munet_lines:
+ return f.munet_lines.pop(0)
+
+ remaining = timeout.remaining()
+ return None
+
+
+def waitline(f, regex, timeout=120):
+ """Match a regex within lines from a file with a timeout.
+
+ This function will take over the file object (by calling `readline` above), the file
+ object should not be used outside of calling these functions once you begin.
+
+ Return: the match object or None.
+ """
+ timeo = Timeout(timeout)
+ while not timeo.is_expired():
+ line = readline(f, timeo.remaining())
+ if line is None:
+ break
+
+ if line == "":
+ logging.warning("waitline: got eof while matching '%s'", regex)
+ return None
+
+ assert line[-1] == "\n"
+ line = line[:-1]
+ if not line:
+ continue
+
+ logging.debug("waitline: searching: '%s' for '%s'", line, regex)
+ m = re.search(regex, line)
+ if m:
+ logging.debug("waitline: matched '%s'", m.group(0))
+ return m
+
+ logging.warning(
+ "Timeout while getting output matching '%s' within %ss (actual %ss)",
+ regex,
+ timeout,
+ timeo.elapsed(),
+ )
+ return None
diff --git a/tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py b/tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py
index 1488e610c8..2a77c3b223 100644
--- a/tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py
+++ b/tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py
@@ -135,12 +135,10 @@ def test_pim_asm_igmp_join_acl():
expected = {
"r1-eth0":{
"name":"r1-eth0",
- "224.0.1.40":"*",
"229.1.1.1":None
},
"r1-eth2":{
"name":"r1-eth2",
- "224.0.1.40":"*",
"229.1.1.1":None
}
}
@@ -166,9 +164,7 @@ def test_pim_asm_igmp_join_acl():
"sources":[
{
"source":"*",
- "timer":"--:--",
"forwarded":False,
- "uptime":"*"
}
]
}
@@ -227,8 +223,6 @@ def test_pim_asm_igmp_join_acl():
"source":"*",
"group":"229.1.1.1",
"primaryAddr":"10.0.20.2",
- "sockFd":"*",
- "upTime":"*"
}
]
}
@@ -286,13 +280,11 @@ def test_pim_ssm_igmp_join_acl():
expected = {
"r1-eth0":{
"name":"r1-eth0",
- "224.0.1.40":"*",
"229.1.1.1":None,
"232.1.1.1":None
},
"r1-eth2":{
"name":"r1-eth2",
- "224.0.1.40":"*",
"229.1.1.1":None,
"232.1.1.1":None
}
@@ -319,9 +311,7 @@ def test_pim_ssm_igmp_join_acl():
"sources":[
{
"source":"10.0.20.2",
- "timer":"*",
"forwarded":False,
- "uptime":"*"
}
]
}
@@ -397,9 +387,7 @@ def test_pim_ssm_igmp_join_acl():
"sources":[
{
"source":"10.0.20.2",
- "timer":"*",
"forwarded":False,
- "uptime":"*"
}
]
}
@@ -422,8 +410,6 @@ def test_pim_ssm_igmp_join_acl():
"source":"10.0.20.2",
"group":"232.1.1.1",
"primaryAddr":"10.0.20.2",
- "sockFd":"*",
- "upTime":"*"
}
]
}
@@ -491,9 +477,7 @@ def test_pim_ssm_igmp_join_acl():
"sources":[
{
"source":"10.0.40.4",
- "timer":"*",
"forwarded":False,
- "uptime":"*"
}
]
}
diff --git a/tools/frr-reload.py b/tools/frr-reload.py
index dba50b3c53..3ea63ce2a3 100755
--- a/tools/frr-reload.py
+++ b/tools/frr-reload.py
@@ -948,10 +948,14 @@ def bgp_remove_neighbor_cfg(lines_to_del, del_nbr_dict):
lines_to_del_to_del = []
for ctx_keys, line in lines_to_del:
+ # lines_to_del has following
+ # (('router bgp 100',), 'neighbor swp1.10 interface peer-group dpeergrp_2'),
+ # (('router bgp 100',), 'neighbor swp1.10 advertisement-interval 1'),
+ # (('router bgp 100',), 'no neighbor swp1.10 capability dynamic'),
if (
ctx_keys[0].startswith("router bgp")
and line
- and line.startswith("neighbor ")
+ and ((line.startswith("neighbor ") or line.startswith("no neighbor ")))
):
if ctx_keys[0] in del_nbr_dict:
for nbr in del_nbr_dict[ctx_keys[0]]:
diff --git a/zebra/zebra_nhg.c b/zebra/zebra_nhg.c
index 6a43fe0c84..0a79ed5590 100644
--- a/zebra/zebra_nhg.c
+++ b/zebra/zebra_nhg.c
@@ -3145,8 +3145,8 @@ backups_done:
remove = new_nhe;
- if (old_re && old_re->type == re->type &&
- old_re->instance == re->instance)
+ if (old_re && old_re->type == re->type && old_re->instance == re->instance &&
+ new_nhe != old_re->nhe)
new_nhe = zebra_nhg_rib_compare_old_nhe(rn, re, new_nhe,
old_re->nhe);