shell/gnrc_icmpv6_echo: test for ICMPv6 echo reply corruption
The Linux ping utility has the nice feature that fills the ICMPv6 echo request payload with a pattern `payload_index & 0xFF`. Then the ICMPv6 echo response payload is checked to verify that the pattern is still intact. This way corrupted messages can be detected. In the past that revealed some 6lo-fragmentation bugs in Linux when corrupted replies arrived. This feature is also useful for RIOT, so implement it in RIOTs `ping` command.
This commit is contained in:
parent
c4df2cca51
commit
3b40ca2b26
@ -73,15 +73,14 @@ typedef struct {
|
||||
gnrc_netif_t *netif;
|
||||
uint16_t id;
|
||||
uint8_t hoplimit;
|
||||
uint8_t pattern;
|
||||
} _ping_data_t;
|
||||
|
||||
static void _usage(char *cmdname);
|
||||
static int _configure(int argc, char **argv, _ping_data_t *data);
|
||||
static void _pinger(_ping_data_t *data);
|
||||
static void _print_reply(_ping_data_t *data, gnrc_pktsnip_t *icmpv6,
|
||||
static void _print_reply(_ping_data_t *data, gnrc_pktsnip_t *icmpv6, uint32_t now_us,
|
||||
ipv6_addr_t *from, unsigned hoplimit, gnrc_netif_hdr_t *netif_hdr);
|
||||
static void _handle_reply(_ping_data_t *data, gnrc_pktsnip_t *pkt);
|
||||
static void _handle_reply(_ping_data_t *data, gnrc_pktsnip_t *pkt, uint32_t now_us);
|
||||
static int _finish(_ping_data_t *data);
|
||||
|
||||
int _gnrc_icmpv6_ping(int argc, char **argv)
|
||||
@ -95,7 +94,6 @@ int _gnrc_icmpv6_ping(int argc, char **argv)
|
||||
.timeout = DEFAULT_TIMEOUT_USEC,
|
||||
.interval = DEFAULT_INTERVAL_USEC,
|
||||
.id = DEFAULT_ID,
|
||||
.pattern = DEFAULT_ID,
|
||||
};
|
||||
int res;
|
||||
|
||||
@ -110,7 +108,7 @@ int _gnrc_icmpv6_ping(int argc, char **argv)
|
||||
msg_receive(&msg);
|
||||
switch (msg.type) {
|
||||
case GNRC_NETAPI_MSG_TYPE_RCV: {
|
||||
_handle_reply(&data, msg.content.ptr);
|
||||
_handle_reply(&data, msg.content.ptr, xtimer_now_usec());
|
||||
gnrc_pktbuf_release(msg.content.ptr);
|
||||
break;
|
||||
}
|
||||
@ -238,6 +236,44 @@ static int _configure(int argc, char **argv, _ping_data_t *data)
|
||||
return res;
|
||||
}
|
||||
|
||||
static void _fill_payload(uint8_t *buf, size_t len)
|
||||
{
|
||||
uint8_t i = 0;
|
||||
|
||||
if (len >= sizeof(uint32_t)) {
|
||||
uint32_t now = xtimer_now_usec();
|
||||
memcpy(buf, &now, sizeof(now));
|
||||
len -= sizeof(now);
|
||||
buf += sizeof(now);
|
||||
}
|
||||
|
||||
while (len--) {
|
||||
*buf++ = i++;
|
||||
}
|
||||
}
|
||||
|
||||
static bool _check_payload(const void *buf, size_t len, uint32_t now,
|
||||
uint32_t *triptime, uint16_t *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;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void _pinger(_ping_data_t *data)
|
||||
{
|
||||
gnrc_pktsnip_t *pkt, *tmp;
|
||||
@ -277,7 +313,6 @@ static void _pinger(_ping_data_t *data)
|
||||
return;
|
||||
}
|
||||
databuf = (uint8_t *)(pkt->data) + sizeof(icmpv6_echo_t);
|
||||
memset(databuf, data->pattern, data->datalen);
|
||||
tmp = gnrc_ipv6_hdr_build(pkt, NULL, &data->host);
|
||||
if (tmp == NULL) {
|
||||
puts("error: packet buffer full");
|
||||
@ -296,10 +331,10 @@ static void _pinger(_ping_data_t *data)
|
||||
gnrc_netif_hdr_set_netif(tmp->data, data->netif);
|
||||
pkt = gnrc_pkt_prepend(pkt, tmp);
|
||||
}
|
||||
if (data->datalen >= sizeof(uint32_t)) {
|
||||
uint32_t now = xtimer_now_usec();
|
||||
memcpy(databuf, &now, sizeof(now));
|
||||
}
|
||||
|
||||
/* add TX timestamp & test data */
|
||||
_fill_payload(databuf, data->datalen);
|
||||
|
||||
if (!gnrc_netapi_dispatch_send(GNRC_NETTYPE_IPV6,
|
||||
GNRC_NETREG_DEMUX_CTX_ALL,
|
||||
pkt)) {
|
||||
@ -311,7 +346,7 @@ error_exit:
|
||||
gnrc_pktbuf_release(pkt);
|
||||
}
|
||||
|
||||
static void _print_reply(_ping_data_t *data, gnrc_pktsnip_t *icmpv6,
|
||||
static void _print_reply(_ping_data_t *data, gnrc_pktsnip_t *icmpv6, uint32_t now,
|
||||
ipv6_addr_t *from, unsigned hoplimit,
|
||||
gnrc_netif_hdr_t *netif_hdr)
|
||||
{
|
||||
@ -319,16 +354,17 @@ static void _print_reply(_ping_data_t *data, gnrc_pktsnip_t *icmpv6,
|
||||
|
||||
kernel_pid_t if_pid = netif_hdr ? netif_hdr->if_pid : KERNEL_PID_UNDEF;
|
||||
int16_t rssi = netif_hdr ? netif_hdr->rssi : GNRC_NETIF_HDR_NO_RSSI;
|
||||
int16_t truncated;
|
||||
|
||||
/* check if payload size matches expectation */
|
||||
truncated = (data->datalen + sizeof(icmpv6_echo_t)) - icmpv6->size;
|
||||
|
||||
/* discard if too short */
|
||||
if (icmpv6->size < (data->datalen + sizeof(icmpv6_echo_t))) {
|
||||
return;
|
||||
}
|
||||
if (icmpv6_hdr->type == ICMPV6_ECHO_REP) {
|
||||
char from_str[IPV6_ADDR_MAX_STR_LEN];
|
||||
const char *dupmsg = " (DUP!)";
|
||||
uint32_t triptime = 0;
|
||||
uint16_t recv_seq;
|
||||
uint16_t corrupted;
|
||||
|
||||
/* not our ping */
|
||||
if (byteorder_ntohs(icmpv6_hdr->id) != data->id) {
|
||||
@ -340,8 +376,38 @@ static void _print_reply(_ping_data_t *data, gnrc_pktsnip_t *icmpv6,
|
||||
}
|
||||
recv_seq = byteorder_ntohs(icmpv6_hdr->seq);
|
||||
ipv6_addr_to_str(&from_str[0], from, sizeof(from_str));
|
||||
|
||||
if (gnrc_netif_highlander() || (if_pid == KERNEL_PID_UNDEF) ||
|
||||
!ipv6_addr_is_link_local(from)) {
|
||||
printf("%u bytes from %s: icmp_seq=%u ttl=%u",
|
||||
(unsigned)icmpv6->size,
|
||||
from_str, recv_seq, hoplimit);
|
||||
} else {
|
||||
printf("%u bytes from %s%%%u: icmp_seq=%u ttl=%u",
|
||||
(unsigned)icmpv6->size,
|
||||
from_str, if_pid, recv_seq, hoplimit);
|
||||
|
||||
}
|
||||
/* check if payload size matches */
|
||||
if (truncated) {
|
||||
printf(" truncated by %d byte", truncated);
|
||||
}
|
||||
/* check response for corruption */
|
||||
else if (_check_payload(icmpv6_hdr + 1, data->datalen, now,
|
||||
&triptime, &corrupted)) {
|
||||
printf(" corrupted at offset %u", corrupted);
|
||||
}
|
||||
if (rssi != GNRC_NETIF_HDR_NO_RSSI) {
|
||||
printf(" rssi=%"PRId16" dBm", rssi);
|
||||
}
|
||||
if (data->datalen >= sizeof(uint32_t)) {
|
||||
printf(" time=%lu.%03lu ms", (long unsigned)triptime / 1000,
|
||||
(long unsigned)triptime % 1000);
|
||||
}
|
||||
|
||||
/* we can only calculate RTT (triptime) if payload was large enough for
|
||||
a TX timestamp */
|
||||
if (data->datalen >= sizeof(uint32_t)) {
|
||||
triptime = xtimer_now_usec() - unaligned_get_u32(icmpv6_hdr + 1);
|
||||
data->tsum += triptime;
|
||||
if (triptime < data->tmin) {
|
||||
data->tmin = triptime;
|
||||
@ -358,29 +424,12 @@ static void _print_reply(_ping_data_t *data, gnrc_pktsnip_t *icmpv6,
|
||||
data->num_recv++;
|
||||
dupmsg += 7;
|
||||
}
|
||||
if (gnrc_netif_highlander() || (if_pid == KERNEL_PID_UNDEF) ||
|
||||
!ipv6_addr_is_link_local(from)) {
|
||||
printf("%u bytes from %s: icmp_seq=%u ttl=%u",
|
||||
(unsigned)icmpv6->size,
|
||||
from_str, recv_seq, hoplimit);
|
||||
} else {
|
||||
printf("%u bytes from %s%%%u: icmp_seq=%u ttl=%u",
|
||||
(unsigned)icmpv6->size,
|
||||
from_str, if_pid, recv_seq, hoplimit);
|
||||
|
||||
}
|
||||
if (rssi != GNRC_NETIF_HDR_NO_RSSI) {
|
||||
printf(" rssi=%"PRId16" dBm", rssi);
|
||||
}
|
||||
if (data->datalen >= sizeof(uint32_t)) {
|
||||
printf(" time=%lu.%03lu ms", (long unsigned)triptime / 1000,
|
||||
(long unsigned)triptime % 1000);
|
||||
}
|
||||
puts(dupmsg);
|
||||
}
|
||||
}
|
||||
|
||||
static void _handle_reply(_ping_data_t *data, gnrc_pktsnip_t *pkt)
|
||||
static void _handle_reply(_ping_data_t *data, gnrc_pktsnip_t *pkt, uint32_t now)
|
||||
{
|
||||
gnrc_pktsnip_t *netif, *ipv6, *icmpv6;
|
||||
gnrc_netif_hdr_t *netif_hdr;
|
||||
@ -395,7 +444,7 @@ static void _handle_reply(_ping_data_t *data, gnrc_pktsnip_t *pkt)
|
||||
}
|
||||
ipv6_hdr = ipv6->data;
|
||||
netif_hdr = netif ? netif->data : NULL;
|
||||
_print_reply(data, icmpv6, &ipv6_hdr->src, ipv6_hdr->hl, netif_hdr);
|
||||
_print_reply(data, icmpv6, now, &ipv6_hdr->src, ipv6_hdr->hl, netif_hdr);
|
||||
#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:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user