summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/docker-daily-master.yml7
-rw-r--r--.github/workflows/docker-stable.yml7
-rw-r--r--alpine/APKBUILD.in2
-rw-r--r--bgpd/bgp_nexthop.c470
-rw-r--r--bgpd/bgp_route.c2
-rw-r--r--bgpd/subdir.am1
-rw-r--r--doc/developer/cli.rst5
-rw-r--r--doc/developer/subdir.am1
-rw-r--r--doc/developer/workflow.rst18
-rw-r--r--doc/user/bgp.rst19
-rw-r--r--doc/user/subdir.am2
-rw-r--r--lib/command.c4
-rw-r--r--lib/elf_py.c2
-rw-r--r--lib/xref.h13
-rw-r--r--ospfd/ospf_packet.c7
-rw-r--r--zebra/dplane_fpm_nl.c9
-rw-r--r--zebra/rt_netlink.c9
-rw-r--r--zebra/rt_netlink.h2
-rw-r--r--zebra/zebra_vxlan.c6
-rw-r--r--zebra/zserv.c68
-rw-r--r--zebra/zserv.h15
21 files changed, 515 insertions, 154 deletions
diff --git a/.github/workflows/docker-daily-master.yml b/.github/workflows/docker-daily-master.yml
index 5b521a870d..90bd23678d 100644
--- a/.github/workflows/docker-daily-master.yml
+++ b/.github/workflows/docker-daily-master.yml
@@ -38,8 +38,9 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_TOKEN }}
+ registry: quay.io
+ username: ${{ secrets.QUAY_USERNAME }}
+ password: ${{ secrets.QUAY_ROBOT_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
@@ -47,6 +48,6 @@ jobs:
context: .
file: ./docker/alpine/Dockerfile
push: true
- tags: ${{ secrets.DOCKERHUB_USERNAME }}/frr:master
+ tags: ${{ secrets.QUAY_USERNAME }}/frr:master
build-args: PKGVER=${{ steps.vars.outputs.date }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
diff --git a/.github/workflows/docker-stable.yml b/.github/workflows/docker-stable.yml
index 1e069a722e..ad69e0e415 100644
--- a/.github/workflows/docker-stable.yml
+++ b/.github/workflows/docker-stable.yml
@@ -39,8 +39,9 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_TOKEN }}
+ registry: quay.io
+ username: ${{ secrets.QUAY_USERNAME }}
+ password: ${{ secrets.QUAY_ROBOT_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
@@ -48,6 +49,6 @@ jobs:
context: .
file: ./docker/alpine/Dockerfile
push: true
- tags: ${{ secrets.DOCKERHUB_USERNAME }}/frr:v${{ steps.vars.outputs.frr_version }}
+ tags: ${{ secrets.QUAY_USERNAME }}/frr:v${{ steps.vars.outputs.frr_version }}
build-args: PKGVER=${{ steps.vars.outputs.date }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
diff --git a/alpine/APKBUILD.in b/alpine/APKBUILD.in
index 3aad9549b5..fef7a61ccc 100644
--- a/alpine/APKBUILD.in
+++ b/alpine/APKBUILD.in
@@ -10,7 +10,7 @@ depends="json-c c-ares iproute2 python3 bash"
makedepends="ncurses-dev net-snmp-dev gawk texinfo perl
acct autoconf automake bash binutils bison bsd-compat-headers build-base
c-ares c-ares-dev ca-certificates cryptsetup-libs curl device-mapper-libs
- expat fakeroot flex fortify-headers gdbm git gmp isl json-c-dev kmod
+ expat fakeroot flex fortify-headers gdbm git gmp json-c-dev kmod
lddtree libacl libatomic libattr libblkid libburn libbz2 libc-dev
libcap-dev libcurl libedit libffi libgcc libgomp libisoburn libisofs
libltdl libressl libssh2 libstdc++ libtool libuuid
diff --git a/bgpd/bgp_nexthop.c b/bgpd/bgp_nexthop.c
index 6bbdbdc1a9..d9822ee974 100644
--- a/bgpd/bgp_nexthop.c
+++ b/bgpd/bgp_nexthop.c
@@ -23,6 +23,7 @@
#include "command.h"
#include "thread.h"
#include "prefix.h"
+#include "lib/json.h"
#include "zclient.h"
#include "stream.h"
#include "network.h"
@@ -731,17 +732,68 @@ bool bgp_subgrp_multiaccess_check_v4(struct in_addr nexthop,
return false;
}
+static void bgp_show_bgp_path_info_flags(uint32_t flags, json_object *json)
+{
+ json_object *json_flags = NULL;
+
+ if (!json)
+ return;
+
+ json_flags = json_object_new_object();
+ json_object_boolean_add(json_flags, "igpChanged",
+ CHECK_FLAG(flags, BGP_PATH_IGP_CHANGED));
+ json_object_boolean_add(json_flags, "damped",
+ CHECK_FLAG(flags, BGP_PATH_DAMPED));
+ json_object_boolean_add(json_flags, "history",
+ CHECK_FLAG(flags, BGP_PATH_HISTORY));
+ json_object_boolean_add(json_flags, "bestpath",
+ CHECK_FLAG(flags, BGP_PATH_SELECTED));
+ json_object_boolean_add(json_flags, "valid",
+ CHECK_FLAG(flags, BGP_PATH_VALID));
+ json_object_boolean_add(json_flags, "attrChanged",
+ CHECK_FLAG(flags, BGP_PATH_ATTR_CHANGED));
+ json_object_boolean_add(json_flags, "deterministicMedCheck",
+ CHECK_FLAG(flags, BGP_PATH_DMED_CHECK));
+ json_object_boolean_add(json_flags, "deterministicMedSelected",
+ CHECK_FLAG(flags, BGP_PATH_DMED_SELECTED));
+ json_object_boolean_add(json_flags, "stale",
+ CHECK_FLAG(flags, BGP_PATH_STALE));
+ json_object_boolean_add(json_flags, "removed",
+ CHECK_FLAG(flags, BGP_PATH_REMOVED));
+ json_object_boolean_add(json_flags, "counted",
+ CHECK_FLAG(flags, BGP_PATH_COUNTED));
+ json_object_boolean_add(json_flags, "multipath",
+ CHECK_FLAG(flags, BGP_PATH_MULTIPATH));
+ json_object_boolean_add(json_flags, "multipathChanged",
+ CHECK_FLAG(flags, BGP_PATH_MULTIPATH_CHG));
+ json_object_boolean_add(json_flags, "ribAttributeChanged",
+ CHECK_FLAG(flags, BGP_PATH_RIB_ATTR_CHG));
+ json_object_boolean_add(json_flags, "nexthopSelf",
+ CHECK_FLAG(flags, BGP_PATH_ANNC_NH_SELF));
+ json_object_boolean_add(json_flags, "linkBandwidthChanged",
+ CHECK_FLAG(flags, BGP_PATH_LINK_BW_CHG));
+ json_object_boolean_add(json_flags, "acceptOwn",
+ CHECK_FLAG(flags, BGP_PATH_ACCEPT_OWN));
+ json_object_object_add(json, "flags", json_flags);
+}
+
static void bgp_show_nexthop_paths(struct vty *vty, struct bgp *bgp,
- struct bgp_nexthop_cache *bnc)
+ struct bgp_nexthop_cache *bnc,
+ json_object *json)
{
struct bgp_dest *dest;
struct bgp_path_info *path;
- int afi;
+ afi_t afi;
safi_t safi;
struct bgp_table *table;
struct bgp *bgp_path;
+ json_object *paths = NULL;
+ json_object *json_path = NULL;
- vty_out(vty, " Paths:\n");
+ if (json)
+ paths = json_object_new_array();
+ else
+ vty_out(vty, " Paths:\n");
LIST_FOREACH (path, &(bnc->paths), nh_thread) {
dest = path->net;
assert(dest && bgp_dest_table(dest));
@@ -750,6 +802,25 @@ static void bgp_show_nexthop_paths(struct vty *vty, struct bgp *bgp,
safi = table->safi;
bgp_path = table->bgp;
+ if (json) {
+ json_path = json_object_new_object();
+ json_object_string_add(json_path, "afi", afi2str(afi));
+ json_object_string_add(json_path, "safi",
+ safi2str(safi));
+ json_object_string_addf(json_path, "prefix", "%pBD",
+ dest);
+ if (dest->pdest)
+ json_object_string_addf(
+ json_path, "rd", "%pRD",
+ (struct prefix_rd *)bgp_dest_get_prefix(
+ dest->pdest));
+ json_object_string_add(
+ json_path, "vrf",
+ vrf_id_to_name(bgp_path->vrf_id));
+ bgp_show_bgp_path_info_flags(path->flags, json_path);
+ json_object_array_add(paths, json_path);
+ continue;
+ }
if (dest->pdest)
vty_out(vty, " %d/%d %pBD RD %pRD %s flags 0x%x\n",
afi, safi, dest,
@@ -760,14 +831,86 @@ static void bgp_show_nexthop_paths(struct vty *vty, struct bgp *bgp,
vty_out(vty, " %d/%d %pBD %s flags 0x%x\n",
afi, safi, dest, bgp_path->name_pretty, path->flags);
}
+ if (json)
+ json_object_object_add(json, "paths", paths);
}
static void bgp_show_nexthops_detail(struct vty *vty, struct bgp *bgp,
- struct bgp_nexthop_cache *bnc)
+ struct bgp_nexthop_cache *bnc,
+ json_object *json)
{
struct nexthop *nexthop;
+ json_object *json_gates = NULL;
+ json_object *json_gate = NULL;
+ if (json)
+ json_gates = json_object_new_array();
for (nexthop = bnc->nexthop; nexthop; nexthop = nexthop->next) {
+ if (json) {
+ json_gate = json_object_new_object();
+ switch (nexthop->type) {
+ case NEXTHOP_TYPE_IPV6:
+ json_object_string_addf(json_gate, "ip", "%pI6",
+ &nexthop->gate.ipv6);
+ break;
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ json_object_string_addf(json_gate, "ip", "%pI6",
+ &nexthop->gate.ipv6);
+ json_object_string_add(
+ json_gate, "interfaceName",
+ ifindex2ifname(
+ bnc->ifindex ? bnc->ifindex
+ : nexthop->ifindex,
+ bgp->vrf_id));
+ break;
+ case NEXTHOP_TYPE_IPV4:
+ json_object_string_addf(json_gate, "ip", "%pI4",
+ &nexthop->gate.ipv4);
+ break;
+ case NEXTHOP_TYPE_IFINDEX:
+ json_object_string_add(
+ json_gate, "interfaceName",
+ ifindex2ifname(
+ bnc->ifindex ? bnc->ifindex
+ : nexthop->ifindex,
+ bgp->vrf_id));
+ break;
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ json_object_string_addf(json_gate, "ip", "%pI4",
+ &nexthop->gate.ipv4);
+ json_object_string_add(
+ json_gate, "interfaceName",
+ ifindex2ifname(
+ bnc->ifindex ? bnc->ifindex
+ : nexthop->ifindex,
+ bgp->vrf_id));
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ json_object_boolean_true_add(json_gate,
+ "unreachable");
+ switch (nexthop->bh_type) {
+ case BLACKHOLE_REJECT:
+ json_object_boolean_true_add(json_gate,
+ "reject");
+ break;
+ case BLACKHOLE_ADMINPROHIB:
+ json_object_boolean_true_add(
+ json_gate, "adminProhibited");
+ break;
+ case BLACKHOLE_NULL:
+ json_object_boolean_true_add(
+ json_gate, "blackhole");
+ break;
+ case BLACKHOLE_UNSPEC:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ json_object_array_add(json_gates, json_gate);
+ continue;
+ }
switch (nexthop->type) {
case NEXTHOP_TYPE_IPV6:
vty_out(vty, " gate %pI6\n", &nexthop->gate.ipv6);
@@ -803,97 +946,190 @@ static void bgp_show_nexthops_detail(struct vty *vty, struct bgp *bgp,
nexthop->type);
}
}
+ if (json)
+ json_object_object_add(json, "nexthops", json_gates);
}
static void bgp_show_nexthop(struct vty *vty, struct bgp *bgp,
- struct bgp_nexthop_cache *bnc,
- bool specific)
+ struct bgp_nexthop_cache *bnc, bool specific,
+ json_object *json)
{
char buf[PREFIX2STR_BUFFER];
time_t tbuf;
struct peer *peer;
+ json_object *json_last_update = NULL;
+ json_object *json_nexthop = NULL;
peer = (struct peer *)bnc->nht_info;
- if (bnc->srte_color)
- vty_out(vty, " SR-TE color %u -", bnc->srte_color);
+ if (json)
+ json_nexthop = json_object_new_object();
+ if (bnc->srte_color) {
+ if (json)
+ json_object_int_add(json_nexthop, "srteColor",
+ bnc->srte_color);
+ else
+ vty_out(vty, " SR-TE color %u -", bnc->srte_color);
+ }
+ inet_ntop(bnc->prefix.family, &bnc->prefix.u.prefix, buf, sizeof(buf));
if (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_VALID)) {
- vty_out(vty, " %s valid [IGP metric %d], #paths %d",
- inet_ntop(bnc->prefix.family, &bnc->prefix.u.prefix,
- buf, sizeof(buf)),
- bnc->metric, bnc->path_count);
- if (peer)
- vty_out(vty, ", peer %s", peer->host);
- if (bnc->is_evpn_gwip_nexthop)
- vty_out(vty, " EVPN Gateway IP");
- vty_out(vty, "\n");
- bgp_show_nexthops_detail(vty, bgp, bnc);
+ if (json) {
+ json_object_boolean_true_add(json_nexthop, "valid");
+ json_object_boolean_true_add(json_nexthop, "complete");
+ json_object_int_add(json_nexthop, "igpMetric",
+ bnc->metric);
+ json_object_int_add(json_nexthop, "pathCount",
+ bnc->path_count);
+ if (peer)
+ json_object_string_add(json_nexthop, "peer",
+ peer->host);
+ if (bnc->is_evpn_gwip_nexthop)
+ json_object_boolean_true_add(json_nexthop,
+ "isEvpnGatewayIp");
+ } else {
+ vty_out(vty, " %s valid [IGP metric %d], #paths %d",
+ buf, bnc->metric, bnc->path_count);
+ if (peer)
+ vty_out(vty, ", peer %s", peer->host);
+ if (bnc->is_evpn_gwip_nexthop)
+ vty_out(vty, " EVPN Gateway IP");
+ vty_out(vty, "\n");
+ }
+ bgp_show_nexthops_detail(vty, bgp, bnc, json_nexthop);
} else if (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_EVPN_INCOMPLETE)) {
- vty_out(vty,
- " %s overlay index unresolved [IGP metric %d], #paths %d",
- inet_ntop(bnc->prefix.family, &bnc->prefix.u.prefix,
- buf, sizeof(buf)),
- bnc->metric, bnc->path_count);
- if (bnc->is_evpn_gwip_nexthop)
- vty_out(vty, " EVPN Gateway IP");
- vty_out(vty, "\n");
- bgp_show_nexthops_detail(vty, bgp, bnc);
+ if (json) {
+ json_object_boolean_true_add(json_nexthop, "valid");
+ json_object_boolean_false_add(json_nexthop, "complete");
+ json_object_int_add(json_nexthop, "igpMetric",
+ bnc->metric);
+ json_object_int_add(json_nexthop, "pathCount",
+ bnc->path_count);
+ if (bnc->is_evpn_gwip_nexthop)
+ json_object_boolean_true_add(json_nexthop,
+ "isEvpnGatewayIp");
+ } else {
+ vty_out(vty,
+ " %s overlay index unresolved [IGP metric %d], #paths %d",
+ buf, bnc->metric, bnc->path_count);
+ if (bnc->is_evpn_gwip_nexthop)
+ vty_out(vty, " EVPN Gateway IP");
+ vty_out(vty, "\n");
+ }
+ bgp_show_nexthops_detail(vty, bgp, bnc, json_nexthop);
} else {
- vty_out(vty, " %s invalid, #paths %d",
- inet_ntop(bnc->prefix.family, &bnc->prefix.u.prefix,
- buf, sizeof(buf)),
- bnc->path_count);
- if (peer)
- vty_out(vty, ", peer %s", peer->host);
- if (bnc->is_evpn_gwip_nexthop)
- vty_out(vty, " EVPN Gateway IP");
- vty_out(vty, "\n");
- if (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED))
- vty_out(vty, " Must be Connected\n");
- if (!CHECK_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED))
- vty_out(vty, " Is not Registered\n");
+ if (json) {
+ json_object_boolean_false_add(json_nexthop, "valid");
+ json_object_boolean_false_add(json_nexthop, "complete");
+ json_object_int_add(json_nexthop, "pathCount",
+ bnc->path_count);
+ if (peer)
+ json_object_string_add(json_nexthop, "peer",
+ peer->host);
+ if (bnc->is_evpn_gwip_nexthop)
+ json_object_boolean_true_add(json_nexthop,
+ "isEvpnGatewayIp");
+ if (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED))
+ json_object_boolean_false_add(json_nexthop,
+ "isConnected");
+ if (!CHECK_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED))
+ json_object_boolean_false_add(json_nexthop,
+ "isRegistered");
+ } else {
+ vty_out(vty, " %s invalid, #paths %d", buf,
+ bnc->path_count);
+ if (peer)
+ vty_out(vty, ", peer %s", peer->host);
+ if (bnc->is_evpn_gwip_nexthop)
+ vty_out(vty, " EVPN Gateway IP");
+ vty_out(vty, "\n");
+ if (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED))
+ vty_out(vty, " Must be Connected\n");
+ if (!CHECK_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED))
+ vty_out(vty, " Is not Registered\n");
+ }
}
tbuf = time(NULL) - (monotime(NULL) - bnc->last_update);
- vty_out(vty, " Last update: %s", ctime(&tbuf));
+ if (json) {
+ if (!specific) {
+ json_last_update = json_object_new_object();
+ json_object_int_add(json_last_update, "epoch", tbuf);
+ json_object_string_add(json_last_update, "string",
+ ctime(&tbuf));
+ json_object_object_add(json_nexthop, "lastUpdate",
+ json_last_update);
+ } else {
+ json_object_int_add(json_nexthop, "lastUpdate", tbuf);
+ }
+ } else {
+ vty_out(vty, " Last update: %s", ctime(&tbuf));
+ }
/* show paths dependent on nexthop, if needed. */
if (specific)
- bgp_show_nexthop_paths(vty, bgp, bnc);
+ bgp_show_nexthop_paths(vty, bgp, bnc, json_nexthop);
+ if (json)
+ json_object_object_add(json, buf, json_nexthop);
}
static void bgp_show_nexthops(struct vty *vty, struct bgp *bgp,
- bool import_table)
+ bool import_table, json_object *json, afi_t afi,
+ bool detail)
{
struct bgp_nexthop_cache *bnc;
- afi_t afi;
struct bgp_nexthop_cache_head(*tree)[AFI_MAX];
+ json_object *json_afi = NULL;
+ bool found = false;
- if (import_table)
- vty_out(vty, "Current BGP import check cache:\n");
- else
- vty_out(vty, "Current BGP nexthop cache:\n");
+ if (!json) {
+ if (import_table)
+ vty_out(vty, "Current BGP import check cache:\n");
+ else
+ vty_out(vty, "Current BGP nexthop cache:\n");
+ }
if (import_table)
tree = &bgp->import_check_table;
else
tree = &bgp->nexthop_cache_table;
+
+ if (afi == AFI_IP || afi == AFI_IP6) {
+ if (json)
+ json_afi = json_object_new_object();
+ frr_each (bgp_nexthop_cache, &(*tree)[afi], bnc) {
+ bgp_show_nexthop(vty, bgp, bnc, detail, json_afi);
+ found = true;
+ }
+ if (found && json)
+ json_object_object_add(
+ json, (afi == AFI_IP) ? "ipv4" : "ipv6",
+ json_afi);
+ return;
+ }
+
for (afi = AFI_IP; afi < AFI_MAX; afi++) {
+ if (json && (afi == AFI_IP || afi == AFI_IP6))
+ json_afi = json_object_new_object();
frr_each (bgp_nexthop_cache, &(*tree)[afi], bnc)
- bgp_show_nexthop(vty, bgp, bnc, false);
+ bgp_show_nexthop(vty, bgp, bnc, detail, json_afi);
+ if (json && (afi == AFI_IP || afi == AFI_IP6))
+ json_object_object_add(
+ json, (afi == AFI_IP) ? "ipv4" : "ipv6",
+ json_afi);
}
}
static int show_ip_bgp_nexthop_table(struct vty *vty, const char *name,
- const char *nhopip_str,
- bool import_table)
+ const char *nhopip_str, bool import_table,
+ json_object *json, afi_t afi, bool detail)
{
struct bgp *bgp;
- if (name)
+ if (name && !strmatch(name, VRF_DEFAULT_NAME))
bgp = bgp_lookup_by_name(name);
else
bgp = bgp_get_default();
if (!bgp) {
- vty_out(vty, "%% No such BGP instance exist\n");
+ if (!json)
+ vty_out(vty, "%% No such BGP instance exist\n");
return CMD_WARNING;
}
@@ -902,46 +1138,69 @@ static int show_ip_bgp_nexthop_table(struct vty *vty, const char *name,
struct bgp_nexthop_cache_head (*tree)[AFI_MAX];
struct bgp_nexthop_cache *bnc;
bool found = false;
+ json_object *json_afi = NULL;
if (!str2prefix(nhopip_str, &nhop)) {
- vty_out(vty, "nexthop address is malformed\n");
+ if (!json)
+ vty_out(vty, "nexthop address is malformed\n");
return CMD_WARNING;
}
tree = import_table ? &bgp->import_check_table
: &bgp->nexthop_cache_table;
+ if (json)
+ json_afi = json_object_new_object();
frr_each (bgp_nexthop_cache, &(*tree)[family2afi(nhop.family)],
bnc) {
if (prefix_cmp(&bnc->prefix, &nhop))
continue;
- bgp_show_nexthop(vty, bgp, bnc, true);
+ bgp_show_nexthop(vty, bgp, bnc, true, json_afi);
found = true;
}
- if (!found)
+ if (json)
+ json_object_object_add(
+ json,
+ (family2afi(nhop.family) == AFI_IP) ? "ipv4"
+ : "ipv6",
+ json_afi);
+ if (!found && !json)
vty_out(vty, "nexthop %s does not have entry\n",
nhopip_str);
} else
- bgp_show_nexthops(vty, bgp, import_table);
+ bgp_show_nexthops(vty, bgp, import_table, json, afi, detail);
return CMD_SUCCESS;
}
-static void bgp_show_all_instances_nexthops_vty(struct vty *vty)
+static void bgp_show_all_instances_nexthops_vty(struct vty *vty,
+ json_object *json, afi_t afi,
+ bool detail)
{
struct listnode *node, *nnode;
struct bgp *bgp;
+ const char *inst_name;
+ json_object *json_instance = NULL;
for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) {
- vty_out(vty, "\nInstance %s:\n",
- (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)
- ? VRF_DEFAULT_NAME
- : bgp->name);
- bgp_show_nexthops(vty, bgp, false);
+ inst_name = (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)
+ ? VRF_DEFAULT_NAME
+ : bgp->name;
+ if (json)
+ json_instance = json_object_new_object();
+ else
+ vty_out(vty, "\nInstance %s:\n", inst_name);
+
+ bgp_show_nexthops(vty, bgp, false, json_instance, afi, detail);
+
+ if (json)
+ json_object_object_add(json, inst_name, json_instance);
}
}
-DEFUN (show_ip_bgp_nexthop,
+#include "bgpd/bgp_nexthop_clippy.c"
+
+DEFPY (show_ip_bgp_nexthop,
show_ip_bgp_nexthop_cmd,
- "show [ip] bgp [<view|vrf> VIEWVRFNAME] nexthop [<A.B.C.D|X:X::X:X>] [detail]",
+ "show [ip] bgp [<view|vrf> VIEWVRFNAME$vrf] nexthop [<A.B.C.D|X:X::X:X>$nhop] [<ipv4$afi [A.B.C.D$nhop]|ipv6$afi [X:X::X:X$nhop]>] [detail$detail] [json$uj]",
SHOW_STR
IP_STR
BGP_STR
@@ -949,54 +1208,85 @@ DEFUN (show_ip_bgp_nexthop,
"BGP nexthop table\n"
"IPv4 nexthop address\n"
"IPv6 nexthop address\n"
- "Show detailed information\n")
+ "BGP nexthop IPv4 table\n"
+ "IPv4 nexthop address\n"
+ "BGP nexthop IPv6 table\n"
+ "IPv6 nexthop address\n"
+ "Show detailed information\n"
+ JSON_STR)
{
- int idx = 0;
- int nh_idx = 0;
- char *vrf = NULL;
- char *nhop_ip = NULL;
+ int rc = 0;
+ json_object *json = NULL;
+ afi_t afiz = AFI_UNSPEC;
+
+ if (uj)
+ json = json_object_new_object();
- if (argv_find(argv, argc, "view", &idx)
- || argv_find(argv, argc, "vrf", &idx))
- vrf = argv[++idx]->arg;
+ if (afi)
+ afiz = bgp_vty_afi_from_str(afi);
- if (argv_find(argv, argc, "A.B.C.D", &nh_idx)
- || argv_find(argv, argc, "X:X::X:X", &nh_idx))
- nhop_ip = argv[nh_idx]->arg;
+ rc = show_ip_bgp_nexthop_table(vty, vrf, nhop_str, false, json, afiz,
+ detail);
- return show_ip_bgp_nexthop_table(vty, vrf, nhop_ip, false);
+ if (uj)
+ vty_json(vty, json);
+
+ return rc;
}
-DEFUN (show_ip_bgp_import_check,
+DEFPY (show_ip_bgp_import_check,
show_ip_bgp_import_check_cmd,
- "show [ip] bgp [<view|vrf> VIEWVRFNAME] import-check-table [detail]",
+ "show [ip] bgp [<view|vrf> VIEWVRFNAME$vrf] import-check-table [detail$detail] [json$uj]",
SHOW_STR
IP_STR
BGP_STR
BGP_INSTANCE_HELP_STR
"BGP import check table\n"
- "Show detailed information\n")
+ "Show detailed information\n"
+ JSON_STR)
{
- int idx = 0;
- char *vrf = NULL;
+ int rc = 0;
+ json_object *json = NULL;
+
+ if (uj)
+ json = json_object_new_object();
+
+ rc = show_ip_bgp_nexthop_table(vty, vrf, NULL, true, json, AFI_UNSPEC,
+ detail);
- if (argv_find(argv, argc, "view", &idx)
- || argv_find(argv, argc, "vrf", &idx))
- vrf = argv[++idx]->arg;
+ if (uj)
+ vty_json(vty, json);
- return show_ip_bgp_nexthop_table(vty, vrf, NULL, true);
+ return rc;
}
-DEFUN (show_ip_bgp_instance_all_nexthop,
+DEFPY (show_ip_bgp_instance_all_nexthop,
show_ip_bgp_instance_all_nexthop_cmd,
- "show [ip] bgp <view|vrf> all nexthop",
+ "show [ip] bgp <view|vrf> all nexthop [<ipv4|ipv6>$afi] [detail$detail] [json$uj]",
SHOW_STR
IP_STR
BGP_STR
BGP_INSTANCE_ALL_HELP_STR
- "BGP nexthop table\n")
+ "BGP nexthop table\n"
+ "BGP IPv4 nexthop table\n"
+ "BGP IPv6 nexthop table\n"
+ "Show detailed information\n"
+ JSON_STR)
{
- bgp_show_all_instances_nexthops_vty(vty);
+ json_object *json = NULL;
+ afi_t afiz = AFI_UNSPEC;
+
+ if (uj)
+ json = json_object_new_object();
+
+ if (afi)
+ afiz = bgp_vty_afi_from_str(afi);
+
+ bgp_show_all_instances_nexthops_vty(vty, json, afiz, detail);
+
+ if (uj)
+ vty_json(vty, json);
+
return CMD_SUCCESS;
}
diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c
index 4bb77b5952..0ef939875a 100644
--- a/bgpd/bgp_route.c
+++ b/bgpd/bgp_route.c
@@ -394,7 +394,7 @@ static int bgp_dest_set_defer_flag(struct bgp_dest *dest, bool delete)
void bgp_path_info_add_with_caller(const char *name, struct bgp_dest *dest,
struct bgp_path_info *pi)
{
- frrtrace(2, frr_bgp, bgp_path_info_add, dest, pi, name);
+ frrtrace(3, frr_bgp, bgp_path_info_add, dest, pi, name);
struct bgp_path_info *top;
top = bgp_dest_get_bgp_path_info(dest);
diff --git a/bgpd/subdir.am b/bgpd/subdir.am
index 88f53da35e..c1f5a61438 100644
--- a/bgpd/subdir.am
+++ b/bgpd/subdir.am
@@ -215,6 +215,7 @@ clippy_scan += \
bgpd/bgp_routemap.c \
bgpd/bgp_rpki.c \
bgpd/bgp_vty.c \
+ bgpd/bgp_nexthop.c \
# end
nodist_bgpd_bgpd_SOURCES = \
diff --git a/doc/developer/cli.rst b/doc/developer/cli.rst
index d51f06d118..5d1dda06df 100644
--- a/doc/developer/cli.rst
+++ b/doc/developer/cli.rst
@@ -106,6 +106,11 @@ convention. Please do not scatter individual CLI commands in the middle of
source files; instead expose the necessary functions in a header and place the
command definition in a ``*_vty.[ch]`` file.
+.. note::
+
+ Please see :ref:`cli-workflow` for requirements when creating CLI commands
+ (e.g., JSON structure and formatting).
+
Definition Grammar
^^^^^^^^^^^^^^^^^^
FRR uses its own grammar for defining CLI commands. The grammar draws from
diff --git a/doc/developer/subdir.am b/doc/developer/subdir.am
index 9cf14a1966..b4c752a473 100644
--- a/doc/developer/subdir.am
+++ b/doc/developer/subdir.am
@@ -71,6 +71,7 @@ EXTRA_DIST += \
doc/developer/draft-zebra-00.ms \
doc/developer/ldpd-basic-test-setup.md \
doc/developer/release-announcement-template.md \
+ doc/developer/_static/overrides.css \
# end
DEVBUILD = doc/developer/_build
diff --git a/doc/developer/workflow.rst b/doc/developer/workflow.rst
index b0d320b5ea..06a2ccbc0a 100644
--- a/doc/developer/workflow.rst
+++ b/doc/developer/workflow.rst
@@ -1308,6 +1308,8 @@ is to correctly set up `LD_LIBRARY_PATH` so that libraries from the build tree
are used. (On some systems, `libtool` is also available from PATH, but this is
not always the case.)
+.. _cli-workflow:
+
CLI changes
-----------
@@ -1374,9 +1376,23 @@ the development mailing list / public Slack instance.
JSON Output
^^^^^^^^^^^
-* All JSON keys are to be camelCased, with no spaces
+New JSON output in FRR needs to be backed by schema, in particular a YANG model.
+When adding new JSON, first search for an existing YANG model, either in FRR or
+a standard model (e.g., IETF) and use that model as the basis for any JSON
+structure and *especially* for key names and canonical values formats.
+
+If no YANG model exists to support the JSON then an FRR YANG model needs to be
+added to or created to support the JSON format.
+
+* All JSON keys are to be ``camelCased``, with no spaces. YANG modules almost
+ always use ``kebab-case`` (i.e., all lower case with hyphens to separate
+ words), so these identifiers need to be mapped to ``camelCase`` by removing
+ the hyphen (or symbol) and capitalizing the following letter, for
+ example "router-id" becomes "routerId"
* Commands which output JSON should produce ``{}`` if they have nothing to
display
+* In general JSON commands include a ``json`` keyword typically at the end of
+ the CLI command (e.g., ``show ip ospf json``)
Use of const
^^^^^^^^^^^^
diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst
index f751eb3a75..0c51ce2d21 100644
--- a/doc/user/bgp.rst
+++ b/doc/user/bgp.rst
@@ -4492,6 +4492,25 @@ Displaying Update Group Information
Display Information about update-group events in FRR.
+Displaying Nexthop Information
+--------------------
+.. clicmd:: show [ip] bgp [<view|vrf> VIEWVRFNAME] nexthop ipv4 [A.B.C.D] [detail] [json]
+
+.. clicmd:: show [ip] bgp [<view|vrf> VIEWVRFNAME] nexthop ipv6 [X:X::X:X] [detail] [json]
+
+.. clicmd:: show [ip] bgp [<view|vrf> VIEWVRFNAME] nexthop [<A.B.C.D|X:X::X:X>] [detail] [json]
+
+.. clicmd:: show [ip] bgp <view|vrf> all nexthop [json]
+
+ Display information about nexthops to bgp neighbors. If a certain nexthop is
+ specified, also provides information about paths associated with the nexthop.
+ With detail option provides information about gates of each nexthop.
+
+.. clicmd:: show [ip] bgp [<view|vrf> VIEWVRFNAME] import-check-table [detail] [json]
+
+ Display information about nexthops from table that is used to check network's
+ existence in the rib for network statements.
+
Segment-Routing IPv6
--------------------
diff --git a/doc/user/subdir.am b/doc/user/subdir.am
index e07c9b6dc3..706e1ea449 100644
--- a/doc/user/subdir.am
+++ b/doc/user/subdir.am
@@ -58,6 +58,8 @@ user_RSTFILES = \
EXTRA_DIST += \
$(user_RSTFILES) \
doc/user/Useful_Sysctl_Settings.md \
+ doc/user/_static/overrides.css \
+ doc/user/_static/overrides.js \
# end
USERBUILD = doc/user/_build
diff --git a/lib/command.c b/lib/command.c
index 1fae32a04a..6d023142ab 100644
--- a/lib/command.c
+++ b/lib/command.c
@@ -502,7 +502,7 @@ static int config_write_host(struct vty *vty)
else if (cputime_threshold != 5000000)
#endif
vty_out(vty, "service cputime-warning %lu\n",
- cputime_threshold);
+ cputime_threshold / 1000);
if (!walltime_threshold)
vty_out(vty, "no service walltime-warning\n");
@@ -512,7 +512,7 @@ static int config_write_host(struct vty *vty)
else if (walltime_threshold != 5000000)
#endif
vty_out(vty, "service walltime-warning %lu\n",
- walltime_threshold);
+ walltime_threshold / 1000);
if (host.advanced)
vty_out(vty, "service advanced-vty\n");
diff --git a/lib/elf_py.c b/lib/elf_py.c
index 75d2d6007f..7c503cfb9d 100644
--- a/lib/elf_py.c
+++ b/lib/elf_py.c
@@ -293,7 +293,7 @@ static PyObject *elfreloc_getsection(PyObject *self, PyObject *args)
if (!w->es)
Py_RETURN_NONE;
- if (w->symidx == 0) {
+ if (!w->symvalid || w->symidx == 0) {
size_t idx = 0;
Elf_Scn *scn;
diff --git a/lib/xref.h b/lib/xref.h
index 0e3f00f690..37242bd79e 100644
--- a/lib/xref.h
+++ b/lib/xref.h
@@ -208,8 +208,19 @@ extern const struct xref * const __stop_xref_array[1] DSO_LOCAL;
* some build issue with it just add -DFRR_XREF_NO_NOTE to your build flags
* to disable it.
*/
-#ifdef FRR_XREF_NO_NOTE
+#if defined(FRR_XREF_NO_NOTE) || defined(__mips64)
#define XREF_NOTE ""
+
+/* mips64 note: MIPS64 (regardless of endianness, both mips64 & mips64el)
+ * does not have a 64-bit PC-relative relocation type. Unfortunately, a
+ * 64-bit PC-relative relocation is exactly what the below asm magic emits.
+ * Therefore, the xref ELF note is permanently disabled on MIPS64.
+ *
+ * For some context, refer to https://reviews.llvm.org/D80390
+ *
+ * As noted above, xref extraction still works through the section header
+ * path, so no functionality is lost.
+ */
#else
#if __SIZEOF_POINTER__ == 4
diff --git a/ospfd/ospf_packet.c b/ospfd/ospf_packet.c
index 0cb9d02725..b22fe5d99b 100644
--- a/ospfd/ospf_packet.c
+++ b/ospfd/ospf_packet.c
@@ -1787,9 +1787,10 @@ static struct list *ospf_ls_upd_list_lsa(struct ospf_neighbor *nbr,
continue;
}
} else if (IS_OPAQUE_LSA(lsah->type)) {
- flog_warn(EC_OSPF_PACKET,
- "LSA[Type%d:%pI4]: Opaque capability mismatch?",
- lsah->type, &lsah->id);
+ flog_warn(
+ EC_OSPF_PACKET,
+ "LSA[Type%d:%pI4] from %pI4: Opaque capability mismatch?",
+ lsah->type, &lsah->id, &lsah->adv_router);
continue;
}
diff --git a/zebra/dplane_fpm_nl.c b/zebra/dplane_fpm_nl.c
index 337113988e..af75ddf742 100644
--- a/zebra/dplane_fpm_nl.c
+++ b/zebra/dplane_fpm_nl.c
@@ -604,7 +604,10 @@ static void fpm_read(struct thread *t)
hdr, 0, false, ctx) != 1) {
dplane_ctx_fini(&ctx);
stream_pulldown(fnc->ibuf);
- return;
+ /*
+ * Let's continue to read other messages
+ * Even if we ignore this one.
+ */
}
break;
default:
@@ -863,7 +866,7 @@ static int fpm_nl_enqueue(struct fpm_nl_ctx *fnc, struct zebra_dplane_ctx *ctx)
case DPLANE_OP_NH_DELETE:
rv = netlink_nexthop_msg_encode(RTM_DELNEXTHOP, ctx, nl_buf,
- sizeof(nl_buf));
+ sizeof(nl_buf), true);
if (rv <= 0) {
zlog_err("%s: netlink_nexthop_msg_encode failed",
__func__);
@@ -875,7 +878,7 @@ static int fpm_nl_enqueue(struct fpm_nl_ctx *fnc, struct zebra_dplane_ctx *ctx)
case DPLANE_OP_NH_INSTALL:
case DPLANE_OP_NH_UPDATE:
rv = netlink_nexthop_msg_encode(RTM_NEWNEXTHOP, ctx, nl_buf,
- sizeof(nl_buf));
+ sizeof(nl_buf), true);
if (rv <= 0) {
zlog_err("%s: netlink_nexthop_msg_encode failed",
__func__);
diff --git a/zebra/rt_netlink.c b/zebra/rt_netlink.c
index 33fe8db99e..10725665e8 100644
--- a/zebra/rt_netlink.c
+++ b/zebra/rt_netlink.c
@@ -2501,7 +2501,7 @@ static bool _netlink_nexthop_build_group(struct nlmsghdr *n, size_t req_size,
*/
ssize_t netlink_nexthop_msg_encode(uint16_t cmd,
const struct zebra_dplane_ctx *ctx,
- void *buf, size_t buflen)
+ void *buf, size_t buflen, bool fpm)
{
struct {
struct nlmsghdr n;
@@ -2528,9 +2528,10 @@ ssize_t netlink_nexthop_msg_encode(uint16_t cmd,
/*
* Nothing to do if the kernel doesn't support nexthop objects or
- * we dont want to install this type of NHG
+ * we dont want to install this type of NHG, but FPM may possible to
+ * handle this.
*/
- if (!kernel_nexthops_supported()) {
+ if (!fpm && !kernel_nexthops_supported()) {
if (IS_ZEBRA_DEBUG_KERNEL || IS_ZEBRA_DEBUG_NHG)
zlog_debug(
"%s: nhg_id %u (%s): kernel nexthops not supported, ignoring",
@@ -2850,7 +2851,7 @@ static ssize_t netlink_nexthop_msg_encoder(struct zebra_dplane_ctx *ctx,
return -1;
}
- return netlink_nexthop_msg_encode(cmd, ctx, buf, buflen);
+ return netlink_nexthop_msg_encode(cmd, ctx, buf, buflen, false);
}
enum netlink_msg_status
diff --git a/zebra/rt_netlink.h b/zebra/rt_netlink.h
index fd2b79a2bf..b67169d6f0 100644
--- a/zebra/rt_netlink.h
+++ b/zebra/rt_netlink.h
@@ -84,7 +84,7 @@ extern int netlink_nexthop_change(struct nlmsghdr *h, ns_id_t ns_id,
extern int netlink_nexthop_read(struct zebra_ns *zns);
extern ssize_t netlink_nexthop_msg_encode(uint16_t cmd,
const struct zebra_dplane_ctx *ctx,
- void *buf, size_t buflen);
+ void *buf, size_t buflen, bool fpm);
extern ssize_t netlink_lsp_msg_encoder(struct zebra_dplane_ctx *ctx, void *buf,
size_t buflen);
diff --git a/zebra/zebra_vxlan.c b/zebra/zebra_vxlan.c
index f1c7debe11..12fec158b0 100644
--- a/zebra/zebra_vxlan.c
+++ b/zebra/zebra_vxlan.c
@@ -3511,6 +3511,12 @@ void zebra_vxlan_print_evpn(struct vty *vty, bool uj)
json = json_object_new_object();
json_object_string_add(json, "advertiseGatewayMacip",
zvrf->advertise_gw_macip ? "Yes" : "No");
+ json_object_string_add(json, "advertiseSviMacip",
+ zvrf->advertise_svi_macip ? "Yes"
+ : "No");
+ json_object_string_add(json, "advertiseSviMac",
+ zebra_evpn_mh_do_adv_svi_mac() ? "Yes"
+ : "No");
json_object_int_add(json, "numVnis", num_vnis);
json_object_int_add(json, "numL2Vnis", num_l2vnis);
json_object_int_add(json, "numL3Vnis", num_l3vnis);
diff --git a/zebra/zserv.c b/zebra/zserv.c
index d4fa6dadae..2024f34534 100644
--- a/zebra/zserv.c
+++ b/zebra/zserv.c
@@ -232,14 +232,16 @@ static void zserv_write(struct thread *thread)
struct stream *msg;
uint32_t wcmd = 0;
struct stream_fifo *cache;
+ uint64_t time_now = monotime(NULL);
/* If we have any data pending, try to flush it first */
switch (buffer_flush_all(client->wb, client->sock)) {
case BUFFER_ERROR:
goto zwrite_fail;
case BUFFER_PENDING:
- atomic_store_explicit(&client->last_write_time, monotime(NULL),
- memory_order_relaxed);
+ frr_with_mutex (&client->stats_mtx) {
+ client->last_write_time = time_now;
+ }
zserv_client_event(client, ZSERV_CLIENT_WRITE);
return;
case BUFFER_EMPTY:
@@ -273,20 +275,19 @@ static void zserv_write(struct thread *thread)
case BUFFER_ERROR:
goto zwrite_fail;
case BUFFER_PENDING:
- atomic_store_explicit(&client->last_write_time, monotime(NULL),
- memory_order_relaxed);
+ frr_with_mutex (&client->stats_mtx) {
+ client->last_write_time = time_now;
+ }
zserv_client_event(client, ZSERV_CLIENT_WRITE);
return;
case BUFFER_EMPTY:
break;
}
- atomic_store_explicit(&client->last_write_cmd, wcmd,
- memory_order_relaxed);
-
- atomic_store_explicit(&client->last_write_time, monotime(NULL),
- memory_order_relaxed);
-
+ frr_with_mutex (&client->stats_mtx) {
+ client->last_write_cmd = wcmd;
+ client->last_write_time = time_now;
+ }
return;
zwrite_fail:
@@ -433,11 +434,13 @@ static void zserv_read(struct thread *thread)
}
if (p2p < p2p_orig) {
+ uint64_t time_now = monotime(NULL);
+
/* update session statistics */
- atomic_store_explicit(&client->last_read_time, monotime(NULL),
- memory_order_relaxed);
- atomic_store_explicit(&client->last_read_cmd, hdr.command,
- memory_order_relaxed);
+ frr_with_mutex (&client->stats_mtx) {
+ client->last_read_time = time_now;
+ client->last_read_cmd = hdr.command;
+ }
/* publish read packets on client's input queue */
frr_with_mutex (&client->ibuf_mtx) {
@@ -631,6 +634,7 @@ static void zserv_client_free(struct zserv *client)
buffer_free(client->wb);
/* Free buffer mutexes */
+ pthread_mutex_destroy(&client->stats_mtx);
pthread_mutex_destroy(&client->obuf_mtx);
pthread_mutex_destroy(&client->ibuf_mtx);
@@ -751,14 +755,13 @@ static struct zserv *zserv_client_create(int sock)
client->obuf_fifo = stream_fifo_new();
client->ibuf_work = stream_new(stream_size);
client->obuf_work = stream_new(stream_size);
+ client->connect_time = monotime(NULL);
pthread_mutex_init(&client->ibuf_mtx, NULL);
pthread_mutex_init(&client->obuf_mtx, NULL);
+ pthread_mutex_init(&client->stats_mtx, NULL);
client->wb = buffer_new(0);
TAILQ_INIT(&(client->gr_info_queue));
- atomic_store_explicit(&client->connect_time, monotime(NULL),
- memory_order_relaxed);
-
/* Initialize flags */
for (afi = AFI_IP; afi < AFI_MAX; afi++) {
for (i = 0; i < ZEBRA_ROUTE_MAX; i++)
@@ -1019,8 +1022,14 @@ static void zebra_show_client_detail(struct vty *vty, struct zserv *client)
vty_out(vty, "------------------------ \n");
vty_out(vty, "FD: %d \n", client->sock);
- connect_time = (time_t) atomic_load_explicit(&client->connect_time,
- memory_order_relaxed);
+ frr_with_mutex (&client->stats_mtx) {
+ connect_time = client->connect_time;
+ last_read_time = client->last_read_time;
+ last_write_time = client->last_write_time;
+
+ last_read_cmd = client->last_read_cmd;
+ last_write_cmd = client->last_write_cmd;
+ }
vty_out(vty, "Connect Time: %s \n",
zserv_time_buf(&connect_time, cbuf, ZEBRA_TIME_BUF));
@@ -1041,16 +1050,6 @@ static void zebra_show_client_detail(struct vty *vty, struct zserv *client)
"Client will %sbe notified about the status of its routes.\n",
client->notify_owner ? "" : "Not ");
- last_read_time = (time_t)atomic_load_explicit(&client->last_read_time,
- memory_order_relaxed);
- last_write_time = (time_t)atomic_load_explicit(&client->last_write_time,
- memory_order_relaxed);
-
- last_read_cmd = atomic_load_explicit(&client->last_read_cmd,
- memory_order_relaxed);
- last_write_cmd = atomic_load_explicit(&client->last_write_cmd,
- memory_order_relaxed);
-
vty_out(vty, "Last Msg Rx Time: %s \n",
zserv_time_buf(&last_read_time, rbuf, ZEBRA_TIME_BUF));
vty_out(vty, "Last Msg Tx Time: %s \n",
@@ -1184,12 +1183,11 @@ static void zebra_show_client_brief(struct vty *vty, struct zserv *client)
char wbuf[ZEBRA_TIME_BUF];
time_t connect_time, last_read_time, last_write_time;
- connect_time = (time_t)atomic_load_explicit(&client->connect_time,
- memory_order_relaxed);
- last_read_time = (time_t)atomic_load_explicit(&client->last_read_time,
- memory_order_relaxed);
- last_write_time = (time_t)atomic_load_explicit(&client->last_write_time,
- memory_order_relaxed);
+ frr_with_mutex (&client->stats_mtx) {
+ connect_time = client->connect_time;
+ last_read_time = client->last_read_time;
+ last_write_time = client->last_write_time;
+ }
if (client->instance || client->session_id)
snprintfrr(client_string, sizeof(client_string), "%s[%u:%u]",
diff --git a/zebra/zserv.h b/zebra/zserv.h
index de784e382a..cfc33f9169 100644
--- a/zebra/zserv.h
+++ b/zebra/zserv.h
@@ -215,16 +215,21 @@ struct zserv {
* relative to last_read_time.
*/
+ pthread_mutex_t stats_mtx;
+ /* BEGIN covered by stats_mtx */
+
/* monotime of client creation */
- _Atomic uint64_t connect_time;
+ uint64_t connect_time;
/* monotime of last message received */
- _Atomic uint64_t last_read_time;
+ uint64_t last_read_time;
/* monotime of last message sent */
- _Atomic uint64_t last_write_time;
+ uint64_t last_write_time;
/* command code of last message read */
- _Atomic uint64_t last_read_cmd;
+ uint64_t last_read_cmd;
/* command code of last message written */
- _Atomic uint64_t last_write_cmd;
+ uint64_t last_write_cmd;
+
+ /* END covered by stats_mtx */
/*
* Number of instances configured with