Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use HTTP HEAD for reachability check to work fine under some strange … #119

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions RealReachability/Ping/PingFoundation.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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:`.
*/
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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
Expand Down
194 changes: 172 additions & 22 deletions RealReachability/Ping/PingFoundation.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -167,7 +169,7 @@ @interface PingFoundation ()

@implementation PingFoundation

- (instancetype)initWithHostName:(NSString *)hostName
- (instancetype)initWithHostName:(NSString *)hostName withHttpMode:(BOOL)httpMode
{
if ([hostName length] <= 0)
{
Expand All @@ -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;
Expand Down Expand Up @@ -354,6 +357,71 @@ - (void)sendPingWithData:(NSData *)data {
}
}

-(void)sendHttpPingWithData:(NSString *)method path:(NSString *)path {
int err;
NSData * packet;
ssize_t bytesSent;
id<PingFoundationDelegate> 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.
Expand Down Expand Up @@ -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<PingFoundationDelegate> 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.
Expand Down Expand Up @@ -553,21 +633,44 @@ - (void)readData {
// Process the data we read.

if (bytesRead > 0) {
NSMutableData * packet;
id<PingFoundationDelegate> 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 {
Expand All @@ -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.
Expand Down Expand Up @@ -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;
}
Expand All @@ -652,10 +777,19 @@ - (void)startWithHostAddress
CFRunLoopSourceRef rls;
id<PingFoundationDelegate> 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;
Expand All @@ -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];
}
}
}
}
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions RealReachability/Ping/PingHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
14 changes: 9 additions & 5 deletions RealReachability/Ping/PingHelper.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand All @@ -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;
}
Expand All @@ -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];

Expand Down Expand Up @@ -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
Expand Down
Loading