├── .gitignore ├── Common ├── SimplePing.h └── SimplePing.m ├── LICENSE.txt ├── MacTool └── main.m ├── README.md ├── SimplePing.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist └── iOSApp ├── AppDelegate.swift ├── Info.plist ├── LaunchScreen.storyboard ├── Main.storyboard ├── MainViewController.swift ├── SimplePingManager.swift └── iOSApp-Bridging-Header.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | 49 | Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /Common/SimplePing.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2016 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | An object wrapper around the low-level BSD Sockets ping function. 7 | */ 8 | 9 | @import Foundation; 10 | 11 | #include // for __Check_Compile_Time 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @protocol SimplePingDelegate; 16 | 17 | /*! Controls the IP address version used by SimplePing instances. 18 | */ 19 | 20 | typedef NS_ENUM(NSInteger, SimplePingAddressStyle) { 21 | SimplePingAddressStyleAny, ///< Use the first IPv4 or IPv6 address found; the default. 22 | SimplePingAddressStyleICMPv4, ///< Use the first IPv4 address found. 23 | SimplePingAddressStyleICMPv6 ///< Use the first IPv6 address found. 24 | }; 25 | 26 | /*! An object wrapper around the low-level BSD Sockets ping function. 27 | * \details To use the class create an instance, set the delegate and call `-start` 28 | * to start the instance on the current run loop. If things go well you'll soon get the 29 | * `-simplePing:didStartWithAddress:` delegate callback. From there you can can call 30 | * `-sendPingWithData:` to send a ping and you'll receive the 31 | * `-simplePing:didReceivePingResponsePacket:sequenceNumber:` and 32 | * `-simplePing:didReceiveUnexpectedPacket:` delegate callbacks as ICMP packets arrive. 33 | * 34 | * The class can be used from any thread but the use of any single instance must be 35 | * confined to a specific thread and that thread must run its run loop. 36 | */ 37 | 38 | @interface SimplePing : NSObject 39 | 40 | - (instancetype)init NS_UNAVAILABLE; 41 | 42 | /*! Initialise the object to ping the specified host. 43 | * \param hostName The DNS name of the host to ping; an IPv4 or IPv6 address in string form will 44 | * work here. 45 | * \returns The initialised object. 46 | */ 47 | 48 | - (instancetype)initWithHostName:(NSString *)hostName NS_DESIGNATED_INITIALIZER; 49 | 50 | /*! A copy of the value passed to `-initWithHostName:`. 51 | */ 52 | 53 | @property (nonatomic, copy, readonly) NSString * hostName; 54 | 55 | /*! The delegate for this object. 56 | * \details Delegate callbacks are schedule in the default run loop mode of the run loop of the 57 | * thread that calls `-start`. 58 | */ 59 | 60 | @property (nonatomic, weak, readwrite, nullable) id delegate; 61 | 62 | /*! Controls the IP address version used by the object. 63 | * \details You should set this value before starting the object. 64 | */ 65 | 66 | @property (nonatomic, assign, readwrite) SimplePingAddressStyle addressStyle; 67 | 68 | /*! The address being pinged. 69 | * \details The contents of the NSData is a (struct sockaddr) of some form. The 70 | * value is nil while the object is stopped and remains nil on start until 71 | * `-simplePing:didStartWithAddress:` is called. 72 | */ 73 | 74 | @property (nonatomic, copy, readonly, nullable) NSData * hostAddress; 75 | 76 | /*! The address family for `hostAddress`, or `AF_UNSPEC` if that's nil. 77 | */ 78 | 79 | @property (nonatomic, assign, readonly) sa_family_t hostAddressFamily; 80 | 81 | /*! The identifier used by pings by this object. 82 | * \details When you create an instance of this object it generates a random identifier 83 | * that it uses to identify its own pings. 84 | */ 85 | 86 | @property (nonatomic, assign, readonly) uint16_t identifier; 87 | 88 | /*! The next sequence number to be used by this object. 89 | * \details This value starts at zero and increments each time you send a ping (safely 90 | * wrapping back to zero if necessary). The sequence number is included in the ping, 91 | * allowing you to match up requests and responses, and thus calculate ping times and 92 | * so on. 93 | */ 94 | 95 | @property (nonatomic, assign, readonly) uint16_t nextSequenceNumber; 96 | 97 | /*! Starts the object. 98 | * \details You should set up the delegate and any ping parameters before calling this. 99 | * 100 | * If things go well you'll soon get the `-simplePing:didStartWithAddress:` delegate 101 | * callback, at which point you can start sending pings (via `-sendPingWithData:`) and 102 | * will start receiving ICMP packets (either ping responses, via the 103 | * `-simplePing:didReceivePingResponsePacket:sequenceNumber:` delegate callback, or 104 | * unsolicited ICMP packets, via the `-simplePing:didReceiveUnexpectedPacket:` delegate 105 | * callback). 106 | * 107 | * If the object fails to start, typically because `hostName` doesn't resolve, you'll get 108 | * the `-simplePing:didFailWithError:` delegate callback. 109 | * 110 | * It is not correct to start an already started object. 111 | */ 112 | 113 | - (void)start; 114 | 115 | /*! Sends a ping packet containing the specified data. 116 | * \details Sends an actual ping. 117 | * 118 | * The object must be started when you call this method and, on starting the object, you must 119 | * wait for the `-simplePing:didStartWithAddress:` delegate callback before calling it. 120 | * \param data Some data to include in the ping packet, after the ICMP header, or nil if you 121 | * want the packet to include a standard 56 byte payload (resulting in a standard 64 byte 122 | * ping). 123 | */ 124 | 125 | - (void)sendPingWithData:(nullable NSData *)data; 126 | 127 | /*! Stops the object. 128 | * \details You should call this when you're done pinging. 129 | * 130 | * It's safe to call this on an object that's stopped. 131 | */ 132 | 133 | - (void)stop; 134 | 135 | @end 136 | 137 | /*! A delegate protocol for the SimplePing class. 138 | */ 139 | 140 | @protocol SimplePingDelegate 141 | 142 | @optional 143 | 144 | /*! A SimplePing delegate callback, called once the object has started up. 145 | * \details This is called shortly after you start the object to tell you that the 146 | * object has successfully started. On receiving this callback, you can call 147 | * `-sendPingWithData:` to send pings. 148 | * 149 | * If the object didn't start, `-simplePing:didFailWithError:` is called instead. 150 | * \param pinger The object issuing the callback. 151 | * \param address The address that's being pinged; at the time this delegate callback 152 | * is made, this will have the same value as the `hostAddress` property. 153 | */ 154 | 155 | - (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address; 156 | 157 | /*! A SimplePing delegate callback, called if the object fails to start up. 158 | * \details This is called shortly after you start the object to tell you that the 159 | * object has failed to start. The most likely cause of failure is a problem 160 | * resolving `hostName`. 161 | * 162 | * By the time this callback is called, the object has stopped (that is, you don't 163 | * need to call `-stop` yourself). 164 | * \param pinger The object issuing the callback. 165 | * \param error Describes the failure. 166 | */ 167 | 168 | - (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error; 169 | 170 | /*! A SimplePing delegate callback, called when the object has successfully sent a ping packet. 171 | * \details Each call to `-sendPingWithData:` will result in either a 172 | * `-simplePing:didSendPacket:sequenceNumber:` delegate callback or a 173 | * `-simplePing:didFailToSendPacket:sequenceNumber:error:` delegate callback (unless you 174 | * stop the object before you get the callback). These callbacks are currently delivered 175 | * synchronously from within `-sendPingWithData:`, but this synchronous behaviour is not 176 | * considered API. 177 | * \param pinger The object issuing the callback. 178 | * \param packet The packet that was sent; this includes the ICMP header (`ICMPHeader`) and the 179 | * data you passed to `-sendPingWithData:` but does not include any IP-level headers. 180 | * \param sequenceNumber The ICMP sequence number of that packet. 181 | */ 182 | 183 | - (void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber; 184 | 185 | /*! A SimplePing delegate callback, called when the object fails to send a ping packet. 186 | * \details Each call to `-sendPingWithData:` will result in either a 187 | * `-simplePing:didSendPacket:sequenceNumber:` delegate callback or a 188 | * `-simplePing:didFailToSendPacket:sequenceNumber:error:` delegate callback (unless you 189 | * stop the object before you get the callback). These callbacks are currently delivered 190 | * synchronously from within `-sendPingWithData:`, but this synchronous behaviour is not 191 | * considered API. 192 | * \param pinger The object issuing the callback. 193 | * \param packet The packet that was not sent; see `-simplePing:didSendPacket:sequenceNumber:` 194 | * for details. 195 | * \param sequenceNumber The ICMP sequence number of that packet. 196 | * \param error Describes the failure. 197 | */ 198 | 199 | - (void)simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber error:(NSError *)error; 200 | 201 | /*! A SimplePing delegate callback, called when the object receives a ping response. 202 | * \details If the object receives an ping response that matches a ping request that it 203 | * sent, it informs the delegate via this callback. Matching is primarily done based on 204 | * the ICMP identifier, although other criteria are used as well. 205 | * \param pinger The object issuing the callback. 206 | * \param packet The packet received; this includes the ICMP header (`ICMPHeader`) and any data that 207 | * follows that in the ICMP message but does not include any IP-level headers. 208 | * \param sequenceNumber The ICMP sequence number of that packet. 209 | */ 210 | 211 | - (void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber; 212 | 213 | /*! A SimplePing delegate callback, called when the object receives an unmatched ICMP message. 214 | * \details If the object receives an ICMP message that does not match a ping request that it 215 | * sent, it informs the delegate via this callback. The nature of ICMP handling in a 216 | * BSD kernel makes this a common event because, when an ICMP message arrives, it is 217 | * delivered to all ICMP sockets. 218 | * 219 | * IMPORTANT: This callback is especially common when using IPv6 because IPv6 uses ICMP 220 | * for important network management functions. For example, IPv6 routers periodically 221 | * send out Router Advertisement (RA) packets via Neighbor Discovery Protocol (NDP), which 222 | * is implemented on top of ICMP. 223 | * 224 | * For more on matching, see the discussion associated with 225 | * `-simplePing:didReceivePingResponsePacket:sequenceNumber:`. 226 | * \param pinger The object issuing the callback. 227 | * \param packet The packet received; this includes the ICMP header (`ICMPHeader`) and any data that 228 | * follows that in the ICMP message but does not include any IP-level headers. 229 | */ 230 | 231 | - (void)simplePing:(SimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet; 232 | 233 | @end 234 | 235 | #pragma mark * ICMP On-The-Wire Format 236 | 237 | /*! Describes the on-the-wire header format for an ICMP ping. 238 | * \details This defines the header structure of ping packets on the wire. Both IPv4 and 239 | * IPv6 use the same basic structure. 240 | * 241 | * This is declared in the header because clients of SimplePing might want to use 242 | * it parse received ping packets. 243 | */ 244 | 245 | struct ICMPHeader { 246 | uint8_t type; 247 | uint8_t code; 248 | uint16_t checksum; 249 | uint16_t identifier; 250 | uint16_t sequenceNumber; 251 | // data... 252 | }; 253 | typedef struct ICMPHeader ICMPHeader; 254 | 255 | __Check_Compile_Time(sizeof(ICMPHeader) == 8); 256 | __Check_Compile_Time(offsetof(ICMPHeader, type) == 0); 257 | __Check_Compile_Time(offsetof(ICMPHeader, code) == 1); 258 | __Check_Compile_Time(offsetof(ICMPHeader, checksum) == 2); 259 | __Check_Compile_Time(offsetof(ICMPHeader, identifier) == 4); 260 | __Check_Compile_Time(offsetof(ICMPHeader, sequenceNumber) == 6); 261 | 262 | enum { 263 | ICMPv4TypeEchoRequest = 8, ///< The ICMP `type` for a ping request; in this case `code` is always 0. 264 | ICMPv4TypeEchoReply = 0 ///< The ICMP `type` for a ping response; in this case `code` is always 0. 265 | }; 266 | 267 | enum { 268 | ICMPv6TypeEchoRequest = 128, ///< The ICMP `type` for a ping request; in this case `code` is always 0. 269 | ICMPv6TypeEchoReply = 129 ///< The ICMP `type` for a ping response; in this case `code` is always 0. 270 | }; 271 | 272 | NS_ASSUME_NONNULL_END 273 | -------------------------------------------------------------------------------- /Common/SimplePing.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2016 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | An object wrapper around the low-level BSD Sockets ping function. 7 | */ 8 | 9 | #import "SimplePing.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #pragma mark * IPv4 and ICMPv4 On-The-Wire Format 16 | 17 | /*! Describes the on-the-wire header format for an IPv4 packet. 18 | * \details This defines the header structure of IPv4 packets on the wire. We need 19 | * this in order to skip this header in the IPv4 case, where the kernel passes 20 | * it to us for no obvious reason. 21 | */ 22 | 23 | struct IPv4Header { 24 | uint8_t versionAndHeaderLength; 25 | uint8_t differentiatedServices; 26 | uint16_t totalLength; 27 | uint16_t identification; 28 | uint16_t flagsAndFragmentOffset; 29 | uint8_t timeToLive; 30 | uint8_t protocol; 31 | uint16_t headerChecksum; 32 | uint8_t sourceAddress[4]; 33 | uint8_t destinationAddress[4]; 34 | // options... 35 | // data... 36 | }; 37 | typedef struct IPv4Header IPv4Header; 38 | 39 | __Check_Compile_Time(sizeof(IPv4Header) == 20); 40 | __Check_Compile_Time(offsetof(IPv4Header, versionAndHeaderLength) == 0); 41 | __Check_Compile_Time(offsetof(IPv4Header, differentiatedServices) == 1); 42 | __Check_Compile_Time(offsetof(IPv4Header, totalLength) == 2); 43 | __Check_Compile_Time(offsetof(IPv4Header, identification) == 4); 44 | __Check_Compile_Time(offsetof(IPv4Header, flagsAndFragmentOffset) == 6); 45 | __Check_Compile_Time(offsetof(IPv4Header, timeToLive) == 8); 46 | __Check_Compile_Time(offsetof(IPv4Header, protocol) == 9); 47 | __Check_Compile_Time(offsetof(IPv4Header, headerChecksum) == 10); 48 | __Check_Compile_Time(offsetof(IPv4Header, sourceAddress) == 12); 49 | __Check_Compile_Time(offsetof(IPv4Header, destinationAddress) == 16); 50 | 51 | /*! Calculates an IP checksum. 52 | * \details This is the standard BSD checksum code, modified to use modern types. 53 | * \param buffer A pointer to the data to checksum. 54 | * \param bufferLen The length of that data. 55 | * \returns The checksum value, in network byte order. 56 | */ 57 | 58 | static uint16_t in_cksum(const void *buffer, size_t bufferLen) { 59 | // 60 | size_t bytesLeft; 61 | int32_t sum; 62 | const uint16_t * cursor; 63 | union { 64 | uint16_t us; 65 | uint8_t uc[2]; 66 | } last; 67 | uint16_t answer; 68 | 69 | bytesLeft = bufferLen; 70 | sum = 0; 71 | cursor = buffer; 72 | 73 | /* 74 | * Our algorithm is simple, using a 32 bit accumulator (sum), we add 75 | * sequential 16 bit words to it, and at the end, fold back all the 76 | * carry bits from the top 16 bits into the lower 16 bits. 77 | */ 78 | while (bytesLeft > 1) { 79 | sum += *cursor; 80 | cursor += 1; 81 | bytesLeft -= 2; 82 | } 83 | 84 | /* mop up an odd byte, if necessary */ 85 | if (bytesLeft == 1) { 86 | last.uc[0] = * (const uint8_t *) cursor; 87 | last.uc[1] = 0; 88 | sum += last.us; 89 | } 90 | 91 | /* add back carry outs from top 16 bits to low 16 bits */ 92 | sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ 93 | sum += (sum >> 16); /* add carry */ 94 | answer = (uint16_t) ~sum; /* truncate to 16 bits */ 95 | 96 | return answer; 97 | } 98 | 99 | #pragma mark * SimplePing 100 | 101 | @interface SimplePing () 102 | 103 | // read/write versions of public properties 104 | 105 | @property (nonatomic, copy, readwrite, nullable) NSData * hostAddress; 106 | @property (nonatomic, assign, readwrite ) uint16_t nextSequenceNumber; 107 | 108 | // private properties 109 | 110 | /*! True if nextSequenceNumber has wrapped from 65535 to 0. 111 | */ 112 | 113 | @property (nonatomic, assign, readwrite) BOOL nextSequenceNumberHasWrapped; 114 | 115 | /*! A host object for name-to-address resolution. 116 | */ 117 | 118 | @property (nonatomic, strong, readwrite, nullable) CFHostRef host __attribute__ ((NSObject)); 119 | 120 | /*! A socket object for ICMP send and receive. 121 | */ 122 | 123 | @property (nonatomic, strong, readwrite, nullable) CFSocketRef socket __attribute__ ((NSObject)); 124 | 125 | @end 126 | 127 | @implementation SimplePing 128 | 129 | - (instancetype)initWithHostName:(NSString *)hostName { 130 | NSParameterAssert(hostName != nil); 131 | self = [super init]; 132 | if (self != nil) { 133 | self->_hostName = [hostName copy]; 134 | self->_identifier = (uint16_t) arc4random(); 135 | } 136 | return self; 137 | } 138 | 139 | - (void)dealloc { 140 | [self stop]; 141 | // Double check that -stop took care of _host and _socket. 142 | assert(self->_host == NULL); 143 | assert(self->_socket == NULL); 144 | } 145 | 146 | - (sa_family_t)hostAddressFamily { 147 | sa_family_t result; 148 | 149 | result = AF_UNSPEC; 150 | if ( (self.hostAddress != nil) && (self.hostAddress.length >= sizeof(struct sockaddr)) ) { 151 | result = ((const struct sockaddr *) self.hostAddress.bytes)->sa_family; 152 | } 153 | return result; 154 | } 155 | 156 | /*! Shuts down the pinger object and tell the delegate about the error. 157 | * \param error Describes the failure. 158 | */ 159 | 160 | - (void)didFailWithError:(NSError *)error { 161 | id strongDelegate; 162 | 163 | assert(error != nil); 164 | 165 | // We retain ourselves temporarily because it's common for the delegate method 166 | // to release its last reference to us, which causes -dealloc to be called here. 167 | // If we then reference self on the return path, things go badly. I don't think 168 | // that happens currently, but I've got into the habit of doing this as a 169 | // defensive measure. 170 | 171 | CFAutorelease( CFBridgingRetain( self )); 172 | 173 | [self stop]; 174 | strongDelegate = self.delegate; 175 | if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didFailWithError:)] ) { 176 | [strongDelegate simplePing:self didFailWithError:error]; 177 | } 178 | } 179 | 180 | /*! Shuts down the pinger object and tell the delegate about the error. 181 | * \details This converts the CFStreamError to an NSError and then call through to 182 | * -didFailWithError: to do the real work. 183 | * \param streamError Describes the failure. 184 | */ 185 | 186 | - (void)didFailWithHostStreamError:(CFStreamError)streamError { 187 | NSDictionary * userInfo; 188 | NSError * error; 189 | 190 | if (streamError.domain == kCFStreamErrorDomainNetDB) { 191 | userInfo = @{(id) kCFGetAddrInfoFailureKey: @(streamError.error)}; 192 | } else { 193 | userInfo = nil; 194 | } 195 | error = [NSError errorWithDomain:(NSString *) kCFErrorDomainCFNetwork code:kCFHostErrorUnknown userInfo:userInfo]; 196 | 197 | [self didFailWithError:error]; 198 | } 199 | 200 | /*! Builds a ping packet from the supplied parameters. 201 | * \param type The packet type, which is different for IPv4 and IPv6. 202 | * \param payload Data to place after the ICMP header. 203 | * \param requiresChecksum Determines whether a checksum is calculated (IPv4) or not (IPv6). 204 | * \returns A ping packet suitable to be passed to the kernel. 205 | */ 206 | 207 | - (NSData *)pingPacketWithType:(uint8_t)type payload:(NSData *)payload requiresChecksum:(BOOL)requiresChecksum { 208 | NSMutableData * packet; 209 | ICMPHeader * icmpPtr; 210 | 211 | packet = [NSMutableData dataWithLength:sizeof(*icmpPtr) + payload.length]; 212 | assert(packet != nil); 213 | 214 | icmpPtr = packet.mutableBytes; 215 | icmpPtr->type = type; 216 | icmpPtr->code = 0; 217 | icmpPtr->checksum = 0; 218 | icmpPtr->identifier = OSSwapHostToBigInt16(self.identifier); 219 | icmpPtr->sequenceNumber = OSSwapHostToBigInt16(self.nextSequenceNumber); 220 | memcpy(&icmpPtr[1], [payload bytes], [payload length]); 221 | 222 | if (requiresChecksum) { 223 | // The IP checksum routine returns a 16-bit number that's already in correct byte order 224 | // (due to wacky 1's complement maths), so we just put it into the packet as a 16-bit unit. 225 | 226 | icmpPtr->checksum = in_cksum(packet.bytes, packet.length); 227 | } 228 | 229 | return packet; 230 | } 231 | 232 | - (void)sendPingWithData:(NSData *)data { 233 | int err; 234 | NSData * payload; 235 | NSData * packet; 236 | ssize_t bytesSent; 237 | id strongDelegate; 238 | 239 | // data may be nil 240 | NSParameterAssert(self.hostAddress != nil); // gotta wait for -simplePing:didStartWithAddress: 241 | 242 | // Construct the ping packet. 243 | 244 | payload = data; 245 | if (payload == nil) { 246 | payload = [[NSString stringWithFormat:@"%28zd bottles of beer on the wall", (ssize_t) 99 - (size_t) (self.nextSequenceNumber % 100) ] dataUsingEncoding:NSASCIIStringEncoding]; 247 | assert(payload != nil); 248 | 249 | // Our dummy payload is sized so that the resulting ICMP packet, including the ICMPHeader, is 250 | // 64-bytes, which makes it easier to recognise our packets on the wire. 251 | 252 | assert([payload length] == 56); 253 | } 254 | 255 | switch (self.hostAddressFamily) { 256 | case AF_INET: { 257 | packet = [self pingPacketWithType:ICMPv4TypeEchoRequest payload:payload requiresChecksum:YES]; 258 | } break; 259 | case AF_INET6: { 260 | packet = [self pingPacketWithType:ICMPv6TypeEchoRequest payload:payload requiresChecksum:NO]; 261 | } break; 262 | default: { 263 | assert(NO); 264 | } break; 265 | } 266 | assert(packet != nil); 267 | 268 | // Send the packet. 269 | 270 | if (self.socket == NULL) { 271 | bytesSent = -1; 272 | err = EBADF; 273 | } else { 274 | bytesSent = sendto( 275 | CFSocketGetNative(self.socket), 276 | packet.bytes, 277 | packet.length, 278 | 0, 279 | self.hostAddress.bytes, 280 | (socklen_t) self.hostAddress.length 281 | ); 282 | err = 0; 283 | if (bytesSent < 0) { 284 | err = errno; 285 | } 286 | } 287 | 288 | // Handle the results of the send. 289 | 290 | strongDelegate = self.delegate; 291 | if ( (bytesSent > 0) && (((NSUInteger) bytesSent) == packet.length) ) { 292 | 293 | // Complete success. Tell the client. 294 | 295 | if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didSendPacket:sequenceNumber:)] ) { 296 | [strongDelegate simplePing:self didSendPacket:packet sequenceNumber:self.nextSequenceNumber]; 297 | } 298 | } else { 299 | NSError * error; 300 | 301 | // Some sort of failure. Tell the client. 302 | 303 | if (err == 0) { 304 | err = ENOBUFS; // This is not a hugely descriptor error, alas. 305 | } 306 | error = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]; 307 | if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didFailToSendPacket:sequenceNumber:error:)] ) { 308 | [strongDelegate simplePing:self didFailToSendPacket:packet sequenceNumber:self.nextSequenceNumber error:error]; 309 | } 310 | } 311 | 312 | self.nextSequenceNumber += 1; 313 | if (self.nextSequenceNumber == 0) { 314 | self.nextSequenceNumberHasWrapped = YES; 315 | } 316 | } 317 | 318 | /*! Calculates the offset of the ICMP header within an IPv4 packet. 319 | * \details In the IPv4 case the kernel returns us a buffer that includes the 320 | * IPv4 header. We're not interested in that, so we have to skip over it. 321 | * This code does a rough check of the IPv4 header and, if it looks OK, 322 | * returns the offset of the ICMP header. 323 | * \param packet The IPv4 packet, as returned to us by the kernel. 324 | * \returns The offset of the ICMP header, or NSNotFound. 325 | */ 326 | 327 | + (NSUInteger)icmpHeaderOffsetInIPv4Packet:(NSData *)packet { 328 | // Returns the offset of the ICMPv4Header within an IP packet. 329 | NSUInteger result; 330 | const struct IPv4Header * ipPtr; 331 | size_t ipHeaderLength; 332 | 333 | result = NSNotFound; 334 | if (packet.length >= (sizeof(IPv4Header) + sizeof(ICMPHeader))) { 335 | ipPtr = (const IPv4Header *) packet.bytes; 336 | if ( ((ipPtr->versionAndHeaderLength & 0xF0) == 0x40) && // IPv4 337 | ( ipPtr->protocol == IPPROTO_ICMP ) ) { 338 | ipHeaderLength = (ipPtr->versionAndHeaderLength & 0x0F) * sizeof(uint32_t); 339 | if (packet.length >= (ipHeaderLength + sizeof(ICMPHeader))) { 340 | result = ipHeaderLength; 341 | } 342 | } 343 | } 344 | return result; 345 | } 346 | 347 | /*! Checks whether the specified sequence number is one we sent. 348 | * \param sequenceNumber The incoming sequence number. 349 | * \returns YES if the sequence number looks like one we sent. 350 | */ 351 | 352 | - (BOOL)validateSequenceNumber:(uint16_t)sequenceNumber { 353 | if (self.nextSequenceNumberHasWrapped) { 354 | // If the sequence numbers have wrapped that we can't reliably check 355 | // whether this is a sequence number we sent. Rather, we check to see 356 | // whether the sequence number is within the last 120 sequence numbers 357 | // we sent. Note that the uint16_t subtraction here does the right 358 | // thing regardless of the wrapping. 359 | // 360 | // Why 120? Well, if we send one ping per second, 120 is 2 minutes, which 361 | // is the standard "max time a packet can bounce around the Internet" value. 362 | return ((uint16_t) (self.nextSequenceNumber - sequenceNumber)) < (uint16_t) 120; 363 | } else { 364 | return sequenceNumber < self.nextSequenceNumber; 365 | } 366 | } 367 | 368 | /*! Checks whether an incoming IPv4 packet looks like a ping response. 369 | * \details This routine modifies this `packet` data! It does this for two reasons: 370 | * 371 | * * It needs to zero out the `checksum` field of the ICMPHeader in order to do 372 | * its checksum calculation. 373 | * 374 | * * It removes the IPv4 header from the front of the packet. 375 | * \param packet The IPv4 packet, as returned to us by the kernel. 376 | * \param sequenceNumberPtr A pointer to a place to start the ICMP sequence number. 377 | * \returns YES if the packet looks like a reasonable IPv4 ping response. 378 | */ 379 | 380 | - (BOOL)validatePing4ResponsePacket:(NSMutableData *)packet sequenceNumber:(uint16_t *)sequenceNumberPtr { 381 | BOOL result; 382 | NSUInteger icmpHeaderOffset; 383 | ICMPHeader * icmpPtr; 384 | uint16_t receivedChecksum; 385 | uint16_t calculatedChecksum; 386 | 387 | result = NO; 388 | 389 | icmpHeaderOffset = [[self class] icmpHeaderOffsetInIPv4Packet:packet]; 390 | if (icmpHeaderOffset != NSNotFound) { 391 | icmpPtr = (struct ICMPHeader *) (((uint8_t *) packet.mutableBytes) + icmpHeaderOffset); 392 | 393 | receivedChecksum = icmpPtr->checksum; 394 | icmpPtr->checksum = 0; 395 | calculatedChecksum = in_cksum(icmpPtr, packet.length - icmpHeaderOffset); 396 | icmpPtr->checksum = receivedChecksum; 397 | 398 | if (receivedChecksum == calculatedChecksum) { 399 | if ( (icmpPtr->type == ICMPv4TypeEchoReply) && (icmpPtr->code == 0) ) { 400 | if ( OSSwapBigToHostInt16(icmpPtr->identifier) == self.identifier ) { 401 | uint16_t sequenceNumber; 402 | 403 | sequenceNumber = OSSwapBigToHostInt16(icmpPtr->sequenceNumber); 404 | if ([self validateSequenceNumber:sequenceNumber]) { 405 | 406 | // Remove the IPv4 header off the front of the data we received, leaving us with 407 | // just the ICMP header and the ping payload. 408 | [packet replaceBytesInRange:NSMakeRange(0, icmpHeaderOffset) withBytes:NULL length:0]; 409 | 410 | *sequenceNumberPtr = sequenceNumber; 411 | result = YES; 412 | } 413 | } 414 | } 415 | } 416 | } 417 | 418 | return result; 419 | } 420 | 421 | /*! Checks whether an incoming IPv6 packet looks like a ping response. 422 | * \param packet The IPv6 packet, as returned to us by the kernel; note that this routine 423 | * could modify this data but does not need to in the IPv6 case. 424 | * \param sequenceNumberPtr A pointer to a place to start the ICMP sequence number. 425 | * \returns YES if the packet looks like a reasonable IPv4 ping response. 426 | */ 427 | 428 | - (BOOL)validatePing6ResponsePacket:(NSMutableData *)packet sequenceNumber:(uint16_t *)sequenceNumberPtr { 429 | BOOL result; 430 | const ICMPHeader * icmpPtr; 431 | 432 | result = NO; 433 | 434 | if (packet.length >= sizeof(*icmpPtr)) { 435 | icmpPtr = packet.bytes; 436 | 437 | // In the IPv6 case we don't check the checksum because that's hard (we need to 438 | // cook up an IPv6 pseudo header and we don't have the ingredients) and unnecessary 439 | // (the kernel has already done this check). 440 | 441 | if ( (icmpPtr->type == ICMPv6TypeEchoReply) && (icmpPtr->code == 0) ) { 442 | if ( OSSwapBigToHostInt16(icmpPtr->identifier) == self.identifier ) { 443 | uint16_t sequenceNumber; 444 | 445 | sequenceNumber = OSSwapBigToHostInt16(icmpPtr->sequenceNumber); 446 | if ([self validateSequenceNumber:sequenceNumber]) { 447 | *sequenceNumberPtr = sequenceNumber; 448 | result = YES; 449 | } 450 | } 451 | } 452 | } 453 | return result; 454 | } 455 | 456 | /*! Checks whether an incoming packet looks like a ping response. 457 | * \param packet The packet, as returned to us by the kernel; note that may end up modifying 458 | * this data. 459 | * \param sequenceNumberPtr A pointer to a place to start the ICMP sequence number. 460 | * \returns YES if the packet looks like a reasonable IPv4 ping response. 461 | */ 462 | 463 | - (BOOL)validatePingResponsePacket:(NSMutableData *)packet sequenceNumber:(uint16_t *)sequenceNumberPtr { 464 | BOOL result; 465 | 466 | switch (self.hostAddressFamily) { 467 | case AF_INET: { 468 | result = [self validatePing4ResponsePacket:packet sequenceNumber:sequenceNumberPtr]; 469 | } break; 470 | case AF_INET6: { 471 | result = [self validatePing6ResponsePacket:packet sequenceNumber:sequenceNumberPtr]; 472 | } break; 473 | default: { 474 | assert(NO); 475 | result = NO; 476 | } break; 477 | } 478 | return result; 479 | } 480 | 481 | /*! Reads data from the ICMP socket. 482 | * \details Called by the socket handling code (SocketReadCallback) to process an ICMP 483 | * message waiting on the socket. 484 | */ 485 | 486 | - (void)readData { 487 | int err; 488 | struct sockaddr_storage addr; 489 | socklen_t addrLen; 490 | ssize_t bytesRead; 491 | void * buffer; 492 | enum { kBufferSize = 65535 }; 493 | 494 | // 65535 is the maximum IP packet size, which seems like a reasonable bound 495 | // here (plus it's what uses). 496 | 497 | buffer = malloc(kBufferSize); 498 | assert(buffer != NULL); 499 | 500 | // Actually read the data. We use recvfrom(), and thus get back the source address, 501 | // but we don't actually do anything with it. It would be trivial to pass it to 502 | // the delegate but we don't need it in this example. 503 | 504 | addrLen = sizeof(addr); 505 | bytesRead = recvfrom(CFSocketGetNative(self.socket), buffer, kBufferSize, 0, (struct sockaddr *) &addr, &addrLen); 506 | err = 0; 507 | if (bytesRead < 0) { 508 | err = errno; 509 | } 510 | 511 | // Process the data we read. 512 | 513 | if (bytesRead > 0) { 514 | NSMutableData * packet; 515 | id strongDelegate; 516 | uint16_t sequenceNumber; 517 | 518 | packet = [NSMutableData dataWithBytes:buffer length:(NSUInteger) bytesRead]; 519 | assert(packet != nil); 520 | 521 | // We got some data, pass it up to our client. 522 | 523 | strongDelegate = self.delegate; 524 | if ( [self validatePingResponsePacket:packet sequenceNumber:&sequenceNumber] ) { 525 | if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didReceivePingResponsePacket:sequenceNumber:)] ) { 526 | [strongDelegate simplePing:self didReceivePingResponsePacket:packet sequenceNumber:sequenceNumber]; 527 | } 528 | } else { 529 | if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didReceiveUnexpectedPacket:)] ) { 530 | [strongDelegate simplePing:self didReceiveUnexpectedPacket:packet]; 531 | } 532 | } 533 | } else { 534 | 535 | // We failed to read the data, so shut everything down. 536 | 537 | if (err == 0) { 538 | err = EPIPE; 539 | } 540 | [self didFailWithError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]]; 541 | } 542 | 543 | free(buffer); 544 | 545 | // Note that we don't loop back trying to read more data. Rather, we just 546 | // let CFSocket call us again. 547 | } 548 | 549 | /*! The callback for our CFSocket object. 550 | * \details This simply routes the call to our `-readData` method. 551 | * \param s See the documentation for CFSocketCallBack. 552 | * \param type See the documentation for CFSocketCallBack. 553 | * \param address See the documentation for CFSocketCallBack. 554 | * \param data See the documentation for CFSocketCallBack. 555 | * \param info See the documentation for CFSocketCallBack; this is actually a pointer to the 556 | * 'owning' object. 557 | */ 558 | 559 | static void SocketReadCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) { 560 | // This C routine is called by CFSocket when there's data waiting on our 561 | // ICMP socket. It just redirects the call to Objective-C code. 562 | SimplePing * obj; 563 | 564 | obj = (__bridge SimplePing *) info; 565 | assert([obj isKindOfClass:[SimplePing class]]); 566 | 567 | #pragma unused(s) 568 | assert(s == obj.socket); 569 | #pragma unused(type) 570 | assert(type == kCFSocketReadCallBack); 571 | #pragma unused(address) 572 | assert(address == nil); 573 | #pragma unused(data) 574 | assert(data == nil); 575 | 576 | [obj readData]; 577 | } 578 | 579 | /*! Starts the send and receive infrastructure. 580 | * \details This is called once we've successfully resolved `hostName` in to 581 | * `hostAddress`. It's responsible for setting up the socket for sending and 582 | * receiving pings. 583 | */ 584 | 585 | - (void)startWithHostAddress { 586 | int err; 587 | int fd; 588 | 589 | assert(self.hostAddress != nil); 590 | 591 | // Open the socket. 592 | 593 | fd = -1; 594 | err = 0; 595 | switch (self.hostAddressFamily) { 596 | case AF_INET: { 597 | fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); 598 | if (fd < 0) { 599 | err = errno; 600 | } 601 | } break; 602 | case AF_INET6: { 603 | fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); 604 | if (fd < 0) { 605 | err = errno; 606 | } 607 | } break; 608 | default: { 609 | err = EPROTONOSUPPORT; 610 | } break; 611 | } 612 | 613 | if (err != 0) { 614 | [self didFailWithError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]]; 615 | } else { 616 | CFSocketContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; 617 | CFRunLoopSourceRef rls; 618 | id strongDelegate; 619 | 620 | // Wrap it in a CFSocket and schedule it on the runloop. 621 | 622 | self.socket = (CFSocketRef) CFAutorelease( CFSocketCreateWithNative(NULL, fd, kCFSocketReadCallBack, SocketReadCallback, &context) ); 623 | assert(self.socket != NULL); 624 | 625 | // The socket will now take care of cleaning up our file descriptor. 626 | 627 | assert( CFSocketGetSocketFlags(self.socket) & kCFSocketCloseOnInvalidate ); 628 | fd = -1; 629 | 630 | rls = CFSocketCreateRunLoopSource(NULL, self.socket, 0); 631 | assert(rls != NULL); 632 | 633 | CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); 634 | 635 | CFRelease(rls); 636 | 637 | strongDelegate = self.delegate; 638 | if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didStartWithAddress:)] ) { 639 | [strongDelegate simplePing:self didStartWithAddress:self.hostAddress]; 640 | } 641 | } 642 | assert(fd == -1); 643 | } 644 | 645 | /*! Processes the results of our name-to-address resolution. 646 | * \details Called by our CFHost resolution callback (HostResolveCallback) when host 647 | * resolution is complete. We just latch the first appropriate address and kick 648 | * off the send and receive infrastructure. 649 | */ 650 | 651 | - (void)hostResolutionDone { 652 | Boolean resolved; 653 | NSArray * addresses; 654 | 655 | // Find the first appropriate address. 656 | 657 | addresses = (__bridge NSArray *) CFHostGetAddressing(self.host, &resolved); 658 | if ( resolved && (addresses != nil) ) { 659 | resolved = false; 660 | for (NSData * address in addresses) { 661 | const struct sockaddr * addrPtr; 662 | 663 | addrPtr = (const struct sockaddr *) address.bytes; 664 | if ( address.length >= sizeof(struct sockaddr) ) { 665 | switch (addrPtr->sa_family) { 666 | case AF_INET: { 667 | if (self.addressStyle != SimplePingAddressStyleICMPv6) { 668 | self.hostAddress = address; 669 | resolved = true; 670 | } 671 | } break; 672 | case AF_INET6: { 673 | if (self.addressStyle != SimplePingAddressStyleICMPv4) { 674 | self.hostAddress = address; 675 | resolved = true; 676 | } 677 | } break; 678 | } 679 | } 680 | if (resolved) { 681 | break; 682 | } 683 | } 684 | } 685 | 686 | // We're done resolving, so shut that down. 687 | 688 | [self stopHostResolution]; 689 | 690 | // If all is OK, start the send and receive infrastructure, otherwise stop. 691 | 692 | if (resolved) { 693 | [self startWithHostAddress]; 694 | } else { 695 | [self didFailWithError:[NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorHostNotFound userInfo:nil]]; 696 | } 697 | } 698 | 699 | /*! The callback for our CFHost object. 700 | * \details This simply routes the call to our `-hostResolutionDone` or 701 | * `-didFailWithHostStreamError:` methods. 702 | * \param theHost See the documentation for CFHostClientCallBack. 703 | * \param typeInfo See the documentation for CFHostClientCallBack. 704 | * \param error See the documentation for CFHostClientCallBack. 705 | * \param info See the documentation for CFHostClientCallBack; this is actually a pointer to 706 | * the 'owning' object. 707 | */ 708 | 709 | static void HostResolveCallback(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void *info) { 710 | // This C routine is called by CFHost when the host resolution is complete. 711 | // It just redirects the call to the appropriate Objective-C method. 712 | SimplePing * obj; 713 | 714 | obj = (__bridge SimplePing *) info; 715 | assert([obj isKindOfClass:[SimplePing class]]); 716 | 717 | #pragma unused(theHost) 718 | assert(theHost == obj.host); 719 | #pragma unused(typeInfo) 720 | assert(typeInfo == kCFHostAddresses); 721 | 722 | if ( (error != NULL) && (error->domain != 0) ) { 723 | [obj didFailWithHostStreamError:*error]; 724 | } else { 725 | [obj hostResolutionDone]; 726 | } 727 | } 728 | 729 | - (void)start { 730 | Boolean success; 731 | CFHostClientContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; 732 | CFStreamError streamError; 733 | 734 | assert(self.host == NULL); 735 | assert(self.hostAddress == nil); 736 | 737 | self.host = (CFHostRef) CFAutorelease( CFHostCreateWithName(NULL, (__bridge CFStringRef) self.hostName) ); 738 | assert(self.host != NULL); 739 | 740 | CFHostSetClient(self.host, HostResolveCallback, &context); 741 | 742 | CFHostScheduleWithRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 743 | 744 | success = CFHostStartInfoResolution(self.host, kCFHostAddresses, &streamError); 745 | if ( ! success ) { 746 | [self didFailWithHostStreamError:streamError]; 747 | } 748 | } 749 | 750 | /*! Stops the name-to-address resolution infrastructure. 751 | */ 752 | 753 | - (void)stopHostResolution { 754 | // Shut down the CFHost. 755 | if (self.host != NULL) { 756 | CFHostSetClient(self.host, NULL, NULL); 757 | CFHostUnscheduleFromRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 758 | self.host = NULL; 759 | } 760 | } 761 | 762 | /*! Stops the send and receive infrastructure. 763 | */ 764 | 765 | - (void)stopSocket { 766 | if (self.socket != NULL) { 767 | CFSocketInvalidate(self.socket); 768 | self.socket = NULL; 769 | } 770 | } 771 | 772 | - (void)stop { 773 | [self stopHostResolution]; 774 | [self stopSocket]; 775 | 776 | // Junk the host address on stop. If the client calls -start again, we'll 777 | // re-resolve the host name. 778 | 779 | self.hostAddress = NULL; 780 | } 781 | 782 | @end 783 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Sample code project: SimplePing 2 | Version: 5.0 3 | 4 | IMPORTANT: This Apple software is supplied to you by Apple 5 | Inc. ("Apple") in consideration of your agreement to the following 6 | terms, and your use, installation, modification or redistribution of 7 | this Apple software constitutes acceptance of these terms. If you do 8 | not agree with these terms, please do not use, install, modify or 9 | redistribute this Apple software. 10 | 11 | In consideration of your agreement to abide by the following terms, and 12 | subject to these terms, Apple grants you a personal, non-exclusive 13 | license, under Apple's copyrights in this original Apple software (the 14 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 15 | Software, with or without modifications, in source and/or binary forms; 16 | provided that if you redistribute the Apple Software in its entirety and 17 | without modifications, you must retain this notice and the following 18 | text and disclaimers in all such redistributions of the Apple Software. 19 | Neither the name, trademarks, service marks or logos of Apple Inc. may 20 | be used to endorse or promote products derived from the Apple Software 21 | without specific prior written permission from Apple. Except as 22 | expressly stated in this notice, no other rights or licenses, express or 23 | implied, are granted by Apple herein, including but not limited to any 24 | patent rights that may be infringed by your derivative works or by other 25 | works in which the Apple Software may be incorporated. 26 | 27 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 28 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 29 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 30 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 31 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 32 | 33 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 34 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 35 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 36 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 37 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 38 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 39 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 40 | POSSIBILITY OF SUCH DAMAGE. 41 | 42 | Copyright (C) 2016 Apple Inc. All Rights Reserved. 43 | -------------------------------------------------------------------------------- /MacTool/main.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2016 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | A Mac command line tool to test the SimplePing class. 7 | */ 8 | 9 | #import "SimplePing.h" 10 | 11 | #include 12 | #include 13 | 14 | #pragma mark * Utilities 15 | 16 | /*! Returns the string representation of the supplied address. 17 | * \param address Contains a (struct sockaddr) with the address to render. 18 | * \returns A string representation of that address. 19 | */ 20 | 21 | static NSString * displayAddressForAddress(NSData * address) { 22 | int err; 23 | NSString * result; 24 | char hostStr[NI_MAXHOST]; 25 | 26 | result = nil; 27 | 28 | if (address != nil) { 29 | err = getnameinfo(address.bytes, (socklen_t) address.length, hostStr, sizeof(hostStr), NULL, 0, NI_NUMERICHOST); 30 | if (err == 0) { 31 | result = @(hostStr); 32 | } 33 | } 34 | 35 | if (result == nil) { 36 | result = @"?"; 37 | } 38 | 39 | return result; 40 | } 41 | 42 | /*! Returns a short error string for the supplied error. 43 | * \param error The error to render. 44 | * \returns A short string representing that error. 45 | */ 46 | 47 | static NSString * shortErrorFromError(NSError * error) { 48 | NSString * result; 49 | NSNumber * failureNum; 50 | int failure; 51 | const char * failureStr; 52 | 53 | assert(error != nil); 54 | 55 | result = nil; 56 | 57 | // Handle DNS errors as a special case. 58 | 59 | if ( [error.domain isEqual:(NSString *)kCFErrorDomainCFNetwork] && (error.code == kCFHostErrorUnknown) ) { 60 | failureNum = error.userInfo[(id) kCFGetAddrInfoFailureKey]; 61 | if ( [failureNum isKindOfClass:[NSNumber class]] ) { 62 | failure = failureNum.intValue; 63 | if (failure != 0) { 64 | failureStr = gai_strerror(failure); 65 | if (failureStr != NULL) { 66 | result = @(failureStr); 67 | } 68 | } 69 | } 70 | } 71 | 72 | // Otherwise try various properties of the error object. 73 | 74 | if (result == nil) { 75 | result = error.localizedFailureReason; 76 | } 77 | if (result == nil) { 78 | result = error.localizedDescription; 79 | } 80 | assert(result != nil); 81 | return result; 82 | } 83 | 84 | 85 | #pragma mark * Main 86 | 87 | /*! The main object for our tool. 88 | * \details This exists primarily because SimplePing requires an object to act as its delegate. 89 | */ 90 | 91 | @interface Main : NSObject 92 | 93 | @property (nonatomic, assign, readwrite) BOOL forceIPv4; 94 | @property (nonatomic, assign, readwrite) BOOL forceIPv6; 95 | @property (nonatomic, strong, readwrite, nullable) SimplePing * pinger; 96 | @property (nonatomic, strong, readwrite, nullable) NSTimer * sendTimer; 97 | 98 | @end 99 | 100 | @implementation Main 101 | 102 | - (void)dealloc { 103 | [self->_pinger stop]; 104 | [self->_sendTimer invalidate]; 105 | } 106 | 107 | /*! The Objective-C 'main' for this program. 108 | * \details This creates a SimplePing object, configures it, and then runs the run loop 109 | * sending pings and printing the results. 110 | * \param hostName The host to ping. 111 | */ 112 | 113 | - (void)runWithHostName:(NSString *)hostName { 114 | assert(self.pinger == nil); 115 | 116 | self.pinger = [[SimplePing alloc] initWithHostName:hostName]; 117 | assert(self.pinger != nil); 118 | 119 | // By default we use the first IP address we get back from host resolution (.Any) 120 | // but these flags let the user override that. 121 | 122 | if (self.forceIPv4 && ! self.forceIPv6) { 123 | self.pinger.addressStyle = SimplePingAddressStyleICMPv4; 124 | } else if (self.forceIPv6 && ! self.forceIPv4) { 125 | self.pinger.addressStyle = SimplePingAddressStyleICMPv6; 126 | } 127 | 128 | self.pinger.delegate = self; 129 | [self.pinger start]; 130 | 131 | do { 132 | [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 133 | } while (self.pinger != nil); 134 | } 135 | 136 | /*! Sends a ping. 137 | * \details Called to send a ping, both directly (as soon as the SimplePing object starts up) 138 | * and via a timer (to continue sending pings periodically). 139 | */ 140 | 141 | - (void)sendPing { 142 | assert(self.pinger != nil); 143 | [self.pinger sendPingWithData:nil]; 144 | } 145 | 146 | - (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address { 147 | #pragma unused(pinger) 148 | assert(pinger == self.pinger); 149 | assert(address != nil); 150 | 151 | NSLog(@"pinging %@", displayAddressForAddress(address)); 152 | 153 | // Send the first ping straight away. 154 | 155 | [self sendPing]; 156 | 157 | // And start a timer to send the subsequent pings. 158 | 159 | assert(self.sendTimer == nil); 160 | self.sendTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(sendPing) userInfo:nil repeats:YES]; 161 | } 162 | 163 | - (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error { 164 | #pragma unused(pinger) 165 | assert(pinger == self.pinger); 166 | NSLog(@"failed: %@", shortErrorFromError(error)); 167 | 168 | [self.sendTimer invalidate]; 169 | self.sendTimer = nil; 170 | 171 | // No need to call -stop. The pinger will stop itself in this case. 172 | // We do however want to nil out pinger so that the runloop stops. 173 | 174 | self.pinger = nil; 175 | } 176 | 177 | - (void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber { 178 | #pragma unused(pinger) 179 | assert(pinger == self.pinger); 180 | #pragma unused(packet) 181 | NSLog(@"#%u sent", (unsigned int) sequenceNumber); 182 | } 183 | 184 | - (void)simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber error:(NSError *)error { 185 | #pragma unused(pinger) 186 | assert(pinger == self.pinger); 187 | #pragma unused(packet) 188 | NSLog(@"#%u send failed: %@", (unsigned int) sequenceNumber, shortErrorFromError(error)); 189 | } 190 | 191 | - (void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber { 192 | #pragma unused(pinger) 193 | assert(pinger == self.pinger); 194 | #pragma unused(packet) 195 | NSLog(@"#%u received, size=%zu", (unsigned int) sequenceNumber, (size_t) packet.length); 196 | } 197 | 198 | - (void)simplePing:(SimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet { 199 | #pragma unused(pinger) 200 | assert(pinger == self.pinger); 201 | 202 | NSLog(@"unexpected packet, size=%zu", (size_t) packet.length); 203 | } 204 | 205 | @end 206 | 207 | int main(int argc, char* argv[]) { 208 | int retVal; 209 | 210 | @autoreleasepool { 211 | int ch; 212 | Main * mainObj; 213 | 214 | mainObj = [[Main alloc] init]; 215 | 216 | retVal = EXIT_SUCCESS; 217 | do { 218 | ch = getopt(argc, argv, "46"); 219 | if (ch != -1) { 220 | switch (ch) { 221 | case '4': { 222 | mainObj.forceIPv4 = YES; 223 | } break; 224 | case '6': { 225 | mainObj.forceIPv6 = YES; 226 | } break; 227 | case '?': 228 | default: { 229 | retVal = EXIT_FAILURE; 230 | } break; 231 | } 232 | } 233 | } while ( (ch != -1) && (retVal == EXIT_SUCCESS) ); 234 | 235 | if ( (retVal == EXIT_SUCCESS) && ((optind + 1) != argc) ) { 236 | retVal = EXIT_FAILURE; 237 | } 238 | 239 | if (retVal == EXIT_FAILURE) { 240 | fprintf(stderr, "usage: %s [-4] [-6] host\n", getprogname()); 241 | } else { 242 | [mainObj runWithHostName:@(argv[optind])]; 243 | } 244 | } 245 | 246 | return retVal; 247 | } 248 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SimplePing 2 | 3 | ## Update 4 | 5 | This project takes [Apple's `SimplePing` code sample](https://developer.apple.com/library/archive/samplecode/SimplePing/Introduction/Intro.html) and has done the following: 6 | 7 | * Updated the iOS target to work with Swift 5.x, Xcode 15. 8 | * Pulled the `SimplePing` logic out of the view controller and into a separate `SimplePingManager` object. 9 | 10 | ``` 11 | let pingManager = SimplePingManager() 12 | 13 | func startPingingAndPrintResults() { 14 | pingManager.start(hostName: hostName, forceIPv4: forceIPv4, forceIPv6: forceIPv6) { result in 15 | print(result) 16 | } 17 | } 18 | ``` 19 | 20 | * Created Combine publisher `SimplePingPublisher`, used as follows: 21 | 22 | ``` 23 | struct ContentView: View { 24 | @State var simplePing: AnyCancellable? 25 | @State var isRunning = false 26 | 27 | var body: some View { 28 | Button(action: { 29 | self.startOrStopPing() 30 | }) { 31 | Text(isRunning ? "Stop" : "Start") 32 | } 33 | } 34 | 35 | private func startOrStopPing() { 36 | defer { isRunning = !isRunning } 37 | 38 | guard !isRunning else { 39 | simplePing?.cancel() 40 | return 41 | } 42 | 43 | simplePing = SimplePingPublisher(hostName: "www.apple.com") 44 | .sink(receiveCompletion: { completion in 45 | print(completion) 46 | }, receiveValue: { response in 47 | print(response) 48 | }) 49 | } 50 | } 51 | ``` 52 | 53 | Robert M. Ryan
54 | 4 April 2020 55 | 56 | --- 57 | 58 | ## About 59 | 60 | 1.4 61 | 62 | SimplePing demonstrates ping (ICMP) send and receive. 63 | 64 | ## Requirements 65 | 66 | ### Build 67 | 68 | Xcode 7.3 69 | 70 | The sample was built using Xcode 7.2.1 on OS X 10.11.2 with the OS X 10.11 SDK. You should be able to just open the project, select the *MacTool* scheme, and choose *Product* > *Build*. 71 | 72 | ### Runtime 73 | 74 | 10.11 75 | 76 | Although the sample requires 10.11, the core code works just fine on all versions of iOS and the underlying approach works on earlier versions of OS X (back to 10.2). 77 | 78 | ## Packing List 79 | 80 | The sample contains the following items: 81 | 82 | * `README.md` — This file. 83 | 84 | * `LICENSE.txt` — The standard sample code license. 85 | 86 | * `SimplePing.xcodeproj` — An Xcode project for the program. 87 | 88 | * `Common` — A directory containing common code, namely, the SimplePing class, an object wrapper around the low-level BSD Sockets ping function. 89 | 90 | * `MacTool` — A directory containing a single file, `main.m`, which is a Mac command line tool to exercise the SimplePing class. 91 | 92 | * `iOSApp` — A directory containing a trivial iOS app to test the SimplePing class. Most of it is boilerplate; the only interesting code is in `MainViewController.swift`, which is the iOS analogue of `main.m`. 93 | 94 | ## Using the Sample 95 | 96 | Once you’ve built the SimplePing tool you can test it by running it from Terminal with the address of a host to ping. For example: 97 | 98 | $ ./SimplePing www.apple.com 99 | 2016-02-29 10:28:03.589 SimplePing[58314:6905268] pinging 23.53.214.138 100 | 2016-02-29 10:28:03.590 SimplePing[58314:6905268] #0 sent 101 | 2016-02-29 10:28:03.626 SimplePing[58314:6905268] #0 received, size=64 102 | 2016-02-29 10:28:04.592 SimplePing[58314:6905268] #1 sent 103 | 2016-02-29 10:28:04.630 SimplePing[58314:6905268] #1 received, size=64 104 | ^C 105 | 106 | The program will keep pinging until you stop it with ^C. 107 | 108 | ## How it Works 109 | 110 | On most platforms ping requires privileges (it’s implemented with a raw IP socket). Apple platforms include a special facility that allows you to ping without privileges. Specifically, you can open a special, non-privileged ICMP socket that allows you to send and receive pings. Look at the `-[SimplePing startWithHostAddress]` method for the details. 111 | 112 | Beyond that, SimplePing is a very simply application of CFHost (for name-to-address resolution) and CFSocket (for integrating the ICMP socket into the runloop). 113 | 114 | ## Using the SimplePing Class 115 | 116 | To use the SimplePing class in your app, add `SimplePing.h` and `SimplePing.m` to your project. 117 | 118 | **Note** If you’re developing solely in Swift, Xcode will prompt you as to whether you want to create a bridging header or not. Agree to that by clicking Create Bridging Header. 119 | 120 | If you want to call SimplePing from your Swift code, add the following line to your bridging header. 121 | 122 | #include "SimplePing.h" 123 | 124 | Alternatively, if you’re using Objective-C, add the same line at the start of your `.m` file. 125 | 126 | Finally, to use the SimplePing class: 127 | 128 | 1. create an instance of the SimplePing class and keep a reference to that instance 129 | 130 | 2. set the `delegate` property 131 | 132 | 3. call `start()`. 133 | 134 | 4. if things go well, your delegate’s `simplePing(_:didStartWithAddress:)` method will be called; to send a ping, call `sendPingWithData(_:)` 135 | 136 | 5. when SimplePing receives an ICMP packet, it will call the `simplePing(_:didReceivePingResponsePacket:sequenceNumber:)` or `simplePing(:didReceiveUnexpectedPacket:)` delegate method 137 | 138 | SimplePing can be used from any thread but the use of any single instance must be confined to a specific thread. Moreover, that thread must run its run loop. In most cases it’s best to use SimplePing from the main thread. 139 | 140 | The SimplePing class has lots of header docs that explain it’s features in more depth. And if you’re looking for an example, there’s both Objective-C (`MacTool/main.m`) and Swift (`iOSApp/MainViewController.swift`) ones available. 141 | 142 | ## Caveats 143 | 144 | Much of what SimplePing does with CFSocket can also be done with GCD. Specifically, GCD makes it easy to integrate a BSD Socket into a typical run loop based program. This sample uses CFSocket because a) there are other examples of using GCD for socket-based networking, b) there are cases where CFSocket has advantages over GCD (for example, if you want to target a specific run loop, or a specific run loop mode), and c) at the time the sample was originally created, CFSocket was more commonly used. 145 | 146 | ## Feedback 147 | 148 | If you find any problems with this sample, or you’d like to suggest improvements, please [file a bug][bug] against it. 149 | 150 | [bug]: 151 | 152 | ## Version History 153 | 154 | 1.0.1 (Oct 2003) was the first shipping version. 155 | 156 | 1.1 (Feb 2010) was a complete rewrite with the twin goals of tidying the code and making the core code portable to iOS. 157 | 158 | 1.2 (Mar 2010) fixed a trivial problem that was causing the SimplePing module to not compile for iOS out of the box. 159 | 160 | 1.3 (Jul 2012) was a minor update to adopt the latest tools and techniques (most notably, ARC). 161 | 162 | 1.4 (Feb 2016) added support for IPv6. There were many other editorial changes that don’t don’t impact on the core functionality of the sample. 163 | 164 | Share and Enjoy 165 | 166 | Apple Developer Technical Support
167 | Core OS/Hardware 168 | 169 | 18 Apr 2016 170 | 171 | Copyright (C) 2016 Apple Inc. All rights reserved. 172 | -------------------------------------------------------------------------------- /SimplePing.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 834A91492439435900CDE7C2 /* SimplePingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834A91482439435900CDE7C2 /* SimplePingManager.swift */; }; 11 | E473E9AE1C4FF27D0079BC19 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E473E9AD1C4FF27D0079BC19 /* LaunchScreen.storyboard */; }; 12 | E473E9B01C4FF27D0079BC19 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E473E9AF1C4FF27D0079BC19 /* Main.storyboard */; }; 13 | E473E9B21C4FF27D0079BC19 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E473E9B11C4FF27D0079BC19 /* AppDelegate.swift */; }; 14 | E473E9B41C4FF27D0079BC19 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E473E9B31C4FF27D0079BC19 /* MainViewController.swift */; }; 15 | E473E9C21C4FF2DA0079BC19 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E473E9BF1C4FF2DA0079BC19 /* main.m */; }; 16 | E473E9C61C4FF2EA0079BC19 /* SimplePing.m in Sources */ = {isa = PBXBuildFile; fileRef = E473E9C51C4FF2EA0079BC19 /* SimplePing.m */; }; 17 | E473E9C71C4FF2F00079BC19 /* SimplePing.m in Sources */ = {isa = PBXBuildFile; fileRef = E473E9C51C4FF2EA0079BC19 /* SimplePing.m */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 834A91482439435900CDE7C2 /* SimplePingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplePingManager.swift; sourceTree = ""; }; 22 | 8DD76FB20486AB0100D96B5E /* SimplePing */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SimplePing; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | E473E9AA1C4FF27D0079BC19 /* SimplePingApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimplePingApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | E473E9AC1C4FF27D0079BC19 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 25 | E473E9AD1C4FF27D0079BC19 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 26 | E473E9AF1C4FF27D0079BC19 /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 27 | E473E9B11C4FF27D0079BC19 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 28 | E473E9B31C4FF27D0079BC19 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; 29 | E473E9BF1C4FF2DA0079BC19 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 30 | E473E9C41C4FF2EA0079BC19 /* SimplePing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimplePing.h; sourceTree = ""; }; 31 | E473E9C51C4FF2EA0079BC19 /* SimplePing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimplePing.m; sourceTree = ""; }; 32 | E473E9F01C4FF6920079BC19 /* iOSApp-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "iOSApp-Bridging-Header.h"; sourceTree = ""; }; 33 | E473E9F21C501E9A0079BC19 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.txt; sourceTree = ""; }; 34 | E473E9F31C501E9A0079BC19 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | 8DD76FAD0486AB0100D96B5E /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | E473E9A71C4FF27D0079BC19 /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | ); 50 | runOnlyForDeploymentPostprocessing = 0; 51 | }; 52 | /* End PBXFrameworksBuildPhase section */ 53 | 54 | /* Begin PBXGroup section */ 55 | 08FB7794FE84155DC02AAC07 /* SimplePing */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | E473E9F31C501E9A0079BC19 /* README.md */, 59 | E473E9F21C501E9A0079BC19 /* LICENSE.txt */, 60 | E473E9C31C4FF2EA0079BC19 /* Common */, 61 | E473E9BE1C4FF2DA0079BC19 /* MacTool */, 62 | E473E9AB1C4FF27D0079BC19 /* iOSApp */, 63 | E473E9F11C4FFA8D0079BC19 /* Products */, 64 | ); 65 | name = SimplePing; 66 | sourceTree = ""; 67 | }; 68 | E473E9AB1C4FF27D0079BC19 /* iOSApp */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | E473E9AC1C4FF27D0079BC19 /* Info.plist */, 72 | E473E9AD1C4FF27D0079BC19 /* LaunchScreen.storyboard */, 73 | E473E9AF1C4FF27D0079BC19 /* Main.storyboard */, 74 | E473E9B11C4FF27D0079BC19 /* AppDelegate.swift */, 75 | E473E9B31C4FF27D0079BC19 /* MainViewController.swift */, 76 | 834A91482439435900CDE7C2 /* SimplePingManager.swift */, 77 | E473E9F01C4FF6920079BC19 /* iOSApp-Bridging-Header.h */, 78 | ); 79 | path = iOSApp; 80 | sourceTree = ""; 81 | }; 82 | E473E9BE1C4FF2DA0079BC19 /* MacTool */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | E473E9BF1C4FF2DA0079BC19 /* main.m */, 86 | ); 87 | path = MacTool; 88 | sourceTree = ""; 89 | }; 90 | E473E9C31C4FF2EA0079BC19 /* Common */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | E473E9C41C4FF2EA0079BC19 /* SimplePing.h */, 94 | E473E9C51C4FF2EA0079BC19 /* SimplePing.m */, 95 | ); 96 | path = Common; 97 | sourceTree = ""; 98 | }; 99 | E473E9F11C4FFA8D0079BC19 /* Products */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 8DD76FB20486AB0100D96B5E /* SimplePing */, 103 | E473E9AA1C4FF27D0079BC19 /* SimplePingApp.app */, 104 | ); 105 | name = Products; 106 | sourceTree = ""; 107 | }; 108 | /* End PBXGroup section */ 109 | 110 | /* Begin PBXNativeTarget section */ 111 | 8DD76FA90486AB0100D96B5E /* MacTool */ = { 112 | isa = PBXNativeTarget; 113 | buildConfigurationList = 1DEB928508733DD80010E9CD /* Build configuration list for PBXNativeTarget "MacTool" */; 114 | buildPhases = ( 115 | 8DD76FAB0486AB0100D96B5E /* Sources */, 116 | 8DD76FAD0486AB0100D96B5E /* Frameworks */, 117 | ); 118 | buildRules = ( 119 | ); 120 | dependencies = ( 121 | ); 122 | name = MacTool; 123 | productInstallPath = "$(HOME)/bin"; 124 | productName = SimplePing; 125 | productReference = 8DD76FB20486AB0100D96B5E /* SimplePing */; 126 | productType = "com.apple.product-type.tool"; 127 | }; 128 | E473E9A91C4FF27D0079BC19 /* iOSApp */ = { 129 | isa = PBXNativeTarget; 130 | buildConfigurationList = E473E9B71C4FF27D0079BC19 /* Build configuration list for PBXNativeTarget "iOSApp" */; 131 | buildPhases = ( 132 | E473E9A61C4FF27D0079BC19 /* Sources */, 133 | E473E9A71C4FF27D0079BC19 /* Frameworks */, 134 | E473E9A81C4FF27D0079BC19 /* Resources */, 135 | ); 136 | buildRules = ( 137 | ); 138 | dependencies = ( 139 | ); 140 | name = iOSApp; 141 | productName = iOSApp; 142 | productReference = E473E9AA1C4FF27D0079BC19 /* SimplePingApp.app */; 143 | productType = "com.apple.product-type.application"; 144 | }; 145 | /* End PBXNativeTarget section */ 146 | 147 | /* Begin PBXProject section */ 148 | 08FB7793FE84155DC02AAC07 /* Project object */ = { 149 | isa = PBXProject; 150 | attributes = { 151 | BuildIndependentTargetsInParallel = YES; 152 | LastSwiftUpdateCheck = 0720; 153 | LastUpgradeCheck = 1500; 154 | TargetAttributes = { 155 | E473E9A91C4FF27D0079BC19 = { 156 | CreatedOnToolsVersion = 7.2; 157 | }; 158 | }; 159 | }; 160 | buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "SimplePing" */; 161 | compatibilityVersion = "Xcode 3.2"; 162 | developmentRegion = en; 163 | hasScannedForEncodings = 1; 164 | knownRegions = ( 165 | en, 166 | Base, 167 | ); 168 | mainGroup = 08FB7794FE84155DC02AAC07 /* SimplePing */; 169 | productRefGroup = 08FB7794FE84155DC02AAC07 /* SimplePing */; 170 | projectDirPath = ""; 171 | projectRoot = ""; 172 | targets = ( 173 | 8DD76FA90486AB0100D96B5E /* MacTool */, 174 | E473E9A91C4FF27D0079BC19 /* iOSApp */, 175 | ); 176 | }; 177 | /* End PBXProject section */ 178 | 179 | /* Begin PBXResourcesBuildPhase section */ 180 | E473E9A81C4FF27D0079BC19 /* Resources */ = { 181 | isa = PBXResourcesBuildPhase; 182 | buildActionMask = 2147483647; 183 | files = ( 184 | E473E9AE1C4FF27D0079BC19 /* LaunchScreen.storyboard in Resources */, 185 | E473E9B01C4FF27D0079BC19 /* Main.storyboard in Resources */, 186 | ); 187 | runOnlyForDeploymentPostprocessing = 0; 188 | }; 189 | /* End PBXResourcesBuildPhase section */ 190 | 191 | /* Begin PBXSourcesBuildPhase section */ 192 | 8DD76FAB0486AB0100D96B5E /* Sources */ = { 193 | isa = PBXSourcesBuildPhase; 194 | buildActionMask = 2147483647; 195 | files = ( 196 | E473E9C21C4FF2DA0079BC19 /* main.m in Sources */, 197 | E473E9C61C4FF2EA0079BC19 /* SimplePing.m in Sources */, 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | }; 201 | E473E9A61C4FF27D0079BC19 /* Sources */ = { 202 | isa = PBXSourcesBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | E473E9C71C4FF2F00079BC19 /* SimplePing.m in Sources */, 206 | 834A91492439435900CDE7C2 /* SimplePingManager.swift in Sources */, 207 | E473E9B21C4FF27D0079BC19 /* AppDelegate.swift in Sources */, 208 | E473E9B41C4FF27D0079BC19 /* MainViewController.swift in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXSourcesBuildPhase section */ 213 | 214 | /* Begin XCBuildConfiguration section */ 215 | 1DEB928608733DD80010E9CD /* Debug */ = { 216 | isa = XCBuildConfiguration; 217 | buildSettings = { 218 | CODE_SIGN_IDENTITY = "-"; 219 | DEAD_CODE_STRIPPING = YES; 220 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; 221 | PRODUCT_NAME = SimplePing; 222 | }; 223 | name = Debug; 224 | }; 225 | 1DEB928708733DD80010E9CD /* Release */ = { 226 | isa = XCBuildConfiguration; 227 | buildSettings = { 228 | CODE_SIGN_IDENTITY = "-"; 229 | DEAD_CODE_STRIPPING = YES; 230 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; 231 | PRODUCT_NAME = SimplePing; 232 | }; 233 | name = Release; 234 | }; 235 | 1DEB928A08733DD80010E9CD /* Debug */ = { 236 | isa = XCBuildConfiguration; 237 | buildSettings = { 238 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 239 | CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; 240 | CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; 241 | CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; 242 | CLANG_ENABLE_MODULES = YES; 243 | CLANG_ENABLE_OBJC_ARC = YES; 244 | CLANG_WARN_ASSIGN_ENUM = YES; 245 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 246 | CLANG_WARN_COMMA = YES; 247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 248 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; 251 | CLANG_WARN_INFINITE_RECURSION = YES; 252 | CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES; 253 | CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; 254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 255 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES; 256 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 258 | CLANG_WARN_STRICT_PROTOTYPES = YES; 259 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; 260 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 261 | CLANG_WARN_UNREACHABLE_CODE = YES; 262 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 263 | DEAD_CODE_STRIPPING = YES; 264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 265 | ENABLE_STRICT_OBJC_MSGSEND = YES; 266 | ENABLE_TESTABILITY = YES; 267 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_OPTIMIZATION_LEVEL = 0; 270 | GCC_PREPROCESSOR_DEFINITIONS = ""; 271 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 272 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; 273 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES; 274 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 275 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 276 | GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; 277 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 278 | GCC_WARN_SHADOW = YES; 279 | GCC_WARN_SIGN_COMPARE = YES; 280 | GCC_WARN_STRICT_SELECTOR_MATCH = YES; 281 | GCC_WARN_UNDECLARED_SELECTOR = YES; 282 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 283 | GCC_WARN_UNUSED_FUNCTION = YES; 284 | GCC_WARN_UNUSED_LABEL = YES; 285 | GCC_WARN_UNUSED_PARAMETER = YES; 286 | GCC_WARN_UNUSED_VARIABLE = YES; 287 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 288 | MACOSX_DEPLOYMENT_TARGET = 10.11; 289 | ONLY_ACTIVE_ARCH = YES; 290 | RUN_CLANG_STATIC_ANALYZER = YES; 291 | SDKROOT = macosx; 292 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 293 | }; 294 | name = Debug; 295 | }; 296 | 1DEB928B08733DD80010E9CD /* Release */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 300 | CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; 301 | CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; 302 | CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; 303 | CLANG_ENABLE_MODULES = YES; 304 | CLANG_ENABLE_OBJC_ARC = YES; 305 | CLANG_WARN_ASSIGN_ENUM = YES; 306 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 307 | CLANG_WARN_COMMA = YES; 308 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 309 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 310 | CLANG_WARN_EMPTY_BODY = YES; 311 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; 312 | CLANG_WARN_INFINITE_RECURSION = YES; 313 | CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES; 314 | CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; 315 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 316 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES; 317 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 318 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 319 | CLANG_WARN_STRICT_PROTOTYPES = YES; 320 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; 321 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 322 | CLANG_WARN_UNREACHABLE_CODE = YES; 323 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 324 | DEAD_CODE_STRIPPING = YES; 325 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 326 | ENABLE_STRICT_OBJC_MSGSEND = YES; 327 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 328 | GCC_NO_COMMON_BLOCKS = YES; 329 | GCC_OPTIMIZATION_LEVEL = s; 330 | GCC_PREPROCESSOR_DEFINITIONS = NDEBUG; 331 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 332 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; 333 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES; 334 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 335 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 336 | GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; 337 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 338 | GCC_WARN_SHADOW = YES; 339 | GCC_WARN_SIGN_COMPARE = YES; 340 | GCC_WARN_STRICT_SELECTOR_MATCH = YES; 341 | GCC_WARN_UNDECLARED_SELECTOR = YES; 342 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 343 | GCC_WARN_UNUSED_FUNCTION = YES; 344 | GCC_WARN_UNUSED_LABEL = YES; 345 | GCC_WARN_UNUSED_PARAMETER = YES; 346 | GCC_WARN_UNUSED_VARIABLE = YES; 347 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 348 | MACOSX_DEPLOYMENT_TARGET = 10.11; 349 | RUN_CLANG_STATIC_ANALYZER = NO; 350 | SDKROOT = macosx; 351 | SWIFT_COMPILATION_MODE = wholemodule; 352 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 353 | }; 354 | name = Release; 355 | }; 356 | E473E9B51C4FF27D0079BC19 /* Debug */ = { 357 | isa = XCBuildConfiguration; 358 | buildSettings = { 359 | ALWAYS_SEARCH_USER_PATHS = NO; 360 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 361 | DEVELOPMENT_TEAM = HQ8TLN3UQR; 362 | INFOPLIST_FILE = iOSApp/Info.plist; 363 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 364 | LD_RUNPATH_SEARCH_PATHS = ( 365 | "$(inherited)", 366 | "@executable_path/Frameworks", 367 | ); 368 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; 369 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.iOSApp"; 370 | PRODUCT_NAME = SimplePingApp; 371 | SDKROOT = iphoneos; 372 | SWIFT_OBJC_BRIDGING_HEADER = "iOSApp/iOSApp-Bridging-Header.h"; 373 | SWIFT_VERSION = 5.0; 374 | WRAPPER_EXTENSION = app; 375 | }; 376 | name = Debug; 377 | }; 378 | E473E9B61C4FF27D0079BC19 /* Release */ = { 379 | isa = XCBuildConfiguration; 380 | buildSettings = { 381 | ALWAYS_SEARCH_USER_PATHS = NO; 382 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 383 | DEVELOPMENT_TEAM = HQ8TLN3UQR; 384 | INFOPLIST_FILE = iOSApp/Info.plist; 385 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 386 | LD_RUNPATH_SEARCH_PATHS = ( 387 | "$(inherited)", 388 | "@executable_path/Frameworks", 389 | ); 390 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; 391 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.iOSApp"; 392 | PRODUCT_NAME = SimplePingApp; 393 | SDKROOT = iphoneos; 394 | SWIFT_OBJC_BRIDGING_HEADER = "iOSApp/iOSApp-Bridging-Header.h"; 395 | SWIFT_VERSION = 5.0; 396 | WRAPPER_EXTENSION = app; 397 | }; 398 | name = Release; 399 | }; 400 | /* End XCBuildConfiguration section */ 401 | 402 | /* Begin XCConfigurationList section */ 403 | 1DEB928508733DD80010E9CD /* Build configuration list for PBXNativeTarget "MacTool" */ = { 404 | isa = XCConfigurationList; 405 | buildConfigurations = ( 406 | 1DEB928608733DD80010E9CD /* Debug */, 407 | 1DEB928708733DD80010E9CD /* Release */, 408 | ); 409 | defaultConfigurationIsVisible = 0; 410 | defaultConfigurationName = Debug; 411 | }; 412 | 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "SimplePing" */ = { 413 | isa = XCConfigurationList; 414 | buildConfigurations = ( 415 | 1DEB928A08733DD80010E9CD /* Debug */, 416 | 1DEB928B08733DD80010E9CD /* Release */, 417 | ); 418 | defaultConfigurationIsVisible = 0; 419 | defaultConfigurationName = Debug; 420 | }; 421 | E473E9B71C4FF27D0079BC19 /* Build configuration list for PBXNativeTarget "iOSApp" */ = { 422 | isa = XCConfigurationList; 423 | buildConfigurations = ( 424 | E473E9B51C4FF27D0079BC19 /* Debug */, 425 | E473E9B61C4FF27D0079BC19 /* Release */, 426 | ); 427 | defaultConfigurationIsVisible = 0; 428 | defaultConfigurationName = Debug; 429 | }; 430 | /* End XCConfigurationList section */ 431 | }; 432 | rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; 433 | } 434 | -------------------------------------------------------------------------------- /SimplePing.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SimplePing.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /iOSApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2016 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | The main app controller for the iOS test app. 7 | */ 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | } 16 | -------------------------------------------------------------------------------- /iOSApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.4 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.4 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /iOSApp/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /iOSApp/Main.storyboard: -------------------------------------------------------------------------------- 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 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 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 | -------------------------------------------------------------------------------- /iOSApp/MainViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2016 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | A view controller for testing SimplePing on iOS. 7 | */ 8 | 9 | import UIKit 10 | 11 | class MainViewController: UITableViewController { 12 | let hostName = "www.apple.com" 13 | 14 | let pingManager = SimplePingManager() 15 | 16 | @IBOutlet var forceIPv4Cell: UITableViewCell! 17 | @IBOutlet var forceIPv6Cell: UITableViewCell! 18 | @IBOutlet var startStopCell: UITableViewCell! 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | title = hostName 23 | } 24 | } 25 | 26 | // MARK: UITableViewDelegate 27 | 28 | extension MainViewController { 29 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 30 | let cell = tableView.cellForRow(at: indexPath)! 31 | 32 | switch cell { 33 | case forceIPv4Cell, forceIPv6Cell: 34 | cell.accessoryType = cell.accessoryType == .none ? .checkmark : .none 35 | 36 | case startStopCell: 37 | if pingManager.isStarted { 38 | stop() 39 | } else { 40 | let forceIPv4 = forceIPv4Cell.accessoryType != .none 41 | let forceIPv6 = forceIPv6Cell.accessoryType != .none 42 | start(forceIPv4: forceIPv4, forceIPv6: forceIPv6) 43 | } 44 | 45 | default: 46 | fatalError() 47 | } 48 | 49 | tableView.deselectRow(at: indexPath, animated: true) 50 | } 51 | } 52 | 53 | // MARK: - Private utility methods 54 | 55 | private extension MainViewController { 56 | /// Called by the table view selection delegate callback to start the ping. 57 | 58 | func start(forceIPv4: Bool, forceIPv6: Bool) { 59 | pingerWillStart() 60 | 61 | pingManager.start(hostName: hostName, forceIPv4: forceIPv4, forceIPv6: forceIPv6) { result in 62 | print(result) 63 | } 64 | } 65 | 66 | /// Called by the table view selection delegate callback to stop the ping. 67 | 68 | func stop() { 69 | pingManager.stop() 70 | pingerDidStop() 71 | } 72 | 73 | func pingerWillStart() { 74 | startStopCell.textLabel!.text = "Stop…" 75 | } 76 | 77 | func pingerDidStop() { 78 | startStopCell.textLabel!.text = "Start…" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /iOSApp/SimplePingManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimplePingManager.swift 3 | // iOSApp 4 | // 5 | // Created by Robert Ryan on 4/4/20. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | enum SimplePingResponse { 12 | case start(String) 13 | case sendFailed(Data, UInt16, Error) 14 | case sent(Data, UInt16) 15 | case received(Data, UInt16) 16 | case unexpectedPacket(Data) 17 | case failed(Error) 18 | } 19 | 20 | class SimplePingManager: NSObject { 21 | typealias SimplePingHandler = (SimplePingResponse) -> Void 22 | 23 | private var pinger: SimplePing? 24 | private var handler: SimplePingHandler? 25 | private weak var sendTimer: Timer? 26 | 27 | var isStarted: Bool { return pinger != nil } 28 | var nextSequenceNumber: Int? { (pinger?.nextSequenceNumber).flatMap { Int($0) } } 29 | 30 | deinit { 31 | stop() 32 | } 33 | } 34 | 35 | // MARK: Public interface 36 | 37 | extension SimplePingManager { 38 | /// Called by the table view selection delegate callback to start the ping. 39 | 40 | func start(hostName: String, forceIPv4: Bool = false, forceIPv6: Bool = false, handler: @escaping SimplePingHandler) { 41 | self.handler = handler 42 | pinger = SimplePing(hostName: hostName) 43 | 44 | // By default we use the first IP address we get back from host resolution (.Any) 45 | // but these flags let the user override that. 46 | 47 | if (forceIPv4 && !forceIPv6) { 48 | pinger?.addressStyle = .icmPv4 49 | } else if (forceIPv6 && !forceIPv4) { 50 | pinger?.addressStyle = .icmPv6 51 | } 52 | 53 | pinger?.delegate = self 54 | pinger?.start() 55 | } 56 | 57 | /// Called by the table view selection delegate callback to stop the ping. 58 | 59 | func stop() { 60 | pinger?.stop() 61 | pinger = nil 62 | 63 | sendTimer?.invalidate() 64 | 65 | handler = nil 66 | } 67 | } 68 | 69 | // MARK: - private utility methods 70 | 71 | private extension SimplePingManager { 72 | /// Sends a ping. 73 | /// 74 | /// Called to send a ping, both directly (as soon as the SimplePing object starts up) and 75 | /// via a timer (to continue sending pings periodically). 76 | 77 | func sendPing() { 78 | pinger!.send(with: nil) 79 | } 80 | 81 | func startTimer() { 82 | sendTimer?.invalidate() 83 | sendTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in 84 | if self == nil { 85 | timer.invalidate() 86 | } else { 87 | self?.sendPing() 88 | } 89 | } 90 | sendTimer?.fire() 91 | } 92 | 93 | /// Returns the string representation of the supplied address. 94 | /// 95 | /// - parameter address: Contains a `(struct sockaddr)` with the address to render. 96 | /// 97 | /// - returns: A string representation of that address. 98 | 99 | func stringRepresentation(forAddress address: Data) -> String { 100 | var hostStr = [Int8](repeating: 0, count: Int(NI_MAXHOST)) 101 | 102 | let result = address.withUnsafeBytes { pointer in 103 | getnameinfo( 104 | pointer.baseAddress?.assumingMemoryBound(to: sockaddr.self), 105 | socklen_t(address.count), 106 | &hostStr, 107 | socklen_t(hostStr.count), 108 | nil, 109 | 0, 110 | NI_NUMERICHOST 111 | ) 112 | } 113 | return result == 0 ? String(cString: hostStr) : "?" 114 | } 115 | 116 | /// Returns a short error string for the supplied error. 117 | /// 118 | /// - parameter error: The error to render. 119 | /// 120 | /// - returns: A short string representing that error. 121 | 122 | func shortErrorFromError(error: Error) -> String { 123 | let error = error as NSError 124 | if error.domain == kCFErrorDomainCFNetwork as String && error.code == Int(CFNetworkErrors.cfHostErrorUnknown.rawValue) { 125 | if let failureObj = error.userInfo[kCFGetAddrInfoFailureKey as String] { 126 | if let failureNum = failureObj as? NSNumber { 127 | if failureNum.intValue != 0 { 128 | if let f = gai_strerror(failureNum.int32Value) { 129 | return String(cString: f) 130 | } 131 | } 132 | } 133 | } 134 | } 135 | if let result = error.localizedFailureReason { 136 | return result 137 | } 138 | return error.localizedDescription 139 | } 140 | } 141 | 142 | // MARK: pinger delegate callback 143 | 144 | extension SimplePingManager: SimplePingDelegate { 145 | func simplePing(_ pinger: SimplePing, didStartWithAddress address: Data) { 146 | handler?(.start(stringRepresentation(forAddress: address))) 147 | startTimer() 148 | } 149 | 150 | func simplePing(_ pinger: SimplePing, didFailWithError error: Error) { 151 | handler?(.failed(error)) 152 | stop() 153 | } 154 | 155 | func simplePing(_ pinger: SimplePing, didSendPacket packet: Data, sequenceNumber: UInt16) { 156 | handler?(.sent(packet, sequenceNumber)) 157 | } 158 | 159 | func simplePing(_ pinger: SimplePing, didFailToSendPacket packet: Data, sequenceNumber: UInt16, error: Error) { 160 | handler?(.sendFailed(packet, sequenceNumber, error)) 161 | } 162 | 163 | func simplePing(_ pinger: SimplePing, didReceivePingResponsePacket packet: Data, sequenceNumber: UInt16) { 164 | handler?(.received(packet, sequenceNumber)) 165 | } 166 | 167 | func simplePing(_ pinger: SimplePing, didReceiveUnexpectedPacket packet: Data) { 168 | handler?(.unexpectedPacket(packet)) 169 | } 170 | } 171 | 172 | @available(iOS 13.0, *) 173 | class SimplePingSubscription: Subscription where SubscriberType.Input == SimplePingResponse, SubscriberType.Failure == Error { 174 | private var subscriber: SubscriberType? 175 | private var manager = SimplePingManager() 176 | 177 | init(subscriber: SubscriberType, hostName: String, forceIPv4: Bool = false, forceIPv6: Bool = false) { 178 | self.subscriber = subscriber 179 | manager.start(hostName: hostName, forceIPv4: forceIPv4, forceIPv6: forceIPv6) { response in 180 | switch response { 181 | case .failed(let error): 182 | subscriber.receive(completion: .failure(error)) 183 | default: 184 | _ = subscriber.receive(response) 185 | } 186 | } 187 | } 188 | 189 | func request(_ demand: Subscribers.Demand) { 190 | // We do nothing here as we only want to send events when they occur. 191 | // See, for more info: https://developer.apple.com/documentation/combine/subscribers/demand 192 | } 193 | 194 | func cancel() { 195 | subscriber = nil 196 | manager.stop() 197 | } 198 | } 199 | 200 | @available(iOS 13.0, *) 201 | struct SimplePingPublisher: Publisher { 202 | typealias Output = SimplePingResponse 203 | typealias Failure = Error 204 | 205 | let hostName: String 206 | let forceIPv4: Bool 207 | let forceIPv6: Bool 208 | 209 | init(hostName: String, forceIPv4: Bool = false, forceIPv6: Bool = false) { 210 | self.hostName = hostName 211 | self.forceIPv4 = forceIPv4 212 | self.forceIPv6 = forceIPv6 213 | } 214 | 215 | func receive(subscriber: S) where S : Subscriber, S.Failure == Failure, S.Input == Output { 216 | let subscription = SimplePingSubscription(subscriber: subscriber, hostName: hostName, forceIPv4: forceIPv4, forceIPv6: forceIPv6) 217 | subscriber.receive(subscription: subscription) 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /iOSApp/iOSApp-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2016 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | A bridging header so we can access SimplePing from Swift. 7 | */ 8 | 9 | #import "SimplePing.h" 10 | --------------------------------------------------------------------------------