Skip to content

bgpd: support brief json for bgp v4 and v6 neighbors route#232

Open
mwinter-osr wants to merge 3 commits intoopensourcerouting:masterfrom
mwinter-osr:PR21414
Open

bgpd: support brief json for bgp v4 and v6 neighbors route#232
mwinter-osr wants to merge 3 commits intoopensourcerouting:masterfrom
mwinter-osr:PR21414

Conversation

@mwinter-osr
Copy link
Copy Markdown
Member

brief-json support is added for show bgp v4 and v6 unicast neighbour routes.

Commands supported:
show bgp vrf ipv4 unicast neighbors routes brief json
show bgp vrf ipv6 unicast neighbors routes brief json

cpe1# show bgp vrf default ipv6 unicast neighbors 2001:db8:10::2 routes json brief
{
"routes": { "2001:db8:2::/64": {
"pathCount":1
,"multiPathCount":1
,"flags": {"bestPathExists":true}
} ,"2001:db8:10::/64": {
"pathCount":1
,"multiPathCount":0
,"flags": {"bestPathExists":false}
} } , "totalRoutes": 2, "totalPaths": 5,"numRoutes":2
}
cpe1# show bgp vrf default ipv6 unicast neighbors 2001:db8:10::2 routes json
{
"vrfId": 0,
"vrfName": "default",
"tableVersion": 4,
"routerId": "172.16.255.1",
"defaultLocPrf": 100,
"localAS": 65000,
"routes": { "2001:db8:2::/64": [{"valid":true,"bestpath":true,"selectionReason":"First path received","pathFrom":"internal","prefix":"2001:db8:2::","prefixLen":64,"network":"2001:db8:2::/64","version":3,"metric":0,"locPrf":100,"weight":0,"peerId":"2001:db8:10::2","path":"","origin":"incomplete","nexthops":[{"ip":"2001:db8:10::2","hostname":"cpe2","afi":"ipv6","scope":"global","linkLocalOnly":false,"length":32},{"ip":"fe80::2c7d:9eff:fecb:65ab","hostname":"cpe2","afi":"ipv6","scope":"link-local","used":true}]}]
,"2001:db8:10::/64": [{"valid":true,"pathFrom":"internal","prefix":"2001:db8:10::","prefixLen":64,"network":"2001:db8:10::/64","version":2,"metric":0,"locPrf":100,"weight":0,"peerId":"2001:db8:10::2","path":"","origin":"incomplete","nexthops":[{"ip":"2001:db8:10::2","hostname":"cpe2","afi":"ipv6","scope":"global","linkLocalOnly":false,"length":32},{"ip":"fe80::2c7d:9eff:fecb:65ab","hostname":"cpe2","afi":"ipv6","scope":"link-local","used":true}]}]
} , "totalRoutes": 2, "totalPaths": 5,"numRoutes":2
}
cpe1#
pe1# show bgp vrf RED ipv4 unicast neighbors 192.168.1.1 routes json brief
{
"routes": { "10.0.0.0/24": {
"pathCount":1
,"multiPathCount":2
,"flags": {"bestPathExists":true}
} ,"172.16.255.1/32": {
"pathCount":1
,"multiPathCount":2
,"flags": {"bestPathExists":true}
} ,"192.168.1.0/24": {
"pathCount":1
,"multiPathCount":2
,"flags": {"bestPathExists":true}
} ,"192.168.2.0/24": {
"pathCount":1
,"multiPathCount":2
,"flags": {"bestPathExists":true}
} } , "totalRoutes": 4, "totalPaths": 8,"numRoutes":4
}
pe1# show bgp vrf RED ipv4 unicast neighbors 192.168.1.1 routes json
{
"vrfId": 4,
"vrfName": "RED",
"tableVersion": 8,
"routerId": "192.168.1.2",
"defaultLocPrf": 100,
"localAS": 65001,
"routes": { "10.0.0.0/24": [{"valid":true,"bestpath":true,"selectionReason":"Older Path","multipath":true,"pathFrom":"external","prefix":"10.0.0.0","prefixLen":24,"network":"10.0.0.0/24","version":5,"metric":0,"weight":0,"peerId":"192.168.1.1","path":"65000","origin":"incomplete","nexthops":[{"ip":"192.168.1.1","hostname":"cpe1","afi":"ipv4","used":true}]}]
,"172.16.255.1/32": [{"valid":true,"bestpath":true,"selectionReason":"Older Path","multipath":true,"pathFrom":"external","prefix":"172.16.255.1","prefixLen":32,"network":"172.16.255.1/32","version":6,"metric":0,"weight":0,"peerId":"192.168.1.1","path":"65000","origin":"incomplete","nexthops":[{"ip":"192.168.1.1","hostname":"cpe1","afi":"ipv4","used":true}]}]
,"192.168.1.0/24": [{"valid":true,"bestpath":true,"selectionReason":"Older Path","multipath":true,"pathFrom":"external","prefix":"192.168.1.0","prefixLen":24,"network":"192.168.1.0/24","version":7,"metric":0,"weight":0,"peerId":"192.168.1.1","path":"65000","origin":"incomplete","nexthops":[{"ip":"192.168.1.1","hostname":"cpe1","afi":"ipv4","used":true}]}]
,"192.168.2.0/24": [{"valid":true,"bestpath":true,"selectionReason":"Older Path","multipath":true,"pathFrom":"external","prefix":"192.168.2.0","prefixLen":24,"network":"192.168.2.0/24","version":8,"weight":0,"peerId":"192.168.1.1","path":"65000","origin":"incomplete","nexthops":[{"ip":"192.168.1.1","hostname":"cpe1","afi":"ipv4","used":true}]}]
} , "totalRoutes": 4, "totalPaths": 8,"numRoutes":4
}
pe1#

