Skip to content
Open
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
8 changes: 7 additions & 1 deletion doc/user/zebra.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1820,7 +1820,7 @@ zebra Terminal Mode Commands
total number of route nodes in the table. Which will be higher than
the actual number of routes that are held.

.. clicmd:: show nexthop-group rib [ID] [vrf NAME] [singleton [ip|ip6]] [type] [json]
.. clicmd:: show nexthop-group rib [ID] [vrf NAME] [singleton [ip|ip6]] [type] [json [brief]]

Display nexthop groups created by zebra. The [vrf NAME] option
is only meaningful if you have started zebra with the --vrfwnetns
Expand All @@ -1833,6 +1833,12 @@ zebra Terminal Mode Commands
Zebra can delay installing this route until it is used by something
else.

With **json**, the output is in JSON format. **brief** may only be
given together with **json** (after it); it omits the full-detail
fields (type, refCount, timeToDeletion) and shows a condensed nexthop
list; groups with dependencies list only ``depends`` and not
per-nexthop detail.

.. clicmd:: show <ip|ipv6> zebra route dump [<vrf> VRFNAME]

It dumps all the routes from RIB with detailed information including
Expand Down
64 changes: 36 additions & 28 deletions lib/nexthop.c
Original file line number Diff line number Diff line change
Expand Up @@ -1203,9 +1203,8 @@ bool nexthop_is_blackhole(const struct nexthop *nh)
* Render a nexthop into a json object; the caller allocates and owns
* the json object memory.
*/
void nexthop_json_helper(json_object *json_nexthop,
const struct nexthop *nexthop, bool display_vrfid,
uint8_t rn_family)
void nexthop_json_helper(json_object *json_nexthop, const struct nexthop *nexthop,
bool display_vrfid, uint8_t rn_family, bool brief)
{
json_object *json_labels = NULL;
json_object *json_backups = NULL;
Expand All @@ -1216,20 +1215,23 @@ void nexthop_json_helper(json_object *json_nexthop,
json_object *json_segs = NULL;
int i;

json_object_int_add(json_nexthop, "flags", nexthop->flags);
if (!brief) {
json_object_int_add(json_nexthop, "flags", nexthop->flags);

if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_DUPLICATE))
json_object_boolean_true_add(json_nexthop, "duplicate");
if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_DUPLICATE))
json_object_boolean_true_add(json_nexthop, "duplicate");

if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB))
json_object_boolean_true_add(json_nexthop, "fib");
if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB))
json_object_boolean_true_add(json_nexthop, "fib");
}

