ping.c
Go to the documentation of this file.
1 /**
2  * @file ping.c
3  * @brief Ping utility
4  *
5  * @section License
6  *
7  * Copyright (C) 2010-2018 Oryx Embedded SARL. All rights reserved.
8  *
9  * This file is part of CycloneTCP Open.
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24  *
25  * @author Oryx Embedded SARL (www.oryx-embedded.com)
26  * @version 1.9.0
27  **/
28 
29 //Switch to the appropriate trace level
30 #define TRACE_LEVEL PING_TRACE_LEVEL
31 
32 //Dependencies
33 #include "core/net.h"
34 #include "core/ping.h"
35 #include "core/ip.h"
36 #include "ipv4/ipv4.h"
37 #include "ipv4/icmp.h"
38 #include "ipv6/ipv6.h"
39 #include "ipv6/ipv6_misc.h"
40 #include "ipv6/icmpv6.h"
41 #include "core/socket.h"
42 #include "debug.h"
43 
44 //Check TCP/IP stack configuration
45 #if (PING_SUPPORT == ENABLED)
46 
47 //Sequence number field
48 static uint16_t pingSequenceNumber = 0;
49 
50 
51 /**
52  * @brief Test the reachability of a host
53  *
54  * Ping operates by sending an ICMP Echo Request message to the
55  * target host and waiting for an ICMP Echo Reply message
56  *
57  * @param[in] interface Underlying network interface (optional parameter)
58  * @param[in] targetIpAddr IP address of the host to reach
59  * @param[in] size Size of the data payload in bytes
60  * @param[in] ttl Time-To-Live value to be used
61  * @param[in] timeout Maximum time to wait before giving up
62  * @param[out] rtt Round-trip time (optional parameter)
63  * @return Error code
64  **/
65 
66 error_t ping(NetInterface *interface, const IpAddr *targetIpAddr,
67  size_t size, uint8_t ttl, systime_t timeout, systime_t *rtt)
68 {
69  error_t error;
70  PingContext context;
71 
72  //Check parameters
73  if(targetIpAddr == NULL)
75 
76  //Initialize context
77  pingInit(&context);
78 
79  //Start of exception handling block
80  do
81  {
82  //Select the specified network interface
83  error = pingBindToInterface(&context, interface);
84  //Any error to report?
85  if(error)
86  break;
87 
88  //Set timeout value
89  error = pingSetTimeout(&context, timeout);
90  //Any error to report?
91  if(error)
92  break;
93 
94  //Send an ICMP Echo Request message
95  error = pingSendRequest(&context, targetIpAddr, size, ttl);
96  //Any error to report?
97  if(error)
98  break;
99 
100  //Wait for a matching Echo Reply message
101  error = pingWaitForReply(&context, NULL, rtt);
102  //Any error to report?
103  if(error)
104  break;
105 
106  //End of exception handling block
107  } while(0);
108 
109  //Release resources
110  pingRelease(&context);
111 
112  //Return status code
113  return error;
114 }
115 
116 
117 /**
118  * @brief Initialize ping context
119  * @param[in] context Pointer to the ping context
120  **/
121 
122 void pingInit(PingContext *context)
123 {
124  //Make sure the context is valid
125  if(context != NULL)
126  {
127  //Initialize context
128  memset(context, 0, sizeof(PingContext));
129 
130  //Set the default timeout to be used
131  context->timeout = PING_DEFAULT_TIMEOUT;
132  }
133 }
134 
135 
136 /**
137  * @brief Set timeout value
138  * @param[in] context Pointer to the ping context
139  * @param[in] timeout Maximum time to wait
140  * @return Error code
141  **/
142 
144 {
145  //Invalid context?
146  if(context == NULL)
148 
149  //Save timeout value
150  context->timeout = timeout;
151 
152  //Successful processing
153  return NO_ERROR;
154 }
155 
156 
157 /**
158  * @brief Select a particular network interface
159  * @param[in] context Pointer to the ping context
160  * @param[in] interface Network interface to be used
161  * @return Error code
162  **/
163 
165 {
166  //Invalid context?
167  if(context == NULL)
169 
170  //Select the specified network interface
171  context->interface = interface;
172 
173  //Successful processing
174  return NO_ERROR;
175 }
176 
177 
178 /**
179  * @brief Send an ICMP Echo Request message
180  * @param[in] context Pointer to the ping context
181  * @param[in] targetIpAddr IP address of the host to reach
182  * @param[in] size Size of the data payload, in bytes
183  * @param[in] ttl Time-To-Live value to be used
184  * @return Error code
185  **/
186 
188  const IpAddr *targetIpAddr, size_t size, uint8_t ttl)
189 {
190  error_t error;
191  size_t i;
192  size_t length;
193  NetInterface *interface;
195 
196  //Invalid context?
197  if(context == NULL)
199 
200  //Limit the size of the data payload
201  context->dataPayloadSize = MIN (size, PING_MAX_DATA_SIZE);
202 
203  //Close existing socket, if necessary
204  if(context->socket != NULL)
205  {
206  socketClose(context->socket);
207  context->socket = NULL;
208  }
209 
210  //Identifier field is used to help matching requests and replies
211  context->identifier = netGetRand();
212 
213  //Get exclusive access
215  //Sequence Number field is increment each time an Echo Request is sent
216  context->sequenceNumber = pingSequenceNumber++;
217  //Release exclusive access
219 
220  //Point to the buffer where to format the ICMP message
221  message = (IcmpEchoMessage *) context->buffer;
222 
223  //Format ICMP Echo Request message
225  message->code = 0;
226  message->checksum = 0;
227  message->identifier = context->identifier;
228  message->sequenceNumber = context->sequenceNumber;
229 
230  //Initialize data payload
231  for(i = 0; i < context->dataPayloadSize; i++)
232  message->data[i] = i & 0xFF;
233 
234  //Length of the complete ICMP message including header and data
235  length = sizeof(IcmpEchoMessage) + context->dataPayloadSize;
236 
237  //Select the relevant network interface
238  interface = context->interface;
239 
240 #if (IPV4_SUPPORT == ENABLED)
241  //Is target address an IPv4 address?
242  if(targetIpAddr->length == sizeof(Ipv4Addr))
243  {
245 
246  //Select the source IPv4 address and the relevant network
247  //interface to use when pinging the specified host
248  error = ipv4SelectSourceAddr(&interface, targetIpAddr->ipv4Addr,
249  &srcIpAddr);
250 
251  //Any error to report?
252  if(error)
253  return error;
254 
255  //ICMP Echo Request message
257  //Message checksum calculation
258  message->checksum = ipCalcChecksum(message, length);
259 
260  //Open a raw socket
262  }
263  else
264 #endif
265 #if (IPV6_SUPPORT == ENABLED)
266  //Is target address an IPv6 address?
267  if(targetIpAddr->length == sizeof(Ipv6Addr))
268  {
269  Ipv6PseudoHeader pseudoHeader;
270 
271  //Select the source IPv6 address and the relevant network
272  //interface to use when pinging the specified host
273  error = ipv6SelectSourceAddr(&interface, &targetIpAddr->ipv6Addr,
274  &pseudoHeader.srcAddr);
275 
276  //Any error to report?
277  if(error)
278  return error;
279 
280  //ICMPv6 Echo Request message
282 
283  //Format IPv6 pseudo header
284  pseudoHeader.destAddr = targetIpAddr->ipv6Addr;
285  pseudoHeader.length = htonl(length);
286  pseudoHeader.reserved = 0;
287  pseudoHeader.nextHeader = IPV6_ICMPV6_HEADER;
288 
289  //Message checksum calculation
290  message->checksum = ipCalcUpperLayerChecksum(&pseudoHeader,
291  sizeof(Ipv6PseudoHeader), message, length);
292 
293  //Open a raw socket
295  }
296  else
297 #endif
298  //Invalid target address?
299  {
300  //Report an error
301  return ERROR_INVALID_ADDRESS;
302  }
303 
304  //Failed to open socket?
305  if(context->socket == NULL)
306  return ERROR_OPEN_FAILED;
307 
308  //Set the TTL value to be used
309  context->socket->ttl = ttl;
310 
311  //Start of exception handling block
312  do
313  {
314  //Associate the newly created socket with the relevant interface
315  error = socketBindToInterface(context->socket, interface);
316  //Unable to bind the socket to the desired interface?
317  if(error)
318  break;
319 
320  //Debug message
321  TRACE_INFO("Sending ICMP echo request to %s (%" PRIuSIZE " bytes)...\r\n",
322  ipAddrToString(targetIpAddr, NULL), length);
323 
324  //Send Echo Request message
325  error = socketSendTo(context->socket, targetIpAddr, 0,
326  message, length, NULL, 0);
327  //Failed to send message ?
328  if(error)
329  break;
330 
331  //Save the time at which the request was sent
332  context->timestamp = osGetSystemTime();
333 
334  //End of exception handling block
335  } while(0);
336 
337  //Any error to report?
338  if(error)
339  {
340  //Clean up side effects
341  socketClose(context->socket);
342  context->socket = NULL;
343  }
344 
345  //Return status code
346  return error;
347 }
348 
349 
350 /**
351  * @brief Check whether an incoming ICMP message is acceptable
352  * @param[in] context Pointer to the ping context
353  * @param[in] srcIpAddr Source IP address
354  * @param[in] destIpAddr Destination IP address
355  * @param[in] message Pointer to the incoming ICMP message
356  * @param[in] length Length of the message, in bytes
357  * @return Error code
358  **/
359 
361  const IpAddr *destIpAddr, const IcmpEchoMessage *message, size_t length)
362 {
363  size_t i;
364 
365  //Check message length
366  if(length != (sizeof(IcmpEchoMessage) + context->dataPayloadSize))
367  return ERROR_INVALID_MESSAGE;
368 
369 #if (IPV4_SUPPORT == ENABLED)
370  //Is target address an IPv4 address?
371  if(context->socket->protocol == SOCKET_IP_PROTO_ICMP)
372  {
373  //Check address type
374  if(destIpAddr->length != sizeof(Ipv4Addr))
375  return ERROR_INVALID_MESSAGE;
376 
377  //Check message type
378  if(message->type != ICMP_TYPE_ECHO_REPLY)
379  return ERROR_INVALID_MESSAGE;
380 
381  //Verify checksum value
382  if(ipCalcChecksum(message, length) != 0x0000)
383  return ERROR_INVALID_MESSAGE;
384  }
385  else
386 #endif
387 #if (IPV6_SUPPORT == ENABLED)
388  //Is target address an IPv6 address?
389  if(context->socket->protocol == SOCKET_IP_PROTO_ICMPV6)
390  {
391  Ipv6PseudoHeader pseudoHeader;
392 
393  //Check address type
394  if(destIpAddr->length != sizeof(Ipv6Addr))
395  return ERROR_INVALID_MESSAGE;
396 
397  //Check message type
398  if(message->type != ICMPV6_TYPE_ECHO_REPLY)
399  return ERROR_INVALID_MESSAGE;
400 
401  //Format IPv6 pseudo header
402  pseudoHeader.srcAddr = srcIpAddr->ipv6Addr;
403  pseudoHeader.destAddr = destIpAddr->ipv6Addr;
404  pseudoHeader.length = htonl(length);
405  pseudoHeader.reserved = 0;
406  pseudoHeader.nextHeader = IPV6_ICMPV6_HEADER;
407 
408  //Verify checksum value
409  if(ipCalcUpperLayerChecksum(&pseudoHeader,
410  sizeof(Ipv6PseudoHeader), message, length) != 0x0000)
411  {
412  //The checksum is not valid
413  return ERROR_INVALID_MESSAGE;
414  }
415  }
416  else
417 #endif
418  //Invalid target address?
419  {
420  //Report an error
421  return ERROR_INVALID_ADDRESS;
422  }
423 
424  //Make sure the response identifier matches the request identifier
425  if(message->identifier != context->identifier)
426  return ERROR_INVALID_MESSAGE;
427  //Make sure the sequence number is correct
428  if(message->sequenceNumber != context->sequenceNumber)
429  return ERROR_INVALID_MESSAGE;
430 
431  //Verify data payload
432  for(i = 0; i < context->dataPayloadSize; i++)
433  {
434  //Compare received data against expected data pattern
435  if(message->data[i] != (i & 0xFF))
436  return ERROR_INVALID_MESSAGE;
437  }
438 
439  //The ICMP Echo Reply message is acceptable
440  return NO_ERROR;
441 }
442 
443 
444 /**
445  * @brief Wait for a matching ICMP Echo Reply message
446  * @param[in] context Pointer to the ping context
447  * @param[out] targetIpAddr IP address of the remote host (optional parameter)
448  * @param[out] rtt Round-trip time (optional parameter)
449  * @return Error code
450  **/
451 
453  IpAddr *targetIpAddr, systime_t *rtt)
454 {
455  error_t error;
456  size_t length;
457  systime_t time;
458  systime_t timeout;
461 
462  //Invalid context?
463  if(context == NULL)
465 
466  //Wait for an ICMP Echo Reply message
467  do
468  {
469  //Get current time
470  time = osGetSystemTime();
471 
472  //Compute the timeout to be used
473  if(timeCompare(time, context->timestamp + context->timeout) < 0)
474  timeout = context->timestamp + context->timeout - time;
475  else
476  timeout = 0;
477 
478  //Adjust receive timeout
479  error = socketSetTimeout(context->socket, timeout);
480  //Any error to report?
481  if(error)
482  break;
483 
484  //Wait for an incoming ICMP message
485  error = socketReceiveEx(context->socket, &srcIpAddr, NULL,
486  &destIpAddr, context->buffer, PING_BUFFER_SIZE, &length, 0);
487 
488 #if (NET_RTOS_SUPPORT == DISABLED)
489  //Catch timeout exception
490  if(error == ERROR_TIMEOUT)
491  error = ERROR_WOULD_BLOCK;
492 #endif
493 
494  //Get current time
495  time = osGetSystemTime();
496 
497  //Check status code
498  if(!error)
499  {
500  //Check whether the incoming ICMP message is acceptable
501  error = pingCheckReply(context, &srcIpAddr, &destIpAddr,
502  (IcmpEchoMessage *) context->buffer, length);
503  }
504 
505  //Check status code
506  if(!error)
507  {
508  //Calculate round-trip time
509  context->rtt = time - context->timestamp;
510 
511  //Debug message
512  TRACE_INFO("ICMP echo reply received from %s (%" PRIu32 " ms)...\r\n",
513  ipAddrToString(&srcIpAddr, NULL), context->rtt);
514 
515  //Return the IP address of the host
516  if(targetIpAddr != NULL)
517  *targetIpAddr = srcIpAddr;
518 
519  //Return the round-trip time
520  if(rtt != NULL)
521  *rtt = context->rtt;
522  }
523  else
524  {
525  //Timeout value exceeded?
526  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
527  {
528  //Report an error
529  error = ERROR_TIMEOUT;
530  }
531  }
532 
533  //Wait for the next incoming ICMP message
534  } while(error == ERROR_INVALID_MESSAGE);
535 
536  //Return status code
537  return error;
538 }
539 
540 
541 /**
542  * @brief Release ping context
543  * @param[in] context Pointer to the ping context
544  **/
545 
546 void pingRelease(PingContext *context)
547 {
548  //Make sure the context is valid
549  if(context != NULL)
550  {
551  //Close underlying socket
552  if(context->socket != NULL)
553  {
554  socketClose(context->socket);
555  context->socket = NULL;
556  }
557  }
558 }
559 
560 #endif
uint32_t Ipv4Addr
IPv4 network address.
Definition: ipv4.h:232
Ping utility.
__start_packed struct @172 IcmpEchoMessage
ICMP Echo Request and Echo Reply messages.
uint32_t systime_t
Definition: compiler_port.h:44
error_t pingCheckReply(PingContext *context, const IpAddr *srcIpAddr, const IpAddr *destIpAddr, const IcmpEchoMessage *message, size_t length)
Check whether an incoming ICMP message is acceptable.
Definition: ping.c:360
uint16_t identifier
Definition: ping.h:76
#define timeCompare(t1, t2)
Definition: os_port.h:40
error_t pingBindToInterface(PingContext *context, NetInterface *interface)
Select a particular network interface.
Definition: ping.c:164
error_t ipv6SelectSourceAddr(NetInterface **interface, const Ipv6Addr *destAddr, Ipv6Addr *srcAddr)
IPv6 source address selection.
Definition: ipv6_misc.c:879
error_t ping(NetInterface *interface, const IpAddr *targetIpAddr, size_t size, uint8_t ttl, systime_t timeout, systime_t *rtt)
Test the reachability of a host.
Definition: ping.c:66
Ipv4Addr ipv4Addr
Definition: ip.h:63
systime_t osGetSystemTime(void)
Retrieve system time.
systime_t timeout
Definition: ping.h:79
uint32_t time
TCP/IP stack core.
systime_t timestamp
Definition: ping.h:78
Debugging facilities.
size_t dataPayloadSize
Definition: ping.h:75
uint8_t message[]
Definition: chap.h:150
error_t ipv4SelectSourceAddr(NetInterface **interface, Ipv4Addr destAddr, Ipv4Addr *srcAddr)
IPv4 source address selection.
Definition: ipv4.c:1134
Invalid parameter.
Definition: error.h:45
void pingRelease(PingContext *context)
Release ping context.
Definition: ping.c:546
IP network address.
Definition: ip.h:57
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:797
IPv4 and IPv6 common routines.
__start_packed struct @183 Ipv6Addr
IPv6 network address.
char_t * ipAddrToString(const IpAddr *ipAddr, char_t *str)
Convert a binary IP address to a string representation.
Definition: ip.c:685
#define ENABLED
Definition: os_port.h:35
uint16_t ipCalcChecksum(const void *data, size_t length)
IP checksum calculation.
Definition: ip.c:351
#define PING_BUFFER_SIZE
Definition: ping.h:59
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:216
#define PING_MAX_DATA_SIZE
Definition: ping.h:53
error_t pingWaitForReply(PingContext *context, IpAddr *targetIpAddr, systime_t *rtt)
Wait for a matching ICMP Echo Reply message.
Definition: ping.c:452
uint32_t netGetRand(void)
Get a random value.
Definition: net.c:1523
#define htonl(value)
Definition: cpu_endian.h:391
IPv4 (Internet Protocol Version 4)
#define IPV4_SUPPORT
Definition: ipv4.h:47
uint32_t ttl
Definition: dns_common.h:203
Socket * socket
Definition: ping.h:74
#define Ipv6PseudoHeader
Definition: ipv6.h:40
#define MIN(a, b)
Definition: os_port.h:60
uint8_t buffer[PING_BUFFER_SIZE]
Definition: ping.h:81
Ping context.
Definition: ping.h:71
Helper functions for IPv6.
Ipv4Addr srcIpAddr
Definition: ipcp.h:75
#define TRACE_INFO(...)
Definition: debug.h:86
IPv6 (Internet Protocol Version 6)
Success.
Definition: error.h:42
uint16_t sequenceNumber
Definition: ping.h:77
error_t
Error codes.
Definition: error.h:40
error_t pingSetTimeout(PingContext *context, systime_t timeout)
Set timeout value.
Definition: ping.c:143
Ipv4Addr destIpAddr
Definition: ipcp.h:76
size_t length
Definition: ip.h:59
Socket * socketOpen(uint_t type, uint_t protocol)
Create a socket (UDP or TCP)
Definition: socket.c:92
void osReleaseMutex(OsMutex *mutex)
Release ownership of the specified mutex object.
Ipv6Addr ipv6Addr
Definition: ip.h:66
ICMPv6 (Internet Control Message Protocol Version 6)
#define PRIuSIZE
Definition: compiler_port.h:72
#define NetInterface
Definition: net.h:34
systime_t rtt
Definition: ping.h:80
NetInterface * interface
Definition: ping.h:73
ICMP (Internet Control Message Protocol)
OsMutex netMutex
Definition: net.c:70
uint16_t ipCalcUpperLayerChecksum(const void *pseudoHeader, size_t pseudoHeaderLen, const void *data, size_t dataLen)
Calculate IP upper-layer checksum.
Definition: ip.c:544
Socket API.
#define PING_DEFAULT_TIMEOUT
Definition: ping.h:46
uint8_t length
Definition: dtls_misc.h:140
void pingInit(PingContext *context)
Initialize ping context.
Definition: ping.c:122
error_t socketSendTo(Socket *socket, const IpAddr *destIpAddr, uint16_t destPort, const void *data, size_t length, size_t *written, uint_t flags)
Send a datagram to a specific destination.
Definition: socket.c:511
error_t socketReceiveEx(Socket *socket, IpAddr *srcIpAddr, uint16_t *srcPort, IpAddr *destIpAddr, void *data, size_t size, size_t *received, uint_t flags)
Receive a datagram.
Definition: socket.c:625
void osAcquireMutex(OsMutex *mutex)
Acquire ownership of the specified mutex object.
error_t socketBindToInterface(Socket *socket, NetInterface *interface)
Bind a socket to a particular network interface.
Definition: socket.c:309
error_t pingSendRequest(PingContext *context, const IpAddr *targetIpAddr, size_t size, uint8_t ttl)
Send an ICMP Echo Request message.
Definition: ping.c:187