diff --git a/RealReachability/Ping/PingFoundation.h b/RealReachability/Ping/PingFoundation.h index 0d0b7a2..f76eab4 100644 --- a/RealReachability/Ping/PingFoundation.h +++ b/RealReachability/Ping/PingFoundation.h @@ -69,6 +69,8 @@ typedef NS_ENUM(NSInteger, PingFoundationAddressStyle) { PingFoundationAddressStyleICMPv6 ///< Use the first IPv6 address found. }; +extern const uint16_t HttpModeSequenceNumber; + @interface PingFoundation : NSObject - (instancetype)init NS_UNAVAILABLE; @@ -79,7 +81,7 @@ typedef NS_ENUM(NSInteger, PingFoundationAddressStyle) { * \returns The initialised object. */ -- (instancetype)initWithHostName:(NSString *)hostName NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithHostName:(NSString *)hostName withHttpMode:(BOOL)httpMode NS_DESIGNATED_INITIALIZER; /*! A copy of the value passed to `-initWithHostName:`. */ @@ -99,6 +101,8 @@ typedef NS_ENUM(NSInteger, PingFoundationAddressStyle) { @property (nonatomic, assign, readwrite) PingFoundationAddressStyle addressStyle; +@property (nonatomic, assign, readwrite) BOOL httpMode; + /*! The address being pinged. * \details The contents of the NSData is a (struct sockaddr) of some form. The * value is nil while the object is stopped and remains nil on start until @@ -128,6 +132,8 @@ typedef NS_ENUM(NSInteger, PingFoundationAddressStyle) { @property (nonatomic, assign, readonly) uint16_t nextSequenceNumber; +- (void)setHttpMode:(BOOL)httpMode; + - (void)start; // Starts the pinger object pinging. You should call this after // you've setup the delegate and any ping parameters. @@ -140,6 +146,8 @@ typedef NS_ENUM(NSInteger, PingFoundationAddressStyle) { // Do not try to send a ping before you receive the -PingFoundation:didStartWithAddress: delegate // callback. +-(void)sendHttpPingWithData:(NSString *)method path:(NSString *)path; + - (void)stop; // Stops the pinger object. You should call this when you're done // pinging. @@ -161,7 +169,7 @@ typedef NS_ENUM(NSInteger, PingFoundationAddressStyle) { * is made, this will have the same value as the `hostAddress` property. */ -- (void)pingFoundation:(PingFoundation *)pinger didStartWithAddress:(NSData *)address; +- (void)pingFoundation:(PingFoundation *)pinger didStartWithAddress:(NSData *)address httpMode:(BOOL)httpMode; /*! A PingFoundation delegate callback, called if the object fails to start up. * \details This is called shortly after you start the object to tell you that the diff --git a/RealReachability/Ping/PingFoundation.m b/RealReachability/Ping/PingFoundation.m index 406fa80..d549b65 100644 --- a/RealReachability/Ping/PingFoundation.m +++ b/RealReachability/Ping/PingFoundation.m @@ -139,6 +139,8 @@ static uint16_t in_cksum(const void *buffer, size_t bufferLen) { #pragma mark * PingFoundation +const uint16_t HttpModeSequenceNumber = 0x8888; + @interface PingFoundation () // read/write versions of public properties @@ -167,7 +169,7 @@ @interface PingFoundation () @implementation PingFoundation -- (instancetype)initWithHostName:(NSString *)hostName +- (instancetype)initWithHostName:(NSString *)hostName withHttpMode:(BOOL)httpMode { if ([hostName length] <= 0) { @@ -177,6 +179,7 @@ - (instancetype)initWithHostName:(NSString *)hostName self = [super init]; if (self != nil) { self->_hostName = [hostName copy]; + self->_httpMode = httpMode; self->_identifier = (uint16_t) arc4random(); } return self; @@ -354,6 +357,71 @@ - (void)sendPingWithData:(NSData *)data { } } +-(void)sendHttpPingWithData:(NSString *)method path:(NSString *)path { + int err; + NSData * packet; + ssize_t bytesSent; + id strongDelegate; + + // data may be nil + + // Construct the ping packet. + + NSMutableString* requestString = [[NSMutableString alloc] init]; + [requestString appendFormat:@"%@ %@ HTTP/1.1\r\n", [method uppercaseString], path]; + [requestString appendFormat:@"HOST:%@\r\n", _hostName]; + [requestString appendFormat:@"User-Agent:RealReachability\r\n"]; + [requestString appendFormat:@"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n\r\n"]; + packet = [requestString dataUsingEncoding:NSUTF8StringEncoding]; + + // Send the packet. + + if (self.socket == NULL) { + bytesSent = -1; + err = EBADF; + } else { + bytesSent = send( + CFSocketGetNative(self.socket), + packet.bytes, + packet.length, + 0 + ); + 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(pingFoundation:didSendPacket:sequenceNumber:)] ) { + [strongDelegate pingFoundation: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(pingFoundation:didFailToSendPacket:sequenceNumber:error:)] ) { + [strongDelegate pingFoundation: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. @@ -517,6 +585,18 @@ - (BOOL)validatePingResponsePacket:(NSMutableData *)packet sequenceNumber:(uint1 return result; } +- (void)connect:(BOOL)success { + if (success) { + // Done connected, we can notify upper layer to send data. + id strongDelegate = self.delegate; + if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(pingFoundation:didStartWithAddress:httpMode:)] ) { + [strongDelegate pingFoundation:self didStartWithAddress:self.hostAddress httpMode:self.httpMode]; + } + } else { + [self didFailWithError:[NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFURLErrorCannotConnectToHost userInfo:nil]]; + } +} + /*! Reads data from the ICMP socket. * \details Called by the socket handling code (SocketReadCallback) to process an ICMP * message waiting on the socket. @@ -553,21 +633,44 @@ - (void)readData { // Process the data we read. if (bytesRead > 0) { - NSMutableData * packet; id strongDelegate; - uint16_t sequenceNumber; - - packet = [NSMutableData dataWithBytes:buffer length:(NSUInteger) bytesRead]; - // 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(pingFoundation:didReceivePingResponsePacket:sequenceNumber:)] ) { - [strongDelegate pingFoundation:self didReceivePingResponsePacket:packet sequenceNumber:sequenceNumber]; + + if (_httpMode) { + char previous = (char)0; + for (int i = 0; i < bytesRead; i++) { + if (previous == '\r' && ((char *)buffer)[i] == '\n') { + // Valid split, try to parse first segment. + NSString* httpStatus = [[NSString alloc]initWithBytes:buffer length:i-1 encoding:kCFStringEncodingUTF8]; + if ([httpStatus hasPrefix:@"HTTP/"]) { + if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(pingFoundation:didReceivePingResponsePacket:sequenceNumber:)] ) { + [strongDelegate pingFoundation:self didReceivePingResponsePacket:nil sequenceNumber:HttpModeSequenceNumber]; + } + } else { + if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(pingFoundation:didReceiveUnexpectedPacket:)] ) { + [strongDelegate pingFoundation:self didReceiveUnexpectedPacket:nil]; + } + } + break; + } + previous = ((char *)buffer)[i]; } } else { - if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(pingFoundation:didReceiveUnexpectedPacket:)] ) { - [strongDelegate pingFoundation:self didReceiveUnexpectedPacket:packet]; + uint16_t sequenceNumber; + NSMutableData * packet; + + packet = [NSMutableData dataWithBytes:buffer length:(NSUInteger) bytesRead]; + // We got some data, pass it up to our client. + + if ( [self validatePingResponsePacket:packet sequenceNumber:&sequenceNumber] ) { + if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(pingFoundation:didReceivePingResponsePacket:sequenceNumber:)] ) { + [strongDelegate pingFoundation:self didReceivePingResponsePacket:packet sequenceNumber:sequenceNumber]; + } + } else { + if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(pingFoundation:didReceiveUnexpectedPacket:)] ) { + [strongDelegate pingFoundation:self didReceiveUnexpectedPacket:packet]; + } } } } else { @@ -586,6 +689,20 @@ - (void)readData { // let CFSocket call us again. } +static void SocketCallback(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. + PingFoundation * obj; + + obj = (__bridge PingFoundation *) info; + + if (type == kCFSocketConnectCallBack) { + [obj connect:data == NULL ? YES : NO]; + } else if (type == kCFSocketReadCallBack) { + [obj readData]; + } +} + /*! The callback for our CFSocket object. * \details This simply routes the call to our `-readData` method. * \param s See the documentation for CFSocketCallBack. @@ -629,13 +746,21 @@ - (void)startWithHostAddress switch (self.hostAddressFamily) { // gzw here to decide what to use! see what is going on in iOS12! TODO: case AF_INET: { - fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); + if (_httpMode) { + fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + } else { + 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 (_httpMode) { + fd = socket(AF_INET6, SOCK_STREAM,IPPROTO_IPV6); + } else { + fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); + } if (fd < 0) { err = errno; } @@ -652,10 +777,19 @@ - (void)startWithHostAddress CFRunLoopSourceRef rls; id strongDelegate; - // Wrap it in a CFSocket and schedule it on the runloop. - - self.socket = (CFSocketRef) CFAutorelease( CFSocketCreateWithNative(NULL, fd, kCFSocketReadCallBack, SocketReadCallback, &context) ); - + if (_httpMode) { + // Wrap it in a CFSocket and schedule it on the runloop. + self.socket = (CFSocketRef) CFAutorelease( CFSocketCreateWithNative(NULL, fd, kCFSocketConnectCallBack | kCFSocketReadCallBack, SocketCallback, &context) ); + + CFDataRef targetAddrRef = CFDataCreate(kCFAllocatorDefault, self.hostAddress.bytes, (socklen_t) self.hostAddress.length); + // ----连接 + CFSocketConnectToAddress(self.socket, targetAddrRef, -1); + // Release + CFRelease(targetAddrRef); + } else { + // Wrap it in a CFSocket and schedule it on the runloop. + self.socket = (CFSocketRef) CFAutorelease( CFSocketCreateWithNative(NULL, fd, kCFSocketReadCallBack, SocketReadCallback, &context) ); + } // The socket will now take care of cleaning up our file descriptor. fd = -1; @@ -666,9 +800,11 @@ - (void)startWithHostAddress CFRelease(rls); - strongDelegate = self.delegate; - if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(pingFoundation:didStartWithAddress:)] ) { - [strongDelegate pingFoundation:self didStartWithAddress:self.hostAddress]; + if (!_httpMode) { + strongDelegate = self.delegate; + if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(pingFoundation:didStartWithAddress:httpMode:)] ) { + [strongDelegate pingFoundation:self didStartWithAddress:self.hostAddress httpMode:self.httpMode]; + } } } } @@ -696,13 +832,27 @@ - (void)hostResolutionDone { switch (addrPtr->sa_family) { case AF_INET: { if (self.addressStyle != PingFoundationAddressStyleICMPv6) { - self.hostAddress = address; + if (_httpMode) { + NSMutableData* mutableAddress = [NSMutableData dataWithBytes:address.bytes length:address.length]; + struct sockaddr_in* addrInPtr = (struct sockaddr_in*) mutableAddress.mutableBytes; + addrInPtr->sin_port = htons(80); + self.hostAddress = mutableAddress; + } else { + self.hostAddress = address; + } resolved = true; } } break; case AF_INET6: { if (self.addressStyle != PingFoundationAddressStyleICMPv4) { - self.hostAddress = address; + if (_httpMode) { + NSMutableData* mutableAddress = [NSMutableData dataWithBytes:address.bytes length:address.length]; + struct sockaddr_in6* addrInPtr = (struct sockaddr_in6*) mutableAddress.mutableBytes; + addrInPtr->sin6_port = htons(80); + self.hostAddress = mutableAddress; + } else { + self.hostAddress = address; + } resolved = true; } } break; diff --git a/RealReachability/Ping/PingHelper.h b/RealReachability/Ping/PingHelper.h index ab0fc79..11cb012 100755 --- a/RealReachability/Ping/PingHelper.h +++ b/RealReachability/Ping/PingHelper.h @@ -20,6 +20,8 @@ /// Ping timeout. Default is 2 seconds @property (nonatomic, assign) NSTimeInterval timeout; +@property (nonatomic, assign) BOOL httpMode; + /** * trigger a ping action with a completion block * diff --git a/RealReachability/Ping/PingHelper.m b/RealReachability/Ping/PingHelper.m index 5ed1710..349a88f 100755 --- a/RealReachability/Ping/PingHelper.m +++ b/RealReachability/Ping/PingHelper.m @@ -94,7 +94,7 @@ - (void)startPing self.isPinging = YES; - self.pingFoundation = [[PingFoundation alloc] initWithHostName:self.host]; + self.pingFoundation = [[PingFoundation alloc] initWithHostName:self.host withHttpMode:self.httpMode]; self.pingFoundation.delegate = self; [self.pingFoundation start]; @@ -109,7 +109,7 @@ - (void)setHost:(NSString *)host self.pingFoundation.delegate = nil; self.pingFoundation = nil; - self.pingFoundation = [[PingFoundation alloc] initWithHostName:_host]; + self.pingFoundation = [[PingFoundation alloc] initWithHostName:_host withHttpMode:self.httpMode]; self.pingFoundation.delegate = self; } @@ -122,7 +122,7 @@ - (void)doubleCheck self.isPinging = YES; - self.pingFoundation = [[PingFoundation alloc] initWithHostName:self.hostForCheck]; + self.pingFoundation = [[PingFoundation alloc] initWithHostName:self.hostForCheck withHttpMode:self.httpMode]; self.pingFoundation.delegate = self; [self.pingFoundation start]; @@ -153,10 +153,14 @@ - (void)endWithFlag:(BOOL)isSuccess #pragma mark - PingFoundation delegate // When the pinger starts, send the ping immediately -- (void)pingFoundation:(PingFoundation *)pinger didStartWithAddress:(NSData *)address +- (void)pingFoundation:(PingFoundation *)pinger didStartWithAddress:(NSData *)address httpMode:(BOOL)httpMode { //NSLog(@"didStartWithAddress"); - [self.pingFoundation sendPingWithData:nil]; + if (httpMode) { + [self.pingFoundation sendHttpPingWithData:@"HEAD" path:@"/"]; + } else { + [self.pingFoundation sendPingWithData:nil]; + } } - (void)pingFoundation:(PingFoundation *)pinger didFailWithError:(NSError *)error diff --git a/RealReachability/RealReachability.h b/RealReachability/RealReachability.h index ed0d955..5ea2a43 100755 --- a/RealReachability/RealReachability.h +++ b/RealReachability/RealReachability.h @@ -84,6 +84,9 @@ typedef NS_ENUM(NSInteger, WWANAccessType) { // Timeout used for ping. Default is 2 seconds @property (nonatomic, assign) NSTimeInterval pingTimeout; +// Timeout used for check. Default is 5 seconds +@property (nonatomic, assign) NSTimeInterval checkTimeout; + + (instancetype)sharedInstance; - (void)startNotifier; diff --git a/RealReachability/RealReachability.m b/RealReachability/RealReachability.m index 3fc5650..a54cba4 100755 --- a/RealReachability/RealReachability.m +++ b/RealReachability/RealReachability.m @@ -22,6 +22,8 @@ #define kDefaultCheckInterval 2.0f #define kDefaultPingTimeout 2.0f +#define kDefaultCheckTimeout 5.0f + #define kMinAutoCheckInterval 0.3f #define kMaxAutoCheckInterval 60.0f @@ -80,6 +82,7 @@ - (id)init _hostForCheck = kDefaultHost; _autoCheckInterval = kDefaultCheckInterval; _pingTimeout = kDefaultPingTimeout; + _checkTimeout = kDefaultCheckTimeout; _vpnFlag = NO; @@ -158,7 +161,10 @@ - (void)startNotifier self.pingHelper.timeout = self.pingTimeout; self.pingChecker.host = _hostForCheck; - self.pingChecker.timeout = self.pingTimeout; + self.pingChecker.timeout = self.checkTimeout; + + // 默认DoubleCheck的时候开启Http模式。 + [self.pingChecker setHttpMode:TRUE]; [self autoCheckReachability]; } @@ -315,11 +321,12 @@ - (void)setHostForCheck:(NSString *)hostForCheck self.pingChecker.host = _hostForCheck; } -- (void)setPingTimeout:(NSTimeInterval)pingTimeout +- (void)setPingTimeout:(NSTimeInterval)pingTimeout withCheckTimeout:(NSTimeInterval)checkTimeout { _pingTimeout = pingTimeout; + _checkTimeout = checkTimeout; self.pingHelper.timeout = pingTimeout; - self.pingChecker.timeout = pingTimeout; + self.pingChecker.timeout = checkTimeout; } - (WWANAccessType)currentWWANtype