SimplePing.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. /*
  2. Copyright (C) 2016 Apple Inc. All Rights Reserved.
  3. See LICENSE.txt for this sample’s licensing information
  4. Abstract:
  5. An object wrapper around the low-level BSD Sockets ping function.
  6. */
  7. #import <Foundation/Foundation.h>
  8. #include <AssertMacros.h> // for __Check_Compile_Time
  9. #ifndef _SA_FAMILY_T
  10. #define _SA_FAMILY_T
  11. #include <machine/types.h> /* __uint8_t */
  12. typedef __uint8_t sa_family_t;
  13. #endif /* _SA_FAMILY_T */
  14. NS_ASSUME_NONNULL_BEGIN
  15. @protocol SimplePingDelegate;
  16. /*! Controls the IP address version used by SimplePing instances.
  17. */
  18. typedef NS_ENUM(NSInteger, SimplePingAddressStyle) {
  19. SimplePingAddressStyleAny, ///< Use the first IPv4 or IPv6 address found; the default.
  20. SimplePingAddressStyleICMPv4, ///< Use the first IPv4 address found.
  21. SimplePingAddressStyleICMPv6 ///< Use the first IPv6 address found.
  22. };
  23. /*! An object wrapper around the low-level BSD Sockets ping function.
  24. * \details To use the class create an instance, set the delegate and call `-start`
  25. * to start the instance on the current run loop. If things go well you'll soon get the
  26. * `-simplePing:didStartWithAddress:` delegate callback. From there you can can call
  27. * `-sendPingWithData:` to send a ping and you'll receive the
  28. * `-simplePing:didReceivePingResponsePacket:sequenceNumber:` and
  29. * `-simplePing:didReceiveUnexpectedPacket:` delegate callbacks as ICMP packets arrive.
  30. *
  31. * The class can be used from any thread but the use of any single instance must be
  32. * confined to a specific thread and that thread must run its run loop.
  33. */
  34. @interface SimplePing : NSObject
  35. - (instancetype)init NS_UNAVAILABLE;
  36. /*! Initialise the object to ping the specified host.
  37. * \param hostName The DNS name of the host to ping; an IPv4 or IPv6 address in string form will
  38. * work here.
  39. * \returns The initialised object.
  40. */
  41. - (instancetype)initWithHostName:(NSString *)hostName NS_DESIGNATED_INITIALIZER;
  42. /*! A copy of the value passed to `-initWithHostName:`.
  43. */
  44. @property (nonatomic, copy, readonly) NSString * hostName;
  45. /*! The delegate for this object.
  46. * \details Delegate callbacks are schedule in the default run loop mode of the run loop of the
  47. * thread that calls `-start`.
  48. */
  49. @property (nonatomic, weak, readwrite, nullable) id<SimplePingDelegate> delegate;
  50. /*! Controls the IP address version used by the object.
  51. * \details You should set this value before starting the object.
  52. */
  53. @property (nonatomic, assign, readwrite) SimplePingAddressStyle addressStyle;
  54. /*! The address being pinged.
  55. * \details The contents of the NSData is a (struct sockaddr) of some form. The
  56. * value is nil while the object is stopped and remains nil on start until
  57. * `-simplePing:didStartWithAddress:` is called.
  58. */
  59. @property (nonatomic, copy, readonly, nullable) NSData * hostAddress;
  60. /*! The address family for `hostAddress`, or `AF_UNSPEC` if that's nil.
  61. */
  62. @property (nonatomic, assign, readonly) sa_family_t hostAddressFamily;
  63. /*! The identifier used by pings by this object.
  64. * \details When you create an instance of this object it generates a random identifier
  65. * that it uses to identify its own pings.
  66. */
  67. @property (nonatomic, assign, readonly) uint16_t identifier;
  68. /*! The next sequence number to be used by this object.
  69. * \details This value starts at zero and increments each time you send a ping (safely
  70. * wrapping back to zero if necessary). The sequence number is included in the ping,
  71. * allowing you to match up requests and responses, and thus calculate ping times and
  72. * so on.
  73. */
  74. @property (nonatomic, assign, readonly) uint16_t nextSequenceNumber;
  75. /*! Starts the object.
  76. * \details You should set up the delegate and any ping parameters before calling this.
  77. *
  78. * If things go well you'll soon get the `-simplePing:didStartWithAddress:` delegate
  79. * callback, at which point you can start sending pings (via `-sendPingWithData:`) and
  80. * will start receiving ICMP packets (either ping responses, via the
  81. * `-simplePing:didReceivePingResponsePacket:sequenceNumber:` delegate callback, or
  82. * unsolicited ICMP packets, via the `-simplePing:didReceiveUnexpectedPacket:` delegate
  83. * callback).
  84. *
  85. * If the object fails to start, typically because `hostName` doesn't resolve, you'll get
  86. * the `-simplePing:didFailWithError:` delegate callback.
  87. *
  88. * It is not correct to start an already started object.
  89. */
  90. - (void)start;
  91. /*! Sends a ping packet containing the specified data.
  92. * \details Sends an actual ping.
  93. *
  94. * The object must be started when you call this method and, on starting the object, you must
  95. * wait for the `-simplePing:didStartWithAddress:` delegate callback before calling it.
  96. * \param data Some data to include in the ping packet, after the ICMP header, or nil if you
  97. * want the packet to include a standard 56 byte payload (resulting in a standard 64 byte
  98. * ping).
  99. */
  100. - (void)sendPingWithData:(nullable NSData *)data;
  101. /*! Stops the object.
  102. * \details You should call this when you're done pinging.
  103. *
  104. * It's safe to call this on an object that's stopped.
  105. */
  106. - (void)stop;
  107. @end
  108. /*! A delegate protocol for the SimplePing class.
  109. */
  110. @protocol SimplePingDelegate <NSObject>
  111. @optional
  112. /*! A SimplePing delegate callback, called once the object has started up.
  113. * \details This is called shortly after you start the object to tell you that the
  114. * object has successfully started. On receiving this callback, you can call
  115. * `-sendPingWithData:` to send pings.
  116. *
  117. * If the object didn't start, `-simplePing:didFailWithError:` is called instead.
  118. * \param pinger The object issuing the callback.
  119. * \param address The address that's being pinged; at the time this delegate callback
  120. * is made, this will have the same value as the `hostAddress` property.
  121. */
  122. - (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address;
  123. /*! A SimplePing delegate callback, called if the object fails to start up.
  124. * \details This is called shortly after you start the object to tell you that the
  125. * object has failed to start. The most likely cause of failure is a problem
  126. * resolving `hostName`.
  127. *
  128. * By the time this callback is called, the object has stopped (that is, you don't
  129. * need to call `-stop` yourself).
  130. * \param pinger The object issuing the callback.
  131. * \param error Describes the failure.
  132. */
  133. - (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error;
  134. /*! A SimplePing delegate callback, called when the object has successfully sent a ping packet.
  135. * \details Each call to `-sendPingWithData:` will result in either a
  136. * `-simplePing:didSendPacket:sequenceNumber:` delegate callback or a
  137. * `-simplePing:didFailToSendPacket:sequenceNumber:error:` delegate callback (unless you
  138. * stop the object before you get the callback). These callbacks are currently delivered
  139. * synchronously from within `-sendPingWithData:`, but this synchronous behaviour is not
  140. * considered API.
  141. * \param pinger The object issuing the callback.
  142. * \param packet The packet that was sent; this includes the ICMP header (`ICMPHeader`) and the
  143. * data you passed to `-sendPingWithData:` but does not include any IP-level headers.
  144. * \param sequenceNumber The ICMP sequence number of that packet.
  145. */
  146. - (void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber;
  147. /*! A SimplePing delegate callback, called when the object fails to send a ping packet.
  148. * \details Each call to `-sendPingWithData:` will result in either a
  149. * `-simplePing:didSendPacket:sequenceNumber:` delegate callback or a
  150. * `-simplePing:didFailToSendPacket:sequenceNumber:error:` delegate callback (unless you
  151. * stop the object before you get the callback). These callbacks are currently delivered
  152. * synchronously from within `-sendPingWithData:`, but this synchronous behaviour is not
  153. * considered API.
  154. * \param pinger The object issuing the callback.
  155. * \param packet The packet that was not sent; see `-simplePing:didSendPacket:sequenceNumber:`
  156. * for details.
  157. * \param sequenceNumber The ICMP sequence number of that packet.
  158. * \param error Describes the failure.
  159. */
  160. - (void)simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber error:(NSError *)error;
  161. /*! A SimplePing delegate callback, called when the object receives a ping response.
  162. * \details If the object receives an ping response that matches a ping request that it
  163. * sent, it informs the delegate via this callback. Matching is primarily done based on
  164. * the ICMP identifier, although other criteria are used as well.
  165. * \param pinger The object issuing the callback.
  166. * \param packet The packet received; this includes the ICMP header (`ICMPHeader`) and any data that
  167. * follows that in the ICMP message but does not include any IP-level headers.
  168. * \param sequenceNumber The ICMP sequence number of that packet.
  169. */
  170. - (void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber;
  171. /*! A SimplePing delegate callback, called when the object receives an unmatched ICMP message.
  172. * \details If the object receives an ICMP message that does not match a ping request that it
  173. * sent, it informs the delegate via this callback. The nature of ICMP handling in a
  174. * BSD kernel makes this a common event because, when an ICMP message arrives, it is
  175. * delivered to all ICMP sockets.
  176. *
  177. * IMPORTANT: This callback is especially common when using IPv6 because IPv6 uses ICMP
  178. * for important network management functions. For example, IPv6 routers periodically
  179. * send out Router Advertisement (RA) packets via Neighbor Discovery Protocol (NDP), which
  180. * is implemented on top of ICMP.
  181. *
  182. * For more on matching, see the discussion associated with
  183. * `-simplePing:didReceivePingResponsePacket:sequenceNumber:`.
  184. * \param pinger The object issuing the callback.
  185. * \param packet The packet received; this includes the ICMP header (`ICMPHeader`) and any data that
  186. * follows that in the ICMP message but does not include any IP-level headers.
  187. */
  188. - (void)simplePing:(SimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet;
  189. @end
  190. #pragma mark * ICMP On-The-Wire Format
  191. /*! Describes the on-the-wire header format for an ICMP ping.
  192. * \details This defines the header structure of ping packets on the wire. Both IPv4 and
  193. * IPv6 use the same basic structure.
  194. *
  195. * This is declared in the header because clients of SimplePing might want to use
  196. * it parse received ping packets.
  197. */
  198. struct ICMPHeader {
  199. uint8_t type;
  200. uint8_t code;
  201. uint16_t checksum;
  202. uint16_t identifier;
  203. uint16_t sequenceNumber;
  204. // data...
  205. };
  206. typedef struct ICMPHeader ICMPHeader;
  207. __Check_Compile_Time(sizeof(ICMPHeader) == 8);
  208. __Check_Compile_Time(offsetof(ICMPHeader, type) == 0);
  209. __Check_Compile_Time(offsetof(ICMPHeader, code) == 1);
  210. __Check_Compile_Time(offsetof(ICMPHeader, checksum) == 2);
  211. __Check_Compile_Time(offsetof(ICMPHeader, identifier) == 4);
  212. __Check_Compile_Time(offsetof(ICMPHeader, sequenceNumber) == 6);
  213. enum {
  214. ICMPv4TypeEchoRequest = 8, ///< The ICMP `type` for a ping request; in this case `code` is always 0.
  215. ICMPv4TypeEchoReply = 0 ///< The ICMP `type` for a ping response; in this case `code` is always 0.
  216. };
  217. enum {
  218. ICMPv6TypeEchoRequest = 128, ///< The ICMP `type` for a ping request; in this case `code` is always 0.
  219. ICMPv6TypeEchoReply = 129 ///< The ICMP `type` for a ping response; in this case `code` is always 0.
  220. };
  221. NS_ASSUME_NONNULL_END