diff --git a/sys/include/net/gnrc/icmpv6/echo.h b/sys/include/net/gnrc/icmpv6/echo.h index 183fad6f70..b84c2ae3aa 100644 --- a/sys/include/net/gnrc/icmpv6/echo.h +++ b/sys/include/net/gnrc/icmpv6/echo.h @@ -24,7 +24,9 @@ #include "byteorder.h" #include "net/gnrc/netif.h" +#include "net/gnrc/netif/hdr.h" #include "net/ipv6/hdr.h" +#include "net/icmpv6.h" #ifdef __cplusplus extern "C" { @@ -58,6 +60,49 @@ gnrc_pktsnip_t *gnrc_icmpv6_echo_build(uint8_t type, uint16_t id, uint16_t seq, void gnrc_icmpv6_echo_req_handle(gnrc_netif_t *netif, ipv6_hdr_t *ipv6_hdr, icmpv6_echo_t *echo, uint16_t len); +/** + * @brief Send out ICMPv6 echo request + * + * @param[in] netif The interface the echo request should be sent on. + * @param[in] addr The destination address of the echo request + * @param[in] id ID for the echo message in host byte-order + * @param[in] seq Sequence number for the echo message in host byte-order + * @param[in] ttl Hop limit of the echo request + * @param[in] len Length of the payload + * + * @return 0 on success + * @return <0 on error + */ +int gnrc_icmpv6_echo_send(const gnrc_netif_t *netif, const ipv6_addr_t *addr, + uint16_t id, uint16_t seq, uint8_t ttl, size_t len); + +/** + * @brief ICMPv6 echo response callback + * + * @param[in] pkt Packet containing the ICMPv6 response + * @param[in] corrupt Offset of corrupt payload, -1 if no corruption detected + * @param[in] rtt_us round-trip-time in µs (0 if this information is not available) + * @param[in] ctx User supplied context + * + * @return 0 on success + * @return <0 on error + */ +typedef int (*gnrc_icmpv6_echo_rsp_handle_cb_t)(gnrc_pktsnip_t *pkt, + int corrupt, uint32_t rtt_us, void *ctx); +/** + * @brief Parse ICMPv6 echo response + * + * @param[in] pkt Incoming ICMPv6 packet + * @param[in] len Expected echo response payload length + * @param[in] cb Callback function to execute + * @param[in] ctx Callback function context + * + * @return 0 on success + * @return <0 on error + */ +int gnrc_icmpv6_echo_rsp_handle(gnrc_pktsnip_t *pkt, size_t len, + gnrc_icmpv6_echo_rsp_handle_cb_t cb, void *ctx); + #ifdef __cplusplus } #endif diff --git a/sys/net/gnrc/network_layer/icmpv6/echo/gnrc_icmpv6_echo.c b/sys/net/gnrc/network_layer/icmpv6/echo/gnrc_icmpv6_echo.c index a20a189559..a6b9003d66 100644 --- a/sys/net/gnrc/network_layer/icmpv6/echo/gnrc_icmpv6_echo.c +++ b/sys/net/gnrc/network_layer/icmpv6/echo/gnrc_icmpv6_echo.c @@ -35,7 +35,7 @@ gnrc_pktsnip_t *gnrc_icmpv6_echo_build(uint8_t type, uint16_t id, uint16_t seq, return NULL; } - DEBUG("icmpv6_echo: Building echo message with type=%" PRIu8 "id=%" PRIu16 + DEBUG("icmpv6_echo: Building echo message with type=%" PRIu8 " id=%" PRIu16 ", seq=%" PRIu16, type, id, seq); echo = (icmpv6_echo_t *)pkt->data; echo->id = byteorder_htons(id); @@ -107,4 +107,121 @@ void gnrc_icmpv6_echo_req_handle(gnrc_netif_t *netif, ipv6_hdr_t *ipv6_hdr, } } +static void _fill_payload(uint8_t *buf, size_t len, uint32_t now) +{ + uint8_t i = 0; + + if (len >= sizeof(uint32_t)) { + memcpy(buf, &now, sizeof(now)); + len -= sizeof(now); + buf += sizeof(now); + } + + while (len--) { + *buf++ = i++; + } +} + +static void _check_payload(const void *buf, size_t len, uint32_t now, + uint32_t *triptime, int *corrupt) +{ + uint8_t i = 0; + const uint8_t *data = buf; + + if (len >= sizeof(uint32_t)) { + *triptime = now - unaligned_get_u32(buf); + len -= sizeof(uint32_t); + data += sizeof(uint32_t); + } + + while (len--) { + if (*data++ != i++) { + *corrupt = data - (uint8_t *)buf - 1; + break; + } + } +} + +int gnrc_icmpv6_echo_send(const gnrc_netif_t *netif, const ipv6_addr_t *addr, + uint16_t id, uint16_t seq, uint8_t ttl, size_t len) +{ + int res = 0; + gnrc_pktsnip_t *pkt, *tmp; + ipv6_hdr_t *ipv6; + uint8_t *databuf; + + pkt = gnrc_icmpv6_echo_build(ICMPV6_ECHO_REQ, id, seq, NULL, len); + if (pkt == NULL) { + DEBUG("error: packet buffer full\n"); + return -ENOMEM; + } + + databuf = (uint8_t *)(pkt->data) + sizeof(icmpv6_echo_t); + tmp = gnrc_ipv6_hdr_build(pkt, NULL, addr); + if (tmp == NULL) { + puts("error: packet buffer full"); + goto error_exit; + } + pkt = tmp; + ipv6 = pkt->data; + /* if ttl is unset (i.e. 0) gnrc_ipv6 will select hop limit */ + ipv6->hl = ttl; + if (netif != NULL) { + tmp = gnrc_netif_hdr_build(NULL, 0, NULL, 0); + if (tmp == NULL) { + DEBUG("error: packet buffer full"); + res = -ENOMEM; + goto error_exit; + } + gnrc_netif_hdr_set_netif(tmp->data, netif); + pkt = gnrc_pkt_prepend(pkt, tmp); + } + + /* add TX timestamp & test data */ + _fill_payload(databuf, len, ztimer_now(ZTIMER_USEC)); + + res = !gnrc_netapi_dispatch_send(GNRC_NETTYPE_IPV6, + GNRC_NETREG_DEMUX_CTX_ALL, + pkt); + if (res) { + DEBUG("error: unable to send ICMPv6 echo request\n"); + res = -EBADF; + } + +error_exit: + if (res) { + gnrc_pktbuf_release(pkt); + } + return res; +} + +int gnrc_icmpv6_echo_rsp_handle(gnrc_pktsnip_t *pkt, size_t len, + gnrc_icmpv6_echo_rsp_handle_cb_t cb, void *ctx) +{ + gnrc_pktsnip_t *ipv6, *icmpv6; + ipv6_hdr_t *ipv6_hdr; + uint32_t now = ztimer_now(ZTIMER_USEC); + uint32_t triptime = 0; + int corrupted = -1; + + ipv6 = gnrc_pktsnip_search_type(pkt, GNRC_NETTYPE_IPV6); + icmpv6 = gnrc_pktsnip_search_type(pkt, GNRC_NETTYPE_ICMPV6); + if ((ipv6 == NULL) || (icmpv6 == NULL)) { + DEBUG("No IPv6 or ICMPv6 header found in reply"); + return -EINVAL; + } + ipv6_hdr = ipv6->data; +#ifdef MODULE_GNRC_IPV6_NIB + /* successful ping to neighbor (NIB handles case if ipv6->src is not a + * neighbor) can be taken as upper-layer hint for reachability: + * https://tools.ietf.org/html/rfc4861#section-7.3.1 */ + gnrc_ipv6_nib_nc_mark_reachable(&ipv6_hdr->src); +#endif + + icmpv6_echo_t *icmpv6_hdr = icmpv6->data; + _check_payload(icmpv6_hdr + 1, len, now, &triptime, &corrupted); + + return cb(pkt, corrupted, triptime, ctx); +} + /** @} */