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)
|
#if defined(CONFIG_NET_IP)
|
||||||
|
|
||||||
K_SEM_DEFINE(ping_timeout, 0, 1);
|
static struct ping_context {
|
||||||
static const struct shell *shell_for_ping;
|
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)
|
#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
|
#ifdef CONFIG_IEEE802154
|
||||||
"rssi=%d "
|
"rssi=%d "
|
||||||
#endif
|
#endif
|
||||||
|
@ -3913,77 +3926,14 @@ static enum net_verdict handle_ipv6_echo_reply(struct net_pkt *pkt,
|
||||||
#endif
|
#endif
|
||||||
time_buf);
|
time_buf);
|
||||||
|
|
||||||
k_sem_give(&ping_timeout);
|
if (ntohs(icmp_echo->sequence) == ping_ctx.count) {
|
||||||
|
ping_done(&ping_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
net_pkt_unref(pkt);
|
net_pkt_unref(pkt);
|
||||||
return NET_OK;
|
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
|
#else
|
||||||
#define ping_ipv6(...) -ENOTSUP
|
|
||||||
#define remove_ipv6_ping_handler()
|
#define remove_ipv6_ping_handler()
|
||||||
#endif /* CONFIG_NET_IPV6 */
|
#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",
|
"%s\n",
|
||||||
ntohs(ip_hdr->len) - net_pkt_ipv6_ext_len(pkt) -
|
ntohs(ip_hdr->len) - net_pkt_ipv6_ext_len(pkt) -
|
||||||
NET_ICMPH_LEN,
|
NET_ICMPH_LEN,
|
||||||
|
@ -4050,57 +4000,15 @@ static enum net_verdict handle_ipv4_echo_reply(struct net_pkt *pkt,
|
||||||
ip_hdr->ttl,
|
ip_hdr->ttl,
|
||||||
time_buf);
|
time_buf);
|
||||||
|
|
||||||
k_sem_give(&ping_timeout);
|
if (ntohs(icmp_echo->sequence) == ping_ctx.count) {
|
||||||
|
ping_done(&ping_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
net_pkt_unref(pkt);
|
net_pkt_unref(pkt);
|
||||||
return NET_OK;
|
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
|
#else
|
||||||
#define ping_ipv4(...) -ENOTSUP
|
|
||||||
#define remove_ipv4_ping_handler()
|
#define remove_ipv4_ping_handler()
|
||||||
#endif /* CONFIG_NET_IPV4 */
|
#endif /* CONFIG_NET_IPV4 */
|
||||||
|
|
||||||
|
@ -4132,6 +4040,133 @@ static int parse_arg(size_t *i, size_t argc, char *argv[])
|
||||||
|
|
||||||
return res;
|
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 */
|
#endif /* CONFIG_NET_IP */
|
||||||
|
|
||||||
static int cmd_net_ping(const struct shell *shell, size_t argc, char *argv[])
|
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;
|
return -EOPNOTSUPP;
|
||||||
#else
|
#else
|
||||||
char *host = NULL;
|
char *host = NULL;
|
||||||
int ret;
|
|
||||||
|
|
||||||
int count = 3;
|
int count = 3;
|
||||||
int interval = 1000;
|
int interval = 1000;
|
||||||
|
@ -4215,44 +4249,35 @@ static int cmd_net_ping(const struct shell *shell, size_t argc, char *argv[])
|
||||||
return -ENOEXEC;
|
return -ENOEXEC;
|
||||||
}
|
}
|
||||||
|
|
||||||
shell_for_ping = shell;
|
memset(&ping_ctx, 0, sizeof(ping_ctx));
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_NET_IPV6)) {
|
k_work_init_delayable(&ping_ctx.work, ping_work);
|
||||||
ret = ping_ipv6(shell, host, count, interval, tos, payload_size,
|
|
||||||
iface_idx);
|
ping_ctx.sh = shell;
|
||||||
if (!ret) {
|
ping_ctx.count = count;
|
||||||
goto wait_reply;
|
ping_ctx.interval = interval;
|
||||||
} else if (ret == -EIO) {
|
ping_ctx.tos = tos;
|
||||||
PR_WARNING("Cannot send IPv6 ping\n");
|
ping_ctx.payload_size = payload_size;
|
||||||
return -ENOEXEC;
|
|
||||||
}
|
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)) {
|
ping_ctx.iface = ping_select_iface(iface_idx, &ping_ctx.addr);
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
return -ENOEXEC;
|
PR("PING %s\n", host);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_reply:
|
shell_set_bypass(shell, ping_bypass);
|
||||||
ret = k_sem_take(&ping_timeout, K_SECONDS(2));
|
k_work_reschedule(&ping_ctx.work, K_NO_WAIT);
|
||||||
if (ret == -EAGAIN) {
|
|
||||||
PR_INFO("Ping timeout\n");
|
|
||||||
remove_ipv6_ping_handler();
|
|
||||||
remove_ipv4_ping_handler();
|
|
||||||
|
|
||||||
return -ETIMEDOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue