diff --git a/docs/meson.build b/docs/meson.build index 232f68ac5..ae97f3566 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -75,9 +75,8 @@ custom_target( # The list is hardcoded since we can't run grcli during meson configuration. grcli_commands = [ 'address', 'affinity', 'conntrack', 'dnat44', 'events', 'fdb', 'flood', - 'graph', 'interface', 'log', 'nexthop', 'ping', 'ping6', 'route', - 'router-advert', 'snat44', 'stats', 'trace', 'traceroute', 'traceroute6', - 'tunsrc', + 'graph', 'interface', 'log', 'nexthop', 'ping', 'route', 'router-advert', + 'snat44', 'stats', 'trace', 'traceroute', 'tunsrc', ] foreach cmd : grcli_commands diff --git a/modules/infra/cli/gr_cli_l3.h b/modules/infra/cli/gr_cli_l3.h index 2dfb14fcd..c647e445c 100644 --- a/modules/infra/cli/gr_cli_l3.h +++ b/modules/infra/cli/gr_cli_l3.h @@ -53,5 +53,13 @@ struct cli_addr_ops { STAILQ_ENTRY(cli_addr_ops) next; }; +struct cli_icmp_ops { + addr_family_t af; + cmd_cb_t ping; + cmd_cb_t traceroute; + STAILQ_ENTRY(cli_icmp_ops) next; +}; + void cli_route_ops_register(struct cli_route_ops *); void cli_addr_ops_register(struct cli_addr_ops *); +void cli_icmp_ops_register(struct cli_icmp_ops *); diff --git a/modules/infra/cli/icmp.c b/modules/infra/cli/icmp.c new file mode 100644 index 000000000..d774940d2 --- /dev/null +++ b/modules/infra/cli/icmp.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026 Robin Jarry + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +static STAILQ_HEAD(, cli_icmp_ops) icmp_ops = STAILQ_HEAD_INITIALIZER(icmp_ops); + +void cli_icmp_ops_register(struct cli_icmp_ops *ops) { + assert(ops != NULL); + assert(ops->ping != NULL); + assert(ops->traceroute != NULL); + struct cli_icmp_ops *o; + STAILQ_FOREACH (o, &icmp_ops, next) + assert(ops->af != o->af); + STAILQ_INSERT_TAIL(&icmp_ops, ops, next); +} + +static cmd_status_t ping(struct gr_api_client *c, const struct ec_pnode *p) { + struct cli_icmp_ops *ops; + uint8_t ip[16]; + + STAILQ_FOREACH (ops, &icmp_ops, next) { + if (arg_ip(p, "DEST", ip, ops->af) == 0) + return ops->ping(c, p); + } + + errno = ENOPROTOOPT; + return CMD_ERROR; +} + +static cmd_status_t traceroute(struct gr_api_client *c, const struct ec_pnode *p) { + struct cli_icmp_ops *ops; + uint8_t ip[16]; + + STAILQ_FOREACH (ops, &icmp_ops, next) { + if (arg_ip(p, "DEST", ip, ops->af) == 0) + return ops->traceroute(c, p); + } + + errno = ENOPROTOOPT; + return CMD_ERROR; +} + +static int ctx_init(struct ec_node *root) { + int ret; + + ret = CLI_COMMAND( + CLI_CONTEXT( + root, CTX_ARG("ping", "Send ICMP{v6} echo requests and wait for replies.") + ), + "DEST [(vrf VRF),(count COUNT),(delay DELAY),(iface IFACE),(ident IDENT)]", + ping, + "Send ICMP{v6} echo requests and wait for replies.", + with_help("IP{v6} destination address.", ec_node_re("DEST", IP_ANY_RE)), + with_help( + "Output interface name.", + ec_node_dyn("IFACE", complete_iface_names, INT2PTR(GR_IFACE_TYPE_UNDEF)) + ), + with_help("L3 routing domain name.", ec_node_dyn("VRF", complete_vrf_names, NULL)), + with_help("Number of packets to send.", ec_node_uint("COUNT", 1, UINT16_MAX, 10)), + with_help("Delay in ms between icmp6 echo.", ec_node_uint("DELAY", 0, 10000, 10)), + with_help( + "Icmp ident field (default: random).", + ec_node_uint("IDENT", 1, UINT16_MAX, 10) + ) + ); + if (ret < 0) + return ret; + + ret = CLI_COMMAND( + CLI_CONTEXT(root, CTX_ARG("traceroute", "Discover IP{v6} intermediate gateways.")), + "DEST [(vrf VRF),(iface IFACE),(ident IDENT)]", + traceroute, + "Discover IP{v6} intermediate gateways.", + with_help("IP{v6} destination address.", ec_node_re("DEST", IP_ANY_RE)), + with_help("L3 routing domain name.", ec_node_dyn("VRF", complete_vrf_names, NULL)), + with_help( + "Output interface name.", + ec_node_dyn("IFACE", complete_iface_names, INT2PTR(GR_IFACE_TYPE_UNDEF)) + ), + with_help( + "Icmp ident field (default: random).", + ec_node_uint("IDENT", 1, UINT16_MAX, 10) + ) + ); + + return ret; +} + +static struct cli_context ctx = { + .name = "icmp", + .init = ctx_init, +}; + +static void __attribute__((constructor, used)) init(void) { + cli_context_register(&ctx); +} diff --git a/modules/infra/cli/meson.build b/modules/infra/cli/meson.build index 9c1e6b608..911031c36 100644 --- a/modules/infra/cli/meson.build +++ b/modules/infra/cli/meson.build @@ -7,6 +7,7 @@ cli_src += files( 'bond.c', 'events.c', 'graph.c', + 'icmp.c', 'iface.c', 'vrf.c', 'nexthop.c', diff --git a/modules/ip/cli/icmp.c b/modules/ip/cli/icmp.c index f24ea0d40..25bb63bc4 100644 --- a/modules/ip/cli/icmp.c +++ b/modules/ip/cli/icmp.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -135,7 +136,7 @@ static cmd_status_t ping(struct gr_api_client *c, const struct ec_pnode *p) { uint16_t ident = random(); uint16_t msdelay = 1000; - if (arg_ip4(p, "IP", &req.addr) < 0) + if (arg_ip4(p, "DEST", &req.addr) < 0) return CMD_ERROR; if (arg_vrf(c, p, "VRF", &req.vrf) < 0) return CMD_ERROR; @@ -162,7 +163,7 @@ static cmd_status_t traceroute(struct gr_api_client *c, const struct ec_pnode *p cmd_status_t ret = CMD_SUCCESS; uint16_t ident = random(); - if (arg_ip4(p, "IP", &req.addr) < 0) + if (arg_ip4(p, "DEST", &req.addr) < 0) return CMD_ERROR; if ((ret = arg_u16(p, "IDENT", &ident)) < 0 && ret != ENOENT) return CMD_ERROR; @@ -180,49 +181,12 @@ static cmd_status_t traceroute(struct gr_api_client *c, const struct ec_pnode *p return ret; } -static int ctx_init(struct ec_node *root) { - int ret; - - ret = CLI_COMMAND( - CLI_CONTEXT( - root, CTX_ARG("ping", "Send IPv4 ICMP echo requests and wait for replies.") - ), - "IP [(vrf VRF),(count COUNT),(delay DELAY),(ident IDENT)]", - ping, - "Send IPv4 ICMP echo requests and wait for replies.", - with_help("IPv4 destination address.", ec_node_re("IP", IPV4_RE)), - with_help("L3 routing domain name.", ec_node_dyn("VRF", complete_vrf_names, NULL)), - with_help("Number of packets to send.", ec_node_uint("COUNT", 1, UINT16_MAX, 10)), - with_help("Delay in ms between icmp echo.", ec_node_uint("DELAY", 0, 10000, 10)), - with_help( - "Icmp ident field (default: random).", - ec_node_uint("IDENT", 1, UINT16_MAX, 10) - ) - ); - if (ret < 0) - return ret; - - ret = CLI_COMMAND( - CLI_CONTEXT(root, CTX_ARG("traceroute", "Discover IPv4 intermediate gateways.")), - "IP [(ident IDENT),(vrf VRF)]", - traceroute, - "Discover IPv4 intermediate gateways.", - with_help("IPv4 destination address.", ec_node_re("IP", IPV4_RE)), - with_help( - "Icmp ident field (default: random).", - ec_node_uint("IDENT", 1, UINT16_MAX, 10) - ), - with_help("L3 routing domain name.", ec_node_dyn("VRF", complete_vrf_names, NULL)) - ); - - return ret; -} - -static struct cli_context ctx = { - .name = "ping", - .init = ctx_init, +static struct cli_icmp_ops ops = { + .af = GR_AF_IP4, + .ping = ping, + .traceroute = traceroute, }; static void __attribute__((constructor, used)) init(void) { - cli_context_register(&ctx); + cli_icmp_ops_register(&ops); } diff --git a/modules/ip6/cli/icmp6.c b/modules/ip6/cli/icmp6.c index 9f6a52fb2..2fdd5f197 100644 --- a/modules/ip6/cli/icmp6.c +++ b/modules/ip6/cli/icmp6.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -193,57 +194,13 @@ static cmd_status_t traceroute(struct gr_api_client *c, const struct ec_pnode *p return ret; } -static int ctx_init(struct ec_node *root) { - int ret; - - ret = CLI_COMMAND( - CLI_CONTEXT( - root, CTX_ARG("ping6", "Send ICMPv6 echo requests and wait for replies.") - ), - "DEST [(vrf VRF),(count COUNT),(delay DELAY),(iface IFACE),(ident IDENT)]", - ping, - "Send ICMPv6 echo requests and wait for replies.", - with_help("IPv6 destination address.", ec_node_re("DEST", IPV6_RE)), - with_help( - "Output interface name.", - ec_node_dyn("IFACE", complete_iface_names, INT2PTR(GR_IFACE_TYPE_UNDEF)) - ), - with_help("L3 routing domain name.", ec_node_dyn("VRF", complete_vrf_names, NULL)), - with_help("Number of packets to send.", ec_node_uint("COUNT", 1, UINT16_MAX, 10)), - with_help("Delay in ms between icmp6 echo.", ec_node_uint("DELAY", 0, 10000, 10)), - with_help( - "Icmp ident field (default: random).", - ec_node_uint("IDENT", 1, UINT16_MAX, 10) - ) - ); - if (ret < 0) - return ret; - - ret = CLI_COMMAND( - CLI_CONTEXT(root, CTX_ARG("traceroute6", "Discover IPv6 intermediate gateways.")), - "DEST [(vrf VRF),(iface IFACE),(ident IDENT)]", - traceroute, - "Discover IPv6 intermediate gateways.", - with_help("IPv6 destination address.", ec_node_re("DEST", IPV6_RE)), - with_help("L3 routing domain name.", ec_node_dyn("VRF", complete_vrf_names, NULL)), - with_help( - "Output interface name.", - ec_node_dyn("IFACE", complete_iface_names, INT2PTR(GR_IFACE_TYPE_UNDEF)) - ), - with_help( - "Icmp ident field (default: random).", - ec_node_uint("IDENT", 1, UINT16_MAX, 10) - ) - ); +static struct cli_icmp_ops ops = { + .af = GR_AF_IP6, + .ping = ping, + .traceroute = traceroute, - return ret; -} - -static struct cli_context ctx = { - .name = "ping6", - .init = ctx_init, }; static void __attribute__((constructor, used)) init(void) { - cli_context_register(&ctx); + cli_icmp_ops_register(&ops); } diff --git a/smoke/ip6_builtin_icmp_test.sh b/smoke/ip6_builtin_icmp_test.sh index ddd0460bd..f3faa58f6 100755 --- a/smoke/ip6_builtin_icmp_test.sh +++ b/smoke/ip6_builtin_icmp_test.sh @@ -20,11 +20,11 @@ done sleep 3 # wait for DAD -grcli ping6 fd00:ba4:0::2 count 10 delay 100 -grcli ping6 fd00:ba4:1::2 count 3 delay 10 +grcli ping fd00:ba4:0::2 count 10 delay 100 +grcli ping fd00:ba4:1::2 count 3 delay 10 # Expect this test to fail -grcli ping6 fd00:baa::1 count 1 && fail "ping to unknown route succeeded" -grcli ping6 fd00:ba4:1::3 count 1 && fail "ping to non-existent host succeeded" +grcli ping fd00:baa::1 count 1 && fail "ping to unknown route succeeded" +grcli ping fd00:ba4:1::3 count 1 && fail "ping to non-existent host succeeded" -grcli traceroute6 fd00:ba4:1::2 +grcli traceroute fd00:ba4:1::2 diff --git a/smoke/ospf6_frr_manualtest.sh b/smoke/ospf6_frr_manualtest.sh index bc2ac171b..c67f584e0 100755 --- a/smoke/ospf6_frr_manualtest.sh +++ b/smoke/ospf6_frr_manualtest.sh @@ -95,4 +95,4 @@ while ! vtysh -c 'show ipv6 route ospf6 json' | jq '."2001:db8:1000::/64"[0].pro attempts=$((attempts - 1)) done -grcli ping6 2001:db8:1000::1 count 3 delay 10 +grcli ping 2001:db8:1000::1 count 3 delay 10