Original PR21414 by hnattamaisub

brief-json support is added for show bgp v4 and v6 unicast neighbour
routes.

Commands supported:
show bgp vrf <vrf> ipv4 unicast neighbors <neigh> routes json brief
show bgp vrf <vrf> ipv6 unicast neighbors <neigh> routes json brief

Signed-off-by: harini <hnattamaisub@nvidia.com>
Signed-off-by: harini <hnattamaisub@nvidia.com>
show bgp vrf <> ipv4 unicast neighbors routes brief json
show bgp vrf <>ipv6 unicast neighbors routes brief json

Signed-off-by: harini <hnattamaisub@nvidia.com>
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 14, 2026

Greptile Summary

Adds json brief output support to show bgp [vrf] ipv{4,6} unicast neighbors <peer> routes by threading a bool brief parameter from the new DEFPY through bgp_show_neighbor_route into the existing bgp_show_table brief logic introduced by the loc-rib brief commit. Documentation and topotests are present.

  • The IPv6 forwarding sysctl is applied after tgen.start_router(), creating a race window where BGP tries to bring up IPv6 sessions before forwarding is enabled; it must be moved before start_router().
  • brief is silently ignored (full JSON returned) when a non-unicast SAFI such as vpn or flowspec is specified; the guard should reject it explicitly.
  • Hard-coded totalPaths assertions (8 for IPv4, 5 for IPv6) tie the test to exact path-count convergence and will produce spurious failures if the topology gains any extra paths.

Confidence Score: 4/5

  • Core C logic is correct; one P1 test reliability issue (sysctl ordering) and two P2 concerns must be addressed before merge.
  • The bgp_route.c implementation correctly wires brief through the existing bgp_show_table path; no data-path regression risk. Documentation and topotests are both present. The sysctl-after-start ordering is a genuine P1 that can cause intermittent IPv6 BGP session failures in CI (the namespace starts with forwarding=0 and there is a real race). The silent SAFI mismatch and fragile totalPaths are P2.
  • tests/topotests/bgp_soo/test_bgp_soo.py (sysctl ordering and fragile path-count assertions), bgpd/bgp_route.c (SAFI guard for brief)

Important Files Changed

Filename Overview
bgpd/bgp_route.c Adds bool brief to bgp_show_neighbor_route and wires it through to bgp_show/bgp_show_table. CLI DEFPY updated to [json$uj [brief$brief]]. Guard added to reject brief for non-routes show types. Logic is correct and consistent with the existing loc-rib brief implementation.
doc/user/bgp.rst New clicmd entry added for [json [brief]] on the neighbor routes command. Documentation accurately describes the brief-only-with-routes restriction and the compact output format.
tests/topotests/bgp_soo/test_bgp_soo.py Adds four new test functions covering IPv4/IPv6 neighbor routes JSON and brief JSON on pe1 vrf RED and cpe1 default. IPv6 forwarding sysctl is applied after start_router(), creating a potential race; hardcoded totalPaths values (5, 8) are topology-sensitive.
tests/topotests/bgp_soo/cpe1/bgpd.conf Adds IPv6 eBGP neighbor (2001:db8:1::2/pe1) and iBGP neighbor (2001:db8:10::2/cpe2) plus address-family ipv6 unicast with connected redistribution. Correct for the new IPv6 test scenarios.
tests/topotests/bgp_soo/pe1/bgpd.conf Adds IPv6 VPN AF (VPNv6 with pe2), IPv6 neighbor in vrf RED, and address-family ipv6 unicast with VPN export/import. Uses the same label (1111) for both IPv4 and IPv6 VPN exports from vrf RED.

