Skip to content

vtysh, lib: fix 'show ip[v6] prefix-list ... json' JSON display by moving it to vtysh to fix formatting issue#151

Open
suphawitwanl wants to merge 5 commits intoaitest-base-pr-15750from
aitest-test-pr-15750
Open

vtysh, lib: fix 'show ip[v6] prefix-list ... json' JSON display by moving it to vtysh to fix formatting issue#151
suphawitwanl wants to merge 5 commits intoaitest-base-pr-15750from
aitest-test-pr-15750

Conversation

@suphawitwanl
Copy link

Json output is not valid for show ip prefix-list ... json commands, because it goes through each running daemon and for each one it calls vty_show_prefix_list creating a new json object. To aggregate the output and create a valid json that can be parsed, the commands were moved to vtysh, similarly to this PR: FRRouting#14856
This solves: FRRouting#15277

Before:

{
  "ZEBRA":{
    "DEFAULT":{
      "addressFamily":"IPv4",
      "entries":[
        {
          "sequenceNumber":10,
          "type":"permit",
          "prefix":"0.0.0.0/0"
        }
      ]
    }
  }
}
{
  "OSPF":{
    "DEFAULT":{
      "addressFamily":"IPv4",
      "entries":[
        {
          "sequenceNumber":10,
          "type":"permit",
          "prefix":"0.0.0.0/0"
        }
      ]
    }
  }
}
{
  "BGP":{
    "DEFAULT":{
      "addressFamily":"IPv4",
      "entries":[
        {
          "sequenceNumber":10,
          "type":"permit",
          "prefix":"0.0.0.0/0"
        }
      ]
    }
  }
}

JSON is not valid.

After:

{"zebra":{
  "DEFAULT":{
    "addressFamily":"IPv4",
    "entries":[
      {
        "sequenceNumber":10,
        "type":"permit",
        "prefix":"0.0.0.0/0"
      }
    ]
  }
}
,"ospfd":{
  "DEFAULT":{
    "addressFamily":"IPv4",
    "entries":[
      {
        "sequenceNumber":10,
        "type":"permit",
        "prefix":"0.0.0.0/0"
      }
    ]
  }
}
,"bgpd":{
  "DEFAULT":{
    "addressFamily":"IPv4",
    "entries":[
      {
        "sequenceNumber":10,
        "type":"permit",
        "prefix":"0.0.0.0/0"
      }
    ]
  }
}
}

JSON is valid.

Analogous behavior for:

  • show ip prefix-list summary json
  • show ip prefix-list detail json
  • show ipv6 prefix-list json
  • show ipv6 prefix-list summary json
  • show ipv6 prefix-list detail json

piotrsuchy and others added 2 commits April 21, 2024 23:40
…ng it to vtysh

Json output is not valid for 'show ip[v6] prefix-list ... json' commands,
as it goes through all the running daemons and for each one it calls
'vty_show_prefix_list' creating a new json object. To aggreagate the output
and create a valid json that can be parsed, the commands were moved to vtysh.

Before:
{
  "ZEBRA":{
    "DEFAULT":{
      "addressFamily":"IPv4",
      "entries":[
        {
          "sequenceNumber":10,
          "type":"permit",
          "prefix":"0.0.0.0/0"
        }
      ]
    }
  }
}
{
  "OSPF":{
    "DEFAULT":{
      "addressFamily":"IPv4",
      "entries":[
        {
          "sequenceNumber":10,
          "type":"permit",
          "prefix":"0.0.0.0/0"
        }
      ]
    }
  }
}
{
  "BGP":{
    "DEFAULT":{
      "addressFamily":"IPv4",
      "entries":[
        {
          "sequenceNumber":10,
          "type":"permit",
          "prefix":"0.0.0.0/0"
        }
      ]
    }
  }
}

After:
{"zebra":{
  "DEFAULT":{
    "addressFamily":"IPv4",
    "entries":[
      {
        "sequenceNumber":10,
        "type":"permit",
        "prefix":"0.0.0.0/0"
      }
    ]
  }
}
,"ospfd":{
  "DEFAULT":{
    "addressFamily":"IPv4",
    "entries":[
      {
        "sequenceNumber":10,
        "type":"permit",
        "prefix":"0.0.0.0/0"
      }
    ]
  }
}
,"bgpd":{
  "DEFAULT":{
    "addressFamily":"IPv4",
    "entries":[
      {
        "sequenceNumber":10,
        "type":"permit",
        "prefix":"0.0.0.0/0"
      }
    ]
  }
}
}

