Common/SimplePing.m
/* |
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 |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-05-05