net: shell: Make it possible to abort ping command

Rewrite the "net ping" command handling, to allow the command to be
aborted during execution. This includes:

* Using shell bypass mode to capture input whilst the ping is active.
* Using system workqueue to send individual ping requestes, instead of
  sending ping requests directly from shell thread, in a blocking
  manner. This is needed because in order to receive input in the
  registered bypass handler, the shell thread must be unblocked to
  process the input.
* The bypass mode is left after receiveing `CTRL-C` character (which
  cancels the ping), after receiving all expected Ping replies or after
  the timeout occurs.

Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
This commit is contained in:
Robert Lubos 2022-12-14 15:08:27 +01:00 committed by Fabio Baltieri
commit 1448923be3

View file

@ -3841,8 +3841,21 @@ static int cmd_net_nbr(const struct shell *shell, size_t argc, char *argv[])
#if defined(CONFIG_NET_IP)
K_SEM_DEFINE(ping_timeout, 0, 1);
static const struct shell *shell_for_ping;
static struct ping_context {
struct k_work_delayable work;
struct net_addr addr;
struct net_if *iface;
const struct shell *sh;
/* Ping parameters */
uint32_t count;
uint32_t interval;
uint32_t sequence;
uint16_t payload_size;
uint8_t tos;
} ping_ctx;
static void ping_done(struct ping_context *ctx);
#if defined(CONFIG_NET_NATIVE_IPV6)
@ -3897,7 +3910,7 @@ static enum net_verdict handle_ipv6_echo_reply(struct net_pkt *pkt,
);
}
PR_SHELL(shell_for_ping, "%d bytes from %s to %s: icmp_seq=%d ttl=%d "
PR_SHELL(ping_ctx.sh, "%d bytes from %s to %s: icmp_seq=%d ttl=%d "
#ifdef CONFIG_IEEE802154
"rssi=%d "
#endif
@ -3913,77 +3926,14 @@ static enum net_verdict handle_ipv6_echo_reply(struct net_pkt *pkt,
#endif
time_buf);
k_sem_give(&ping_timeout);
if (ntohs(icmp_echo->sequence) == ping_ctx.count) {
ping_done(&ping_ctx);
}
net_pkt_unref(pkt);
return NET_OK;
}
static int ping_ipv6(const struct shell *shell,
char *host,
unsigned int count,
unsigned int interval,
uint8_t tos,
int payload_size,
int iface_idx)
{
struct net_if *iface = net_if_get_by_index(iface_idx);
int ret = 0;
struct in6_addr ipv6_target;
struct net_nbr *nbr;
#if defined(CONFIG_NET_ROUTE)
struct net_route_entry *route;
#endif
if (net_addr_pton(AF_INET6, host, &ipv6_target) < 0) {
return -EINVAL;
}
net_icmpv6_register_handler(&ping6_handler);
if (!iface) {
iface = net_if_ipv6_select_src_iface(&ipv6_target);
if (!iface) {
nbr = net_ipv6_nbr_lookup(NULL, &ipv6_target);
if (nbr) {
iface = nbr->iface;
} else {
iface = net_if_get_default();
}
}
}
#if defined(CONFIG_NET_ROUTE)
route = net_route_lookup(NULL, &ipv6_target);
if (route) {
iface = route->iface;
}
#endif
PR("PING %s\n", host);
for (int i = 0; i < count; ++i) {
ret = net_icmpv6_send_echo_request(iface,
&ipv6_target,
sys_rand32_get(),
i,
tos,
NULL,
payload_size);
if (ret) {
break;
}
k_msleep(interval);
}
remove_ipv6_ping_handler();
return ret;
}
#else
#define ping_ipv6(...) -ENOTSUP
#define remove_ipv6_ping_handler()
#endif /* CONFIG_NET_IPV6 */
@ -4040,7 +3990,7 @@ static enum net_verdict handle_ipv4_echo_reply(struct net_pkt *pkt,
);
}
PR_SHELL(shell_for_ping, "%d bytes from %s to %s: icmp_seq=%d ttl=%d "
PR_SHELL(ping_ctx.sh, "%d bytes from %s to %s: icmp_seq=%d ttl=%d "
"%s\n",
ntohs(ip_hdr->len) - net_pkt_ipv6_ext_len(pkt) -
NET_ICMPH_LEN,
@ -4050,57 +4000,15 @@ static enum net_verdict handle_ipv4_echo_reply(struct net_pkt *pkt,
ip_hdr->ttl,
time_buf);
k_sem_give(&ping_timeout);
if (ntohs(icmp_echo->sequence) == ping_ctx.count) {
ping_done(&ping_ctx);
}
net_pkt_unref(pkt);
return NET_OK;
}
static int ping_ipv4(const struct shell *shell,
char *host,
unsigned int count,
unsigned int interval,
uint8_t tos,
int payload_size,
int iface_idx)
{
struct in_addr ipv4_target;
struct net_if *iface = net_if_get_by_index(iface_idx);
int ret = 0;
if (net_addr_pton(AF_INET, host, &ipv4_target) < 0) {
return -EINVAL;
}
if (!iface) {
iface = net_if_ipv4_select_src_iface(&ipv4_target);
}
net_icmpv4_register_handler(&ping4_handler);
PR("PING %s\n", host);
for (int i = 0; i < count; ++i) {
ret = net_icmpv4_send_echo_request(iface,
&ipv4_target,
sys_rand32_get(),
i,
tos,
NULL,
payload_size);
if (ret) {
break;
}
k_msleep(interval);
}
remove_ipv4_ping_handler();
return ret;
}
#else
#define ping_ipv4(...) -ENOTSUP
#define remove_ipv4_ping_handler()
#endif /* CONFIG_NET_IPV4 */
@ -4132,6 +4040,133 @@ static int parse_arg(size_t *i, size_t argc, char *argv[])
return res;
}
static void ping_cleanup(struct ping_context *ctx)
{
remove_ipv6_ping_handler();
remove_ipv4_ping_handler();
shell_set_bypass(ctx->sh, NULL);
}
static void ping_done(struct ping_context *ctx)
{
k_work_cancel_delayable(&ctx->work);
ping_cleanup(ctx);
/* Dummy write to refresh the prompt. */
shell_fprintf(ctx->sh, SHELL_NORMAL, "");
}
static void ping_work(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct ping_context *ctx =
CONTAINER_OF(dwork, struct ping_context, work);
const struct shell *shell = ctx->sh;
int ret;
ctx->sequence++;
if (ctx->sequence > ctx->count) {
PR_INFO("Ping timeout\n");
ping_done(ctx);
return;
}
if (ctx->addr.family == AF_INET6) {
ret = net_icmpv6_send_echo_request(ctx->iface,
&ctx->addr.in6_addr,
sys_rand32_get(),
ctx->sequence,
ctx->tos,
NULL,
ctx->payload_size);
} else {
ret = net_icmpv4_send_echo_request(ctx->iface,
&ctx->addr.in_addr,
sys_rand32_get(),
ctx->sequence,
ctx->tos,
NULL,
ctx->payload_size);
}
if (ret != 0) {
PR_WARNING("Failed to send ping, err: %d", ret);
return;
}
if (ctx->sequence < ctx->count) {
k_work_reschedule(&ctx->work, K_MSEC(ctx->interval));
} else {
k_work_reschedule(&ctx->work, K_SECONDS(2));
}
}
#define ASCII_CTRL_C 0x03
static void ping_bypass(const struct shell *sh, uint8_t *data, size_t len)
{
ARG_UNUSED(sh);
for (size_t i = 0; i < len; i++) {
if (data[i] == ASCII_CTRL_C) {
k_work_cancel_delayable(&ping_ctx.work);
ping_cleanup(&ping_ctx);
break;
}
}
}
static struct net_if *ping_select_iface(int id, struct net_addr *target)
{
struct net_if *iface = net_if_get_by_index(id);
if (iface != NULL) {
goto out;
}
if (IS_ENABLED(CONFIG_NET_IPV4) && target->family == AF_INET) {
iface = net_if_ipv4_select_src_iface(&target->in_addr);
if (iface != NULL) {
goto out;
}
iface = net_if_get_default();
goto out;
}
if (IS_ENABLED(CONFIG_NET_IPV6) && target->family == AF_INET6) {
struct net_nbr *nbr;
#if defined(CONFIG_NET_ROUTE)
struct net_route_entry *route;
#endif
iface = net_if_ipv6_select_src_iface(&target->in6_addr);
if (iface != NULL) {
goto out;
}
nbr = net_ipv6_nbr_lookup(NULL, &target->in6_addr);
if (nbr) {
iface = nbr->iface;
goto out;
}
#if defined(CONFIG_NET_ROUTE)
route = net_route_lookup(NULL, &target->in6_addr);
if (route) {
iface = route->iface;
goto out;
}
#endif
iface = net_if_get_default();
}
out:
return iface;
}
#endif /* CONFIG_NET_IP */
static int cmd_net_ping(const struct shell *shell, size_t argc, char *argv[])
@ -4144,7 +4179,6 @@ static int cmd_net_ping(const struct shell *shell, size_t argc, char *argv[])
return -EOPNOTSUPP;
#else
char *host = NULL;
int ret;
int count = 3;
int interval = 1000;
@ -4215,44 +4249,35 @@ static int cmd_net_ping(const struct shell *shell, size_t argc, char *argv[])
return -ENOEXEC;
}
shell_for_ping = shell;
memset(&ping_ctx, 0, sizeof(ping_ctx));
if (IS_ENABLED(CONFIG_NET_IPV6)) {
ret = ping_ipv6(shell, host, count, interval, tos, payload_size,
iface_idx);
if (!ret) {
goto wait_reply;
} else if (ret == -EIO) {
PR_WARNING("Cannot send IPv6 ping\n");
return -ENOEXEC;
}
k_work_init_delayable(&ping_ctx.work, ping_work);
ping_ctx.sh = shell;
ping_ctx.count = count;
ping_ctx.interval = interval;
ping_ctx.tos = tos;
ping_ctx.payload_size = payload_size;
if (IS_ENABLED(CONFIG_NET_IPV6) &&
net_addr_pton(AF_INET6, host, &ping_ctx.addr.in6_addr) == 0) {
ping_ctx.addr.family = AF_INET6;
net_icmpv6_register_handler(&ping6_handler);
} else if (IS_ENABLED(CONFIG_NET_IPV4) &&
net_addr_pton(AF_INET, host, &ping_ctx.addr.in_addr) == 0) {
ping_ctx.addr.family = AF_INET;
net_icmpv4_register_handler(&ping4_handler);
} else {
PR_WARNING("Invalid IP address\n");
return 0;
}
if (IS_ENABLED(CONFIG_NET_IPV4)) {
ret = ping_ipv4(shell, host, count, interval, tos, payload_size,
iface_idx);
if (ret) {
if (ret == -EIO || ret == -ENETUNREACH) {
PR_WARNING("Cannot send IPv4 ping\n");
} else if (ret == -EINVAL) {
PR_WARNING("Invalid IP address\n");
} else if (ret == -ENOTSUP) {
PR_WARNING("Feature is not supported\n");
}
ping_ctx.iface = ping_select_iface(iface_idx, &ping_ctx.addr);
return -ENOEXEC;
}
}
PR("PING %s\n", host);
wait_reply:
ret = k_sem_take(&ping_timeout, K_SECONDS(2));
if (ret == -EAGAIN) {
PR_INFO("Ping timeout\n");
remove_ipv6_ping_handler();
remove_ipv4_ping_handler();
return -ETIMEDOUT;
}
shell_set_bypass(shell, ping_bypass);
k_work_reschedule(&ping_ctx.work, K_NO_WAIT);
return 0;
#endif