Signed-off-by: Piotr Suchy <piotrsuchy@proton.me>
@suphawitwanl suphawitwanl reopened this Mar 4, 2026
@greptile-apps
Copy link

greptile-apps bot commented Mar 4, 2026

Greptile Summary

This PR fixes malformed JSON output for show ip[v6] prefix-list ... json commands by moving the aggregation logic from individual daemons into vtysh. The daemon-side changes strip the per-daemon frr_protoname wrapper from the JSON object, and six affected DEFPY commands are marked as DEFPY_NOSH so vtysh can aggregate their output. vtysh then wraps each daemon's response under its process name key, producing a single valid JSON object.

Key changes:

  • lib/plist.c: Six show commands converted to DEFPY_NOSH, removing json_proto wrapping. The daemon-level logic is correct.
  • vtysh/vtysh.c: Adds show_prefix_list_send() and six DEFPY handlers. Critical issue: the commands are installed only in ENABLE_NODE, but they were previously available in VIEW_NODE in each daemon — non-privileged vtysh users will now get "Unknown command".

The core JSON aggregation logic is sound, but the vtysh-side installation introduces a user-visible regression by breaking access for non-privileged users.

Confidence Score: 1/5

  • Not safe to merge — contains a VIEW_NODE regression that breaks show ip[v6] prefix-list for non-privileged vtysh users.
  • The VIEW_NODE regression is a clear user-visible breaking change that silently downgrades access for unprivileged users from working commands to "Unknown command" errors. This must be fixed before merge.
  • vtysh/vtysh.c (missing VIEW_NODE registrations for the six prefix-list show commands)

Last reviewed commit: c89fd58

};

extern enum vtysh_write_integrated vtysh_write_integrated;
extern enum display_type display_type;
Copy link

Choose a reason for hiding this comment

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

Spurious extern variable declaration

extern enum display_type display_type; declares a global variable named display_type of type enum display_type, but no such variable is ever defined anywhere in the codebase. This is a copy-paste error modelled after the vtysh_write_integrated pattern — but display_type is meant to be a type name, not a variable. While this won't cause a compile error because the symbol is never referenced, it is incorrect and will cause a linker error the moment any translation unit actually uses it.

Suggested change
extern enum display_type display_type;
extern enum vtysh_write_integrated vtysh_write_integrated;
Prompt To Fix With AI
This is a comment left during a code review.
Path: vtysh/vtysh.h
Line: 107

Comment:
**Spurious extern variable declaration**

`extern enum display_type display_type;` declares a global *variable* named `display_type` of type `enum display_type`, but no such variable is ever defined anywhere in the codebase. This is a copy-paste error modelled after the `vtysh_write_integrated` pattern — but `display_type` is meant to be a *type name*, not a variable. While this won't cause a compile error because the symbol is never referenced, it is incorrect and will cause a linker error the moment any translation unit actually uses it.

```suggestion
extern enum vtysh_write_integrated vtysh_write_integrated;
```

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

Comment on lines +3485 to +3490
if (afi == AFI_IP)
snprintf(command_line, sizeof(command_line),
"do show ip prefix-list ");
else if (afi == AFI_IP6)
snprintf(command_line, sizeof(command_line),
"do show ipv6 prefix-list ");
Copy link

Choose a reason for hiding this comment

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

Uninitialized buffer if afi is neither AFI_IP nor AFI_IP6

command_line is a local array that is only initialised via snprintf inside the if (afi == AFI_IP) or else if (afi == AFI_IP6) branches. If afi holds any other value, the buffer is left uninitialised and all subsequent strlcat calls operate on garbage memory, invoking undefined behaviour.

Although the current callers always pass AFI_IP or AFI_IP6, the static analysis risk and future-proofing argue for an else branch with an early return, or at minimum an assert/return guard:

	if (afi == AFI_IP)
		snprintf(command_line, sizeof(command_line),
			 "do show ip prefix-list ");
	else if (afi == AFI_IP6)
		snprintf(command_line, sizeof(command_line),
			 "do show ipv6 prefix-list ");
	else
		return;