Sequence Diagram

sequenceDiagram
    participant VTY
    participant show_ip_bgp_neighbor_routes as DEFPY handler
    participant bgp_show_neighbor_route
    participant bgp_show
    participant bgp_show_table

    VTY->>show_ip_bgp_neighbor_routes: show bgp vrf X ipv4 unicast neighbors P routes json brief
    show_ip_bgp_neighbor_routes->>show_ip_bgp_neighbor_routes: parse AFI/SAFI/peer (argc-- for json)
    show_ip_bgp_neighbor_routes->>show_ip_bgp_neighbor_routes: guard: brief only with routes
    show_ip_bgp_neighbor_routes->>bgp_show_neighbor_route: "afi, safi, bgp_show_type_neighbor, use_json=true, brief=true"
    bgp_show_neighbor_route->>bgp_show_neighbor_route: SET_FLAG(show_flags, BGP_SHOW_OPT_JSON)
    bgp_show_neighbor_route->>bgp_show: "show_flags, brief=true"
    bgp_show->>bgp_show_table: "table, show_flags, brief=true"
    bgp_show_table-->>VTY: "brief JSON header {"routes": {"
    loop per dest (filtered by peer address)
        bgp_show_table-->>VTY: ""prefix": { "pathCount":N, "multiPathCount":M, "flags":{...} }"
    end
    bgp_show_table-->>VTY: footer: totalRoutes, totalPaths, numRoutes
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: tests/topotests/bgp_soo/test_bgp_soo.py
Line: 92-93

Comment:
**IPv6 forwarding sysctl applied after `start_router()`**

`sysctl -w net.ipv6.conf.all.forwarding=1` is set after `tgen.start_router()`, meaning FRR daemons start and attempt to bring up IPv6 BGP sessions before the kernel forwarding flag is guaranteed to be set. In a network namespace that defaults to forwarding=0, this creates a race: BGP Open messages can be dropped before zebra has processed `ipv6 forwarding` from the config. Compare the existing pre-start IPv4 forwarding setup at lines 69–70 for pe1/pe2. Move this loop before `tgen.start_router()` to match that pattern.

```suggestion
    for _, (_, router) in enumerate(router_list.items(), 1):
        router.run("sysctl -w net.ipv6.conf.all.forwarding=1")

    tgen.start_router()
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: tests/topotests/bgp_soo/test_bgp_soo.py
Line: 247-251

Comment:
**Hard-coded `totalPaths` assertion is fragile**

`"totalPaths": 8` (and `"totalPaths": 5` in the IPv6 test) counts all path entries in the entire BGP table, not just paths from the queried neighbor. This value is tightly coupled to the exact redistribution state and number of multipath slots at convergence. If the topology acquires any additional paths (e.g., an extra redistributed prefix from a loopback, a transient ECMP slot), the assertion will flip to a spurious failure. Either relax this to `"*"` (wildcard in `json_cmp`) or add a comment explaining exactly how each path is counted so reviewers can verify determinism.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: bgpd/bgp_route.c
Line: 17570-17573

Comment:
**`brief` silently ignored for non-unicast SAFIs**

The guard here correctly rejects `brief` for `flap-statistics` and `dampened-routes`, but it does not reject it for non-unicast SAFIs (e.g., `vpn`, `multicast`, `flowspec`). For those, `bgp_show()` dispatches to `bgp_show_table_rd()` or `bgp_evpn_show_all_routes()`, neither of which accepts a `brief` parameter, so the flag is silently dropped and full JSON is returned. The doc string already limits the feature to "unicast neighbor routes," but the runtime path produces no error. Consider adding a SAFI check alongside the `sh_type` check:

```c
if (brief && (sh_type != bgp_show_type_neighbor ||
              (safi != SAFI_UNICAST && safi != SAFI_LABELED_UNICAST))) {
    vty_out(vty, "%% brief option is only supported with unicast 'routes'\n");
    return CMD_WARNING;
}
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "tests: Enhanced tests to validate new co..." | Re-trigger Greptile

Comment on lines +92 to +93
for _, (_, router) in enumerate(router_list.items(), 1):
router.run("sysctl -w net.ipv6.conf.all.forwarding=1")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 IPv6 forwarding sysctl applied after start_router()

sysctl -w net.ipv6.conf.all.forwarding=1 is set after tgen.start_router(), meaning FRR daemons start and attempt to bring up IPv6 BGP sessions before the kernel forwarding flag is guaranteed to be set. In a network namespace that defaults to forwarding=0, this creates a race: BGP Open messages can be dropped before zebra has processed ipv6 forwarding from the config. Compare the existing pre-start IPv4 forwarding setup at lines 69–70 for pe1/pe2. Move this loop before tgen.start_router() to match that pattern.

