SimplePing.m 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783
  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 "SimplePing.h"
  8. #include <sys/socket.h>
  9. #include <netinet/in.h>
  10. #include <errno.h>
  11. #pragma mark * IPv4 and ICMPv4 On-The-Wire Format
  12. /*! Describes the on-the-wire header format for an IPv4 packet.
  13. * \details This defines the header structure of IPv4 packets on the wire. We need
  14. * this in order to skip this header in the IPv4 case, where the kernel passes
  15. * it to us for no obvious reason.
  16. */
  17. struct IPv4Header {
  18. uint8_t versionAndHeaderLength;
  19. uint8_t differentiatedServices;
  20. uint16_t totalLength;
  21. uint16_t identification;
  22. uint16_t flagsAndFragmentOffset;
  23. uint8_t timeToLive;
  24. uint8_t protocol;
  25. uint16_t headerChecksum;
  26. uint8_t sourceAddress[4];
  27. uint8_t destinationAddress[4];
  28. // options...
  29. // data...
  30. };
  31. typedef struct IPv4Header IPv4Header;
  32. __Check_Compile_Time(sizeof(IPv4Header) == 20);
  33. __Check_Compile_Time(offsetof(IPv4Header, versionAndHeaderLength) == 0);
  34. __Check_Compile_Time(offsetof(IPv4Header, differentiatedServices) == 1);
  35. __Check_Compile_Time(offsetof(IPv4Header, totalLength) == 2);
  36. __Check_Compile_Time(offsetof(IPv4Header, identification) == 4);
  37. __Check_Compile_Time(offsetof(IPv4Header, flagsAndFragmentOffset) == 6);
  38. __Check_Compile_Time(offsetof(IPv4Header, timeToLive) == 8);
  39. __Check_Compile_Time(offsetof(IPv4Header, protocol) == 9);
  40. __Check_Compile_Time(offsetof(IPv4Header, headerChecksum) == 10);
  41. __Check_Compile_Time(offsetof(IPv4Header, sourceAddress) == 12);
  42. __Check_Compile_Time(offsetof(IPv4Header, destinationAddress) == 16);
  43. /*! Calculates an IP checksum.
  44. * \details This is the standard BSD checksum code, modified to use modern types.
  45. * \param buffer A pointer to the data to checksum.
  46. * \param bufferLen The length of that data.
  47. * \returns The checksum value, in network byte order.
  48. */
  49. static uint16_t in_cksum(const void *buffer, size_t bufferLen) {
  50. //
  51. size_t bytesLeft;
  52. int32_t sum;
  53. const uint16_t * cursor;
  54. union {
  55. uint16_t us;
  56. uint8_t uc[2];
  57. } last;
  58. uint16_t answer;
  59. bytesLeft = bufferLen;
  60. sum = 0;
  61. cursor = buffer;
  62. /*
  63. * Our algorithm is simple, using a 32 bit accumulator (sum), we add
  64. * sequential 16 bit words to it, and at the end, fold back all the
  65. * carry bits from the top 16 bits into the lower 16 bits.
  66. */
  67. while (bytesLeft > 1) {
  68. sum += *cursor;
  69. cursor += 1;
  70. bytesLeft -= 2;
  71. }
  72. /* mop up an odd byte, if necessary */
  73. if (bytesLeft == 1) {
  74. last.uc[0] = * (const uint8_t *) cursor;
  75. last.uc[1] = 0;
  76. sum += last.us;
  77. }
  78. /* add back carry outs from top 16 bits to low 16 bits */
  79. sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
  80. sum += (sum >> 16); /* add carry */
  81. answer = (uint16_t) ~sum; /* truncate to 16 bits */
  82. return answer;
  83. }
  84. #pragma mark * SimplePing
  85. @interface SimplePing ()
  86. // read/write versions of public properties
  87. @property (nonatomic, copy, readwrite, nullable) NSData * hostAddress;
  88. @property (nonatomic, assign, readwrite ) uint16_t nextSequenceNumber;
  89. // private properties
  90. /*! True if nextSequenceNumber has wrapped from 65535 to 0.
  91. */
  92. @property (nonatomic, assign, readwrite) BOOL nextSequenceNumberHasWrapped;
  93. /*! A host object for name-to-address resolution.
  94. */
  95. @property (nonatomic, strong, readwrite, nullable) CFHostRef host __attribute__ ((NSObject));
  96. /*! A socket object for ICMP send and receive.
  97. */
  98. @property (nonatomic, strong, readwrite, nullable) CFSocketRef socket __attribute__ ((NSObject));
  99. @end
  100. @implementation SimplePing
  101. - (instancetype)initWithHostName:(NSString *)hostName {
  102. NSParameterAssert(hostName != nil);
  103. self = [super init];
  104. if (self != nil) {
  105. self->_hostName = [hostName copy];
  106. self->_identifier = (uint16_t) arc4random();
  107. }
  108. return self;
  109. }
  110. - (void)dealloc {
  111. [self stop];
  112. // Double check that -stop took care of _host and _socket.
  113. assert(self->_host == NULL);
  114. assert(self->_socket == NULL);
  115. }
  116. - (sa_family_t)hostAddressFamily {
  117. sa_family_t result;
  118. result = AF_UNSPEC;
  119. if ( (self.hostAddress != nil) && (self.hostAddress.length >= sizeof(struct sockaddr)) ) {
  120. result = ((const struct sockaddr *) self.hostAddress.bytes)->sa_family;
  121. }
  122. return result;
  123. }
  124. /*! Shuts down the pinger object and tell the delegate about the error.
  125. * \param error Describes the failure.
  126. */
  127. - (void)didFailWithError:(NSError *)error {
  128. id<SimplePingDelegate> strongDelegate;
  129. assert(error != nil);
  130. // We retain ourselves temporarily because it's common for the delegate method
  131. // to release its last reference to us, which causes -dealloc to be called here.
  132. // If we then reference self on the return path, things go badly. I don't think
  133. // that happens currently, but I've got into the habit of doing this as a
  134. // defensive measure.
  135. CFAutorelease( CFBridgingRetain( self ));
  136. [self stop];
  137. strongDelegate = self.delegate;
  138. if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didFailWithError:)] ) {
  139. [strongDelegate simplePing:self didFailWithError:error];
  140. }
  141. }
  142. /*! Shuts down the pinger object and tell the delegate about the error.
  143. * \details This converts the CFStreamError to an NSError and then call through to
  144. * -didFailWithError: to do the real work.
  145. * \param streamError Describes the failure.
  146. */
  147. - (void)didFailWithHostStreamError:(CFStreamError)streamError {
  148. NSDictionary * userInfo;
  149. NSError * error;
  150. if (streamError.domain == kCFStreamErrorDomainNetDB) {
  151. userInfo = @{(id) kCFGetAddrInfoFailureKey: @(streamError.error)};
  152. } else {
  153. userInfo = nil;
  154. }
  155. error = [NSError errorWithDomain:(NSString *) kCFErrorDomainCFNetwork code:kCFHostErrorUnknown userInfo:userInfo];
  156. [self didFailWithError:error];
  157. }
  158. /*! Builds a ping packet from the supplied parameters.
  159. * \param type The packet type, which is different for IPv4 and IPv6.
  160. * \param payload Data to place after the ICMP header.
  161. * \param requiresChecksum Determines whether a checksum is calculated (IPv4) or not (IPv6).
  162. * \returns A ping packet suitable to be passed to the kernel.
  163. */
  164. - (NSData *)pingPacketWithType:(uint8_t)type payload:(NSData *)payload requiresChecksum:(BOOL)requiresChecksum {
  165. NSMutableData * packet;
  166. ICMPHeader * icmpPtr;
  167. packet = [NSMutableData dataWithLength:sizeof(*icmpPtr) + payload.length];
  168. assert(packet != nil);
  169. icmpPtr = packet.mutableBytes;
  170. icmpPtr->type = type;
  171. icmpPtr->code = 0;
  172. icmpPtr->checksum = 0;
  173. icmpPtr->identifier = OSSwapHostToBigInt16(self.identifier);
  174. icmpPtr->sequenceNumber = OSSwapHostToBigInt16(self.nextSequenceNumber);
  175. memcpy(&icmpPtr[1], [payload bytes], [payload length]);
  176. if (requiresChecksum) {
  177. // The IP checksum routine returns a 16-bit number that's already in correct byte order
  178. // (due to wacky 1's complement maths), so we just put it into the packet as a 16-bit unit.
  179. icmpPtr->checksum = in_cksum(packet.bytes, packet.length);
  180. }
  181. return packet;
  182. }
  183. - (void)sendPingWithData:(NSData *)data {
  184. int err;
  185. NSData * payload;
  186. NSData * packet;
  187. ssize_t bytesSent;
  188. id<SimplePingDelegate> strongDelegate;
  189. // data may be nil
  190. NSParameterAssert(self.hostAddress != nil); // gotta wait for -simplePing:didStartWithAddress:
  191. // Construct the ping packet.
  192. payload = data;
  193. if (payload == nil) {
  194. payload = [[NSString stringWithFormat:@"%28zd bottles of beer on the wall", (ssize_t) 99 - (size_t) (self.nextSequenceNumber % 100) ] dataUsingEncoding:NSASCIIStringEncoding];
  195. assert(payload != nil);
  196. // Our dummy payload is sized so that the resulting ICMP packet, including the ICMPHeader, is
  197. // 64-bytes, which makes it easier to recognise our packets on the wire.
  198. assert([payload length] == 56);
  199. }
  200. switch (self.hostAddressFamily) {
  201. case AF_INET: {
  202. packet = [self pingPacketWithType:ICMPv4TypeEchoRequest payload:payload requiresChecksum:YES];
  203. } break;
  204. case AF_INET6: {
  205. packet = [self pingPacketWithType:ICMPv6TypeEchoRequest payload:payload requiresChecksum:NO];
  206. } break;
  207. default: {
  208. assert(NO);
  209. } break;
  210. }
  211. assert(packet != nil);
  212. // Send the packet.
  213. if (self.socket == NULL) {
  214. bytesSent = -1;
  215. err = EBADF;
  216. } else {
  217. bytesSent = sendto(
  218. CFSocketGetNative(self.socket),
  219. packet.bytes,
  220. packet.length,
  221. 0,
  222. self.hostAddress.bytes,
  223. (socklen_t) self.hostAddress.length
  224. );
  225. err = 0;
  226. if (bytesSent < 0) {
  227. err = errno;
  228. }
  229. }
  230. // Handle the results of the send.
  231. strongDelegate = self.delegate;
  232. if ( (bytesSent > 0) && (((NSUInteger) bytesSent) == packet.length) ) {
  233. // Complete success. Tell the client.
  234. if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didSendPacket:sequenceNumber:)] ) {
  235. [strongDelegate simplePing:self didSendPacket:packet sequenceNumber:self.nextSequenceNumber];
  236. }
  237. } else {
  238. NSError * error;
  239. // Some sort of failure. Tell the client.
  240. if (err == 0) {
  241. err = ENOBUFS; // This is not a hugely descriptor error, alas.
  242. }
  243. error = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil];
  244. if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didFailToSendPacket:sequenceNumber:error:)] ) {
  245. [strongDelegate simplePing:self didFailToSendPacket:packet sequenceNumber:self.nextSequenceNumber error:error];
  246. }
  247. }
  248. self.nextSequenceNumber += 1;
  249. if (self.nextSequenceNumber == 0) {
  250. self.nextSequenceNumberHasWrapped = YES;
  251. }
  252. }
  253. /*! Calculates the offset of the ICMP header within an IPv4 packet.
  254. * \details In the IPv4 case the kernel returns us a buffer that includes the
  255. * IPv4 header. We're not interested in that, so we have to skip over it.
  256. * This code does a rough check of the IPv4 header and, if it looks OK,
  257. * returns the offset of the ICMP header.
  258. * \param packet The IPv4 packet, as returned to us by the kernel.
  259. * \returns The offset of the ICMP header, or NSNotFound.
  260. */
  261. + (NSUInteger)icmpHeaderOffsetInIPv4Packet:(NSData *)packet {
  262. // Returns the offset of the ICMPv4Header within an IP packet.
  263. NSUInteger result;
  264. const struct IPv4Header * ipPtr;
  265. size_t ipHeaderLength;
  266. result = NSNotFound;
  267. if (packet.length >= (sizeof(IPv4Header) + sizeof(ICMPHeader))) {
  268. ipPtr = (const IPv4Header *) packet.bytes;
  269. if ( ((ipPtr->versionAndHeaderLength & 0xF0) == 0x40) && // IPv4
  270. ( ipPtr->protocol == IPPROTO_ICMP ) ) {
  271. ipHeaderLength = (ipPtr->versionAndHeaderLength & 0x0F) * sizeof(uint32_t);
  272. if (packet.length >= (ipHeaderLength + sizeof(ICMPHeader))) {
  273. result = ipHeaderLength;
  274. }
  275. }
  276. }
  277. return result;
  278. }
  279. /*! Checks whether the specified sequence number is one we sent.
  280. * \param sequenceNumber The incoming sequence number.
  281. * \returns YES if the sequence number looks like one we sent.
  282. */
  283. - (BOOL)validateSequenceNumber:(uint16_t)sequenceNumber {
  284. if (self.nextSequenceNumberHasWrapped) {
  285. // If the sequence numbers have wrapped that we can't reliably check
  286. // whether this is a sequence number we sent. Rather, we check to see
  287. // whether the sequence number is within the last 120 sequence numbers
  288. // we sent. Note that the uint16_t subtraction here does the right
  289. // thing regardless of the wrapping.
  290. //
  291. // Why 120? Well, if we send one ping per second, 120 is 2 minutes, which
  292. // is the standard "max time a packet can bounce around the Internet" value.
  293. return ((uint16_t) (self.nextSequenceNumber - sequenceNumber)) < (uint16_t) 120;
  294. } else {
  295. return sequenceNumber < self.nextSequenceNumber;
  296. }
  297. }
  298. /*! Checks whether an incoming IPv4 packet looks like a ping response.
  299. * \details This routine modifies this `packet` data! It does this for two reasons:
  300. *
  301. * * It needs to zero out the `checksum` field of the ICMPHeader in order to do
  302. * its checksum calculation.
  303. *
  304. * * It removes the IPv4 header from the front of the packet.
  305. * \param packet The IPv4 packet, as returned to us by the kernel.
  306. * \param sequenceNumberPtr A pointer to a place to start the ICMP sequence number.
  307. * \returns YES if the packet looks like a reasonable IPv4 ping response.
  308. */
  309. - (BOOL)validatePing4ResponsePacket:(NSMutableData *)packet sequenceNumber:(uint16_t *)sequenceNumberPtr {
  310. BOOL result;
  311. NSUInteger icmpHeaderOffset;
  312. ICMPHeader * icmpPtr;
  313. uint16_t receivedChecksum;
  314. uint16_t calculatedChecksum;
  315. result = NO;
  316. icmpHeaderOffset = [[self class] icmpHeaderOffsetInIPv4Packet:packet];
  317. if (icmpHeaderOffset != NSNotFound) {
  318. icmpPtr = (struct ICMPHeader *) (((uint8_t *) packet.mutableBytes) + icmpHeaderOffset);
  319. receivedChecksum = icmpPtr->checksum;
  320. icmpPtr->checksum = 0;
  321. calculatedChecksum = in_cksum(icmpPtr, packet.length - icmpHeaderOffset);
  322. icmpPtr->checksum = receivedChecksum;
  323. if (receivedChecksum == calculatedChecksum) {
  324. if ( (icmpPtr->type == ICMPv4TypeEchoReply) && (icmpPtr->code == 0) ) {
  325. if ( OSSwapBigToHostInt16(icmpPtr->identifier) == self.identifier ) {
  326. uint16_t sequenceNumber;
  327. sequenceNumber = OSSwapBigToHostInt16(icmpPtr->sequenceNumber);
  328. if ([self validateSequenceNumber:sequenceNumber]) {
  329. // Remove the IPv4 header off the front of the data we received, leaving us with
  330. // just the ICMP header and the ping payload.
  331. [packet replaceBytesInRange:NSMakeRange(0, icmpHeaderOffset) withBytes:NULL length:0];
  332. *sequenceNumberPtr = sequenceNumber;
  333. result = YES;
  334. }
  335. }
  336. }
  337. }
  338. }
  339. return result;
  340. }
  341. /*! Checks whether an incoming IPv6 packet looks like a ping response.
  342. * \param packet The IPv6 packet, as returned to us by the kernel; note that this routine
  343. * could modify this data but does not need to in the IPv6 case.
  344. * \param sequenceNumberPtr A pointer to a place to start the ICMP sequence number.
  345. * \returns YES if the packet looks like a reasonable IPv4 ping response.
  346. */
  347. - (BOOL)validatePing6ResponsePacket:(NSMutableData *)packet sequenceNumber:(uint16_t *)sequenceNumberPtr {
  348. BOOL result;
  349. const ICMPHeader * icmpPtr;
  350. result = NO;
  351. if (packet.length >= sizeof(*icmpPtr)) {
  352. icmpPtr = packet.bytes;
  353. // In the IPv6 case we don't check the checksum because that's hard (we need to
  354. // cook up an IPv6 pseudo header and we don't have the ingredients) and unnecessary
  355. // (the kernel has already done this check).
  356. if ( (icmpPtr->type == ICMPv6TypeEchoReply) && (icmpPtr->code == 0) ) {
  357. if ( OSSwapBigToHostInt16(icmpPtr->identifier) == self.identifier ) {
  358. uint16_t sequenceNumber;
  359. sequenceNumber = OSSwapBigToHostInt16(icmpPtr->sequenceNumber);
  360. if ([self validateSequenceNumber:sequenceNumber]) {
  361. *sequenceNumberPtr = sequenceNumber;
  362. result = YES;
  363. }
  364. }
  365. }
  366. }
  367. return result;
  368. }
  369. /*! Checks whether an incoming packet looks like a ping response.
  370. * \param packet The packet, as returned to us by the kernel; note that may end up modifying
  371. * this data.
  372. * \param sequenceNumberPtr A pointer to a place to start the ICMP sequence number.
  373. * \returns YES if the packet looks like a reasonable IPv4 ping response.
  374. */
  375. - (BOOL)validatePingResponsePacket:(NSMutableData *)packet sequenceNumber:(uint16_t *)sequenceNumberPtr {
  376. BOOL result;
  377. switch (self.hostAddressFamily) {
  378. case AF_INET: {
  379. result = [self validatePing4ResponsePacket:packet sequenceNumber:sequenceNumberPtr];
  380. } break;
  381. case AF_INET6: {
  382. result = [self validatePing6ResponsePacket:packet sequenceNumber:sequenceNumberPtr];
  383. } break;
  384. default: {
  385. assert(NO);
  386. result = NO;
  387. } break;
  388. }
  389. return result;
  390. }
  391. /*! Reads data from the ICMP socket.
  392. * \details Called by the socket handling code (SocketReadCallback) to process an ICMP
  393. * message waiting on the socket.
  394. */
  395. - (void)readData {
  396. int err;
  397. struct sockaddr_storage addr;
  398. socklen_t addrLen;
  399. ssize_t bytesRead;
  400. void * buffer;
  401. enum { kBufferSize = 65535 };
  402. // 65535 is the maximum IP packet size, which seems like a reasonable bound
  403. // here (plus it's what <x-man-page://8/ping> uses).
  404. buffer = malloc(kBufferSize);
  405. assert(buffer != NULL);
  406. // Actually read the data. We use recvfrom(), and thus get back the source address,
  407. // but we don't actually do anything with it. It would be trivial to pass it to
  408. // the delegate but we don't need it in this example.
  409. addrLen = sizeof(addr);
  410. bytesRead = recvfrom(CFSocketGetNative(self.socket), buffer, kBufferSize, 0, (struct sockaddr *) &addr, &addrLen);
  411. err = 0;
  412. if (bytesRead < 0) {
  413. err = errno;
  414. }
  415. // Process the data we read.
  416. if (bytesRead > 0) {
  417. NSMutableData * packet;
  418. id<SimplePingDelegate> strongDelegate;
  419. uint16_t sequenceNumber;
  420. packet = [NSMutableData dataWithBytes:buffer length:(NSUInteger) bytesRead];
  421. assert(packet != nil);
  422. // We got some data, pass it up to our client.
  423. strongDelegate = self.delegate;
  424. if ( [self validatePingResponsePacket:packet sequenceNumber:&sequenceNumber] ) {
  425. if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didReceivePingResponsePacket:sequenceNumber:)] ) {
  426. [strongDelegate simplePing:self didReceivePingResponsePacket:packet sequenceNumber:sequenceNumber];
  427. }
  428. } else {
  429. if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didReceiveUnexpectedPacket:)] ) {
  430. [strongDelegate simplePing:self didReceiveUnexpectedPacket:packet];
  431. }
  432. }
  433. } else {
  434. // We failed to read the data, so shut everything down.
  435. if (err == 0) {
  436. err = EPIPE;
  437. }
  438. [self didFailWithError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
  439. }
  440. free(buffer);
  441. // Note that we don't loop back trying to read more data. Rather, we just
  442. // let CFSocket call us again.
  443. }
  444. /*! The callback for our CFSocket object.
  445. * \details This simply routes the call to our `-readData` method.
  446. * \param s See the documentation for CFSocketCallBack.
  447. * \param type See the documentation for CFSocketCallBack.
  448. * \param address See the documentation for CFSocketCallBack.
  449. * \param data See the documentation for CFSocketCallBack.
  450. * \param info See the documentation for CFSocketCallBack; this is actually a pointer to the
  451. * 'owning' object.
  452. */
  453. static void SocketReadCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
  454. // This C routine is called by CFSocket when there's data waiting on our
  455. // ICMP socket. It just redirects the call to Objective-C code.
  456. SimplePing * obj;
  457. obj = (__bridge SimplePing *) info;
  458. assert([obj isKindOfClass:[SimplePing class]]);
  459. #pragma unused(s)
  460. assert(s == obj.socket);
  461. #pragma unused(type)
  462. assert(type == kCFSocketReadCallBack);
  463. #pragma unused(address)
  464. assert(address == nil);
  465. #pragma unused(data)
  466. assert(data == nil);
  467. [obj readData];
  468. }
  469. /*! Starts the send and receive infrastructure.
  470. * \details This is called once we've successfully resolved `hostName` in to
  471. * `hostAddress`. It's responsible for setting up the socket for sending and
  472. * receiving pings.
  473. */
  474. - (void)startWithHostAddress {
  475. int err;
  476. int fd;
  477. assert(self.hostAddress != nil);
  478. // Open the socket.
  479. fd = -1;
  480. err = 0;
  481. switch (self.hostAddressFamily) {
  482. case AF_INET: {
  483. fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
  484. if (fd < 0) {
  485. err = errno;
  486. }
  487. } break;
  488. case AF_INET6: {
  489. fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
  490. if (fd < 0) {
  491. err = errno;
  492. }
  493. } break;
  494. default: {
  495. err = EPROTONOSUPPORT;
  496. } break;
  497. }
  498. if (err != 0) {
  499. [self didFailWithError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
  500. } else {
  501. CFSocketContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
  502. CFRunLoopSourceRef rls;
  503. id<SimplePingDelegate> strongDelegate;
  504. // Wrap it in a CFSocket and schedule it on the runloop.
  505. self.socket = (CFSocketRef) CFAutorelease( CFSocketCreateWithNative(NULL, fd, kCFSocketReadCallBack, SocketReadCallback, &context) );
  506. assert(self.socket != NULL);
  507. // The socket will now take care of cleaning up our file descriptor.
  508. assert( CFSocketGetSocketFlags(self.socket) & kCFSocketCloseOnInvalidate );
  509. fd = -1;
  510. rls = CFSocketCreateRunLoopSource(NULL, self.socket, 0);
  511. assert(rls != NULL);
  512. CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
  513. CFRelease(rls);
  514. strongDelegate = self.delegate;
  515. if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didStartWithAddress:)] ) {
  516. [strongDelegate simplePing:self didStartWithAddress:self.hostAddress];
  517. }
  518. }
  519. assert(fd == -1);
  520. }
  521. /*! Processes the results of our name-to-address resolution.
  522. * \details Called by our CFHost resolution callback (HostResolveCallback) when host
  523. * resolution is complete. We just latch the first appropriate address and kick
  524. * off the send and receive infrastructure.
  525. */
  526. - (void)hostResolutionDone {
  527. Boolean resolved;
  528. NSArray * addresses;
  529. // Find the first appropriate address.
  530. addresses = (__bridge NSArray *) CFHostGetAddressing(self.host, &resolved);
  531. if ( resolved && (addresses != nil) ) {
  532. resolved = false;
  533. for (NSData * address in addresses) {
  534. const struct sockaddr * addrPtr;
  535. addrPtr = (const struct sockaddr *) address.bytes;
  536. if ( address.length >= sizeof(struct sockaddr) ) {
  537. switch (addrPtr->sa_family) {
  538. case AF_INET: {
  539. if (self.addressStyle != SimplePingAddressStyleICMPv6) {
  540. self.hostAddress = address;
  541. resolved = true;
  542. }
  543. } break;
  544. case AF_INET6: {
  545. if (self.addressStyle != SimplePingAddressStyleICMPv4) {
  546. self.hostAddress = address;
  547. resolved = true;
  548. }
  549. } break;
  550. }
  551. }
  552. if (resolved) {
  553. break;
  554. }
  555. }
  556. }
  557. // We're done resolving, so shut that down.
  558. [self stopHostResolution];
  559. // If all is OK, start the send and receive infrastructure, otherwise stop.
  560. if (resolved) {
  561. [self startWithHostAddress];
  562. } else {
  563. [self didFailWithError:[NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorHostNotFound userInfo:nil]];
  564. }
  565. }
  566. /*! The callback for our CFHost object.
  567. * \details This simply routes the call to our `-hostResolutionDone` or
  568. * `-didFailWithHostStreamError:` methods.
  569. * \param theHost See the documentation for CFHostClientCallBack.
  570. * \param typeInfo See the documentation for CFHostClientCallBack.
  571. * \param error See the documentation for CFHostClientCallBack.
  572. * \param info See the documentation for CFHostClientCallBack; this is actually a pointer to
  573. * the 'owning' object.
  574. */
  575. static void HostResolveCallback(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void *info) {
  576. // This C routine is called by CFHost when the host resolution is complete.
  577. // It just redirects the call to the appropriate Objective-C method.
  578. SimplePing * obj;
  579. obj = (__bridge SimplePing *) info;
  580. assert([obj isKindOfClass:[SimplePing class]]);
  581. #pragma unused(theHost)
  582. assert(theHost == obj.host);
  583. #pragma unused(typeInfo)
  584. assert(typeInfo == kCFHostAddresses);
  585. if ( (error != NULL) && (error->domain != 0) ) {
  586. [obj didFailWithHostStreamError:*error];
  587. } else {
  588. [obj hostResolutionDone];
  589. }
  590. }
  591. - (void)start {
  592. Boolean success;
  593. CFHostClientContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
  594. CFStreamError streamError;
  595. assert(self.host == NULL);
  596. assert(self.hostAddress == nil);
  597. self.host = (CFHostRef) CFAutorelease( CFHostCreateWithName(NULL, (__bridge CFStringRef) self.hostName) );
  598. assert(self.host != NULL);
  599. CFHostSetClient(self.host, HostResolveCallback, &context);
  600. CFHostScheduleWithRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
  601. success = CFHostStartInfoResolution(self.host, kCFHostAddresses, &streamError);
  602. if ( ! success ) {
  603. [self didFailWithHostStreamError:streamError];
  604. }
  605. }
  606. /*! Stops the name-to-address resolution infrastructure.
  607. */
  608. - (void)stopHostResolution {
  609. // Shut down the CFHost.
  610. if (self.host != NULL) {
  611. CFHostSetClient(self.host, NULL, NULL);
  612. CFHostUnscheduleFromRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
  613. self.host = NULL;
  614. }
  615. }
  616. /*! Stops the send and receive infrastructure.
  617. */
  618. - (void)stopSocket {
  619. if (self.socket != NULL) {
  620. CFSocketInvalidate(self.socket);
  621. self.socket = NULL;
  622. }
  623. }
  624. - (void)stop {
  625. [self stopHostResolution];
  626. [self stopSocket];
  627. // Junk the host address on stop. If the client calls -start again, we'll
  628. // re-resolve the host name.
  629. self.hostAddress = NULL;
  630. }
  631. @end