123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783 |
- /*
- Copyright (C) 2016 Apple Inc. All Rights Reserved.
- See LICENSE.txt for this sample’s licensing information
-
- Abstract:
- An object wrapper around the low-level BSD Sockets ping function.
- */
- #import "SimplePing.h"
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <errno.h>
- #pragma mark * IPv4 and ICMPv4 On-The-Wire Format
- /*! Describes the on-the-wire header format for an IPv4 packet.
- * \details This defines the header structure of IPv4 packets on the wire. We need
- * this in order to skip this header in the IPv4 case, where the kernel passes
- * it to us for no obvious reason.
- */
- struct IPv4Header {
- uint8_t versionAndHeaderLength;
- uint8_t differentiatedServices;
- uint16_t totalLength;
- uint16_t identification;
- uint16_t flagsAndFragmentOffset;
- uint8_t timeToLive;
- uint8_t protocol;
- uint16_t headerChecksum;
- uint8_t sourceAddress[4];
- uint8_t destinationAddress[4];
- // options...
- // data...
- };
- typedef struct IPv4Header IPv4Header;
- __Check_Compile_Time(sizeof(IPv4Header) == 20);
- __Check_Compile_Time(offsetof(IPv4Header, versionAndHeaderLength) == 0);
- __Check_Compile_Time(offsetof(IPv4Header, differentiatedServices) == 1);
- __Check_Compile_Time(offsetof(IPv4Header, totalLength) == 2);
- __Check_Compile_Time(offsetof(IPv4Header, identification) == 4);
- __Check_Compile_Time(offsetof(IPv4Header, flagsAndFragmentOffset) == 6);
- __Check_Compile_Time(offsetof(IPv4Header, timeToLive) == 8);
- __Check_Compile_Time(offsetof(IPv4Header, protocol) == 9);
- __Check_Compile_Time(offsetof(IPv4Header, headerChecksum) == 10);
- __Check_Compile_Time(offsetof(IPv4Header, sourceAddress) == 12);
- __Check_Compile_Time(offsetof(IPv4Header, destinationAddress) == 16);
- /*! Calculates an IP checksum.
- * \details This is the standard BSD checksum code, modified to use modern types.
- * \param buffer A pointer to the data to checksum.
- * \param bufferLen The length of that data.
- * \returns The checksum value, in network byte order.
- */
- static uint16_t in_cksum(const void *buffer, size_t bufferLen) {
- //
- size_t bytesLeft;
- int32_t sum;
- const uint16_t * cursor;
- union {
- uint16_t us;
- uint8_t uc[2];
- } last;
- uint16_t answer;
-
- bytesLeft = bufferLen;
- sum = 0;
- cursor = buffer;
-
- /*
- * Our algorithm is simple, using a 32 bit accumulator (sum), we add
- * sequential 16 bit words to it, and at the end, fold back all the
- * carry bits from the top 16 bits into the lower 16 bits.
- */
- while (bytesLeft > 1) {
- sum += *cursor;
- cursor += 1;
- bytesLeft -= 2;
- }
-
- /* mop up an odd byte, if necessary */
- if (bytesLeft == 1) {
- last.uc[0] = * (const uint8_t *) cursor;
- last.uc[1] = 0;
- sum += last.us;
- }
-
- /* add back carry outs from top 16 bits to low 16 bits */
- sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
- sum += (sum >> 16); /* add carry */
- answer = (uint16_t) ~sum; /* truncate to 16 bits */
-
- return answer;
- }
- #pragma mark * SimplePing
- @interface SimplePing ()
- // read/write versions of public properties
- @property (nonatomic, copy, readwrite, nullable) NSData * hostAddress;
- @property (nonatomic, assign, readwrite ) uint16_t nextSequenceNumber;
- // private properties
- /*! True if nextSequenceNumber has wrapped from 65535 to 0.
- */
- @property (nonatomic, assign, readwrite) BOOL nextSequenceNumberHasWrapped;
- /*! A host object for name-to-address resolution.
- */
- @property (nonatomic, strong, readwrite, nullable) CFHostRef host __attribute__ ((NSObject));
- /*! A socket object for ICMP send and receive.
- */
- @property (nonatomic, strong, readwrite, nullable) CFSocketRef socket __attribute__ ((NSObject));
- @end
- @implementation SimplePing
- - (instancetype)initWithHostName:(NSString *)hostName {
- NSParameterAssert(hostName != nil);
- self = [super init];
- if (self != nil) {
- self->_hostName = [hostName copy];
- self->_identifier = (uint16_t) arc4random();
- }
- return self;
- }
- - (void)dealloc {
- [self stop];
- // Double check that -stop took care of _host and _socket.
- assert(self->_host == NULL);
- assert(self->_socket == NULL);
- }
- - (sa_family_t)hostAddressFamily {
- sa_family_t result;
-
- result = AF_UNSPEC;
- if ( (self.hostAddress != nil) && (self.hostAddress.length >= sizeof(struct sockaddr)) ) {
- result = ((const struct sockaddr *) self.hostAddress.bytes)->sa_family;
- }
- return result;
- }
- /*! Shuts down the pinger object and tell the delegate about the error.
- * \param error Describes the failure.
- */
- - (void)didFailWithError:(NSError *)error {
- id<SimplePingDelegate> strongDelegate;
-
- assert(error != nil);
-
- // We retain ourselves temporarily because it's common for the delegate method
- // to release its last reference to us, which causes -dealloc to be called here.
- // If we then reference self on the return path, things go badly. I don't think
- // that happens currently, but I've got into the habit of doing this as a
- // defensive measure.
-
- CFAutorelease( CFBridgingRetain( self ));
-
- [self stop];
- strongDelegate = self.delegate;
- if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didFailWithError:)] ) {
- [strongDelegate simplePing:self didFailWithError:error];
- }
- }
- /*! Shuts down the pinger object and tell the delegate about the error.
- * \details This converts the CFStreamError to an NSError and then call through to
- * -didFailWithError: to do the real work.
- * \param streamError Describes the failure.
- */
- - (void)didFailWithHostStreamError:(CFStreamError)streamError {
- NSDictionary * userInfo;
- NSError * error;
-
- if (streamError.domain == kCFStreamErrorDomainNetDB) {
- userInfo = @{(id) kCFGetAddrInfoFailureKey: @(streamError.error)};
- } else {
- userInfo = nil;
- }
- error = [NSError errorWithDomain:(NSString *) kCFErrorDomainCFNetwork code:kCFHostErrorUnknown userInfo:userInfo];
-
- [self didFailWithError:error];
- }
- /*! Builds a ping packet from the supplied parameters.
- * \param type The packet type, which is different for IPv4 and IPv6.
- * \param payload Data to place after the ICMP header.
- * \param requiresChecksum Determines whether a checksum is calculated (IPv4) or not (IPv6).
- * \returns A ping packet suitable to be passed to the kernel.
- */
- - (NSData *)pingPacketWithType:(uint8_t)type payload:(NSData *)payload requiresChecksum:(BOOL)requiresChecksum {
- NSMutableData * packet;
- ICMPHeader * icmpPtr;
-
- packet = [NSMutableData dataWithLength:sizeof(*icmpPtr) + payload.length];
- assert(packet != nil);
-
- icmpPtr = packet.mutableBytes;
- icmpPtr->type = type;
- icmpPtr->code = 0;
- icmpPtr->checksum = 0;
- icmpPtr->identifier = OSSwapHostToBigInt16(self.identifier);
- icmpPtr->sequenceNumber = OSSwapHostToBigInt16(self.nextSequenceNumber);
- memcpy(&icmpPtr[1], [payload bytes], [payload length]);
-
- if (requiresChecksum) {
- // The IP checksum routine returns a 16-bit number that's already in correct byte order
- // (due to wacky 1's complement maths), so we just put it into the packet as a 16-bit unit.
-
- icmpPtr->checksum = in_cksum(packet.bytes, packet.length);
- }
-
- return packet;
- }
- - (void)sendPingWithData:(NSData *)data {
- int err;
- NSData * payload;
- NSData * packet;
- ssize_t bytesSent;
- id<SimplePingDelegate> strongDelegate;
-
- // data may be nil
- NSParameterAssert(self.hostAddress != nil); // gotta wait for -simplePing:didStartWithAddress:
-
- // Construct the ping packet.
-
- payload = data;
- if (payload == nil) {
- payload = [[NSString stringWithFormat:@"%28zd bottles of beer on the wall", (ssize_t) 99 - (size_t) (self.nextSequenceNumber % 100) ] dataUsingEncoding:NSASCIIStringEncoding];
- assert(payload != nil);
-
- // Our dummy payload is sized so that the resulting ICMP packet, including the ICMPHeader, is
- // 64-bytes, which makes it easier to recognise our packets on the wire.
-
- assert([payload length] == 56);
- }
-
- switch (self.hostAddressFamily) {
- case AF_INET: {
- packet = [self pingPacketWithType:ICMPv4TypeEchoRequest payload:payload requiresChecksum:YES];
- } break;
- case AF_INET6: {
- packet = [self pingPacketWithType:ICMPv6TypeEchoRequest payload:payload requiresChecksum:NO];
- } break;
- default: {
- assert(NO);
- } break;
- }
- assert(packet != nil);
-
- // Send the packet.
-
- if (self.socket == NULL) {
- bytesSent = -1;
- err = EBADF;
- } else {
- bytesSent = sendto(
- CFSocketGetNative(self.socket),
- packet.bytes,
- packet.length,
- 0,
- self.hostAddress.bytes,
- (socklen_t) self.hostAddress.length
- );
- err = 0;
- if (bytesSent < 0) {
- err = errno;
- }
- }
-
- // Handle the results of the send.
-
- strongDelegate = self.delegate;
- if ( (bytesSent > 0) && (((NSUInteger) bytesSent) == packet.length) ) {
-
- // Complete success. Tell the client.
-
- if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didSendPacket:sequenceNumber:)] ) {
- [strongDelegate simplePing:self didSendPacket:packet sequenceNumber:self.nextSequenceNumber];
- }
- } else {
- NSError * error;
-
- // Some sort of failure. Tell the client.
-
- if (err == 0) {
- err = ENOBUFS; // This is not a hugely descriptor error, alas.
- }
- error = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil];
- if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didFailToSendPacket:sequenceNumber:error:)] ) {
- [strongDelegate simplePing:self didFailToSendPacket:packet sequenceNumber:self.nextSequenceNumber error:error];
- }
- }
-
- self.nextSequenceNumber += 1;
- if (self.nextSequenceNumber == 0) {
- self.nextSequenceNumberHasWrapped = YES;
- }
- }
- /*! Calculates the offset of the ICMP header within an IPv4 packet.
- * \details In the IPv4 case the kernel returns us a buffer that includes the
- * IPv4 header. We're not interested in that, so we have to skip over it.
- * This code does a rough check of the IPv4 header and, if it looks OK,
- * returns the offset of the ICMP header.
- * \param packet The IPv4 packet, as returned to us by the kernel.
- * \returns The offset of the ICMP header, or NSNotFound.
- */
- + (NSUInteger)icmpHeaderOffsetInIPv4Packet:(NSData *)packet {
- // Returns the offset of the ICMPv4Header within an IP packet.
- NSUInteger result;
- const struct IPv4Header * ipPtr;
- size_t ipHeaderLength;
-
- result = NSNotFound;
- if (packet.length >= (sizeof(IPv4Header) + sizeof(ICMPHeader))) {
- ipPtr = (const IPv4Header *) packet.bytes;
- if ( ((ipPtr->versionAndHeaderLength & 0xF0) == 0x40) && // IPv4
- ( ipPtr->protocol == IPPROTO_ICMP ) ) {
- ipHeaderLength = (ipPtr->versionAndHeaderLength & 0x0F) * sizeof(uint32_t);
- if (packet.length >= (ipHeaderLength + sizeof(ICMPHeader))) {
- result = ipHeaderLength;
- }
- }
- }
- return result;
- }
- /*! Checks whether the specified sequence number is one we sent.
- * \param sequenceNumber The incoming sequence number.
- * \returns YES if the sequence number looks like one we sent.
- */
- - (BOOL)validateSequenceNumber:(uint16_t)sequenceNumber {
- if (self.nextSequenceNumberHasWrapped) {
- // If the sequence numbers have wrapped that we can't reliably check
- // whether this is a sequence number we sent. Rather, we check to see
- // whether the sequence number is within the last 120 sequence numbers
- // we sent. Note that the uint16_t subtraction here does the right
- // thing regardless of the wrapping.
- //
- // Why 120? Well, if we send one ping per second, 120 is 2 minutes, which
- // is the standard "max time a packet can bounce around the Internet" value.
- return ((uint16_t) (self.nextSequenceNumber - sequenceNumber)) < (uint16_t) 120;
- } else {
- return sequenceNumber < self.nextSequenceNumber;
- }
- }
- /*! Checks whether an incoming IPv4 packet looks like a ping response.
- * \details This routine modifies this `packet` data! It does this for two reasons:
- *
- * * It needs to zero out the `checksum` field of the ICMPHeader in order to do
- * its checksum calculation.
- *
- * * It removes the IPv4 header from the front of the packet.
- * \param packet The IPv4 packet, as returned to us by the kernel.
- * \param sequenceNumberPtr A pointer to a place to start the ICMP sequence number.
- * \returns YES if the packet looks like a reasonable IPv4 ping response.
- */
- - (BOOL)validatePing4ResponsePacket:(NSMutableData *)packet sequenceNumber:(uint16_t *)sequenceNumberPtr {
- BOOL result;
- NSUInteger icmpHeaderOffset;
- ICMPHeader * icmpPtr;
- uint16_t receivedChecksum;
- uint16_t calculatedChecksum;
-
- result = NO;
-
- icmpHeaderOffset = [[self class] icmpHeaderOffsetInIPv4Packet:packet];
- if (icmpHeaderOffset != NSNotFound) {
- icmpPtr = (struct ICMPHeader *) (((uint8_t *) packet.mutableBytes) + icmpHeaderOffset);
-
- receivedChecksum = icmpPtr->checksum;
- icmpPtr->checksum = 0;
- calculatedChecksum = in_cksum(icmpPtr, packet.length - icmpHeaderOffset);
- icmpPtr->checksum = receivedChecksum;
-
- if (receivedChecksum == calculatedChecksum) {
- if ( (icmpPtr->type == ICMPv4TypeEchoReply) && (icmpPtr->code == 0) ) {
- if ( OSSwapBigToHostInt16(icmpPtr->identifier) == self.identifier ) {
- uint16_t sequenceNumber;
-
- sequenceNumber = OSSwapBigToHostInt16(icmpPtr->sequenceNumber);
- if ([self validateSequenceNumber:sequenceNumber]) {
-
- // Remove the IPv4 header off the front of the data we received, leaving us with
- // just the ICMP header and the ping payload.
- [packet replaceBytesInRange:NSMakeRange(0, icmpHeaderOffset) withBytes:NULL length:0];
-
- *sequenceNumberPtr = sequenceNumber;
- result = YES;
- }
- }
- }
- }
- }
-
- return result;
- }
- /*! Checks whether an incoming IPv6 packet looks like a ping response.
- * \param packet The IPv6 packet, as returned to us by the kernel; note that this routine
- * could modify this data but does not need to in the IPv6 case.
- * \param sequenceNumberPtr A pointer to a place to start the ICMP sequence number.
- * \returns YES if the packet looks like a reasonable IPv4 ping response.
- */
- - (BOOL)validatePing6ResponsePacket:(NSMutableData *)packet sequenceNumber:(uint16_t *)sequenceNumberPtr {
- BOOL result;
- const ICMPHeader * icmpPtr;
-
- result = NO;
-
- if (packet.length >= sizeof(*icmpPtr)) {
- icmpPtr = packet.bytes;
-
- // In the IPv6 case we don't check the checksum because that's hard (we need to
- // cook up an IPv6 pseudo header and we don't have the ingredients) and unnecessary
- // (the kernel has already done this check).
-
- if ( (icmpPtr->type == ICMPv6TypeEchoReply) && (icmpPtr->code == 0) ) {
- if ( OSSwapBigToHostInt16(icmpPtr->identifier) == self.identifier ) {
- uint16_t sequenceNumber;
-
- sequenceNumber = OSSwapBigToHostInt16(icmpPtr->sequenceNumber);
- if ([self validateSequenceNumber:sequenceNumber]) {
- *sequenceNumberPtr = sequenceNumber;
- result = YES;
- }
- }
- }
- }
- return result;
- }
- /*! Checks whether an incoming packet looks like a ping response.
- * \param packet The packet, as returned to us by the kernel; note that may end up modifying
- * this data.
- * \param sequenceNumberPtr A pointer to a place to start the ICMP sequence number.
- * \returns YES if the packet looks like a reasonable IPv4 ping response.
- */
- - (BOOL)validatePingResponsePacket:(NSMutableData *)packet sequenceNumber:(uint16_t *)sequenceNumberPtr {
- BOOL result;
-
- switch (self.hostAddressFamily) {
- case AF_INET: {
- result = [self validatePing4ResponsePacket:packet sequenceNumber:sequenceNumberPtr];
- } break;
- case AF_INET6: {
- result = [self validatePing6ResponsePacket:packet sequenceNumber:sequenceNumberPtr];
- } break;
- default: {
- assert(NO);
- result = NO;
- } break;
- }
- return result;
- }
- /*! Reads data from the ICMP socket.
- * \details Called by the socket handling code (SocketReadCallback) to process an ICMP
- * message waiting on the socket.
- */
- - (void)readData {
- int err;
- struct sockaddr_storage addr;
- socklen_t addrLen;
- ssize_t bytesRead;
- void * buffer;
- enum { kBufferSize = 65535 };
-
- // 65535 is the maximum IP packet size, which seems like a reasonable bound
- // here (plus it's what <x-man-page://8/ping> uses).
-
- buffer = malloc(kBufferSize);
- assert(buffer != NULL);
-
- // Actually read the data. We use recvfrom(), and thus get back the source address,
- // but we don't actually do anything with it. It would be trivial to pass it to
- // the delegate but we don't need it in this example.
-
- addrLen = sizeof(addr);
- bytesRead = recvfrom(CFSocketGetNative(self.socket), buffer, kBufferSize, 0, (struct sockaddr *) &addr, &addrLen);
- err = 0;
- if (bytesRead < 0) {
- err = errno;
- }
-
- // Process the data we read.
-
- if (bytesRead > 0) {
- NSMutableData * packet;
- id<SimplePingDelegate> strongDelegate;
- uint16_t sequenceNumber;
-
- packet = [NSMutableData dataWithBytes:buffer length:(NSUInteger) bytesRead];
- assert(packet != nil);
-
- // We got some data, pass it up to our client.
-
- strongDelegate = self.delegate;
- if ( [self validatePingResponsePacket:packet sequenceNumber:&sequenceNumber] ) {
- if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didReceivePingResponsePacket:sequenceNumber:)] ) {
- [strongDelegate simplePing:self didReceivePingResponsePacket:packet sequenceNumber:sequenceNumber];
- }
- } else {
- if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didReceiveUnexpectedPacket:)] ) {
- [strongDelegate simplePing:self didReceiveUnexpectedPacket:packet];
- }
- }
- } else {
-
- // We failed to read the data, so shut everything down.
-
- if (err == 0) {
- err = EPIPE;
- }
- [self didFailWithError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
- }
-
- free(buffer);
-
- // Note that we don't loop back trying to read more data. Rather, we just
- // let CFSocket call us again.
- }
- /*! The callback for our CFSocket object.
- * \details This simply routes the call to our `-readData` method.
- * \param s See the documentation for CFSocketCallBack.
- * \param type See the documentation for CFSocketCallBack.
- * \param address See the documentation for CFSocketCallBack.
- * \param data See the documentation for CFSocketCallBack.
- * \param info See the documentation for CFSocketCallBack; this is actually a pointer to the
- * 'owning' object.
- */
- static void SocketReadCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
- // This C routine is called by CFSocket when there's data waiting on our
- // ICMP socket. It just redirects the call to Objective-C code.
- SimplePing * obj;
-
- obj = (__bridge SimplePing *) info;
- assert([obj isKindOfClass:[SimplePing class]]);
-
- #pragma unused(s)
- assert(s == obj.socket);
- #pragma unused(type)
- assert(type == kCFSocketReadCallBack);
- #pragma unused(address)
- assert(address == nil);
- #pragma unused(data)
- assert(data == nil);
-
- [obj readData];
- }
- /*! Starts the send and receive infrastructure.
- * \details This is called once we've successfully resolved `hostName` in to
- * `hostAddress`. It's responsible for setting up the socket for sending and
- * receiving pings.
- */
- - (void)startWithHostAddress {
- int err;
- int fd;
-
- assert(self.hostAddress != nil);
-
- // Open the socket.
-
- fd = -1;
- err = 0;
- switch (self.hostAddressFamily) {
- case AF_INET: {
- fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
- if (fd < 0) {
- err = errno;
- }
- } break;
- case AF_INET6: {
- fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
- if (fd < 0) {
- err = errno;
- }
- } break;
- default: {
- err = EPROTONOSUPPORT;
- } break;
- }
-
- if (err != 0) {
- [self didFailWithError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
- } else {
- CFSocketContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
- CFRunLoopSourceRef rls;
- id<SimplePingDelegate> strongDelegate;
-
- // Wrap it in a CFSocket and schedule it on the runloop.
-
- self.socket = (CFSocketRef) CFAutorelease( CFSocketCreateWithNative(NULL, fd, kCFSocketReadCallBack, SocketReadCallback, &context) );
- assert(self.socket != NULL);
-
- // The socket will now take care of cleaning up our file descriptor.
-
- assert( CFSocketGetSocketFlags(self.socket) & kCFSocketCloseOnInvalidate );
- fd = -1;
-
- rls = CFSocketCreateRunLoopSource(NULL, self.socket, 0);
- assert(rls != NULL);
-
- CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
-
- CFRelease(rls);
-
- strongDelegate = self.delegate;
- if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didStartWithAddress:)] ) {
- [strongDelegate simplePing:self didStartWithAddress:self.hostAddress];
- }
- }
- assert(fd == -1);
- }
- /*! Processes the results of our name-to-address resolution.
- * \details Called by our CFHost resolution callback (HostResolveCallback) when host
- * resolution is complete. We just latch the first appropriate address and kick
- * off the send and receive infrastructure.
- */
- - (void)hostResolutionDone {
- Boolean resolved;
- NSArray * addresses;
-
- // Find the first appropriate address.
-
- addresses = (__bridge NSArray *) CFHostGetAddressing(self.host, &resolved);
- if ( resolved && (addresses != nil) ) {
- resolved = false;
- for (NSData * address in addresses) {
- const struct sockaddr * addrPtr;
-
- addrPtr = (const struct sockaddr *) address.bytes;
- if ( address.length >= sizeof(struct sockaddr) ) {
- switch (addrPtr->sa_family) {
- case AF_INET: {
- if (self.addressStyle != SimplePingAddressStyleICMPv6) {
- self.hostAddress = address;
- resolved = true;
- }
- } break;
- case AF_INET6: {
- if (self.addressStyle != SimplePingAddressStyleICMPv4) {
- self.hostAddress = address;
- resolved = true;
- }
- } break;
- }
- }
- if (resolved) {
- break;
- }
- }
- }
-
- // We're done resolving, so shut that down.
-
- [self stopHostResolution];
-
- // If all is OK, start the send and receive infrastructure, otherwise stop.
-
- if (resolved) {
- [self startWithHostAddress];
- } else {
- [self didFailWithError:[NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorHostNotFound userInfo:nil]];
- }
- }
- /*! The callback for our CFHost object.
- * \details This simply routes the call to our `-hostResolutionDone` or
- * `-didFailWithHostStreamError:` methods.
- * \param theHost See the documentation for CFHostClientCallBack.
- * \param typeInfo See the documentation for CFHostClientCallBack.
- * \param error See the documentation for CFHostClientCallBack.
- * \param info See the documentation for CFHostClientCallBack; this is actually a pointer to
- * the 'owning' object.
- */
- static void HostResolveCallback(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void *info) {
- // This C routine is called by CFHost when the host resolution is complete.
- // It just redirects the call to the appropriate Objective-C method.
- SimplePing * obj;
-
- obj = (__bridge SimplePing *) info;
- assert([obj isKindOfClass:[SimplePing class]]);
-
- #pragma unused(theHost)
- assert(theHost == obj.host);
- #pragma unused(typeInfo)
- assert(typeInfo == kCFHostAddresses);
-
- if ( (error != NULL) && (error->domain != 0) ) {
- [obj didFailWithHostStreamError:*error];
- } else {
- [obj hostResolutionDone];
- }
- }
- - (void)start {
- Boolean success;
- CFHostClientContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
- CFStreamError streamError;
-
- assert(self.host == NULL);
- assert(self.hostAddress == nil);
-
- self.host = (CFHostRef) CFAutorelease( CFHostCreateWithName(NULL, (__bridge CFStringRef) self.hostName) );
- assert(self.host != NULL);
-
- CFHostSetClient(self.host, HostResolveCallback, &context);
-
- CFHostScheduleWithRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
-
- success = CFHostStartInfoResolution(self.host, kCFHostAddresses, &streamError);
- if ( ! success ) {
- [self didFailWithHostStreamError:streamError];
- }
- }
- /*! Stops the name-to-address resolution infrastructure.
- */
- - (void)stopHostResolution {
- // Shut down the CFHost.
- if (self.host != NULL) {
- CFHostSetClient(self.host, NULL, NULL);
- CFHostUnscheduleFromRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
- self.host = NULL;
- }
- }
- /*! Stops the send and receive infrastructure.
- */
- - (void)stopSocket {
- if (self.socket != NULL) {
- CFSocketInvalidate(self.socket);
- self.socket = NULL;
- }
- }
- - (void)stop {
- [self stopHostResolution];
- [self stopSocket];
-
- // Junk the host address on stop. If the client calls -start again, we'll
- // re-resolve the host name.
-
- self.hostAddress = NULL;
- }
- @end
|