Suggested change
for _, (_, router) in enumerate(router_list.items(), 1):
router.run("sysctl -w net.ipv6.conf.all.forwarding=1")
for _, (_, router) in enumerate(router_list.items(), 1):
router.run("sysctl -w net.ipv6.conf.all.forwarding=1")
tgen.start_router()
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/topotests/bgp_soo/test_bgp_soo.py
Line: 92-93

Comment:
**IPv6 forwarding sysctl applied after `start_router()`**

`sysctl -w net.ipv6.conf.all.forwarding=1` is set after `tgen.start_router()`, meaning FRR daemons start and attempt to bring up IPv6 BGP sessions before the kernel forwarding flag is guaranteed to be set. In a network namespace that defaults to forwarding=0, this creates a race: BGP Open messages can be dropped before zebra has processed `ipv6 forwarding` from the config. Compare the existing pre-start IPv4 forwarding setup at lines 69–70 for pe1/pe2. Move this loop before `tgen.start_router()` to match that pattern.

```suggestion
    for _, (_, router) in enumerate(router_list.items(), 1):
        router.run("sysctl -w net.ipv6.conf.all.forwarding=1")

    tgen.start_router()
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +247 to +251
pe1 = tgen.gears["pe1"]

if tgen.routers_have_failure():
pytest.skip(tgen.errors)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hard-coded totalPaths assertion is fragile

"totalPaths": 8 (and "totalPaths": 5 in the IPv6 test) counts all path entries in the entire BGP table, not just paths from the queried neighbor. This value is tightly coupled to the exact redistribution state and number of multipath slots at convergence. If the topology acquires any additional paths (e.g., an extra redistributed prefix from a loopback, a transient ECMP slot), the assertion will flip to a spurious failure. Either relax this to "*" (wildcard in json_cmp) or add a comment explaining exactly how each path is counted so reviewers can verify determinism.

Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/topotests/bgp_soo/test_bgp_soo.py
Line: 247-251

Comment:
**Hard-coded `totalPaths` assertion is fragile**

`"totalPaths": 8` (and `"totalPaths": 5` in the IPv6 test) counts all path entries in the entire BGP table, not just paths from the queried neighbor. This value is tightly coupled to the exact redistribution state and number of multipath slots at convergence. If the topology acquires any additional paths (e.g., an extra redistributed prefix from a loopback, a transient ECMP slot), the assertion will flip to a spurious failure. Either relax this to `"*"` (wildcard in `json_cmp`) or add a comment explaining exactly how each path is counted so reviewers can verify determinism.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread bgpd/bgp_route.c
Comment on lines +17570 to +17573
if (brief && sh_type != bgp_show_type_neighbor) {
vty_out(vty, "%% brief option is only supported with 'routes'\n");
return CMD_WARNING;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 brief silently ignored for non-unicast SAFIs

The guard here correctly rejects brief for flap-statistics and dampened-routes, but it does not reject it for non-unicast SAFIs (e.g., vpn, multicast, flowspec). For those, bgp_show() dispatches to bgp_show_table_rd() or bgp_evpn_show_all_routes(), neither of which accepts a brief parameter, so the flag is silently dropped and full JSON is returned. The doc string already limits the feature to "unicast neighbor routes," but the runtime path produces no error. Consider adding a SAFI check alongside the sh_type check:

if (brief && (sh_type != bgp_show_type_neighbor ||
              (safi != SAFI_UNICAST && safi != SAFI_LABELED_UNICAST))) {
    vty_out(vty, "%% brief option is only supported with unicast 'routes'\n");
    return CMD_WARNING;
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: bgpd/bgp_route.c
Line: 17570-17573

Comment:
**`brief` silently ignored for non-unicast SAFIs**

The guard here correctly rejects `brief` for `flap-statistics` and `dampened-routes`, but it does not reject it for non-unicast SAFIs (e.g., `vpn`, `multicast`, `flowspec`). For those, `bgp_show()` dispatches to `bgp_show_table_rd()` or `bgp_evpn_show_all_routes()`, neither of which accepts a `brief` parameter, so the flag is silently dropped and full JSON is returned. The doc string already limits the feature to "unicast neighbor routes," but the runtime path produces no error. Consider adding a SAFI check alongside the `sh_type` check:

```c
if (brief && (sh_type != bgp_show_type_neighbor ||
              (safi != SAFI_UNICAST && safi != SAFI_LABELED_UNICAST))) {
    vty_out(vty, "%% brief option is only supported with unicast 'routes'\n");
    return CMD_WARNING;
}
```

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants