Commit ed4b5a1fc9 for frr
commit ed4b5a1fc925d12d38ff9ca941d1d9868b708c13
Author: Donald Sharp <sharpd@nvidia.com>
Date: Thu Sep 24 10:25:58 2026 -0400
bgpd: Convert the bmp code to use a connection for the bmp_bgp_peer
The bmp_bgp_peer code is currently saving a bmp_bgp_peer per
peer (this includes both the doppelganger and the config node )
this is to save the data associated with the received OPEN and
sent OPEN's.
Modify to using a connection and just creatinga bgp_bmp_peer
per remote address + vrf_id. This way we can save the
open's received and sent with the associated direction of the
connection. This fixes a bug where FRR was inadvertently
deleting the doppelgangers sent open causing issues with
not properly following the bmp rfc.
Signed-off-by: Donald Sharp <sharpd@nvidia.com>
diff --git a/bgpd/bgp_bmp.c b/bgpd/bgp_bmp.c
index cda2dc4d4d..8a75ade8e6 100644
--- a/bgpd/bgp_bmp.c
+++ b/bgpd/bgp_bmp.c
@@ -46,8 +46,9 @@
static void bmp_close(struct bmp *bmp);
static struct bmp_bgp *bmp_bgp_find(struct bgp *bgp);
static void bmp_targets_put(struct bmp_targets *bt);
-static struct bmp_bgp_peer *bmp_bgp_peer_find(uint64_t peerid);
+static struct bmp_bgp_peer *bmp_bgp_peer_find(struct peer *peer);
static struct bmp_bgp_peer *bmp_bgp_peer_get(struct peer *peer);
+static bool bmp_bgp_peer_addr_in_use(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,
@@ -96,16 +97,17 @@ struct bmp_bgph_head bmp_bgph;
static int bmp_bgp_peer_cmp(const struct bmp_bgp_peer *a,
const struct bmp_bgp_peer *b)
{
- if (a->peerid < b->peerid)
+ if (a->vrf_id < b->vrf_id)
return -1;
- if (a->peerid > b->peerid)
+ if (a->vrf_id > b->vrf_id)
return 1;
- return 0;
+
+ return sockunion_cmp(&a->remote, &b->remote);
}
static uint32_t bmp_bgp_peer_hash(const struct bmp_bgp_peer *e)
{
- return e->peerid;
+ return jhash_1word(sockunion_hash(&e->remote), e->vrf_id);
}
DECLARE_HASH(bmp_peerh, struct bmp_bgp_peer, bpi,
@@ -457,6 +459,101 @@ static void bmp_notify_put(struct stream *s, struct bgp_notify *nfy)
+ sizeof(marker));
}
+static struct bmp_saved_open *bmp_open_slot(struct bmp_saved_open *opens,
+ enum connection_direction dir)
+{
+ switch (dir) {
+ case UNKNOWN:
+ case CONNECTION_INCOMING:
+ case CONNECTION_OUTGOING:
+ return &opens[dir];
+ case ESTABLISHED:
+ assert(!"DEV ESCAPE: Established should not be used as a direction to store an open message");
+ return NULL;
+ }
+
+ assert(!"DEV ESCAPE: Unknown connection direction received");
+ return NULL;
+}
+
+static void bmp_open_clear(struct bmp_saved_open *msg)
+{
+ if (!msg)
+ return;
+
+ XFREE(MTYPE_BMP_OPEN, msg->data);
+ msg->len = 0;
+}
+
+static void bmp_open_save(struct bmp_saved_open *msg, struct stream *packet, size_t size)
+{
+ bmp_open_clear(msg);
+ msg->len = size;
+ msg->data = XMALLOC(MTYPE_BMP_OPEN, size);
+ memcpy(msg->data, packet->data, size);
+}
+
+static void bmp_bgp_peer_clear_opens(struct bmp_bgp_peer *bbpeer)
+{
+ int i;
+
+ for (i = 0; i < BMP_PEER_OPEN_MAX; i++) {
+ bmp_open_clear(&bbpeer->open_rx[i]);
+ bmp_open_clear(&bbpeer->open_tx[i]);
+ }
+ bbpeer->established_dir = UNKNOWN;
+}
+
+/*
+ * Drop the OPENs for the session that just went down. Another direction may
+ * already hold OPENs for a connection that is still coming up.
+ */
+static void bmp_bgp_peer_clear_established(struct bmp_bgp_peer *bbpeer)
+{
+ enum connection_direction dir = bbpeer->established_dir;
+
+ bmp_open_clear(bmp_open_slot(bbpeer->open_rx, dir));
+ bmp_open_clear(bmp_open_slot(bbpeer->open_tx, dir));
+ bbpeer->established_dir = UNKNOWN;
+}
+
+static void bmp_open_save_dir(struct bmp_saved_open *opens, struct peer *peer,
+ struct peer_connection *connection, struct stream *packet,
+ size_t size, const char *which)
+{
+ struct bmp_saved_open *msg = bmp_open_slot(opens, connection->dir);
+
+ if (!msg) {
+ zlog_warn("bmp: not saving %s OPEN for peer %s (%s)", which, peer->host,
+ bgp_peer_get_connection_direction_string(connection));
+ return;
+ }
+
+ bmp_open_save(msg, packet, size);
+}
+
+/*
+ * Direction whose saved OPENs belong in a Peer Up for this peer.
+ * Loc-RIB uses the unknown slot. A session that is already Established
+ * reports ESTABLISHED, so use the direction recorded when it came up.
+ */
+static enum connection_direction bmp_peer_open_dir(struct peer *peer, bool is_locrib,
+ struct bmp_bgp_peer *bbpeer)
+{
+ enum connection_direction dir;
+
+ if (is_locrib)
+ return UNKNOWN;
+
+ dir = bgp_peer_get_connection_direction(peer->connection);
+ if (dir == ESTABLISHED && bbpeer)
+ return bbpeer->established_dir;
+ if (dir == ESTABLISHED)
+ return UNKNOWN;
+
+ return dir;
+}
+
/* send peer up/down for peer based on down boolean value
* returns the message to send or NULL if the peer_distinguisher is not
* available
@@ -538,24 +635,32 @@ static struct stream *bmp_peerstate(struct peer *peer, bool down)
0x00, 0x13, 0x01,
};
- bbpeer = bmp_bgp_peer_find(peer->qobj_node.nid);
+ enum connection_direction dir;
+ struct bmp_saved_open *tx = NULL, *rx = NULL;
- if (bbpeer && bbpeer->open_tx) {
- if (is_locrib)
- /* update bgp id each time peer up LOC-RIB message is to be sent */
- bmp_bgp_peer_vrf(bbpeer, peer->bgp);
- stream_put(s, bbpeer->open_tx, bbpeer->open_tx_len);
- } else {
+ bbpeer = bmp_bgp_peer_find(peer);
+ if (is_locrib && bbpeer && bbpeer->open_tx[UNKNOWN].data) {
+ /* update bgp id each time peer up LOC-RIB message is to be sent */
+ bmp_bgp_peer_vrf(bbpeer, peer->bgp);
+ }
+
+ dir = bmp_peer_open_dir(peer, is_locrib, bbpeer);
+ if (bbpeer) {
+ tx = bmp_open_slot(bbpeer->open_tx, dir);
+ rx = bmp_open_slot(bbpeer->open_rx, dir);
+ }
+
+ if (tx && tx->data)
+ stream_put(s, tx->data, tx->len);
+ else {
stream_put(s, dummy_open, sizeof(dummy_open));
- zlog_warn("bmp: missing TX OPEN message for peer %s",
- peer->host);
+ zlog_warn("bmp: missing TX OPEN message for peer %s", peer->host);
}
- if (bbpeer && bbpeer->open_rx)
- stream_put(s, bbpeer->open_rx, bbpeer->open_rx_len);
+ if (rx && rx->data)
+ stream_put(s, rx->data, rx->len);
else {
stream_put(s, dummy_open, sizeof(dummy_open));
- zlog_warn("bmp: missing RX OPEN message for peer %s",
- peer->host);
+ zlog_warn("bmp: missing RX OPEN message for peer %s", peer->host);
}
if (peer->desc)
@@ -814,11 +919,9 @@ static int bmp_mirror_packet(struct peer_connection *connection, uint8_t type, b
if (type == BGP_MSG_OPEN) {
struct bmp_bgp_peer *bbpeer = bmp_bgp_peer_get(peer);
- XFREE(MTYPE_BMP_OPEN, bbpeer->open_rx);
-
- bbpeer->open_rx_len = size;
- bbpeer->open_rx = XMALLOC(MTYPE_BMP_OPEN, size);
- memcpy(bbpeer->open_rx, packet->data, size);
+ if (bbpeer)
+ bmp_open_save_dir(bbpeer->open_rx, peer, connection, packet, size,
+ "received");
}
/*
@@ -962,18 +1065,16 @@ static int bmp_outgoing_packet(struct peer_connection *connection, uint8_t type,
struct stream *packet)
{
struct peer *peer = connection->peer;
+ struct bmp_bgp_peer *bbpeer;
- if (type == BGP_MSG_OPEN) {
- frrtrace(2, frr_bgp, bmp_update_saved_open, peer, packet);
-
- struct bmp_bgp_peer *bbpeer = bmp_bgp_peer_get(peer);
+ if (type != BGP_MSG_OPEN || !peer)
+ return 0;
- XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx);
+ frrtrace(2, frr_bgp, bmp_update_saved_open, peer, packet);
- bbpeer->open_tx_len = size;
- bbpeer->open_tx = XMALLOC(MTYPE_BMP_OPEN, size);
- memcpy(bbpeer->open_tx, packet->data, size);
- }
+ bbpeer = bmp_bgp_peer_get(peer);
+ if (bbpeer)
+ bmp_open_save_dir(bbpeer->open_tx, peer, connection, packet, size, "sent");
return 0;
}
@@ -1096,41 +1197,40 @@ static void bmp_adj_in_release(struct bmp_targets *bt, afi_t afi, safi_t safi)
static int bmp_peer_status_changed(struct peer_connection *connection)
{
struct peer *peer = connection->peer;
- struct bmp_bgp_peer *bbpeer, *bbdopp;
+ struct bmp_bgp_peer *bbpeer;
+ enum connection_direction dir;
frrtrace(1, frr_bgp, bmp_peer_status_changed, peer);
if (connection->status == Deleted) {
- bbpeer = bmp_bgp_peer_find(peer->qobj_node.nid);
- if (bbpeer) {
- XFREE(MTYPE_BMP_OPEN, bbpeer->open_rx);
- XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx);
+ if (connection->peer != peer)
+ return 0;
+
+ bbpeer = bmp_bgp_peer_find(peer);
+ /* Incoming and outgoing sockets for one neighbor share this
+ * entry. Drop it only when the last of those peers is gone.
+ */
+ if (bbpeer && !bmp_bgp_peer_addr_in_use(peer)) {
+ bmp_bgp_peer_clear_opens(bbpeer);
bmp_peerh_del(&bmp_peerh, bbpeer);
XFREE(MTYPE_BMP_PEER, bbpeer);
}
return 0;
}
- /* Check if this peer just went to Established */
- if ((connection->ostatus != OpenConfirm) || !(peer_established(connection)))
+ /* Check if this connection just went to Established */
+ if (connection->ostatus != OpenConfirm || !peer_established(connection))
return 0;
- if (peer->doppelganger &&
- (peer->doppelganger->connection->status != Deleted)) {
+ /* OPENs were stored under the direction of the socket that carried
+ * them. Remember which socket won so later Peer Up notifications can
+ * find them after dir becomes ESTABLISHED.
+ */
+ dir = bgp_peer_get_connection_direction(connection);
+ if (dir == CONNECTION_INCOMING || dir == CONNECTION_OUTGOING) {
bbpeer = bmp_bgp_peer_get(peer);
- bbdopp = bmp_bgp_peer_find(peer->doppelganger->qobj_node.nid);
- if (bbdopp) {
- XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx);
- XFREE(MTYPE_BMP_OPEN, bbpeer->open_rx);
-
- bbpeer->open_tx = bbdopp->open_tx;
- bbpeer->open_tx_len = bbdopp->open_tx_len;
- bbpeer->open_rx = bbdopp->open_rx;
- bbpeer->open_rx_len = bbdopp->open_rx_len;
-
- bmp_peerh_del(&bmp_peerh, bbdopp);
- XFREE(MTYPE_BMP_PEER, bbdopp);
- }
+ if (bbpeer)
+ bbpeer->established_dir = dir;
}
bmp_send_all_bgp(peer, false);
@@ -1143,13 +1243,9 @@ static int bmp_peer_backward(struct peer *peer)
frrtrace(1, frr_bgp, bmp_peer_backward_transition, peer);
- bbpeer = bmp_bgp_peer_find(peer->qobj_node.nid);
- if (bbpeer) {
- XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx);
- bbpeer->open_tx_len = 0;
- XFREE(MTYPE_BMP_OPEN, bbpeer->open_rx);
- bbpeer->open_rx_len = 0;
- }
+ bbpeer = bmp_bgp_peer_find(peer);
+ if (bbpeer)
+ bmp_bgp_peer_clear_established(bbpeer);
bmp_send_all_bgp(peer, true);
return 0;
@@ -2411,24 +2507,13 @@ static void bmp_bgp_peer_vrf(struct bmp_bgp_peer *bbpeer, struct bgp *bgp)
s = bgp_open_make(peer->connection, send_holdtime, local_as, &peer->local_id);
open_len = stream_get_endp(s);
-
- bbpeer->open_rx_len = open_len;
- if (bbpeer->open_rx)
- XFREE(MTYPE_BMP_OPEN, bbpeer->open_rx);
- bbpeer->open_rx = XMALLOC(MTYPE_BMP_OPEN, open_len);
- memcpy(bbpeer->open_rx, s->data, open_len);
-
+ bmp_open_save(&bbpeer->open_rx[UNKNOWN], s, open_len);
stream_free(s);
/* rfc9069#section-5.2 : Received OPEN Message: Repeat of the same sent OPEN message */
s = bgp_open_make(peer->connection, send_holdtime, local_as, &peer->local_id);
open_len = stream_get_endp(s);
- bbpeer->open_tx_len = open_len;
- if (bbpeer->open_tx)
- XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx);
- bbpeer->open_tx = XMALLOC(MTYPE_BMP_OPEN, open_len);
- memcpy(bbpeer->open_tx, s->data, open_len);
-
+ bmp_open_save(&bbpeer->open_tx[UNKNOWN], s, open_len);
stream_free(s);
}
@@ -2462,12 +2547,12 @@ bool bmp_bgp_update_vrf_status(enum bmp_vrf_state *vrf_state, struct bgp *bgp,
peer = bgp->peer_self;
if (*vrf_state == vrf_state_up) {
bbpeer = bmp_bgp_peer_get(peer);
- bmp_bgp_peer_vrf(bbpeer, bgp);
+ if (bbpeer)
+ bmp_bgp_peer_vrf(bbpeer, bgp);
} else {
- bbpeer = bmp_bgp_peer_find(peer->qobj_node.nid);
+ bbpeer = bmp_bgp_peer_find(peer);
if (bbpeer) {
- XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx);
- XFREE(MTYPE_BMP_OPEN, bbpeer->open_rx);
+ bmp_bgp_peer_clear_opens(bbpeer);
bmp_peerh_del(&bmp_peerh, bbpeer);
XFREE(MTYPE_BMP_PEER, bbpeer);
}
@@ -2477,27 +2562,117 @@ bool bmp_bgp_update_vrf_status(enum bmp_vrf_state *vrf_state, struct bgp *bgp,
return changed;
}
-static struct bmp_bgp_peer *bmp_bgp_peer_find(uint64_t peerid)
+/* Copy the neighbor address and leave the port zero. The port is not a
+ * BMP peer discriminator: incoming and outgoing sockets differ only there.
+ */
+static void bmp_remote_addr(union sockunion *dst, const union sockunion *src)
{
- struct bmp_bgp_peer dummy = { .peerid = peerid };
+ memset(dst, 0, sizeof(*dst));
+ if (!src)
+ return;
+
+ switch (src->sa.sa_family) {
+ case AF_INET:
+ dst->sa.sa_family = AF_INET;
+ dst->sin.sin_addr = src->sin.sin_addr;
+ break;
+ case AF_INET6:
+ dst->sa.sa_family = AF_INET6;
+ dst->sin6.sin6_addr = src->sin6.sin6_addr;
+ dst->sin6.sin6_scope_id = src->sin6.sin6_scope_id;
+ break;
+ default:
+ break;
+ }
+}
+
+/* Key is the BGP instance's vrf_id plus the neighbor address in su_remote,
+ * without the port. Loc-RIB has no su_remote, so its address stays zero.
+ * Returns false when a real neighbor has no su_remote yet.
+ */
+static bool bmp_bgp_peer_set_key(struct bmp_bgp_peer *bbpeer, struct peer *peer)
+{
+ memset(bbpeer, 0, sizeof(*bbpeer));
+ bbpeer->vrf_id = peer->bgp->vrf_id;
+
+ if (peer->bgp->peer_self == peer)
+ return true;
+
+ if (!peer->connection->su_remote)
+ return false;
+
+ bmp_remote_addr(&bbpeer->remote, peer->connection->su_remote);
+
+ return true;
+}
+
+static struct bmp_bgp_peer *bmp_bgp_peer_find(struct peer *peer)
+{
+ struct bmp_bgp_peer dummy;
+
+ if (!bmp_bgp_peer_set_key(&dummy, peer))
+ return NULL;
+
return bmp_peerh_find(&bmp_peerh, &dummy);
}
static struct bmp_bgp_peer *bmp_bgp_peer_get(struct peer *peer)
{
- struct bmp_bgp_peer *bbpeer;
+ struct bmp_bgp_peer *bbpeer, dummy;
- bbpeer = bmp_bgp_peer_find(peer->qobj_node.nid);
+ if (!bmp_bgp_peer_set_key(&dummy, peer))
+ return NULL;
+
+ bbpeer = bmp_peerh_find(&bmp_peerh, &dummy);
if (bbpeer)
return bbpeer;
bbpeer = XCALLOC(MTYPE_BMP_PEER, sizeof(*bbpeer));
- bbpeer->peerid = peer->qobj_node.nid;
+ bbpeer->vrf_id = dummy.vrf_id;
+ bbpeer->remote = dummy.remote;
bmp_peerh_add(&bmp_peerh, bbpeer);
return bbpeer;
}
+/* True when some other still-live peer in this VRF has the same neighbor
+ * address. Dual TCP connections (the config peer and its doppelganger)
+ * share one bmp_bgp_peer and must not free it while either remains.
+ */
+static bool bmp_bgp_peer_addr_in_use(struct peer *peer)
+{
+ struct listnode *bnode, *node;
+ struct bgp *bgp;
+ struct peer *other;
+ union sockunion want, got;
+
+ if (peer->bgp->peer_self == peer)
+ return false;
+
+ if (!peer->connection->su_remote)
+ return false;
+
+ bmp_remote_addr(&want, peer->connection->su_remote);
+ for (ALL_LIST_ELEMENTS_RO(bm->bgp, bnode, bgp)) {
+ if (bgp->vrf_id != peer->bgp->vrf_id)
+ continue;
+
+ for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, other)) {
+ if (other == peer || !other->connection ||
+ other->connection->status == Deleted)
+ continue;
+ if (!other->connection->su_remote)
+ continue;
+
+ bmp_remote_addr(&got, other->connection->su_remote);
+ if (sockunion_cmp(&want, &got) == 0)
+ return true;
+ }
+ }
+
+ return false;
+}
+
static struct bmp_targets *bmp_targets_find1(struct bgp *bgp, const char *name)
{
struct bmp_bgp *bmpbgp = bmp_bgp_find(bgp);
diff --git a/bgpd/bgp_bmp.h b/bgpd/bgp_bmp.h
index 94e17276bb..70862db218 100644
--- a/bgpd/bgp_bmp.h
+++ b/bgpd/bgp_bmp.h
@@ -253,21 +253,29 @@ struct bmp_targets {
};
DECLARE_QOBJ_TYPE(bmp_targets);
-/* per struct peer * data. Lookup by peer->qobj_node.nid, created on demand,
- * deleted in peer_backward hook. */
+#define BMP_PEER_OPEN_MAX 3
+
PREDECL_HASH(bmp_peerh);
+struct bmp_saved_open {
+ uint8_t *data;
+ size_t len;
+};
+
struct bmp_bgp_peer {
struct bmp_peerh_item bpi;
- uint64_t peerid;
- /* struct peer *peer; */
+ /* VRF of the BGP instance, plus the neighbor address taken from
+ * su_remote. The port is not stored. Loc-RIB has no su_remote;
+ * its address is left zero.
+ */
+ vrf_id_t vrf_id;
+ union sockunion remote;
- uint8_t *open_rx;
- size_t open_rx_len;
+ struct bmp_saved_open open_rx[BMP_PEER_OPEN_MAX];
+ struct bmp_saved_open open_tx[BMP_PEER_OPEN_MAX];
- uint8_t *open_tx;
- size_t open_tx_len;
+ enum connection_direction established_dir;
};
/* per struct bgp * data */
diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c
index 36f11a0019..81091a1abc 100644
--- a/bgpd/bgp_fsm.c
+++ b/bgpd/bgp_fsm.c
@@ -2103,12 +2103,11 @@ void bgp_fsm_change_status(struct peer_connection *connection,
/* Save event that caused status change. */
peer->last_major_event = peer->cur_event;
+ hook_call(peer_status_changed, connection);
+
if (status == Established)
connection->dir = ESTABLISHED;
- /* Operations after status change */
- hook_call(peer_status_changed, connection);
-
/* If max-med processing is applicable, do the necessary. */
if (status == Established) {
if (bgp_maxmed_onstartup_configured(bgp) && bgp_maxmed_onstartup_applicable(bgp))