Prompt To Fix With AI
This is a comment left during a code review.
Path: vtysh/vtysh.c
Line: 3485-3490

Comment:
**Uninitialized buffer if afi is neither AFI_IP nor AFI_IP6**

`command_line` is a local array that is only initialised via `snprintf` inside the `if (afi == AFI_IP)` or `else if (afi == AFI_IP6)` branches. If `afi` holds any other value, the buffer is left uninitialised and all subsequent `strlcat` calls operate on garbage memory, invoking undefined behaviour.

Although the current callers always pass `AFI_IP` or `AFI_IP6`, the static analysis risk and future-proofing argue for an `else` branch with an early return, or at minimum an `assert`/`return` guard:

```c
	if (afi == AFI_IP)
		snprintf(command_line, sizeof(command_line),
			 "do show ip prefix-list ");
	else if (afi == AFI_IP6)
		snprintf(command_line, sizeof(command_line),
			 "do show ipv6 prefix-list ");
	else
		return;
```

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

Comment on lines +71 to +74
#define VTYSH_PREFIX_LIST_SHOW \
VTYSH_ZEBRA | VTYSH_RIPD | VTYSH_RIPNGD | VTYSH_OSPFD | VTYSH_OSPF6D | \
VTYSH_BGPD | VTYSH_ISISD | VTYSH_PIMD | VTYSH_EIGRPD | \
VTYSH_FABRICD
Copy link

Choose a reason for hiding this comment

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

VTYSH_PREFIX_LIST_SHOW duplicates VTYSH_RMAP_SHOW exactly

Both macros expand to the identical bitmask. Defining them separately creates a latent maintenance hazard: if a new daemon is added to one but not the other, the coverage silently diverges. Consider defining one in terms of the other, or at least adding a comment tying them together:

/* Same set of daemons as VTYSH_RMAP_SHOW — keep in sync */
#define VTYSH_PREFIX_LIST_SHOW VTYSH_RMAP_SHOW
Prompt To Fix With AI
This is a comment left during a code review.
Path: vtysh/vtysh.h
Line: 71-74

Comment:
**`VTYSH_PREFIX_LIST_SHOW` duplicates `VTYSH_RMAP_SHOW` exactly**

Both macros expand to the identical bitmask. Defining them separately creates a latent maintenance hazard: if a new daemon is added to one but not the other, the coverage silently diverges. Consider defining one in terms of the other, or at least adding a comment tying them together:

```c
/* Same set of daemons as VTYSH_RMAP_SHOW — keep in sync */
#define VTYSH_PREFIX_LIST_SHOW VTYSH_RMAP_SHOW
```

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

@suphawitwanl suphawitwanl reopened this Mar 6, 2026
Comment on lines +97 to +104
enum display_type {
normal_display,
summary_display,
detail_display,
sequential_display,
longer_display,
first_match_display
};
Copy link

Choose a reason for hiding this comment

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

Duplicate enum display_type definition

enum display_type is already defined as a file-private type in lib/plist.c at line 912 with identical enumerators. By adding a second definition here in vtysh/vtysh.h, two independent definitions now exist in separate translation units that must be kept manually in sync.

If a new display type is ever added to lib/plist.c but not vtysh/vtysh.h (or vice versa), the mismatch will be silent — the code will compile cleanly but show_prefix_list_send will use the wrong enum values.

The correct fix is to move the definition into lib/plist.h (the public header for plist.c), and include lib/plist.h from vtysh/vtysh.h or vtysh/vtysh.c, removing the duplicate:

/* In lib/plist.h */
enum display_type {
	normal_display,
	summary_display,
	detail_display,
	sequential_display,
	longer_display,
	first_match_display
};

Then remove the definition from both lib/plist.c and vtysh/vtysh.h, and add #include "lib/plist.h" where needed.

Prompt To Fix With AI
This is a comment left during a code review.
Path: vtysh/vtysh.h
Line: 97-104

Comment:
**Duplicate `enum display_type` definition**

`enum display_type` is already defined as a file-private type in `lib/plist.c` at line 912 with identical enumerators. By adding a second definition here in `vtysh/vtysh.h`, two independent definitions now exist in separate translation units that must be kept manually in sync.

