summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bgpd/bgp_attr.h8
-rw-r--r--bgpd/bgp_bfd.c11
-rw-r--r--bgpd/bgp_bmp.c829
-rw-r--r--bgpd/bgp_bmp.h20
-rw-r--r--bgpd/bgp_dump.c8
-rw-r--r--bgpd/bgp_fsm.c21
-rw-r--r--bgpd/bgp_mplsvpn.c28
-rw-r--r--bgpd/bgp_mplsvpn.h5
-rw-r--r--bgpd/bgp_network.c56
-rw-r--r--bgpd/bgp_network.h1
-rw-r--r--bgpd/bgp_packet.c110
-rw-r--r--bgpd/bgp_packet.h6
-rw-r--r--bgpd/bgp_route.c64
-rw-r--r--bgpd/bgp_route.h3
-rw-r--r--bgpd/bgp_routemap.c13
-rw-r--r--bgpd/bgp_script.c4
-rw-r--r--bgpd/bgp_snmp_bgp4.c18
-rw-r--r--bgpd/bgp_snmp_bgp4v2.c47
-rw-r--r--bgpd/bgp_trace.h3
-rw-r--r--bgpd/bgp_updgrp.h1
-rw-r--r--bgpd/bgp_updgrp_packet.c4
-rw-r--r--bgpd/bgp_vty.c53
-rw-r--r--bgpd/bgp_vty.h13
-rw-r--r--bgpd/bgp_zebra.c11
-rw-r--r--bgpd/bgpd.c22
-rw-r--r--bgpd/bgpd.h5
-rw-r--r--bgpd/rfapi/rfapi.c2
-rw-r--r--bgpd/rfapi/rfapi_import.c4
-rw-r--r--configure.ac53
-rw-r--r--doc/developer/building-doc.rst62
-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/developer/subdir.am1
-rw-r--r--doc/user/_static/overrides.js2
-rw-r--r--doc/user/about.rst26
-rw-r--r--doc/user/bmp.rst5
-rw-r--r--doc/user/pim.rst14
-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--lib/darr.h20
-rw-r--r--lib/frrscript.c2
-rw-r--r--lib/if.c128
-rw-r--r--lib/if.h10
-rw-r--r--lib/mgmt_be_client.c233
-rw-r--r--lib/mgmt_be_client.h2
-rw-r--r--lib/mgmt_msg_native.c3
-rw-r--r--lib/mgmt_msg_native.h1
-rw-r--r--lib/northbound.c5
-rw-r--r--lib/northbound.h93
-rw-r--r--lib/northbound_notif.c680
-rw-r--r--lib/northbound_oper.c96
-rw-r--r--lib/subdir.am2
-rw-r--r--lib/vrf.c48
-rw-r--r--lib/vrf.h3
-rw-r--r--lib/yang.c111
-rw-r--r--lib/yang.h60
-rw-r--r--m4/ax_lua.m492
-rw-r--r--mgmtd/mgmt_be_adapter.c46
-rw-r--r--mgmtd/mgmt_fe_adapter.c235
-rw-r--r--mgmtd/mgmt_fe_adapter.h7
-rw-r--r--mgmtd/mgmt_history.c1
-rw-r--r--mgmtd/mgmt_main.c7
-rw-r--r--mgmtd/mgmt_txn.c47
-rw-r--r--mgmtd/mgmt_txn.h10
-rw-r--r--ospfd/ospf_asbr.c3
-rw-r--r--pimd/pim_bsm.c41
-rw-r--r--pimd/pim_bsr_rpdb.c2
-rw-r--r--pimd/pim_cmd.c28
-rw-r--r--pimd/pim_igmp_mtrace.c13
-rw-r--r--pimd/pim_instance.h3
-rw-r--r--pimd/pim_mroute.c6
-rw-r--r--pimd/pim_msdp.c2
-rw-r--r--pimd/pim_nb.c9
-rw-r--r--pimd/pim_nb.h6
-rw-r--r--pimd/pim_nb_config.c55
-rw-r--r--pimd/pim_nht.c417
-rw-r--r--pimd/pim_nht.h30
-rw-r--r--pimd/pim_vty.c11
-rw-r--r--pimd/pim_zlookup.c26
-rw-r--r--pimd/pim_zlookup.h6
-rw-r--r--ripd/rip_main.c1
-rw-r--r--ripngd/ripng_main.c1
-rw-r--r--staticd/static_main.c1
-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/bgp_dynamic_capability/r1/frr.conf9
-rw-r--r--tests/topotests/bgp_dynamic_capability/r2/frr.conf12
-rw-r--r--tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_enhe.py215
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py0
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf35
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf48
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py142
-rwxr-xr-xtests/topotests/conftest.py2
-rw-r--r--tests/topotests/mgmt_notif/r1/frr.conf2
-rw-r--r--tests/topotests/mgmt_notif/r2/frr.conf2
-rw-r--r--tests/topotests/mgmt_notif/test_notif.py228
-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/ospf_metric_propagation/r1/frr.conf9
-rw-r--r--tests/topotests/ospf_metric_propagation/r1/show_ip_route_static.json31
-rw-r--r--tests/topotests/ospf_metric_propagation/r4/frr.conf5
-rw-r--r--tests/topotests/ospf_metric_propagation/test_ospf_metric_propagation.py6
-rw-r--r--tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py16
-rw-r--r--tests/topotests/pim_mrib/r1/frr.conf3
-rw-r--r--tests/topotests/pim_mrib/r2/frr.conf1
-rw-r--r--tests/topotests/pim_mrib/r3/frr.conf1
-rw-r--r--tests/topotests/pim_mrib/r4/frr.conf14
-rw-r--r--tests/topotests/pim_mrib/test_pim_mrib.py965
-rwxr-xr-xtools/frr-reload.py6
-rw-r--r--yang/frr-backend.yang102
-rw-r--r--yang/frr-pim.yang26
-rw-r--r--yang/subdir.am1
-rw-r--r--zebra/dplane_fpm_nl.c20
-rw-r--r--zebra/interface.c32
-rw-r--r--zebra/main.c4
-rw-r--r--zebra/rt_netlink.c460
-rw-r--r--zebra/rt_netlink.h13
-rw-r--r--zebra/zebra_dplane.c89
-rw-r--r--zebra/zebra_dplane.h26
-rw-r--r--zebra/zebra_nhg.c19
-rw-r--r--zebra/zebra_rib.c36
-rw-r--r--zebra/zebra_vrf.c19
-rw-r--r--zebra/zebra_vrf.h3
144 files changed, 7419 insertions, 1301 deletions
diff --git a/bgpd/bgp_attr.h b/bgpd/bgp_attr.h
index c5532f400..341c06251 100644
--- a/bgpd/bgp_attr.h
+++ b/bgpd/bgp_attr.h
@@ -323,7 +323,6 @@ struct attr {
/* rmap_change_flags definition */
#define BATTR_RMAP_IPV4_NHOP_CHANGED (1 << 0)
#define BATTR_RMAP_NEXTHOP_PEER_ADDRESS (1 << 1)
-#define BATTR_REFLECTED (1 << 2)
#define BATTR_RMAP_NEXTHOP_UNCHANGED (1 << 3)
#define BATTR_RMAP_IPV6_GLOBAL_NHOP_CHANGED (1 << 4)
#define BATTR_RMAP_IPV6_LL_NHOP_CHANGED (1 << 5)
@@ -604,10 +603,11 @@ static inline uint64_t bgp_aigp_metric_total(struct bgp_path_info *bpi)
{
uint64_t aigp = bgp_attr_get_aigp_metric(bpi->attr);
- if (bpi->nexthop)
- return aigp + bpi->nexthop->metric;
- else
+ /* Don't increment if it's locally sourced */
+ if (bpi->peer == bpi->peer->bgp->peer_self)
return aigp;
+
+ return bpi->extra ? (aigp + bpi->extra->igpmetric) : aigp;
}
static inline void bgp_attr_set_med(struct attr *attr, uint32_t med)
diff --git a/bgpd/bgp_bfd.c b/bgpd/bgp_bfd.c
index 50b00d21b..78759ae2b 100644
--- a/bgpd/bgp_bfd.c
+++ b/bgpd/bgp_bfd.c
@@ -188,7 +188,7 @@ void bgp_peer_bfd_update_source(struct peer *p)
}
}
} else {
- source = p->su_local;
+ source = p->connection->su_local;
}
/* Update peer's source/destination addresses. */
@@ -316,13 +316,14 @@ void bgp_peer_configure_bfd(struct peer *p, bool manual)
/* Configure session with basic BGP peer data. */
if (p->connection->su.sa.sa_family == AF_INET)
bfd_sess_set_ipv4_addrs(p->bfd_config->session,
- p->su_local ? &p->su_local->sin.sin_addr
- : NULL,
+ p->connection->su_local
+ ? &p->connection->su_local->sin.sin_addr
+ : NULL,
&p->connection->su.sin.sin_addr);
else
bfd_sess_set_ipv6_addrs(p->bfd_config->session,
- p->su_local
- ? &p->su_local->sin6.sin6_addr
+ p->connection->su_local
+ ? &p->connection->su_local->sin6.sin6_addr
: NULL,
&p->connection->su.sin6.sin6_addr);
diff --git a/bgpd/bgp_bmp.c b/bgpd/bgp_bmp.c
index 036bece35..21f921255 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);
@@ -496,29 +518,29 @@ static struct stream *bmp_peerstate(struct peer *peer, bool down)
/* Local Address (16 bytes) */
if (is_locrib)
stream_put(s, 0, 16);
- else if (peer->su_local->sa.sa_family == AF_INET6)
- stream_put(s, &peer->su_local->sin6.sin6_addr, 16);
- else if (peer->su_local->sa.sa_family == AF_INET) {
+ else if (peer->connection->su_local->sa.sa_family == AF_INET6)
+ stream_put(s, &peer->connection->su_local->sin6.sin6_addr, 16);
+ else if (peer->connection->su_local->sa.sa_family == AF_INET) {
stream_putl(s, 0);
stream_putl(s, 0);
stream_putl(s, 0);
- stream_put_in_addr(s, &peer->su_local->sin.sin_addr);
+ stream_put_in_addr(s, &peer->connection->su_local->sin.sin_addr);
}
/* Local Port, Remote Port */
- if (!peer->su_local || is_locrib)
+ if (!peer->connection->su_local || is_locrib)
stream_putw(s, 0);
- else if (peer->su_local->sa.sa_family == AF_INET6)
- stream_putw(s, htons(peer->su_local->sin6.sin6_port));
- else if (peer->su_local->sa.sa_family == AF_INET)
- stream_putw(s, htons(peer->su_local->sin.sin_port));
+ else if (peer->connection->su_local->sa.sa_family == AF_INET6)
+ stream_putw(s, htons(peer->connection->su_local->sin6.sin6_port));
+ else if (peer->connection->su_local->sa.sa_family == AF_INET)
+ stream_putw(s, htons(peer->connection->su_local->sin.sin_port));
- if (!peer->su_remote || is_locrib)
+ if (!peer->connection->su_remote || is_locrib)
stream_putw(s, 0);
- else if (peer->su_remote->sa.sa_family == AF_INET6)
- stream_putw(s, htons(peer->su_remote->sin6.sin6_port));
- else if (peer->su_remote->sa.sa_family == AF_INET)
- stream_putw(s, htons(peer->su_remote->sin.sin_port));
+ else if (peer->connection->su_remote->sa.sa_family == AF_INET6)
+ stream_putw(s, htons(peer->connection->su_remote->sin6.sin6_port));
+ else if (peer->connection->su_remote->sa.sa_family == AF_INET)
+ stream_putw(s, htons(peer->connection->su_remote->sin.sin_port));
/* TODO craft message with fields & capabilities for loc-rib */
static const uint8_t dummy_open[] = {
@@ -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 d45a4278f..d81b8f9b0 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_dump.c b/bgpd/bgp_dump.c
index 53b521248..e71835d1c 100644
--- a/bgpd/bgp_dump.c
+++ b/bgpd/bgp_dump.c
@@ -480,8 +480,8 @@ static void bgp_dump_common(struct stream *obuf, struct peer *peer,
stream_put(obuf, &peer->connection->su.sin.sin_addr,
IPV4_MAX_BYTELEN);
- if (peer->su_local)
- stream_put(obuf, &peer->su_local->sin.sin_addr,
+ if (peer->connection->su_local)
+ stream_put(obuf, &peer->connection->su_local->sin.sin_addr,
IPV4_MAX_BYTELEN);
else
stream_put(obuf, empty, IPV4_MAX_BYTELEN);
@@ -494,8 +494,8 @@ static void bgp_dump_common(struct stream *obuf, struct peer *peer,
stream_put(obuf, &peer->connection->su.sin6.sin6_addr,
IPV6_MAX_BYTELEN);
- if (peer->su_local)
- stream_put(obuf, &peer->su_local->sin6.sin6_addr,
+ if (peer->connection->su_local)
+ stream_put(obuf, &peer->connection->su_local->sin6.sin6_addr,
IPV6_MAX_BYTELEN);
else
stream_put(obuf, empty, IPV6_MAX_BYTELEN);
diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c
index 3d02214ca..0e3ed9f0d 100644
--- a/bgpd/bgp_fsm.c
+++ b/bgpd/bgp_fsm.c
@@ -1722,8 +1722,8 @@ bgp_connect_success(struct peer_connection *connection)
if (bgp_debug_neighbor_events(peer)) {
if (!CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER))
- zlog_debug("%s open active, local address %pSU",
- peer->host, peer->su_local);
+ zlog_debug("%s open active, local address %pSU", peer->host,
+ connection->su_local);
else
zlog_debug("%s passive open", peer->host);
}
@@ -1768,8 +1768,8 @@ bgp_connect_success_w_delayopen(struct peer_connection *connection)
if (bgp_debug_neighbor_events(peer)) {
if (!CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER))
- zlog_debug("%s open active, local address %pSU",
- peer->host, peer->su_local);
+ zlog_debug("%s open active, local address %pSU", peer->host,
+ connection->su_local);
else
zlog_debug("%s passive open", peer->host);
}
@@ -1819,14 +1819,13 @@ static void bgp_connect_in_progress_update_connection(struct peer_connection *co
{
struct peer *peer = connection->peer;
- bgp_updatesockname(peer, connection);
- if (!peer->su_remote && !BGP_CONNECTION_SU_UNSPEC(peer->connection)) {
+ if (!connection->su_remote && !BGP_CONNECTION_SU_UNSPEC(connection)) {
/* if connect initiated, then dest port and dest addresses are well known */
- peer->su_remote = sockunion_dup(&connection->su);
- if (sockunion_family(peer->su_remote) == AF_INET)
- peer->su_remote->sin.sin_port = htons(peer->port);
- else if (sockunion_family(peer->su_remote) == AF_INET6)
- peer->su_remote->sin6.sin6_port = htons(peer->port);
+ connection->su_remote = sockunion_dup(&connection->su);
+ if (sockunion_family(connection->su_remote) == AF_INET)
+ connection->su_remote->sin.sin_port = htons(peer->port);
+ else if (sockunion_family(connection->su_remote) == AF_INET6)
+ connection->su_remote->sin6.sin6_port = htons(peer->port);
}
}
diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c
index b96c287f8..46e529f03 100644
--- a/bgpd/bgp_mplsvpn.c
+++ b/bgpd/bgp_mplsvpn.c
@@ -1312,8 +1312,8 @@ leak_update(struct bgp *to_bgp, struct bgp_dest *bn,
else
bgp_path_info_unset_flag(bn, new, BGP_PATH_VALID);
- bgp_aggregate_increment(to_bgp, p, new, afi, safi);
bgp_path_info_add(bn, new);
+ bgp_aggregate_increment(to_bgp, p, new, afi, safi);
bgp_process(to_bgp, bn, new, afi, safi);
@@ -1951,7 +1951,7 @@ void vpn_leak_from_vrf_update(struct bgp *to_bgp, /* to */
* because of loop checking.
*/
if (new_info)
- vpn_leak_to_vrf_update(from_bgp, new_info, NULL);
+ vpn_leak_to_vrf_update(from_bgp, new_info, NULL, path_vrf->peer);
else
bgp_dest_unlock_node(bn);
}
@@ -2143,10 +2143,10 @@ static struct bgp *bgp_lookup_by_rd(struct bgp_path_info *bpi,
return NULL;
}
-static void vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */
+static void vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */
struct bgp *from_bgp, /* from */
- struct bgp_path_info *path_vpn,
- struct prefix_rd *prd)
+ struct bgp_path_info *path_vpn, struct prefix_rd *prd,
+ struct peer *from)
{
const struct prefix *p = bgp_dest_get_prefix(path_vpn->net);
afi_t afi = family2afi(p->family);
@@ -2231,6 +2231,12 @@ static void vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */
/* Check if leaked route has our asn. If so, don't import it. */
if (CHECK_FLAG(peer->af_flags[afi][SAFI_MPLS_VPN], PEER_FLAG_ALLOWAS_IN))
aspath_loop_count = peer->allowas_in[afi][SAFI_MPLS_VPN];
+ else if (peer == peer->bgp->peer_self && from)
+ /* If this is an import from one VRF to another and the source
+ * VRF's peer has allowas-in applied, respect it.
+ */
+ aspath_loop_count = from->allowas_in[afi][SAFI_UNICAST];
+
if (aspath_loop_check(path_vpn->attr->aspath, to_bgp->as) > aspath_loop_count) {
for (bpi = bgp_dest_get_bgp_path_info(bn); bpi;
bpi = bpi->next) {
@@ -2511,9 +2517,8 @@ bool vpn_leak_to_vrf_no_retain_filter_check(struct bgp *from_bgp,
return true;
}
-void vpn_leak_to_vrf_update(struct bgp *from_bgp,
- struct bgp_path_info *path_vpn,
- struct prefix_rd *prd)
+void vpn_leak_to_vrf_update(struct bgp *from_bgp, struct bgp_path_info *path_vpn,
+ struct prefix_rd *prd, struct peer *peer)
{
struct listnode *mnode, *mnnode;
struct bgp *bgp;
@@ -2528,8 +2533,7 @@ void vpn_leak_to_vrf_update(struct bgp *from_bgp,
for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) {
if (!path_vpn->extra || !path_vpn->extra->vrfleak ||
path_vpn->extra->vrfleak->bgp_orig != bgp) { /* no loop */
- vpn_leak_to_vrf_update_onevrf(bgp, from_bgp, path_vpn,
- prd);
+ vpn_leak_to_vrf_update_onevrf(bgp, from_bgp, path_vpn, prd, peer);
}
}
}
@@ -2728,8 +2732,8 @@ void vpn_leak_to_vrf_update_all(struct bgp *to_bgp, struct bgp *vpn_from,
bpi->extra->vrfleak->bgp_orig == to_bgp)
continue;
- vpn_leak_to_vrf_update_onevrf(to_bgp, vpn_from,
- bpi, NULL);
+ vpn_leak_to_vrf_update_onevrf(to_bgp, vpn_from, bpi, NULL,
+ bpi->peer);
}
}
}
diff --git a/bgpd/bgp_mplsvpn.h b/bgpd/bgp_mplsvpn.h
index 18639fc69..56dd33f9b 100644
--- a/bgpd/bgp_mplsvpn.h
+++ b/bgpd/bgp_mplsvpn.h
@@ -67,9 +67,8 @@ extern bool vpn_leak_to_vrf_no_retain_filter_check(struct bgp *from_bgp,
struct attr *attr,
afi_t afi);
-extern void vpn_leak_to_vrf_update(struct bgp *from_bgp,
- struct bgp_path_info *path_vpn,
- struct prefix_rd *prd);
+extern void vpn_leak_to_vrf_update(struct bgp *from_bgp, struct bgp_path_info *path_vpn,
+ struct prefix_rd *prd, struct peer *peer);
extern void vpn_leak_to_vrf_withdraw(struct bgp_path_info *path_vpn);
diff --git a/bgpd/bgp_network.c b/bgpd/bgp_network.c
index f1bea1c18..af5d815d3 100644
--- a/bgpd/bgp_network.c
+++ b/bgpd/bgp_network.c
@@ -484,6 +484,9 @@ static void bgp_accept(struct event *thread)
/* Dynamic neighbor has been created, let it proceed */
connection1->fd = bgp_sock;
+ connection1->su_local = sockunion_getsockname(connection1->fd);
+ connection1->su_remote = sockunion_dup(&su);
+
if (bgp_set_socket_ttl(connection1) < 0) {
peer1->last_reset = PEER_DOWN_SOCKET_ERROR;
zlog_err("%s: Unable to set min/max TTL on peer %s (dynamic), error received: %s(%d)",
@@ -623,7 +626,10 @@ static void bgp_accept(struct event *thread)
peer->doppelganger = peer1;
peer1->doppelganger = peer;
+
connection->fd = bgp_sock;
+ connection->su_local = sockunion_getsockname(connection->fd);
+ connection->su_remote = sockunion_dup(&su);
if (bgp_set_socket_ttl(connection) < 0)
if (bgp_debug_neighbor_events(peer))
@@ -857,24 +863,28 @@ enum connect_result bgp_connect(struct peer_connection *connection)
peer->host, connection->fd);
/* Connect to the remote peer. */
- return sockunion_connect(connection->fd, &connection->su,
- htons(peer->port), ifindex);
-}
+ enum connect_result res;
-void bgp_updatesockname(struct peer *peer, struct peer_connection *connection)
-{
- if (peer->su_local) {
- sockunion_free(peer->su_local);
- peer->su_local = NULL;
- }
+ res = sockunion_connect(connection->fd, &connection->su, htons(peer->port), ifindex);
- if (peer->su_remote) {
- sockunion_free(peer->su_remote);
- peer->su_remote = NULL;
+ if (connection->su_remote)
+ sockunion_free(connection->su_remote);
+
+ connection->su_remote = sockunion_dup(&connection->su);
+ switch (connection->su.sa.sa_family) {
+ case AF_INET:
+ connection->su_remote->sin.sin_port = htons(peer->port);
+ break;
+ case AF_INET6:
+ connection->su_remote->sin6.sin6_port = htons(peer->port);
+ break;
}
- peer->su_local = sockunion_getsockname(connection->fd);
- peer->su_remote = sockunion_getpeername(connection->fd);
+ if (connection->su_local)
+ sockunion_free(connection->su_local);
+ connection->su_local = sockunion_getsockname(connection->fd);
+
+ return res;
}
/* After TCP connection is established. Get local address and port. */
@@ -882,17 +892,13 @@ int bgp_getsockname(struct peer_connection *connection)
{
struct peer *peer = connection->peer;
- bgp_updatesockname(peer, peer->connection);
-
- if (!bgp_zebra_nexthop_set(peer->su_local, peer->su_remote,
- &peer->nexthop, peer)) {
- flog_err(
- EC_BGP_NH_UPD,
- "%s: nexthop_set failed, local: %pSUp remote: %pSUp update_if: %s resetting connection - intf %s",
- peer->host, peer->su_local, peer->su_remote,
- peer->update_if ? peer->update_if : "(None)",
- peer->nexthop.ifp ? peer->nexthop.ifp->name
- : "(Unknown)");
+ if (!bgp_zebra_nexthop_set(connection->su_local, connection->su_remote, &peer->nexthop,
+ peer)) {
+ flog_err(EC_BGP_NH_UPD,
+ "%s: nexthop_set failed, local: %pSUp remote: %pSUp update_if: %s resetting connection - intf %s",
+ peer->host, connection->su_local, connection->su_remote,
+ peer->update_if ? peer->update_if : "(None)",
+ peer->nexthop.ifp ? peer->nexthop.ifp->name : "(Unknown)");
return -1;
}
return 0;
diff --git a/bgpd/bgp_network.h b/bgpd/bgp_network.h
index ed1a72ec8..a2f4851f1 100644
--- a/bgpd/bgp_network.h
+++ b/bgpd/bgp_network.h
@@ -23,7 +23,6 @@ extern void bgp_close_vrf_socket(struct bgp *bgp);
extern void bgp_close(void);
extern enum connect_result bgp_connect(struct peer_connection *connection);
extern int bgp_getsockname(struct peer_connection *connection);
-extern void bgp_updatesockname(struct peer *peer, struct peer_connection *connection);
extern int bgp_md5_set_prefix(struct bgp *bgp, struct prefix *p,
const char *password);
diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c
index c5e390b04..ca2e8de04 100644
--- a/bgpd/bgp_packet.c
+++ b/bgpd/bgp_packet.c
@@ -1620,9 +1620,38 @@ void bgp_capability_send(struct peer *peer, afi_t afi, safi_t safi,
case CAPABILITY_CODE_AS4:
case CAPABILITY_CODE_DYNAMIC:
case CAPABILITY_CODE_ENHANCED_RR:
- case CAPABILITY_CODE_ENHE:
case CAPABILITY_CODE_EXT_MESSAGE:
break;
+ case CAPABILITY_CODE_ENHE:
+ FOREACH_AFI_SAFI (afi, safi) {
+ if (!peer->afc[afi][safi])
+ continue;
+
+ bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi);
+
+ if (CHECK_FLAG(peer->flags, PEER_FLAG_CAPABILITY_ENHE) &&
+ peer->connection->su.sa.sa_family == AF_INET6 && afi == AFI_IP &&
+ (safi == SAFI_UNICAST || safi == SAFI_MPLS_VPN ||
+ safi == SAFI_LABELED_UNICAST)) {
+ stream_putc(s, action);
+ stream_putc(s, CAPABILITY_CODE_ENHE);
+ stream_putc(s, CAPABILITY_CODE_ENHE_LEN);
+ stream_putw(s, pkt_afi);
+ stream_putw(s, pkt_safi);
+ stream_putw(s, afi_int2iana(AFI_IP6));
+
+ COND_FLAG(peer->af_cap[AFI_IP][safi], PEER_CAP_ENHE_AF_ADV,
+ action == CAPABILITY_ACTION_SET);
+
+ if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_RCV))
+ COND_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_NEGO,
+ action == CAPABILITY_ACTION_SET);
+ }
+ }
+ COND_FLAG(peer->cap, PEER_CAP_ENHE_ADV, action == CAPABILITY_ACTION_SET);
+ update_group_adjust_peer_afs(peer);
+ bgp_announce_route_all(peer);
+ break;
case CAPABILITY_CODE_ROLE:
stream_putc(s, action);
stream_putc(s, CAPABILITY_CODE_ROLE);
@@ -3321,6 +3350,81 @@ ignore:
}
}
+static void bgp_dynamic_capability_enhe(uint8_t *pnt, int action, struct capability_header *hdr,
+ struct peer *peer)
+{
+ uint8_t *data = pnt + 3;
+ uint8_t *end = data + hdr->length;
+ size_t len = end - data;
+
+ if (data + CAPABILITY_CODE_ENHE_LEN > end) {
+ flog_warn(EC_BGP_CAPABILITY_INVALID_LENGTH,
+ "Extended NH: Received invalid length %zu, less than %d", len,
+ CAPABILITY_CODE_ENHE_LEN);
+ return;
+ }
+
+ if (action == CAPABILITY_ACTION_SET) {
+ if (hdr->length % CAPABILITY_CODE_ENHE_LEN) {
+ flog_warn(EC_BGP_CAPABILITY_INVALID_LENGTH,
+ "Extended NH: Received invalid length %d, non-multiple of %d",
+ hdr->length, CAPABILITY_CODE_ENHE_LEN);
+ return;
+ }
+
+ while (data + CAPABILITY_CODE_ENHE_LEN <= end) {
+ afi_t afi;
+ safi_t safi;
+ afi_t nh_afi;
+ struct bgp_enhe_capability bec = {};
+
+ memcpy(&bec, data, sizeof(bec));
+ afi = ntohs(bec.afi);
+ safi = ntohs(bec.safi);
+ nh_afi = afi_iana2int(ntohs(bec.nh_afi));
+
+ /* RFC 5549 specifies use of this capability only for IPv4 AFI,
+ * with the Nexthop AFI being IPv6. A future spec may introduce
+ * other possibilities, so we ignore other values with a log.
+ * Also, only SAFI_UNICAST and SAFI_LABELED_UNICAST are currently
+ * supported (and expected).
+ */
+ if (afi != AFI_IP || nh_afi != AFI_IP6 ||
+ !(safi == SAFI_UNICAST || safi == SAFI_MPLS_VPN ||
+ safi == SAFI_LABELED_UNICAST)) {
+ flog_warn(EC_BGP_CAPABILITY_INVALID_DATA,
+ "%s Unexpected afi/safi/next-hop afi: %s/%s/%u in Extended Next-hop capability, ignoring",
+ peer->host, afi2str(afi), safi2str(safi), nh_afi);
+ goto ignore;
+ }
+
+ SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_RCV);
+
+ if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_ADV))
+ SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_NEGO);
+
+ignore:
+ data += CAPABILITY_CODE_ENHE_LEN;
+ }
+
+ SET_FLAG(peer->cap, PEER_CAP_ENHE_RCV);
+ update_group_adjust_peer_afs(peer);
+ bgp_announce_route_all(peer);
+ } else {
+ afi_t afi;
+ safi_t safi;
+
+ UNSET_FLAG(peer->cap, PEER_CAP_ENHE_RCV);
+
+ FOREACH_AFI_SAFI (afi, safi) {
+ UNSET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_RCV);
+
+ if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_ADV))
+ UNSET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_NEGO);
+ }
+ }
+}
+
static void bgp_dynamic_capability_orf(uint8_t *pnt, int action,
struct capability_header *hdr,
struct peer *peer)
@@ -3943,9 +4047,11 @@ static int bgp_capability_msg_parse(struct peer *peer, uint8_t *pnt,
case CAPABILITY_CODE_AS4:
case CAPABILITY_CODE_DYNAMIC:
case CAPABILITY_CODE_ENHANCED_RR:
- case CAPABILITY_CODE_ENHE:
case CAPABILITY_CODE_EXT_MESSAGE:
break;
+ case CAPABILITY_CODE_ENHE:
+ bgp_dynamic_capability_enhe(pnt, action, hdr, peer);
+ break;
case CAPABILITY_CODE_ROLE:
bgp_dynamic_capability_role(pnt, action, peer);
break;
diff --git a/bgpd/bgp_packet.h b/bgpd/bgp_packet.h
index c266b1726..866b8f617 100644
--- a/bgpd/bgp_packet.h
+++ b/bgpd/bgp_packet.h
@@ -8,6 +8,12 @@
#include "hook.h"
+struct bgp_enhe_capability {
+ uint16_t afi;
+ uint16_t safi;
+ uint16_t nh_afi;
+};
+
DECLARE_HOOK(bgp_packet_dump,
(struct peer *peer, uint8_t type, bgp_size_t size,
struct stream *s),
diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c
index f520c2e2b..f27f0b97c 100644
--- a/bgpd/bgp_route.c
+++ b/bgpd/bgp_route.c
@@ -1576,17 +1576,17 @@ int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new,
}
/* locally configured routes to advertise do not have su_remote */
- if (peer_new->su_remote == NULL) {
+ if (peer_new->connection->su_remote == NULL) {
*reason = bgp_path_selection_local_configured;
return 0;
}
- if (peer_exist->su_remote == NULL) {
+ if (peer_exist->connection->su_remote == NULL) {
*reason = bgp_path_selection_local_configured;
return 1;
}
- ret = sockunion_cmp(peer_new->su_remote, peer_exist->su_remote);
+ ret = sockunion_cmp(peer_new->connection->su_remote, peer_exist->connection->su_remote);
if (ret == 1) {
*reason = bgp_path_selection_neighbor_ip;
@@ -2464,8 +2464,6 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi,
* announced to an EBGP peer (and they have the same attributes barring
* their nexthop).
*/
- if (ibgp_to_ibgp)
- SET_FLAG(attr->rmap_change_flags, BATTR_REFLECTED);
#define NEXTHOP_IS_V6 \
((safi != SAFI_ENCAP && safi != SAFI_MPLS_VPN \
@@ -5539,7 +5537,7 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id,
}
if ((SAFI_MPLS_VPN == safi)
&& (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) {
- vpn_leak_to_vrf_update(bgp, pi, prd);
+ vpn_leak_to_vrf_update(bgp, pi, prd, peer);
}
#ifdef ENABLE_BGP_VNC
@@ -5592,12 +5590,12 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id,
/* Addpath ID */
new->addpath_rx_id = addpath_id;
- /* Increment prefix */
- bgp_aggregate_increment(bgp, p, new, afi, safi);
-
/* Register new BGP information. */
bgp_path_info_add(dest, new);
+ /* Increment prefix */
+ bgp_aggregate_increment(bgp, p, new, afi, safi);
+
/* route_node_get lock */
bgp_dest_unlock_node(dest);
@@ -5633,7 +5631,7 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id,
}
if ((SAFI_MPLS_VPN == safi)
&& (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) {
- vpn_leak_to_vrf_update(bgp, new, prd);
+ vpn_leak_to_vrf_update(bgp, new, prd, peer);
}
#ifdef ENABLE_BGP_VNC
if (SAFI_MPLS_VPN == safi) {
@@ -7142,8 +7140,7 @@ void bgp_static_update(struct bgp *bgp, const struct prefix *p,
if (SAFI_MPLS_VPN == safi &&
bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) {
- vpn_leak_to_vrf_update(bgp, pi,
- &bgp_static->prd);
+ vpn_leak_to_vrf_update(bgp, pi, &bgp_static->prd, NULL);
}
#ifdef ENABLE_BGP_VNC
if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP ||
@@ -7187,12 +7184,12 @@ void bgp_static_update(struct bgp *bgp, const struct prefix *p,
bgp_nexthop_reachability_check(afi, safi, new, p, dest, bgp, bgp);
- /* Aggregate address increment. */
- bgp_aggregate_increment(bgp, p, new, afi, safi);
-
/* Register new BGP information. */
bgp_path_info_add(dest, new);
+ /* Aggregate address increment. */
+ bgp_aggregate_increment(bgp, p, new, afi, safi);
+
/* route_node_get lock */
bgp_dest_unlock_node(dest);
@@ -7207,7 +7204,7 @@ void bgp_static_update(struct bgp *bgp, const struct prefix *p,
if (SAFI_MPLS_VPN == safi &&
bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) {
- vpn_leak_to_vrf_update(bgp, new, &bgp_static->prd);
+ vpn_leak_to_vrf_update(bgp, new, &bgp_static->prd, NULL);
}
#ifdef ENABLE_BGP_VNC
if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN)
@@ -8874,6 +8871,27 @@ static int bgp_aggregate_unset(struct vty *vty, const char *prefix_str,
return CMD_SUCCESS;
}
+static bool bgp_aggregate_cmp_params(struct bgp_aggregate *aggregate, const char *rmap,
+ uint8_t summary_only, uint8_t as_set, uint8_t origin,
+ bool match_med, const char *suppress_map)
+{
+ if ((aggregate->origin != origin) || (aggregate->as_set != as_set) ||
+ (aggregate->match_med != match_med) || (aggregate->summary_only != summary_only))
+ return false;
+
+ if ((!rmap && aggregate->rmap.name) || (rmap && !aggregate->rmap.name) ||
+ (rmap && aggregate->rmap.name && !strmatch(rmap, aggregate->rmap.name)))
+ return false;
+
+ if ((!suppress_map && aggregate->suppress_map_name) ||
+ (suppress_map && !aggregate->suppress_map_name) ||
+ (suppress_map && aggregate->suppress_map_name &&
+ !strmatch(suppress_map, aggregate->suppress_map_name)))
+ return false;
+
+ return true;
+}
+
static int bgp_aggregate_set(struct vty *vty, const char *prefix_str, afi_t afi,
safi_t safi, const char *rmap,
uint8_t summary_only, uint8_t as_set,
@@ -8913,6 +8931,11 @@ static int bgp_aggregate_set(struct vty *vty, const char *prefix_str, afi_t afi,
aggregate = bgp_dest_get_bgp_aggregate_info(dest);
if (aggregate) {
+ /* Check for duplicate configs */
+ if (bgp_aggregate_cmp_params(aggregate, rmap, summary_only, as_set, origin,
+ match_med, suppress_map))
+ return CMD_SUCCESS;
+
vty_out(vty, "There is already same aggregate network.\n");
/* try to remove the old entry */
ret = bgp_aggregate_unset(vty, prefix_str, afi, safi);
@@ -8948,7 +8971,7 @@ static int bgp_aggregate_set(struct vty *vty, const char *prefix_str, afi_t afi,
}
aggregate->as_set = as_set_new;
- aggregate->safi = safi;
+
/* Override ORIGIN attribute if defined.
* E.g.: Cisco and Juniper set ORIGIN for aggregated address
* to IGP which is not what rfc4271 says.
@@ -9311,8 +9334,8 @@ void bgp_redistribute_add(struct bgp *bgp, struct prefix *p,
bgp->peer_self, new_attr, bn);
SET_FLAG(new->flags, BGP_PATH_VALID);
- bgp_aggregate_increment(bgp, p, new, afi, SAFI_UNICAST);
bgp_path_info_add(bn, new);
+ bgp_aggregate_increment(bgp, p, new, afi, SAFI_UNICAST);
bgp_dest_unlock_node(bn);
SET_FLAG(bn->flags, BGP_NODE_FIB_INSTALLED);
bgp_process(bgp, bn, new, afi, SAFI_UNICAST);
@@ -12003,9 +12026,8 @@ static int bgp_show_table(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t sa
|| type == bgp_show_type_damp_neighbor) {
union sockunion *su = output_arg;
- if (pi->peer == NULL
- || pi->peer->su_remote == NULL
- || !sockunion_same(pi->peer->su_remote, su))
+ if (pi->peer == NULL || pi->peer->connection->su_remote == NULL ||
+ !sockunion_same(pi->peer->connection->su_remote, su))
continue;
}
if (type == bgp_show_type_cidr_only) {
diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h
index 43033c8c3..7f4a3b918 100644
--- a/bgpd/bgp_route.h
+++ b/bgpd/bgp_route.h
@@ -492,9 +492,6 @@ struct bgp_aggregate {
/* Aggregate route's as-path. */
struct aspath *aspath;
- /* SAFI configuration. */
- safi_t safi;
-
/** MED value found in current group. */
uint32_t med_matched_value;
diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c
index f0bc84ce3..d8d1fa5dd 100644
--- a/bgpd/bgp_routemap.c
+++ b/bgpd/bgp_routemap.c
@@ -2066,10 +2066,9 @@ route_set_ip_nexthop(void *rule, const struct prefix *prefix, void *object)
BATTR_RMAP_NEXTHOP_UNCHANGED);
} else if (rins->peer_address) {
if ((CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IN)) &&
- peer->su_remote &&
- sockunion_family(peer->su_remote) == AF_INET) {
- path->attr->nexthop.s_addr =
- sockunion2ip(peer->su_remote);
+ peer->connection->su_remote &&
+ sockunion_family(peer->connection->su_remote) == AF_INET) {
+ path->attr->nexthop.s_addr = sockunion2ip(peer->connection->su_remote);
SET_FLAG(path->attr->flag, ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP));
} else if (CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_OUT)) {
/* The next hop value will be set as part of
@@ -4143,9 +4142,9 @@ route_set_ipv6_nexthop_peer(void *rule, const struct prefix *pfx, void *object)
path = object;
peer = path->peer;
- if ((CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IN)) &&
- peer->su_remote && sockunion_family(peer->su_remote) == AF_INET6) {
- peer_address = peer->su_remote->sin6.sin6_addr;
+ if ((CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IN)) && peer->connection->su_remote &&
+ sockunion_family(peer->connection->su_remote) == AF_INET6) {
+ peer_address = peer->connection->su_remote->sin6.sin6_addr;
/* Set next hop value and length in attribute. */
if (IN6_IS_ADDR_LINKLOCAL(&peer_address)) {
path->attr->mp_nexthop_local = peer_address;
diff --git a/bgpd/bgp_script.c b/bgpd/bgp_script.c
index b37385812..4874813be 100644
--- a/bgpd/bgp_script.c
+++ b/bgpd/bgp_script.c
@@ -37,9 +37,9 @@ void lua_pushpeer(lua_State *L, const struct peer *peer)
lua_setfield(L, -2, "last_readtime");
lua_pushinteger(L, peer->resettime);
lua_setfield(L, -2, "last_resettime");
- lua_pushsockunion(L, peer->su_local);
+ lua_pushsockunion(L, peer->connection->su_local);
lua_setfield(L, -2, "local_address");
- lua_pushsockunion(L, peer->su_remote);
+ lua_pushsockunion(L, peer->connection->su_remote);
lua_setfield(L, -2, "remote_address");
lua_pushinteger(L, peer->cap);
lua_setfield(L, -2, "capabilities");
diff --git a/bgpd/bgp_snmp_bgp4.c b/bgpd/bgp_snmp_bgp4.c
index 755777c16..32430f42a 100644
--- a/bgpd/bgp_snmp_bgp4.c
+++ b/bgpd/bgp_snmp_bgp4.c
@@ -266,25 +266,23 @@ static uint8_t *bgpPeerTable(struct variable *v, oid name[], size_t *length,
case BGPPEERNEGOTIATEDVERSION:
return SNMP_INTEGER(BGP_VERSION_4);
case BGPPEERLOCALADDR:
- if (peer->su_local)
- return SNMP_IPADDRESS(peer->su_local->sin.sin_addr);
+ if (peer->connection->su_local)
+ return SNMP_IPADDRESS(peer->connection->su_local->sin.sin_addr);
else
return SNMP_IPADDRESS(bgp_empty_addr);
case BGPPEERLOCALPORT:
- if (peer->su_local)
- return SNMP_INTEGER(
- ntohs(peer->su_local->sin.sin_port));
+ if (peer->connection->su_local)
+ return SNMP_INTEGER(ntohs(peer->connection->su_local->sin.sin_port));
else
return SNMP_INTEGER(0);
case BGPPEERREMOTEADDR:
- if (peer->su_remote)
- return SNMP_IPADDRESS(peer->su_remote->sin.sin_addr);
+ if (peer->connection->su_remote)
+ return SNMP_IPADDRESS(peer->connection->su_remote->sin.sin_addr);
else
return SNMP_IPADDRESS(bgp_empty_addr);
case BGPPEERREMOTEPORT:
- if (peer->su_remote)
- return SNMP_INTEGER(
- ntohs(peer->su_remote->sin.sin_port));
+ if (peer->connection->su_remote)
+ return SNMP_INTEGER(ntohs(peer->connection->su_remote->sin.sin_port));
else
return SNMP_INTEGER(0);
case BGPPEERREMOTEAS:
diff --git a/bgpd/bgp_snmp_bgp4v2.c b/bgpd/bgp_snmp_bgp4v2.c
index 5f36e2987..724eefe60 100644
--- a/bgpd/bgp_snmp_bgp4v2.c
+++ b/bgpd/bgp_snmp_bgp4v2.c
@@ -208,49 +208,42 @@ static uint8_t *bgpv2PeerTable(struct variable *v, oid name[], size_t *length,
case BGP4V2_PEER_INSTANCE:
return SNMP_INTEGER(peer->bgp->vrf_id);
case BGP4V2_PEER_LOCAL_ADDR_TYPE:
- if (peer->su_local)
- return SNMP_INTEGER(peer->su_local->sa.sa_family ==
- AF_INET
+ if (peer->connection->su_local)
+ return SNMP_INTEGER(peer->connection->su_local->sa.sa_family == AF_INET
? AFI_IP
: AFI_IP6);
else
return SNMP_INTEGER(0);
case BGP4V2_PEER_LOCAL_ADDR:
- if (peer->su_local)
- if (peer->su_local->sa.sa_family == AF_INET)
- return SNMP_IPADDRESS(
- peer->su_local->sin.sin_addr);
+ if (peer->connection->su_local)
+ if (peer->connection->su_local->sa.sa_family == AF_INET)
+ return SNMP_IPADDRESS(peer->connection->su_local->sin.sin_addr);
else
- return SNMP_IP6ADDRESS(
- peer->su_local->sin6.sin6_addr);
+ return SNMP_IP6ADDRESS(peer->connection->su_local->sin6.sin6_addr);
else
return SNMP_IPADDRESS(bgp_empty_addr);
case BGP4V2_PEER_REMOTE_ADDR_TYPE:
- if (peer->su_remote)
- return SNMP_INTEGER(peer->su_remote->sa.sa_family ==
- AF_INET
+ if (peer->connection->su_remote)
+ return SNMP_INTEGER(peer->connection->su_remote->sa.sa_family == AF_INET
? AFI_IP
: AFI_IP6);
else
return SNMP_INTEGER(0);
case BGP4V2_PEER_REMOTE_ADDR:
- if (peer->su_remote)
- if (peer->su_remote->sa.sa_family == AF_INET)
- return SNMP_IPADDRESS(
- peer->su_remote->sin.sin_addr);
+ if (peer->connection->su_remote)
+ if (peer->connection->su_remote->sa.sa_family == AF_INET)
+ return SNMP_IPADDRESS(peer->connection->su_remote->sin.sin_addr);
else
- return SNMP_IP6ADDRESS(
- peer->su_remote->sin6.sin6_addr);
+ return SNMP_IP6ADDRESS(peer->connection->su_remote->sin6.sin6_addr);
else
return SNMP_IPADDRESS(bgp_empty_addr);
case BGP4V2_PEER_LOCAL_PORT:
- if (peer->su_local)
- if (peer->su_local->sa.sa_family == AF_INET)
- return SNMP_INTEGER(
- ntohs(peer->su_local->sin.sin_port));
+ if (peer->connection->su_local)
+ if (peer->connection->su_local->sa.sa_family == AF_INET)
+ return SNMP_INTEGER(ntohs(peer->connection->su_local->sin.sin_port));
else
return SNMP_INTEGER(
- ntohs(peer->su_local->sin6.sin6_port));
+ ntohs(peer->connection->su_local->sin6.sin6_port));
else
return SNMP_INTEGER(0);
case BGP4V2_PEER_LOCAL_AS:
@@ -258,13 +251,13 @@ static uint8_t *bgpv2PeerTable(struct variable *v, oid name[], size_t *length,
case BGP4V2_PEER_LOCAL_IDENTIFIER:
return SNMP_IPADDRESS(peer->local_id);
case BGP4V2_PEER_REMOTE_PORT:
- if (peer->su_remote)
- if (peer->su_remote->sa.sa_family == AF_INET)
+ if (peer->connection->su_remote)
+ if (peer->connection->su_remote->sa.sa_family == AF_INET)
return SNMP_INTEGER(
- ntohs(peer->su_remote->sin.sin_port));
+ ntohs(peer->connection->su_remote->sin.sin_port));
else
return SNMP_INTEGER(
- ntohs(peer->su_remote->sin6.sin6_port));
+ ntohs(peer->connection->su_remote->sin6.sin6_port));
else
return SNMP_INTEGER(0);
case BGP4V2_PEER_REMOTE_AS:
diff --git a/bgpd/bgp_trace.h b/bgpd/bgp_trace.h
index 43bc7a5a1..ce8692063 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/bgpd/bgp_updgrp.h b/bgpd/bgp_updgrp.h
index d0fd226d9..6549c99e8 100644
--- a/bgpd/bgp_updgrp.h
+++ b/bgpd/bgp_updgrp.h
@@ -66,7 +66,6 @@ typedef struct {
#define BPKT_ATTRVEC_FLAGS_UPDATED (1 << 0)
#define BPKT_ATTRVEC_FLAGS_RMAP_NH_PEER_ADDRESS (1 << 1)
-#define BPKT_ATTRVEC_FLAGS_REFLECTED (1 << 2)
#define BPKT_ATTRVEC_FLAGS_RMAP_NH_UNCHANGED (1 << 3)
#define BPKT_ATTRVEC_FLAGS_RMAP_IPV4_NH_CHANGED (1 << 4)
#define BPKT_ATTRVEC_FLAGS_RMAP_IPV6_GNH_CHANGED (1 << 5)
diff --git a/bgpd/bgp_updgrp_packet.c b/bgpd/bgp_updgrp_packet.c
index 3ce136ef8..ec418f2b1 100644
--- a/bgpd/bgp_updgrp_packet.c
+++ b/bgpd/bgp_updgrp_packet.c
@@ -1284,10 +1284,6 @@ bpacket_vec_arr_inherit_attr_flags(struct bpacket_attr_vec_arr *vecarr,
SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags,
BPKT_ATTRVEC_FLAGS_RMAP_NH_PEER_ADDRESS);
- if (CHECK_FLAG(attr->rmap_change_flags, BATTR_REFLECTED))
- SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags,
- BPKT_ATTRVEC_FLAGS_REFLECTED);
-
if (CHECK_FLAG(attr->rmap_change_flags, BATTR_RMAP_NEXTHOP_UNCHANGED))
SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags,
BPKT_ATTRVEC_FLAGS_RMAP_NH_UNCHANGED);
diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c
index c6b09481b..33b220d3e 100644
--- a/bgpd/bgp_vty.c
+++ b/bgpd/bgp_vty.c
@@ -93,18 +93,6 @@ FRR_CFG_DEFAULT_BOOL(BGP_DETERMINISTIC_MED,
{ .val_bool = true, .match_profile = "datacenter", },
{ .val_bool = false },
);
-FRR_CFG_DEFAULT_ULONG(BGP_CONNECT_RETRY,
- { .val_ulong = 10, .match_profile = "datacenter", },
- { .val_ulong = BGP_DEFAULT_CONNECT_RETRY },
-);
-FRR_CFG_DEFAULT_ULONG(BGP_HOLDTIME,
- { .val_ulong = 9, .match_profile = "datacenter", },
- { .val_ulong = BGP_DEFAULT_KEEPALIVE },
-);
-FRR_CFG_DEFAULT_ULONG(BGP_KEEPALIVE,
- { .val_ulong = 3, .match_profile = "datacenter", },
- { .val_ulong = BGP_DEFAULT_KEEPALIVE },
-);
FRR_CFG_DEFAULT_BOOL(BGP_EBGP_REQUIRES_POLICY,
{ .val_bool = false, .match_profile = "datacenter", },
{ .val_bool = false, .match_version = "< 7.4", },
@@ -5989,13 +5977,17 @@ DEFUN (neighbor_capability_enhe,
{
int idx_peer = 1;
struct peer *peer;
+ int ret;
peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg);
if (peer && peer->conf_if)
return CMD_SUCCESS;
- return peer_flag_set_vty(vty, argv[idx_peer]->arg,
- PEER_FLAG_CAPABILITY_ENHE);
+ ret = peer_flag_set_vty(vty, argv[idx_peer]->arg, PEER_FLAG_CAPABILITY_ENHE);
+
+ bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, CAPABILITY_CODE_ENHE, CAPABILITY_ACTION_SET);
+
+ return ret;
}
DEFUN (no_neighbor_capability_enhe,
@@ -6009,6 +6001,7 @@ DEFUN (no_neighbor_capability_enhe,
{
int idx_peer = 2;
struct peer *peer;
+ int ret;
peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg);
if (peer && peer->conf_if) {
@@ -6018,8 +6011,12 @@ DEFUN (no_neighbor_capability_enhe,
return CMD_WARNING_CONFIG_FAILED;
}
- return peer_flag_unset_vty(vty, argv[idx_peer]->arg,
- PEER_FLAG_CAPABILITY_ENHE);
+ ret = peer_flag_unset_vty(vty, argv[idx_peer]->arg, PEER_FLAG_CAPABILITY_ENHE);
+
+ bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, CAPABILITY_CODE_ENHE,
+ CAPABILITY_ACTION_UNSET);
+
+ return ret;
}
/* neighbor capability software-version */
@@ -15857,15 +15854,15 @@ CPP_NOTICE("Remove `gracefulRestartCapability` JSON field")
}
/* Local address. */
- if (p->su_local) {
+ if (p->connection->su_local) {
if (use_json) {
json_object_string_addf(json_neigh, "hostLocal", "%pSU",
- p->su_local);
+ p->connection->su_local);
json_object_int_add(json_neigh, "portLocal",
- ntohs(p->su_local->sin.sin_port));
+ ntohs(p->connection->su_local->sin.sin_port));
} else
- vty_out(vty, "Local host: %pSU, Local port: %d\n",
- p->su_local, ntohs(p->su_local->sin.sin_port));
+ vty_out(vty, "Local host: %pSU, Local port: %d\n", p->connection->su_local,
+ ntohs(p->connection->su_local->sin.sin_port));
} else {
if (use_json) {
json_object_string_add(json_neigh, "hostLocal",
@@ -15875,16 +15872,16 @@ CPP_NOTICE("Remove `gracefulRestartCapability` JSON field")
}
/* Remote address. */
- if (p->su_remote) {
+ if (p->connection->su_remote) {
if (use_json) {
- json_object_string_addf(json_neigh, "hostForeign",
- "%pSU", p->su_remote);
+ json_object_string_addf(json_neigh, "hostForeign", "%pSU",
+ p->connection->su_remote);
json_object_int_add(json_neigh, "portForeign",
- ntohs(p->su_remote->sin.sin_port));
+ ntohs(p->connection->su_remote->sin.sin_port));
} else
vty_out(vty, "Foreign host: %pSU, Foreign port: %d\n",
- p->su_remote,
- ntohs(p->su_remote->sin.sin_port));
+ p->connection->su_remote,
+ ntohs(p->connection->su_remote->sin.sin_port));
} else {
if (use_json) {
json_object_string_add(json_neigh, "hostForeign",
@@ -15894,7 +15891,7 @@ CPP_NOTICE("Remove `gracefulRestartCapability` JSON field")
}
/* Nexthop display. */
- if (p->su_local) {
+ if (p->connection->su_local) {
if (use_json) {
json_object_string_addf(json_neigh, "nexthop", "%pI4",
&p->nexthop.v4);
diff --git a/bgpd/bgp_vty.h b/bgpd/bgp_vty.h
index f88f5c812..00a313507 100644
--- a/bgpd/bgp_vty.h
+++ b/bgpd/bgp_vty.h
@@ -10,6 +10,19 @@
#include "stream.h"
struct bgp;
+FRR_CFG_DEFAULT_ULONG(BGP_KEEPALIVE,
+ { .val_ulong = 3, .match_profile = "datacenter", },
+ { .val_ulong = BGP_DEFAULT_KEEPALIVE },
+);
+FRR_CFG_DEFAULT_ULONG(BGP_HOLDTIME,
+ { .val_ulong = 9, .match_profile = "datacenter", },
+ { .val_ulong = BGP_DEFAULT_HOLDTIME },
+);
+FRR_CFG_DEFAULT_ULONG(BGP_CONNECT_RETRY,
+ { .val_ulong = 10, .match_profile = "datacenter", },
+ { .val_ulong = BGP_DEFAULT_CONNECT_RETRY },
+);
+
#define BGP_INSTANCE_HELP_STR "BGP view\nBGP VRF\nView/VRF name\n"
#define BGP_INSTANCE_ALL_HELP_STR "BGP view\nBGP VRF\nAll Views/VRFs\n"
diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c
index 7ad9ce472..146630ec6 100644
--- a/bgpd/bgp_zebra.c
+++ b/bgpd/bgp_zebra.c
@@ -953,13 +953,10 @@ bgp_path_info_to_ipv6_nexthop(struct bgp_path_info *path, ifindex_t *ifindex)
*ifindex = path->attr->nh_ifindex;
} else {
/* Workaround for Cisco's nexthop bug. */
- if (IN6_IS_ADDR_UNSPECIFIED(
- &path->attr->mp_nexthop_global)
- && path->peer->su_remote
- && path->peer->su_remote->sa.sa_family
- == AF_INET6) {
- nexthop =
- &path->peer->su_remote->sin6.sin6_addr;
+ if (IN6_IS_ADDR_UNSPECIFIED(&path->attr->mp_nexthop_global) &&
+ path->peer->connection->su_remote &&
+ path->peer->connection->su_remote->sa.sa_family == AF_INET6) {
+ nexthop = &path->peer->connection->su_remote->sin6.sin6_addr;
if (IN6_IS_ADDR_LINKLOCAL(nexthop))
*ifindex = path->peer->nexthop.ifp
->ifindex;
diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c
index 05bc804db..c2254ae79 100644
--- a/bgpd/bgpd.c
+++ b/bgpd/bgpd.c
@@ -583,9 +583,9 @@ void bgp_timers_set(struct vty *vty, struct bgp *bgp, uint32_t keepalive,
/* mostly for completeness - CLI uses its own defaults */
void bgp_timers_unset(struct bgp *bgp)
{
- bgp->default_keepalive = BGP_DEFAULT_KEEPALIVE;
- bgp->default_holdtime = BGP_DEFAULT_HOLDTIME;
- bgp->default_connect_retry = BGP_DEFAULT_CONNECT_RETRY;
+ bgp->default_keepalive = DFLT_BGP_KEEPALIVE;
+ bgp->default_holdtime = DFLT_BGP_HOLDTIME;
+ bgp->default_connect_retry = DFLT_BGP_CONNECT_RETRY;
bgp->default_delayopen = BGP_DEFAULT_DELAYOPEN;
}
@@ -2768,14 +2768,14 @@ int peer_delete(struct peer *peer)
}
/* Local and remote addresses. */
- if (peer->su_local) {
- sockunion_free(peer->su_local);
- peer->su_local = NULL;
+ if (peer->connection->su_local) {
+ sockunion_free(peer->connection->su_local);
+ peer->connection->su_local = NULL;
}
- if (peer->su_remote) {
- sockunion_free(peer->su_remote);
- peer->su_remote = NULL;
+ if (peer->connection->su_remote) {
+ sockunion_free(peer->connection->su_remote);
+ peer->connection->su_remote = NULL;
}
/* Free filter related memory. */
@@ -4953,6 +4953,10 @@ static void peer_flag_modify_action(struct peer *peer, uint64_t flag)
peer->v_start = BGP_INIT_START_TIMER;
BGP_EVENT_ADD(peer->connection, BGP_Stop);
}
+ } else if (CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_RCV) &&
+ CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV) &&
+ flag == PEER_FLAG_CAPABILITY_ENHE) {
+ peer->last_reset = PEER_DOWN_CAPABILITY_CHANGE;
} else if (!peer_notify_config_change(peer->connection))
bgp_session_reset(peer);
}
diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h
index 65b268c4e..c72072852 100644
--- a/bgpd/bgpd.h
+++ b/bgpd/bgpd.h
@@ -1258,6 +1258,9 @@ struct peer_connection {
union sockunion su;
#define BGP_CONNECTION_SU_UNSPEC(connection) \
(connection->su.sa.sa_family == AF_UNSPEC)
+
+ union sockunion *su_local; /* Sockunion of local address. */
+ union sockunion *su_remote; /* Sockunion of remote address. */
};
extern struct peer_connection *bgp_peer_connection_new(struct peer *peer);
extern void bgp_peer_connection_free(struct peer_connection **connection);
@@ -1350,8 +1353,6 @@ struct peer {
char *update_if;
union sockunion *update_source;
- union sockunion *su_local; /* Sockunion of local address. */
- union sockunion *su_remote; /* Sockunion of remote address. */
bool shared_network; /* Is this peer shared same network. */
struct bgp_nexthop nexthop; /* Nexthop */
diff --git a/bgpd/rfapi/rfapi.c b/bgpd/rfapi/rfapi.c
index 61d154f1b..241cbcb35 100644
--- a/bgpd/rfapi/rfapi.c
+++ b/bgpd/rfapi/rfapi.c
@@ -1029,8 +1029,8 @@ void add_vnc_route(struct rfapi_descriptor *rfd, /* cookie, VPN UN addr, peer */
rfapiPrintBi(NULL, new);
}
- bgp_aggregate_increment(bgp, p, new, afi, safi);
bgp_path_info_add(bn, new);
+ bgp_aggregate_increment(bgp, p, new, afi, safi);
if (safi == SAFI_MPLS_VPN) {
struct bgp_dest *pdest = NULL;
diff --git a/bgpd/rfapi/rfapi_import.c b/bgpd/rfapi/rfapi_import.c
index 44dfc88cf..99d8bcfce 100644
--- a/bgpd/rfapi/rfapi_import.c
+++ b/bgpd/rfapi/rfapi_import.c
@@ -1931,8 +1931,8 @@ static void rfapiBgpInfoAttachSorted(struct agg_node *rn,
if (VNC_DEBUG(IMPORT_BI_ATTACH)) {
vnc_zlog_debug_verbose("%s: info_new->peer=%p", __func__,
info_new->peer);
- vnc_zlog_debug_verbose("%s: info_new->peer->su_remote=%p",
- __func__, info_new->peer->su_remote);
+ vnc_zlog_debug_verbose("%s: info_new->peer->su_remote=%p", __func__,
+ info_new->peer->connection->su_remote);
}
for (prev = NULL, next = rn->info; next;
diff --git a/configure.ac b/configure.ac
index e8036fcff..e04c0b6d4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -375,26 +375,45 @@ fi
AM_CONDITIONAL([SCRIPTING], [test "$enable_scripting" = "yes"])
if test "$enable_scripting" = "yes"; then
- AX_PROG_LUA([5.3], [5.4], [], [
- AC_MSG_ERROR([Lua 5.3 is required to build with Lua support. No other version is supported.])
+ AX_PROG_LUA([5.3], [], [], [
+ AC_MSG_ERROR([Lua >= 5.3 is required to build with Lua support. No other version is supported.])
])
AX_LUA_HEADERS([], [
- AC_MSG_ERROR([Lua 5.3 headers are required to build with Lua support. No other version is supported.])
+ AC_MSG_ERROR([Lua >= 5.3 headers are required to build with Lua support. No other version is supported.])
])
- PKG_CHECK_MODULES([LUA], [lua5.3], [
- AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
- LIBS="$LIBS $LUA_LIBS"
- SCRIPTING=true
- ], [
- AX_LUA_LIBS([
- AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
- LIBS="$LIBS $LUA_LIB"
- SCRIPTING=true
- ], [
- SCRIPTING=false
- AC_MSG_ERROR([Lua 5.3 libraries are required to build with Lua support. No other version is supported.])
- ])
- ])
+
+ for version in 5.3 5.4; do
+ PKG_CHECK_MODULES([LUA], [lua >= $version], [
+ AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
+ LIBS="$LIBS $LUA_LIBS"
+ SCRIPTING=true
+ break
+ ], [
+ PKG_CHECK_MODULES([LUA], [lua$version], [
+ AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
+ LIBS="$LIBS $LUA_LIBS"
+ SCRIPTING=true
+ break
+ ], [
+ PKG_CHECK_MODULES([LUA], [lua-$version], [
+ AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
+ LIBS="$LIBS $LUA_LIBS"
+ SCRIPTING=true
+ break
+ ], [])
+ ])
+ ])
+ done
+
+ if [ "$SCRIPTING" != "true" ]; then
+ AX_LUA_LIBS([
+ AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
+ LIBS="$LIBS $LUA_LIB"
+ SCRIPTING=true
+ ], [
+ AC_MSG_ERROR([Lua >= 5.3 libraries are required to build with Lua support. No other version is supported.])
+ ])
+ fi
fi
dnl the following flags go in CFLAGS rather than AC_CFLAGS since they make
diff --git a/doc/developer/building-doc.rst b/doc/developer/building-doc.rst
new file mode 100644
index 000000000..bf0544ccc
--- /dev/null
+++ b/doc/developer/building-doc.rst
@@ -0,0 +1,62 @@
+Building Documentation
+======================
+
+To build FRR documentation, first install the dependencies.
+Notice that if you plan to only build html documenation, you only
+need the package ``python3-sphinx``.
+
+.. code-block:: console
+
+ sudo apt-get install -y python3-sphinx \
+ texlive-latex-base texlive-latex-extra latexmk
+
+To prepate for building both user and developer documentation, do:
+
+.. code-block:: console
+
+ cd doc
+ make
+
+User documentation
+------------------
+
+To build html user documentation:
+
+.. code-block:: console
+
+ cd user
+ make html
+
+This will generate html documentation files under ``_build/html/``.
+With the main page named ``index.html``.
+
+PFD can then be built by:
+
+.. code-block:: console
+
+ cd user
+ make pdf
+
+The generated PDF file will be saved at ``_build/latex/FRR.pdf``
+
+Developer documentation
+-----------------------
+
+To build the developer documentation:
+
+.. code-block:: console
+
+ cd developer
+ make html
+
+This will generate html documentation files under ``_build/html/``.
+With the main page named ``index.html``.
+
+PFD can then be built by:
+
+.. code-block:: console
+
+ cd developer
+ make pdf
+
+The generated PDF file will be saved at ``_build/latex/FRR.pdf``
diff --git a/doc/developer/building-frr-for-ubuntu2004.rst b/doc/developer/building-frr-for-ubuntu2004.rst
index 3db97c4b2..19353e317 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 c898c3cd2..726cf0a91 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 000000000..e6b264993
--- /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 000000000..78b45e141
--- /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/developer/subdir.am b/doc/developer/subdir.am
index 652ee4e1a..bdf93a05c 100644
--- a/doc/developer/subdir.am
+++ b/doc/developer/subdir.am
@@ -28,6 +28,7 @@ dev_RSTFILES = \
doc/developer/building-frr-for-ubuntu1804.rst \
doc/developer/building-frr-for-ubuntu2004.rst \
doc/developer/building-frr-for-ubuntu2204.rst \
+ doc/developer/building-doc.rst \
doc/developer/building-libunwind-note.rst \
doc/developer/building-libyang.rst \
doc/developer/building.rst \
diff --git a/doc/user/_static/overrides.js b/doc/user/_static/overrides.js
index 73bf6123b..f6af539bf 100644
--- a/doc/user/_static/overrides.js
+++ b/doc/user/_static/overrides.js
@@ -5,7 +5,7 @@
*/
$(document).ready(function() {
$("span.mark:contains('Y')" ).addClass("mark-y" ).parent("td").addClass("mark");
- $("span.mark:contains('≥')" ).addClass("mark-geq").parent("td").addClass("mark");
+ $("span.mark:contains('>=')").addClass("mark-geq").parent("td").addClass("mark");
$("span.mark:contains('N')" ).addClass("mark-n" ).parent("td").addClass("mark");
$("span.mark:contains('CP')").addClass("mark-cp" ).parent("td").addClass("mark");
$("span.mark:contains('†')" ).addClass("mark-dag").parent("td").addClass("mark");
diff --git a/doc/user/about.rst b/doc/user/about.rst
index e16ed7e3c..d9470f5f3 100644
--- a/doc/user/about.rst
+++ b/doc/user/about.rst
@@ -153,7 +153,7 @@ feature you're interested in, it should be supported on your platform.
.. comment - the :mark:`X` pieces mesh with a little bit of JavaScript and
CSS in _static/overrides.{js,css} respectively. The JS code looks at the
- presence of the 'Y' 'N' '≥' '†' or 'CP' strings. This seemed to be the
+ presence of the 'Y' 'N' '>=' '†' or 'CP' strings. This seemed to be the
best / least intrusive way of getting a nice table in HTML. The table
will look somewhat shoddy on other sphinx targets like PDF or info (but
should still be readable.)
@@ -165,9 +165,9 @@ feature you're interested in, it should be supported on your platform.
+-----------------------------------+----------------+--------------+------------+------------+
| `zebra` | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
-| VRF | :mark:`≥4.8` | :mark:`N` | :mark:`N` | :mark:`N` |
+| VRF | :mark:`>=4.8` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
-| MPLS | :mark:`≥4.5` | :mark:`Y` | :mark:`N` | :mark:`N` |
+| MPLS | :mark:`>=4.5` | :mark:`Y` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
| `pbrd` (Policy Routing) | :mark:`Y` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
@@ -175,21 +175,21 @@ feature you're interested in, it should be supported on your platform.
+-----------------------------------+----------------+--------------+------------+------------+
| `bgpd` (BGP) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
-| VRF / L3VPN | :mark:`≥4.8` | :mark:`CP` | :mark:`CP` | :mark:`CP` |
+| VRF / L3VPN | :mark:`>=4.8` | :mark:`CP` | :mark:`CP` | :mark:`CP` |
| | :mark:`†4.3` | | | |
+-----------------------------------+----------------+--------------+------------+------------+
-| EVPN | :mark:`≥4.18` | :mark:`CP` | :mark:`CP` | :mark:`CP` |
+| EVPN | :mark:`>=4.18` | :mark:`CP` | :mark:`CP` | :mark:`CP` |
| | :mark:`†4.9` | | | |
+-----------------------------------+----------------+--------------+------------+------------+
| VNC (Virtual Network Control) | :mark:`CP` | :mark:`CP` | :mark:`CP` | :mark:`CP` |
+-----------------------------------+----------------+--------------+------------+------------+
| Flowspec | :mark:`CP` | :mark:`CP` | :mark:`CP` | :mark:`CP` |
+-----------------------------------+----------------+--------------+------------+------------+
-| `ldpd` (LDP) | :mark:`≥4.5` | :mark:`Y` | :mark:`N` | :mark:`N` |
+| `ldpd` (LDP) | :mark:`>=4.5` | :mark:`Y` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
-| VPWS / PW | :mark:`N` | :mark:`≥5.8` | :mark:`N` | :mark:`N` |
+| VPWS / PW | :mark:`N` | :mark:`>=5.8`| :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
-| VPLS | :mark:`N` | :mark:`≥5.8` | :mark:`N` | :mark:`N` |
+| VPLS | :mark:`N` | :mark:`>=5.8`| :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
| `nhrpd` (NHRP) | :mark:`Y` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
@@ -197,7 +197,7 @@ feature you're interested in, it should be supported on your platform.
+-----------------------------------+----------------+--------------+------------+------------+
| `ospfd` (OSPFv2) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
-| Segment Routing | :mark:`≥4.12` | :mark:`N` | :mark:`N` | :mark:`N` |
+| Segment Routing | :mark:`>=4.12` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
| `ospf6d` (OSPFv3) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
@@ -215,21 +215,21 @@ feature you're interested in, it should be supported on your platform.
+-----------------------------------+----------------+--------------+------------+------------+
| **Multicast Routing** | | | | |
+-----------------------------------+----------------+--------------+------------+------------+
-| `pimd` (PIM) | :mark:`≥4.19` | :mark:`N` | :mark:`Y` | :mark:`Y` |
+| `pimd` (PIM) | :mark:`>=4.19` | :mark:`N` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
| SSM (Source Specific) | :mark:`Y` | :mark:`N` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
| ASM (Any Source) | :mark:`Y` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
-| EVPN BUM Forwarding | :mark:`≥5.0` | :mark:`N` | :mark:`N` | :mark:`N` |
+| EVPN BUM Forwarding | :mark:`>=5.0` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
-| `vrrpd` (VRRP) | :mark:`≥5.1` | :mark:`N` | :mark:`N` | :mark:`N` |
+| `vrrpd` (VRRP) | :mark:`>=5.1` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
The indicators have the following semantics:
* :mark:`Y` - daemon/feature fully functional
-* :mark:`≥X.X` - fully functional with kernel version X.X or newer
+* :mark:`>=X.X` - fully functional with kernel version X.X or newer
* :mark:`†X.X` - restricted functionality or impaired performance with kernel version X.X or newer
* :mark:`CP` - control plane only (i.e. BGP route server / route reflector)
* :mark:`N` - daemon/feature not supported by operating system
diff --git a/doc/user/bmp.rst b/doc/user/bmp.rst
index 14d0849b3..07c3c1c8b 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/doc/user/pim.rst b/doc/user/pim.rst
index ff45f21b5..c139e6488 100644
--- a/doc/user/pim.rst
+++ b/doc/user/pim.rst
@@ -217,7 +217,7 @@ PIM Routers
never do SM over. This command is vrf aware, to configure for a vrf, specify
the vrf in the router pim block.
-.. clicmd:: rpf-lookup-mode MODE
+.. clicmd:: rpf-lookup-mode MODE [group-list PREFIX_LIST] [source-list PREFIX_LIST]
MODE sets the method used to perform RPF lookups. Supported modes:
@@ -246,6 +246,18 @@ PIM Routers
configured to make the configuration immune against possible changes in
what the default behavior is.
+ If a group and/or source prefix list is provided, then the RPF lookup mode
+ will only apply to source, group addresses that match the given prefix list(s).
+ Not all RPF lookups have a valid group address when performing a lookup, e.g. RPF
+ to an RP only does a lookup to the RP address and has no specific group.
+ Lookups that do not have a specific group will only use lookup modes that do not
+ specify a group-list.
+ A global rpf lookup mode that does not have a group or source list is always installed
+ and, as documented above, uses the ``mrib-then-urib`` mode by default.
+ This can be changed with an rpf-lookup-mode MODE that does not specify group or source lists.
+ There can be any number of rpf lookup modes, as long as the combination of group and source
+ list is unique.
+
.. warning::
Unreachable routes do not receive special treatment and do not cause
diff --git a/docker/ubuntu-ci/Dockerfile b/docker/ubuntu-ci/Dockerfile
index aaad3bc17..0bfcb5187 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 617192eb7..116b3c0e4 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 000000000..38ba0ee17
--- /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/lib/darr.h b/lib/darr.h
index 121e3dd14..084c2a103 100644
--- a/lib/darr.h
+++ b/lib/darr.h
@@ -571,16 +571,16 @@ void *__darr_resize(void *a, uint count, size_t esize, struct memtype *mt);
* Return:
* The dynamic_array D with the new string content.
*/
-#define darr_in_strcat(D, S) \
- ({ \
- uint __dlen = darr_strlen(D); \
- uint __slen = strlen(S); \
- darr_ensure_cap_mt(D, __dlen + __slen + 1, MTYPE_DARR_STR); \
- if (darr_len(D) == 0) \
- *darr_append(D) = 0; \
- memcpy(darr_last(D), (S), __slen + 1); \
- _darr_len(D) += __slen; \
- D; \
+#define darr_in_strcat(D, S) \
+ ({ \
+ uint __dlen = darr_strlen(D); \
+ uint __slen = strlen(S); \
+ darr_ensure_cap_mt(D, __dlen + __slen + 1, MTYPE_DARR_STR); \
+ if (darr_len(D) == 0) \
+ *darr_append(D) = 0; \
+ memcpy(&(D)[darr_strlen(D)] /* darr_last(D) clangSA :( */, (S), __slen + 1); \
+ _darr_len(D) += __slen; \
+ D; \
})
/**
diff --git a/lib/frrscript.c b/lib/frrscript.c
index 06460b014..8b068ba61 100644
--- a/lib/frrscript.c
+++ b/lib/frrscript.c
@@ -248,10 +248,12 @@ int _frrscript_call_lua(struct lua_function_state *lfs, int nargs)
zlog_err("Lua hook call '%s' : error handler error: %s",
lfs->name, lua_tostring(lfs->L, -1));
break;
+#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 503
case LUA_ERRGCMM:
zlog_err("Lua hook call '%s' : garbage collector error: %s",
lfs->name, lua_tostring(lfs->L, -1));
break;
+#endif
default:
zlog_err("Lua hook call '%s' : unknown error: %s", lfs->name,
lua_tostring(lfs->L, -1));
diff --git a/lib/if.c b/lib/if.c
index 864c82bbf..796929ef0 100644
--- a/lib/if.c
+++ b/lib/if.c
@@ -29,6 +29,10 @@
#include "admin_group.h"
#include "lib/if_clippy.c"
+
+/* Set by the owner (zebra). */
+bool if_notify_oper_changes;
+
DEFINE_MTYPE_STATIC(LIB, IF, "Interface");
DEFINE_MTYPE_STATIC(LIB, IFDESC, "Intf Desc");
DEFINE_MTYPE_STATIC(LIB, CONNECTED, "Connected");
@@ -208,6 +212,104 @@ void if_down_via_zapi(struct interface *ifp)
hook_call(if_down, ifp);
}
+void if_update_state_metric(struct interface *ifp, uint32_t metric)
+{
+ if (ifp->metric == metric)
+ return;
+ ifp->metric = metric;
+ if (ifp->state && if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "metric", "%u", ifp->metric);
+}
+
+void if_update_state_mtu(struct interface *ifp, uint mtu)
+{
+ if (ifp->mtu == mtu)
+ return;
+ ifp->mtu = mtu;
+ if (ifp->state && if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "mtu", "%u", ifp->mtu);
+}
+
+void if_update_state_mtu6(struct interface *ifp, uint mtu)
+{
+ if (ifp->mtu6 == mtu)
+ return;
+ ifp->mtu6 = mtu;
+ if (ifp->state && if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "mtu6", "%u", ifp->mtu);
+}
+
+void if_update_state_hw_addr(struct interface *ifp, const uint8_t *hw_addr, uint len)
+{
+ if (len == (uint)ifp->hw_addr_len && (len == 0 || !memcmp(hw_addr, ifp->hw_addr, len)))
+ return;
+ memcpy(ifp->hw_addr, hw_addr, len);
+ ifp->hw_addr_len = len;
+ if (ifp->state && if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "phy-address", "%pEA", (struct ethaddr *)ifp->hw_addr);
+}
+
+void if_update_state_speed(struct interface *ifp, uint32_t speed)
+{
+ if (ifp->speed == speed)
+ return;
+ ifp->speed = speed;
+ if (ifp->state && if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "speed", "%u", ifp->speed);
+}
+
+void if_update_state(struct interface *ifp)
+{
+ struct lyd_node *state = ifp->state;
+
+ if (!state || !if_notify_oper_changes)
+ return;
+
+ /*
+ * Remove top level container update when we have patch support, for now
+ * this keeps us from generating 6 separate REPLACE messages though.
+ */
+ // nb_op_update(state, ".", NULL);
+ nb_op_updatef(state, "if-index", "%d", ifp->ifindex);
+ nb_op_updatef(state, "mtu", "%u", ifp->mtu);
+ nb_op_updatef(state, "mtu6", "%u", ifp->mtu);
+ nb_op_updatef(state, "speed", "%u", ifp->speed);
+ nb_op_updatef(state, "metric", "%u", ifp->metric);
+ nb_op_updatef(state, "phy-address", "%pEA", (struct ethaddr *)ifp->hw_addr);
+}
+
+static void if_update_state_remove(struct interface *ifp)
+{
+ if (!if_notify_oper_changes || ifp->name[0] == 0)
+ return;
+
+ if (vrf_is_backend_netns())
+ nb_op_update_delete_pathf(NULL, "/frr-interface:lib/interface[name=\"%s:%s\"]/state",
+ ifp->vrf->name, ifp->name);
+ else
+ nb_op_update_delete_pathf(NULL, "/frr-interface:lib/interface[name=\"%s\"]/state",
+ ifp->name);
+ if (ifp->state) {
+ lyd_free_all(ifp->state);
+ ifp->state = NULL;
+ }
+}
+
+static void if_update_state_add(struct interface *ifp)
+{
+ if (!if_notify_oper_changes || ifp->name[0] == 0)
+ return;
+
+ if (vrf_is_backend_netns())
+ ifp->state = nb_op_update_pathf(NULL,
+ "/frr-interface:lib/interface[name=\"%s:%s\"]/state",
+ NULL, ifp->vrf->name, ifp->name);
+ else
+ ifp->state = nb_op_update_pathf(NULL,
+ "/frr-interface:lib/interface[name=\"%s\"]/state",
+ NULL, ifp->name);
+}
+
static struct interface *if_create_name(const char *name, struct vrf *vrf)
{
struct interface *ifp;
@@ -216,7 +318,11 @@ static struct interface *if_create_name(const char *name, struct vrf *vrf)
if_set_name(ifp, name);
+ if (if_notify_oper_changes && ifp->state)
+ if_update_state(ifp);
+
hook_call(if_add, ifp);
+
return ifp;
}
@@ -228,8 +334,10 @@ void if_update_to_new_vrf(struct interface *ifp, vrf_id_t vrf_id)
/* remove interface from old master vrf list */
old_vrf = ifp->vrf;
- if (ifp->name[0] != '\0')
+ if (ifp->name[0] != '\0') {
IFNAME_RB_REMOVE(old_vrf, ifp);
+ if_update_state_remove(ifp);
+ }
if (ifp->ifindex != IFINDEX_INTERNAL)
IFINDEX_RB_REMOVE(old_vrf, ifp);
@@ -237,8 +345,11 @@ void if_update_to_new_vrf(struct interface *ifp, vrf_id_t vrf_id)
vrf = vrf_get(vrf_id, NULL);
ifp->vrf = vrf;
- if (ifp->name[0] != '\0')
+ if (ifp->name[0] != '\0') {
IFNAME_RB_INSERT(vrf, ifp);
+ if_update_state_add(ifp);
+ if_update_state(ifp);
+ }
if (ifp->ifindex != IFINDEX_INTERNAL)
IFINDEX_RB_INSERT(vrf, ifp);
@@ -280,6 +391,8 @@ void if_delete(struct interface **ifp)
XFREE(MTYPE_IFDESC, ptr->desc);
+ if_update_state_remove(ptr);
+
XFREE(MTYPE_IF, ptr);
*ifp = NULL;
}
@@ -630,6 +743,9 @@ int if_set_index(struct interface *ifp, ifindex_t ifindex)
ifp->ifindex = ifindex;
+ if (if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "if-index", "%d", ifp->ifindex);
+
if (ifp->ifindex != IFINDEX_INTERNAL) {
/*
* This should never happen, since we checked if there was
@@ -648,13 +764,17 @@ static void if_set_name(struct interface *ifp, const char *name)
if (if_cmp_name_func(ifp->name, name) == 0)
return;
- if (ifp->name[0] != '\0')
+ if (ifp->name[0] != '\0') {
IFNAME_RB_REMOVE(ifp->vrf, ifp);
+ if_update_state_remove(ifp);
+ }
strlcpy(ifp->name, name, sizeof(ifp->name));
- if (ifp->name[0] != '\0')
+ if (ifp->name[0] != '\0') {
IFNAME_RB_INSERT(ifp->vrf, ifp);
+ if_update_state_add(ifp);
+ }
}
/* Does interface up ? */
diff --git a/lib/if.h b/lib/if.h
index c2ec73378..1e52020b6 100644
--- a/lib/if.h
+++ b/lib/if.h
@@ -297,6 +297,8 @@ struct interface {
struct vrf *vrf;
+ struct lyd_node *state;
+
/*
* Has the end users entered `interface XXXX` from the cli in some
* fashion?
@@ -633,6 +635,14 @@ extern void if_up_via_zapi(struct interface *ifp);
extern void if_down_via_zapi(struct interface *ifp);
extern void if_destroy_via_zapi(struct interface *ifp);
+extern void if_update_state(struct interface *ifp);
+extern void if_update_state_metric(struct interface *ifp, uint32_t metric);
+extern void if_update_state_mtu(struct interface *ifp, uint mtu);
+extern void if_update_state_mtu6(struct interface *ifp, uint mtu);
+extern void if_update_state_hw_addr(struct interface *ifp, const uint8_t *hw_addr, uint len);
+extern void if_update_state_speed(struct interface *ifp, uint32_t speed);
+
+extern bool if_notify_oper_changes;
extern const struct frr_yang_module_info frr_interface_info;
extern const struct frr_yang_module_info frr_interface_cli_info;
diff --git a/lib/mgmt_be_client.c b/lib/mgmt_be_client.c
index 155d56aa6..3a07a1d2d 100644
--- a/lib/mgmt_be_client.c
+++ b/lib/mgmt_be_client.c
@@ -99,12 +99,12 @@ struct mgmt_be_client {
struct nb_config *candidate_config;
struct nb_config *running_config;
- unsigned long num_edit_nb_cfg;
- unsigned long avg_edit_nb_cfg_tm;
- unsigned long num_prep_nb_cfg;
- unsigned long avg_prep_nb_cfg_tm;
- unsigned long num_apply_nb_cfg;
- unsigned long avg_apply_nb_cfg_tm;
+ uint64_t num_edit_nb_cfg;
+ uint64_t avg_edit_nb_cfg_tm;
+ uint64_t num_prep_nb_cfg;
+ uint64_t avg_prep_nb_cfg_tm;
+ uint64_t num_apply_nb_cfg;
+ uint64_t avg_apply_nb_cfg_tm;
struct mgmt_be_txns_head txn_head;
@@ -117,7 +117,7 @@ struct mgmt_be_client {
struct debug mgmt_dbg_be_client = {
.conf = "debug mgmt client backend",
- .desc = "Management backend client operations"
+ .desc = "Management backend client operations",
};
/* NOTE: only one client per proc for now. */
@@ -322,8 +322,7 @@ static int __send_notification(struct mgmt_be_client *client, const char *xpath,
LY_ERR err;
int ret = 0;
- assert(tree);
-
+ assert(op != NOTIFY_OP_NOTIFICATION || xpath || tree);
debug_be_client("%s: sending %sYANG %snotification: %s", __func__,
op == NOTIFY_OP_DS_DELETE ? "delete "
: op == NOTIFY_OP_DS_REPLACE ? "replace "
@@ -622,7 +621,7 @@ static int mgmt_be_txn_cfg_prepare(struct mgmt_be_txn_ctx *txn)
mgmt_be_send_cfgdata_create_reply(client_ctx, txn->txn_id,
error ? false : true, error ? err_buf : NULL);
- debug_be_client("Avg-nb-edit-duration %lu uSec, nb-prep-duration %lu (avg: %lu) uSec, batch size %u",
+ debug_be_client("Avg-nb-edit-duration %Lu uSec, nb-prep-duration %lu (avg: %Lu) uSec, batch size %u",
client_ctx->avg_edit_nb_cfg_tm, prep_nb_cfg_tm,
client_ctx->avg_prep_nb_cfg_tm, (uint32_t)num_processed);
@@ -771,10 +770,9 @@ static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn)
gettimeofday(&apply_nb_cfg_end, NULL);
apply_nb_cfg_tm = timeval_elapsed(apply_nb_cfg_end, apply_nb_cfg_start);
- client_ctx->avg_apply_nb_cfg_tm = ((client_ctx->avg_apply_nb_cfg_tm *
- client_ctx->num_apply_nb_cfg) +
- apply_nb_cfg_tm) /
- (client_ctx->num_apply_nb_cfg + 1);
+ client_ctx->avg_apply_nb_cfg_tm =
+ ((client_ctx->avg_apply_nb_cfg_tm * client_ctx->num_apply_nb_cfg) + apply_nb_cfg_tm) /
+ (client_ctx->num_apply_nb_cfg + 1);
client_ctx->num_apply_nb_cfg++;
txn->nb_txn = NULL;
@@ -790,8 +788,8 @@ static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn)
mgmt_be_send_apply_reply(client_ctx, txn->txn_id, true, NULL);
- debug_be_client("Nb-apply-duration %lu (avg: %lu) uSec",
- apply_nb_cfg_tm, client_ctx->avg_apply_nb_cfg_tm);
+ debug_be_client("Nb-apply-duration %lu (avg: %Lu) uSec", apply_nb_cfg_tm,
+ client_ctx->avg_apply_nb_cfg_tm);
return 0;
}
@@ -1155,6 +1153,22 @@ static void be_client_handle_notify(struct mgmt_be_client *client, void *msgbuf,
}
/*
+ * Process a notify select msg
+ */
+static void be_client_handle_notify_select(struct mgmt_be_client *client, void *msgbuf,
+ size_t msg_len)
+{
+ struct mgmt_msg_notify_select *msg = msgbuf;
+ const char **selectors = NULL;
+
+ debug_be_client("Received notify-select for client %s", client->name);
+
+ if (msg_len >= sizeof(*msg))
+ selectors = mgmt_msg_native_strings_decode(msg, msg_len, msg->selectors);
+ nb_notif_set_filters(selectors, msg->replace);
+}
+
+/*
* Handle a native encoded message
*
* We don't create transactions with native messaging.
@@ -1175,6 +1189,9 @@ static void be_client_handle_native_msg(struct mgmt_be_client *client,
case MGMT_MSG_CODE_NOTIFY:
be_client_handle_notify(client, msg, msg_len);
break;
+ case MGMT_MSG_CODE_NOTIFY_SELECT:
+ be_client_handle_notify_select(client, msg, msg_len);
+ break;
default:
log_err_be_client("unknown native message txn-id %" PRIu64
" req-id %" PRIu64 " code %u to client %s",
@@ -1315,6 +1332,190 @@ DEFPY(debug_mgmt_client_be, debug_mgmt_client_be_cmd,
return CMD_SUCCESS;
}
+/*
+ * XPath: /frr-backend:clients/client
+ *
+ * We only implement a list of one entry (for the this backend client) the
+ * results will be merged inside mgmtd.
+ */
+static const void *clients_client_get_next(struct nb_cb_get_next_args *args)
+{
+ if (args->list_entry == NULL)
+ return __be_client;
+ return NULL;
+}
+
+static int clients_client_get_keys(struct nb_cb_get_keys_args *args)
+{
+ args->keys->num = 1;
+ strlcpy(args->keys->key[0], __be_client->name, sizeof(args->keys->key[0]));
+
+ return NB_OK;
+}
+
+static const void *clients_client_lookup_entry(struct nb_cb_lookup_entry_args *args)
+{
+ const char *name = args->keys->key[0];
+
+ if (!strcmp(name, __be_client->name))
+ return __be_client;
+
+ return NULL;
+}
+
+/*
+ * XPath: /frr-backend:clients/client/name
+ */
+static enum nb_error clients_client_name_get(const struct nb_node *nb_node,
+ const void *parent_list_entry, struct lyd_node *parent)
+{
+ const struct lysc_node *snode = nb_node->snode;
+ LY_ERR err;
+
+ err = lyd_new_term(parent, snode->module, snode->name, __be_client->name, false, NULL);
+ if (err != LY_SUCCESS)
+ return NB_ERR_RESOURCE;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-backend:clients/client/state/candidate-config-version
+ */
+static enum nb_error clients_client_state_candidate_config_version_get(
+ const struct nb_node *nb_node, const void *parent_list_entry, struct lyd_node *parent)
+{
+ const struct lysc_node *snode = nb_node->snode;
+ uint64_t value = __be_client->candidate_config->version;
+
+ if (lyd_new_term_bin(parent, snode->module, snode->name, &value, sizeof(value),
+ LYD_NEW_PATH_UPDATE, NULL))
+ return NB_ERR_RESOURCE;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-backend:clients/client/state/running-config-version
+ */
+static enum nb_error clients_client_state_running_config_version_get(const struct nb_node *nb_node,
+ const void *parent_list_entry,
+ struct lyd_node *parent)
+{
+ const struct lysc_node *snode = nb_node->snode;
+ uint64_t value = __be_client->running_config->version;
+
+ if (lyd_new_term_bin(parent, snode->module, snode->name, &value, sizeof(value),
+ LYD_NEW_PATH_UPDATE, NULL))
+ return NB_ERR_RESOURCE;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-backend:clients/client/state/notify-selectors
+ *
+ * Is this better in northbound_notif.c? Let's decide when we add more to this module.
+ */
+
+static enum nb_error clients_client_state_notify_selectors_get(const struct nb_node *nb_node,
+ const void *parent_list_entry,
+ struct lyd_node *parent)
+{
+ const struct lysc_node *snode = nb_node->snode;
+ const char **p;
+ LY_ERR err;
+
+ darr_foreach_p (nb_notif_filters, p) {
+ err = lyd_new_term(parent, snode->module, snode->name, *p, false, NULL);
+ if (err != LY_SUCCESS)
+ return NB_ERR_RESOURCE;
+ }
+
+ return NB_OK;
+}
+
+/* clang-format off */
+const struct frr_yang_module_info frr_backend_info = {
+ .name = "frr-backend",
+ .nodes = {
+ {
+ .xpath = "/frr-backend:clients/client",
+ .cbs = {
+ .get_next = clients_client_get_next,
+ .get_keys = clients_client_get_keys,
+ .lookup_entry = clients_client_lookup_entry,
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/name",
+ .cbs.get = clients_client_name_get,
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/candidate-config-version",
+ .cbs = {
+ .get = clients_client_state_candidate_config_version_get,
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/running-config-version",
+ .cbs = {
+ .get = clients_client_state_running_config_version_get,
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/edit-count",
+ .cbs = {
+ .get = nb_oper_uint64_get,
+ .get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, num_edit_nb_cfg),
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/avg-edit-time",
+ .cbs = {
+ .get = nb_oper_uint64_get,
+ .get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, avg_edit_nb_cfg_tm),
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/prep-count",
+ .cbs = {
+ .get = nb_oper_uint64_get,
+ .get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, num_prep_nb_cfg),
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/avg-prep-time",
+ .cbs = {
+ .get = nb_oper_uint64_get,
+ .get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, avg_prep_nb_cfg_tm),
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/apply-count",
+ .cbs = {
+ .get = nb_oper_uint64_get,
+ .get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, num_apply_nb_cfg),
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/avg-apply-time",
+ .cbs = {
+ .get = nb_oper_uint64_get,
+ .get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, avg_apply_nb_cfg_tm),
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/notify-selectors",
+ .cbs.get = clients_client_state_notify_selectors_get,
+ },
+ {
+ .xpath = NULL,
+ },
+ }
+};
+/* clang-format on */
+
struct mgmt_be_client *mgmt_be_client_create(const char *client_name,
struct mgmt_be_client_cbs *cbs,
uintptr_t user_data,
diff --git a/lib/mgmt_be_client.h b/lib/mgmt_be_client.h
index a3e3896d5..5e78f0f43 100644
--- a/lib/mgmt_be_client.h
+++ b/lib/mgmt_be_client.h
@@ -85,6 +85,8 @@ struct mgmt_be_client_cbs {
extern struct debug mgmt_dbg_be_client;
+extern const struct frr_yang_module_info frr_backend_info;
+
/***************************************************************
* API prototypes
***************************************************************/
diff --git a/lib/mgmt_msg_native.c b/lib/mgmt_msg_native.c
index b85c7d1b6..46dfe7f2e 100644
--- a/lib/mgmt_msg_native.c
+++ b/lib/mgmt_msg_native.c
@@ -14,7 +14,8 @@ DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_ERROR, "native error msg");
DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_GET_TREE, "native get tree msg");
DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_TREE_DATA, "native tree data msg");
DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_GET_DATA, "native get data msg");
-DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_NOTIFY, "native get data msg");
+DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_NOTIFY, "native notify msg");
+DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_NOTIFY_SELECT, "native notify select msg");
DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_EDIT, "native edit msg");
DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_EDIT_REPLY, "native edit reply msg");
DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_RPC, "native RPC msg");
diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h
index 4076977a2..73303846e 100644
--- a/lib/mgmt_msg_native.h
+++ b/lib/mgmt_msg_native.h
@@ -159,6 +159,7 @@ DECLARE_MTYPE(MSG_NATIVE_GET_TREE);
DECLARE_MTYPE(MSG_NATIVE_TREE_DATA);
DECLARE_MTYPE(MSG_NATIVE_GET_DATA);
DECLARE_MTYPE(MSG_NATIVE_NOTIFY);
+DECLARE_MTYPE(MSG_NATIVE_NOTIFY_SELECT);
DECLARE_MTYPE(MSG_NATIVE_EDIT);
DECLARE_MTYPE(MSG_NATIVE_EDIT_REPLY);
DECLARE_MTYPE(MSG_NATIVE_RPC);
diff --git a/lib/northbound.c b/lib/northbound.c
index c67ed924a..418cb246f 100644
--- a/lib/northbound.c
+++ b/lib/northbound.c
@@ -2754,10 +2754,15 @@ void nb_init(struct event_loop *tm,
/* Initialize oper-state */
nb_oper_init(tm);
+
+ /* Initialize notification-state */
+ nb_notif_init(tm);
}
void nb_terminate(void)
{
+ nb_notif_terminate();
+
nb_oper_terminate();
/* Terminate the northbound CLI. */
diff --git a/lib/northbound.h b/lib/northbound.h
index 38d8c2bdc..ce59bfd01 100644
--- a/lib/northbound.h
+++ b/lib/northbound.h
@@ -836,6 +836,9 @@ extern struct debug nb_dbg_libyang;
/* Global running configuration. */
extern struct nb_config *running_config;
+/* Global notification filters */
+extern const char **nb_notif_filters;
+
/* Wrappers for the northbound callbacks. */
extern struct yang_data *nb_callback_has_new_get_elem(const struct nb_node *nb_node);
@@ -1512,6 +1515,22 @@ extern void nb_oper_cancel_walk(void *walk);
*/
extern void nb_oper_cancel_all_walks(void);
+/**
+ * nb_oper_walk_finish_arg() - return the finish arg for this walk
+ */
+extern void *nb_oper_walk_finish_arg(void *walk);
+/**
+ * nb_oper_walk_cb_arg() - return the callback arg for this walk
+ */
+extern void *nb_oper_walk_cb_arg(void *walk);
+
+/* Generic getter functions */
+extern enum nb_error nb_oper_uint32_get(const struct nb_node *nb_node,
+ const void *parent_list_entry, struct lyd_node *parent);
+
+extern enum nb_error nb_oper_uint64_get(const struct nb_node *nb_node,
+ const void *parent_list_entry, struct lyd_node *parent);
+
/*
* Validate if the northbound callback operation is valid for the given node.
*
@@ -1744,6 +1763,80 @@ extern void nb_oper_init(struct event_loop *loop);
extern void nb_oper_terminate(void);
extern bool nb_oper_is_yang_lib_query(const char *xpath);
+
+/**
+ * nb_op_update() - Create new state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to create.
+ * @value: The canonical value of the state.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *nb_op_update(struct lyd_node *tree, const char *path, const char *value);
+
+/**
+ * nb_op_update_delete() - Delete state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to delete, or NULL if @tree should just be
+ * deleted.
+ */
+extern void nb_op_update_delete(struct lyd_node *tree, const char *path);
+
+/**
+ * nb_op_update_pathf() - Create new state data.
+ * @tree: subtree @path_fmt is relative to or NULL in which case @path_fmt must
+ * be absolute.
+ * @path_fmt: The path format string of the state node to create.
+ * @value: The canonical value of the state.
+ * @...: The values to substitute into @path_fmt.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *nb_op_update_pathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, ...) PRINTFRR(2, 4);
+extern struct lyd_node *nb_op_update_vpathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, va_list ap);
+/**
+ * nb_op_update_delete_pathf() - Delete state data.
+ * @tree: subtree @path_fmt is relative to or NULL in which case @path_fmt must
+ * be absolute.
+ * @path: The path of the state node to delete.
+ * @...: The values to substitute into @path_fmt.
+ */
+extern void nb_op_update_delete_pathf(struct lyd_node *tree, const char *path_fmt, ...)
+ PRINTFRR(2, 3);
+extern void nb_op_update_delete_vpathf(struct lyd_node *tree, const char *path_fmt, va_list ap);
+
+/**
+ * nb_op_updatef() - Create new state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to create.
+ * @val_fmt: The value format string to set the canonical value of the state.
+ * @...: The values to substitute into @val_fmt.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *nb_op_updatef(struct lyd_node *tree, const char *path, const char *val_fmt,
+ ...) PRINTFRR(3, 4);
+
+extern struct lyd_node *nb_op_vupdatef(struct lyd_node *tree, const char *path, const char *val_fmt,
+ va_list ap);
+
+/**
+ * nb_notif_set_filters() - add or replace notification filters
+ * @selectors: darr array of selector (filter) xpath strings, can be NULL if
+ * @replace is true. nb_notif_set_filters takes ownership of this
+ * array and the contained darr strings.
+ * @replace: true to replace existing set otherwise append.
+ */
+extern void nb_notif_set_filters(const char **selectors, bool replace);
+
+extern void nb_notif_init(struct event_loop *loop);
+extern void nb_notif_terminate(void);
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/northbound_notif.c b/lib/northbound_notif.c
new file mode 100644
index 000000000..b75c86561
--- /dev/null
+++ b/lib/northbound_notif.c
@@ -0,0 +1,680 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * December 1 2024, Christian Hopps <chopps@labn.net>
+ *
+ * Copyright (c) 2024, LabN Consulting, L.L.C.
+ *
+ */
+#include <zebra.h>
+#include "debug.h"
+#include "lib_errors.h"
+#include "typesafe.h"
+#include "northbound.h"
+#include "mgmt_be_client.h"
+
+#define __dbg(fmt, ...) DEBUGD(&nb_dbg_notif, "NB_OP_CHANGE: %s: " fmt, __func__, ##__VA_ARGS__)
+#define __log_err(fmt, ...) zlog_err("NB_OP_CHANGE: %s: ERROR: " fmt, __func__, ##__VA_ARGS__)
+
+#define NB_NOTIF_TIMER_MSEC (10) /* 10msec */
+
+/*
+ * ADDS:
+ * - Less specific:
+ * - Any new add will cause more specific pending adds to be dropped and equal
+ * or more specific deletes to be dropped.
+ * - More specific:
+ * - Ignore any new add that is the same or more specific than an existing add.
+ * - A new add that is more specific than a delete should change the delete
+ * into an add query (since adds are reported as a replace).
+ *
+ * DELETES:
+ * - Less specific:
+ * - Any new delete will cause more specific pending deletes to be dropped and
+ * equal or more specific adds to be dropped.
+ * - More specific:
+ * - Ignore new deletes that are the same or more specific than existing
+ * deletes.
+ * - A new delete that is more specific than an add can be dropped since we
+ * use replacement methodology for the add.
+ *
+ * One thing we have to pay close attention to is that the state is going to be
+ * queried when the notification sent, not when we are told of the change.
+ */
+
+DEFINE_MTYPE_STATIC(LIB, OP_CHANGE, "NB Oper Change");
+DEFINE_MTYPE_STATIC(LIB, OP_CHANGES_GROUP, "NB Oper Changes Group");
+DEFINE_MTYPE_STATIC(LIB, NB_NOTIF_WALK_ARGS, "NB Notify Oper Walk");
+
+struct op_change {
+ RB_ENTRY(op_change) link;
+ char path[];
+};
+
+/*
+ * RB tree for op_change
+ */
+static int op_change_cmp(const struct op_change *e1, const struct op_change *e2);
+RB_HEAD(op_changes, op_change);
+RB_PROTOTYPE(op_changes, op_change, link, op_change_cmp)
+RB_GENERATE(op_changes, op_change, link, op_change_cmp)
+
+struct op_changes nb_notif_adds = RB_INITIALIZER(&nb_notif_adds);
+struct op_changes nb_notif_dels = RB_INITIALIZER(&nb_notif_dels);
+struct event_loop *nb_notif_master;
+struct event *nb_notif_timer;
+void *nb_notif_walk;
+
+const char **nb_notif_filters;
+
+/*
+ * We maintain a queue of change lists one entry per query and notification send
+ * action
+ */
+PREDECL_LIST(op_changes_queue);
+struct op_changes_group {
+ struct op_changes_queue_item item;
+ struct op_changes adds;
+ struct op_changes dels;
+ struct op_changes *cur_changes; /* used when walking */
+ struct op_change *cur_change; /* " " " */
+};
+
+DECLARE_LIST(op_changes_queue, struct op_changes_group, item);
+static struct op_changes_queue_head op_changes_queue;
+
+struct nb_notif_walk_args {
+ struct op_changes_group *group;
+ struct lyd_node *tree;
+};
+
+static void nb_notif_set_walk_timer(void);
+
+
+static int pathncmp(const char *s1, const char *s2, size_t n)
+{
+ size_t i = 0;
+
+ while (i < n && *s1 && *s2) {
+ char c1 = *s1;
+ char c2 = *s2;
+
+ if ((c1 == '\'' && c2 == '\"') || (c1 == '\"' && c2 == '\'')) {
+ s1++;
+ s2++;
+ i++;
+ continue;
+ }
+ if (c1 != c2)
+ return (unsigned char)c1 - (unsigned char)c2;
+ s1++;
+ s2++;
+ i++;
+ }
+ if (i < n)
+ return (unsigned char)*s1 - (unsigned char)*s2;
+ return 0;
+}
+
+static int pathcmp(const char *s1, const char *s2)
+{
+ while (*s1 && *s2) {
+ char c1 = *s1;
+ char c2 = *s2;
+
+ if ((c1 == '\'' && c2 == '\"') || (c1 == '\"' && c2 == '\'')) {
+ s1++;
+ s2++;
+ continue;
+ }
+ if (c1 != c2)
+ return (unsigned char)c1 - (unsigned char)c2;
+ s1++;
+ s2++;
+ }
+ return (unsigned char)*s1 - (unsigned char)*s2;
+}
+
+
+static int op_change_cmp(const struct op_change *e1, const struct op_change *e2)
+{
+ return pathcmp(e1->path, e2->path);
+}
+
+static struct op_change *op_change_alloc(const char *path)
+{
+ struct op_change *note;
+ size_t ssize = strlen(path) + 1;
+
+ note = XMALLOC(MTYPE_OP_CHANGE, sizeof(*note) + ssize);
+ memset(note, 0, sizeof(*note));
+ strlcpy(note->path, path, ssize);
+
+ return note;
+}
+
+static void op_change_free(struct op_change *note)
+{
+ XFREE(MTYPE_OP_CHANGE, note);
+}
+
+/**
+ * op_changes_group_push() - Save the current set of changes on the queue.
+ *
+ * This function will save the current set of changes on the queue and
+ * initialize a new set of changes.
+ */
+static void op_changes_group_push(void)
+{
+ struct op_changes_group *changes;
+
+ if (RB_EMPTY(op_changes, &nb_notif_adds) && RB_EMPTY(op_changes, &nb_notif_dels))
+ return;
+
+ __dbg("pushing current oper changes onto queue");
+
+ changes = XCALLOC(MTYPE_OP_CHANGES_GROUP, sizeof(*changes));
+ changes->adds = nb_notif_adds;
+ changes->dels = nb_notif_dels;
+ op_changes_queue_add_tail(&op_changes_queue, changes);
+
+ RB_INIT(op_changes, &nb_notif_adds);
+ RB_INIT(op_changes, &nb_notif_dels);
+}
+
+static void op_changes_group_free(struct op_changes_group *group)
+{
+ struct op_change *e, *next;
+
+ RB_FOREACH_SAFE (e, op_changes, &group->adds, next) {
+ RB_REMOVE(op_changes, &group->adds, e);
+ op_change_free(e);
+ }
+ RB_FOREACH_SAFE (e, op_changes, &group->dels, next) {
+ RB_REMOVE(op_changes, &group->dels, e);
+ op_change_free(e);
+ }
+ XFREE(MTYPE_OP_CHANGES_GROUP, group);
+}
+
+static struct op_change *__find_less_specific(struct op_changes *head, struct op_change *note)
+{
+ struct op_change *e;
+ size_t plen;
+
+ /*
+ * RB_NFIND finds equal or greater (more specific) than the key,
+ * so the previous node will be a less specific or no match that
+ * sorts earlier. We want to find when we are a more specific
+ * match.
+ */
+ e = RB_NFIND(op_changes, head, note);
+ if (e)
+ e = RB_PREV(op_changes, e);
+ else
+ e = RB_MAX(op_changes, head);
+ if (!e)
+ return NULL;
+ plen = strlen(e->path);
+ if (pathncmp(e->path, note->path, plen))
+ return NULL;
+ /* equal would have been returned from RB_NFIND() then we went RB_PREV */
+ assert(strlen(note->path) != plen);
+ return e;
+}
+
+static void __drop_eq_or_more_specific(struct op_changes *head, const char *path, int plen,
+ struct op_change *next)
+{
+ struct op_change *e;
+
+ for (e = next; e != NULL; e = next) {
+ /* if the prefix no longer matches we are done */
+ if (pathncmp(path, e->path, plen))
+ break;
+ __dbg("dropping more specific %s: %s", head == &nb_notif_adds ? "add" : "delete",
+ e->path);
+ next = RB_NEXT(op_changes, e);
+ RB_REMOVE(op_changes, head, e);
+ op_change_free(e);
+ }
+}
+
+static void __op_change_add_del(const char *path, struct op_changes *this_head,
+ struct op_changes *other_head)
+{
+ /* find out if this has been subsumed or will subsume */
+
+ const char *op = this_head == &nb_notif_adds ? "add" : "delete";
+ struct op_change *note = op_change_alloc(path);
+ struct op_change *next, *e;
+ int plen;
+
+ __dbg("processing oper %s change path: %s", op, path);
+
+ /*
+ * See if we are already covered by a more general `op`.
+ */
+ e = __find_less_specific(this_head, note);
+ if (e) {
+ __dbg("%s path already covered by: %s", op, e->path);
+ op_change_free(note);
+ return;
+ }
+
+ /*
+ * Handle having a less-specific `other op`.
+ */
+ e = __find_less_specific(other_head, note);
+ if (e) {
+ if (this_head == &nb_notif_dels) {
+ /*
+ * If we have a less-specific add then drop this
+ * more-specific delete as the add-replace will remove
+ * this missing state.
+ */
+ __dbg("delete path already covered add-replace: %s", e->path);
+ } else {
+ /*
+ * If we have a less-specific delete, convert the delete
+ * to an add, and drop this more-specific add. The new
+ * less-specific add will pick up the more specific add
+ * during the walk and as adds are processed as replaces
+ * any other existing state that was to be deleted will
+ * still be deleted (unless it also returns) by the replace.
+ */
+ __dbg("add covered, converting covering delete to add-replace: %s", e->path);
+ RB_REMOVE(op_changes, other_head, e);
+ __op_change_add_del(e->path, &nb_notif_adds, &nb_notif_dels);
+ op_change_free(e);
+ }
+ op_change_free(note);
+ return;
+ }
+
+ e = RB_INSERT(op_changes, this_head, note);
+ if (e) {
+ __dbg("path already in %s tree: %s", op, path);
+ op_change_free(note);
+ return;
+ }
+
+ __dbg("scanning for subsumed or subsuming: %s", path);
+
+ plen = strlen(path);
+
+ next = RB_NEXT(op_changes, note);
+ __drop_eq_or_more_specific(this_head, path, plen, next);
+
+ /* Drop exact match or more specific `other op` */
+ next = RB_NFIND(op_changes, other_head, note);
+ __drop_eq_or_more_specific(other_head, path, plen, next);
+
+ nb_notif_set_walk_timer();
+}
+
+static void nb_notif_add(const char *path)
+{
+ __op_change_add_del(path, &nb_notif_adds, &nb_notif_dels);
+}
+
+
+static void nb_notif_delete(const char *path)
+{
+ __op_change_add_del(path, &nb_notif_dels, &nb_notif_adds);
+}
+
+struct lyd_node *nb_op_update(struct lyd_node *tree, const char *path, const char *value)
+{
+ struct lyd_node *dnode;
+ const char *abs_path = NULL;
+
+ __dbg("updating path: %s with value: %s", path, value);
+
+ dnode = yang_state_new(tree, path, value);
+
+ if (path[0] == '/')
+ abs_path = path;
+ else
+ abs_path = lyd_path(dnode, LYD_PATH_STD, NULL, 0);
+
+ nb_notif_add(abs_path);
+
+ if (abs_path != path)
+ free((char *)abs_path);
+
+ return dnode;
+}
+
+void nb_op_update_delete(struct lyd_node *tree, const char *path)
+{
+ char *abs_path = NULL;
+
+ __dbg("deleting path: %s", path);
+
+ if (path && path[0] == '/')
+ abs_path = (char *)path;
+ else {
+ assert(tree);
+ abs_path = lyd_path(tree, LYD_PATH_STD, NULL, 0);
+ assert(abs_path);
+ if (path) {
+ char *tmp = darr_strdup(abs_path);
+
+ free(abs_path);
+ abs_path = tmp;
+ if (*darr_last(abs_path) != '/')
+ darr_in_strcat(abs_path, "/");
+ assert(abs_path); /* silence bad CLANG NULL warning */
+ darr_in_strcat(abs_path, path);
+ }
+ }
+
+ yang_state_delete(tree, path);
+
+ nb_notif_delete(abs_path);
+
+ if (abs_path != path) {
+ if (path)
+ darr_free(abs_path);
+ else
+ free(abs_path);
+ }
+}
+
+PRINTFRR(2, 0)
+struct lyd_node *nb_op_update_vpathf(struct lyd_node *tree, const char *path_fmt, const char *value,
+ va_list ap)
+{
+ struct lyd_node *dnode;
+ char *path;
+
+ path = darr_vsprintf(path_fmt, ap);
+ dnode = nb_op_update(tree, path, value);
+ darr_free(path);
+
+ return dnode;
+}
+
+struct lyd_node *nb_op_update_pathf(struct lyd_node *tree, const char *path_fmt, const char *value,
+ ...)
+{
+ struct lyd_node *dnode;
+ va_list ap;
+
+ va_start(ap, value);
+ dnode = nb_op_update_vpathf(tree, path_fmt, value, ap);
+ va_end(ap);
+
+ return dnode;
+}
+
+PRINTFRR(2, 0)
+void nb_op_update_delete_vpathf(struct lyd_node *tree, const char *path_fmt, va_list ap)
+{
+ char *path;
+
+ path = darr_vsprintf(path_fmt, ap);
+ nb_op_update_delete(tree, path);
+ darr_free(path);
+}
+
+void nb_op_update_delete_pathf(struct lyd_node *tree, const char *path_fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, path_fmt);
+ nb_op_update_delete_vpathf(tree, path_fmt, ap);
+ va_end(ap);
+}
+
+
+PRINTFRR(3, 0)
+struct lyd_node *nb_op_vupdatef(struct lyd_node *tree, const char *path, const char *val_fmt,
+ va_list ap)
+{
+ struct lyd_node *dnode;
+ char *value;
+
+ value = darr_vsprintf(val_fmt, ap);
+ dnode = nb_op_update(tree, path, value);
+ darr_free(value);
+
+ return dnode;
+}
+
+
+struct lyd_node *nb_op_updatef(struct lyd_node *tree, const char *path, const char *val_fmt, ...)
+{
+ struct lyd_node *dnode;
+ va_list ap;
+
+ va_start(ap, val_fmt);
+ dnode = nb_op_vupdatef(tree, path, val_fmt, ap);
+ va_end(ap);
+
+ return dnode;
+}
+
+static struct op_changes_group *op_changes_group_next(void)
+{
+ struct op_changes_group *group;
+
+ group = op_changes_queue_pop(&op_changes_queue);
+ if (!group) {
+ op_changes_group_push();
+ group = op_changes_queue_pop(&op_changes_queue);
+ }
+ if (!group)
+ return NULL;
+ group->cur_changes = &group->dels;
+ group->cur_change = RB_MIN(op_changes, group->cur_changes);
+ if (!group->cur_change) {
+ group->cur_changes = &group->adds;
+ group->cur_change = RB_MIN(op_changes, group->cur_changes);
+ assert(group->cur_change);
+ }
+ return group;
+}
+
+/* ---------------------------- */
+/* Query for changes and notify */
+/* ---------------------------- */
+
+static void timer_walk_continue(struct event *event);
+
+static enum nb_error oper_walk_done(const struct lyd_node *tree, void *arg, enum nb_error ret)
+{
+ struct nb_notif_walk_args *args = arg;
+ struct op_changes_group *group = args->group;
+ const char *path = group->cur_change->path;
+ const char *op = group->cur_changes == &group->adds ? "add" : "delete";
+
+ /* we don't send batches when yielding as we need completed edit in any patch */
+ assert(ret != NB_YIELD);
+
+ nb_notif_walk = NULL;
+
+ if (ret == NB_ERR_NOT_FOUND) {
+ __dbg("Path not found while walking oper tree: %s", path);
+ XFREE(MTYPE_NB_NOTIF_WALK_ARGS, args);
+ return ret;
+ }
+ /* Something else went wrong with the walk */
+ if (ret != NB_OK) {
+error:
+ __log_err("Error notifying for datastore change on path: %s: %s", path,
+ nb_err_name(ret));
+ XFREE(MTYPE_NB_NOTIF_WALK_ARGS, args);
+ /* XXX Need to inform mgmtd/front-ends things are out-of-sync */
+ return ret;
+ }
+
+ __dbg("done with oper-path collection for %s path: %s", op, path);
+
+ /* Do we need this? */
+ while (tree->parent)
+ tree = lyd_parent(tree);
+
+ /* Send the add (replace) notification */
+ if (mgmt_be_send_ds_replace_notification(path, tree)) {
+ ret = NB_ERR;
+ goto error;
+ }
+
+ /*
+ * Advance to next change (either dels or adds or both).
+ */
+
+ group->cur_change = RB_NEXT(op_changes, group->cur_change);
+ if (!group->cur_change) {
+ __dbg("done with oper-path collection for group");
+ op_changes_group_free(group);
+
+ group = op_changes_group_next();
+ args->group = group;
+ if (!group) {
+ __dbg("done with ALL oper-path collection for notification");
+ XFREE(MTYPE_NB_NOTIF_WALK_ARGS, args);
+ goto done;
+ }
+ }
+
+ event_add_timer_msec(nb_notif_master, timer_walk_continue, args, 0, &nb_notif_timer);
+done:
+ /* Done with current walk and scheduled next one if there is more */
+ nb_notif_walk = NULL;
+
+ return NB_OK;
+}
+
+static LY_ERR nb_notify_delete_changes(struct nb_notif_walk_args *args)
+{
+ struct op_changes_group *group = args->group;
+ LY_ERR err;
+
+ group->cur_change = RB_MIN(op_changes, group->cur_changes);
+ while (group->cur_change) {
+ err = mgmt_be_send_ds_delete_notification(group->cur_change->path);
+ assert(err == LY_SUCCESS); /* XXX */
+
+ group->cur_change = RB_NEXT(op_changes, group->cur_change);
+ }
+
+ return LY_SUCCESS;
+}
+
+static void timer_walk_continue(struct event *event)
+{
+ struct nb_notif_walk_args *args = EVENT_ARG(event);
+ struct op_changes_group *group = args->group;
+ const char *path;
+ LY_ERR err;
+
+ /*
+ * Notify about deletes until we have add changes to collect.
+ */
+ while (group->cur_changes == &group->dels) {
+ err = nb_notify_delete_changes(args);
+ assert(err == LY_SUCCESS); /* XXX */
+ assert(!group->cur_change); /* we send all the deletes in one message */
+
+ /* after deletes advance to adds */
+ group->cur_changes = &group->adds;
+ group->cur_change = RB_MIN(op_changes, group->cur_changes);
+ if (group->cur_change)
+ break;
+
+ __dbg("done with oper-path change group");
+ op_changes_group_free(group);
+
+ group = op_changes_group_next();
+ args->group = group;
+ if (!group) {
+ __dbg("done with ALL oper-path changes");
+ XFREE(MTYPE_NB_NOTIF_WALK_ARGS, args);
+ return;
+ }
+ }
+
+ path = group->cur_change->path;
+ __dbg("starting next oper-path replace walk for path: %s", path);
+ nb_notif_walk = nb_oper_walk(path, NULL, 0, false, NULL, NULL, oper_walk_done, args);
+}
+
+static void timer_walk_start(struct event *event)
+{
+ struct op_changes_group *group;
+ struct nb_notif_walk_args *args;
+
+ __dbg("oper-state change notification timer fires");
+
+ group = op_changes_group_next();
+ if (!group) {
+ __dbg("no oper changes to notify");
+ return;
+ }
+
+ args = XCALLOC(MTYPE_NB_NOTIF_WALK_ARGS, sizeof(*args));
+ args->group = group;
+
+ EVENT_ARG(event) = args;
+ timer_walk_continue(event);
+}
+
+static void nb_notif_set_walk_timer(void)
+{
+ if (nb_notif_walk) {
+ __dbg("oper-state walk already in progress.");
+ return;
+ }
+ if (event_is_scheduled(nb_notif_timer)) {
+ __dbg("oper-state notification timer already set.");
+ return;
+ }
+
+ __dbg("oper-state notification setting timer to fire in: %d msec ", NB_NOTIF_TIMER_MSEC);
+ event_add_timer_msec(nb_notif_master, timer_walk_start, NULL, NB_NOTIF_TIMER_MSEC,
+ &nb_notif_timer);
+}
+
+void nb_notif_set_filters(const char **selectors, bool replace)
+{
+ const char **csp;
+
+ if (replace) {
+ darr_free_free(nb_notif_filters);
+ nb_notif_filters = selectors;
+ return;
+ }
+ darr_foreach_p (selectors, csp)
+ *darr_append(nb_notif_filters) = *csp;
+ darr_free(selectors);
+}
+
+void nb_notif_init(struct event_loop *tm)
+{
+ nb_notif_master = tm;
+ op_changes_queue_init(&op_changes_queue);
+}
+
+void nb_notif_terminate(void)
+{
+ struct nb_notif_walk_args *args;
+ struct op_changes_group *group;
+
+ EVENT_OFF(nb_notif_timer);
+
+ if (nb_notif_walk) {
+ nb_oper_cancel_walk(nb_notif_walk);
+ /* need to free the group that's in the walk */
+ args = nb_oper_walk_finish_arg(nb_notif_walk);
+ if (args)
+ op_changes_group_free(args->group);
+ nb_notif_walk = NULL;
+ }
+
+ while ((group = op_changes_group_next()))
+ op_changes_group_free(group);
+
+ darr_free_free(nb_notif_filters);
+}
diff --git a/lib/northbound_oper.c b/lib/northbound_oper.c
index c80cdc116..6336db502 100644
--- a/lib/northbound_oper.c
+++ b/lib/northbound_oper.c
@@ -35,6 +35,7 @@
* We must also process containers with lookup-next descendants last.
*/
+DEFINE_MTYPE_STATIC(LIB, NB_STATE, "Northbound State");
DEFINE_MTYPE_STATIC(LIB, NB_YIELD_STATE, "NB Yield State");
DEFINE_MTYPE_STATIC(LIB, NB_NODE_INFOS, "NB Node Infos");
@@ -1833,6 +1834,20 @@ bool nb_oper_is_yang_lib_query(const char *xpath)
return strlen(xpath) > liblen;
}
+void *nb_oper_walk_finish_arg(void *walk)
+{
+ struct nb_op_yield_state *ys = walk;
+
+ return ys->finish_arg;
+}
+
+void *nb_oper_walk_cb_arg(void *walk)
+{
+ struct nb_op_yield_state *ys = walk;
+
+ return ys->cb_arg;
+}
+
void *nb_oper_walk(const char *xpath, struct yang_translator *translator,
uint32_t flags, bool should_batch, nb_oper_data_cb cb,
void *cb_arg, nb_oper_data_finish_cb finish, void *finish_arg)
@@ -1903,6 +1918,87 @@ enum nb_error nb_oper_iterate_legacy(const char *xpath,
return ret;
}
+static const char *__adjust_ptr(struct lysc_node_leaf *lsnode, const char *valuep, size_t *size)
+{
+ switch (lsnode->type->basetype) {
+ case LY_TYPE_INT8:
+ case LY_TYPE_UINT8:
+#ifdef BIG_ENDIAN
+ valuep += 7;
+#endif
+ *size = 1;
+ break;
+ case LY_TYPE_INT16:
+ case LY_TYPE_UINT16:
+#ifdef BIG_ENDIAN
+ valuep += 6;
+#endif
+ *size = 2;
+ break;
+ case LY_TYPE_INT32:
+ case LY_TYPE_UINT32:
+#ifdef BIG_ENDIAN
+ valuep += 4;
+#endif
+ *size = 4;
+ break;
+ case LY_TYPE_INT64:
+ case LY_TYPE_UINT64:
+ *size = 8;
+ break;
+ case LY_TYPE_UNKNOWN:
+ case LY_TYPE_BINARY:
+ case LY_TYPE_STRING:
+ case LY_TYPE_BITS:
+ case LY_TYPE_BOOL:
+ case LY_TYPE_DEC64:
+ case LY_TYPE_EMPTY:
+ case LY_TYPE_ENUM:
+ case LY_TYPE_IDENT:
+ case LY_TYPE_INST:
+ case LY_TYPE_LEAFREF:
+ case LY_TYPE_UNION:
+ default:
+ assert(0);
+ }
+ return valuep;
+}
+
+enum nb_error nb_oper_uint64_get(const struct nb_node *nb_node, const void *parent_list_entry,
+ struct lyd_node *parent)
+{
+ struct lysc_node_leaf *lsnode = (struct lysc_node_leaf *)nb_node->snode;
+ struct lysc_node *snode = &lsnode->node;
+ ssize_t offset = (ssize_t)nb_node->cbs.get_elem;
+ uint64_t ubigval = *(uint64_t *)((char *)parent_list_entry + offset);
+ const char *valuep;
+ size_t size;
+
+ valuep = __adjust_ptr(lsnode, (const char *)&ubigval, &size);
+ if (lyd_new_term_bin(parent, snode->module, snode->name, valuep, size, LYD_NEW_PATH_UPDATE,
+ NULL))
+ return NB_ERR_RESOURCE;
+ return NB_OK;
+}
+
+
+enum nb_error nb_oper_uint32_get(const struct nb_node *nb_node, const void *parent_list_entry,
+ struct lyd_node *parent)
+{
+ struct lysc_node_leaf *lsnode = (struct lysc_node_leaf *)nb_node->snode;
+ struct lysc_node *snode = &lsnode->node;
+ ssize_t offset = (ssize_t)nb_node->cbs.get_elem;
+ uint64_t ubigval = *(uint64_t *)((char *)parent_list_entry + offset);
+ const char *valuep;
+ size_t size;
+
+ valuep = __adjust_ptr(lsnode, (const char *)&ubigval, &size);
+ if (lyd_new_term_bin(parent, snode->module, snode->name, valuep, size, LYD_NEW_PATH_UPDATE,
+ NULL))
+ return NB_ERR_RESOURCE;
+ return NB_OK;
+}
+
void nb_oper_init(struct event_loop *loop)
{
event_loop = loop;
diff --git a/lib/subdir.am b/lib/subdir.am
index 4bcce9a2b..a975eb2fc 100644
--- a/lib/subdir.am
+++ b/lib/subdir.am
@@ -84,6 +84,7 @@ lib_libfrr_la_SOURCES = \
lib/northbound.c \
lib/northbound_cli.c \
lib/northbound_db.c \
+ lib/northbound_notif.c \
lib/northbound_oper.c \
lib/ntop.c \
lib/openbsd-tree.c \
@@ -144,6 +145,7 @@ lib_libfrr_la_SOURCES = \
nodist_lib_libfrr_la_SOURCES = \
yang/frr-affinity-map.yang.c \
+ yang/frr-backend.yang.c \
yang/frr-filter.yang.c \
yang/frr-if-rmap.yang.c \
yang/frr-interface.yang.c \
diff --git a/lib/vrf.c b/lib/vrf.c
index 31632a80d..9be8a9faa 100644
--- a/lib/vrf.c
+++ b/lib/vrf.c
@@ -22,6 +22,9 @@
#include "northbound.h"
#include "northbound_cli.h"
+/* Set by the owner (zebra). */
+bool vrf_notify_oper_changes;
+
/* default VRF name value used when VRF backend is not NETNS */
#define VRF_DEFAULT_NAME_INTERNAL "default"
@@ -105,6 +108,19 @@ int vrf_switchback_to_initial(void)
return ret;
}
+static void vrf_update_state(struct vrf *vrf)
+{
+ if (!vrf->state || !vrf_notify_oper_changes)
+ return;
+
+ /*
+ * Remove top level container update when we have patch support, for now
+ * this keeps us from generating 2 separate REPLACE messages though.
+ */
+ nb_op_updatef(vrf->state, "id", "%u", vrf->vrf_id);
+ nb_op_update(vrf->state, "active", CHECK_FLAG(vrf->status, VRF_ACTIVE) ? "true" : "false");
+}
+
/* Get a VRF. If not found, create one.
* Arg:
* name - The name of the vrf. May be NULL if unknown.
@@ -155,16 +171,32 @@ struct vrf *vrf_get(vrf_id_t vrf_id, const char *name)
/* Set name */
if (name && vrf->name[0] != '\0' && strcmp(name, vrf->name)) {
- /* update the vrf name */
+ /* vrf name has changed */
+ if (vrf_notify_oper_changes) {
+ nb_op_update_delete_pathf(NULL, "/frr-vrf:lib/vrf[name=\"%s\"]", vrf->name);
+ lyd_free_all(vrf->state);
+ }
RB_REMOVE(vrf_name_head, &vrfs_by_name, vrf);
- strlcpy(vrf->data.l.netns_name,
- name, NS_NAMSIZ);
+ strlcpy(vrf->data.l.netns_name, name, NS_NAMSIZ);
strlcpy(vrf->name, name, sizeof(vrf->name));
RB_INSERT(vrf_name_head, &vrfs_by_name, vrf);
+ /* New state with new name */
+ if (vrf_notify_oper_changes)
+ vrf->state = nb_op_update_pathf(NULL, "/frr-vrf:lib/vrf[name=\"%s\"]/state",
+ NULL, vrf->name);
} else if (name && vrf->name[0] == '\0') {
strlcpy(vrf->name, name, sizeof(vrf->name));
RB_INSERT(vrf_name_head, &vrfs_by_name, vrf);
+
+ /* We have a name now so we can have state */
+ if (vrf_notify_oper_changes)
+ vrf->state = nb_op_update_pathf(NULL, "/frr-vrf:lib/vrf[name=\"%s\"]/state",
+ NULL, vrf->name);
}
+ /* Update state before hook call */
+ if (vrf->state)
+ vrf_update_state(vrf);
+
if (new &&vrf_master.vrf_new_hook)
(*vrf_master.vrf_new_hook)(vrf);
@@ -208,6 +240,7 @@ struct vrf *vrf_update(vrf_id_t new_vrf_id, const char *name)
vrf->vrf_id = new_vrf_id;
RB_INSERT(vrf_id_head, &vrfs_by_id, vrf);
+ vrf_update_state(vrf);
} else {
/*
@@ -254,6 +287,11 @@ void vrf_delete(struct vrf *vrf)
if (vrf->name[0] != '\0')
RB_REMOVE(vrf_name_head, &vrfs_by_name, vrf);
+ if (vrf_notify_oper_changes) {
+ nb_op_update_delete_pathf(NULL, "/frr-vrf:lib/vrf[name=\"%s\"]", vrf->name);
+ lyd_free_all(vrf->state);
+ }
+
XFREE(MTYPE_VRF, vrf);
}
@@ -282,6 +320,8 @@ int vrf_enable(struct vrf *vrf)
SET_FLAG(vrf->status, VRF_ACTIVE);
+ vrf_update_state(vrf);
+
if (vrf_master.vrf_enable_hook)
(*vrf_master.vrf_enable_hook)(vrf);
@@ -307,6 +347,8 @@ void vrf_disable(struct vrf *vrf)
UNSET_FLAG(vrf->status, VRF_ACTIVE);
+ vrf_update_state(vrf);
+
if (debug_vrf)
zlog_debug("VRF %s(%u) is to be disabled.", vrf->name,
vrf->vrf_id);
diff --git a/lib/vrf.h b/lib/vrf.h
index 3ebb6ddf5..ad302de9b 100644
--- a/lib/vrf.h
+++ b/lib/vrf.h
@@ -80,6 +80,8 @@ struct vrf {
/* Back pointer to namespace context */
void *ns_ctxt;
+ struct lyd_node *state;
+
QOBJ_FIELDS;
};
RB_HEAD(vrf_id_head, vrf);
@@ -299,6 +301,7 @@ extern void vrf_disable(struct vrf *vrf);
extern int vrf_enable(struct vrf *vrf);
extern void vrf_delete(struct vrf *vrf);
+extern bool vrf_notify_oper_changes;
extern const struct frr_yang_module_info frr_vrf_info;
extern const struct frr_yang_module_info frr_vrf_cli_info;
diff --git a/lib/yang.c b/lib/yang.c
index b847b8b77..2aa353925 100644
--- a/lib/yang.c
+++ b/lib/yang.c
@@ -14,6 +14,7 @@
#include <libyang/version.h>
#include "northbound.h"
#include "frrstr.h"
+#include "darr.h"
#include "lib/config_paths.h"
@@ -680,6 +681,116 @@ void yang_dnode_rpc_output_add(struct lyd_node *output, const char *xpath,
assert(err == LY_SUCCESS);
}
+struct lyd_node *yang_state_new(struct lyd_node *tree, const char *path, const char *value)
+{
+ struct lyd_node *dnode, *parent;
+ LY_ERR err;
+
+ err = lyd_new_path2(tree, ly_native_ctx, path, value, 0, 0, LYD_NEW_PATH_UPDATE, &parent,
+ &dnode);
+ assert(err == LY_SUCCESS);
+
+ /*
+ * If the node exists and isn't updated returned dnode will be NULL, so
+ * we need to find it. But even if returned it can be the first newly
+ * created node (could be container of path) not the actual path dnode.
+ * So we always find.
+ */
+ err = lyd_find_path(tree ?: parent, path, false, &dnode);
+ assert(err == LY_SUCCESS);
+
+ return dnode;
+}
+
+void yang_state_delete(struct lyd_node *tree, const char *path)
+{
+ LY_ERR err;
+
+ if (!tree)
+ return;
+
+ if (path) {
+ err = lyd_find_path(tree, path, false, &tree);
+ if (err != LY_SUCCESS) {
+ zlog_info("State %s has already been deleted", path);
+ return;
+ }
+ }
+ lyd_free_tree(tree);
+}
+
+PRINTFRR(2, 0)
+struct lyd_node *yang_state_new_vpathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, va_list ap)
+{
+ struct lyd_node *dnode;
+ char *path;
+
+ path = darr_vsprintf(path_fmt, ap);
+ dnode = yang_state_new(tree, path, value);
+ darr_free(path);
+
+ return dnode;
+}
+
+struct lyd_node *yang_state_new_pathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, ...)
+{
+ struct lyd_node *dnode;
+ va_list ap;
+
+ va_start(ap, value);
+ dnode = yang_state_new_vpathf(tree, path_fmt, value, ap);
+ va_end(ap);
+
+ return dnode;
+}
+
+PRINTFRR(2, 0)
+void yang_state_delete_vpathf(struct lyd_node *tree, const char *path_fmt, va_list ap)
+{
+ char *path;
+
+ path = darr_vsprintf(path_fmt, ap);
+ yang_state_delete(tree, path);
+ darr_free(path);
+}
+
+void yang_state_delete_pathf(struct lyd_node *tree, const char *path_fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, path_fmt);
+ yang_state_delete_vpathf(tree, path_fmt, ap);
+ va_end(ap);
+}
+
+PRINTFRR(3, 0)
+struct lyd_node *yang_state_vnewf(struct lyd_node *tree, const char *path, const char *val_fmt,
+ va_list ap)
+{
+ struct lyd_node *dnode;
+ char *value;
+
+ value = darr_vsprintf(val_fmt, ap);
+ dnode = yang_state_new(tree, path, value);
+ darr_free(value);
+
+ return dnode;
+}
+
+struct lyd_node *yang_state_newf(struct lyd_node *tree, const char *path, const char *val_fmt, ...)
+{
+ struct lyd_node *dnode;
+ va_list ap;
+
+ va_start(ap, val_fmt);
+ dnode = yang_state_vnewf(tree, path, val_fmt, ap);
+ va_end(ap);
+
+ return dnode;
+}
+
struct yang_data *yang_data_new(const char *xpath, const char *value)
{
struct yang_data *data;
diff --git a/lib/yang.h b/lib/yang.h
index 52857ecf0..eed2fa8db 100644
--- a/lib/yang.h
+++ b/lib/yang.h
@@ -535,6 +535,66 @@ extern struct lyd_node *yang_dnode_dup(const struct lyd_node *dnode);
*/
extern void yang_dnode_free(struct lyd_node *dnode);
+/**
+ * yang_state_new() - Create new state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to create.
+ * @value: The canonical value of the state.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *yang_state_new(struct lyd_node *tree, const char *path, const char *value);
+
+/**
+ * yang_state_delete() - Delete state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to delete, or NULL if @tree should just be
+ * deleted.
+ */
+extern void yang_state_delete(struct lyd_node *tree, const char *path);
+
+/**
+ * yang_state_new_pathf() - Create new state data.
+ * @tree: subtree @path_fmt is relative to or NULL in which case @path_fmt must
+ * be absolute.
+ * @path_fmt: The path format string of the state node to create.
+ * @value: The canonical value of the state.
+ * @...: The values to substitute into @path_fmt.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *yang_state_new_pathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, ...) PRINTFRR(2, 4);
+extern struct lyd_node *yang_state_new_vpathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, va_list ap);
+/**
+ * yang_state_delete_pathf() - Delete state data.
+ * @tree: subtree @path_fmt is relative to or NULL in which case @path_fmt must
+ * be absolute.
+ * @path: The path of the state node to delete.
+ * @...: The values to substitute into @path_fmt.
+ */
+extern void yang_state_delete_pathf(struct lyd_node *tree, const char *path_fmt, ...) PRINTFRR(2, 3);
+extern void yang_state_delete_vpathf(struct lyd_node *tree, const char *path_fmt, va_list ap);
+
+/**
+ * yang_state_newf() - Create new state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to create.
+ * @val_fmt: The value format string to set the canonical value of the state.
+ * @...: The values to substitute into @val_fmt.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *yang_state_newf(struct lyd_node *tree, const char *path,
+ const char *val_fmt, ...) PRINTFRR(3, 4);
+
+extern struct lyd_node *yang_state_vnewf(struct lyd_node *tree, const char *path,
+ const char *val_fmt, va_list ap);
+
/*
* Add a libyang data node to an RPC/action output container.
*
diff --git a/m4/ax_lua.m4 b/m4/ax_lua.m4
index f4236cf08..60bdf3da3 100644
--- a/m4/ax_lua.m4
+++ b/m4/ax_lua.m4
@@ -1,5 +1,5 @@
# ===========================================================================
-# http://www.gnu.org/software/autoconf-archive/ax_lua.html
+# https://www.gnu.org/software/autoconf-archive/ax_lua.html
# ===========================================================================
#
# SYNOPSIS
@@ -19,7 +19,7 @@
# header is checked to match the Lua interpreter version exactly. When
# searching for Lua libraries, the version number is used as a suffix.
# This is done with the goal of supporting multiple Lua installs (5.1,
-# 5.2, and 5.3 side-by-side).
+# 5.2, 5.3, and 5.4 side-by-side).
#
# A note on compatibility with previous versions: This file has been
# mostly rewritten for serial 18. Most developers should be able to use
@@ -49,6 +49,14 @@
# interpreter. If LUA is blank, the user's path is searched for an
# suitable interpreter.
#
+# Optionally a LUAJIT option may be set ahead of time to look for and
+# validate a LuaJIT install instead of PUC Lua. Usage might look like:
+#
+# AC_ARG_WITH(luajit, [AS_HELP_STRING([--with-luajit],
+# [Prefer LuaJIT over PUC Lua, even if the latter is newer. Default: no])
+# ])
+# AM_CONDITIONAL([LUAJIT], [test "x$with_luajit" != 'xno'])
+#
# If MINIMUM-VERSION is supplied, then only Lua interpreters with a
# version number greater or equal to MINIMUM-VERSION will be accepted. If
# TOO-BIG-VERSION is also supplied, then only Lua interpreters with a
@@ -82,7 +90,7 @@
# appropriate Automake primary, e.g. lua_SCRIPS or luaexec_LIBRARIES.
#
# If an acceptable Lua interpreter is found, then ACTION-IF-FOUND is
-# performed, otherwise ACTION-IF-NOT-FOUND is preformed. If ACTION-IF-NOT-
+# performed, otherwise ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-
# FOUND is blank, then it will default to printing an error. To prevent
# the default behavior, give ':' as an action.
#
@@ -152,6 +160,7 @@
#
# LICENSE
#
+# Copyright (c) 2023 Caleb Maclennan <caleb@alerque.com>
# Copyright (c) 2015 Reuben Thomas <rrt@sc3d.org>
# Copyright (c) 2014 Tim Perkins <tprk77@gmail.com>
#
@@ -166,7 +175,7 @@
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
+# with this program. If not, see <https://www.gnu.org/licenses/>.
#
# As a special exception, the respective Autoconf Macro's copyright owner
# gives unlimited permission to copy, distribute and modify the configure
@@ -181,7 +190,7 @@
# modified version of the Autoconf Macro, you may extend this special
# exception to the GPL to apply to your modified version as well.
-#serial 39
+#serial 47
dnl =========================================================================
dnl AX_PROG_LUA([MINIMUM-VERSION], [TOO-BIG-VERSION],
@@ -197,13 +206,13 @@ AC_DEFUN([AX_PROG_LUA],
AC_ARG_VAR([LUA], [The Lua interpreter, e.g. /usr/bin/lua5.1])
dnl Find a Lua interpreter.
- m4_define_default([_AX_LUA_INTERPRETER_LIST],
- [lua lua5.3 lua53 lua5.2 lua52 lua5.1 lua51 lua50])
+ m4_define_default([_ax_lua_interpreter_list],
+ [lua lua5.4 lua54 lua5.3 lua53 lua5.2 lua52 lua5.1 lua51 lua50])
m4_if([$1], [],
[ dnl No version check is needed. Find any Lua interpreter.
AS_IF([test "x$LUA" = 'x'],
- [AC_PATH_PROGS([LUA], [_AX_LUA_INTERPRETER_LIST], [:])])
+ [AC_PATH_PROGS([LUA], [_ax_lua_interpreter_list], [:])])
ax_display_LUA='lua'
AS_IF([test "x$LUA" != 'x:'],
@@ -242,7 +251,7 @@ AC_DEFUN([AX_PROG_LUA],
[_ax_check_text="for a Lua interpreter with version >= $1, < $2"])
AC_CACHE_CHECK([$_ax_check_text],
[ax_cv_pathless_LUA],
- [ for ax_cv_pathless_LUA in _AX_LUA_INTERPRETER_LIST none; do
+ [ for ax_cv_pathless_LUA in _ax_lua_interpreter_list none; do
test "x$ax_cv_pathless_LUA" = 'xnone' && break
_AX_LUA_CHK_IS_INTRP([$ax_cv_pathless_LUA], [], [continue])
_AX_LUA_CHK_VER([$ax_cv_pathless_LUA], [$1], [$2], [break])
@@ -268,7 +277,7 @@ AC_DEFUN([AX_PROG_LUA],
ax_cv_lua_version=[`$LUA -e '
-- return a version number in X.Y format
local _, _, ver = string.find(_VERSION, "^Lua (%d+%.%d+)")
- print(ver)'`]
+ print(ver or "")'`]
])
AS_IF([test "x$ax_cv_lua_version" = 'x'],
[AC_MSG_ERROR([invalid Lua version number])])
@@ -469,7 +478,7 @@ AC_DEFUN([AX_LUA_HEADERS],
dnl Some default directories to search.
LUA_SHORT_VERSION=`echo "$LUA_VERSION" | $SED 's|\.||'`
- m4_define_default([_AX_LUA_INCLUDE_LIST],
+ m4_define_default([_ax_lua_include_list],
[ /usr/include/lua$LUA_VERSION \
/usr/include/lua-$LUA_VERSION \
/usr/include/lua/$LUA_VERSION \
@@ -488,9 +497,11 @@ AC_DEFUN([AX_LUA_HEADERS],
dnl Try some other directories if LUA_INCLUDE was not set.
AS_IF([test "x$LUA_INCLUDE" = 'x' &&
- test "x$ac_cv_header_lua_h" != 'xyes'],
+ test "x$ac_cv_header_lua_h" != 'xyes' ||
+ test "x$with_luajit" != 'xno' &&
+ test "x$ac_cv_header_luajit_h" != 'xyes'],
[ dnl Try some common include paths.
- for _ax_include_path in _AX_LUA_INCLUDE_LIST; do
+ for _ax_include_path in _ax_lua_include_list; do
test ! -d "$_ax_include_path" && continue
AC_MSG_CHECKING([for Lua headers in])
@@ -500,6 +511,7 @@ AC_DEFUN([AX_LUA_HEADERS],
AS_UNSET([ac_cv_header_lualib_h])
AS_UNSET([ac_cv_header_lauxlib_h])
AS_UNSET([ac_cv_header_luaconf_h])
+ AS_UNSET([ac_cv_header_luajit_h])
_ax_lua_saved_cppflags=$CPPFLAGS
CPPFLAGS="$CPPFLAGS -I$_ax_include_path"
@@ -514,24 +526,42 @@ AC_DEFUN([AX_LUA_HEADERS],
])
AS_IF([test "x$ac_cv_header_lua_h" = 'xyes'],
- [ AC_CACHE_CHECK([for Lua header version],
- [ax_cv_lua_header_version],
- [
- ax_cv_lua_header_version=`echo LUA_VERSION | \
- $CC -P -E $LUA_INCLUDE -imacros lua.h - | \
- $SED -e 's%"@<:@@<:@:space:@:>@@:>@*"%%g' -e 's%^@<:@@<:@:space:@:>@@:>@*%%' | \
- tr -d '"\n' | \
- $SED -n "s|^Lua \(@<:@0-9@:>@\{1,\}\.@<:@0-9@:>@\{1,\}\).\{0,\}|\1|p"`
- ])
-
- dnl Compare this to the previously found LUA_VERSION.
- AC_MSG_CHECKING([if Lua header version matches $LUA_VERSION])
- AS_IF([test "x$ax_cv_lua_header_version" = "x$LUA_VERSION"],
- [ AC_MSG_RESULT([yes])
- ax_header_version_match='yes'
+ [ dnl Make a program to print LUA_VERSION defined in the header.
+ dnl TODO It would be really nice if we could do this without compiling a
+ dnl program, then it would work when cross compiling. But I'm not sure how
+ dnl to do this reliably. For now, assume versions match when cross compiling.
+
+ AS_IF([test "x$cross_compiling" != 'xyes'],
+ [ AC_CACHE_CHECK([for Lua header version],
+ [ax_cv_lua_header_version],
+ [ _ax_lua_saved_cppflags=$CPPFLAGS
+ CPPFLAGS="$CPPFLAGS $LUA_INCLUDE"
+ AC_COMPUTE_INT(ax_cv_lua_header_version_major,[LUA_VERSION_NUM/100],[AC_INCLUDES_DEFAULT
+#include <lua.h>
+],[ax_cv_lua_header_version_major=unknown])
+ AC_COMPUTE_INT(ax_cv_lua_header_version_minor,[LUA_VERSION_NUM%100],[AC_INCLUDES_DEFAULT
+#include <lua.h>
+],[ax_cv_lua_header_version_minor=unknown])
+ AS_IF([test "x$ax_cv_lua_header_version_major" = xunknown || test "x$ax_cv_lua_header_version_minor" = xunknown],[
+ ax_cv_lua_header_version=unknown
+ ],[
+ ax_cv_lua_header_version="$ax_cv_lua_header_version_major.$ax_cv_lua_header_version_minor"
+ ])
+ CPPFLAGS=$_ax_lua_saved_cppflags
+ ])
+
+ dnl Compare this to the previously found LUA_VERSION.
+ AC_MSG_CHECKING([if Lua header version matches $LUA_VERSION])
+ AS_IF([test "x$ax_cv_lua_header_version" = "x$LUA_VERSION"],
+ [ AC_MSG_RESULT([yes])
+ ax_header_version_match='yes'
+ ],
+ [ AC_MSG_RESULT([no])
+ ax_header_version_match='no'
+ ])
],
- [ AC_MSG_RESULT([no])
- ax_header_version_match='no'
+ [ AC_MSG_WARN([cross compiling so assuming header version number matches])
+ ax_header_version_match='yes'
])
])
@@ -612,7 +642,7 @@ AC_DEFUN([AX_LUA_LIBS],
],
[_ax_found_lua_libs='yes'],
[_ax_found_lua_libs='no'],
- [$_ax_lua_extra_libs])
+ [$_ax_lua_extra_libs])])
LIBS=$_ax_lua_saved_libs
AS_IF([test "x$ac_cv_search_lua_load" != 'xno' &&
diff --git a/mgmtd/mgmt_be_adapter.c b/mgmtd/mgmt_be_adapter.c
index 45e154d83..1c32f936b 100644
--- a/mgmtd/mgmt_be_adapter.c
+++ b/mgmtd/mgmt_be_adapter.c
@@ -77,6 +77,7 @@ static const char *const zebra_config_xpaths[] = {
};
static const char *const zebra_oper_xpaths[] = {
+ "/frr-backend:clients",
"/frr-interface:lib/interface",
"/frr-vrf:lib/vrf/frr-zebra:zebra",
"/frr-zebra:zebra",
@@ -94,6 +95,7 @@ static const char *const ripd_config_xpaths[] = {
NULL,
};
static const char *const ripd_oper_xpaths[] = {
+ "/frr-backend:clients",
"/frr-ripd:ripd",
"/ietf-key-chain:key-chains",
NULL,
@@ -114,6 +116,7 @@ static const char *const ripngd_config_xpaths[] = {
NULL,
};
static const char *const ripngd_oper_xpaths[] = {
+ "/frr-backend:clients",
"/frr-ripngd:ripngd",
NULL,
};
@@ -130,6 +133,11 @@ static const char *const staticd_config_xpaths[] = {
"/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd",
NULL,
};
+
+static const char *const staticd_oper_xpaths[] = {
+ "/frr-backend:clients",
+ NULL,
+};
#endif
static const char *const *be_client_config_xpaths[MGMTD_BE_CLIENT_ID_MAX] = {
@@ -152,6 +160,9 @@ static const char *const *be_client_oper_xpaths[MGMTD_BE_CLIENT_ID_MAX] = {
#ifdef HAVE_RIPNGD
[MGMTD_BE_CLIENT_ID_RIPNGD] = ripngd_oper_xpaths,
#endif
+#ifdef HAVE_STATICD
+ [MGMTD_BE_CLIENT_ID_STATICD] = staticd_oper_xpaths,
+#endif
[MGMTD_BE_CLIENT_ID_ZEBRA] = zebra_oper_xpaths,
};
@@ -320,7 +331,7 @@ static void mgmt_be_xpath_map_init(void)
__dbg("Total Cfg XPath Maps: %u", darr_len(be_cfg_xpath_map));
__dbg("Total Oper XPath Maps: %u", darr_len(be_oper_xpath_map));
- __dbg("Total Noitf XPath Maps: %u", darr_len(be_notif_xpath_map));
+ __dbg("Total Notif XPath Maps: %u", darr_len(be_notif_xpath_map));
__dbg("Total RPC XPath Maps: %u", darr_len(be_rpc_xpath_map));
}
@@ -651,13 +662,17 @@ int mgmt_be_send_native(enum mgmt_be_client_id id, void *msg)
return mgmt_msg_native_send_msg(adapter->conn, msg, false);
}
+/*
+ * Send notification to back-ends that subscribed for them.
+ */
static void mgmt_be_adapter_send_notify(struct mgmt_msg_notify_data *msg,
size_t msglen)
{
struct mgmt_be_client_adapter *adapter;
struct mgmt_be_xpath_map *map;
- struct nb_node *nb_node;
+ struct nb_node *nb_node = NULL;
const char *notif;
+ bool is_root;
uint id, len;
if (!darr_len(be_notif_xpath_map))
@@ -669,28 +684,34 @@ static void mgmt_be_adapter_send_notify(struct mgmt_msg_notify_data *msg,
return;
}
- nb_node = nb_node_find(notif);
- if (!nb_node) {
- __log_err("No schema found for notification: %s", notif);
- return;
+ is_root = !strcmp(notif, "/");
+ if (!is_root) {
+ nb_node = nb_node_find(notif);
+ if (!nb_node) {
+ __log_err("No schema found for notification: %s", notif);
+ return;
+ }
}
darr_foreach_p (be_notif_xpath_map, map) {
- len = strlen(map->xpath_prefix);
- if (strncmp(map->xpath_prefix, nb_node->xpath, len) &&
- strncmp(map->xpath_prefix, notif, len))
- continue;
-
+ if (!is_root) {
+ len = strlen(map->xpath_prefix);
+ if (strncmp(map->xpath_prefix, nb_node->xpath, len) &&
+ strncmp(map->xpath_prefix, notif, len))
+ continue;
+ }
FOREACH_BE_CLIENT_BITS (id, map->clients) {
adapter = mgmt_be_get_adapter_by_id(id);
if (!adapter)
continue;
+
msg_conn_send_msg(adapter->conn, MGMT_MSG_VERSION_NATIVE,
msg, msglen, NULL, false);
}
}
}
+
/*
* Handle a native encoded message
*/
@@ -735,6 +756,9 @@ static void be_adapter_handle_native_msg(struct mgmt_be_client_adapter *adapter,
mgmt_txn_notify_rpc_reply(adapter, rpc_msg, msg_len);
break;
case MGMT_MSG_CODE_NOTIFY:
+ /*
+ * Handle notify message from a back-end client
+ */
notify_msg = (typeof(notify_msg))msg;
__dbg("Got NOTIFY from '%s'", adapter->name);
mgmt_be_adapter_send_notify(notify_msg, msg_len);
diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c
index 22656f189..96b7cbd59 100644
--- a/mgmtd/mgmt_fe_adapter.c
+++ b/mgmtd/mgmt_fe_adapter.c
@@ -9,6 +9,7 @@
#include <zebra.h>
#include "darr.h"
+#include "frrstr.h"
#include "sockopt.h"
#include "network.h"
#include "libfrr.h"
@@ -31,6 +32,7 @@
#define FOREACH_ADAPTER_IN_LIST(adapter) \
frr_each_safe (mgmt_fe_adapters, &mgmt_fe_adapters, (adapter))
+
enum mgmt_session_event {
MGMTD_FE_SESSION_CFG_TXN_CLNUP = 1,
MGMTD_FE_SESSION_SHOW_TXN_CLNUP,
@@ -55,6 +57,22 @@ DECLARE_LIST(mgmt_fe_sessions, struct mgmt_fe_session_ctx, list_linkage);
#define FOREACH_SESSION_IN_LIST(adapter, session) \
frr_each_safe (mgmt_fe_sessions, &(adapter)->fe_sessions, (session))
+/*
+ * A tree for storing unique notify-select strings.
+ */
+PREDECL_RBTREE_UNIQ(ns_string);
+struct ns_string {
+ struct ns_string_item link;
+ struct list *sessions;
+ char s[];
+};
+static uint32_t ns_string_compare(const struct ns_string *ns1, const struct ns_string *ns2);
+DECLARE_RBTREE_UNIQ(ns_string, struct ns_string, link, ns_string_compare);
+
+/* ---------------- */
+/* Global variables */
+/* ---------------- */
+
static struct event_loop *mgmt_loop;
static struct msg_server mgmt_fe_server = {.fd = -1};
@@ -63,6 +81,72 @@ static struct mgmt_fe_adapters_head mgmt_fe_adapters;
static struct hash *mgmt_fe_sessions;
static uint64_t mgmt_fe_next_session_id;
+static struct ns_string_head mgmt_fe_ns_strings;
+
+/* ------------------------------ */
+/* Notify select string functions */
+/* ------------------------------ */
+
+static uint32_t ns_string_compare(const struct ns_string *ns1, const struct ns_string *ns2)
+{
+ return strcmp(ns1->s, ns2->s);
+}
+
+static void mgmt_fe_free_ns_string(struct ns_string *ns)
+{
+ list_delete(&ns->sessions);
+ XFREE(MTYPE_MGMTD_XPATH, ns);
+}
+
+static void mgmt_fe_free_ns_strings(struct ns_string_head *head)
+{
+ struct ns_string *ns;
+
+ while ((ns = ns_string_pop(head)))
+ mgmt_fe_free_ns_string(ns);
+ ns_string_fini(head);
+}
+
+static void mgmt_fe_ns_string_remove_session(struct ns_string_head *head,
+ struct mgmt_fe_session_ctx *session)
+{
+ struct ns_string *ns;
+
+ frr_each_safe (ns_string, head, ns) {
+ listnode_delete(ns->sessions, session);
+ if (list_isempty(ns->sessions)) {
+ ns_string_del(head, ns);
+ mgmt_fe_free_ns_string(ns);
+ }
+ }
+}
+
+static void mgmt_fe_add_ns_string(struct ns_string_head *head, const char *path, size_t plen,
+ struct mgmt_fe_session_ctx *session)
+{
+ struct ns_string *e, *ns;
+
+ ns = XCALLOC(MTYPE_MGMTD_XPATH, sizeof(*ns) + plen + 1);
+ strlcpy(ns->s, path, plen + 1);
+ e = ns_string_add(head, ns);
+ if (!e)
+ ns->sessions = list_new();
+ if (!listnode_lookup(ns->sessions, session))
+ listnode_add(ns->sessions, session);
+}
+
+char **mgmt_fe_get_all_selectors(void)
+{
+ char **selectors = NULL;
+ struct ns_string *ns;
+
+ frr_each (ns_string, &mgmt_fe_ns_strings, ns)
+ *darr_append(selectors) = darr_strdup(ns->s);
+
+ return selectors;
+}
+
+
/* Forward declarations */
static void
mgmt_fe_session_register_event(struct mgmt_fe_session_ctx *session,
@@ -190,6 +274,7 @@ static void mgmt_fe_cleanup_session(struct mgmt_fe_session_ctx **sessionp)
assert(session->adapter->refcount > 1);
mgmt_fe_adapter_unlock(&session->adapter);
}
+ mgmt_fe_ns_string_remove_session(&mgmt_fe_ns_strings, session);
darr_free_free(session->notify_xpaths);
hash_release(mgmt_fe_sessions, session);
XFREE(MTYPE_MGMTD_FE_SESSION, session);
@@ -1542,32 +1627,90 @@ static void fe_adapter_handle_edit(struct mgmt_fe_session_ctx *session,
* @__msg: the message data.
* @msg_len: the length of the message data.
*/
-static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session,
- void *__msg, size_t msg_len)
+static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session, void *__msg,
+ size_t msg_len)
{
struct mgmt_msg_notify_select *msg = __msg;
uint64_t req_id = msg->req_id;
const char **selectors = NULL;
const char **new;
+ const char **sp;
+ char *selstr = NULL;
+ uint64_t clients = 0;
+ uint ret;
if (msg_len >= sizeof(*msg)) {
- selectors = mgmt_msg_native_strings_decode(msg, msg_len,
- msg->selectors);
+ selectors = mgmt_msg_native_strings_decode(msg, msg_len, msg->selectors);
if (!selectors) {
- fe_adapter_send_error(session, req_id, false, -EINVAL,
- "Invalid message");
+ fe_adapter_send_error(session, req_id, false, -EINVAL, "Invalid message");
return;
}
}
+ if (DEBUG_MODE_CHECK(&mgmt_debug_fe, DEBUG_MODE_ALL)) {
+ selstr = frrstr_join(selectors, darr_len(selectors), ", ");
+ if (!selstr)
+ selstr = XSTRDUP(MTYPE_TMP, "");
+ }
+
if (msg->replace) {
+ mgmt_fe_ns_string_remove_session(&mgmt_fe_ns_strings, session);
+ // [ ] Keep a local tree to optimize sending selectors to BE?
+ // [*] Or just KISS and fanout the original message to BEs?
+ // mgmt_remove_add_notify_selectors(session->notify_xpaths, selectors);
darr_free_free(session->notify_xpaths);
session->notify_xpaths = selectors;
} else if (selectors) {
- new = darr_append_nz(session->notify_xpaths,
- darr_len(selectors));
+ // [ ] Keep a local tree to optimize sending selectors to BE?
+ // [*] Or just KISS and fanout the original message to BEs?
+ // mgmt_remove_add_notify_selectors(session->notify_xpaths, selectors);
+ new = darr_append_nz(session->notify_xpaths, darr_len(selectors));
memcpy(new, selectors, darr_len(selectors) * sizeof(*selectors));
- darr_free(selectors);
+ } else {
+ __log_err("Invalid msg from session-id: %Lu: no selectors present in non-replace msg",
+ session->session_id);
+ darr_free_free(selectors);
+ selectors = NULL;
+ goto done;
}
+
+
+ if (session->notify_xpaths && DEBUG_MODE_CHECK(&mgmt_debug_fe, DEBUG_MODE_ALL)) {
+ const char **sel = session->notify_xpaths;
+ char *s = frrstr_join(sel, darr_len(sel), ", ");
+ __dbg("New NOTIF %d selectors '%s' (replace: %d) txn-id: %Lu for session-id: %Lu",
+ darr_len(sel), s, msg->replace, session->cfg_txn_id, session->session_id);
+ XFREE(MTYPE_TMP, s);
+ }
+
+ /* Add the new selectors to the global tree */
+ darr_foreach_p (selectors, sp)
+ mgmt_fe_add_ns_string(&mgmt_fe_ns_strings, *sp, darr_strlen(*sp), session);
+
+ /* Check if any backends are interested in the new selectors. */
+ if (msg->replace) {
+ /* If we are replacing we'll send all the selectors again with replace flag */
+ clients = mgmt_be_interested_clients("/", MGMT_BE_XPATH_SUBSCR_TYPE_OPER);
+ } else {
+ darr_foreach_p (selectors, sp)
+ clients |= mgmt_be_interested_clients(*sp, MGMT_BE_XPATH_SUBSCR_TYPE_OPER);
+ }
+ if (!clients) {
+ __dbg("No backends provide oper for notify selectors: '%s' txn-id %Lu session-id: %Lu",
+ selstr, session->txn_id, session->session_id);
+ goto done;
+ }
+
+ /* We don't use a transaction for this, just send the message */
+ ret = mgmt_txn_send_notify_selectors(req_id, clients, msg->replace ? NULL : selectors);
+ if (ret) {
+ fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
+ "Failed to create a NOTIFY_SELECT transaction");
+ }
+done:
+ if (session->notify_xpaths != selectors)
+ darr_free(selectors);
+ if (selstr)
+ XFREE(MTYPE_TMP, selstr);
}
/**
@@ -1758,10 +1901,11 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen
{
struct mgmt_fe_client_adapter *adapter;
struct mgmt_fe_session_ctx *session;
- struct nb_node *nb_node;
- const char **xpath_prefix;
+ struct nb_node *nb_node = NULL;
+ struct listnode *node;
+ struct ns_string *ns;
const char *notif;
- bool sendit;
+ bool is_root;
uint len;
assert(msg->refer_id == 0);
@@ -1772,36 +1916,48 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen
return;
}
- /*
- * We need the nb_node to obtain a path which does not include any
- * specific list entry selectors
- */
- nb_node = nb_node_find(notif);
- if (!nb_node) {
- __log_err("No schema found for notification: %s", notif);
- return;
+ is_root = !strcmp(notif, "/");
+ if (!is_root) {
+ /*
+ * We need the nb_node to obtain a path which does not include any
+ * specific list entry selectors
+ */
+ nb_node = nb_node_find(notif);
+ if (!nb_node) {
+ __log_err("No schema found for notification: %s", notif);
+ return;
+ }
}
- FOREACH_ADAPTER_IN_LIST (adapter) {
- FOREACH_SESSION_IN_LIST (adapter, session) {
- /* If no selectors then always send */
- sendit = !session->notify_xpaths;
- darr_foreach_p (session->notify_xpaths, xpath_prefix) {
- len = strlen(*xpath_prefix);
- if (!strncmp(*xpath_prefix, notif, len) ||
- !strncmp(*xpath_prefix, nb_node->xpath,
- len)) {
- sendit = true;
- break;
- }
- }
- if (sendit) {
+ frr_each (ns_string, &mgmt_fe_ns_strings, ns) {
+ if (!is_root) {
+ len = strlen(ns->s);
+ if (strncmp(ns->s, notif, len) && strncmp(ns->s, nb_node->xpath, len))
+ continue;
+ }
+ for (ALL_LIST_ELEMENTS_RO(ns->sessions, node, session)) {
+ msg->refer_id = session->session_id;
+ (void)fe_adapter_send_native_msg(session->adapter, msg, msglen, false);
+ }
+ }
+
+ /*
+ * Send all YANG defined notifications to all sesisons with *no*
+ * selectors as well (i.e., original NETCONF/RESTCONF notification
+ * scheme).
+ */
+ if (!is_root && CHECK_FLAG(nb_node->snode->nodetype, LYS_NOTIF)) {
+ FOREACH_ADAPTER_IN_LIST (adapter) {
+ FOREACH_SESSION_IN_LIST (adapter, session) {
+ if (session->notify_xpaths)
+ continue;
msg->refer_id = session->session_id;
(void)fe_adapter_send_native_msg(adapter, msg,
msglen, false);
}
}
}
+
msg->refer_id = 0;
}
@@ -1810,9 +1966,10 @@ void mgmt_fe_adapter_lock(struct mgmt_fe_client_adapter *adapter)
adapter->refcount++;
}
-extern void mgmt_fe_adapter_unlock(struct mgmt_fe_client_adapter **adapter)
+void mgmt_fe_adapter_unlock(struct mgmt_fe_client_adapter **adapter)
{
struct mgmt_fe_client_adapter *a = *adapter;
+
assert(a && a->refcount);
if (!--a->refcount) {
@@ -1840,6 +1997,8 @@ void mgmt_fe_adapter_init(struct event_loop *tm)
hash_create(mgmt_fe_session_hash_key, mgmt_fe_session_hash_cmp,
"MGMT Frontend Sessions");
+ ns_string_init(&mgmt_fe_ns_strings);
+
snprintf(server_path, sizeof(server_path), MGMTD_FE_SOCK_NAME);
if (msg_server_init(&mgmt_fe_server, server_path, tm,
@@ -1869,10 +2028,13 @@ void mgmt_fe_adapter_destroy(void)
msg_server_cleanup(&mgmt_fe_server);
+
/* Deleting the adapters will delete all the sessions */
FOREACH_ADAPTER_IN_LIST (adapter)
mgmt_fe_adapter_delete(adapter);
+ mgmt_fe_free_ns_strings(&mgmt_fe_ns_strings);
+
hash_clean_and_free(&mgmt_fe_sessions, mgmt_fe_abort_if_session);
}
@@ -1885,8 +2047,7 @@ struct msg_conn *mgmt_fe_create_adapter(int conn_fd, union sockunion *from)
adapter = mgmt_fe_find_adapter_by_fd(conn_fd);
if (!adapter) {
- adapter = XCALLOC(MTYPE_MGMTD_FE_ADPATER,
- sizeof(struct mgmt_fe_client_adapter));
+ adapter = XCALLOC(MTYPE_MGMTD_FE_ADPATER, sizeof(struct mgmt_fe_client_adapter));
snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d",
conn_fd);
diff --git a/mgmtd/mgmt_fe_adapter.h b/mgmtd/mgmt_fe_adapter.h
index 4d94e7604..19a3d1634 100644
--- a/mgmtd/mgmt_fe_adapter.h
+++ b/mgmtd/mgmt_fe_adapter.h
@@ -225,6 +225,13 @@ extern int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id,
const char *errstr);
+/**
+ * mgmt_fe_get_all_selectors() - Get all selectors for all frontend adapters.
+ *
+ * Returns: A darr array of all selectors for all frontend adapters.
+ */
+extern char **mgmt_fe_get_all_selectors(void);
+
/* Fetch frontend client session set-config stats */
extern struct mgmt_setcfg_stats *
mgmt_fe_get_session_setcfg_stats(uint64_t session_id);
diff --git a/mgmtd/mgmt_history.c b/mgmtd/mgmt_history.c
index c97cb7f0f..934748b1f 100644
--- a/mgmtd/mgmt_history.c
+++ b/mgmtd/mgmt_history.c
@@ -177,6 +177,7 @@ static bool mgmt_history_dump_cmt_record_index(void)
return false;
}
+ assert(cnt <= 10); /* silence bad CLANG SA warning */
ret = fwrite(&cmt_info_set, sizeof(struct mgmt_cmt_info_t), cnt, fp);
fclose(fp);
if (ret != cnt) {
diff --git a/mgmtd/mgmt_main.c b/mgmtd/mgmt_main.c
index 1880d9441..7d909446c 100644
--- a/mgmtd/mgmt_main.c
+++ b/mgmtd/mgmt_main.c
@@ -159,6 +159,12 @@ const struct frr_yang_module_info ietf_netconf_with_defaults_info = {
* clients into mgmtd. The modules are used by libyang in order to support
* parsing binary data returns from the backend.
*/
+const struct frr_yang_module_info frr_backend_client_info = {
+ .name = "frr-backend",
+ .ignore_cfg_cbs = true,
+ .nodes = { { .xpath = NULL } },
+};
+
const struct frr_yang_module_info zebra_route_map_info = {
.name = "frr-zebra-route-map",
.ignore_cfg_cbs = true,
@@ -183,6 +189,7 @@ static const struct frr_yang_module_info *const mgmt_yang_modules[] = {
/*
* YANG module info used by backend clients get added here.
*/
+ &frr_backend_client_info,
&frr_zebra_cli_info,
&zebra_route_map_info,
diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c
index ccfdd7539..4afab389b 100644
--- a/mgmtd/mgmt_txn.c
+++ b/mgmtd/mgmt_txn.c
@@ -237,6 +237,7 @@ struct mgmt_txn_ctx {
struct event *clnup;
/* List of backend adapters involved in this transaction */
+ /* XXX reap this */
struct mgmt_txn_badapters_head be_adapters;
int refcount;
@@ -2651,6 +2652,52 @@ int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients,
return 0;
}
+int mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t clients, const char **selectors)
+{
+ struct mgmt_msg_notify_select *msg;
+ char **all_selectors = NULL;
+ uint64_t id;
+ int ret;
+ uint i;
+
+ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_notify_select, 0,
+ MTYPE_MSG_NATIVE_NOTIFY_SELECT);
+ msg->refer_id = MGMTD_TXN_ID_NONE;
+ msg->req_id = req_id;
+ msg->code = MGMT_MSG_CODE_NOTIFY_SELECT;
+ msg->replace = selectors == NULL;
+
+ if (selectors == NULL) {
+ /* Get selectors for all sessions */
+ all_selectors = mgmt_fe_get_all_selectors();
+ selectors = (const char **)all_selectors;
+ }
+
+ darr_foreach_i (selectors, i)
+ mgmt_msg_native_add_str(msg, selectors[i]);
+
+ assert(clients);
+ FOREACH_BE_CLIENT_BITS (id, clients) {
+ /* make sure the backend is running/connected */
+ if (!mgmt_be_get_adapter_by_id(id))
+ continue;
+ ret = mgmt_be_send_native(id, msg);
+ if (ret) {
+ __log_err("Could not send notify-select message to backend client %s",
+ mgmt_be_client_id2name(id));
+ continue;
+ }
+
+ __dbg("Sent notify-select req to backend client %s", mgmt_be_client_id2name(id));
+ }
+ mgmt_msg_native_free_msg(msg);
+
+ if (all_selectors)
+ darr_free_free(all_selectors);
+
+ return 0;
+}
+
/*
* Error reply from the backend client.
*/
diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h
index 37dadc017..879d20517 100644
--- a/mgmtd/mgmt_txn.h
+++ b/mgmtd/mgmt_txn.h
@@ -297,6 +297,16 @@ extern int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients,
LYD_FORMAT result_type, const char *xpath,
const char *data, size_t data_len);
+/**
+ * mgmt_txn_send_notify_selectors() - Send NOTIFY SELECT request.
+ * @req_id: FE client request identifier.
+ * @clients: Bitmask of clients to send RPC to.
+ * @selectors: Array of selectors or NULL to resend all selectors to BE clients.
+ *
+ * Returns 0 on success.
+ */
+extern int mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t clients, const char **selectors);
+
/*
* Notifiy backend adapter on connection.
*/
diff --git a/ospfd/ospf_asbr.c b/ospfd/ospf_asbr.c
index aa1146702..978a6fcc1 100644
--- a/ospfd/ospf_asbr.c
+++ b/ospfd/ospf_asbr.c
@@ -1145,8 +1145,7 @@ static void ospf_external_aggr_timer(struct ospf *ospf,
aggr->action = operation;
if (ospf->t_external_aggr) {
- if (ospf->aggr_action == OSPF_ROUTE_AGGR_ADD) {
-
+ if (ospf->aggr_action == OSPF_ROUTE_AGGR_ADD || operation != OSPF_ROUTE_AGGR_ADD) {
if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR))
zlog_debug("%s: Not required to restart timer,set is already added.",
__func__);
diff --git a/pimd/pim_bsm.c b/pimd/pim_bsm.c
index 6c4d64923..50fe543b2 100644
--- a/pimd/pim_bsm.c
+++ b/pimd/pim_bsm.c
@@ -354,24 +354,29 @@ static void pim_on_g2rp_timer(struct event *t)
bsrp = EVENT_ARG(t);
EVENT_OFF(bsrp->g2rp_timer);
bsgrp_node = bsrp->bsgrp_node;
-
- /* elapse time is the hold time of expired node */
- elapse = bsrp->rp_holdtime;
+ pim = bsgrp_node->scope->pim;
bsrp_addr = bsrp->rp_address;
- /* update elapse for all bsrp nodes */
- frr_each_safe (bsm_rpinfos, bsgrp_node->bsrp_list, bsrp_node) {
- bsrp_node->elapse_time += elapse;
-
- if (is_hold_time_elapsed(bsrp_node)) {
- bsm_rpinfos_del(bsgrp_node->bsrp_list, bsrp_node);
- pim_bsm_rpinfo_free(bsrp_node);
+ /*
+ * Update elapse for all bsrp nodes except on the BSR itself.
+ * The timer is meant to remove any bsr RPs learned from the BSR that
+ * we don't hear from anymore. on the BSR itself, no need to do this.
+ */
+ if (pim->global_scope.state != BSR_ELECTED) {
+ /* elapse time is the hold time of expired node */
+ elapse = bsrp->rp_holdtime;
+ frr_each_safe (bsm_rpinfos, bsgrp_node->bsrp_list, bsrp_node) {
+ bsrp_node->elapse_time += elapse;
+
+ if (is_hold_time_elapsed(bsrp_node)) {
+ bsm_rpinfos_del(bsgrp_node->bsrp_list, bsrp_node);
+ pim_bsm_rpinfo_free(bsrp_node);
+ }
}
}
/* Get the next elected rp node */
bsrp = bsm_rpinfos_first(bsgrp_node->bsrp_list);
- pim = bsgrp_node->scope->pim;
rn = route_node_lookup(pim->rp_table, &bsgrp_node->group);
if (!rn) {
@@ -386,7 +391,7 @@ static void pim_on_g2rp_timer(struct event *t)
return;
}
- if (rp_info->rp_src != RP_SRC_STATIC) {
+ if (rp_info->rp_src == RP_SRC_BSR) {
/* If new rp available, change it else delete the existing */
if (bsrp) {
pim_g2rp_timer_start(
@@ -2165,6 +2170,7 @@ static void cand_addrsel_config_write(struct vty *vty,
int pim_cand_config_write(struct pim_instance *pim, struct vty *vty)
{
struct bsm_scope *scope = &pim->global_scope;
+ struct cand_rp_group *group;
int ret = 0;
if (scope->cand_rp_addrsel.cfg_enable) {
@@ -2176,14 +2182,11 @@ int pim_cand_config_write(struct pim_instance *pim, struct vty *vty)
cand_addrsel_config_write(vty, &scope->cand_rp_addrsel);
vty_out(vty, "\n");
ret++;
+ }
- struct cand_rp_group *group;
-
- frr_each (cand_rp_groups, scope->cand_rp_groups, group) {
- vty_out(vty, " bsr candidate-rp group %pFX\n",
- &group->p);
- ret++;
- }
+ frr_each (cand_rp_groups, scope->cand_rp_groups, group) {
+ vty_out(vty, " bsr candidate-rp group %pFX\n", &group->p);
+ ret++;
}
if (scope->bsr_addrsel.cfg_enable) {
diff --git a/pimd/pim_bsr_rpdb.c b/pimd/pim_bsr_rpdb.c
index 02e7a69ff..860009312 100644
--- a/pimd/pim_bsr_rpdb.c
+++ b/pimd/pim_bsr_rpdb.c
@@ -417,7 +417,7 @@ void pim_crp_nht_update(struct pim_instance *pim, struct pim_nexthop_cache *pnc)
rp = bsr_crp_rps_find(scope->ebsr_rps, &ref);
assertf(rp, "addr=%pPA", &ref.addr);
- ok = pim_nht_pnc_is_valid(pim, pnc);
+ ok = pim_nht_pnc_is_valid(pim, pnc, PIMADDR_ANY);
if (ok == rp->nht_ok)
return;
diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c
index a34fb344f..a1ad26186 100644
--- a/pimd/pim_cmd.c
+++ b/pimd/pim_cmd.c
@@ -3296,7 +3296,7 @@ DEFUN (show_ip_rib,
return CMD_WARNING;
}
- if (!pim_nht_lookup(vrf->info, &nexthop, addr, 0)) {
+ if (!pim_nht_lookup(vrf->info, &nexthop, addr, PIMADDR_ANY, false)) {
vty_out(vty,
"Failure querying RIB nexthop for unicast address %s\n",
addr_str);
@@ -8878,21 +8878,31 @@ done:
}
DEFPY_YANG(pim_rpf_lookup_mode, pim_rpf_lookup_mode_cmd,
- "[no] rpf-lookup-mode ![urib-only|mrib-only|mrib-then-urib|lower-distance|longer-prefix]$mode",
+ "[no] rpf-lookup-mode\
+ ![urib-only|mrib-only|mrib-then-urib|lower-distance|longer-prefix]$mode\
+ [{group-list PREFIX_LIST$grp_list|source-list PREFIX_LIST$src_list}]",
NO_STR
"RPF lookup behavior\n"
"Lookup in unicast RIB only\n"
"Lookup in multicast RIB only\n"
"Try multicast RIB first, fall back to unicast RIB\n"
"Lookup both, use entry with lower distance\n"
- "Lookup both, use entry with longer prefix\n")
-{
- if (no)
- nb_cli_enqueue_change(vty, "./mcast-rpf-lookup", NB_OP_DESTROY, NULL);
- else
- nb_cli_enqueue_change(vty, "./mcast-rpf-lookup", NB_OP_MODIFY, mode);
+ "Lookup both, use entry with longer prefix\n"
+ "Set a specific mode matching group\n"
+ "Multicast group prefix list\n"
+ "Set a specific mode matching source address\n"
+ "Source address prefix list\n")
+{
+ if (no) {
+ nb_cli_enqueue_change(vty, "./mode", NB_OP_DESTROY, NULL);
+ nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL);
+ } else {
+ nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+ nb_cli_enqueue_change(vty, "./mode", NB_OP_MODIFY, mode);
+ }
- return nb_cli_apply_changes(vty, NULL);
+ return nb_cli_apply_changes(vty, "./mcast-rpf-lookup[group-list='%s'][source-list='%s']",
+ (grp_list ? grp_list : ""), (src_list ? src_list : ""));
}
struct cmd_node pim_node = {
diff --git a/pimd/pim_igmp_mtrace.c b/pimd/pim_igmp_mtrace.c
index ad6f26510..f0fbb2bbf 100644
--- a/pimd/pim_igmp_mtrace.c
+++ b/pimd/pim_igmp_mtrace.c
@@ -59,7 +59,8 @@ static bool mtrace_fwd_info_weak(struct pim_instance *pim,
memset(&nexthop, 0, sizeof(nexthop));
- if (!pim_nht_lookup(pim, &nexthop, mtracep->src_addr, 1)) {
+ /* TODO Is there any valid group address to use for lookup? */
+ if (!pim_nht_lookup(pim, &nexthop, mtracep->src_addr, PIMADDR_ANY, true)) {
if (PIM_DEBUG_MTRACE)
zlog_debug("mtrace not found neighbor");
return false;
@@ -354,7 +355,8 @@ static int mtrace_un_forward_packet(struct pim_instance *pim, struct ip *ip_hdr,
if (interface == NULL) {
memset(&nexthop, 0, sizeof(nexthop));
- if (!pim_nht_lookup(pim, &nexthop, ip_hdr->ip_dst, 0)) {
+ /* TODO Is there any valid group address to use for lookup? */
+ if (!pim_nht_lookup(pim, &nexthop, ip_hdr->ip_dst, PIMADDR_ANY, false)) {
if (PIM_DEBUG_MTRACE)
zlog_debug(
"Dropping mtrace packet, no route to destination");
@@ -535,8 +537,11 @@ static int mtrace_send_response(struct pim_instance *pim,
zlog_debug("mtrace response to RP");
} else {
memset(&nexthop, 0, sizeof(nexthop));
- /* TODO: should use unicast rib lookup */
- if (!pim_nht_lookup(pim, &nexthop, mtracep->rsp_addr, 1)) {
+ /* TODO: should use unicast rib lookup
+ * NEB 10/30/24 - Not sure why this needs the unicast rib...right now it will look up per the rpf mode
+ * Are any of the igmp_mtrace addresses a valid group address to use for lookups??
+ */
+ if (!pim_nht_lookup(pim, &nexthop, mtracep->rsp_addr, PIMADDR_ANY, true)) {
if (PIM_DEBUG_MTRACE)
zlog_debug(
"Dropped response qid=%ud, no route to response address",
diff --git a/pimd/pim_instance.h b/pimd/pim_instance.h
index 7f022111b..7aa9d857d 100644
--- a/pimd/pim_instance.h
+++ b/pimd/pim_instance.h
@@ -18,6 +18,7 @@
#include "pim_upstream.h"
#include "pim_mroute.h"
#include "pim_autorp.h"
+#include "pim_nht.h"
enum pim_spt_switchover {
PIM_SPT_IMMEDIATE,
@@ -116,7 +117,7 @@ struct pim_instance {
char *register_plist;
struct hash *nht_hash;
- enum pim_rpf_lookup_mode rpf_mode;
+ struct pim_lookup_mode_head rpf_mode;
void *ssm_info; /* per-vrf SSM configuration */
diff --git a/pimd/pim_mroute.c b/pimd/pim_mroute.c
index 93bdd8dac..6c13e1324 100644
--- a/pimd/pim_mroute.c
+++ b/pimd/pim_mroute.c
@@ -567,7 +567,8 @@ int pim_mroute_msg_wrvifwhole(int fd, struct interface *ifp, const char *buf,
* setting the SPTBIT to true
*/
if (!(pim_addr_is_any(up->upstream_register)) &&
- pim_nht_lookup(pim_ifp->pim, &source, up->upstream_register, 0)) {
+ pim_nht_lookup(pim_ifp->pim, &source, up->upstream_register, up->sg.grp,
+ false)) {
pim_register_stop_send(source.interface, &sg,
pim_ifp->primary_address,
up->upstream_register);
@@ -580,7 +581,8 @@ int pim_mroute_msg_wrvifwhole(int fd, struct interface *ifp, const char *buf,
__func__);
} else {
if (I_am_RP(pim_ifp->pim, up->sg.grp)) {
- if (pim_nht_lookup(pim_ifp->pim, &source, up->upstream_register, 0))
+ if (pim_nht_lookup(pim_ifp->pim, &source, up->upstream_register,
+ up->sg.grp, false))
pim_register_stop_send(
source.interface, &sg,
pim_ifp->primary_address,
diff --git a/pimd/pim_msdp.c b/pimd/pim_msdp.c
index 5e5ee5e91..46d5f4881 100644
--- a/pimd/pim_msdp.c
+++ b/pimd/pim_msdp.c
@@ -706,7 +706,7 @@ bool pim_msdp_peer_rpf_check(struct pim_msdp_peer *mp, struct in_addr rp)
}
/* check if the MSDP peer is the nexthop for the RP */
- if (pim_nht_lookup(mp->pim, &nexthop, rp, 0) &&
+ if (pim_nht_lookup(mp->pim, &nexthop, rp, PIMADDR_ANY, false) &&
nexthop.mrib_nexthop_addr.s_addr == mp->peer.s_addr) {
return true;
}
diff --git a/pimd/pim_nb.c b/pimd/pim_nb.c
index b55541b81..ea9ce3cec 100644
--- a/pimd/pim_nb.c
+++ b/pimd/pim_nb.c
@@ -266,7 +266,14 @@ const struct frr_yang_module_info frr_pim_info = {
{
.xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mcast-rpf-lookup",
.cbs = {
- .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_modify,
+ .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_create,
+ .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mcast-rpf-lookup/mode",
+ .cbs = {
+ .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_mode_modify
}
},
{
diff --git a/pimd/pim_nb.h b/pimd/pim_nb.h
index a5ef6ad60..a15c6e6d9 100644
--- a/pimd/pim_nb.h
+++ b/pimd/pim_nb.h
@@ -102,7 +102,11 @@ int routing_control_plane_protocols_control_plane_protocol_pim_address_family_re
struct nb_cb_modify_args *args);
int routing_control_plane_protocols_control_plane_protocol_pim_address_family_register_accept_list_destroy(
struct nb_cb_destroy_args *args);
-int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_modify(
+int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_create(
+ struct nb_cb_create_args *args);
+int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_destroy(
+ struct nb_cb_destroy_args *args);
+int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_mode_modify(
struct nb_cb_modify_args *args);
int lib_interface_pim_address_family_dr_priority_modify(
struct nb_cb_modify_args *args);
diff --git a/pimd/pim_nb_config.c b/pimd/pim_nb_config.c
index b55d08bab..51f061588 100644
--- a/pimd/pim_nb_config.c
+++ b/pimd/pim_nb_config.c
@@ -1895,12 +1895,25 @@ int routing_control_plane_protocols_control_plane_protocol_pim_address_family_re
/*
* XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mcast-rpf-lookup
*/
-int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_modify(
- struct nb_cb_modify_args *args)
+int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_create(
+ struct nb_cb_create_args *args)
+{
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ case NB_EV_APPLY:
+ break;
+ }
+
+ return NB_OK;
+}
+
+int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_destroy(
+ struct nb_cb_destroy_args *args)
{
struct vrf *vrf;
struct pim_instance *pim;
- enum pim_rpf_lookup_mode old_mode;
switch (args->event) {
case NB_EV_VALIDATE:
@@ -1910,15 +1923,37 @@ int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mc
case NB_EV_APPLY:
vrf = nb_running_get_entry(args->dnode, NULL, true);
pim = vrf->info;
- old_mode = pim->rpf_mode;
- pim->rpf_mode = yang_dnode_get_enum(args->dnode, NULL);
+ pim_nht_change_rpf_mode(pim, yang_dnode_get_string(args->dnode, "group-list"),
+ yang_dnode_get_string(args->dnode, "source-list"),
+ MCAST_NO_CONFIG);
+ break;
+ }
- if (pim->rpf_mode != old_mode &&
- /* MCAST_MIX_MRIB_FIRST is the default if not configured */
- (old_mode != MCAST_NO_CONFIG && pim->rpf_mode != MCAST_MIX_MRIB_FIRST)) {
- pim_nht_mode_changed(pim);
- }
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mcast-rpf-lookup/mode
+ */
+int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_mode_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct vrf *vrf;
+ struct pim_instance *pim;
+ enum pim_rpf_lookup_mode mode = MCAST_NO_CONFIG;
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+
+ case NB_EV_APPLY:
+ vrf = nb_running_get_entry(args->dnode, NULL, true);
+ pim = vrf->info;
+ mode = yang_dnode_get_enum(args->dnode, NULL);
+ pim_nht_change_rpf_mode(pim, yang_dnode_get_string(args->dnode, "../group-list"),
+ yang_dnode_get_string(args->dnode, "../source-list"), mode);
break;
}
diff --git a/pimd/pim_nht.c b/pimd/pim_nht.c
index 00ab46b4c..1e9ea24b2 100644
--- a/pimd/pim_nht.c
+++ b/pimd/pim_nht.c
@@ -34,6 +34,176 @@
#include "pim_register.h"
#include "pim_vxlan.h"
+DEFINE_MTYPE_STATIC(PIMD, PIM_LOOKUP_MODE, "PIM RPF lookup mode");
+DEFINE_MTYPE_STATIC(PIMD, PIM_LOOKUP_MODE_STR, "PIM RPF lookup mode prefix list string");
+
+static void pim_update_rp_nh(struct pim_instance *pim, struct pim_nexthop_cache *pnc);
+static int pim_update_upstream_nh(struct pim_instance *pim, struct pim_nexthop_cache *pnc);
+
+static int pim_lookup_mode_cmp(const struct pim_lookup_mode *l, const struct pim_lookup_mode *r)
+{
+ /* Let's just sort anything with both lists set above those with only one list set,
+ * which is above the global where neither are set
+ */
+
+ /* Both are set on right, either lower or equal */
+ if (l->grp_plist != NULL && l->src_plist != NULL)
+ return (r->grp_plist == NULL || r->src_plist == NULL) ? -1 : 0;
+
+ /* Only one set on the left */
+ if (!(l->grp_plist == NULL && l->src_plist == NULL)) {
+ /* Lower only if both are not set on right */
+ if (r->grp_plist == NULL && r->src_plist == NULL)
+ return -1;
+ /* Higher only if both are set on right */
+ if (r->grp_plist != NULL && r->src_plist != NULL)
+ return 1;
+ /* Otherwise both sides have at least one set, so equal */
+ return 0;
+ }
+
+ /* Neither set on left, so equal if neither set on right also */
+ if (r->grp_plist == NULL && r->src_plist == NULL)
+ return 0;
+
+ /* Otherwise higher */
+ return 1;
+}
+
+DECLARE_SORTLIST_NONUNIQ(pim_lookup_mode, struct pim_lookup_mode, list, pim_lookup_mode_cmp);
+
+static void pim_lookup_mode_free(struct pim_lookup_mode *m)
+{
+ if (m->grp_plist)
+ XFREE(MTYPE_PIM_LOOKUP_MODE_STR, m->grp_plist);
+ if (m->src_plist)
+ XFREE(MTYPE_PIM_LOOKUP_MODE_STR, m->src_plist);
+ XFREE(MTYPE_PIM_LOOKUP_MODE, m);
+}
+
+static void pim_lookup_mode_list_free(struct pim_lookup_mode_head *head)
+{
+ struct pim_lookup_mode *m;
+
+ while ((m = pim_lookup_mode_pop(head)))
+ pim_lookup_mode_free(m);
+}
+
+enum pim_rpf_lookup_mode pim_get_lookup_mode(struct pim_instance *pim, pim_addr group,
+ pim_addr source)
+{
+ struct pim_lookup_mode *m;
+ struct prefix_list *plist;
+ struct prefix p;
+
+ frr_each_safe (pim_lookup_mode, &(pim->rpf_mode), m) {
+ if (!pim_addr_is_any(group) && m->grp_plist) {
+ /* Match group against plist, continue if no match */
+ plist = prefix_list_lookup(PIM_AFI, m->grp_plist);
+ if (plist == NULL)
+ continue;
+ pim_addr_to_prefix(&p, group);
+ if (prefix_list_apply(plist, &p) == PREFIX_DENY)
+ continue;
+ }
+
+ if (!pim_addr_is_any(source) && m->src_plist) {
+ /* Match source against plist, continue if no match */
+ plist = prefix_list_lookup(PIM_AFI, m->src_plist);
+ if (plist == NULL)
+ continue;
+ pim_addr_to_prefix(&p, source);
+ if (prefix_list_apply(plist, &p) == PREFIX_DENY)
+ continue;
+ }
+
+ /* If lookup mode has a group list, but no group is provided, don't match it */
+ if (pim_addr_is_any(group) && m->grp_plist)
+ continue;
+
+ /* If lookup mode has a source list, but no source is provided, don't match it */
+ if (pim_addr_is_any(source) && m->src_plist)
+ continue;
+
+ /* Match found */
+ return m->mode;
+ }
+
+ /* This shouldn't happen since we have the global mode, but if it's gone,
+ * just return the default of no config
+ */
+ if (PIM_DEBUG_PIM_NHT)
+ zlog_debug("%s: No RPF lookup matched for given group %pPA and source %pPA",
+ __func__, &group, &source);
+
+ return MCAST_NO_CONFIG;
+}
+
+static bool pim_rpf_mode_changed(enum pim_rpf_lookup_mode old, enum pim_rpf_lookup_mode new)
+{
+ if (old != new) {
+ /* These two are equivalent, so don't update in that case */
+ if (old == MCAST_NO_CONFIG && new == MCAST_MIX_MRIB_FIRST)
+ return false;
+ if (old == MCAST_MIX_MRIB_FIRST && new == MCAST_NO_CONFIG)
+ return false;
+ return true;
+ }
+ return false;
+}
+
+struct pnc_mode_update_hash_walk_data {
+ struct pim_instance *pim;
+ struct prefix_list *grp_plist;
+ struct prefix_list *src_plist;
+};
+
+static int pim_nht_hash_mode_update_helper(struct hash_bucket *bucket, void *arg)
+{
+ struct pim_nexthop_cache *pnc = bucket->data;
+ struct pnc_mode_update_hash_walk_data *pwd = arg;
+ struct pim_instance *pim = pwd->pim;
+ struct prefix p;
+
+ pim_addr_to_prefix(&p, pnc->addr);
+
+ /* Make sure this pnc entry matches the prefix lists */
+ /* TODO: For now, pnc only has the source address, so we can only check that */
+ if (pwd->src_plist &&
+ (pim_addr_is_any(pnc->addr) || prefix_list_apply(pwd->src_plist, &p) == PREFIX_DENY))
+ return HASHWALK_CONTINUE;
+
+ /* Otherwise the address is any, or matches the prefix list, or no prefix list to match, so do the updates */
+ /* TODO for RP, there are groups....but I don't think we'd want to use those */
+ if (listcount(pnc->rp_list))
+ pim_update_rp_nh(pim, pnc);
+
+ /* TODO for upstream, there is an S,G key...can/should we use that group?? */
+ if (pnc->upstream_hash->count)
+ pim_update_upstream_nh(pim, pnc);
+
+ if (pnc->candrp_count)
+ pim_crp_nht_update(pim, pnc);
+
+ return HASHWALK_CONTINUE;
+}
+
+static void pim_rpf_mode_changed_update(struct pim_instance *pim, const char *group_plist,
+ const char *source_plist)
+{
+ struct pnc_mode_update_hash_walk_data pwd;
+
+ /* Update the refresh time to force new lookups if needed */
+ pim_rpf_set_refresh_time(pim);
+
+ /* Force update the registered RP and upstreams for all cache entries */
+ pwd.pim = pim;
+ pwd.grp_plist = prefix_list_lookup(PIM_AFI, group_plist);
+ pwd.src_plist = prefix_list_lookup(PIM_AFI, source_plist);
+
+ hash_walk(pim->nht_hash, pim_nht_hash_mode_update_helper, &pwd);
+}
+
/**
* pim_sendmsg_zebra_rnh -- Format and send a nexthop register/Unregister
* command to Zebra.
@@ -106,9 +276,10 @@ static struct pim_nexthop_cache *pim_nexthop_cache_add(struct pim_instance *pim,
return pnc;
}
-static bool pim_nht_pnc_has_answer(struct pim_instance *pim, struct pim_nexthop_cache *pnc)
+static bool pim_nht_pnc_has_answer(struct pim_instance *pim, struct pim_nexthop_cache *pnc,
+ pim_addr group)
{
- switch (pim->rpf_mode) {
+ switch (pim_get_lookup_mode(pim, group, pnc->addr)) {
case MCAST_MRIB_ONLY:
return CHECK_FLAG(pnc->mrib.flags, PIM_NEXTHOP_ANSWER_RECEIVED);
@@ -133,25 +304,28 @@ static bool pim_nht_pnc_has_answer(struct pim_instance *pim, struct pim_nexthop_
}
static struct pim_nexthop_cache_rib *pim_pnc_get_rib(struct pim_instance *pim,
- struct pim_nexthop_cache *pnc)
+ struct pim_nexthop_cache *pnc, pim_addr group)
{
struct pim_nexthop_cache_rib *pnc_rib = NULL;
+ enum pim_rpf_lookup_mode mode;
- if (pim->rpf_mode == MCAST_MRIB_ONLY)
+ mode = pim_get_lookup_mode(pim, group, pnc->addr);
+
+ if (mode == MCAST_MRIB_ONLY)
pnc_rib = &pnc->mrib;
- else if (pim->rpf_mode == MCAST_URIB_ONLY)
+ else if (mode == MCAST_URIB_ONLY)
pnc_rib = &pnc->urib;
- else if (pim->rpf_mode == MCAST_MIX_MRIB_FIRST || pim->rpf_mode == MCAST_NO_CONFIG) {
+ else if (mode == MCAST_MIX_MRIB_FIRST || mode == MCAST_NO_CONFIG) {
if (pnc->mrib.nexthop_num > 0)
pnc_rib = &pnc->mrib;
else
pnc_rib = &pnc->urib;
- } else if (pim->rpf_mode == MCAST_MIX_DISTANCE) {
+ } else if (mode == MCAST_MIX_DISTANCE) {
if (pnc->mrib.distance <= pnc->urib.distance)
pnc_rib = &pnc->mrib;
else
pnc_rib = &pnc->urib;
- } else if (pim->rpf_mode == MCAST_MIX_PFXLEN) {
+ } else if (mode == MCAST_MIX_PFXLEN) {
if (pnc->mrib.prefix_len >= pnc->urib.prefix_len)
pnc_rib = &pnc->mrib;
else
@@ -161,9 +335,151 @@ static struct pim_nexthop_cache_rib *pim_pnc_get_rib(struct pim_instance *pim,
return pnc_rib;
}
-bool pim_nht_pnc_is_valid(struct pim_instance *pim, struct pim_nexthop_cache *pnc)
+void pim_nht_change_rpf_mode(struct pim_instance *pim, const char *group_plist,
+ const char *source_plist, enum pim_rpf_lookup_mode mode)
+{
+ struct pim_lookup_mode *m;
+ bool found = false;
+ bool update = false;
+ const char *glist = NULL;
+ const char *slist = NULL;
+
+ /* Prefix lists may be passed in as empty string, leave them NULL instead */
+ if (group_plist && strlen(group_plist))
+ glist = group_plist;
+ if (source_plist && strlen(source_plist))
+ slist = source_plist;
+
+ frr_each_safe (pim_lookup_mode, &(pim->rpf_mode), m) {
+ if ((m->grp_plist && glist && strmatch(m->grp_plist, glist)) &&
+ (m->src_plist && slist && strmatch(m->src_plist, slist))) {
+ /* Group and source plists are both set and matched */
+ found = true;
+ if (mode == MCAST_NO_CONFIG) {
+ /* MCAST_NO_CONFIG means we should remove this lookup mode
+ * We don't know what other modes might match, or if only the global, so we need to
+ * update all lookups
+ */
+ pim_lookup_mode_del(&pim->rpf_mode, m);
+ pim_lookup_mode_free(m);
+ glist = NULL;
+ slist = NULL;
+ update = true;
+ } else {
+ /* Just changing mode */
+ update = pim_rpf_mode_changed(m->mode, mode);
+ m->mode = mode; /* Always make sure the mode is set, even if not updating */
+ }
+
+ if (update)
+ pim_rpf_mode_changed_update(pim, glist, slist);
+ break;
+ }
+
+ if ((m->grp_plist && glist && strmatch(m->grp_plist, glist)) &&
+ (!m->src_plist && !slist)) {
+ /* Only group list set and matched */
+ found = true;
+ if (mode == MCAST_NO_CONFIG) {
+ /* MCAST_NO_CONFIG means we should remove this lookup mode
+ * We don't know what other modes might match, or if only the global, so we need to
+ * update all lookups
+ */
+ pim_lookup_mode_del(&pim->rpf_mode, m);
+ pim_lookup_mode_free(m);
+ glist = NULL;
+ slist = NULL;
+ update = true;
+ } else {
+ /* Just changing mode */
+ update = pim_rpf_mode_changed(m->mode, mode);
+ m->mode = mode; /* Always make sure the mode is set, even if not updating */
+ }
+
+ if (update)
+ pim_rpf_mode_changed_update(pim, glist, slist);
+ break;
+ }
+
+ if ((!m->grp_plist && !glist) &&
+ (m->src_plist && slist && strmatch(m->src_plist, slist))) {
+ /* Only source list set and matched */
+ found = true;
+ if (mode == MCAST_NO_CONFIG) {
+ /* MCAST_NO_CONFIG means we should remove this lookup mode
+ * We don't know what other modes might match, or if only the global, so we need to
+ * update all lookups
+ */
+ pim_lookup_mode_del(&pim->rpf_mode, m);
+ pim_lookup_mode_free(m);
+ glist = NULL;
+ slist = NULL;
+ update = true;
+ } else {
+ /* Just changing mode */
+ update = pim_rpf_mode_changed(m->mode, mode);
+ m->mode = mode; /* Always make sure the mode is set, even if not updating */
+ }
+
+ if (update)
+ pim_rpf_mode_changed_update(pim, glist, slist);
+ break;
+ }
+
+ if (!m->grp_plist && !glist && !m->src_plist && !slist) {
+ /* No prefix lists set, so this is the global mode */
+ /* We never delete this mode, even when set back to MCAST_NO_CONFIG */
+ update = pim_rpf_mode_changed(m->mode, mode);
+ m->mode = mode; /* Always make sure the mode is set, even if not updating */
+ if (update)
+ pim_rpf_mode_changed_update(pim, glist, slist);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ /* Adding a new lookup mode with unique prefix lists, add it */
+ m = XCALLOC(MTYPE_PIM_LOOKUP_MODE, sizeof(struct pim_lookup_mode));
+ m->grp_plist = XSTRDUP(MTYPE_PIM_LOOKUP_MODE_STR, glist);
+ m->src_plist = XSTRDUP(MTYPE_PIM_LOOKUP_MODE_STR, slist);
+ m->mode = mode;
+ pim_lookup_mode_add(&(pim->rpf_mode), m);
+ pim_rpf_mode_changed_update(pim, glist, slist);
+ }
+}
+
+int pim_lookup_mode_write(struct pim_instance *pim, struct vty *vty)
{
- switch (pim->rpf_mode) {
+ int writes = 0;
+ struct pim_lookup_mode *m;
+
+ frr_each_safe (pim_lookup_mode, &(pim->rpf_mode), m) {
+ if (m->mode == MCAST_NO_CONFIG)
+ continue;
+
+ ++writes;
+ vty_out(vty, " rpf-lookup-mode %s",
+ m->mode == MCAST_URIB_ONLY ? "urib-only"
+ : m->mode == MCAST_MRIB_ONLY ? "mrib-only"
+ : m->mode == MCAST_MIX_MRIB_FIRST ? "mrib-then-urib"
+ : m->mode == MCAST_MIX_DISTANCE ? "lower-distance"
+ : "longer-prefix");
+
+ if (m->grp_plist)
+ vty_out(vty, " group-list %s", m->grp_plist);
+
+ if (m->src_plist)
+ vty_out(vty, " source-list %s", m->src_plist);
+
+ vty_out(vty, "\n");
+ }
+ return writes;
+}
+
+bool pim_nht_pnc_is_valid(struct pim_instance *pim, struct pim_nexthop_cache *pnc, pim_addr group)
+{
+ switch (pim_get_lookup_mode(pim, group, pnc->addr)) {
case MCAST_MRIB_ONLY:
return CHECK_FLAG(pnc->mrib.flags, PIM_NEXTHOP_VALID);
@@ -275,6 +591,7 @@ bool pim_nht_find_or_track(struct pim_instance *pim, pim_addr addr, struct pim_u
{
struct pim_nexthop_cache *pnc;
struct listnode *ch_node = NULL;
+ pim_addr group = PIMADDR_ANY;
/* This will find the entry and add it to tracking if not found */
pnc = pim_nht_get(pim, addr);
@@ -289,10 +606,12 @@ bool pim_nht_find_or_track(struct pim_instance *pim, pim_addr addr, struct pim_u
}
/* Store the upstream if provided and not currently in the list */
- if (up != NULL)
+ if (up != NULL) {
(void)hash_get(pnc->upstream_hash, up, hash_alloc_intern);
+ group = up->sg.grp;
+ }
- if (pim_nht_pnc_is_valid(pim, pnc)) {
+ if (pim_nht_pnc_is_valid(pim, pnc, group)) {
if (out_pnc)
memcpy(out_pnc, pnc, sizeof(struct pim_nexthop_cache));
return true;
@@ -315,7 +634,7 @@ bool pim_nht_candrp_add(struct pim_instance *pim, pim_addr addr)
pnc = pim_nht_get(pim, addr);
pnc->candrp_count++;
- return pim_nht_pnc_is_valid(pim, pnc);
+ return pim_nht_pnc_is_valid(pim, pnc, PIMADDR_ANY);
}
static void pim_nht_drop_maybe(struct pim_instance *pim, struct pim_nexthop_cache *pnc)
@@ -448,7 +767,7 @@ bool pim_nht_bsr_rpf_check(struct pim_instance *pim, pim_addr bsr_addr,
lookup.addr = bsr_addr;
pnc = hash_lookup(pim->nht_hash, &lookup);
- if (!pnc || !pim_nht_pnc_has_answer(pim, pnc)) {
+ if (!pnc || !pim_nht_pnc_has_answer(pim, pnc, PIMADDR_ANY)) {
/* BSM from a new freshly registered BSR - do a synchronous
* zebra query since otherwise we'd drop the first packet,
* leading to additional delay in picking up BSM data
@@ -465,9 +784,8 @@ bool pim_nht_bsr_rpf_check(struct pim_instance *pim, pim_addr bsr_addr,
int num_ifindex;
memset(nexthop_tab, 0, sizeof(nexthop_tab));
- num_ifindex = zclient_lookup_nexthop(
- pim, nexthop_tab, router->multipath, bsr_addr,
- PIM_NEXTHOP_LOOKUP_MAX);
+ num_ifindex = zclient_lookup_nexthop(pim, nexthop_tab, router->multipath, bsr_addr,
+ PIMADDR_ANY, PIM_NEXTHOP_LOOKUP_MAX);
if (num_ifindex <= 0)
return false;
@@ -507,7 +825,7 @@ bool pim_nht_bsr_rpf_check(struct pim_instance *pim, pim_addr bsr_addr,
return false;
}
- if (pim_nht_pnc_is_valid(pim, pnc)) {
+ if (pim_nht_pnc_is_valid(pim, pnc, PIMADDR_ANY)) {
/* if we accept BSMs from more than one ECMP nexthop, this will cause
* BSM message "multiplication" for each ECMP hop. i.e. if you have
* 4-way ECMP and 4 hops you end up with 256 copies of each BSM
@@ -515,7 +833,7 @@ bool pim_nht_bsr_rpf_check(struct pim_instance *pim, pim_addr bsr_addr,
*
* so... only accept the first (IPv4) valid nexthop as source.
*/
- struct pim_nexthop_cache_rib *rib = pim_pnc_get_rib(pim, pnc);
+ struct pim_nexthop_cache_rib *rib = pim_pnc_get_rib(pim, pnc, PIMADDR_ANY);
for (nh = rib->nexthop; nh; nh = nh->next) {
pim_addr nhaddr;
@@ -754,14 +1072,17 @@ static bool pim_ecmp_nexthop_search(struct pim_instance *pim, struct pim_nexthop
pim_addr nh_addr;
pim_addr grp_addr;
struct pim_nexthop_cache_rib *rib;
+ pim_addr group;
+
+ group = pim_addr_from_prefix(grp);
/* Early return if required parameters aren't provided */
- if (!pim || !pnc || !pim_nht_pnc_is_valid(pim, pnc) || !nexthop || !grp)
+ if (!pim || !pnc || !pim_nht_pnc_is_valid(pim, pnc, group) || !nexthop || !grp)
return false;
nh_addr = nexthop->mrib_nexthop_addr;
grp_addr = pim_addr_from_prefix(grp);
- rib = pim_pnc_get_rib(pim, pnc);
+ rib = pim_pnc_get_rib(pim, pnc, group);
/* Current Nexthop is VALID, check to stay on the current path. */
if (nexthop->interface && nexthop->interface->info &&
@@ -934,6 +1255,9 @@ bool pim_nht_lookup_ecmp(struct pim_instance *pim, struct pim_nexthop *nexthop,
uint32_t hash_val = 0;
uint32_t mod_val = 0;
uint32_t num_nbrs = 0;
+ pim_addr group;
+
+ group = pim_addr_from_prefix(grp);
if (PIM_DEBUG_PIM_NHT_DETAIL)
zlog_debug("%s: Looking up: %pPA(%s), last lookup time: %lld", __func__, &src,
@@ -941,12 +1265,12 @@ bool pim_nht_lookup_ecmp(struct pim_instance *pim, struct pim_nexthop *nexthop,
pnc = pim_nexthop_cache_find(pim, src);
if (pnc) {
- if (pim_nht_pnc_has_answer(pim, pnc))
+ if (pim_nht_pnc_has_answer(pim, pnc, group))
return pim_ecmp_nexthop_search(pim, pnc, nexthop, src, grp, neighbor_needed);
}
memset(nexthop_tab, 0, sizeof(struct pim_zlookup_nexthop) * router->multipath);
- num_ifindex = zclient_lookup_nexthop(pim, nexthop_tab, router->multipath, src,
+ num_ifindex = zclient_lookup_nexthop(pim, nexthop_tab, router->multipath, src, group,
PIM_NEXTHOP_LOOKUP_MAX);
if (num_ifindex < 1) {
if (PIM_DEBUG_PIM_NHT)
@@ -1051,7 +1375,7 @@ bool pim_nht_lookup_ecmp(struct pim_instance *pim, struct pim_nexthop *nexthop,
}
bool pim_nht_lookup(struct pim_instance *pim, struct pim_nexthop *nexthop, pim_addr addr,
- int neighbor_needed)
+ pim_addr group, bool neighbor_needed)
{
struct pim_zlookup_nexthop nexthop_tab[router->multipath];
struct pim_neighbor *nbr = NULL;
@@ -1087,7 +1411,7 @@ bool pim_nht_lookup(struct pim_instance *pim, struct pim_nexthop *nexthop, pim_a
&addr, nexthop->last_lookup_time, pim->last_route_change_time);
memset(nexthop_tab, 0, sizeof(struct pim_zlookup_nexthop) * router->multipath);
- num_ifindex = zclient_lookup_nexthop(pim, nexthop_tab, router->multipath, addr,
+ num_ifindex = zclient_lookup_nexthop(pim, nexthop_tab, router->multipath, addr, group,
PIM_NEXTHOP_LOOKUP_MAX);
if (num_ifindex < 1) {
if (PIM_DEBUG_PIM_NHT)
@@ -1349,36 +1673,6 @@ void pim_nexthop_update(struct vrf *vrf, struct prefix *match, struct zapi_route
pim_crp_nht_update(pim, pnc);
}
-static int pim_nht_hash_mode_update_helper(struct hash_bucket *bucket, void *arg)
-{
- struct pim_nexthop_cache *pnc = bucket->data;
- struct pnc_hash_walk_data *pwd = arg;
- struct pim_instance *pim = pwd->pim;
-
- if (listcount(pnc->rp_list))
- pim_update_rp_nh(pim, pnc);
-
- if (pnc->upstream_hash->count)
- pim_update_upstream_nh(pim, pnc);
-
- if (pnc->candrp_count)
- pim_crp_nht_update(pim, pnc);
-
- return HASHWALK_CONTINUE;
-}
-
-void pim_nht_mode_changed(struct pim_instance *pim)
-{
- struct pnc_hash_walk_data pwd;
-
- /* Update the refresh time to force new lookups if needed */
- pim_rpf_set_refresh_time(pim);
-
- /* Force update the registered RP and upstreams for all cache entries */
- pwd.pim = pim;
- hash_walk(pim->nht_hash, pim_nht_hash_mode_update_helper, &pwd);
-}
-
/* Cleanup pim->nht_hash each node data */
static void pim_nht_hash_clean(void *data)
{
@@ -1418,11 +1712,19 @@ static bool pim_nht_equal(const void *arg1, const void *arg2)
void pim_nht_init(struct pim_instance *pim)
{
char hash_name[64];
+ struct pim_lookup_mode *global_mode;
snprintf(hash_name, sizeof(hash_name), "PIM %s NHT Hash", pim->vrf->name);
pim->nht_hash = hash_create_size(256, pim_nht_hash_key, pim_nht_equal, hash_name);
- pim->rpf_mode = MCAST_NO_CONFIG;
+ pim_lookup_mode_init(&(pim->rpf_mode));
+
+ /* Add the default global mode */
+ global_mode = XCALLOC(MTYPE_PIM_LOOKUP_MODE, sizeof(*global_mode));
+ global_mode->grp_plist = NULL;
+ global_mode->src_plist = NULL;
+ global_mode->mode = MCAST_NO_CONFIG;
+ pim_lookup_mode_add(&(pim->rpf_mode), global_mode);
if (PIM_DEBUG_ZEBRA)
zlog_debug("%s: NHT hash init: %s ", __func__, hash_name);
@@ -1432,4 +1734,7 @@ void pim_nht_terminate(struct pim_instance *pim)
{
/* Traverse and cleanup nht_hash */
hash_clean_and_free(&pim->nht_hash, (void *)pim_nht_hash_clean);
+
+ pim_lookup_mode_list_free(&(pim->rpf_mode));
+ pim_lookup_mode_fini(&(pim->rpf_mode));
}
diff --git a/pimd/pim_nht.h b/pimd/pim_nht.h
index 144139f40..671fa8720 100644
--- a/pimd/pim_nht.h
+++ b/pimd/pim_nht.h
@@ -16,6 +16,15 @@
#include "pim_rp.h"
#include "pim_rpf.h"
+PREDECL_SORTLIST_NONUNIQ(pim_lookup_mode);
+
+struct pim_lookup_mode {
+ char *grp_plist;
+ char *src_plist;
+ enum pim_rpf_lookup_mode mode;
+ struct pim_lookup_mode_item list;
+};
+
/* PIM nexthop cache value structure. */
struct pim_nexthop_cache_rib {
/* IGP route's metric. */
@@ -54,8 +63,22 @@ struct pnc_hash_walk_data {
struct interface *ifp;
};
+/* Find the right lookup mode for the given group and/or source
+ * either may be ANY (although source should realistically always be provided)
+ * Find the lookup mode that has matching group and/or source prefix lists, or the global mode.
+ */
+enum pim_rpf_lookup_mode pim_get_lookup_mode(struct pim_instance *pim, pim_addr group,
+ pim_addr source);
+
+/* Change the RPF lookup config, may trigger updates to RP's and Upstreams registered for matching cache entries */
+void pim_nht_change_rpf_mode(struct pim_instance *pim, const char *group_plist,
+ const char *source_plist, enum pim_rpf_lookup_mode mode);
+
+/* Write the rpf lookup mode configuration */
+int pim_lookup_mode_write(struct pim_instance *pim, struct vty *vty);
+
/* Verify that we have nexthop information in the cache entry */
-bool pim_nht_pnc_is_valid(struct pim_instance *pim, struct pim_nexthop_cache *pnc);
+bool pim_nht_pnc_is_valid(struct pim_instance *pim, struct pim_nexthop_cache *pnc, pim_addr group);
/* Get (or add) the NH cache entry for the given address */
struct pim_nexthop_cache *pim_nht_get(struct pim_instance *pim, pim_addr addr);
@@ -109,7 +132,7 @@ bool pim_nht_lookup_ecmp(struct pim_instance *pim, struct pim_nexthop *nexthop,
* a synchronous lookup. No ECMP decision is made.
*/
bool pim_nht_lookup(struct pim_instance *pim, struct pim_nexthop *nexthop, pim_addr addr,
- int neighbor_needed);
+ pim_addr group, bool neighbor_needed);
/* Performs a pim_nht_lookup_ecmp and returns the mroute VIF index of the nexthop interface */
int pim_nht_lookup_ecmp_if_vif_index(struct pim_instance *pim, pim_addr src, struct prefix *grp);
@@ -117,9 +140,6 @@ int pim_nht_lookup_ecmp_if_vif_index(struct pim_instance *pim, pim_addr src, str
/* Tracked nexthop update from zebra */
void pim_nexthop_update(struct vrf *vrf, struct prefix *match, struct zapi_route *nhr);
-/* RPF lookup mode changed via configuration */
-void pim_nht_mode_changed(struct pim_instance *pim);
-
/* NHT init and finish funcitons */
void pim_nht_init(struct pim_instance *pim);
void pim_nht_terminate(struct pim_instance *pim);
diff --git a/pimd/pim_vty.c b/pimd/pim_vty.c
index 974cf30cf..a972a38c7 100644
--- a/pimd/pim_vty.c
+++ b/pimd/pim_vty.c
@@ -29,6 +29,7 @@
#include "pim_bfd.h"
#include "pim_bsm.h"
#include "pim_vxlan.h"
+#include "pim_nht.h"
#include "pim6_mld.h"
int pim_debug_config_write(struct vty *vty)
@@ -275,15 +276,7 @@ int pim_global_config_write_worker(struct pim_instance *pim, struct vty *vty)
}
}
- if (pim->rpf_mode != MCAST_NO_CONFIG) {
- ++writes;
- vty_out(vty, " rpf-lookup-mode %s\n",
- pim->rpf_mode == MCAST_URIB_ONLY ? "urib-only"
- : pim->rpf_mode == MCAST_MRIB_ONLY ? "mrib-only"
- : pim->rpf_mode == MCAST_MIX_MRIB_FIRST ? "mrib-then-urib"
- : pim->rpf_mode == MCAST_MIX_DISTANCE ? "lower-distance"
- : "longer-prefix");
- }
+ writes += pim_lookup_mode_write(pim, vty);
return writes;
}
diff --git a/pimd/pim_zlookup.c b/pimd/pim_zlookup.c
index febc595ad..4ffb5bac1 100644
--- a/pimd/pim_zlookup.c
+++ b/pimd/pim_zlookup.c
@@ -375,12 +375,16 @@ static int zclient_rib_lookup(struct pim_instance *pim, struct pim_zlookup_nexth
static int zclient_lookup_nexthop_once(struct pim_instance *pim,
struct pim_zlookup_nexthop nexthop_tab[], const int tab_size,
- pim_addr addr)
+ pim_addr addr, pim_addr group)
{
- if (pim->rpf_mode == MCAST_MRIB_ONLY)
+ enum pim_rpf_lookup_mode mode;
+
+ mode = pim_get_lookup_mode(pim, group, addr);
+
+ if (mode == MCAST_MRIB_ONLY)
return zclient_rib_lookup(pim, nexthop_tab, tab_size, addr, SAFI_MULTICAST);
- if (pim->rpf_mode == MCAST_URIB_ONLY)
+ if (mode == MCAST_URIB_ONLY)
return zclient_rib_lookup(pim, nexthop_tab, tab_size, addr, SAFI_UNICAST);
/* All other modes require looking up both tables and making a choice */
@@ -420,15 +424,14 @@ static int zclient_lookup_nexthop_once(struct pim_instance *pim,
/* Both tables have results, so compare them. Distance and prefix length are the same for all
* nexthops, so only compare the first in the list
*/
- if (pim->rpf_mode == MCAST_MIX_DISTANCE &&
+ if (mode == MCAST_MIX_DISTANCE &&
mrib_tab[0].protocol_distance > urib_tab[0].protocol_distance) {
if (PIM_DEBUG_PIM_NHT_DETAIL)
zlog_debug("%s: addr=%pPAs(%s), URIB has shortest distance", __func__,
&addr, pim->vrf->name);
memcpy(nexthop_tab, urib_tab, sizeof(struct pim_zlookup_nexthop) * tab_size);
return urib_num;
- } else if (pim->rpf_mode == MCAST_MIX_PFXLEN &&
- mrib_tab[0].prefix_len < urib_tab[0].prefix_len) {
+ } else if (mode == MCAST_MIX_PFXLEN && mrib_tab[0].prefix_len < urib_tab[0].prefix_len) {
if (PIM_DEBUG_PIM_NHT_DETAIL)
zlog_debug("%s: addr=%pPAs(%s), URIB has lengthest prefix length", __func__,
&addr, pim->vrf->name);
@@ -459,15 +462,13 @@ void zclient_lookup_read_pipe(struct event *thread)
return;
}
- zclient_lookup_nexthop_once(pim, nexthop_tab, 10, l);
+ zclient_lookup_nexthop_once(pim, nexthop_tab, 10, l, PIMADDR_ANY);
event_add_timer(router->master, zclient_lookup_read_pipe, zlookup, 60,
&zlookup_read);
}
-int zclient_lookup_nexthop(struct pim_instance *pim,
- struct pim_zlookup_nexthop nexthop_tab[],
- const int tab_size, pim_addr addr,
- int max_lookup)
+int zclient_lookup_nexthop(struct pim_instance *pim, struct pim_zlookup_nexthop nexthop_tab[],
+ const int tab_size, pim_addr addr, pim_addr group, int max_lookup)
{
int lookup;
uint32_t route_metric = 0xFFFFFFFF;
@@ -480,8 +481,7 @@ int zclient_lookup_nexthop(struct pim_instance *pim,
int first_ifindex;
pim_addr nexthop_addr;
- num_ifindex = zclient_lookup_nexthop_once(pim, nexthop_tab,
- tab_size, addr);
+ num_ifindex = zclient_lookup_nexthop_once(pim, nexthop_tab, tab_size, addr, group);
if (num_ifindex < 1) {
if (PIM_DEBUG_PIM_NHT_DETAIL)
zlog_debug(
diff --git a/pimd/pim_zlookup.h b/pimd/pim_zlookup.h
index c9461eb7e..720cc4fca 100644
--- a/pimd/pim_zlookup.h
+++ b/pimd/pim_zlookup.h
@@ -27,10 +27,8 @@ struct pim_zlookup_nexthop {
void zclient_lookup_new(void);
void zclient_lookup_free(void);
-int zclient_lookup_nexthop(struct pim_instance *pim,
- struct pim_zlookup_nexthop nexthop_tab[],
- const int tab_size, pim_addr addr,
- int max_lookup);
+int zclient_lookup_nexthop(struct pim_instance *pim, struct pim_zlookup_nexthop nexthop_tab[],
+ const int tab_size, pim_addr addr, pim_addr group, int max_lookup);
void pim_zlookup_show_ip_multicast(struct vty *vty);
diff --git a/ripd/rip_main.c b/ripd/rip_main.c
index 67469f5fe..cfe4a7e43 100644
--- a/ripd/rip_main.c
+++ b/ripd/rip_main.c
@@ -127,6 +127,7 @@ static struct frr_signal_t ripd_signals[] = {
};
static const struct frr_yang_module_info *const ripd_yang_modules[] = {
+ &frr_backend_info,
&frr_filter_info,
&frr_interface_info,
&frr_ripd_info,
diff --git a/ripngd/ripng_main.c b/ripngd/ripng_main.c
index ada9ad4e7..b3584b9c3 100644
--- a/ripngd/ripng_main.c
+++ b/ripngd/ripng_main.c
@@ -120,6 +120,7 @@ struct frr_signal_t ripng_signals[] = {
};
static const struct frr_yang_module_info *const ripngd_yang_modules[] = {
+ &frr_backend_info,
&frr_filter_info,
&frr_interface_info,
&frr_ripngd_info,
diff --git a/staticd/static_main.c b/staticd/static_main.c
index 9468a98b8..5e74326e3 100644
--- a/staticd/static_main.c
+++ b/staticd/static_main.c
@@ -107,6 +107,7 @@ struct frr_signal_t static_signals[] = {
};
static const struct frr_yang_module_info *const staticd_yang_modules[] = {
+ &frr_backend_info,
&frr_interface_info,
&frr_vrf_info,
&frr_routing_info,
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 000000000..3542f4e49
--- /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 000000000..60066d502
--- /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 000000000..cf71f2048
--- /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 000000000..b555c2a37
--- /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 000000000..43273cc93
--- /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 000000000..20549926d
--- /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 000000000..fcf518390
--- /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 000000000..1e5040ba6
--- /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 000000000..6626e9136
--- /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 000000000..d3fb1b7ba
--- /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 000000000..bec4eb01c
--- /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 000000000..c21a586c3
--- /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 000000000..154bef799
--- /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 000000000..14df5ec93
--- /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 000000000..7c7a95e33
--- /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 000000000..145e156b1
--- /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 000000000..212cf9e69
--- /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/bgp_dynamic_capability/r1/frr.conf b/tests/topotests/bgp_dynamic_capability/r1/frr.conf
index c9594626f..d91913e15 100644
--- a/tests/topotests/bgp_dynamic_capability/r1/frr.conf
+++ b/tests/topotests/bgp_dynamic_capability/r1/frr.conf
@@ -3,6 +3,7 @@
!
int r1-eth0
ip address 192.168.1.1/24
+ ipv6 address 2001:db8::1/64
!
router bgp 65001
no bgp ebgp-requires-policy
@@ -12,11 +13,19 @@ router bgp 65001
neighbor 192.168.1.2 timers 1 3
neighbor 192.168.1.2 timers connect 1
neighbor 192.168.1.2 capability dynamic
+ neighbor 2001:db8::2 remote-as external
+ neighbor 2001:db8::2 timers 1 3
+ neighbor 2001:db8::2 timers connect 1
+ neighbor 2001:db8::2 capability dynamic
!
address-family ipv4 unicast
neighbor 192.168.1.2 addpath-tx-all-paths
neighbor 192.168.1.2 addpath-rx-paths-limit 10
exit-address-family
+ !
+ address-family ipv6 unicast
+ neighbor 2001:db8::2 activate
+ exit-address-family
!
ip prefix-list r2 seq 5 permit 10.10.10.10/32
!
diff --git a/tests/topotests/bgp_dynamic_capability/r2/frr.conf b/tests/topotests/bgp_dynamic_capability/r2/frr.conf
index 3cc1f1fc3..621e9381e 100644
--- a/tests/topotests/bgp_dynamic_capability/r2/frr.conf
+++ b/tests/topotests/bgp_dynamic_capability/r2/frr.conf
@@ -7,6 +7,7 @@ int lo
!
int r2-eth0
ip address 192.168.1.2/24
+ ipv6 address 2001:db8::2/64
!
router bgp 65002
bgp graceful-restart
@@ -16,9 +17,20 @@ router bgp 65002
neighbor 192.168.1.1 timers 1 3
neighbor 192.168.1.1 timers connect 1
neighbor 192.168.1.1 capability dynamic
+ neighbor 192.168.1.1 capability extended-nexthop
neighbor 192.168.1.1 addpath-rx-paths-limit 20
+ neighbor 2001:db8::1 remote-as external
+ neighbor 2001:db8::1 timers 1 3
+ neighbor 2001:db8::1 timers connect 1
+ neighbor 2001:db8::1 capability dynamic
+ neighbor 2001:db8::1 capability extended-nexthop
!
address-family ipv4 unicast
redistribute connected
exit-address-family
+ !
+ address-family ipv6 unicast
+ redistribute connected
+ neighbor 2001:db8::1 activate
+ exit-address-family
!
diff --git a/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_enhe.py b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_enhe.py
new file mode 100644
index 000000000..aa0508e88
--- /dev/null
+++ b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_enhe.py
@@ -0,0 +1,215 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+# Copyright (c) 2024 by
+# Donatas Abraitis <donatas@opensourcerouting.org>
+#
+
+"""
+Test if extended nexthop capability is exchanged dynamically.
+"""
+
+import os
+import sys
+import json
+import pytest
+import functools
+import time
+
+pytestmark = [pytest.mark.bgpd]
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+from lib import topotest
+from lib.topogen import Topogen, get_topogen
+from lib.common_config import step
+
+
+def setup_module(mod):
+ topodef = {"s1": ("r1", "r2")}
+ tgen = Topogen(topodef, mod.__name__)
+ tgen.start_topology()
+
+ router_list = tgen.routers()
+
+ for _, (rname, router) in enumerate(router_list.items(), 1):
+ router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname)))
+
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_bgp_dynamic_capability_enhe():
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"]
+ r2 = tgen.gears["r2"]
+
+ def _bgp_converge():
+ output = json.loads(r1.vtysh_cmd("show bgp neighbor 2001:db8::2 json"))
+ expected = {
+ "2001:db8::2": {
+ "bgpState": "Established",
+ "localRole": "undefined",
+ "remoteRole": "undefined",
+ "neighborCapabilities": {
+ "dynamic": "advertisedAndReceived",
+ "extendedNexthop": "received",
+ },
+ }
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(
+ _bgp_converge,
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Can't converge"
+
+ def _bgp_check_nexthop():
+ output = json.loads(r1.vtysh_cmd("show ip route 10.10.10.10/32 json"))
+ expected = {
+ "10.10.10.10/32": [
+ {
+ "protocol": "bgp",
+ "selected": True,
+ "installed": True,
+ "nexthops": [
+ {
+ "fib": True,
+ "ip": "192.168.1.2",
+ "afi": "ipv4",
+ "interfaceName": "r1-eth0",
+ "active": True,
+ },
+ {
+ "duplicate": True,
+ "ip": "192.168.1.2",
+ "afi": "ipv4",
+ "interfaceName": "r1-eth0",
+ "active": True,
+ },
+ ],
+ }
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(
+ _bgp_check_nexthop,
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Can't see 10.10.10.10/32 with IPv4 only nexthops"
+
+ step("Enable ENHE capability")
+
+ # Clear message stats to check if we receive a notification or not after we
+ # change the role.
+ r1.vtysh_cmd("clear bgp 2001:db8::2 message-stats")
+ r1.vtysh_cmd(
+ """
+ configure terminal
+ router bgp
+ neighbor 2001:db8::2 capability extended-nexthop
+ """
+ )
+
+ def _bgp_check_if_session_not_reset():
+ output = json.loads(r2.vtysh_cmd("show bgp neighbor 2001:db8::1 json"))
+ expected = {
+ "2001:db8::1": {
+ "bgpState": "Established",
+ "neighborCapabilities": {
+ "dynamic": "advertisedAndReceived",
+ "extendedNexthop": "advertisedAndReceived",
+ "extendedNexthopFamililesByPeer": {
+ "ipv4Unicast": "recieved",
+ },
+ },
+ "messageStats": {
+ "notificationsRecv": 0,
+ "capabilityRecv": 1,
+ },
+ }
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(
+ _bgp_check_if_session_not_reset,
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Session was reset after setting ENHE capability"
+
+ def _bgp_check_if_session_not_reset():
+ output = json.loads(r2.vtysh_cmd("show bgp neighbor 2001:db8::1 json"))
+ expected = {
+ "2001:db8::1": {
+ "bgpState": "Established",
+ "neighborCapabilities": {
+ "dynamic": "advertisedAndReceived",
+ "extendedNexthop": "advertisedAndReceived",
+ "extendedNexthopFamililesByPeer": {
+ "ipv4Unicast": "recieved",
+ },
+ },
+ "messageStats": {
+ "notificationsRecv": 0,
+ "capabilityRecv": 1,
+ },
+ }
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(
+ _bgp_check_if_session_not_reset,
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Session was reset after setting ENHE capability"
+
+ def _bgp_check_nexthop_enhe():
+ output = json.loads(r1.vtysh_cmd("show ip route 10.10.10.10/32 json"))
+ expected = {
+ "10.10.10.10/32": [
+ {
+ "protocol": "bgp",
+ "selected": True,
+ "installed": True,
+ "nexthops": [
+ {
+ "fib": True,
+ "ip": "192.168.1.2",
+ "afi": "ipv4",
+ "interfaceName": "r1-eth0",
+ "active": True,
+ },
+ {
+ "fib": True,
+ "afi": "ipv6",
+ "interfaceName": "r1-eth0",
+ "active": True,
+ },
+ ],
+ }
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(
+ _bgp_check_nexthop_enhe,
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Can't see 10.10.10.10/32 with IPv4 only nexthops"
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf
new file mode 100644
index 000000000..428b1d992
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf
@@ -0,0 +1,35 @@
+!
+interface r1-eth0
+ ip address 192.168.179.4/24
+exit
+!
+router bgp 65001
+!
+router bgp 65001 vrf CUSTOMER-A
+ bgp router-id 192.168.179.4
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+ neighbor 192.168.179.5 remote-as external
+!
+ address-family ipv4 unicast
+ neighbor 192.168.179.5 next-hop-self
+ neighbor 192.168.179.5 allowas-in 10
+ label vpn export auto
+ rd vpn export 100:1
+ rt vpn both 100:1 100:2
+ export vpn
+ import vpn
+ exit-address-family
+!
+router bgp 65001 vrf CUSTOMER-B
+ bgp router-id 192.168.0.1
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+!
+ address-family ipv4 unicast
+ label vpn export auto
+ rd vpn export 100:2
+ rt vpn import 100:1 100:2
+ export vpn
+ import vpn
+ exit-address-family
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf
new file mode 100644
index 000000000..58e63d6cf
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf
@@ -0,0 +1,48 @@
+!
+interface lo
+ ip address 10.10.10.10/32
+!
+interface r2-eth0
+ ip address 192.168.179.5/24
+exit
+!
+interface r2-eth1
+ ip address 192.168.2.2/24
+exit
+!
+router bgp 65002
+!
+router bgp 65002 vrf CUSTOMER-A
+ bgp router-id 192.168.179.5
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+ neighbor 192.168.179.4 remote-as external
+!
+ address-family ipv4 unicast
+ neighbor 192.168.179.4 next-hop-self
+ neighbor 192.168.179.4 route-map r1 out
+ label vpn export auto
+ rd vpn export 100:1
+ rt vpn import 100:1 100:2
+ export vpn
+ import vpn
+ exit-address-family
+!
+router bgp 65002 vrf CUSTOMER-B
+ bgp router-id 192.168.0.2
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+!
+ address-family ipv4 unicast
+ redistribute connected
+ network 10.10.10.10/32
+ label vpn export auto
+ rd vpn export 100:2
+ rt vpn both 100:2
+ export vpn
+ import vpn
+ exit-address-family
+!
+route-map r1 permit 10
+ set as-path prepend 65001
+!
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py
new file mode 100644
index 000000000..23325c7a1
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+#
+# Copyright (c) 2024 by
+# Donatas Abraitis <donatas@opensourcerouting.org>
+#
+
+import os
+import sys
+import json
+import pytest
+import functools
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+from lib import topotest
+from lib.topogen import Topogen, get_topogen
+
+pytestmark = [pytest.mark.bgpd]
+
+
+def build_topo(tgen):
+ tgen.add_router("r1")
+ tgen.add_router("r2")
+
+ switch = tgen.add_switch("s1")
+ switch.add_link(tgen.gears["r1"])
+ switch.add_link(tgen.gears["r2"])
+
+ switch = tgen.add_switch("s2")
+ switch.add_link(tgen.gears["r1"])
+
+ switch = tgen.add_switch("s3")
+ switch.add_link(tgen.gears["r2"])
+
+
+def setup_module(mod):
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ r1 = tgen.gears["r1"]
+ r2 = tgen.gears["r2"]
+
+ r1.run("ip link add CUSTOMER-A type vrf table 1001")
+ r1.run("ip link set up dev CUSTOMER-A")
+ r1.run("ip link set r1-eth0 master CUSTOMER-A")
+
+ r1.run("ip link add CUSTOMER-B type vrf table 1002")
+ r1.run("ip link set up dev CUSTOMER-B")
+ r1.run("ip link set r1-eth1 master CUSTOMER-B")
+
+ r2.run("ip link add CUSTOMER-A type vrf table 1001")
+ r2.run("ip link set up dev CUSTOMER-A")
+ r2.run("ip link set r2-eth0 master CUSTOMER-A")
+
+ r2.run("ip link add CUSTOMER-B type vrf table 1002")
+ r2.run("ip link set up dev CUSTOMER-B")
+ r2.run("ip link set r2-eth1 master CUSTOMER-B")
+
+ router_list = tgen.routers()
+
+ for _, (rname, router) in enumerate(router_list.items(), 1):
+ router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname)))
+
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_bgp_vpnv4_import_allowas_in_between_vrf():
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"]
+
+ def _bgp_converge():
+ output = json.loads(
+ r1.vtysh_cmd("show bgp vrf CUSTOMER-A ipv4 unicast 10.10.10.10/32 json")
+ )
+ expected = {
+ "paths": [
+ {
+ "aspath": {
+ "string": "65002 65001",
+ },
+ "valid": True,
+ }
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(_bgp_converge)
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Failed to see 10.10.10.10/32 with a valid next-hop"
+
+ def _vrf_route_imported_to_vrf():
+ output = json.loads(
+ r1.vtysh_cmd("show ip route vrf CUSTOMER-B 10.10.10.10/32 json")
+ )
+ expected = {
+ "10.10.10.10/32": [
+ {
+ "protocol": "bgp",
+ "vrfName": "CUSTOMER-B",
+ "selected": True,
+ "installed": True,
+ "table": 1002,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": True,
+ "ip": "192.168.179.5",
+ "afi": "ipv4",
+ "interfaceName": "r1-eth0",
+ "vrf": "CUSTOMER-A",
+ "active": True,
+ }
+ ],
+ }
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(_vrf_route_imported_to_vrf)
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert (
+ result is None
+ ), "Failed to see 10.10.10.10/32 to be imported into CUSTOMER-B VRF (Zebra)"
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/conftest.py b/tests/topotests/conftest.py
index dafd19c28..117ff74e4 100755
--- a/tests/topotests/conftest.py
+++ b/tests/topotests/conftest.py
@@ -31,7 +31,7 @@ from lib import topolog, topotest
try:
# Used by munet native tests
- from munet.testing.fixtures import unet # pylint: disable=all # noqa
+ from munet.testing.fixtures import stepf, unet # pylint: disable=all # noqa
@pytest.fixture(scope="module")
def rundir_module(pytestconfig):
diff --git a/tests/topotests/mgmt_notif/r1/frr.conf b/tests/topotests/mgmt_notif/r1/frr.conf
index 47e73956c..36981c94d 100644
--- a/tests/topotests/mgmt_notif/r1/frr.conf
+++ b/tests/topotests/mgmt_notif/r1/frr.conf
@@ -4,7 +4,7 @@ log file frr.log
no debug memstats-at-exit
debug northbound notifications
-debug northbound libyang
+!! debug northbound libyang
debug northbound events
debug northbound callbacks
diff --git a/tests/topotests/mgmt_notif/r2/frr.conf b/tests/topotests/mgmt_notif/r2/frr.conf
index cd052011e..540961a0e 100644
--- a/tests/topotests/mgmt_notif/r2/frr.conf
+++ b/tests/topotests/mgmt_notif/r2/frr.conf
@@ -16,7 +16,7 @@ ip route 22.22.22.22/32 lo
interface r2-eth0
ip address 1.1.1.2/24
- ip rip authentication string bar
+ ip rip authentication string foo
ip rip authentication mode text
exit
diff --git a/tests/topotests/mgmt_notif/test_notif.py b/tests/topotests/mgmt_notif/test_notif.py
index e5286faae..526f051e6 100644
--- a/tests/topotests/mgmt_notif/test_notif.py
+++ b/tests/topotests/mgmt_notif/test_notif.py
@@ -10,9 +10,12 @@
Test YANG Notifications
"""
import json
+import logging
import os
+import re
import pytest
+from lib.micronet import Timeout, comm_error
from lib.topogen import Topogen
from lib.topotest import json_cmp
from oper import check_kernel_32
@@ -42,7 +45,57 @@ def tgen(request):
tgen.stop_topology()
-def test_frontend_notification(tgen):
+def myreadline(f):
+ buf = ""
+ while True:
+ # logging.debug("READING 1 CHAR")
+ c = f.read(1)
+ if not c:
+ return buf if buf else None
+ buf += c
+ # logging.debug("READ CHAR: '%s'", c)
+ if c == "\n":
+ return buf
+
+
+def _wait_output(f, regex, maxwait=120):
+ timeout = Timeout(maxwait)
+ while not timeout.is_expired():
+ # line = p.stdout.readline()
+ line = myreadline(f)
+ if not line:
+ assert None, "EOF waiting for '{}'".format(regex)
+ line = line.rstrip()
+ if line:
+ logging.debug("GOT LINE: '%s'", line)
+ m = re.search(regex, line)
+ if m:
+ return m
+ assert None, "Failed to get output matching '{}' withint {} actual {}s".format(
+ regex, maxwait, timeout.elapsed()
+ )
+
+
+def get_op_and_json(output):
+ op = ""
+ path = ""
+ data = ""
+ for line in output.split("\n"):
+ if not line:
+ continue
+ if not op:
+ m = re.match("#OP=([A-Z]*): (.*)", line)
+ if m:
+ op = m.group(1)
+ path = m.group(2)
+ continue
+ data += line + "\n"
+ if not op:
+ assert False, f"No notifcation op present in:\n{output}"
+ return op, path, data
+
+
+def test_frontend_datastore_notification(tgen):
if tgen.routers_have_failure():
pytest.skip(tgen.errors)
@@ -50,30 +103,141 @@ def test_frontend_notification(tgen):
check_kernel_32(r1, "11.11.11.11", 1, "")
- fe_client_path = CWD + "/../lib/fe_client.py --verbose"
+ fe_client_path = CWD + "/../lib/fe_client.py"
rc, _, _ = r1.cmd_status(fe_client_path + " --help")
if rc:
pytest.skip("No protoc or present cannot run test")
- # The first notifications is a frr-ripd:authentication-type-failure
- # So we filter to avoid that, all the rest are frr-ripd:authentication-failure
- # making our test deterministic
- output = r1.cmd_raises(
- fe_client_path + " --listen /frr-ripd:authentication-failure"
+ # Start our FE client in the background
+ p = r1.popen(
+ [fe_client_path, "--datastore", "--listen=/frr-interface:lib/interface"]
)
- jsout = json.loads(output)
+ _wait_output(p.stderr, "Connected", maxwait=10)
+
+ r1.cmd_raises("ip link set r1-eth0 mtu 1200")
+
+ # {"frr-interface:lib":{"interface":[{"name":"r1-eth0","state":{"if-index":2,"mtu":1200,"mtu6":1200,"speed":10000,"metric":0,"phy-address":"ba:fd:de:b5:8b:90"}}]}}
+
+ try:
+ # Wait for FE client to exit
+ output, error = p.communicate(timeout=10)
+ op, path, data = get_op_and_json(output)
+
+ assert op == "REPLACE"
+ assert path.startswith("/frr-interface:lib/interface[name='r1-eth0']/state")
+
+ jsout = json.loads(data)
+ expected = json.loads(
+ '{"frr-interface:lib":{"interface":[{"name":"r1-eth0","state":{"mtu":1200}}]}}'
+ )
+ result = json_cmp(jsout, expected)
+ assert result is None
+ finally:
+ p.kill()
+ r1.cmd_raises("ip link set r1-eth0 mtu 1500")
+
+
+def test_frontend_notification(tgen):
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"].net
+
+ check_kernel_32(r1, "11.11.11.11", 1, "")
+
+ fe_client_path = CWD + "/../lib/fe_client.py"
+ rc, _, _ = r1.cmd_status(fe_client_path + " --help")
+
+ if rc:
+ pytest.skip("No protoc or present cannot run test")
+
+ # Update config to non-matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string bar
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
+
+ try:
+ output = r1.cmd_raises(
+ fe_client_path + " --listen /frr-ripd:authentication-failure"
+ )
- expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
- result = json_cmp(jsout, expected)
- assert result is None
+ jsout = json.loads(output)
+ expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+ result = json_cmp(jsout, expected)
+ assert result is None
- output = r1.cmd_raises(fe_client_path + " --use-protobuf --listen")
- jsout = json.loads(output)
+ output = r1.cmd_raises(
+ fe_client_path + " --use-protobuf --listen /frr-ripd:authentication-failure"
+ )
+ jsout = json.loads(output)
+ expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+ result = json_cmp(jsout, expected)
+ assert result is None
+ finally:
+ # Update config to matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string foo
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
- expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
- result = json_cmp(jsout, expected)
- assert result is None
+
+def test_frontend_all_notification(tgen):
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"].net
+
+ check_kernel_32(r1, "11.11.11.11", 1, "")
+
+ fe_client_path = CWD + "/../lib/fe_client.py"
+ rc, _, _ = r1.cmd_status(fe_client_path + " --help")
+
+ if rc:
+ pytest.skip("No protoc or present cannot run test")
+
+ # Update config to non-matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string bar
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
+
+ try:
+ # The first notifications is a frr-ripd:authentication-type-failure
+ # All the rest are frr-ripd:authentication-failure so we check for both.
+ output = r1.cmd_raises(fe_client_path + " --listen /")
+ jsout = json.loads(output)
+ expected = {
+ "frr-ripd:authentication-type-failure": {"interface-name": "r1-eth0"}
+ }
+ result = json_cmp(jsout, expected)
+ if result is not None:
+ expected = {
+ "frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}
+ }
+ result = json_cmp(jsout, expected)
+ assert result is None
+
+ output = r1.cmd_raises(fe_client_path + " --use-protobuf --listen /")
+ jsout = json.loads(output)
+ expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+ result = json_cmp(jsout, expected)
+ assert result is None
+ finally:
+ # Update config to matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string foo
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
def test_backend_notification(tgen):
@@ -90,12 +254,28 @@ def test_backend_notification(tgen):
if rc:
pytest.skip("No mgmtd_testc")
- output = r1.cmd_raises(
- be_client_path + " --timeout 20 --log file:mgmt_testc.log --listen /frr-ripd"
- )
-
- jsout = json.loads(output)
+ # Update config to non-matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string bar
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
- expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
- result = json_cmp(jsout, expected)
- assert result is None
+ try:
+ output = r1.cmd_raises(
+ be_client_path
+ + " --timeout 20 --log file:mgmt_testc.log --listen /frr-ripd"
+ )
+ jsout = json.loads(output)
+ expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+ result = json_cmp(jsout, expected)
+ assert result is None
+ finally:
+ # Update config to matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string foo
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
diff --git a/tests/topotests/munet/base.py b/tests/topotests/munet/base.py
index e77eb15dc..e9410d442 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 6ebc368dc..44453cb44 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 abc63af36..e367e65a1 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 e3b782396..4e29fe91b 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 99687c0a8..02ff9bd69 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/ospf_metric_propagation/r1/frr.conf b/tests/topotests/ospf_metric_propagation/r1/frr.conf
index 082f7df51..09ae6e8d1 100644
--- a/tests/topotests/ospf_metric_propagation/r1/frr.conf
+++ b/tests/topotests/ospf_metric_propagation/r1/frr.conf
@@ -1,6 +1,10 @@
!
hostname r1
!
+vrf green
+ ip route 10.48.48.0/24 10.0.91.2
+exit
+!
interface r1-eth0
ip address 10.0.1.1/24
ip ospf cost 100
@@ -61,6 +65,7 @@ router bgp 99 vrf green
address-family ipv4 unicast
redistribute connected
redistribute ospf
+ redistribute static
import vrf route-map rmap
import vrf default
import vrf blue
@@ -75,7 +80,7 @@ ip prefix-list min seq 5 permit 10.0.80.0/24
route-map costmax permit 20
set metric-type type-1
set metric +1
- set metric-min 713
+ set min-metric 713
match ip address prefix-list min
exit
!
@@ -83,7 +88,7 @@ ip prefix-list max seq 10 permit 10.0.70.0/24
route-map costplus permit 30
set metric-type type-1
set metric +1
- set metric-max 13
+ set max-metric 13
match ip address prefix-list max
exit
!
diff --git a/tests/topotests/ospf_metric_propagation/r1/show_ip_route_static.json b/tests/topotests/ospf_metric_propagation/r1/show_ip_route_static.json
index 628a556c6..6060e8bd6 100644
--- a/tests/topotests/ospf_metric_propagation/r1/show_ip_route_static.json
+++ b/tests/topotests/ospf_metric_propagation/r1/show_ip_route_static.json
@@ -3,44 +3,23 @@
{
"prefix":"10.48.48.0/24",
"prefixLen":24,
- "protocol":"ospf",
- "vrfId":0,
- "vrfName":"default",
- "distance":20,
- "metric":134,
- "table":254,
- "nexthops":[
- {
- "flags":3,
- "fib":true,
- "ip":"10.0.1.2",
- "afi":"ipv4",
- "interfaceName":"r1-eth0",
- "active":true,
- "weight":1
- }
- ]
- },
- {
- "prefix":"10.48.48.0/24",
- "prefixLen":24,
"protocol":"bgp",
"vrfId":0,
"vrfName":"default",
"selected":true,
"destSelected":true,
- "distance":20,
- "metric":34,
+ "distance":1,
+ "metric":1,
"installed":true,
"table":254,
"nexthops":[
{
"flags":3,
"fib":true,
- "ip":"10.0.10.5",
+ "ip":"10.0.91.2",
"afi":"ipv4",
- "interfaceName":"r1-eth1",
- "vrf":"blue",
+ "interfaceName":"r1-eth2",
+ "vrf":"green",
"active":true,
"weight":1
}
diff --git a/tests/topotests/ospf_metric_propagation/r4/frr.conf b/tests/topotests/ospf_metric_propagation/r4/frr.conf
index d9832d80b..b02ae18fc 100644
--- a/tests/topotests/ospf_metric_propagation/r4/frr.conf
+++ b/tests/topotests/ospf_metric_propagation/r4/frr.conf
@@ -1,10 +1,6 @@
!
hostname r4
!
-vrf green
- ip route 10.48.48.0/24 10.0.94.2
-exit
-
interface r4-eth0
ip address 10.0.3.4/24
ip ospf cost 100
@@ -63,7 +59,6 @@ router bgp 99 vrf green
address-family ipv4 unicast
redistribute connected
redistribute ospf
- redistribute static
import vrf route-map rmap
import vrf default
import vrf blue
diff --git a/tests/topotests/ospf_metric_propagation/test_ospf_metric_propagation.py b/tests/topotests/ospf_metric_propagation/test_ospf_metric_propagation.py
index 4639a1e26..f574dac4e 100644
--- a/tests/topotests/ospf_metric_propagation/test_ospf_metric_propagation.py
+++ b/tests/topotests/ospf_metric_propagation/test_ospf_metric_propagation.py
@@ -190,8 +190,8 @@ def test_all_links_up():
assert result is None, assertmsg
-def test_static_remote():
- "Test static route at R1 configured on R4"
+def test_static():
+ "Test static route at R1 leaked from VRF green"
tgen = get_topogen()
if tgen.routers_have_failure():
@@ -201,7 +201,7 @@ def test_static_remote():
json_file = "{}/r1/show_ip_route_static.json".format(CWD)
expected = json.loads(open(json_file).read())
test_func = partial(
- topotest.router_json_cmp, r1, "show ip route 10.48.48.2 json", expected
+ topotest.router_json_cmp, r1, "show ip route 10.48.48.0/24 json", expected
)
_, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
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 1488e610c..2a77c3b22 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/tests/topotests/pim_mrib/r1/frr.conf b/tests/topotests/pim_mrib/r1/frr.conf
index 28cf2b2c4..7c9d27c60 100644
--- a/tests/topotests/pim_mrib/r1/frr.conf
+++ b/tests/topotests/pim_mrib/r1/frr.conf
@@ -20,9 +20,10 @@ interface r1-eth1
ip forwarding
!
ip route 10.0.2.0/24 10.0.0.2 50
-ip route 10.0.3.0/24 10.0.1.3 50
+ip route 10.0.3.0/24 10.0.0.2 50
!
router pim
rpf-lookup-mode mrib-then-urib
rp 10.0.0.1 224.0.0.0/4
+ rp 10.0.1.1 225.0.0.0/24
! \ No newline at end of file
diff --git a/tests/topotests/pim_mrib/r2/frr.conf b/tests/topotests/pim_mrib/r2/frr.conf
index 3e647f679..260b6b0f7 100644
--- a/tests/topotests/pim_mrib/r2/frr.conf
+++ b/tests/topotests/pim_mrib/r2/frr.conf
@@ -25,4 +25,5 @@ ip route 10.0.3.0/24 10.0.2.4 50
router pim
rpf-lookup-mode mrib-then-urib
rp 10.0.0.1 224.0.0.0/4
+ rp 10.0.1.1 225.0.0.0/24
! \ No newline at end of file
diff --git a/tests/topotests/pim_mrib/r3/frr.conf b/tests/topotests/pim_mrib/r3/frr.conf
index 9815484d0..5966ae0e8 100644
--- a/tests/topotests/pim_mrib/r3/frr.conf
+++ b/tests/topotests/pim_mrib/r3/frr.conf
@@ -25,4 +25,5 @@ ip route 10.0.2.0/24 10.0.3.4 50
router pim
rpf-lookup-mode mrib-then-urib
rp 10.0.0.1 224.0.0.0/4
+ rp 10.0.1.1 225.0.0.0/24
! \ No newline at end of file
diff --git a/tests/topotests/pim_mrib/r4/frr.conf b/tests/topotests/pim_mrib/r4/frr.conf
index 8432a7a35..8d9d8f7e2 100644
--- a/tests/topotests/pim_mrib/r4/frr.conf
+++ b/tests/topotests/pim_mrib/r4/frr.conf
@@ -18,12 +18,24 @@ interface r4-eth1
ip igmp
ip pim
!
+interface r4-dum0
+ ip address 10.10.0.4/24
+ ip igmp
+ ip pim
+ ip pim passive
+!
ip forwarding
!
ip route 10.0.0.0/24 10.0.2.2 50
-ip route 10.0.1.0/24 10.0.3.3 50
+ip route 10.0.1.0/24 10.0.2.2 50
+!
+ip prefix-list SRCPLIST permit 10.0.0.1/32
+ip prefix-list SRCPLIST2 permit 10.0.1.1/32
+ip prefix-list GRPPLIST permit 239.1.1.1/32
+ip prefix-list GRPPLIST2 permit 239.2.2.2/32
!
router pim
rpf-lookup-mode mrib-then-urib
rp 10.0.0.1 224.0.0.0/4
+ rp 10.0.1.1 225.0.0.0/24
! \ No newline at end of file
diff --git a/tests/topotests/pim_mrib/test_pim_mrib.py b/tests/topotests/pim_mrib/test_pim_mrib.py
index 355c503e3..2a391fa57 100644
--- a/tests/topotests/pim_mrib/test_pim_mrib.py
+++ b/tests/topotests/pim_mrib/test_pim_mrib.py
@@ -20,6 +20,8 @@ from lib.topogen import Topogen, get_topogen
from lib.topolog import logger
from lib.pim import (
verify_pim_rp_info,
+ verify_upstream_iif,
+ McastTesterHelper,
)
from lib.common_config import step, write_test_header
@@ -29,6 +31,8 @@ test_pim_mrib.py: Test PIM MRIB overrides and RPF modes
TOPOLOGY = """
Test PIM MRIB overrides and RPF modes
+ Static routes installed that uses R2 to get between R1 and R4.
+ Tests will install MRIB override through R3
+---+---+ +---+---+
| | 10.0.0.0/24 | |
@@ -42,7 +46,7 @@ TOPOLOGY = """
.3 | r3-eth0 r4-eth0 | .4
+---+---+ r3-eth1 r4-eth1 +---+---+
| | .3 .4 | |
- + R3 +----------------------+ R4 |
+ + R3 +----------------------+ R4 |---r4-dum0 10.10.0.4/24
| | 10.0.3.0/24 | |
+---+---+ +---+---+
"""
@@ -54,9 +58,12 @@ sys.path.append(os.path.join(CWD, "../"))
# Required to instantiate the topology builder class.
pytestmark = [pytest.mark.pimd]
+GROUP1 = "239.1.1.1"
+GROUP2 = "239.2.2.2"
+
def build_topo(tgen):
- '''Build function'''
+ """Build function"""
# Create routers
tgen.add_router("r1")
@@ -70,6 +77,8 @@ def build_topo(tgen):
tgen.add_link(tgen.gears["r2"], tgen.gears["r4"], "r2-eth1", "r4-eth0")
tgen.add_link(tgen.gears["r3"], tgen.gears["r4"], "r3-eth1", "r4-eth1")
+ tgen.gears["r4"].run("ip link add r4-dum0 type dummy")
+
def setup_module(mod):
logger.info("PIM MRIB/RPF functionality:\n {}".format(TOPOLOGY))
@@ -87,13 +96,13 @@ def setup_module(mod):
def teardown_module(mod):
- '''Teardown the pytest environment'''
+ """Teardown the pytest environment"""
tgen = get_topogen()
tgen.stop_topology()
def test_pim_mrib_init(request):
- '''Test boot in MRIB-than-URIB with the default MRIB'''
+ """Test boot in MRIB-than-URIB with the default MRIB"""
tgen = get_topogen()
tc_name = request.node.name
write_test_header(tc_name)
@@ -116,8 +125,23 @@ def test_pim_mrib_init(request):
)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth0",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
def test_pim_mrib_override(request):
- '''Test MRIB override nexthop'''
+ """Test MRIB override nexthop"""
tgen = get_topogen()
tc_name = request.node.name
write_test_header(tc_name)
@@ -128,10 +152,11 @@ def test_pim_mrib_override(request):
# Install a MRIB route that has a shorter prefix length and lower cost.
# In MRIB-than-URIB mode, it should use this route
tgen.routers()["r4"].vtysh_cmd(
- '''
+ """
conf term
ip mroute 10.0.0.0/16 10.0.3.3 25
- '''
+ exit
+ """
)
step("Verify rp-info using MRIB nexthop")
@@ -149,8 +174,23 @@ def test_pim_mrib_override(request):
)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth1",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
def test_pim_mrib_prefix_mode(request):
- '''Test longer prefix lookup mode'''
+ """Test longer prefix lookup mode"""
tgen = get_topogen()
tc_name = request.node.name
write_test_header(tc_name)
@@ -161,11 +201,13 @@ def test_pim_mrib_prefix_mode(request):
# Switch to longer prefix match, should switch back to the URIB route
# even with the lower cost, the longer prefix match will win because of the mode
tgen.routers()["r4"].vtysh_cmd(
- '''
+ """
conf term
router pim
rpf-lookup-mode longer-prefix
- '''
+ exit
+ exit
+ """
)
step("Verify rp-info using URIB nexthop")
@@ -183,8 +225,23 @@ def test_pim_mrib_prefix_mode(request):
)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth0",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
def test_pim_mrib_dist_mode(request):
- '''Test lower distance lookup mode'''
+ """Test lower distance lookup mode"""
tgen = get_topogen()
tc_name = request.node.name
write_test_header(tc_name)
@@ -194,11 +251,13 @@ def test_pim_mrib_dist_mode(request):
# Switch to lower distance match, should switch back to the MRIB route
tgen.routers()["r4"].vtysh_cmd(
- '''
+ """
conf term
router pim
rpf-lookup-mode lower-distance
- '''
+ exit
+ exit
+ """
)
step("Verify rp-info using MRIB nexthop")
@@ -216,8 +275,23 @@ def test_pim_mrib_dist_mode(request):
)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth1",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
def test_pim_mrib_urib_mode(request):
- '''Test URIB only lookup mode'''
+ """Test URIB only lookup mode"""
tgen = get_topogen()
tc_name = request.node.name
write_test_header(tc_name)
@@ -227,11 +301,13 @@ def test_pim_mrib_urib_mode(request):
# Switch to urib only match, should switch back to the URIB route
tgen.routers()["r4"].vtysh_cmd(
- '''
+ """
conf term
router pim
rpf-lookup-mode urib-only
- '''
+ exit
+ exit
+ """
)
step("Verify rp-info using URIB nexthop")
@@ -249,8 +325,23 @@ def test_pim_mrib_urib_mode(request):
)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth0",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
def test_pim_mrib_mrib_mode(request):
- '''Test MRIB only lookup mode'''
+ """Test MRIB only lookup mode"""
tgen = get_topogen()
tc_name = request.node.name
write_test_header(tc_name)
@@ -260,11 +351,13 @@ def test_pim_mrib_mrib_mode(request):
# Switch to mrib only match, should switch back to the MRIB route
tgen.routers()["r4"].vtysh_cmd(
- '''
+ """
conf term
router pim
rpf-lookup-mode mrib-only
- '''
+ exit
+ exit
+ """
)
step("Verify rp-info using MRIB nexthop")
@@ -282,8 +375,23 @@ def test_pim_mrib_mrib_mode(request):
)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth1",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
def test_pim_mrib_mrib_mode_no_route(request):
- '''Test MRIB only with no route'''
+ """Test MRIB only with no route"""
tgen = get_topogen()
tc_name = request.node.name
write_test_header(tc_name)
@@ -293,10 +401,11 @@ def test_pim_mrib_mrib_mode_no_route(request):
# Remove the MRIB route, in mrib-only mode, it should switch to no path for the RP
tgen.routers()["r4"].vtysh_cmd(
- '''
+ """
conf term
no ip mroute 10.0.0.0/16 10.0.3.3 25
- '''
+ exit
+ """
)
step("Verify rp-info with Unknown next hop")
@@ -314,8 +423,818 @@ def test_pim_mrib_mrib_mode_no_route(request):
)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "Unknown",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_init(request):
+ """Test RPF lookup source list with initial setup"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Reset back to mrib then urib mode
+ # Also add mode using SRCPLIST(10.0.0.1) and SRCPLIST2(10.0.1.1)
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode mrib-then-urib
+ rpf-lookup-mode mrib-then-urib source-list SRCPLIST
+ rpf-lookup-mode mrib-then-urib source-list SRCPLIST2
+ exit
+ exit
+ """
+ )
+
+ step("Verify rp-info with default next hop")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth0",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth0",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_add_mroute(request):
+ """Test RPF lookup source list with MRIB route on alternate path"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Add a MRIB route through r4-eth1 that is better distance but worse prefix
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ ip mroute 10.0.0.0/16 10.0.3.3 25
+ exit
+ """
+ )
+
+ step("Verify rp-info with MRIB next hop")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth1",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth1",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_src1_prefix_mode(request):
+ """Test RPF lookup source list src1 longer prefix mode"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Switch just source 1 to longest prefix
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode longer-prefix source-list SRCPLIST
+ exit
+ exit
+ """
+ )
+
+ step("Verify rp-info with URIB next hop for source 1 and MRIB for source 2")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth0",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth1",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_src1_dist_src2_prefix_mode(request):
+ """Test RPF lookup source list src1 lower distance mode and src2 longer prefix mode"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Switch source 1 to shortest distance, source 2 to longest prefix
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode lower-distance source-list SRCPLIST
+ rpf-lookup-mode longer-prefix source-list SRCPLIST2
+ exit
+ exit
+ """
+ )
+
+ step("Verify rp-info with MRIB next hop for source 1 and URIB for source 2")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth1",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth0",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_src1_urib_src2_dist_mode(request):
+ """Test RPF lookup source list src1 urib mode and src2 lower distance mode"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Switch source 1 to urib only, source 2 to shorter distance
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode urib-only source-list SRCPLIST
+ rpf-lookup-mode lower-distance source-list SRCPLIST2
+ exit
+ exit
+ """
+ )
+
+ step("Verify rp-info with URIB next hop for source 1 and MRIB for source 2")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth0",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth1",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_src1_mrib_src2_urib_mode(request):
+ """Test RPF lookup source list src1 mrib mode and src2 urib mode"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Switch source 1 to mrib only, source 2 to urib only
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode mrib-only source-list SRCPLIST
+ rpf-lookup-mode urib-only source-list SRCPLIST2
+ exit
+ exit
+ """
+ )
+
+ step("Verify rp-info with MRIB next hop for source 1 and URIB for source 2")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth1",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth0",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_removed(request):
+ """Test RPF lookup source list removed"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Remove both special modes, both should switch to MRIB route
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ no rpf-lookup-mode mrib-only source-list SRCPLIST
+ no rpf-lookup-mode urib-only source-list SRCPLIST2
+ exit
+ exit
+ """
+ )
+
+ step("Verify rp-info with MRIB next hop for both sources")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth1",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth1",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_del_mroute(request):
+ """Test RPF lookup source list delete mroute"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Remove the MRIB route, both should switch to URIB
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ no ip mroute 10.0.0.0/16 10.0.3.3 25
+ exit
+ """
+ )
+
+ step("Verify rp-info with URIB next hop for both sources")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth0",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth0",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_group_list(request):
+ """Test RPF lookup group list"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ with McastTesterHelper(tgen) as apphelper:
+ step(
+ ("Send multicast traffic from R1 to dense groups {}, {}").format(
+ GROUP1, GROUP2
+ )
+ )
+ result = apphelper.run_traffic("r1", [GROUP1, GROUP2], bind_intf="r1-eth1")
+ assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result)
+
+ # Reset back to mrib then urib mode
+ # Also add mode using GRPPLIST(239.1.1.1) and GRPPLIST2(239.2.2.2)
+ # And do an igmp join to both groups on r4-eth2
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode mrib-then-urib
+ rpf-lookup-mode mrib-then-urib group-list GRPPLIST
+ rpf-lookup-mode mrib-then-urib group-list GRPPLIST2
+ exit
+ int r4-dum0
+ ip igmp join-group {}
+ ip igmp join-group {}
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif with default next hop")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Add MRIB route through alternate path")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ ip mroute 10.0.0.0/16 10.0.3.3 25
+ exit
+ """
+ )
+
+ step("Verify upstream iif with alternate next hop")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Switch group1 to longer prefix match (URIB)")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode longer-prefix group-list GRPPLIST
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is URIB, group2 is MRIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step(
+ "Switch group1 to lower distance match (MRIB), and group2 to longer prefix (URIB)"
+ )
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode lower-distance group-list GRPPLIST
+ rpf-lookup-mode longer-prefix group-list GRPPLIST2
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is MRIB, group2 is URIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Switch group1 to urib match only, and group2 to lower distance (URIB)")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode urib-only group-list GRPPLIST
+ rpf-lookup-mode lower-distance group-list GRPPLIST2
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is URIB, group2 is MRIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Switch group1 to mrib match only, and group2 to urib match only")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode mrib-only group-list GRPPLIST
+ rpf-lookup-mode urib-only group-list GRPPLIST2
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is MRIB, group2 is URIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Delete MRIB route")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ no ip mroute 10.0.0.0/16 10.0.3.3 25
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is Unknown, group2 is URIB")
+ result = verify_upstream_iif(
+ tgen, "r4", "Unknown", "10.0.1.1", GROUP1, "NotJoined"
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+
+def test_pim_mrib_rpf_lookup_source_group_lists(request):
+ """Test RPF lookup source and group lists"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ with McastTesterHelper(tgen) as apphelper:
+ step(
+ ("Send multicast traffic from R1 to dense groups {}, {}").format(
+ GROUP1, GROUP2
+ )
+ )
+ result = apphelper.run_traffic("r1", [GROUP1, GROUP2], bind_intf="r1-eth1")
+ assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result)
+
+ # Reset back to mrib then urib mode
+ # Also add mode using GRPPLIST(239.1.1.1) and GRPPLIST2(239.2.2.2), both using SRCPLIST2
+ # And do an igmp join to both groups on r4-eth2
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode mrib-then-urib
+ rpf-lookup-mode mrib-then-urib group-list GRPPLIST source-list SRCPLIST2
+ rpf-lookup-mode mrib-then-urib group-list GRPPLIST2 source-list SRCPLIST2
+ exit
+ int r4-dum0
+ ip igmp join-group {}
+ ip igmp join-group {}
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif with default next hop")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Add MRIB route through alternate path")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ ip mroute 10.0.0.0/16 10.0.3.3 25
+ exit
+ """
+ )
+
+ step("Verify upstream iif with alternate next hop")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Switch group1 to longer prefix match (URIB)")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode longer-prefix group-list GRPPLIST source-list SRCPLIST2
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is URIB, group2 is MRIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step(
+ "Switch group1 to lower distance match (MRIB), and group2 to longer prefix (URIB)"
+ )
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode lower-distance group-list GRPPLIST source-list SRCPLIST2
+ rpf-lookup-mode longer-prefix group-list GRPPLIST2 source-list SRCPLIST2
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is MRIB, group2 is URIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Switch group1 to urib match only, and group2 to lower distance (URIB)")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode urib-only group-list GRPPLIST source-list SRCPLIST2
+ rpf-lookup-mode lower-distance group-list GRPPLIST2 source-list SRCPLIST2
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is URIB, group2 is MRIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Switch group1 to mrib match only, and group2 to urib match only")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode mrib-only group-list GRPPLIST source-list SRCPLIST2
+ rpf-lookup-mode urib-only group-list GRPPLIST2 source-list SRCPLIST2
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is MRIB, group2 is URIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Delete MRIB route")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ no ip mroute 10.0.0.0/16 10.0.3.3 25
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is Unknown, group2 is URIB")
+ result = verify_upstream_iif(
+ tgen, "r4", "Unknown", "10.0.1.1", GROUP1, "NotJoined"
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+
def test_memory_leak():
- '''Run the memory leak test and report results.'''
+ """Run the memory leak test and report results."""
tgen = get_topogen()
if not tgen.is_memleak_enabled():
pytest.skip("Memory leak test/report is disabled")
diff --git a/tools/frr-reload.py b/tools/frr-reload.py
index dba50b3c5..3ea63ce2a 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/yang/frr-backend.yang b/yang/frr-backend.yang
new file mode 100644
index 000000000..7149cbb99
--- /dev/null
+++ b/yang/frr-backend.yang
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: BSD-2-Clause
+module frr-backend {
+ yang-version 1.1;
+ namespace "http://frrouting.org/yang/oper";
+ prefix frr-backend;
+
+ organization
+ "FRRouting";
+ contact
+ "FRR Users List: <mailto:frog@lists.frrouting.org>
+ FRR Development List: <mailto:dev@lists.frrouting.org>";
+ description
+ "This module defines a model for FRR backend management.
+
+ Copyright (c) 2024, LabN Consulting, L.L.C.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.";
+
+ revision 2024-12-29 {
+ description "Initial revision";
+ reference "FRR source code";
+ }
+
+ container clients {
+ config false;
+ description "The backend clients";
+
+ list client {
+ key name;
+ description "A backend client";
+
+ leaf name {
+ type string;
+ description "Name of the backend client";
+ }
+
+ container state {
+ description "FRR backend operational state";
+
+ leaf candidate-config-version {
+ type uint64;
+ description "Local candidate config version.";
+ }
+ leaf running-config-version {
+ type uint64;
+ description "Local running config version.";
+ }
+ leaf edit-count {
+ type uint64;
+ description "Number of config edits handled.";
+ }
+ leaf avg-edit-time {
+ type uint64;
+ description "Average edit time in microseconds.";
+ }
+ leaf prep-count {
+ type uint64;
+ description "Number of config preps handled.";
+ }
+ leaf avg-prep-time {
+ type uint64;
+ description "Average prep time in microseconds.";
+ }
+ leaf apply-count {
+ type uint64;
+ description "Number of config applies handled.";
+ }
+ leaf avg-apply-time {
+ type uint64;
+ description "Average apply time in microseconds.";
+ }
+ leaf-list notify-selectors {
+ type string;
+ description
+ "List of paths identifying which state to send change
+ notifications for.";
+ }
+ }
+ }
+ }
+}
diff --git a/yang/frr-pim.yang b/yang/frr-pim.yang
index 8dadf4fd7..6b6870f66 100644
--- a/yang/frr-pim.yang
+++ b/yang/frr-pim.yang
@@ -202,11 +202,29 @@ module frr-pim {
description
"A grouping defining per address family pim global attributes";
- leaf mcast-rpf-lookup {
- type mcast-rpf-lookup-mode;
- default "none";
+ list mcast-rpf-lookup {
+ key "group-list source-list";
description
- "Multicast RPF lookup behavior.";
+ "RPF lookup modes.";
+
+ leaf group-list {
+ type plist-ref;
+ description
+ "Multicast group prefix list.";
+ }
+
+ leaf source-list {
+ type plist-ref;
+ description
+ "Unicast source address prefix list.";
+ }
+
+ leaf mode {
+ type mcast-rpf-lookup-mode;
+ default "none";
+ description
+ "Multicast RPF lookup behavior.";
+ }
}
leaf ecmp {
diff --git a/yang/subdir.am b/yang/subdir.am
index 786bd0bca..9d4bc8e78 100644
--- a/yang/subdir.am
+++ b/yang/subdir.am
@@ -20,6 +20,7 @@ EXTRA_DIST += yang/embedmodel.py
# without problems, as seen in libfrr.
dist_yangmodels_DATA += yang/frr-affinity-map.yang
+dist_yangmodels_DATA += yang/frr-backend.yang
dist_yangmodels_DATA += yang/frr-filter.yang
dist_yangmodels_DATA += yang/frr-module-translator.yang
dist_yangmodels_DATA += yang/frr-nexthop.yang
diff --git a/zebra/dplane_fpm_nl.c b/zebra/dplane_fpm_nl.c
index 3ec1c9d65..b8dbabb60 100644
--- a/zebra/dplane_fpm_nl.c
+++ b/zebra/dplane_fpm_nl.c
@@ -587,6 +587,7 @@ static void fpm_read(struct event *t)
struct zebra_dplane_ctx *ctx;
size_t available_bytes;
size_t hdr_available_bytes;
+ int ival;
/* Let's ignore the input at the moment. */
rv = stream_read_try(fnc->ibuf, fnc->socket,
@@ -715,17 +716,28 @@ static void fpm_read(struct event *t)
break;
}
+ /* Parse the route data into a dplane ctx, then
+ * enqueue it to zebra for processing.
+ */
ctx = dplane_ctx_alloc();
dplane_ctx_route_init(ctx, DPLANE_OP_ROUTE_NOTIFY, NULL,
NULL);
- if (netlink_route_change_read_unicast_internal(
- hdr, 0, false, ctx) != 1) {
- dplane_ctx_fini(&ctx);
- stream_pulldown(fnc->ibuf);
+
+ if (netlink_route_notify_read_ctx(hdr, 0, ctx) >= 0) {
+ /* In the FPM encoding, the vrfid is present */
+ ival = dplane_ctx_get_table(ctx);
+ dplane_ctx_set_vrf(ctx, ival);
+ dplane_ctx_set_table(ctx,
+ ZEBRA_ROUTE_TABLE_UNKNOWN);
+
+ dplane_provider_enqueue_to_zebra(ctx);
+ } else {
/*
* Let's continue to read other messages
* Even if we ignore this one.
*/
+ dplane_ctx_fini(&ctx);
+ stream_pulldown(fnc->ibuf);
}
break;
default:
diff --git a/zebra/interface.c b/zebra/interface.c
index 1c86a6a5c..e49e8eac5 100644
--- a/zebra/interface.c
+++ b/zebra/interface.c
@@ -81,7 +81,7 @@ static void if_zebra_speed_update(struct event *thread)
if (new_speed != ifp->speed) {
zlog_info("%s: %s old speed: %u new speed: %u", __func__,
ifp->name, ifp->speed, new_speed);
- ifp->speed = new_speed;
+ if_update_state_speed(ifp, new_speed);
if_add_update(ifp);
changed = true;
}
@@ -1563,17 +1563,20 @@ static inline void zebra_if_set_ziftype(struct interface *ifp,
static void interface_update_hw_addr(struct zebra_dplane_ctx *ctx,
struct interface *ifp)
{
- int i;
+ uint8_t hw_addr[INTERFACE_HWADDR_MAX];
+ uint i, hw_addr_len;
- ifp->hw_addr_len = dplane_ctx_get_ifp_hw_addr_len(ctx);
- memcpy(ifp->hw_addr, dplane_ctx_get_ifp_hw_addr(ctx), ifp->hw_addr_len);
+ hw_addr_len = dplane_ctx_get_ifp_hw_addr_len(ctx);
+ memcpy(hw_addr, dplane_ctx_get_ifp_hw_addr(ctx), hw_addr_len);
- for (i = 0; i < ifp->hw_addr_len; i++)
- if (ifp->hw_addr[i] != 0)
+ for (i = 0; i < hw_addr_len; i++)
+ if (hw_addr[i] != 0)
break;
- if (i == ifp->hw_addr_len)
- ifp->hw_addr_len = 0;
+ if (i == hw_addr_len)
+ hw_addr_len = 0;
+
+ if_update_state_hw_addr(ifp, hw_addr, hw_addr_len);
}
static void interface_update_l2info(struct zebra_dplane_ctx *ctx,
@@ -1984,9 +1987,10 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx)
/* Update interface information. */
set_ifindex(ifp, ifindex, zns);
ifp->flags = flags;
- ifp->mtu6 = ifp->mtu = mtu;
- ifp->metric = 0;
- ifp->speed = kernel_get_speed(ifp, NULL);
+ if_update_state_mtu(ifp, mtu);
+ if_update_state_mtu6(ifp, mtu);
+ if_update_state_metric(ifp, 0);
+ if_update_state_speed(ifp, kernel_get_speed(ifp, NULL));
ifp->ptm_status = ZEBRA_PTM_STATUS_UNKNOWN;
ifp->txqlen = dplane_ctx_get_intf_txqlen(ctx);
@@ -2036,6 +2040,7 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx)
IS_ZEBRA_IF_BRIDGE_VLAN_AWARE(
zif));
}
+ // if_update_state(ifp);
} else if (ifp->vrf->vrf_id != vrf_id) {
/* VRF change for an interface. */
if (IS_ZEBRA_DEBUG_KERNEL)
@@ -2058,8 +2063,9 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx)
(unsigned long long)flags);
set_ifindex(ifp, ifindex, zns);
- ifp->mtu6 = ifp->mtu = mtu;
- ifp->metric = 0;
+ if_update_state_mtu(ifp, mtu);
+ if_update_state_mtu6(ifp, mtu);
+ if_update_state_metric(ifp, 0);
ifp->txqlen = dplane_ctx_get_intf_txqlen(ctx);
/*
diff --git a/zebra/main.c b/zebra/main.c
index 4546d1477..d189d1e0a 100644
--- a/zebra/main.c
+++ b/zebra/main.c
@@ -287,6 +287,7 @@ struct frr_signal_t zebra_signals[] = {
/* clang-format off */
static const struct frr_yang_module_info *const zebra_yang_modules[] = {
+ &frr_backend_info,
&frr_filter_info,
&frr_interface_info,
&frr_route_map_info,
@@ -356,6 +357,9 @@ int main(int argc, char **argv)
zserv_path = NULL;
+ if_notify_oper_changes = true;
+ vrf_notify_oper_changes = true;
+
vrf_configure_backend(VRF_BACKEND_VRF_LITE);
frr_preinit(&zebra_di, argc, argv);
diff --git a/zebra/rt_netlink.c b/zebra/rt_netlink.c
index 492fe5288..b32882e85 100644
--- a/zebra/rt_netlink.c
+++ b/zebra/rt_netlink.c
@@ -723,44 +723,52 @@ static uint16_t parse_multipath_nexthops_unicast(ns_id_t ns_id, struct nexthop_g
return nhop_num;
}
-/* Looking up routing table by netlink interface. */
-int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
- ns_id_t ns_id, int startup,
- struct zebra_dplane_ctx *ctx)
+/*
+ * Parse netlink route message and capture info into a dplane ctx.
+ * Returns <0 if the message is to be skipped (might be an error)
+ */
+static int netlink_route_read_unicast_ctx(struct nlmsghdr *h, ns_id_t ns_id,
+ struct rtattr **tb_in,
+ struct zebra_dplane_ctx *ctx)
{
+ int ret = 0;
int len;
struct rtmsg *rtm;
- struct rtattr *tb[RTA_MAX + 1];
+ struct rtattr **tb, *tb_array[RTA_MAX + 1];
uint32_t flags = 0;
struct prefix p;
- struct prefix_ipv6 src_p = {};
- vrf_id_t vrf_id;
+ struct prefix src_p = {};
bool selfroute;
-
- char anyaddr[16] = {0};
-
+ char anyaddr[16] = {};
int proto = ZEBRA_ROUTE_KERNEL;
int index = 0;
- int table;
+ int tableid;
int metric = 0;
uint32_t mtu = 0;
uint8_t distance = 0;
route_tag_t tag = 0;
- uint32_t nhe_id = 0;
-
+ uint32_t nhg_id = 0;
void *dest = NULL;
void *gate = NULL;
+ int gate_len;
void *prefsrc = NULL; /* IPv4 preferred source host address */
+ int prefsrc_len;
void *src = NULL; /* IPv6 srcdest source prefix */
enum blackhole_type bh_type = BLACKHOLE_UNSPEC;
+ afi_t afi = AFI_IP;
+ struct ipaddr addr = {};
- frrtrace(3, frr_zebra, netlink_route_change_read_unicast, h, ns_id,
- startup);
+ len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg));
+ if (len < 0) {
+ zlog_err(
+ "%s: Netlink route message received with invalid size %d %zu",
+ __func__, h->nlmsg_len,
+ (size_t)NLMSG_LENGTH(sizeof(struct rtmsg)));
+ return -1;
+ }
rtm = NLMSG_DATA(h);
- if (startup && h->nlmsg_type != RTM_NEWROUTE)
- return 0;
switch (rtm->rtm_type) {
case RTN_UNICAST:
break;
@@ -778,54 +786,42 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
zlog_debug("Route rtm_type: %s(%d) intentionally ignoring",
nl_rttype_to_str(rtm->rtm_type),
rtm->rtm_type);
- return 0;
+ ret = -1;
+ goto done;
}
- len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg));
- if (len < 0) {
- zlog_err(
- "%s: Message received from netlink is of a broken size %d %zu",
- __func__, h->nlmsg_len,
- (size_t)NLMSG_LENGTH(sizeof(struct rtmsg)));
- return -1;
+ if ((rtm->rtm_flags & RTM_F_CLONED) ||
+ (rtm->rtm_protocol == RTPROT_REDIRECT)) {
+ ret = -1;
+ goto done;
}
- netlink_parse_rtattr(tb, RTA_MAX, RTM_RTA(rtm), len);
-
- if (rtm->rtm_flags & RTM_F_CLONED)
- return 0;
- if (rtm->rtm_protocol == RTPROT_REDIRECT)
- return 0;
+ /* We don't care about change notifications for the MPLS table. */
+ /* TODO: Revisit this. */
+ if (rtm->rtm_family == AF_MPLS) {
+ ret = -1;
+ goto done;
+ }
- selfroute = is_selfroute(rtm->rtm_protocol);
+ dplane_ctx_set_ns_id(ctx, ns_id);
- if (!startup && selfroute && h->nlmsg_type == RTM_NEWROUTE &&
- !zrouter.asic_offloaded && !ctx) {
- if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("Route type: %d Received that we think we have originated, ignoring",
- rtm->rtm_protocol);
- return 0;
+ /* Parse attrs if necessary */
+ if (tb_in != NULL) {
+ tb = tb_in;
+ } else {
+ netlink_parse_rtattr(tb_array, RTA_MAX, RTM_RTA(rtm), len);
+ tb = tb_array;
}
- /* We don't care about change notifications for the MPLS table. */
- /* TODO: Revisit this. */
- if (rtm->rtm_family == AF_MPLS)
- return 0;
+ selfroute = is_selfroute(rtm->rtm_protocol);
/* Table corresponding to route. */
if (tb[RTA_TABLE])
- table = *(int *)RTA_DATA(tb[RTA_TABLE]);
+ tableid = *(int *)RTA_DATA(tb[RTA_TABLE]);
else
- table = rtm->rtm_table;
-
- /* Map to VRF */
- vrf_id = zebra_vrf_lookup_by_table(table, ns_id);
- if (vrf_id == VRF_DEFAULT) {
- if (!is_zebra_valid_kernel_table(table)
- && !is_zebra_main_routing_table(table))
- return 0;
- }
+ tableid = rtm->rtm_table;
+ /* Map flags values */
if (rtm->rtm_flags & RTM_F_TRAP)
flags |= ZEBRA_FLAG_TRAPPED;
if (rtm->rtm_flags & RTM_F_OFFLOAD)
@@ -836,7 +832,7 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
if (h->nlmsg_flags & NLM_F_APPEND)
flags |= ZEBRA_FLAG_OUTOFSYNC;
- /* Route which inserted by Zebra. */
+ /* Route which was inserted by Zebra. */
if (selfroute) {
flags |= ZEBRA_FLAG_SELFROUTE;
proto = proto2zebra(rtm->rtm_protocol, rtm->rtm_family, false);
@@ -854,14 +850,18 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
else
src = anyaddr;
- if (tb[RTA_PREFSRC])
+ if (tb[RTA_PREFSRC]) {
prefsrc = RTA_DATA(tb[RTA_PREFSRC]);
+ prefsrc_len = RTA_PAYLOAD(tb[RTA_PREFSRC]);
+ }
- if (tb[RTA_GATEWAY])
+ if (tb[RTA_GATEWAY]) {
gate = RTA_DATA(tb[RTA_GATEWAY]);
+ gate_len = RTA_PAYLOAD(tb[RTA_GATEWAY]);
+ }
if (tb[RTA_NH_ID])
- nhe_id = *(uint32_t *)RTA_DATA(tb[RTA_NH_ID]);
+ nhg_id = *(uint32_t *)RTA_DATA(tb[RTA_NH_ID]);
if (tb[RTA_PRIORITY])
metric = *(int *)RTA_DATA(tb[RTA_PRIORITY]);
@@ -887,7 +887,8 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
zlog_err(
"Invalid destination prefix length: %u received from kernel route change",
rtm->rtm_dst_len);
- return -1;
+ ret = -1;
+ goto done;
}
memcpy(&p.u.prefix4, dest, 4);
p.prefixlen = rtm->rtm_dst_len;
@@ -895,14 +896,16 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
if (rtm->rtm_src_len != 0) {
flog_warn(
EC_ZEBRA_UNSUPPORTED_V4_SRCDEST,
- "unsupported IPv4 sourcedest route (dest %pFX vrf %u)",
- &p, vrf_id);
- return 0;
+ "unsupported IPv4 sourcedest route (dest %pFX table %u)",
+ &p, tableid);
+ ret = -1;
+ goto done;
}
/* Force debug below to not display anything for source */
src_p.prefixlen = 0;
} else if (rtm->rtm_family == AF_INET6) {
+ afi = AFI_IP6;
p.family = AF_INET6;
if (rtm->rtm_dst_len > IPV6_MAX_BITLEN) {
zlog_err(
@@ -920,14 +923,15 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
rtm->rtm_src_len);
return -1;
}
- memcpy(&src_p.prefix, src, 16);
+ memcpy(&src_p.u.prefix6, src, 16);
src_p.prefixlen = rtm->rtm_src_len;
} else {
/* We only handle the AFs we handle... */
if (IS_ZEBRA_DEBUG_KERNEL)
zlog_debug("%s: unknown address-family %u", __func__,
rtm->rtm_family);
- return 0;
+ ret = -1;
+ goto done;
}
/*
@@ -956,6 +960,249 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
char buf2[PREFIX_STRLEN];
zlog_debug(
+ "%s %pFX%s%s nsid: %u table_id: %u metric: %d Admin Distance: %d",
+ nl_msg_type_to_str(h->nlmsg_type), &p,
+ src_p.prefixlen ? " from " : "",
+ src_p.prefixlen ? prefix2str(&src_p, buf2, sizeof(buf2))
+ : "",
+ ns_id, tableid, metric, distance);
+ }
+
+ /* Set values in ctx. Note that vrf is not set, because we can only
+ * resolve the FRR vrf info in the main pthread.
+ */
+ dplane_ctx_set_afi(ctx, afi);
+ dplane_ctx_set_safi(ctx, SAFI_UNICAST);
+ dplane_ctx_set_table(ctx, tableid);
+ dplane_ctx_set_vrf(ctx, VRF_UNKNOWN);
+ dplane_ctx_set_ns_id(ctx, ns_id);
+ dplane_ctx_set_dest(ctx, &p);
+ if (src_p.prefixlen > 0)
+ dplane_ctx_set_src(ctx, &src_p);
+ else
+ dplane_ctx_set_src(ctx, NULL);
+ dplane_ctx_set_type(ctx, proto);
+ dplane_ctx_set_flags(ctx, flags);
+ dplane_ctx_set_route_metric(ctx, metric);
+ dplane_ctx_set_route_mtu(ctx, mtu);
+ dplane_ctx_set_distance(ctx, distance);
+ dplane_ctx_set_tag(ctx, tag);
+
+ dplane_ctx_set_ifindex(ctx, index);
+ dplane_ctx_set_route_bhtype(ctx, bh_type);
+ if (prefsrc) {
+ /* Convert to ipaddr */
+ memset(&addr, 0, sizeof(addr));
+
+ if (afi == AFI_IP) {
+ SET_IPADDR_V4(&addr);
+ memcpy(&addr.ipaddr_v4, prefsrc, prefsrc_len);
+ } else {
+ SET_IPADDR_V6(&addr);
+ memcpy(&addr.ipaddr_v6, prefsrc, prefsrc_len);
+ }
+
+ dplane_ctx_set_route_prefsrc(ctx, &addr);
+ } else {
+ dplane_ctx_set_route_prefsrc(ctx, NULL);
+ }
+
+ if (gate) {
+ /* Convert to ipaddr */
+ memset(&addr, 0, sizeof(addr));
+
+ if (afi == AFI_IP) {
+ SET_IPADDR_V4(&addr);
+ memcpy(&addr.ipaddr_v4, gate, gate_len);
+ } else {
+ SET_IPADDR_V6(&addr);
+ memcpy(&addr.ipaddr_v6, gate, gate_len);
+ }
+
+ dplane_ctx_set_route_gw(ctx, &addr);
+ }
+
+ if (nhg_id > 0)
+ dplane_ctx_set_nhg_id(ctx, nhg_id);
+
+done:
+
+ return ret;
+}
+
+/*
+ * Public api for use parsing a route notification message: this notification
+ * only parses the top-level route attributes, and doesn't include nexthops.
+ */
+int netlink_route_notify_read_ctx(struct nlmsghdr *h, ns_id_t ns_id,
+ struct zebra_dplane_ctx *ctx)
+{
+ /* Use the common parser for route-level netlink message info;
+ * we expect the caller to have set the context up with the correct
+ * dplane opcode, and we expect the caller to submit the resulting ctx
+ * for processing in zebra.
+ */
+ return netlink_route_read_unicast_ctx(h, ns_id, NULL, ctx);
+}
+
+/*
+ * Parse a route update netlink message, extract and validate its data,
+ * call into zebra with an update.
+ */
+static int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
+ ns_id_t ns_id, int startup)
+{
+ int len;
+ struct rtmsg *rtm;
+ struct rtattr *tb[RTA_MAX + 1];
+ uint32_t flags = 0;
+ struct prefix p;
+ struct prefix src_p = {};
+ vrf_id_t vrf_id;
+ bool selfroute;
+
+ int proto = ZEBRA_ROUTE_KERNEL;
+ int index = 0;
+ int table;
+ int metric = 0;
+ uint32_t mtu = 0;
+ uint8_t distance = 0;
+ route_tag_t tag = 0;
+ uint32_t nhe_id = 0;
+ void *gate = NULL;
+ const struct ipaddr *gate_addr;
+ void *prefsrc = NULL; /* IPv4 preferred source host address */
+ const struct ipaddr *prefsrc_addr;
+ enum blackhole_type bh_type = BLACKHOLE_UNSPEC;
+ afi_t afi;
+ struct zebra_dplane_ctx *ctx = NULL;
+ int ret;
+
+ frrtrace(3, frr_zebra, netlink_route_change_read_unicast, h, ns_id,
+ startup);
+
+ rtm = NLMSG_DATA(h);
+
+ if (startup && h->nlmsg_type != RTM_NEWROUTE)
+ return 0;
+
+ switch (rtm->rtm_type) {
+ case RTN_UNICAST:
+ case RTN_BLACKHOLE:
+ case RTN_UNREACHABLE:
+ case RTN_PROHIBIT:
+ break;
+ default:
+ if (IS_ZEBRA_DEBUG_KERNEL)
+ zlog_debug("Route rtm_type: %s(%d) intentionally ignoring",
+ nl_rttype_to_str(rtm->rtm_type),
+ rtm->rtm_type);
+ return 0;
+ }
+
+ len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg));
+ if (len < 0) {
+ zlog_err(
+ "%s: Message received from netlink is of a broken size %d %zu",
+ __func__, h->nlmsg_len,
+ (size_t)NLMSG_LENGTH(sizeof(struct rtmsg)));
+ return -1;
+ }
+
+ if (rtm->rtm_flags & RTM_F_CLONED)
+ return 0;
+ if (rtm->rtm_protocol == RTPROT_REDIRECT)
+ return 0;
+
+ /* We don't care about change notifications for the MPLS table. */
+ /* TODO: Revisit this. */
+ if (rtm->rtm_family == AF_MPLS)
+ return 0;
+
+ netlink_parse_rtattr(tb, RTA_MAX, RTM_RTA(rtm), len);
+
+ /*
+ * Allocate a context object and parse the core parts of the route
+ * message.
+ * After this point, note that we need to 'goto done' to exit,
+ * so that the ctx gets cleaned-up.
+ */
+ ctx = dplane_ctx_alloc();
+
+ dplane_ctx_route_init(ctx,
+ h->nlmsg_type == RTM_NEWROUTE ?
+ DPLANE_OP_ROUTE_INSTALL :
+ DPLANE_OP_ROUTE_DELETE, NULL, NULL);
+
+ /* Finish parsing the core route info */
+ ret = netlink_route_read_unicast_ctx(h, ns_id, tb, ctx);
+ if (ret < 0) {
+ ret = 0;
+ goto done;
+ }
+
+ flags = dplane_ctx_get_flags(ctx);
+
+ selfroute = CHECK_FLAG(flags, ZEBRA_FLAG_SELFROUTE);
+
+ if (!startup && selfroute && h->nlmsg_type == RTM_NEWROUTE &&
+ !zrouter.asic_offloaded) {
+ if (IS_ZEBRA_DEBUG_KERNEL)
+ zlog_debug("Route type: %d Received that we think we have originated, ignoring",
+ rtm->rtm_protocol);
+ ret = 0;
+ goto done;
+ }
+
+ /* Table corresponding to route. */
+ table = dplane_ctx_get_table(ctx);
+
+ /* Map to VRF: note that this can _only_ be done in the main pthread */
+ vrf_id = zebra_vrf_lookup_by_table(table, ns_id);
+ if (vrf_id == VRF_DEFAULT) {
+ if (!is_zebra_valid_kernel_table(table)
+ && !is_zebra_main_routing_table(table)) {
+ ret = 0;
+ goto done;
+ }
+ }
+
+ /* Route which inserted by Zebra. */
+ if (selfroute)
+ proto = dplane_ctx_get_type(ctx);
+
+ index = dplane_ctx_get_ifindex(ctx);
+
+ p = *(dplane_ctx_get_dest(ctx));
+
+ if (dplane_ctx_get_src(ctx) == NULL)
+ src_p.prefixlen = 0;
+ else
+ src_p = *(dplane_ctx_get_src(ctx));
+
+ prefsrc_addr = dplane_ctx_get_route_prefsrc(ctx);
+ if (prefsrc_addr)
+ prefsrc = (void *)&(prefsrc_addr->ip.addr);
+
+ gate_addr = dplane_ctx_get_route_gw(ctx);
+ if (!IS_IPADDR_NONE(gate_addr))
+ gate = (void *)&(gate_addr->ip.addr);
+
+ nhe_id = dplane_ctx_get_nhe_id(ctx);
+
+ metric = dplane_ctx_get_metric(ctx);
+ distance = dplane_ctx_get_distance(ctx);
+ tag = dplane_ctx_get_tag(ctx);
+ mtu = dplane_ctx_get_mtu(ctx);
+
+ afi = dplane_ctx_get_afi(ctx);
+
+ bh_type = dplane_ctx_get_route_bhtype(ctx);
+
+ if (IS_ZEBRA_DEBUG_KERNEL) {
+ char buf2[PREFIX_STRLEN];
+
+ zlog_debug(
"%s %pFX%s%s vrf %s(%u) table_id: %u metric: %d Admin Distance: %d",
nl_msg_type_to_str(h->nlmsg_type), &p,
src_p.prefixlen ? " from " : "",
@@ -965,10 +1212,6 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
distance);
}
- afi_t afi = AFI_IP;
- if (rtm->rtm_family == AF_INET6)
- afi = AFI_IP6;
-
if (h->nlmsg_type == RTM_NEWROUTE) {
struct route_entry *re;
struct nexthop_group *ng = NULL;
@@ -1018,12 +1261,11 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
}
}
if (nhe_id || ng) {
- dplane_rib_add_multipath(afi, SAFI_UNICAST, &p, &src_p,
- re, ng, startup, ctx);
+ rib_add_multipath(afi, SAFI_UNICAST, &p,
+ (struct prefix_ipv6 *)&src_p,
+ re, ng, startup);
if (ng)
nexthop_group_delete(&ng);
- if (ctx)
- zebra_rib_route_entry_free(re);
} else {
/*
* I really don't see how this is possible
@@ -1038,17 +1280,10 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
zebra_rib_route_entry_free(re);
}
} else {
- if (ctx) {
- zlog_err(
- "%s: %pFX RTM_DELROUTE received but received a context as well",
- __func__, &p);
- return 0;
- }
-
if (nhe_id) {
rib_delete(afi, SAFI_UNICAST, vrf_id, proto, 0, flags,
- &p, &src_p, NULL, nhe_id, table, metric,
- distance, true);
+ &p, (struct prefix_ipv6 *)&src_p, NULL,
+ nhe_id, table, metric, distance, true);
} else {
if (!tb[RTA_MULTIPATH]) {
struct nexthop nh;
@@ -1057,26 +1292,33 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
ns_id, rtm, tb, bh_type, index, prefsrc,
gate, afi, vrf_id);
rib_delete(afi, SAFI_UNICAST, vrf_id, proto, 0,
- flags, &p, &src_p, &nh, 0, table,
- metric, distance, true);
+ flags, &p,
+ (struct prefix_ipv6 *)&src_p, &nh, 0,
+ table, metric, distance, true);
} else {
/* XXX: need to compare the entire list of
* nexthops here for NLM_F_APPEND stupidity */
rib_delete(afi, SAFI_UNICAST, vrf_id, proto, 0,
- flags, &p, &src_p, NULL, 0, table,
- metric, distance, true);
+ flags, &p,
+ (struct prefix_ipv6 *)&src_p, NULL, 0,
+ table, metric, distance, true);
}
}
}
- return 1;
+ ret = 1;
+
+done:
+ if (ctx)
+ dplane_ctx_fini(&ctx);
+
+ return ret;
}
static int netlink_route_change_read_unicast(struct nlmsghdr *h, ns_id_t ns_id,
int startup)
{
- return netlink_route_change_read_unicast_internal(h, ns_id, startup,
- NULL);
+ return netlink_route_change_read_unicast_internal(h, ns_id, startup);
}
static struct mcast_route_data *mroute = NULL;
@@ -1615,13 +1857,10 @@ static bool _netlink_route_build_singlepath(const struct prefix *p,
{
char label_buf[256];
- struct vrf *vrf;
char addrstr[INET6_ADDRSTRLEN];
assert(nexthop);
- vrf = vrf_lookup_by_id(nexthop->vrf_id);
-
if (!_netlink_route_encode_label_info(nexthop, nlmsg, req_size, rtmsg,
label_buf, sizeof(label_buf)))
return false;
@@ -1782,10 +2021,10 @@ static bool _netlink_route_build_singlepath(const struct prefix *p,
}
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: 5549 (%s): %pFX nexthop via %s %s if %u vrf %s(%u)",
+ zlog_debug("%s: 5549 (%s): %pFX nexthop via %s %s if %u vrf %u",
__func__, routedesc, p, ipv4_ll_buf,
label_buf, nexthop->ifindex,
- VRF_LOGNAME(vrf), nexthop->vrf_id);
+ nexthop->vrf_id);
return true;
}
@@ -1808,10 +2047,9 @@ static bool _netlink_route_build_singlepath(const struct prefix *p,
if (IS_ZEBRA_DEBUG_KERNEL) {
inet_ntop(AF_INET, &nexthop->gate.ipv4, addrstr,
sizeof(addrstr));
- zlog_debug("%s: (%s): %pFX nexthop via %s %s if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via %s %s if %u vrf %u",
__func__, routedesc, p, addrstr, label_buf,
- nexthop->ifindex, VRF_LOGNAME(vrf),
- nexthop->vrf_id);
+ nexthop->ifindex, nexthop->vrf_id);
}
}
@@ -1832,10 +2070,9 @@ static bool _netlink_route_build_singlepath(const struct prefix *p,
if (IS_ZEBRA_DEBUG_KERNEL) {
inet_ntop(AF_INET6, &nexthop->gate.ipv6, addrstr,
sizeof(addrstr));
- zlog_debug("%s: (%s): %pFX nexthop via %s %s if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via %s %s if %u vrf %u",
__func__, routedesc, p, addrstr, label_buf,
- nexthop->ifindex, VRF_LOGNAME(vrf),
- nexthop->vrf_id);
+ nexthop->ifindex, nexthop->vrf_id);
}
}
@@ -1857,9 +2094,9 @@ static bool _netlink_route_build_singlepath(const struct prefix *p,
}
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: (%s): %pFX nexthop via if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via if %u vrf %u",
__func__, routedesc, p, nexthop->ifindex,
- VRF_LOGNAME(vrf), nexthop->vrf_id);
+ nexthop->vrf_id);
}
return true;
@@ -1943,7 +2180,6 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
route_tag_t tag, bool fpm)
{
char label_buf[256];
- struct vrf *vrf;
struct rtnexthop *rtnh;
rtnh = nl_attr_rtnh(nlmsg, req_size);
@@ -1952,8 +2188,6 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
assert(nexthop);
- vrf = vrf_lookup_by_id(nexthop->vrf_id);
-
if (!_netlink_route_encode_label_info(nexthop, nlmsg, req_size, rtmsg,
label_buf, sizeof(label_buf)))
return false;
@@ -1976,10 +2210,9 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
if (IS_ZEBRA_DEBUG_KERNEL)
zlog_debug(
- "%s: 5549 (%s): %pFX nexthop via %s %s if %u vrf %s(%u)",
+ "%s: 5549 (%s): %pFX nexthop via %s %s if %u vrf %u",
__func__, routedesc, p, ipv4_ll_buf, label_buf,
- nexthop->ifindex, VRF_LOGNAME(vrf),
- nexthop->vrf_id);
+ nexthop->ifindex, nexthop->vrf_id);
nl_attr_rtnh_end(nlmsg, rtnh);
return true;
}
@@ -1997,10 +2230,9 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
*src = &nexthop->src;
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: (%s): %pFX nexthop via %pI4 %s if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via %pI4 %s if %u vrf %u",
__func__, routedesc, p, &nexthop->gate.ipv4,
- label_buf, nexthop->ifindex,
- VRF_LOGNAME(vrf), nexthop->vrf_id);
+ label_buf, nexthop->ifindex, nexthop->vrf_id);
}
if (nexthop->type == NEXTHOP_TYPE_IPV6
|| nexthop->type == NEXTHOP_TYPE_IPV6_IFINDEX) {
@@ -2015,10 +2247,9 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
*src = &nexthop->src;
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: (%s): %pFX nexthop via %pI6 %s if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via %pI6 %s if %u vrf %u",
__func__, routedesc, p, &nexthop->gate.ipv6,
- label_buf, nexthop->ifindex,
- VRF_LOGNAME(vrf), nexthop->vrf_id);
+ label_buf, nexthop->ifindex, nexthop->vrf_id);
}
/*
@@ -2037,9 +2268,9 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
*src = &nexthop->src;
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: (%s): %pFX nexthop via if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via if %u vrf %u",
__func__, routedesc, p, nexthop->ifindex,
- VRF_LOGNAME(vrf), nexthop->vrf_id);
+ nexthop->vrf_id);
}
if (nexthop->weight)
@@ -3057,9 +3288,8 @@ ssize_t netlink_nexthop_msg_encode(uint16_t cmd,
nexthop_done:
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: ID (%u): %pNHv(%d) vrf %s(%u) %s ",
+ zlog_debug("%s: ID (%u): %pNHv(%d) vrf %u %s ",
__func__, id, nh, nh->ifindex,
- vrf_id_to_name(nh->vrf_id),
nh->vrf_id, label_buf);
}
diff --git a/zebra/rt_netlink.h b/zebra/rt_netlink.h
index d51944f1a..1c113baee 100644
--- a/zebra/rt_netlink.h
+++ b/zebra/rt_netlink.h
@@ -64,6 +64,15 @@ extern ssize_t netlink_macfdb_update_ctx(struct zebra_dplane_ctx *ctx,
extern int netlink_route_change(struct nlmsghdr *h, ns_id_t ns_id, int startup);
extern int netlink_route_read(struct zebra_ns *zns);
+/*
+ * Public api for parsing a route notification message: this notification
+ * only parses the top-level route attributes, and doesn't include nexthops.
+ * FPM, for example, is a user.
+ * Returns <0 if the message should be ignored/skipped.
+ */
+int netlink_route_notify_read_ctx(struct nlmsghdr *h, ns_id_t ns_id,
+ struct zebra_dplane_ctx *ctx);
+
extern int netlink_nexthop_change(struct nlmsghdr *h, ns_id_t ns_id,
int startup);
extern int netlink_nexthop_read(struct zebra_ns *zns);
@@ -109,10 +118,6 @@ netlink_put_lsp_update_msg(struct nl_batch *bth, struct zebra_dplane_ctx *ctx);
extern enum netlink_msg_status
netlink_put_pw_update_msg(struct nl_batch *bth, struct zebra_dplane_ctx *ctx);
-int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
- ns_id_t ns_id, int startup,
- struct zebra_dplane_ctx *ctx);
-
#ifdef NETLINK_DEBUG
const char *nlmsg_type2str(uint16_t type);
const char *af_type2str(int type);
diff --git a/zebra/zebra_dplane.c b/zebra/zebra_dplane.c
index 88c1a0493..b57c93015 100644
--- a/zebra/zebra_dplane.c
+++ b/zebra/zebra_dplane.c
@@ -150,6 +150,11 @@ struct dplane_route_info {
/* Optional list of extra interface info */
struct dplane_intf_extra_list_head intf_extra_list;
+
+ /* Route-level info that aligns with some netlink route data */
+ enum blackhole_type zd_bh_type;
+ struct ipaddr zd_prefsrc;
+ struct ipaddr zd_gateway;
};
/*
@@ -1906,6 +1911,12 @@ void dplane_ctx_set_flags(struct zebra_dplane_ctx *ctx, uint32_t flags)
ctx->u.rinfo.zd_flags = flags;
}
+void dplane_ctx_set_route_metric(struct zebra_dplane_ctx *ctx, uint32_t metric)
+{
+ DPLANE_CTX_VALID(ctx);
+ ctx->u.rinfo.zd_metric = metric;
+}
+
uint32_t dplane_ctx_get_metric(const struct zebra_dplane_ctx *ctx)
{
DPLANE_CTX_VALID(ctx);
@@ -1927,6 +1938,12 @@ uint32_t dplane_ctx_get_mtu(const struct zebra_dplane_ctx *ctx)
return ctx->u.rinfo.zd_mtu;
}
+void dplane_ctx_set_route_mtu(struct zebra_dplane_ctx *ctx, uint32_t mtu)
+{
+ DPLANE_CTX_VALID(ctx);
+ ctx->u.rinfo.zd_mtu = mtu;
+}
+
uint32_t dplane_ctx_get_nh_mtu(const struct zebra_dplane_ctx *ctx)
{
DPLANE_CTX_VALID(ctx);
@@ -1955,6 +1972,58 @@ uint8_t dplane_ctx_get_old_distance(const struct zebra_dplane_ctx *ctx)
return ctx->u.rinfo.zd_old_distance;
}
+/* Route blackhole type */
+enum blackhole_type dplane_ctx_get_route_bhtype(const struct zebra_dplane_ctx *ctx)
+{
+ DPLANE_CTX_VALID(ctx);
+ return ctx->u.rinfo.zd_bh_type;
+}
+
+void dplane_ctx_set_route_bhtype(struct zebra_dplane_ctx *ctx,
+ enum blackhole_type bhtype)
+{
+ DPLANE_CTX_VALID(ctx);
+ ctx->u.rinfo.zd_bh_type = bhtype;
+}
+
+/* IP 'preferred source', at route-level */
+const struct ipaddr *dplane_ctx_get_route_prefsrc(const struct zebra_dplane_ctx *ctx)
+{
+ DPLANE_CTX_VALID(ctx);
+
+ if (ctx->u.rinfo.zd_prefsrc.ipa_type != 0)
+ return &(ctx->u.rinfo.zd_prefsrc);
+ else
+ return NULL;
+}
+
+void dplane_ctx_set_route_prefsrc(struct zebra_dplane_ctx *ctx,
+ const struct ipaddr *addr)
+{
+ DPLANE_CTX_VALID(ctx);
+ if (addr)
+ ctx->u.rinfo.zd_prefsrc = *addr;
+ else
+ memset(&ctx->u.rinfo.zd_prefsrc, 0,
+ sizeof(ctx->u.rinfo.zd_prefsrc));
+}
+
+/* Route-level 'gateway' */
+const struct ipaddr *dplane_ctx_get_route_gw(const struct zebra_dplane_ctx *ctx)
+{
+ DPLANE_CTX_VALID(ctx);
+ return &(ctx->u.rinfo.zd_gateway);
+}
+
+void dplane_ctx_set_route_gw(struct zebra_dplane_ctx *ctx, const struct ipaddr *gw)
+{
+ DPLANE_CTX_VALID(ctx);
+ if (gw)
+ ctx->u.rinfo.zd_gateway = *gw;
+ else
+ memset(&ctx->u.rinfo.zd_gateway, 0, sizeof(ctx->u.rinfo.zd_gateway));
+}
+
int dplane_ctx_tc_qdisc_get_kind(const struct zebra_dplane_ctx *ctx)
{
DPLANE_CTX_VALID(ctx);
@@ -2179,6 +2248,12 @@ uint32_t dplane_ctx_get_nhg_id(const struct zebra_dplane_ctx *ctx)
return ctx->u.rinfo.zd_nhg_id;
}
+void dplane_ctx_set_nhg_id(struct zebra_dplane_ctx *ctx, uint32_t nhgid)
+{
+ DPLANE_CTX_VALID(ctx);
+ ctx->u.rinfo.zd_nhg_id = nhgid;
+}
+
const struct nexthop_group *dplane_ctx_get_ng(
const struct zebra_dplane_ctx *ctx)
{
@@ -6923,20 +6998,6 @@ kernel_dplane_process_ipset_entry(struct zebra_dplane_provider *prov,
dplane_provider_enqueue_out_ctx(prov, ctx);
}
-void dplane_rib_add_multipath(afi_t afi, safi_t safi, struct prefix *p,
- struct prefix_ipv6 *src_p, struct route_entry *re,
- struct nexthop_group *ng, int startup,
- struct zebra_dplane_ctx *ctx)
-{
- if (!ctx)
- rib_add_multipath(afi, safi, p, src_p, re, ng, startup);
- else {
- dplane_ctx_route_init_basic(ctx, dplane_ctx_get_op(ctx), re, p,
- src_p, afi, safi);
- dplane_provider_enqueue_to_zebra(ctx);
- }
-}
-
/*
* Kernel provider callback
*/
diff --git a/zebra/zebra_dplane.h b/zebra/zebra_dplane.h
index 285b00c9b..cabc70c23 100644
--- a/zebra/zebra_dplane.h
+++ b/zebra/zebra_dplane.h
@@ -524,11 +524,26 @@ uint32_t dplane_ctx_get_flags(const struct zebra_dplane_ctx *ctx);
void dplane_ctx_set_flags(struct zebra_dplane_ctx *ctx, uint32_t flags);
uint32_t dplane_ctx_get_metric(const struct zebra_dplane_ctx *ctx);
uint32_t dplane_ctx_get_old_metric(const struct zebra_dplane_ctx *ctx);
+void dplane_ctx_set_route_metric(struct zebra_dplane_ctx *ctx, uint32_t metric);
+void dplane_ctx_set_route_mtu(struct zebra_dplane_ctx *ctx, uint32_t mtu);
uint32_t dplane_ctx_get_mtu(const struct zebra_dplane_ctx *ctx);
uint32_t dplane_ctx_get_nh_mtu(const struct zebra_dplane_ctx *ctx);
uint8_t dplane_ctx_get_distance(const struct zebra_dplane_ctx *ctx);
void dplane_ctx_set_distance(struct zebra_dplane_ctx *ctx, uint8_t distance);
uint8_t dplane_ctx_get_old_distance(const struct zebra_dplane_ctx *ctx);
+/* Route blackhole type */
+enum blackhole_type dplane_ctx_get_route_bhtype(
+ const struct zebra_dplane_ctx *ctx);
+void dplane_ctx_set_route_bhtype(struct zebra_dplane_ctx *ctx,
+ enum blackhole_type bhtype);
+/* IPv4 'preferred source', at route-level */
+const struct ipaddr *dplane_ctx_get_route_prefsrc(
+ const struct zebra_dplane_ctx *ctx);
+void dplane_ctx_set_route_prefsrc(struct zebra_dplane_ctx *ctx,
+ const struct ipaddr *addr);
+/* Route 'gateway', at route-level */
+const struct ipaddr *dplane_ctx_get_route_gw(const struct zebra_dplane_ctx *ctx);
+void dplane_ctx_set_route_gw(struct zebra_dplane_ctx *ctx, const struct ipaddr *gw);
/* Accessors for traffic control context */
int dplane_ctx_tc_qdisc_get_kind(const struct zebra_dplane_ctx *ctx);
@@ -572,6 +587,7 @@ void dplane_ctx_set_nexthops(struct zebra_dplane_ctx *ctx, struct nexthop *nh);
void dplane_ctx_set_backup_nhg(struct zebra_dplane_ctx *ctx,
const struct nexthop_group *nhg);
+void dplane_ctx_set_nhg_id(struct zebra_dplane_ctx *ctx, uint32_t nhgid);
uint32_t dplane_ctx_get_nhg_id(const struct zebra_dplane_ctx *ctx);
const struct nexthop_group *dplane_ctx_get_ng(
const struct zebra_dplane_ctx *ctx);
@@ -1256,16 +1272,6 @@ void zebra_dplane_shutdown(void);
void zebra_dplane_startup_stage(struct zebra_ns *zns,
enum zebra_dplane_startup_notifications spot);
-/*
- * decision point for sending a routing update through the old
- * straight to zebra master pthread or through the dplane to
- * the master pthread for handling
- */
-void dplane_rib_add_multipath(afi_t afi, safi_t safi, struct prefix *p,
- struct prefix_ipv6 *src_p, struct route_entry *re,
- struct nexthop_group *ng, int startup,
- struct zebra_dplane_ctx *ctx);
-
enum zebra_dplane_startup_notifications
dplane_ctx_get_startup_spot(struct zebra_dplane_ctx *ctx);
diff --git a/zebra/zebra_nhg.c b/zebra/zebra_nhg.c
index 6a43fe0c8..712b2534c 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);
@@ -3454,7 +3454,13 @@ void zebra_nhg_dplane_result(struct zebra_dplane_ctx *ctx)
ZAPI_NHG_INSTALLED);
break;
case ZEBRA_DPLANE_REQUEST_FAILURE:
- UNSET_FLAG(nhe->flags, NEXTHOP_GROUP_INSTALLED);
+ /*
+ * With a request failure it is unknown what we now know
+ * this is because Zebra has lost track of whether or not
+ * any previous versions of this NHG are in the kernel
+ * or even what those versions were. So at this point
+ * we cannot unset the INSTALLED flag.
+ */
/* If daemon nhg, send it an update */
if (PROTO_OWNED(nhe))
zsend_nhg_notify(nhe->type, nhe->zapi_instance,
@@ -3918,7 +3924,14 @@ void zebra_interface_nhg_reinstall(struct interface *ifp)
__func__, ifp->name);
frr_each (nhg_connected_tree, &zif->nhg_dependents, rb_node_dep) {
+ /*
+ * The nexthop associated with this was set as !ACTIVE
+ * so we need to turn it back to active when we get to
+ * this point again
+ */
+ SET_FLAG(rb_node_dep->nhe->nhg.nexthop->flags, NEXTHOP_FLAG_ACTIVE);
nh = rb_node_dep->nhe->nhg.nexthop;
+
if (zebra_nhg_set_valid_if_active(rb_node_dep->nhe)) {
if (IS_ZEBRA_DEBUG_NHG_DETAIL)
zlog_debug(
diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c
index 0226c355c..2881192eb 100644
--- a/zebra/zebra_rib.c
+++ b/zebra/zebra_rib.c
@@ -2220,8 +2220,20 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
bool fib_changed = false;
bool debug_p = IS_ZEBRA_DEBUG_DPLANE | IS_ZEBRA_DEBUG_RIB;
int start_count, end_count;
+ vrf_id_t vrf_id;
+ int tableid;
+
+ /* Locate vrf and route table - we must have one or the other */
+ tableid = dplane_ctx_get_table(ctx);
+ vrf_id = dplane_ctx_get_vrf(ctx);
+ if (vrf_id == VRF_UNKNOWN)
+ vrf_id = zebra_vrf_lookup_by_table(tableid,
+ dplane_ctx_get_ns_id(ctx));
+ else if (tableid == ZEBRA_ROUTE_TABLE_UNKNOWN)
+ tableid = zebra_vrf_lookup_tableid(vrf_id,
+ dplane_ctx_get_ns_id(ctx));
- vrf = vrf_lookup_by_id(dplane_ctx_get_vrf(ctx));
+ vrf = vrf_lookup_by_id(vrf_id);
/* Locate rn and re(s) from ctx */
rn = rib_find_rn_from_ctx(ctx);
@@ -2230,7 +2242,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
zlog_debug(
"Failed to process dplane notification: no routes for %s(%u:%u):%pRN",
VRF_LOGNAME(vrf), dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn);
+ tableid, rn);
}
goto done;
}
@@ -2240,7 +2252,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
if (debug_p)
zlog_debug("%s(%u:%u):%pRN Processing dplane notif ctx %p",
VRF_LOGNAME(vrf), dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn, ctx);
+ tableid, rn, ctx);
/*
* Take a pass through the routes, look for matches with the context
@@ -2257,7 +2269,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
zlog_debug(
"%s(%u:%u):%pRN Unable to process dplane notification: no entry for type %s",
VRF_LOGNAME(vrf), dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn,
+ tableid, rn,
zebra_route_string(dplane_ctx_get_type(ctx)));
goto done;
@@ -2293,7 +2305,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
"%s(%u:%u):%pRN dplane notif, uninstalled type %s route",
VRF_LOGNAME(vrf),
dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn,
+ tableid, rn,
zebra_route_string(
dplane_ctx_get_type(ctx)));
} else {
@@ -2303,7 +2315,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
"%s(%u:%u):%pRN dplane notif, but type %s not selected_fib",
VRF_LOGNAME(vrf),
dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn,
+ tableid, rn,
zebra_route_string(
dplane_ctx_get_type(ctx)));
}
@@ -2342,7 +2354,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
zlog_debug(
"%s(%u:%u):%pRN dplane notification: rib_update returns FALSE",
VRF_LOGNAME(vrf), dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn);
+ tableid, rn);
}
/*
@@ -2361,7 +2373,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
"%s(%u:%u):%pRN applied nexthop changes from dplane notification",
VRF_LOGNAME(vrf),
dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn);
+ tableid, rn);
/* Changed nexthops - update kernel/others */
dplane_route_notif_update(rn, re,
@@ -2373,7 +2385,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
"%s(%u:%u):%pRN installed transition from dplane notification",
VRF_LOGNAME(vrf),
dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn);
+ tableid, rn);
/* We expect this to be the selected route, so we want
* to tell others about this transition.
@@ -2393,7 +2405,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
"%s(%u:%u):%pRN un-installed transition from dplane notification",
VRF_LOGNAME(vrf),
dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn);
+ tableid, rn);
/* Transition from _something_ installed to _nothing_
* installed.
@@ -3973,10 +3985,10 @@ static void rib_link(struct route_node *rn, struct route_entry *re, int process)
dest = rib_dest_from_rnode(rn);
if (!dest) {
+ dest = zebra_rib_create_dest(rn);
+
if (IS_ZEBRA_DEBUG_RIB_DETAILED)
rnode_debug(rn, re->vrf_id, "rn %p adding dest", rn);
-
- dest = zebra_rib_create_dest(rn);
}
re_list_add_head(&dest->routes, re);
diff --git a/zebra/zebra_vrf.c b/zebra/zebra_vrf.c
index 2b3cfc876..c7781e86d 100644
--- a/zebra/zebra_vrf.c
+++ b/zebra/zebra_vrf.c
@@ -417,6 +417,25 @@ vrf_id_t zebra_vrf_lookup_by_table(uint32_t table_id, ns_id_t ns_id)
return VRF_DEFAULT;
}
+/*
+ * Lookup tableid by vrfid; handle vrf-lite and vrf-netns cases
+ */
+int zebra_vrf_lookup_tableid(vrf_id_t vrf_id, ns_id_t ns_id)
+{
+ struct zebra_vrf *zvrf;
+
+ /* Handle vrf-lite and vrf-netns */
+ if (vrf_is_backend_netns())
+ zvrf = vrf_info_lookup(ns_id);
+ else
+ zvrf = vrf_info_lookup(vrf_id);
+
+ if (zvrf)
+ return zvrf->table_id;
+ else
+ return ZEBRA_ROUTE_TABLE_UNKNOWN;
+}
+
/* Lookup VRF by identifier. */
struct zebra_vrf *zebra_vrf_lookup_by_id(vrf_id_t vrf_id)
{
diff --git a/zebra/zebra_vrf.h b/zebra/zebra_vrf.h
index f97138c81..334bb9368 100644
--- a/zebra/zebra_vrf.h
+++ b/zebra/zebra_vrf.h
@@ -24,6 +24,8 @@ FRR_CFG_DEFAULT_BOOL(ZEBRA_IP_NHT_RESOLVE_VIA_DEFAULT,
{ .val_bool = false },
);
+#define ZEBRA_ROUTE_TABLE_UNKNOWN 0
+
/* MPLS (Segment Routing) global block */
struct mpls_srgb {
uint32_t start_label;
@@ -247,6 +249,7 @@ extern struct zebra_vrf *zebra_vrf_lookup_by_name(const char *);
extern vrf_id_t zebra_vrf_lookup_by_table(uint32_t table_id, ns_id_t ns_id);
extern struct zebra_vrf *zebra_vrf_alloc(struct vrf *vrf);
extern struct route_table *zebra_vrf_table(afi_t, safi_t, vrf_id_t);
+int zebra_vrf_lookup_tableid(vrf_id_t vrf_id, ns_id_t ns_id);
/*
* API to associate a VRF with a NETNS.