268 lines
7.6 KiB
Objective-C
268 lines
7.6 KiB
Objective-C
//
|
|
// PingManger.m
|
|
// MacTool
|
|
//
|
|
// Created by Rudy Yang on 2017/9/29.
|
|
//
|
|
|
|
#import "PingManager.h"
|
|
#import "SimplePing.h"
|
|
|
|
#include <sys/socket.h>
|
|
#include <netdb.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#pragma mark * Utilities
|
|
|
|
/*! Returns the string representation of the supplied address.
|
|
* \param address Contains a (struct sockaddr) with the address to render.
|
|
* \returns A string representation of that address.
|
|
*/
|
|
|
|
static NSString * displayAddressForAddress(NSData * address) {
|
|
int err;
|
|
NSString * result;
|
|
char hostStr[NI_MAXHOST];
|
|
|
|
result = nil;
|
|
|
|
if (address != nil) {
|
|
err = getnameinfo(address.bytes, (socklen_t) address.length, hostStr, sizeof(hostStr), NULL, 0, NI_NUMERICHOST);
|
|
if (err == 0) {
|
|
result = @(hostStr);
|
|
}
|
|
}
|
|
|
|
if (result == nil) {
|
|
result = @"?";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*! Returns a short error string for the supplied error.
|
|
* \param error The error to render.
|
|
* \returns A short string representing that error.
|
|
*/
|
|
|
|
static NSString * shortErrorFromError(NSError * error) {
|
|
NSString * result;
|
|
NSNumber * failureNum;
|
|
int failure;
|
|
const char * failureStr;
|
|
|
|
assert(error != nil);
|
|
|
|
result = nil;
|
|
|
|
// Handle DNS errors as a special case.
|
|
|
|
if ( [error.domain isEqual:(NSString *)kCFErrorDomainCFNetwork] && (error.code == kCFHostErrorUnknown) ) {
|
|
failureNum = error.userInfo[(id) kCFGetAddrInfoFailureKey];
|
|
if ( [failureNum isKindOfClass:[NSNumber class]] ) {
|
|
failure = failureNum.intValue;
|
|
if (failure != 0) {
|
|
failureStr = gai_strerror(failure);
|
|
if (failureStr != NULL) {
|
|
result = @(failureStr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise try various properties of the error object.
|
|
|
|
if (result == nil) {
|
|
result = error.localizedFailureReason;
|
|
}
|
|
if (result == nil) {
|
|
result = error.localizedDescription;
|
|
}
|
|
assert(result != nil);
|
|
return result;
|
|
}
|
|
|
|
|
|
@interface PingManager() <SimplePingDelegate>
|
|
|
|
@property (nonatomic, assign, readwrite) BOOL forceIPv4;
|
|
@property (nonatomic, assign, readwrite) BOOL forceIPv6;
|
|
@property (nonatomic, strong, readwrite, nullable) SimplePing * pinger;
|
|
@property (nonatomic, strong, readwrite, nullable) NSTimer * sendTimer;
|
|
|
|
@property (nonatomic, assign) NSTimeInterval ping;
|
|
|
|
@property (nonatomic, strong) NSDate *beginDate;
|
|
|
|
@property (strong, nonatomic) NSMutableDictionary *PingTimers;
|
|
|
|
@property (copy, nonatomic) void(^success)(NSInteger msCount);
|
|
@property (copy, nonatomic) void(^failure)();
|
|
|
|
@end
|
|
|
|
@implementation PingManager
|
|
|
|
- (void)dealloc {
|
|
[self->_pinger stop];
|
|
[self->_sendTimer invalidate];
|
|
}
|
|
|
|
- (BOOL)isValidIpAddress:(NSString *)ip {
|
|
const char *utf8 = [ip UTF8String];
|
|
|
|
// Check valid IPv4.
|
|
struct in_addr dst;
|
|
int success = inet_pton(AF_INET, utf8, &(dst.s_addr));
|
|
if (success != 1) {
|
|
// Check valid IPv6.
|
|
struct in6_addr dst6;
|
|
success = inet_pton(AF_INET6, utf8, &dst6);
|
|
}
|
|
return (success == 1);
|
|
}
|
|
|
|
- (void)pingHost:(NSString *)host success:(void(^)(NSInteger msCount))success failure:(void(^)())failure {
|
|
|
|
const char *utf8 = [host UTF8String];
|
|
struct in_addr dst;
|
|
int isIPv4 = inet_pton(AF_INET, utf8, &(dst.s_addr));
|
|
|
|
struct in6_addr dst6;
|
|
int isIPv6 = inet_pton(AF_INET6, utf8, &dst6);
|
|
self.forceIPv6 = NO;
|
|
self.forceIPv4 = NO;
|
|
if (isIPv4 == 1) {
|
|
self.forceIPv4 = YES;
|
|
} else if (isIPv6 == 1) {
|
|
self.forceIPv6 = YES;
|
|
} else {
|
|
self.forceIPv4 = YES;
|
|
}
|
|
|
|
self.success = success;
|
|
self.failure = failure;
|
|
[self runWithHostName:host];
|
|
}
|
|
|
|
- (void)close {
|
|
[self.sendTimer invalidate];
|
|
self.sendTimer = nil;
|
|
self.pinger = nil;
|
|
}
|
|
/*! The Objective-C 'main' for this program.
|
|
* \details This creates a SimplePing object, configures it, and then runs the run loop
|
|
* sending pings and printing the results.
|
|
* \param hostName The host to ping.
|
|
*/
|
|
|
|
- (void)runWithHostName:(NSString *)hostName {
|
|
assert(self.pinger == nil);
|
|
|
|
self.pinger = [[SimplePing alloc] initWithHostName:hostName];
|
|
assert(self.pinger != nil);
|
|
|
|
// By default we use the first IP address we get back from host resolution (.Any)
|
|
// but these flags let the user override that.
|
|
|
|
if (self.forceIPv4 && ! self.forceIPv6) {
|
|
self.pinger.addressStyle = SimplePingAddressStyleICMPv4;
|
|
} else if (self.forceIPv6 && ! self.forceIPv4) {
|
|
self.pinger.addressStyle = SimplePingAddressStyleICMPv6;
|
|
}
|
|
|
|
self.pinger.delegate = self;
|
|
[self.pinger start];
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
if (self.pinger != nil) {
|
|
[self close];
|
|
if (self.success) {
|
|
self.success(-1);
|
|
}
|
|
}
|
|
});
|
|
do {
|
|
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
|
|
} while (self.pinger != nil);
|
|
}
|
|
|
|
/*! Sends a ping.
|
|
* \details Called to send a ping, both directly (as soon as the SimplePing object starts up)
|
|
* and via a timer (to continue sending pings periodically).
|
|
*/
|
|
|
|
- (void)sendPing {
|
|
assert(self.pinger != nil);
|
|
[self.pinger sendPingWithData:nil];
|
|
}
|
|
|
|
- (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address {
|
|
#pragma unused(pinger)
|
|
assert(pinger == self.pinger);
|
|
assert(address != nil);
|
|
self.beginDate = [NSDate date];
|
|
NSLog(@"pinging %@", displayAddressForAddress(address));
|
|
|
|
// Send the first ping straight away.
|
|
|
|
[self sendPing];
|
|
|
|
// And start a timer to send the subsequent pings.
|
|
|
|
assert(self.sendTimer == nil);
|
|
self.sendTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(sendPing) userInfo:nil repeats:YES];
|
|
}
|
|
|
|
- (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error {
|
|
#pragma unused(pinger)
|
|
assert(pinger == self.pinger);
|
|
NSLog(@"failed: %@", shortErrorFromError(error));
|
|
[self close];
|
|
if (self.failure) {
|
|
self.failure();
|
|
}
|
|
}
|
|
|
|
- (void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber {
|
|
#pragma unused(pinger)
|
|
assert(pinger == self.pinger);
|
|
#pragma unused(packet)
|
|
NSLog(@"#%u sent", (unsigned int) sequenceNumber);
|
|
}
|
|
|
|
- (void)simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber error:(NSError *)error {
|
|
#pragma unused(pinger)
|
|
assert(pinger == self.pinger);
|
|
#pragma unused(packet)
|
|
NSLog(@"#%u send failed: %@", (unsigned int) sequenceNumber, shortErrorFromError(error));
|
|
[self close];
|
|
if (self.failure) {
|
|
self.failure();
|
|
}
|
|
}
|
|
|
|
- (void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber {
|
|
#pragma unused(pinger)
|
|
assert(pinger == self.pinger);
|
|
#pragma unused(packet)
|
|
self.ping = [[NSDate date] timeIntervalSinceDate:self.beginDate] * 1000;
|
|
// NSLog(@"ping = %@", @(_ping));
|
|
NSLog(@"#%u received, size=%zu", (unsigned int) sequenceNumber, (size_t) packet.length);
|
|
[self close];
|
|
if (self.success) {
|
|
self.success(self.ping);
|
|
}
|
|
}
|
|
|
|
- (void)simplePing:(SimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet {
|
|
#pragma unused(pinger)
|
|
assert(pinger == self.pinger);
|
|
[self close];
|
|
NSLog(@"unexpected packet, size=%zu", (size_t) packet.length);
|
|
if (self.success) {
|
|
self.success(0);
|
|
}
|
|
}
|
|
|
|
@end
|