switch (nexthop->type) {
case NEXTHOP_TYPE_IPV4:
case NEXTHOP_TYPE_IPV4_IFINDEX:
json_object_string_addf(json_nexthop, "ip", "%pI4",
&nexthop->gate.ipv4);
json_object_string_add(json_nexthop, "afi", "ipv4");
if (!brief)
json_object_string_add(json_nexthop, "afi", "ipv4");

if (nexthop->ifindex) {
json_object_int_add(json_nexthop, "interfaceIndex",
Expand All @@ -1243,7 +1245,8 @@ void nexthop_json_helper(json_object *json_nexthop,
case NEXTHOP_TYPE_IPV6_IFINDEX:
json_object_string_addf(json_nexthop, "ip", "%pI6",
&nexthop->gate.ipv6);
json_object_string_add(json_nexthop, "afi", "ipv6");
if (!brief)
json_object_string_add(json_nexthop, "afi", "ipv6");

if (nexthop->ifindex) {
json_object_int_add(json_nexthop, "interfaceIndex",
Expand All @@ -1255,42 +1258,47 @@ void nexthop_json_helper(json_object *json_nexthop,
break;

case NEXTHOP_TYPE_IFINDEX:
json_object_boolean_true_add(json_nexthop, "directlyConnected");
if (!brief)
json_object_boolean_true_add(json_nexthop, "directlyConnected");
json_object_int_add(json_nexthop, "interfaceIndex",
nexthop->ifindex);
json_object_string_add(json_nexthop, "interfaceName",
ifindex2ifname(nexthop->ifindex,
nexthop->vrf_id));
break;
case NEXTHOP_TYPE_BLACKHOLE:
json_object_boolean_true_add(json_nexthop, "unreachable");
switch (nexthop->bh_type) {
case BLACKHOLE_REJECT:
json_object_boolean_true_add(json_nexthop, "reject");
break;
case BLACKHOLE_ADMINPROHIB:
json_object_boolean_true_add(json_nexthop,
"adminProhibited");
break;
case BLACKHOLE_NULL:
json_object_boolean_true_add(json_nexthop, "blackhole");
break;
case BLACKHOLE_UNSPEC:
break;
if (!brief) {
json_object_boolean_true_add(json_nexthop, "unreachable");
switch (nexthop->bh_type) {
case BLACKHOLE_REJECT:
json_object_boolean_true_add(json_nexthop, "reject");
break;
case BLACKHOLE_ADMINPROHIB:
json_object_boolean_true_add(json_nexthop, "adminProhibited");
break;
case BLACKHOLE_NULL:
json_object_boolean_true_add(json_nexthop, "blackhole");
break;
case BLACKHOLE_UNSPEC:
break;
}
}
break;
}

if (display_vrfid)
json_object_string_add(json_nexthop, "vrf", vrf_id_to_name(nexthop->vrf_id));

if (brief)
return;

/* This nexthop is a resolver for the parent nexthop.
* Set resolver flag for better clarity and delimiter
* in flat list of nexthops in json.
*/
if (nexthop->rparent)
json_object_boolean_true_add(json_nexthop, "resolver");

if (display_vrfid)
json_object_string_add(json_nexthop, "vrf",
vrf_id_to_name(nexthop->vrf_id));
if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_DUPLICATE))
json_object_boolean_true_add(json_nexthop, "duplicate");

Expand Down
5 changes: 2 additions & 3 deletions lib/nexthop.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,8 @@ extern bool nexthop_is_blackhole(const struct nexthop *nh);
int nexthop_str2backups(const char *str, int *num_backups,
uint8_t *backups);

void nexthop_json_helper(struct json_object *json_nexthop,
const struct nexthop *nexthop, bool display_vrfid,
uint8_t rn_family);
void nexthop_json_helper(struct json_object *json_nexthop, const struct nexthop *nexthop,
bool display_vrfid, uint8_t rn_family, bool brief);
void nexthop_vty_helper(struct vty *vty, const struct nexthop *nexthop,
bool display_vrfid, uint8_t rn_family);

Expand Down
147 changes: 147 additions & 0 deletions tests/topotests/all_protocol_startup/test_all_protocol_startup.py
Original file line number Diff line number Diff line change
Expand Up @@ -1649,6 +1649,153 @@ def test_nexthop_group_replace():
verify_route_nexthop_group("3.3.3.1/32", False, 3)


def test_show_nexthop_group_rib_brief_json():
"""Check 'show nexthop-group rib ... json brief' return valid brief JSON.

Commands covered:
1) show nexthop-group rib json brief
2) show nexthop-group rib <id> json brief
3) show nexthop-group rib singleton ip json brief
4) show nexthop-group rib singleton ipv6 json brief
5) show nexthop-group rib zebra json brief
"""
global fatal_error

if fatal_error != "":
pytest.skip(fatal_error)

print("\n\n** Verifying show nexthop-group rib json brief commands")
print("******************************************\n")

tgen = get_topogen()
r1 = tgen.routers()["r1"]
full_only_fields = {"type", "refCount", "timeToDeletion"}

def _extract_json(s):
"""Extract first complete JSON object from string (handles trailing)."""
s = s.strip()
if not s:
return None, "empty output"
start = s.find("{")
if start == -1:
return None, "no JSON object in output"
depth = 0
for i in range(start, len(s)):
if s[i] == "{":
depth += 1
elif s[i] == "}":
depth -= 1
if depth == 0:
try:
return json.loads(s[start : i + 1]), None
except json.JSONDecodeError as e:
return None, str(e)
return None, "unclosed JSON object"

def _check_brief_nhg_entry(obj):
"""Brief entries have uptime, vrf; no type/refCount/timeToDeletion."""
if not isinstance(obj, dict):
return "NHG entry is not a dict"
if "uptime" not in obj:
return "Brief entry missing 'uptime'"
if "vrf" not in obj:
return "Brief entry missing 'vrf'"
for key in full_only_fields:
if key in obj:
return f"Brief entry should not have '{key}'"
return None

# 1) show nexthop-group rib json brief
out = r1.vtysh_cmd("show nexthop-group rib json brief").strip()
err = None
if not out:
data = {}
else:
data, parse_err = _extract_json(out)
if parse_err:
err = f"show nexthop-group rib json brief: invalid JSON: {parse_err}"
elif data is not None and not isinstance(data, dict):
err = "show nexthop-group rib json brief: top-level is not a dict"
elif data is None:
data = {}
if err is None:
for vrfname, vrfdata in data.items():
if not isinstance(vrfdata, dict):
continue
for nhg_id_str, nhg_obj in vrfdata.items():
chk = _check_brief_nhg_entry(nhg_obj)
if chk:
err = f"show nexthop-group rib json brief (vrf={vrfname}, id={nhg_id_str}): {chk}"
break
if err:
break
assert err is None, err
print("r1 show nexthop-group rib json brief ok")

# 2) show nexthop-group rib <id> json brief (use first available id)
nhg_id_used = None
for vrfname, vrfdata in data.items():
if isinstance(vrfdata, dict) and vrfdata:
nhg_id_used = next(iter(vrfdata.keys()))
break
if nhg_id_used is not None:
out = r1.vtysh_cmd(
f"show nexthop-group rib {nhg_id_used} json brief"
).strip()
data_id, parse_err = _extract_json(out)
if parse_err:
assert (
False
), f"show nexthop-group rib {nhg_id_used} json brief: invalid JSON: {parse_err}"
if data_id is None:
data_id = {}
# Output is {"<id>": { brief NHG object }}
if not isinstance(data_id, dict) or len(data_id) != 1:
assert (
False
), f"show nexthop-group rib {nhg_id_used} json brief: expected single key"
nhg_obj = next(iter(data_id.values()))
err = _check_brief_nhg_entry(nhg_obj)
assert err is None, f"show nexthop-group rib {nhg_id_used} json brief: {err}"
print(f"r1 show nexthop-group rib {nhg_id_used} json brief ok")

# 3) show nexthop-group rib singleton ip json brief
out = r1.vtysh_cmd("show nexthop-group rib singleton ip json brief").strip()
data3, parse_err = _extract_json(out)
if parse_err:
assert (
False
), f"show nexthop-group rib singleton ip json brief: invalid JSON: {parse_err}"
assert isinstance(
data3, dict
), "show nexthop-group rib singleton ip json brief: top-level is not a dict"
print("r1 show nexthop-group rib singleton ip json brief ok")

# 4) show nexthop-group rib singleton ipv6 json brief
out = r1.vtysh_cmd("show nexthop-group rib singleton ipv6 json brief").strip()
data4, parse_err = _extract_json(out)
if parse_err:
assert (
False
), f"show nexthop-group rib singleton ipv6 json brief: invalid JSON: {parse_err}"
assert isinstance(
data4, dict
), "show nexthop-group rib singleton ipv6 json brief: top-level is not a dict"
print("r1 show nexthop-group rib singleton ipv6 json brief ok")

# 5) show nexthop-group rib zebra json brief
out = r1.vtysh_cmd("show nexthop-group rib zebra json brief").strip()
data5, parse_err = _extract_json(out)
if parse_err:
assert (
False
), f"show nexthop-group rib zebra json brief: invalid JSON: {parse_err}"
assert isinstance(
data5, dict
), "show nexthop-group rib zebra json brief: top-level is not a dict"
print("r1 show nexthop-group rib zebra json brief ok")


def test_mpls_interfaces():
global fatal_error
net = get_topogen().net
Expand Down
11 changes: 5 additions & 6 deletions zebra/zebra_rnh.c
Original file line number Diff line number Diff line change
Expand Up @@ -1254,10 +1254,8 @@ int zebra_send_rnh_update(struct rnh *rnh, struct zserv *client,
* Render a nexthop into a json object; the caller allocates and owns
* the json object memory.
*/
void show_nexthop_json_helper(json_object *json_nexthop,
const struct nexthop *nexthop,
const struct route_node *rn,
const struct route_entry *re)
void show_nexthop_json_helper(json_object *json_nexthop, const struct nexthop *nexthop,
const struct route_node *rn, const struct route_entry *re, bool brief)
{
bool display_vrfid = false;
uint8_t rn_family;
Expand All @@ -1270,7 +1268,7 @@ void show_nexthop_json_helper(json_object *json_nexthop,
else
rn_family = AF_UNSPEC;

nexthop_json_helper(json_nexthop, nexthop, display_vrfid, rn_family);
nexthop_json_helper(json_nexthop, nexthop, display_vrfid, rn_family, brief);
}

/*
Expand Down Expand Up @@ -1381,7 +1379,8 @@ static void print_rnh(struct route_node *rn, struct vty *vty, json_object *json)
if (json) {
json_nexthop = json_object_new_object();
json_object_array_add(json_nexthop_array, json_nexthop);
show_nexthop_json_helper(json_nexthop, nexthop, rn, NULL);
show_nexthop_json_helper(json_nexthop, nexthop, rn, NULL,
false);
} else {
show_route_nexthop_helper(vty, rn, NULL, nexthop);
vty_out(vty, "\n");
Expand Down
7 changes: 3 additions & 4 deletions zebra/zebra_rnh.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,9 @@ extern int rnh_resolve_via_default(struct zebra_vrf *zvrf, int family);
extern bool rnh_nexthop_valid(const struct route_entry *re,
const struct nexthop *nh);

void show_nexthop_json_helper(struct json_object *json_nexthop,
const struct nexthop *nexthop,
const struct route_node *rn,
const struct route_entry *re);
void show_nexthop_json_helper(struct json_object *json_nexthop, const struct nexthop *nexthop,
const struct route_node *rn, const struct route_entry *re,
bool brief);
void show_route_nexthop_helper(struct vty *vty, const struct route_node *rn,
const struct route_entry *re,
const struct nexthop *nexthop);
Expand Down
Loading