Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
From 63e37a76cb4f1282b0d8ee4dd655221bec01a1de Mon Sep 17 00:00:00 2001
From: Vaideesh Ravi Shankar <[email protected]>
Date: Mon, 15 Sep 2025 14:42:00 -0700
Subject: [PATCH] bgpd: Fix JSON wrapper brace consistency in neighbor commands

The BGP neighbor adj-route commands (advertised-routes, received-routes,
filtered-routes) had inconsistent JSON wrapper brace handling. Only
advertised-routes and received-routes got wrapper braces from the command
handler, while filtered-routes used complete JSON objects.

This caused malformed JSON output like:
{ { warning: message } }

Fix by conditionally handling JSON output based on route type:
- advertised/received routes: output raw JSON fragments for CLI brace wrapping
- filtered routes: output complete JSON objects with vty_json

This ensures proper JSON formatting for all route types while maintaining
backward compatibility.

Signed-off-by: Vaideesh Ravi Shankar <[email protected]>

diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c
index 4742ccabc6..4ba2c68c0c 100644
--- a/bgpd/bgp_route.c
+++ b/bgpd/bgp_route.c
@@ -6730,7 +6730,7 @@ static int clear_batch_rib_helper(struct bgp_clearing_info *cinfo)
/* This will resume the "inner" walk if necessary */
ret = walk_batch_table_helper(cinfo, table, true /*inner*/);
if (ret != 0) {
- /* The "inner" resume info will be set;
+ /* The "inner" resume info will be set;
* capture the resume info we need
* from the outer afi/safi and dest
*/
@@ -15534,11 +15534,18 @@ static int peer_adj_routes(struct vty *vty, struct peer *peer, afi_t afi,

if (!peer || !peer->afc[afi][safi]) {
if (use_json) {
- json_object_string_add(
- json, "warning",
- "No such neighbor or address family");
- vty_out(vty, "%s\n", json_object_to_json_string(json));
- json_object_free(json);
+ if (type == bgp_show_adj_route_advertised ||
+ type == bgp_show_adj_route_received) {
+ /* Raw fragment for CLI brace wrapping */
+ vty_out(vty,
+ "\"warning\": \"No such neighbor or address family\"\n");
+ json_object_free(json);
+ } else {
+ /* Complete object for filtered/bestpath */
+ json_object_string_add(json, "warning",
+ "No such neighbor or address family");
+ vty_json(vty, json);
+ }
json_object_free(json_ar);
} else
vty_out(vty, "%% No such neighbor or address family\n");
@@ -15551,11 +15558,17 @@ static int peer_adj_routes(struct vty *vty, struct peer *peer, afi_t afi,
&& !CHECK_FLAG(peer->af_flags[afi][safi],
PEER_FLAG_SOFT_RECONFIG)) {
if (use_json) {
- json_object_string_add(
- json, "warning",
- "Inbound soft reconfiguration not enabled");
- vty_out(vty, "%s\n", json_object_to_json_string(json));
- json_object_free(json);
+ if (type == bgp_show_adj_route_received) {
+ /* Raw fragment for CLI brace wrapping */
+ vty_out(vty,
+ "\"warning\": \"Inbound soft reconfiguration not enabled\"\n");
+ json_object_free(json);
+ } else {
+ /* Complete object for filtered routes */
+ json_object_string_add(json, "warning",
+ "Inbound soft reconfiguration not enabled");
+ vty_json(vty, json);
+ }
json_object_free(json_ar);
} else
vty_out(vty,
diff --git a/tests/topotests/bgp_received_routes_with_soft_inbound/test_bgp_received_routes_with_soft_inbound.py b/tests/topotests/bgp_received_routes_with_soft_inbound/test_bgp_received_routes_with_soft_inbound.py
index 0b933add2f..07a14b2767 100644
--- a/tests/topotests/bgp_received_routes_with_soft_inbound/test_bgp_received_routes_with_soft_inbound.py
+++ b/tests/topotests/bgp_received_routes_with_soft_inbound/test_bgp_received_routes_with_soft_inbound.py
@@ -98,6 +98,81 @@ def test_bgp_received_routes_with_soft_inbound():
assert result is None, "Can't converge"


+def test_bgp_adj_routes_json_error_paths():
+ """Test JSON formatting consistency for BGP adj-route error paths"""
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"]
+
+ # Disable soft-reconfiguration to trigger warning paths
+ r1.vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65001
+ address-family ipv4 unicast
+ no neighbor 192.168.1.2 soft-reconfiguration inbound
+ end
+ """
+ )
+
+ def _check_adj_route_json_consistency():
+ """Check that all adj-route JSON outputs are well-formed"""
+ test_commands = [
+ "show bgp ipv4 unicast neighbors 192.168.1.2 received-routes json",
+ "show bgp ipv4 unicast neighbors 192.168.1.2 advertised-routes json",
+ "show bgp ipv4 unicast neighbors 192.168.1.2 filtered-routes json",
+ ]
+
+ for cmd in test_commands:
+ output = r1.vtysh_cmd(cmd)
+
+ # Critical: JSON should ALWAYS be valid after our fix
+ try:
+ parsed = json.loads(output)
+ except json.JSONDecodeError as e:
+ pytest.fail(f"Malformed JSON in command '{cmd}': {e}\nOutput: {output}")
+
+ # Test CONTENT for expected error messages
+ if "received-routes" in cmd or "filtered-routes" in cmd:
+ warning_msg = parsed.get("warning", "")
+ expected = "Inbound soft reconfiguration not enabled"
+ if warning_msg != expected:
+ return (
+ f"Expected soft reconfig warning in {cmd}, "
+ f"got: '{warning_msg}'"
+ )
+
+ # Verify JSON structure is a single object, not double-nested
+ if not isinstance(parsed, dict):
+ return f"Expected dict object for {cmd}"
+
+ # Critical: Should not have nested structure { { "warning": ... } }
+ warning_value = parsed.get("warning", "")
+ if isinstance(warning_value, dict):
+ # This indicates our fix failed - fail test immediately
+ pytest.fail(f"Warning should be string, not nested object in {cmd}")
+
+ return None
+
+ test_func = functools.partial(_check_adj_route_json_consistency)
+ _, result = topotest.run_and_expect(test_func, None, count=5, wait=3)
+ assert result is None, f"JSON consistency check failed: {result}"
+
+ # Restore original configuration
+ r1.vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65001
+ address-family ipv4 unicast
+ neighbor 192.168.1.2 soft-reconfiguration inbound
+ end
+ """
+ )
+
+
if __name__ == "__main__":
args = ["-s"] + sys.argv[1:]
sys.exit(pytest.main(args))
--
2.50.1

1 change: 1 addition & 0 deletions src/sonic-frr/patch/series
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@
0058-mgmtd-normalize-argument-order-to-copy-dst-src.patch
0059-zebra-Ensure-that-the-dplane-can-send-the-full-packe.patch
0060-bgpd-Convert-bmp-path_info-tracking-from-hash-to-rbt.patch
0061-bgpd-Fix-JSON-wrapper-brace-consistency-in-neighbor.patch
Loading