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:
parent
55af3dd075
commit
1448923be3
1 changed files with 174 additions and 149 deletions
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue