diff mbox series

[v3,05/12] net-lwip: add ping command

Message ID 7ae6750ee1319cafa8b5b86f34394d597439b610.1717680809.git.jerome.forissier@linaro.org
State New
Headers show
Series Introduce the lwIP network stack | expand

Commit Message

Jerome Forissier June 6, 2024, 1:36 p.m. UTC
Add support for the the ping command with NET_LWIP. The implementation
is derived from lwIP's contrib/apps/ping/ping.c.

Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
---
 boot/Kconfig       |   2 +-
 cmd/Kconfig        |   6 ++
 cmd/net-lwip.c     |   8 +++
 include/net-lwip.h |   3 +
 net-lwip/Makefile  |   1 +
 net-lwip/ping.c    | 163 +++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 182 insertions(+), 1 deletion(-)
 create mode 100644 net-lwip/ping.c

Comments

Ilias Apalodimas June 6, 2024, 4:53 p.m. UTC | #1
[...]

> +static int ping_raw_init(void *recv_arg)
> +{
> +       ping_pcb = raw_new(IP_PROTO_ICMP);
> +       if (!ping_pcb)
> +               return -ENOMEM;
> +
> +       raw_recv(ping_pcb, ping_recv, recv_arg);
> +       raw_bind(ping_pcb, IP_ADDR_ANY);
> +
> +       return 0;
> +}
> +
> +static void ping_raw_stop(void)
> +{
> +       if (ping_pcb != NULL) {

nits, but we usually do if (!ping_pcb) for NULL pointers and variables
that have a value of 0.
Please change it in other files as well


> +               raw_remove(ping_pcb);
> +               ping_pcb = NULL;
> +       }
> +}
> +
> +static void ping_prepare_echo(struct icmp_echo_hdr *iecho)
> +{
> +       ICMPH_TYPE_SET(iecho, ICMP_ECHO);
> +       ICMPH_CODE_SET(iecho, 0);
> +       iecho->chksum = 0;
> +       iecho->id = PING_ID;
> +       iecho->seqno = lwip_htons(++ping_seq_num);
> +
> +       iecho->chksum = inet_chksum(iecho, sizeof(*iecho));
> +}
> +
> +static void ping_send_icmp(struct raw_pcb *raw, const ip_addr_t *addr)
> +{
> +       struct pbuf *p;
> +       struct icmp_echo_hdr *iecho;
> +       size_t ping_size = sizeof(struct icmp_echo_hdr);
> +
> +       p = pbuf_alloc(PBUF_IP, (u16_t)ping_size, PBUF_RAM);
> +       if (!p)
> +               return;
> +
> +       if ((p->len == p->tot_len) && (p->next == NULL)) {

&& !p->next

> +               iecho = (struct icmp_echo_hdr *)p->payload;
> +               ping_prepare_echo(iecho);
> +               raw_sendto(raw, p, addr);
> +       }
> +
> +       pbuf_free(p);
> +}
> +
> +static void ping_send(void *arg)
> +{
> +       struct raw_pcb *pcb = (struct raw_pcb *)arg;
> +
> +       ping_send_icmp(pcb, ping_target);
> +       sys_timeout(PING_DELAY_MS, ping_send, ping_pcb);
> +}
> +
> +static int ping_loop(const ip_addr_t* addr)
> +{
> +       bool alive;
> +       ulong start;
> +       int ret;
> +
> +       printf("Using %s device\n", eth_get_name());
> +
> +       ret = ping_raw_init(&alive);
> +       if (ret < 0)
> +               return ret;
> +       ping_target = addr;
> +       ping_seq_num = 0;
> +
> +       start = get_timer(0);
> +       ping_send(ping_pcb);
> +
> +       do {
> +               eth_rx();
> +               if (alive)
> +                       break;
> +               sys_check_timeouts();
> +               if (ctrlc()) {
> +                       printf("\nAbort\n");
> +                       break;
> +               }
> +       } while (get_timer(start) < PING_TIMEOUT_MS);

I am a bit confused about what happens here.
ping_send() will send the packet, but it will also schedule itself to
rerun after 1 ms and send another ping?

> +
> +       sys_untimeout(ping_send, ping_pcb);

So we need the sys_untimeout() because we queued 2 pings? Because
sys_timeout() is supposed to be an one shot.

Thanks
/Ilias
> +       ping_raw_stop();
> +       ping_target = NULL;
> +
> +       if (alive) {
> +               alive = false;
> +               return 0;
> +       }
> +       printf("ping failed; host %s is not alive\n", ipaddr_ntoa(addr));
> +       return -1;
> +}
> +
> +int do_ping(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
> +{
> +       ip_addr_t addr;
> +
> +       if (argc < 2)
> +               return CMD_RET_USAGE;
> +
> +       if (!ipaddr_aton(argv[1], &addr))
> +               return CMD_RET_USAGE;
> +
> +       if (ping_loop(&addr) < 0)
> +               return CMD_RET_FAILURE;
> +
> +       return CMD_RET_SUCCESS;
> +}
> --
> 2.40.1
>
Ilias Apalodimas June 6, 2024, 5:02 p.m. UTC | #2
On Thu, 6 Jun 2024 at 19:53, Ilias Apalodimas
<ilias.apalodimas@linaro.org> wrote:
>
> [...]
>
> > +static int ping_raw_init(void *recv_arg)
> > +{
> > +       ping_pcb = raw_new(IP_PROTO_ICMP);
> > +       if (!ping_pcb)
> > +               return -ENOMEM;
> > +
> > +       raw_recv(ping_pcb, ping_recv, recv_arg);
> > +       raw_bind(ping_pcb, IP_ADDR_ANY);
> > +
> > +       return 0;
> > +}
> > +
> > +static void ping_raw_stop(void)
> > +{
> > +       if (ping_pcb != NULL) {
>
> nits, but we usually do if (!ping_pcb) for NULL pointers and variables
> that have a value of 0.
> Please change it in other files as well
>
>
> > +               raw_remove(ping_pcb);
> > +               ping_pcb = NULL;
> > +       }
> > +}
> > +
> > +static void ping_prepare_echo(struct icmp_echo_hdr *iecho)
> > +{
> > +       ICMPH_TYPE_SET(iecho, ICMP_ECHO);
> > +       ICMPH_CODE_SET(iecho, 0);
> > +       iecho->chksum = 0;
> > +       iecho->id = PING_ID;
> > +       iecho->seqno = lwip_htons(++ping_seq_num);
> > +
> > +       iecho->chksum = inet_chksum(iecho, sizeof(*iecho));
> > +}
> > +
> > +static void ping_send_icmp(struct raw_pcb *raw, const ip_addr_t *addr)
> > +{
> > +       struct pbuf *p;
> > +       struct icmp_echo_hdr *iecho;
> > +       size_t ping_size = sizeof(struct icmp_echo_hdr);
> > +
> > +       p = pbuf_alloc(PBUF_IP, (u16_t)ping_size, PBUF_RAM);
> > +       if (!p)
> > +               return;
> > +
> > +       if ((p->len == p->tot_len) && (p->next == NULL)) {
>
> && !p->next
>
> > +               iecho = (struct icmp_echo_hdr *)p->payload;
> > +               ping_prepare_echo(iecho);
> > +               raw_sendto(raw, p, addr);
> > +       }
> > +
> > +       pbuf_free(p);
> > +}
> > +
> > +static void ping_send(void *arg)
> > +{
> > +       struct raw_pcb *pcb = (struct raw_pcb *)arg;
> > +
> > +       ping_send_icmp(pcb, ping_target);
> > +       sys_timeout(PING_DELAY_MS, ping_send, ping_pcb);
> > +}
> > +
> > +static int ping_loop(const ip_addr_t* addr)
> > +{
> > +       bool alive;
> > +       ulong start;
> > +       int ret;
> > +
> > +       printf("Using %s device\n", eth_get_name());
> > +
> > +       ret = ping_raw_init(&alive);
> > +       if (ret < 0)
> > +               return ret;
> > +       ping_target = addr;
> > +       ping_seq_num = 0;
> > +
> > +       start = get_timer(0);
> > +       ping_send(ping_pcb);
> > +
> > +       do {
> > +               eth_rx();
> > +               if (alive)
> > +                       break;
> > +               sys_check_timeouts();
> > +               if (ctrlc()) {
> > +                       printf("\nAbort\n");
> > +                       break;
> > +               }
> > +       } while (get_timer(start) < PING_TIMEOUT_MS);
>
> I am a bit confused about what happens here.
> ping_send() will send the packet, but it will also schedule itself to
> rerun after 1 ms and send another ping?
>
> > +
> > +       sys_untimeout(ping_send, ping_pcb);
>
> So we need the sys_untimeout() because we queued 2 pings? Because
> sys_timeout() is supposed to be an one shot.

Ah nvm, I misread that. We always reschedule ping_send_icmp(), that's
why we have to delete it here


>
> Thanks
> /Ilias
> > +       ping_raw_stop();
> > +       ping_target = NULL;
> > +
> > +       if (alive) {
> > +               alive = false;
> > +               return 0;
> > +       }
> > +       printf("ping failed; host %s is not alive\n", ipaddr_ntoa(addr));
> > +       return -1;
> > +}
> > +
> > +int do_ping(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
> > +{
> > +       ip_addr_t addr;
> > +
> > +       if (argc < 2)
> > +               return CMD_RET_USAGE;
> > +
> > +       if (!ipaddr_aton(argv[1], &addr))
> > +               return CMD_RET_USAGE;
> > +
> > +       if (ping_loop(&addr) < 0)
> > +               return CMD_RET_FAILURE;
> > +
> > +       return CMD_RET_SUCCESS;
> > +}
> > --
> > 2.40.1
> >
Ilias Apalodimas June 6, 2024, 5:45 p.m. UTC | #3
On Thu, 6 Jun 2024 at 20:02, Ilias Apalodimas
<ilias.apalodimas@linaro.org> wrote:
>
> On Thu, 6 Jun 2024 at 19:53, Ilias Apalodimas
> <ilias.apalodimas@linaro.org> wrote:
> >
> > [...]
> >
> > > +static int ping_raw_init(void *recv_arg)
> > > +{
> > > +       ping_pcb = raw_new(IP_PROTO_ICMP);
> > > +       if (!ping_pcb)
> > > +               return -ENOMEM;
> > > +
> > > +       raw_recv(ping_pcb, ping_recv, recv_arg);
> > > +       raw_bind(ping_pcb, IP_ADDR_ANY);
> > > +
> > > +       return 0;
> > > +}
> > > +
> > > +static void ping_raw_stop(void)
> > > +{
> > > +       if (ping_pcb != NULL) {
> >
> > nits, but we usually do if (!ping_pcb) for NULL pointers and variables
> > that have a value of 0.
> > Please change it in other files as well
> >
> >
> > > +               raw_remove(ping_pcb);
> > > +               ping_pcb = NULL;
> > > +       }
> > > +}
> > > +
> > > +static void ping_prepare_echo(struct icmp_echo_hdr *iecho)
> > > +{
> > > +       ICMPH_TYPE_SET(iecho, ICMP_ECHO);
> > > +       ICMPH_CODE_SET(iecho, 0);
> > > +       iecho->chksum = 0;
> > > +       iecho->id = PING_ID;
> > > +       iecho->seqno = lwip_htons(++ping_seq_num);
> > > +
> > > +       iecho->chksum = inet_chksum(iecho, sizeof(*iecho));
> > > +}
> > > +
> > > +static void ping_send_icmp(struct raw_pcb *raw, const ip_addr_t *addr)
> > > +{
> > > +       struct pbuf *p;
> > > +       struct icmp_echo_hdr *iecho;
> > > +       size_t ping_size = sizeof(struct icmp_echo_hdr);
> > > +
> > > +       p = pbuf_alloc(PBUF_IP, (u16_t)ping_size, PBUF_RAM);
> > > +       if (!p)
> > > +               return;
> > > +
> > > +       if ((p->len == p->tot_len) && (p->next == NULL)) {
> >
> > && !p->next
> >
> > > +               iecho = (struct icmp_echo_hdr *)p->payload;
> > > +               ping_prepare_echo(iecho);
> > > +               raw_sendto(raw, p, addr);
> > > +       }
> > > +
> > > +       pbuf_free(p);
> > > +}
> > > +
> > > +static void ping_send(void *arg)
> > > +{
> > > +       struct raw_pcb *pcb = (struct raw_pcb *)arg;
> > > +
> > > +       ping_send_icmp(pcb, ping_target);
> > > +       sys_timeout(PING_DELAY_MS, ping_send, ping_pcb);
> > > +}
> > > +
> > > +static int ping_loop(const ip_addr_t* addr)
> > > +{
> > > +       bool alive;
> > > +       ulong start;
> > > +       int ret;
> > > +
> > > +       printf("Using %s device\n", eth_get_name());
> > > +
> > > +       ret = ping_raw_init(&alive);
> > > +       if (ret < 0)
> > > +               return ret;
> > > +       ping_target = addr;
> > > +       ping_seq_num = 0;
> > > +
> > > +       start = get_timer(0);
> > > +       ping_send(ping_pcb);
> > > +
> > > +       do {
> > > +               eth_rx();
> > > +               if (alive)
> > > +                       break;
> > > +               sys_check_timeouts();
> > > +               if (ctrlc()) {
> > > +                       printf("\nAbort\n");
> > > +                       break;
> > > +               }
> > > +       } while (get_timer(start) < PING_TIMEOUT_MS);
> >
> > I am a bit confused about what happens here.
> > ping_send() will send the packet, but it will also schedule itself to
> > rerun after 1 ms and send another ping?
> >
> > > +
> > > +       sys_untimeout(ping_send, ping_pcb);
> >
> > So we need the sys_untimeout() because we queued 2 pings? Because
> > sys_timeout() is supposed to be an one shot.
>
> Ah nvm, I misread that. We always reschedule ping_send_icmp(), that's
> why we have to delete it here

Ok so looking at it a bit more. Why do we have to schedule contunuous pings?
We just have to send one packet and wait for the response no?

Thanks
/Ilias
>
>
> >
> > Thanks
> > /Ilias
> > > +       ping_raw_stop();
> > > +       ping_target = NULL;
> > > +
> > > +       if (alive) {
> > > +               alive = false;
> > > +               return 0;
> > > +       }
> > > +       printf("ping failed; host %s is not alive\n", ipaddr_ntoa(addr));
> > > +       return -1;
> > > +}
> > > +
> > > +int do_ping(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
> > > +{
> > > +       ip_addr_t addr;
> > > +
> > > +       if (argc < 2)
> > > +               return CMD_RET_USAGE;
> > > +
> > > +       if (!ipaddr_aton(argv[1], &addr))
> > > +               return CMD_RET_USAGE;
> > > +
> > > +       if (ping_loop(&addr) < 0)
> > > +               return CMD_RET_FAILURE;
> > > +
> > > +       return CMD_RET_SUCCESS;
> > > +}
> > > --
> > > 2.40.1
> > >
Jerome Forissier June 7, 2024, 7:55 a.m. UTC | #4
On 6/6/24 19:45, Ilias Apalodimas wrote:
> On Thu, 6 Jun 2024 at 20:02, Ilias Apalodimas
> <ilias.apalodimas@linaro.org> wrote:
>>
>> On Thu, 6 Jun 2024 at 19:53, Ilias Apalodimas
>> <ilias.apalodimas@linaro.org> wrote:
>>>
>>> [...]
>>>
>>>> +static int ping_raw_init(void *recv_arg)
>>>> +{
>>>> +       ping_pcb = raw_new(IP_PROTO_ICMP);
>>>> +       if (!ping_pcb)
>>>> +               return -ENOMEM;
>>>> +
>>>> +       raw_recv(ping_pcb, ping_recv, recv_arg);
>>>> +       raw_bind(ping_pcb, IP_ADDR_ANY);
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static void ping_raw_stop(void)
>>>> +{
>>>> +       if (ping_pcb != NULL) {
>>>
>>> nits, but we usually do if (!ping_pcb) for NULL pointers and variables
>>> that have a value of 0.
>>> Please change it in other files as well
>>>
>>>
>>>> +               raw_remove(ping_pcb);
>>>> +               ping_pcb = NULL;
>>>> +       }
>>>> +}
>>>> +
>>>> +static void ping_prepare_echo(struct icmp_echo_hdr *iecho)
>>>> +{
>>>> +       ICMPH_TYPE_SET(iecho, ICMP_ECHO);
>>>> +       ICMPH_CODE_SET(iecho, 0);
>>>> +       iecho->chksum = 0;
>>>> +       iecho->id = PING_ID;
>>>> +       iecho->seqno = lwip_htons(++ping_seq_num);
>>>> +
>>>> +       iecho->chksum = inet_chksum(iecho, sizeof(*iecho));
>>>> +}
>>>> +
>>>> +static void ping_send_icmp(struct raw_pcb *raw, const ip_addr_t *addr)
>>>> +{
>>>> +       struct pbuf *p;
>>>> +       struct icmp_echo_hdr *iecho;
>>>> +       size_t ping_size = sizeof(struct icmp_echo_hdr);
>>>> +
>>>> +       p = pbuf_alloc(PBUF_IP, (u16_t)ping_size, PBUF_RAM);
>>>> +       if (!p)
>>>> +               return;
>>>> +
>>>> +       if ((p->len == p->tot_len) && (p->next == NULL)) {
>>>
>>> && !p->next
>>>
>>>> +               iecho = (struct icmp_echo_hdr *)p->payload;
>>>> +               ping_prepare_echo(iecho);
>>>> +               raw_sendto(raw, p, addr);
>>>> +       }
>>>> +
>>>> +       pbuf_free(p);
>>>> +}
>>>> +
>>>> +static void ping_send(void *arg)
>>>> +{
>>>> +       struct raw_pcb *pcb = (struct raw_pcb *)arg;
>>>> +
>>>> +       ping_send_icmp(pcb, ping_target);
>>>> +       sys_timeout(PING_DELAY_MS, ping_send, ping_pcb);
>>>> +}
>>>> +
>>>> +static int ping_loop(const ip_addr_t* addr)
>>>> +{
>>>> +       bool alive;
>>>> +       ulong start;
>>>> +       int ret;
>>>> +
>>>> +       printf("Using %s device\n", eth_get_name());
>>>> +
>>>> +       ret = ping_raw_init(&alive);
>>>> +       if (ret < 0)
>>>> +               return ret;
>>>> +       ping_target = addr;
>>>> +       ping_seq_num = 0;
>>>> +
>>>> +       start = get_timer(0);
>>>> +       ping_send(ping_pcb);
>>>> +
>>>> +       do {
>>>> +               eth_rx();
>>>> +               if (alive)
>>>> +                       break;
>>>> +               sys_check_timeouts();
>>>> +               if (ctrlc()) {
>>>> +                       printf("\nAbort\n");
>>>> +                       break;
>>>> +               }
>>>> +       } while (get_timer(start) < PING_TIMEOUT_MS);
>>>
>>> I am a bit confused about what happens here.
>>> ping_send() will send the packet, but it will also schedule itself to
>>> rerun after 1 ms and send another ping?
>>>
>>>> +
>>>> +       sys_untimeout(ping_send, ping_pcb);
>>>
>>> So we need the sys_untimeout() because we queued 2 pings? Because
>>> sys_timeout() is supposed to be an one shot.
>>
>> Ah nvm, I misread that. We always reschedule ping_send_icmp(), that's
>> why we have to delete it here
> 
> Ok so looking at it a bit more. Why do we have to schedule contunuous pings?
> We just have to send one packet and wait for the response no?

We could send just one packet as NET does, but if there's packet loss then
the ping command will report the host is unreachable. I think it is safer
to allow for a few retries for better reliability. The code for one packet
would be marginally simpler anyways. What I can do however is use a send
counter rather than a timeout.

Thanks,
diff mbox series

Patch

diff --git a/boot/Kconfig b/boot/Kconfig
index 004e69dd92a..983a284ec52 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -379,7 +379,7 @@  config BOOT_DEFAULTS_CMDS
 	select CMD_FS_GENERIC
 	select CMD_PART if PARTITIONS
 	select CMD_DHCP if CMD_NET || CMD_NET_LWIP
-	select CMD_PING if CMD_NET
+	select CMD_PING if CMD_NET || CMD_NET_LWIP
 	select CMD_PXE if CMD_NET
 	select CMD_BOOTI if ARM64
 	select CMD_BOOTZ if ARM && !ARM64
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 94a8de266f6..07cfe824e3f 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2098,6 +2098,12 @@  config CMD_DHCP
 	help
 	  Boot image via network using DHCP/TFTP protocol
 
+config CMD_PING
+	bool "ping"
+	select PROT_RAW_LWIP
+	help
+	  Send ICMP ECHO_REQUEST to network host
+
 config CMD_TFTPBOOT
 	bool "tftp"
 	select PROT_UDP_LWIP
diff --git a/cmd/net-lwip.c b/cmd/net-lwip.c
index 490a2e4ac5c..13856703fcf 100644
--- a/cmd/net-lwip.c
+++ b/cmd/net-lwip.c
@@ -12,6 +12,14 @@  U_BOOT_CMD(
 );
 #endif
 
+#if defined(CONFIG_CMD_PING)
+U_BOOT_CMD(
+	ping,	2,	1,	do_ping,
+	"send ICMP ECHO_REQUEST to network host",
+	"pingAddress"
+);
+#endif
+
 #if defined(CONFIG_CMD_TFTPBOOT)
 U_BOOT_CMD(
 	tftpboot,	3,	0,	do_tftpb,
diff --git a/include/net-lwip.h b/include/net-lwip.h
index 2308703e46b..2abaaa3b4e3 100644
--- a/include/net-lwip.h
+++ b/include/net-lwip.h
@@ -5,6 +5,7 @@ 
 
 #include <asm/cache.h>
 #include <linux/types.h>
+#include <lwip/ip_addr.h>
 #include <lwip/netif.h>
 #include <time.h>
 
@@ -50,6 +51,7 @@  int eth_env_get_enetaddr_by_index(const char *base_name, int index,
 int eth_init(void);			/* Initialize the device */
 int eth_send(void *packet, int length);	   /* Send a packet */
 int eth_rx(void);
+const char *eth_get_name(void);
 int eth_get_dev_index(void);
 int eth_init_state_only(void); /* Set active state */
 void eth_set_current(void);		/* set nterface to ethcur var */
@@ -80,6 +82,7 @@  int net_lwip_init(void);
 struct netif *net_lwip_get_netif(void);
 
 int do_dhcp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
+int do_ping(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_tftpb(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 
 #endif /* __NET_LWIP_H__ */
diff --git a/net-lwip/Makefile b/net-lwip/Makefile
index a56c32bfa74..e68d4e24197 100644
--- a/net-lwip/Makefile
+++ b/net-lwip/Makefile
@@ -7,6 +7,7 @@  obj-$(CONFIG_$(SPL_)DM_ETH) += ../net/eth_common.o
 obj-$(CONFIG_$(SPL_)DM_ETH) += ../net/eth-uclass.o
 obj-$(CONFIG_$(SPL_)DM_ETH) += net-lwip.o
 obj-$(CONFIG_CMD_DHCP) += dhcp.o
+obj-$(CONFIG_CMD_PING) += ping.o
 obj-$(CONFIG_CMD_TFTPBOOT) += tftp.o
 
 # Disable this warning as it is triggered by:
diff --git a/net-lwip/ping.c b/net-lwip/ping.c
new file mode 100644
index 00000000000..51947d615ed
--- /dev/null
+++ b/net-lwip/ping.c
@@ -0,0 +1,163 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (C) 2024 Linaro Ltd. */
+
+#include <command.h>
+#include <console.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <lwip/icmp.h>
+#include <lwip/inet_chksum.h>
+#include <lwip/raw.h>
+#include <lwip/timeouts.h>
+#include <net-lwip.h>
+#include <time.h>
+
+#define PING_DELAY_MS 1000
+#define PING_TIMEOUT_MS 10000
+/* Ping identifier - must fit on a u16_t */
+#define PING_ID 0xAFAF
+
+static const ip_addr_t *ping_target;
+static struct raw_pcb *ping_pcb;
+static uint16_t ping_seq_num;
+
+static u8_t ping_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p,
+		      const ip_addr_t *addr)
+{
+	struct icmp_echo_hdr *iecho;
+	bool *alive = arg;
+
+	if (addr->addr != ping_target->addr)
+		return 0;
+
+	if ((p->tot_len >= (IP_HLEN + sizeof(struct icmp_echo_hdr))) &&
+	    pbuf_remove_header(p, IP_HLEN) == 0) {
+		iecho = (struct icmp_echo_hdr *)p->payload;
+
+		if ((iecho->id == PING_ID) &&
+		    (iecho->seqno == lwip_htons(ping_seq_num))) {
+			*alive = true;
+			printf("host %s is alive\n", ipaddr_ntoa(addr));
+			pbuf_free(p);
+			return 1; /* eat the packet */
+		}
+		/* not eaten, restore original packet */
+		pbuf_add_header(p, IP_HLEN);
+	}
+
+	return 0; /* don't eat the packet */
+}
+
+static int ping_raw_init(void *recv_arg)
+{
+	ping_pcb = raw_new(IP_PROTO_ICMP);
+	if (!ping_pcb)
+		return -ENOMEM;
+
+	raw_recv(ping_pcb, ping_recv, recv_arg);
+	raw_bind(ping_pcb, IP_ADDR_ANY);
+
+	return 0;
+}
+
+static void ping_raw_stop(void)
+{
+	if (ping_pcb != NULL) {
+		raw_remove(ping_pcb);
+		ping_pcb = NULL;
+	}
+}
+
+static void ping_prepare_echo(struct icmp_echo_hdr *iecho)
+{
+	ICMPH_TYPE_SET(iecho, ICMP_ECHO);
+	ICMPH_CODE_SET(iecho, 0);
+	iecho->chksum = 0;
+	iecho->id = PING_ID;
+	iecho->seqno = lwip_htons(++ping_seq_num);
+
+	iecho->chksum = inet_chksum(iecho, sizeof(*iecho));
+}
+
+static void ping_send_icmp(struct raw_pcb *raw, const ip_addr_t *addr)
+{
+	struct pbuf *p;
+	struct icmp_echo_hdr *iecho;
+	size_t ping_size = sizeof(struct icmp_echo_hdr);
+
+	p = pbuf_alloc(PBUF_IP, (u16_t)ping_size, PBUF_RAM);
+	if (!p)
+		return;
+
+	if ((p->len == p->tot_len) && (p->next == NULL)) {
+		iecho = (struct icmp_echo_hdr *)p->payload;
+		ping_prepare_echo(iecho);
+		raw_sendto(raw, p, addr);
+	}
+
+	pbuf_free(p);
+}
+
+static void ping_send(void *arg)
+{
+	struct raw_pcb *pcb = (struct raw_pcb *)arg;
+
+	ping_send_icmp(pcb, ping_target);
+	sys_timeout(PING_DELAY_MS, ping_send, ping_pcb);
+}
+
+static int ping_loop(const ip_addr_t* addr)
+{
+	bool alive;
+	ulong start;
+	int ret;
+
+	printf("Using %s device\n", eth_get_name());
+
+	ret = ping_raw_init(&alive);
+	if (ret < 0)
+		return ret;
+	ping_target = addr;
+	ping_seq_num = 0;
+
+	start = get_timer(0);
+	ping_send(ping_pcb);
+
+	do {
+		eth_rx();
+		if (alive)
+			break;
+		sys_check_timeouts();
+		if (ctrlc()) {
+			printf("\nAbort\n");
+			break;
+		}
+	} while (get_timer(start) < PING_TIMEOUT_MS);
+
+	sys_untimeout(ping_send, ping_pcb);
+	ping_raw_stop();
+	ping_target = NULL;
+
+	if (alive) {
+		alive = false;
+		return 0;
+	}
+	printf("ping failed; host %s is not alive\n", ipaddr_ntoa(addr));
+	return -1;
+}
+
+int do_ping(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
+{
+	ip_addr_t addr;
+
+	if (argc < 2)
+		return CMD_RET_USAGE;
+
+	if (!ipaddr_aton(argv[1], &addr))
+		return CMD_RET_USAGE;
+
+	if (ping_loop(&addr) < 0)
+		return CMD_RET_FAILURE;
+
+	return CMD_RET_SUCCESS;
+}