If a new display type is ever added to `lib/plist.c` but not `vtysh/vtysh.h` (or vice versa), the mismatch will be silent — the code will compile cleanly but `show_prefix_list_send` will use the wrong enum values.

The correct fix is to move the definition into `lib/plist.h` (the public header for `plist.c`), and include `lib/plist.h` from `vtysh/vtysh.h` or `vtysh/vtysh.c`, removing the duplicate:

```c
/* In lib/plist.h */
enum display_type {
	normal_display,
	summary_display,
	detail_display,
	sequential_display,
	longer_display,
	first_match_display
};
```

Then remove the definition from both `lib/plist.c` and `vtysh/vtysh.h`, and add `#include "lib/plist.h"` where needed.

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

Comment on lines +3521 to +3524
if (json && !first)
vty_out(vty, ",");
else
first = false;
Copy link

Choose a reason for hiding this comment

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

first flag never set when json=false

The else branch executes when !(json && !first) — which includes both "first connected daemon in JSON mode" AND "any connected daemon in non-JSON mode". As a result, when json=false, first is reset to false on every iteration but is never used to suppress a comma (since the if condition is already gated on json). While harmless in non-JSON mode, the problem shows up in JSON mode for edge cases where connected daemons appear after non-connected ones:

Consider the sequence: daemon A (not connected → skipped), daemon B (connected, first=true):

  • json && !firsttrue && truefalseelse: first = false
  • Daemon B outputs without a leading comma ✓

Then daemon C (connected, first=false):

  • json && !firsttrue && truetrue → comma printed ✓

This path is actually correct. However, the non-JSON path means first is unconditionally set to false even on non-connected daemons that were skipped via continue. Since the continue skips both the flag check and the body, first is only mutated for connected daemons, keeping the logic intact.

The concern is that this non-obvious control flow is identical to the show_route_map_send template but could be confusing. A clearer formulation would be:

Suggested change
if (json && !first)
vty_out(vty, ",");
else
first = false;
if (json) {
if (!first)
vty_out(vty, ",");
else
first = false;
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: vtysh/vtysh.c
Line: 3521-3524

Comment:
**`first` flag never set when `json=false`**

The `else` branch executes when `!(json && !first)` — which includes both "first connected daemon in JSON mode" AND "any connected daemon in non-JSON mode". As a result, when `json=false`, `first` is reset to `false` on every iteration but is never used to suppress a comma (since the `if` condition is already gated on `json`). While harmless in non-JSON mode, the problem shows up in JSON mode for edge cases where connected daemons appear after non-connected ones:

Consider the sequence: daemon A (not connected → skipped), daemon B (connected, `first=true`):
- `json && !first``true && true``false``else: first = false`
- Daemon B outputs without a leading comma ✓

Then daemon C (connected, `first=false`):
- `json && !first``true && true``true` → comma printed ✓

This path is actually correct. However, the non-JSON path means `first` is unconditionally set to `false` even on non-connected daemons that were skipped via `continue`. Since the `continue` skips both the flag check and the body, `first` is only mutated for **connected** daemons, keeping the logic intact.

The concern is that this non-obvious control flow is identical to the `show_route_map_send` template but could be confusing. A clearer formulation would be:

```suggestion
		if (json) {
			if (!first)
				vty_out(vty, ",");
			else
				first = false;
		}
```

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

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@suphawitwanl suphawitwanl reopened this Mar 6, 2026
};

extern enum vtysh_write_integrated vtysh_write_integrated;
extern enum display_type display_type;
Copy link

Choose a reason for hiding this comment

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

extern enum display_type display_type; declares a global variable that is never defined anywhere in the codebase. This is a copy-paste error modeled after line 106 (extern enum vtysh_write_integrated vtysh_write_integrated;), but unlike that variable which has a real definition in vtysh.c, display_type is a type, not a variable. This declaration should be removed.

Prompt To Fix With AI
This is a comment left during a code review.
Path: vtysh/vtysh.h
Line: 107

Comment:
`extern enum display_type display_type;` declares a global *variable* that is never defined anywhere in the codebase. This is a copy-paste error modeled after line 106 (`extern enum vtysh_write_integrated vtysh_write_integrated;`), but unlike that variable which has a real definition in vtysh.c, `display_type` is a type, not a variable. This declaration should be removed.

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

Copy link
Author

