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