Choose a reason for hiding this comment

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

@suphawitwanl
Copy link
Author

@greptileai

Comment on lines +5232 to +5237
install_element(ENABLE_NODE, &show_ip_prefix_list_cmd);
install_element(ENABLE_NODE, &show_ip_prefix_list_summary_cmd);
install_element(ENABLE_NODE, &show_ip_prefix_list_detail_cmd);
install_element(ENABLE_NODE, &show_ipv6_prefix_list_cmd);
install_element(ENABLE_NODE, &show_ipv6_prefix_list_summary_cmd);
install_element(ENABLE_NODE, &show_ipv6_prefix_list_detail_cmd);
Copy link

Choose a reason for hiding this comment

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

All six show ip[v6] prefix-list commands are registered in each daemon's CLI in VIEW_NODE (see lib/plist.c:1662-1665, 1681-1684). Before this PR, vtysh would forward the command to the daemons when it wasn't registered locally, so it remained accessible from the unprivileged VIEW prompt.

After this PR the vtysh-level handlers are installed only in ENABLE_NODE (lines 5232-5237). Any user who has not entered enable mode in vtysh will now get % Unknown command when running these show commands — a clear behavioural regression.

The reference PR FRRouting#14856 (show route-map) did not introduce a regression because show route-map was already gated behind ENABLE_NODE in each daemon. That is not the case here.

To preserve existing behaviour:

install_element(VIEW_NODE, &show_ip_prefix_list_cmd);
install_element(ENABLE_NODE, &show_ip_prefix_list_cmd);
install_element(VIEW_NODE, &show_ip_prefix_list_summary_cmd);
install_element(ENABLE_NODE, &show_ip_prefix_list_summary_cmd);
install_element(VIEW_NODE, &show_ip_prefix_list_detail_cmd);
install_element(ENABLE_NODE, &show_ip_prefix_list_detail_cmd);
install_element(VIEW_NODE, &show_ipv6_prefix_list_cmd);
install_element(ENABLE_NODE, &show_ipv6_prefix_list_cmd);
install_element(VIEW_NODE, &show_ipv6_prefix_list_summary_cmd);
install_element(ENABLE_NODE, &show_ipv6_prefix_list_summary_cmd);
install_element(VIEW_NODE, &show_ipv6_prefix_list_detail_cmd);
install_element(ENABLE_NODE, &show_ipv6_prefix_list_detail_cmd);
Prompt To Fix With AI
This is a comment left during a code review.
Path: vtysh/vtysh.c
Line: 5232-5237

Comment:
All six `show ip[v6] prefix-list` commands are registered in each daemon's CLI in `VIEW_NODE` (see `lib/plist.c:1662-1665, 1681-1684`). Before this PR, vtysh would forward the command to the daemons when it wasn't registered locally, so it remained accessible from the unprivileged VIEW prompt.

After this PR the vtysh-level handlers are installed **only** in `ENABLE_NODE` (lines 5232-5237). Any user who has not entered `enable` mode in vtysh will now get `% Unknown command` when running these show commands — a clear behavioural regression.

The reference PR #14856 (`show route-map`) did not introduce a regression because `show route-map` was already gated behind `ENABLE_NODE` in each daemon. That is not the case here.

To preserve existing behaviour:
```c
install_element(VIEW_NODE, &show_ip_prefix_list_cmd);
install_element(ENABLE_NODE, &show_ip_prefix_list_cmd);
install_element(VIEW_NODE, &show_ip_prefix_list_summary_cmd);
install_element(ENABLE_NODE, &show_ip_prefix_list_summary_cmd);
install_element(VIEW_NODE, &show_ip_prefix_list_detail_cmd);
install_element(ENABLE_NODE, &show_ip_prefix_list_detail_cmd);
install_element(VIEW_NODE, &show_ipv6_prefix_list_cmd);
install_element(ENABLE_NODE, &show_ipv6_prefix_list_cmd);
install_element(VIEW_NODE, &show_ipv6_prefix_list_summary_cmd);
install_element(ENABLE_NODE, &show_ipv6_prefix_list_summary_cmd);
install_element(VIEW_NODE, &show_ipv6_prefix_list_detail_cmd);
install_element(ENABLE_NODE, &show_ipv6_prefix_list_detail_cmd);
```

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