mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-02 02:43:07 +00:00
no message
This commit is contained in:
parent
5db24cb305
commit
2acc16f46d
11
MTConnectionProbing.h
Normal file
11
MTConnectionProbing.h
Normal file
@ -0,0 +1,11 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class MTSignal;
|
||||
@class MTContext;
|
||||
@class MTSocksProxySettings;
|
||||
|
||||
@interface MTConnectionProbing : NSObject
|
||||
|
||||
+ (MTSignal *)probeProxyWithContext:(MTContext *)context datacenterId:(NSInteger)datacenterId settings:(MTSocksProxySettings *)settings;
|
||||
|
||||
@end
|
142
MTConnectionProbing.m
Normal file
142
MTConnectionProbing.m
Normal file
@ -0,0 +1,142 @@
|
||||
#import "MTConnectionProbing.h"
|
||||
|
||||
#if defined(MtProtoKitDynamicFramework)
|
||||
# import <MTProtoKitDynamic/MTSignal.h>
|
||||
# import <MTProtoKitDynamic/MTQueue.h>
|
||||
# import <MTProtoKitDynamic/MTAtomic.h>
|
||||
# import <MTProtoKitDynamic/MTHttpRequestOperation.h>
|
||||
# import <MTProtoKitDynamic/MTEncryption.h>
|
||||
# import <MTProtoKitDynamic/MTRequestMessageService.h>
|
||||
# import <MTProtoKitDynamic/MTRequest.h>
|
||||
# import <MTProtoKitDynamic/MTContext.h>
|
||||
# import <MTProtoKitDynamic/MTApiEnvironment.h>
|
||||
# import <MTProtoKitDynamic/MTDatacenterAddress.h>
|
||||
# import <MTProtoKitDynamic/MTDatacenterAddressSet.h>
|
||||
# import <MTProtoKitDynamic/MTProto.h>
|
||||
# import <MTProtoKitDynamic/MTSerialization.h>
|
||||
# import <MTProtoKitDynamic/MTLogging.h>
|
||||
# import <MTProtoKitDynamic/MTProxyConnectivity.h>
|
||||
#elif defined(MtProtoKitMacFramework)
|
||||
# import <MTProtoKitMac/MTSignal.h>
|
||||
# import <MTProtoKitMac/MTQueue.h>
|
||||
# import <MTProtoKitMac/MTAtomic.h>
|
||||
# import <MTProtoKitMac/MTHttpRequestOperation.h>
|
||||
# import <MTProtoKitMac/MTEncryption.h>
|
||||
# import <MTProtoKitMac/MTRequestMessageService.h>
|
||||
# import <MTProtoKitMac/MTRequest.h>
|
||||
# import <MTProtoKitMac/MTContext.h>
|
||||
# import <MTProtoKitMac/MTApiEnvironment.h>
|
||||
# import <MTProtoKitMac/MTDatacenterAddress.h>
|
||||
# import <MTProtoKitMac/MTDatacenterAddressSet.h>
|
||||
# import <MTProtoKitMac/MTProto.h>
|
||||
# import <MTProtoKitMac/MTSerialization.h>
|
||||
# import <MTProtoKitMac/MTLogging.h>
|
||||
# import <MTProtoKitMac/MTProxyConnectivity.h>
|
||||
#else
|
||||
# import <MTProtoKit/MTSignal.h>
|
||||
# import <MTProtoKit/MTQueue.h>
|
||||
# import <MTProtoKit/MTAtomic.h>
|
||||
# import <MTProtoKit/MTHttpRequestOperation.h>
|
||||
# import <MTProtoKit/MTEncryption.h>
|
||||
# import <MTProtoKit/MTRequestMessageService.h>
|
||||
# import <MTProtoKit/MTRequest.h>
|
||||
# import <MTProtoKit/MTContext.h>
|
||||
# import <MTProtoKit/MTApiEnvironment.h>
|
||||
# import <MTProtoKit/MTDatacenterAddress.h>
|
||||
# import <MTProtoKit/MTDatacenterAddressSet.h>
|
||||
# import <MTProtoKit/MTProto.h>
|
||||
# import <MTProtoKit/MTSerialization.h>
|
||||
# import <MTProtoKit/MTLogging.h>
|
||||
# import <MTProtoKit/MTProxyConnectivity.h>
|
||||
#endif
|
||||
|
||||
#import "PingFoundation.h"
|
||||
|
||||
@interface MTPingHelper : NSObject <PingFoundationDelegate> {
|
||||
void (^_success)();
|
||||
PingFoundation *_ping;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MTPingHelper
|
||||
|
||||
- (instancetype)initWithSuccess:(void (^)())success {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_success = [success copy];
|
||||
|
||||
NSArray *hosts = @[
|
||||
@"google.com",
|
||||
@"8.8.8.8"
|
||||
];
|
||||
|
||||
NSString *host = hosts[(int)(arc4random_uniform((uint32_t)hosts.count))];
|
||||
|
||||
_ping = [[PingFoundation alloc] initWithHostName:host];
|
||||
_ping.delegate = self;
|
||||
[_ping start];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
_ping.delegate = nil;
|
||||
[_ping stop];
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
}
|
||||
|
||||
- (void)pingFoundation:(PingFoundation *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber {
|
||||
if (_success) {
|
||||
_success();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pingFoundation:(PingFoundation *)pinger didStartWithAddress:(NSData *)__unused address {
|
||||
[pinger sendPingWithData:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MTConnectionProbing
|
||||
|
||||
+ (MTSignal *)pingAddress {
|
||||
return [[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber) {
|
||||
MTQueue *queue = [MTQueue mainQueue];
|
||||
MTMetaDisposable *disposable = [[MTMetaDisposable alloc] init];
|
||||
|
||||
[queue dispatchOnQueue:^{
|
||||
MTPingHelper *helper = [[MTPingHelper alloc] initWithSuccess:^{
|
||||
[subscriber putNext:@true];
|
||||
[subscriber putCompletion];
|
||||
}];
|
||||
|
||||
[disposable setDisposable:[[MTBlockDisposable alloc] initWithBlock:^{
|
||||
[helper stop];
|
||||
}]];
|
||||
}];
|
||||
|
||||
return disposable;
|
||||
}];
|
||||
}
|
||||
|
||||
+ (MTSignal *)probeProxyWithContext:(MTContext *)context datacenterId:(NSInteger)datacenterId settings:(MTSocksProxySettings *)settings {
|
||||
MTSignal *proxyAvailable = [[[MTProxyConnectivity pingProxyWithContext:context datacenterId:datacenterId settings:settings] map:^id(MTProxyConnectivityStatus *status) {
|
||||
return @(status.reachable);
|
||||
}] timeout:10.0 onQueue:[MTQueue concurrentDefaultQueue] orSignal:[MTSignal single:@false]];
|
||||
MTSignal *referenceAvailable = [[self pingAddress] timeout:10.0 onQueue:[MTQueue concurrentDefaultQueue] orSignal:[MTSignal single:@false]];
|
||||
MTSignal *combined = [[MTSignal combineSignals:@[proxyAvailable, referenceAvailable]] map:^id(NSArray *values) {
|
||||
NSNumber *proxy = values[0];
|
||||
NSNumber *ping = values[1];
|
||||
if (![proxy boolValue] && [ping boolValue]) {
|
||||
return @true;
|
||||
} else {
|
||||
return @false;
|
||||
}
|
||||
}];
|
||||
return combined;
|
||||
}
|
||||
|
||||
@end
|
@ -150,8 +150,8 @@
|
||||
id<MTTransportDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(transportNetworkAvailabilityChanged:isNetworkAvailable:)])
|
||||
[delegate transportNetworkAvailabilityChanged:self isNetworkAvailable:_isNetworkAvailable];
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionStateChanged:isConnected:proxyAddress:)])
|
||||
[delegate transportConnectionStateChanged:self isConnected:_isConnected proxyAddress:nil];
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionStateChanged:isConnected:proxySettings:)])
|
||||
[delegate transportConnectionStateChanged:self isConnected:_isConnected proxySettings:nil];
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionContextUpdateStateChanged:isUpdatingConnectionContext:)])
|
||||
[delegate transportConnectionContextUpdateStateChanged:self isUpdatingConnectionContext:_currentActualizationPingId != 0];
|
||||
}];
|
||||
@ -199,8 +199,8 @@
|
||||
_isConnected = false;
|
||||
|
||||
id<MTTransportDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionStateChanged:isConnected:proxyAddress:)])
|
||||
[delegate transportConnectionStateChanged:self isConnected:_isConnected proxyAddress:nil];
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionStateChanged:isConnected:proxySettings:)])
|
||||
[delegate transportConnectionStateChanged:self isConnected:_isConnected proxySettings:nil];
|
||||
}
|
||||
}];
|
||||
}
|
||||
@ -281,8 +281,8 @@
|
||||
_isConnected = true;
|
||||
|
||||
id<MTTransportDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionStateChanged:isConnected:proxyAddress:)])
|
||||
[delegate transportConnectionStateChanged:self isConnected:_isConnected proxyAddress:nil];
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionStateChanged:isConnected:proxySettings:)])
|
||||
[delegate transportConnectionStateChanged:self isConnected:_isConnected proxySettings:nil];
|
||||
}
|
||||
|
||||
[self stopConnectionWatchdogTimer];
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
@property (nonatomic, readonly) bool isConnected;
|
||||
@property (nonatomic, readonly) NSString *proxyAddress;
|
||||
@property (nonatomic, readonly) bool proxyHasConnectionIssues;
|
||||
|
||||
@end
|
||||
|
||||
@ -49,6 +50,7 @@
|
||||
@property (nonatomic) bool media;
|
||||
@property (nonatomic) bool enforceMedia;
|
||||
@property (nonatomic) bool cdn;
|
||||
@property (nonatomic) bool checkForProxyConnectionIssues;
|
||||
@property (nonatomic) id requiredAuthToken;
|
||||
@property (nonatomic) NSInteger authTokenMasterDatacenterId;
|
||||
|
||||
|
@ -57,10 +57,23 @@
|
||||
#import "MTRpcResultMessage.h"
|
||||
#import "MTRpcError.h"
|
||||
|
||||
#import "MTConnectionProbing.h"
|
||||
|
||||
#import "MTApiEnvironment.h"
|
||||
|
||||
#import "MTTime.h"
|
||||
|
||||
#if defined(MtProtoKitDynamicFramework)
|
||||
# import <MTProtoKitDynamic/MTSignal.h>
|
||||
# import <MTProtoKitDynamic/MTQueue.h>
|
||||
#elif defined(MtProtoKitMacFramework)
|
||||
# import <MTProtoKitMac/MTSignal.h>
|
||||
# import <MTProtoKitMac/MTQueue.h>
|
||||
#else
|
||||
# import <MTProtoKit/MTSignal.h>
|
||||
# import <MTProtoKit/MTQueue.h>
|
||||
#endif
|
||||
|
||||
#define MTProtoV2 1
|
||||
|
||||
typedef enum {
|
||||
@ -81,11 +94,12 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64;
|
||||
|
||||
@implementation MTProtoConnectionState
|
||||
|
||||
- (instancetype)initWithIsConnected:(bool)isConnected proxyAddress:(NSString *)proxyAddress {
|
||||
- (instancetype)initWithIsConnected:(bool)isConnected proxyAddress:(NSString *)proxyAddress proxyHasConnectionIssues:(bool)proxyHasConnectionIssues {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_isConnected = isConnected;
|
||||
_proxyAddress = proxyAddress;
|
||||
_proxyHasConnectionIssues = proxyHasConnectionIssues;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -111,6 +125,12 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64;
|
||||
bool _willRequestTransactionOnNextQueuePass;
|
||||
|
||||
MTNetworkUsageCalculationInfo *_usageCalculationInfo;
|
||||
|
||||
MTProtoConnectionState *_connectionState;
|
||||
|
||||
bool _isProbing;
|
||||
MTMetaDisposable *_probingDisposable;
|
||||
NSNumber *_probingStatus;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -161,10 +181,12 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64;
|
||||
MTTransport *transport = _transport;
|
||||
_transport.delegate = nil;
|
||||
_transport = nil;
|
||||
id<MTDisposable> probingDisposable = _probingDisposable;
|
||||
|
||||
[[MTProto managerQueue] dispatchOnQueue:^
|
||||
{
|
||||
[transport stop];
|
||||
[probingDisposable dispose];
|
||||
}];
|
||||
}
|
||||
|
||||
@ -240,7 +262,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64;
|
||||
id<MTProtoDelegate> delegate = _delegate;
|
||||
if ([delegate respondsToSelector:@selector(mtProtoNetworkAvailabilityChanged:isNetworkAvailable:)])
|
||||
[delegate mtProtoNetworkAvailabilityChanged:self isNetworkAvailable:false];
|
||||
if ([delegate respondsToSelector:@selector(mtProtoConnectionStateChanged:isConnected:)])
|
||||
if ([delegate respondsToSelector:@selector(mtProtoConnectionStateChanged:state:)])
|
||||
[delegate mtProtoConnectionStateChanged:self state:nil];
|
||||
if ([delegate respondsToSelector:@selector(mtProtoConnectionContextUpdateStateChanged:isUpdatingConnectionContext:)])
|
||||
[delegate mtProtoConnectionContextUpdateStateChanged:self isUpdatingConnectionContext:false];
|
||||
@ -734,7 +756,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)transportConnectionStateChanged:(MTTransport *)transport isConnected:(bool)isConnected proxyAddress:(NSString *)proxyAddress
|
||||
- (void)transportConnectionStateChanged:(MTTransport *)transport isConnected:(bool)isConnected proxySettings:(MTSocksProxySettings *)proxySettings
|
||||
{
|
||||
[[MTProto managerQueue] dispatchOnQueue:^
|
||||
{
|
||||
@ -751,9 +773,50 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64;
|
||||
[messageService mtProtoConnectionStateChanged:self isConnected:isConnected];
|
||||
}
|
||||
|
||||
if (isConnected || proxySettings == nil) {
|
||||
_probingStatus = nil;
|
||||
}
|
||||
|
||||
MTProtoConnectionState *connectionState = [[MTProtoConnectionState alloc] initWithIsConnected:isConnected proxyAddress:proxySettings.ip proxyHasConnectionIssues:[_probingStatus boolValue]];
|
||||
_connectionState = connectionState;
|
||||
|
||||
id<MTProtoDelegate> delegate = _delegate;
|
||||
if ([delegate respondsToSelector:@selector(mtProtoConnectionStateChanged:state:)])
|
||||
[delegate mtProtoConnectionStateChanged:self state:[[MTProtoConnectionState alloc] initWithIsConnected:isConnected proxyAddress:proxyAddress]];
|
||||
if ([delegate respondsToSelector:@selector(mtProtoConnectionStateChanged:state:)]) {
|
||||
[delegate mtProtoConnectionStateChanged:self state:connectionState];
|
||||
}
|
||||
|
||||
if (isConnected || proxySettings == nil || !_checkForProxyConnectionIssues) {
|
||||
if (_isProbing) {
|
||||
_isProbing = false;
|
||||
[_probingDisposable setDisposable:nil];
|
||||
_probingStatus = nil;
|
||||
}
|
||||
} else {
|
||||
if (!_isProbing) {
|
||||
_isProbing = true;
|
||||
__weak MTProto *weakSelf = self;
|
||||
MTSignal *checkSignal = [[MTConnectionProbing probeProxyWithContext:_context datacenterId:_datacenterId settings:proxySettings] delay:5.0 onQueue:[MTQueue concurrentDefaultQueue]];
|
||||
checkSignal = [[checkSignal then:[[MTSignal complete] delay:20.0 onQueue:[MTQueue concurrentDefaultQueue]]] restart];
|
||||
[_probingDisposable setDisposable:[checkSignal startWithNext:^(NSNumber *next) {
|
||||
[[MTProto managerQueue] dispatchOnQueue:^{
|
||||
__strong MTProto *strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
if (strongSelf->_isProbing) {
|
||||
strongSelf->_probingStatus = next;
|
||||
if (strongSelf->_connectionState != nil) {
|
||||
strongSelf->_connectionState = [[MTProtoConnectionState alloc] initWithIsConnected:strongSelf->_connectionState.isConnected proxyAddress:strongSelf->_connectionState.proxyAddress proxyHasConnectionIssues:[strongSelf->_probingStatus boolValue]];
|
||||
id<MTProtoDelegate> delegate = strongSelf->_delegate;
|
||||
if ([delegate respondsToSelector:@selector(mtProtoConnectionStateChanged:state:)]) {
|
||||
[delegate mtProtoConnectionStateChanged:self state:connectionState];
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
}]];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -563,7 +563,8 @@
|
||||
rpcError = maybeInternalMessage;
|
||||
else
|
||||
{
|
||||
rpcResult = request.responseParser([MTInternalMessageParser unwrapMessage:rpcResultMessage.data]);
|
||||
NSData *unwrappedData = [MTInternalMessageParser unwrapMessage:rpcResultMessage.data];
|
||||
rpcResult = request.responseParser(unwrappedData);
|
||||
if (rpcResult == nil)
|
||||
{
|
||||
rpcError = [[MTRpcError alloc] initWithErrorCode:500 errorDescription:@"TL_PARSING_ERROR"];
|
||||
|
@ -37,7 +37,7 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0;
|
||||
|
||||
@property (nonatomic, strong) MTDatacenterAddress *address;
|
||||
@property (nonatomic, strong) MTTcpConnection *connection;
|
||||
@property (nonatomic, strong) NSString *proxyAddress;
|
||||
@property (nonatomic, strong) MTSocksProxySettings *proxySettings;
|
||||
|
||||
@property (nonatomic) bool connectionConnected;
|
||||
@property (nonatomic) bool connectionIsValid;
|
||||
@ -117,7 +117,7 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0;
|
||||
|
||||
transportContext.isNetworkAvailable = true;
|
||||
|
||||
transportContext.proxyAddress = context.apiEnvironment.socksProxySettings.ip;
|
||||
transportContext.proxySettings = context.apiEnvironment.socksProxySettings;
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
@ -163,8 +163,8 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0;
|
||||
id<MTTransportDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(transportNetworkAvailabilityChanged:isNetworkAvailable:)])
|
||||
[delegate transportNetworkAvailabilityChanged:self isNetworkAvailable:transportContext.isNetworkAvailable];
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionStateChanged:isConnected:proxyAddress:)])
|
||||
[delegate transportConnectionStateChanged:self isConnected:transportContext.connectionConnected proxyAddress:transportContext.proxyAddress];
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionStateChanged:isConnected:proxySettings:)])
|
||||
[delegate transportConnectionStateChanged:self isConnected:transportContext.connectionConnected proxySettings:transportContext.proxySettings];
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionContextUpdateStateChanged:isUpdatingConnectionContext:)])
|
||||
[delegate transportConnectionContextUpdateStateChanged:self isUpdatingConnectionContext:transportContext.currentActualizationPingMessageId != 0];
|
||||
}];
|
||||
@ -236,8 +236,8 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0;
|
||||
transportContext.connectionIsValid = false;
|
||||
|
||||
id<MTTransportDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionStateChanged:isConnected:proxyAddress:)])
|
||||
[delegate transportConnectionStateChanged:self isConnected:false proxyAddress:transportContext.proxyAddress];
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionStateChanged:isConnected:proxySettings:)])
|
||||
[delegate transportConnectionStateChanged:self isConnected:false proxySettings:transportContext.proxySettings];
|
||||
|
||||
transportContext.connectionBehaviour.needsReconnection = false;
|
||||
|
||||
@ -407,8 +407,8 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0;
|
||||
[transportContext.connectionBehaviour connectionOpened];
|
||||
|
||||
id<MTTransportDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionStateChanged:isConnected:proxyAddress:)])
|
||||
[delegate transportConnectionStateChanged:self isConnected:true proxyAddress:transportContext.proxyAddress];
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionStateChanged:isConnected:proxySettings:)])
|
||||
[delegate transportConnectionStateChanged:self isConnected:true proxySettings:transportContext.proxySettings];
|
||||
|
||||
transportContext.didSendActualizationPingAfterConnection = false;
|
||||
transportContext.currentActualizationPingMessageId = 0;
|
||||
@ -438,8 +438,8 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0;
|
||||
[self restartSleepWatchdogTimer];
|
||||
|
||||
id<MTTransportDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionStateChanged:isConnected:proxyAddress:)])
|
||||
[delegate transportConnectionStateChanged:self isConnected:false proxyAddress:transportContext.proxyAddress];
|
||||
if ([delegate respondsToSelector:@selector(transportConnectionStateChanged:isConnected:proxySettings:)])
|
||||
[delegate transportConnectionStateChanged:self isConnected:false proxySettings:transportContext.proxySettings];
|
||||
|
||||
if ([delegate respondsToSelector:@selector(transportTransactionsMayHaveFailed:transactionIds:)])
|
||||
[delegate transportTransactionsMayHaveFailed:self transactionIds:@[connection.internalId]];
|
||||
|
@ -16,6 +16,7 @@
|
||||
@class MTIncomingMessage;
|
||||
@class MTMessageTransaction;
|
||||
@class MTNetworkUsageCalculationInfo;
|
||||
@class MTSocksProxySettings;
|
||||
|
||||
#if defined(MtProtoKitDynamicFramework)
|
||||
# import <MTProtoKitDynamic/MTMessageService.h>
|
||||
@ -30,7 +31,7 @@
|
||||
@optional
|
||||
|
||||
- (void)transportNetworkAvailabilityChanged:(MTTransport *)transport isNetworkAvailable:(bool)isNetworkAvailable;
|
||||
- (void)transportConnectionStateChanged:(MTTransport *)transport isConnected:(bool)isConnected proxyAddress:(NSString *)proxyAddress;
|
||||
- (void)transportConnectionStateChanged:(MTTransport *)transport isConnected:(bool)isConnected proxySettings:(MTSocksProxySettings *)proxySettings;
|
||||
- (void)transportConnectionContextUpdateStateChanged:(MTTransport *)transport isUpdatingConnectionContext:(bool)isUpdatingConnectionContext;
|
||||
- (void)transportConnectionProblemsStatusChanged:(MTTransport *)transport hasConnectionProblems:(bool)hasConnectionProblems isProbablyHttp:(bool)isProbablyHttp;
|
||||
|
||||
|
@ -154,7 +154,6 @@ typedef struct {
|
||||
|
||||
+ (MTSignal *)pingProxyWithContext:(MTContext *)context datacenterId:(NSInteger)datacenterId settings:(MTSocksProxySettings *)settings {
|
||||
return [[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber) {
|
||||
MTTransportScheme *transportScheme = [context transportSchemeForDatacenterWithId:datacenterId media:false isProxy:true];
|
||||
MTDatacenterAddressSet *addressSet = [context addressSetForDatacenterWithId:datacenterId];
|
||||
NSMutableArray *signals = [[NSMutableArray alloc] init];
|
||||
for (MTDatacenterAddress *address in addressSet.addressList) {
|
||||
|
@ -16,6 +16,18 @@
|
||||
D010DB7E1D70ABEE0012AD96 /* MTRsa.m in Sources */ = {isa = PBXBuildFile; fileRef = D010DB7C1D70ABEE0012AD96 /* MTRsa.m */; };
|
||||
D010DB811D70B3B90012AD96 /* MTAes.h in Headers */ = {isa = PBXBuildFile; fileRef = D010DB7F1D70B3B90012AD96 /* MTAes.h */; };
|
||||
D010DB821D70B3B90012AD96 /* MTAes.m in Sources */ = {isa = PBXBuildFile; fileRef = D010DB801D70B3B90012AD96 /* MTAes.m */; };
|
||||
D0119CC120CAD34800895300 /* MTConnectionProbing.h in Headers */ = {isa = PBXBuildFile; fileRef = D0119CBF20CAD34800895300 /* MTConnectionProbing.h */; };
|
||||
D0119CC220CAD34800895300 /* MTConnectionProbing.h in Headers */ = {isa = PBXBuildFile; fileRef = D0119CBF20CAD34800895300 /* MTConnectionProbing.h */; };
|
||||
D0119CC320CAD34800895300 /* MTConnectionProbing.h in Headers */ = {isa = PBXBuildFile; fileRef = D0119CBF20CAD34800895300 /* MTConnectionProbing.h */; };
|
||||
D0119CC420CAD34800895300 /* MTConnectionProbing.m in Sources */ = {isa = PBXBuildFile; fileRef = D0119CC020CAD34800895300 /* MTConnectionProbing.m */; };
|
||||
D0119CC520CAD34800895300 /* MTConnectionProbing.m in Sources */ = {isa = PBXBuildFile; fileRef = D0119CC020CAD34800895300 /* MTConnectionProbing.m */; };
|
||||
D0119CC620CAD34800895300 /* MTConnectionProbing.m in Sources */ = {isa = PBXBuildFile; fileRef = D0119CC020CAD34800895300 /* MTConnectionProbing.m */; };
|
||||
D0119CC920CAD65D00895300 /* PingFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = D0119CC720CAD65D00895300 /* PingFoundation.h */; };
|
||||
D0119CCA20CAD65D00895300 /* PingFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = D0119CC720CAD65D00895300 /* PingFoundation.h */; };
|
||||
D0119CCB20CAD65D00895300 /* PingFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = D0119CC720CAD65D00895300 /* PingFoundation.h */; };
|
||||
D0119CCC20CAD65D00895300 /* PingFoundation.m in Sources */ = {isa = PBXBuildFile; fileRef = D0119CC820CAD65D00895300 /* PingFoundation.m */; };
|
||||
D0119CCD20CAD65D00895300 /* PingFoundation.m in Sources */ = {isa = PBXBuildFile; fileRef = D0119CC820CAD65D00895300 /* PingFoundation.m */; };
|
||||
D0119CCE20CAD65D00895300 /* PingFoundation.m in Sources */ = {isa = PBXBuildFile; fileRef = D0119CC820CAD65D00895300 /* PingFoundation.m */; };
|
||||
D0185E722089D265005E1A6C /* MTProxyConnectivity.h in Headers */ = {isa = PBXBuildFile; fileRef = D0185E702089D265005E1A6C /* MTProxyConnectivity.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D0185E732089D265005E1A6C /* MTProxyConnectivity.h in Headers */ = {isa = PBXBuildFile; fileRef = D0185E702089D265005E1A6C /* MTProxyConnectivity.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D0185E742089D265005E1A6C /* MTProxyConnectivity.h in Headers */ = {isa = PBXBuildFile; fileRef = D0185E702089D265005E1A6C /* MTProxyConnectivity.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@ -607,6 +619,10 @@
|
||||
D010DB7C1D70ABEE0012AD96 /* MTRsa.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRsa.m; sourceTree = "<group>"; };
|
||||
D010DB7F1D70B3B90012AD96 /* MTAes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTAes.h; sourceTree = "<group>"; };
|
||||
D010DB801D70B3B90012AD96 /* MTAes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTAes.m; sourceTree = "<group>"; };
|
||||
D0119CBF20CAD34800895300 /* MTConnectionProbing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTConnectionProbing.h; sourceTree = "<group>"; };
|
||||
D0119CC020CAD34800895300 /* MTConnectionProbing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTConnectionProbing.m; sourceTree = "<group>"; };
|
||||
D0119CC720CAD65D00895300 /* PingFoundation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PingFoundation.h; sourceTree = "<group>"; };
|
||||
D0119CC820CAD65D00895300 /* PingFoundation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PingFoundation.m; sourceTree = "<group>"; };
|
||||
D0185E702089D265005E1A6C /* MTProxyConnectivity.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTProxyConnectivity.h; sourceTree = "<group>"; };
|
||||
D0185E712089D265005E1A6C /* MTProxyConnectivity.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTProxyConnectivity.m; sourceTree = "<group>"; };
|
||||
D020FAF81D994E3100F279AA /* MTHttpRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTHttpRequestOperation.h; path = MTProtoKit/MTHttpRequestOperation.h; sourceTree = "<group>"; };
|
||||
@ -1071,6 +1087,10 @@
|
||||
D0E2E17620866780005737E8 /* MTDNS.m */,
|
||||
D0185E702089D265005E1A6C /* MTProxyConnectivity.h */,
|
||||
D0185E712089D265005E1A6C /* MTProxyConnectivity.m */,
|
||||
D0119CBF20CAD34800895300 /* MTConnectionProbing.h */,
|
||||
D0119CC020CAD34800895300 /* MTConnectionProbing.m */,
|
||||
D0119CC720CAD65D00895300 /* PingFoundation.h */,
|
||||
D0119CC820CAD65D00895300 /* PingFoundation.m */,
|
||||
);
|
||||
name = Utils;
|
||||
sourceTree = "<group>";
|
||||
@ -1555,6 +1575,7 @@
|
||||
D0CD98161D74B99400F41187 /* MTMsgContainerMessage.h in Headers */,
|
||||
D0CD981A1D74B99400F41187 /* MTMsgsStateInfoMessage.h in Headers */,
|
||||
D0CD98191D74B99400F41187 /* MTMsgsAckMessage.h in Headers */,
|
||||
D0119CC220CAD34800895300 /* MTConnectionProbing.h in Headers */,
|
||||
D0CD98FA1D75C0E400F41187 /* MTApiEnvironment.h in Headers */,
|
||||
D0CD98881D74BA5100F41187 /* MTDatacenterAddressListData.h in Headers */,
|
||||
D0CD98591D74B9BF00F41187 /* MTServerDhInnerDataMessage.h in Headers */,
|
||||
@ -1591,6 +1612,7 @@
|
||||
D0CFBB8B1FD718C500B65C0D /* AFHTTPRequestOperation.h in Headers */,
|
||||
D0CD98841D74BA5100F41187 /* MTDatacenterAddress.h in Headers */,
|
||||
D0CD97EA1D74B94300F41187 /* MTInternalId.h in Headers */,
|
||||
D0119CCA20CAD65D00895300 /* PingFoundation.h in Headers */,
|
||||
D0CAF2DB1D75E31A0011F558 /* MTDisposable.h in Headers */,
|
||||
D0CD98851D74BA5100F41187 /* MTDatacenterAddressSet.h in Headers */,
|
||||
D0CD98B61D74BA7500F41187 /* MTTransportTransaction.h in Headers */,
|
||||
@ -1632,6 +1654,7 @@
|
||||
D0CB06321ADC4583005E298F /* MTDatacenterAuthInfo.h in Headers */,
|
||||
D0D1A0541ADD983C007D9ED6 /* MTMsgsStateReqMessage.h in Headers */,
|
||||
D0CB06201ADC454C005E298F /* MTSerialization.h in Headers */,
|
||||
D0119CC920CAD65D00895300 /* PingFoundation.h in Headers */,
|
||||
D0D1A0401ADD983C007D9ED6 /* MTExportedAuthorizationData.h in Headers */,
|
||||
D0CB063C1ADC4591005E298F /* MTOutgoingMessage.h in Headers */,
|
||||
D0D1A0361ADD983C007D9ED6 /* MTBadMsgNotificationMessage.h in Headers */,
|
||||
@ -1661,6 +1684,7 @@
|
||||
D0CB06251ADC4562005E298F /* MTTransportScheme.h in Headers */,
|
||||
D0D1A05C1ADD983C007D9ED6 /* MTResPqMessage.h in Headers */,
|
||||
D0D1A03E1ADD983C007D9ED6 /* MTDropRpcResultMessage.h in Headers */,
|
||||
D0119CC120CAD34800895300 /* MTConnectionProbing.h in Headers */,
|
||||
D0185E722089D265005E1A6C /* MTProxyConnectivity.h in Headers */,
|
||||
D0D1A05E1ADD983C007D9ED6 /* MTRpcError.h in Headers */,
|
||||
D0E2E17720866780005737E8 /* MTDNS.h in Headers */,
|
||||
@ -1753,6 +1777,7 @@
|
||||
D0CD98241D74B99500F41187 /* MTMsgContainerMessage.h in Headers */,
|
||||
D0CD98281D74B99500F41187 /* MTMsgsStateInfoMessage.h in Headers */,
|
||||
D0CD98271D74B99500F41187 /* MTMsgsAckMessage.h in Headers */,
|
||||
D0119CC320CAD34800895300 /* MTConnectionProbing.h in Headers */,
|
||||
D0CD98FF1D75C0E500F41187 /* MTApiEnvironment.h in Headers */,
|
||||
D0CD988D1D74BA5200F41187 /* MTDatacenterAddressListData.h in Headers */,
|
||||
D0CD985D1D74B9BF00F41187 /* MTServerDhInnerDataMessage.h in Headers */,
|
||||
@ -1789,6 +1814,7 @@
|
||||
D0CFBB8C1FD718C600B65C0D /* AFHTTPRequestOperation.h in Headers */,
|
||||
D0CD98891D74BA5200F41187 /* MTDatacenterAddress.h in Headers */,
|
||||
D0CD97F01D74B94300F41187 /* MTInternalId.h in Headers */,
|
||||
D0119CCB20CAD65D00895300 /* PingFoundation.h in Headers */,
|
||||
D0CAF2DC1D75E31B0011F558 /* MTDisposable.h in Headers */,
|
||||
D0CD988A1D74BA5200F41187 /* MTDatacenterAddressSet.h in Headers */,
|
||||
D0CD98B91D74BA7500F41187 /* MTTransportTransaction.h in Headers */,
|
||||
@ -2017,6 +2043,7 @@
|
||||
D0CD987E1D74BA4900F41187 /* MTDatacenterTransferAuthAction.m in Sources */,
|
||||
D0CD988F1D74BA5900F41187 /* MTDatacenterAddressSet.m in Sources */,
|
||||
D0CD98091D74B96C00F41187 /* MTDestroySessionResponseMessage.m in Sources */,
|
||||
D0119CC520CAD34800895300 /* MTConnectionProbing.m in Sources */,
|
||||
D0CD982E1D74B9AA00F41187 /* MTExportedAuthorizationData.m in Sources */,
|
||||
D0CD986E1D74B9F400F41187 /* MTTransportScheme.m in Sources */,
|
||||
D0CD98D81D74BAAD00F41187 /* MTHttpWorkerBehaviour.m in Sources */,
|
||||
@ -2033,6 +2060,7 @@
|
||||
D0CD98081D74B96C00F41187 /* MTBufferReader.m in Sources */,
|
||||
D0CD98341D74B9AA00F41187 /* MTMsgDetailedInfoMessage.m in Sources */,
|
||||
D0CD984F1D74B9B700F41187 /* MTRpcResultMessage.m in Sources */,
|
||||
D0119CCD20CAD65D00895300 /* PingFoundation.m in Sources */,
|
||||
D020FAFE1D994E3100F279AA /* MTHttpRequestOperation.m in Sources */,
|
||||
D0CD98391D74B9AA00F41187 /* MTNewSessionCreatedMessage.m in Sources */,
|
||||
D0CD98381D74B9AA00F41187 /* MTMsgsStateReqMessage.m in Sources */,
|
||||
@ -2115,6 +2143,7 @@
|
||||
D0CB066F1ADC49FF005E298F /* AFHTTPRequestOperation.m in Sources */,
|
||||
D0D1A06C1ADD987A007D9ED6 /* MTDatacenterAddressListData.m in Sources */,
|
||||
D09A59591B582EFF00FC3724 /* MTFileBasedKeychain.m in Sources */,
|
||||
D0119CC420CAD34800895300 /* MTConnectionProbing.m in Sources */,
|
||||
D0CB06681ADC45DA005E298F /* MTHttpWorker.m in Sources */,
|
||||
D0CB06561ADC45BA005E298F /* MTApiEnvironment.m in Sources */,
|
||||
D0D1A05F1ADD983C007D9ED6 /* MTRpcError.m in Sources */,
|
||||
@ -2131,6 +2160,7 @@
|
||||
D0D1A0391ADD983C007D9ED6 /* MTBuffer.m in Sources */,
|
||||
D0D1A0591ADD983C007D9ED6 /* MTPingMessage.m in Sources */,
|
||||
D0CB06361ADC4588005E298F /* MTDatacenterAuthInfo.m in Sources */,
|
||||
D0119CCC20CAD65D00895300 /* PingFoundation.m in Sources */,
|
||||
D020FAFD1D994E3100F279AA /* MTHttpRequestOperation.m in Sources */,
|
||||
D0CB062D1ADC457B005E298F /* MTDatacenterTransferAuthAction.m in Sources */,
|
||||
D0D1A04F1ADD983C007D9ED6 /* MTMsgResendReqMessage.m in Sources */,
|
||||
@ -2213,6 +2243,7 @@
|
||||
D0CD98811D74BA4900F41187 /* MTDatacenterTransferAuthAction.m in Sources */,
|
||||
D0CD98941D74BA5A00F41187 /* MTDatacenterAddressSet.m in Sources */,
|
||||
D0CD980E1D74B96C00F41187 /* MTDestroySessionResponseMessage.m in Sources */,
|
||||
D0119CC620CAD34800895300 /* MTConnectionProbing.m in Sources */,
|
||||
D0CD983D1D74B9AA00F41187 /* MTExportedAuthorizationData.m in Sources */,
|
||||
D0CD986F1D74B9F500F41187 /* MTTransportScheme.m in Sources */,
|
||||
D0CD98D91D74BAAD00F41187 /* MTHttpWorkerBehaviour.m in Sources */,
|
||||
@ -2229,6 +2260,7 @@
|
||||
D0CD980D1D74B96C00F41187 /* MTBufferReader.m in Sources */,
|
||||
D0CD98431D74B9AA00F41187 /* MTMsgDetailedInfoMessage.m in Sources */,
|
||||
D0CD98541D74B9B700F41187 /* MTRpcResultMessage.m in Sources */,
|
||||
D0119CCE20CAD65D00895300 /* PingFoundation.m in Sources */,
|
||||
D020FAFF1D994E3100F279AA /* MTHttpRequestOperation.m in Sources */,
|
||||
D0CD98481D74B9AA00F41187 /* MTNewSessionCreatedMessage.m in Sources */,
|
||||
D0CD98471D74B9AA00F41187 /* MTMsgsStateReqMessage.m in Sources */,
|
||||
|
233
PingFoundation.h
Normal file
233
PingFoundation.h
Normal file
@ -0,0 +1,233 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if TARGET_OS_EMBEDDED || TARGET_IPHONE_SIMULATOR
|
||||
#import <CFNetwork/CFNetwork.h>
|
||||
#else
|
||||
#import <CoreServices/CoreServices.h>
|
||||
#endif
|
||||
|
||||
#include <AssertMacros.h>
|
||||
|
||||
#pragma mark * PingFoundation
|
||||
|
||||
@protocol PingFoundationDelegate;
|
||||
|
||||
typedef NS_ENUM(NSInteger, PingFoundationAddressStyle) {
|
||||
PingFoundationAddressStyleAny, ///< Use the first IPv4 or IPv6 address found; the default.
|
||||
PingFoundationAddressStyleICMPv4, ///< Use the first IPv4 address found.
|
||||
PingFoundationAddressStyleICMPv6 ///< Use the first IPv6 address found.
|
||||
};
|
||||
|
||||
@interface PingFoundation : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/*! Initialise the object to ping the specified host.
|
||||
* \param hostName The DNS name of the host to ping; an IPv4 or IPv6 address in string form will
|
||||
* work here.
|
||||
* \returns The initialised object.
|
||||
*/
|
||||
|
||||
- (instancetype)initWithHostName:(NSString *)hostName NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/*! A copy of the value passed to `-initWithHostName:`.
|
||||
*/
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString * hostName;
|
||||
|
||||
/*! The delegate for this object.
|
||||
* \details Delegate callbacks are schedule in the default run loop mode of the run loop of the
|
||||
* thread that calls `-start`.
|
||||
*/
|
||||
|
||||
@property (nonatomic, weak, readwrite) id<PingFoundationDelegate> delegate;
|
||||
|
||||
/*! Controls the IP address version used by the object.
|
||||
* \details You should set this value before starting the object.
|
||||
*/
|
||||
|
||||
@property (nonatomic, assign, readwrite) PingFoundationAddressStyle addressStyle;
|
||||
|
||||
/*! 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
|
||||
* `-pingFoundation:didStartWithAddress:` is called.
|
||||
*/
|
||||
|
||||
@property (nonatomic, copy, readonly) NSData * hostAddress;
|
||||
|
||||
/*! The address family for `hostAddress`, or `AF_UNSPEC` if that's nil.
|
||||
*/
|
||||
|
||||
@property (nonatomic, assign, readonly) sa_family_t hostAddressFamily;
|
||||
|
||||
/*! The identifier used by pings by this object.
|
||||
* \details When you create an instance of this object it generates a random identifier
|
||||
* that it uses to identify its own pings.
|
||||
*/
|
||||
|
||||
@property (nonatomic, assign, readonly) uint16_t identifier;
|
||||
|
||||
/*! The next sequence number to be used by this object.
|
||||
* \details This value starts at zero and increments each time you send a ping (safely
|
||||
* wrapping back to zero if necessary). The sequence number is included in the ping,
|
||||
* allowing you to match up requests and responses, and thus calculate ping times and
|
||||
* so on.
|
||||
*/
|
||||
|
||||
@property (nonatomic, assign, readonly) uint16_t nextSequenceNumber;
|
||||
|
||||
- (void)start;
|
||||
// Starts the pinger object pinging. You should call this after
|
||||
// you've setup the delegate and any ping parameters.
|
||||
|
||||
- (void)sendPingWithData:(NSData *)data;
|
||||
// Sends an actual ping. Pass nil for data to use a standard 56 byte payload (resulting in a
|
||||
// standard 64 byte ping). Otherwise pass a non-nil value and it will be appended to the
|
||||
// ICMP header.
|
||||
//
|
||||
// Do not try to send a ping before you receive the -PingFoundation:didStartWithAddress: delegate
|
||||
// callback.
|
||||
|
||||
- (void)stop;
|
||||
// Stops the pinger object. You should call this when you're done
|
||||
// pinging.
|
||||
|
||||
@end
|
||||
|
||||
@protocol PingFoundationDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
/*! A PingFoundation delegate callback, called once the object has started up.
|
||||
* \details This is called shortly after you start the object to tell you that the
|
||||
* object has successfully started. On receiving this callback, you can call
|
||||
* `-sendPingWithData:` to send pings.
|
||||
*
|
||||
* If the object didn't start, `-pingFoundation:didFailWithError:` is called instead.
|
||||
* \param pinger The object issuing the callback.
|
||||
* \param address The address that's being pinged; at the time this delegate callback
|
||||
* is made, this will have the same value as the `hostAddress` property.
|
||||
*/
|
||||
|
||||
- (void)pingFoundation:(PingFoundation *)pinger didStartWithAddress:(NSData *)address;
|
||||
|
||||
/*! 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
|
||||
* object has failed to start. The most likely cause of failure is a problem
|
||||
* resolving `hostName`.
|
||||
*
|
||||
* By the time this callback is called, the object has stopped (that is, you don't
|
||||
* need to call `-stop` yourself).
|
||||
* \param pinger The object issuing the callback.
|
||||
* \param error Describes the failure.
|
||||
*/
|
||||
|
||||
- (void)pingFoundation:(PingFoundation *)pinger didFailWithError:(NSError *)error;
|
||||
|
||||
/*! A PingFoundation delegate callback, called when the object has successfully sent a ping packet.
|
||||
* \details Each call to `-sendPingWithData:` will result in either a
|
||||
* `-pingFoundation:didSendPacket:sequenceNumber:` delegate callback or a
|
||||
* `-pingFoundation:didFailToSendPacket:sequenceNumber:error:` delegate callback (unless you
|
||||
* stop the object before you get the callback). These callbacks are currently delivered
|
||||
* synchronously from within `-sendPingWithData:`, but this synchronous behaviour is not
|
||||
* considered API.
|
||||
* \param pinger The object issuing the callback.
|
||||
* \param packet The packet that was sent; this includes the ICMP header (`ICMPHeader`) and the
|
||||
* data you passed to `-sendPingWithData:` but does not include any IP-level headers.
|
||||
* \param sequenceNumber The ICMP sequence number of that packet.
|
||||
*/
|
||||
|
||||
- (void)pingFoundation:(PingFoundation *)pinger didSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber;
|
||||
|
||||
/*! A PingFoundation delegate callback, called when the object fails to send a ping packet.
|
||||
* \details Each call to `-sendPingWithData:` will result in either a
|
||||
* `-pingFoundation:didSendPacket:sequenceNumber:` delegate callback or a
|
||||
* `-pingFoundation:didFailToSendPacket:sequenceNumber:error:` delegate callback (unless you
|
||||
* stop the object before you get the callback). These callbacks are currently delivered
|
||||
* synchronously from within `-sendPingWithData:`, but this synchronous behaviour is not
|
||||
* considered API.
|
||||
* \param pinger The object issuing the callback.
|
||||
* \param packet The packet that was not sent; see `-pingFoundation:didSendPacket:sequenceNumber:`
|
||||
* for details.
|
||||
* \param sequenceNumber The ICMP sequence number of that packet.
|
||||
* \param error Describes the failure.
|
||||
*/
|
||||
|
||||
- (void)pingFoundation:(PingFoundation *)pinger didFailToSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber error:(NSError *)error;
|
||||
|
||||
/*! A PingFoundation delegate callback, called when the object receives a ping response.
|
||||
* \details If the object receives an ping response that matches a ping request that it
|
||||
* sent, it informs the delegate via this callback. Matching is primarily done based on
|
||||
* the ICMP identifier, although other criteria are used as well.
|
||||
* \param pinger The object issuing the callback.
|
||||
* \param packet The packet received; this includes the ICMP header (`ICMPHeader`) and any data that
|
||||
* follows that in the ICMP message but does not include any IP-level headers.
|
||||
* \param sequenceNumber The ICMP sequence number of that packet.
|
||||
*/
|
||||
|
||||
- (void)pingFoundation:(PingFoundation *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber;
|
||||
|
||||
/*! A PingFoundation delegate callback, called when the object receives an unmatched ICMP message.
|
||||
* \details If the object receives an ICMP message that does not match a ping request that it
|
||||
* sent, it informs the delegate via this callback. The nature of ICMP handling in a
|
||||
* BSD kernel makes this a common event because, when an ICMP message arrives, it is
|
||||
* delivered to all ICMP sockets.
|
||||
*
|
||||
* IMPORTANT: This callback is especially common when using IPv6 because IPv6 uses ICMP
|
||||
* for important network management functions. For example, IPv6 routers periodically
|
||||
* send out Router Advertisement (RA) packets via Neighbor Discovery Protocol (NDP), which
|
||||
* is implemented on top of ICMP.
|
||||
*
|
||||
* For more on matching, see the discussion associated with
|
||||
* `-pingFoundation:didReceivePingResponsePacket:sequenceNumber:`.
|
||||
* \param pinger The object issuing the callback.
|
||||
* \param packet The packet received; this includes the ICMP header (`ICMPHeader`) and any data that
|
||||
* follows that in the ICMP message but does not include any IP-level headers.
|
||||
*/
|
||||
|
||||
- (void)pingFoundation:(PingFoundation *)pinger didReceiveUnexpectedPacket:(NSData *)packet;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark * IP and ICMP On-The-Wire Format
|
||||
|
||||
/*! Describes the on-the-wire header format for an ICMP ping.
|
||||
* \details This defines the header structure of ping packets on the wire. Both IPv4 and
|
||||
* IPv6 use the same basic structure.
|
||||
*
|
||||
* This is declared in the header because clients of PingFoundation might want to use
|
||||
* it parse received ping packets.
|
||||
*/
|
||||
|
||||
// ICMP type and code combinations:
|
||||
|
||||
enum {
|
||||
ICMPv4TypeEchoRequest = 8, ///< The ICMP `type` for a ping request; in this case `code` is always 0.
|
||||
ICMPv4TypeEchoReply = 0 ///< The ICMP `type` for a ping response; in this case `code` is always 0.
|
||||
};
|
||||
|
||||
enum {
|
||||
ICMPv6TypeEchoRequest = 128, ///< The ICMP `type` for a ping request; in this case `code` is always 0.
|
||||
ICMPv6TypeEchoReply = 129 ///< The ICMP `type` for a ping response; in this case `code` is always 0.
|
||||
};
|
||||
|
||||
// ICMP header structure:
|
||||
|
||||
struct ICMPHeader
|
||||
{
|
||||
uint8_t type;
|
||||
uint8_t code;
|
||||
uint16_t checksum;
|
||||
uint16_t identifier;
|
||||
uint16_t sequenceNumber;
|
||||
// data...
|
||||
};
|
||||
typedef struct ICMPHeader ICMPHeader;
|
||||
|
||||
__Check_Compile_Time(sizeof(ICMPHeader) == 8);
|
||||
__Check_Compile_Time(offsetof(ICMPHeader, type) == 0);
|
||||
__Check_Compile_Time(offsetof(ICMPHeader, code) == 1);
|
||||
__Check_Compile_Time(offsetof(ICMPHeader, checksum) == 2);
|
||||
__Check_Compile_Time(offsetof(ICMPHeader, identifier) == 4);
|
||||
__Check_Compile_Time(offsetof(ICMPHeader, sequenceNumber) == 6);
|
774
PingFoundation.m
Normal file
774
PingFoundation.m
Normal file
@ -0,0 +1,774 @@
|
||||
#import "PingFoundation.h"
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <errno.h>
|
||||
|
||||
#pragma mark * IPv4 and ICMPv4 On-The-Wire Format
|
||||
|
||||
/*! Describes the on-the-wire header format for an IPv4 packet.
|
||||
* \details This defines the header structure of IPv4 packets on the wire. We need
|
||||
* this in order to skip this header in the IPv4 case, where the kernel passes
|
||||
* it to us for no obvious reason.
|
||||
*/
|
||||
|
||||
struct IPv4Header {
|
||||
uint8_t versionAndHeaderLength;
|
||||
uint8_t differentiatedServices;
|
||||
uint16_t totalLength;
|
||||
uint16_t identification;
|
||||
uint16_t flagsAndFragmentOffset;
|
||||
uint8_t timeToLive;
|
||||
uint8_t protocol;
|
||||
uint16_t headerChecksum;
|
||||
uint8_t sourceAddress[4];
|
||||
uint8_t destinationAddress[4];
|
||||
// options...
|
||||
// data...
|
||||
};
|
||||
typedef struct IPv4Header IPv4Header;
|
||||
|
||||
__Check_Compile_Time(sizeof(IPv4Header) == 20);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, versionAndHeaderLength) == 0);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, differentiatedServices) == 1);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, totalLength) == 2);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, identification) == 4);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, flagsAndFragmentOffset) == 6);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, timeToLive) == 8);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, protocol) == 9);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, headerChecksum) == 10);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, sourceAddress) == 12);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, destinationAddress) == 16);
|
||||
|
||||
/*! Calculates an IP checksum.
|
||||
* \details This is the standard BSD checksum code, modified to use modern types.
|
||||
* \param buffer A pointer to the data to checksum.
|
||||
* \param bufferLen The length of that data.
|
||||
* \returns The checksum value, in network byte order.
|
||||
*/
|
||||
|
||||
static uint16_t in_cksum(const void *buffer, size_t bufferLen) {
|
||||
//
|
||||
size_t bytesLeft;
|
||||
int32_t sum;
|
||||
const uint16_t * cursor;
|
||||
union {
|
||||
uint16_t us;
|
||||
uint8_t uc[2];
|
||||
} last;
|
||||
uint16_t answer;
|
||||
|
||||
bytesLeft = bufferLen;
|
||||
sum = 0;
|
||||
cursor = buffer;
|
||||
|
||||
/*
|
||||
* Our algorithm is simple, using a 32 bit accumulator (sum), we add
|
||||
* sequential 16 bit words to it, and at the end, fold back all the
|
||||
* carry bits from the top 16 bits into the lower 16 bits.
|
||||
*/
|
||||
while (bytesLeft > 1) {
|
||||
sum += *cursor;
|
||||
cursor += 1;
|
||||
bytesLeft -= 2;
|
||||
}
|
||||
|
||||
/* mop up an odd byte, if necessary */
|
||||
if (bytesLeft == 1) {
|
||||
last.uc[0] = * (const uint8_t *) cursor;
|
||||
last.uc[1] = 0;
|
||||
sum += last.us;
|
||||
}
|
||||
|
||||
/* add back carry outs from top 16 bits to low 16 bits */
|
||||
sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
|
||||
sum += (sum >> 16); /* add carry */
|
||||
answer = (uint16_t) ~sum; /* truncate to 16 bits */
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
#pragma mark * PingFoundation
|
||||
|
||||
@interface PingFoundation ()
|
||||
|
||||
// read/write versions of public properties
|
||||
|
||||
@property (nonatomic, copy, readwrite, nullable) NSData * hostAddress;
|
||||
@property (nonatomic, assign, readwrite ) uint16_t nextSequenceNumber;
|
||||
|
||||
// private properties
|
||||
|
||||
/*! True if nextSequenceNumber has wrapped from 65535 to 0.
|
||||
*/
|
||||
|
||||
@property (nonatomic, assign, readwrite) BOOL nextSequenceNumberHasWrapped;
|
||||
|
||||
/*! A host object for name-to-address resolution.
|
||||
*/
|
||||
|
||||
@property (nonatomic, strong, readwrite, nullable) CFHostRef host __attribute__ ((NSObject));
|
||||
|
||||
/*! A socket object for ICMP send and receive.
|
||||
*/
|
||||
|
||||
@property (nonatomic, strong, readwrite, nullable) CFSocketRef socket __attribute__ ((NSObject));
|
||||
|
||||
@end
|
||||
|
||||
@implementation PingFoundation
|
||||
|
||||
- (instancetype)initWithHostName:(NSString *)hostName
|
||||
{
|
||||
if ([hostName length] <= 0)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
self->_hostName = [hostName copy];
|
||||
self->_identifier = (uint16_t) arc4random();
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self stop];
|
||||
}
|
||||
|
||||
- (sa_family_t)hostAddressFamily {
|
||||
sa_family_t result;
|
||||
|
||||
result = AF_UNSPEC;
|
||||
if ( (self.hostAddress != nil) && (self.hostAddress.length >= sizeof(struct sockaddr)) )
|
||||
{
|
||||
result = ((const struct sockaddr *) self.hostAddress.bytes)->sa_family;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*! Shuts down the pinger object and tell the delegate about the error.
|
||||
* \param error Describes the failure.
|
||||
*/
|
||||
|
||||
- (void)didFailWithError:(NSError *)error {
|
||||
id<PingFoundationDelegate> strongDelegate;
|
||||
|
||||
// We retain ourselves temporarily because it's common for the delegate method
|
||||
// to release its last reference to us, which causes -dealloc to be called here.
|
||||
// If we then reference self on the return path, things go badly. I don't think
|
||||
// that happens currently, but I've got into the habit of doing this as a
|
||||
// defensive measure.
|
||||
|
||||
CFAutorelease(CFBridgingRetain(self));
|
||||
|
||||
[self stop];
|
||||
|
||||
strongDelegate = self.delegate;
|
||||
if ((strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(pingFoundation:didFailWithError:)])
|
||||
{
|
||||
[strongDelegate pingFoundation:self didFailWithError:error];
|
||||
}
|
||||
}
|
||||
|
||||
/*! Shuts down the pinger object and tell the delegate about the error.
|
||||
* \details This converts the CFStreamError to an NSError and then call through to
|
||||
* -didFailWithError: to do the real work.
|
||||
* \param streamError Describes the failure.
|
||||
*/
|
||||
|
||||
- (void)didFailWithHostStreamError:(CFStreamError)streamError {
|
||||
NSDictionary * userInfo;
|
||||
NSError * error;
|
||||
|
||||
if (streamError.domain == kCFStreamErrorDomainNetDB) {
|
||||
userInfo = @{(id) kCFGetAddrInfoFailureKey: @(streamError.error)};
|
||||
} else {
|
||||
userInfo = nil;
|
||||
}
|
||||
error = [NSError errorWithDomain:(NSString *) kCFErrorDomainCFNetwork code:kCFHostErrorUnknown userInfo:userInfo];
|
||||
|
||||
[self didFailWithError:error];
|
||||
}
|
||||
|
||||
/*! Builds a ping packet from the supplied parameters.
|
||||
* \param type The packet type, which is different for IPv4 and IPv6.
|
||||
* \param payload Data to place after the ICMP header.
|
||||
* \param requiresChecksum Determines whether a checksum is calculated (IPv4) or not (IPv6).
|
||||
* \returns A ping packet suitable to be passed to the kernel.
|
||||
*/
|
||||
|
||||
- (NSData *)pingPacketWithType:(uint8_t)type payload:(NSData *)payload requiresChecksum:(BOOL)requiresChecksum {
|
||||
NSMutableData * packet;
|
||||
ICMPHeader * icmpPtr;
|
||||
|
||||
packet = [NSMutableData dataWithLength:sizeof(*icmpPtr) + payload.length];
|
||||
|
||||
icmpPtr = packet.mutableBytes;
|
||||
icmpPtr->type = type;
|
||||
icmpPtr->code = 0;
|
||||
icmpPtr->checksum = 0;
|
||||
icmpPtr->identifier = OSSwapHostToBigInt16(self.identifier);
|
||||
icmpPtr->sequenceNumber = OSSwapHostToBigInt16(self.nextSequenceNumber);
|
||||
memcpy(&icmpPtr[1], [payload bytes], [payload length]);
|
||||
|
||||
if (requiresChecksum) {
|
||||
// The IP checksum routine returns a 16-bit number that's already in correct byte order
|
||||
// (due to wacky 1's complement maths), so we just put it into the packet as a 16-bit unit.
|
||||
|
||||
icmpPtr->checksum = in_cksum(packet.bytes, packet.length);
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
- (void)sendPingWithData:(NSData *)data {
|
||||
int err;
|
||||
NSData * payload;
|
||||
NSData * packet;
|
||||
ssize_t bytesSent;
|
||||
id<PingFoundationDelegate> strongDelegate;
|
||||
|
||||
// data may be nil
|
||||
|
||||
// Construct the ping packet.
|
||||
|
||||
payload = data;
|
||||
if (payload == nil)
|
||||
{
|
||||
payload = [[NSString stringWithFormat:@"%28zd bottles of beer on the wall", (ssize_t) 99 - (size_t) (self.nextSequenceNumber % 100) ] dataUsingEncoding:NSASCIIStringEncoding];
|
||||
// Our dummy payload is sized so that the resulting ICMP packet, including the ICMPHeader, is
|
||||
// 64-bytes, which makes it easier to recognise our packets on the wire.
|
||||
}
|
||||
|
||||
switch (self.hostAddressFamily) {
|
||||
case AF_INET: {
|
||||
packet = [self pingPacketWithType:ICMPv4TypeEchoRequest payload:payload requiresChecksum:YES];
|
||||
} break;
|
||||
case AF_INET6: {
|
||||
packet = [self pingPacketWithType:ICMPv6TypeEchoRequest payload:payload requiresChecksum:NO];
|
||||
} break;
|
||||
default: {
|
||||
assert(NO);
|
||||
} break;
|
||||
}
|
||||
|
||||
// Send the packet.
|
||||
|
||||
if (self.socket == NULL) {
|
||||
bytesSent = -1;
|
||||
err = EBADF;
|
||||
} else {
|
||||
bytesSent = sendto(
|
||||
CFSocketGetNative(self.socket),
|
||||
packet.bytes,
|
||||
packet.length,
|
||||
0,
|
||||
self.hostAddress.bytes,
|
||||
(socklen_t) self.hostAddress.length
|
||||
);
|
||||
err = 0;
|
||||
if (bytesSent < 0) {
|
||||
err = errno;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the results of the send.
|
||||
|
||||
strongDelegate = self.delegate;
|
||||
if ( (bytesSent > 0) && (((NSUInteger) bytesSent) == packet.length) ) {
|
||||
|
||||
// Complete success. Tell the client.
|
||||
|
||||
if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(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.
|
||||
* This code does a rough check of the IPv4 header and, if it looks OK,
|
||||
* returns the offset of the ICMP header.
|
||||
* \param packet The IPv4 packet, as returned to us by the kernel.
|
||||
* \returns The offset of the ICMP header, or NSNotFound.
|
||||
*/
|
||||
|
||||
+ (NSUInteger)icmpHeaderOffsetInIPv4Packet:(NSData *)packet {
|
||||
// Returns the offset of the ICMPv4Header within an IP packet.
|
||||
NSUInteger result;
|
||||
const struct IPv4Header * ipPtr;
|
||||
size_t ipHeaderLength;
|
||||
|
||||
result = NSNotFound;
|
||||
if (packet.length >= (sizeof(IPv4Header) + sizeof(ICMPHeader))) {
|
||||
ipPtr = (const IPv4Header *) packet.bytes;
|
||||
if ( ((ipPtr->versionAndHeaderLength & 0xF0) == 0x40) && // IPv4
|
||||
( ipPtr->protocol == IPPROTO_ICMP ) ) {
|
||||
ipHeaderLength = (ipPtr->versionAndHeaderLength & 0x0F) * sizeof(uint32_t);
|
||||
if (packet.length >= (ipHeaderLength + sizeof(ICMPHeader))) {
|
||||
result = ipHeaderLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*! Checks whether the specified sequence number is one we sent.
|
||||
* \param sequenceNumber The incoming sequence number.
|
||||
* \returns YES if the sequence number looks like one we sent.
|
||||
*/
|
||||
|
||||
- (BOOL)validateSequenceNumber:(uint16_t)sequenceNumber {
|
||||
if (self.nextSequenceNumberHasWrapped) {
|
||||
// If the sequence numbers have wrapped that we can't reliably check
|
||||
// whether this is a sequence number we sent. Rather, we check to see
|
||||
// whether the sequence number is within the last 120 sequence numbers
|
||||
// we sent. Note that the uint16_t subtraction here does the right
|
||||
// thing regardless of the wrapping.
|
||||
//
|
||||
// Why 120? Well, if we send one ping per second, 120 is 2 minutes, which
|
||||
// is the standard "max time a packet can bounce around the Internet" value.
|
||||
return ((uint16_t) (self.nextSequenceNumber - sequenceNumber)) < (uint16_t) 120;
|
||||
} else {
|
||||
return sequenceNumber < self.nextSequenceNumber;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Checks whether an incoming IPv4 packet looks like a ping response.
|
||||
* \details This routine modifies this `packet` data! It does this for two reasons:
|
||||
*
|
||||
* * It needs to zero out the `checksum` field of the ICMPHeader in order to do
|
||||
* its checksum calculation.
|
||||
*
|
||||
* * It removes the IPv4 header from the front of the packet.
|
||||
* \param packet The IPv4 packet, as returned to us by the kernel.
|
||||
* \param sequenceNumberPtr A pointer to a place to start the ICMP sequence number.
|
||||
* \returns YES if the packet looks like a reasonable IPv4 ping response.
|
||||
*/
|
||||
|
||||
- (BOOL)validatePing4ResponsePacket:(NSMutableData *)packet sequenceNumber:(uint16_t *)sequenceNumberPtr {
|
||||
BOOL result;
|
||||
NSUInteger icmpHeaderOffset;
|
||||
ICMPHeader * icmpPtr;
|
||||
uint16_t receivedChecksum;
|
||||
uint16_t calculatedChecksum;
|
||||
|
||||
result = NO;
|
||||
|
||||
icmpHeaderOffset = [[self class] icmpHeaderOffsetInIPv4Packet:packet];
|
||||
if (icmpHeaderOffset != NSNotFound) {
|
||||
icmpPtr = (struct ICMPHeader *) (((uint8_t *) packet.mutableBytes) + icmpHeaderOffset);
|
||||
|
||||
receivedChecksum = icmpPtr->checksum;
|
||||
icmpPtr->checksum = 0;
|
||||
calculatedChecksum = in_cksum(icmpPtr, packet.length - icmpHeaderOffset);
|
||||
icmpPtr->checksum = receivedChecksum;
|
||||
|
||||
if (receivedChecksum == calculatedChecksum) {
|
||||
if ( (icmpPtr->type == ICMPv4TypeEchoReply) && (icmpPtr->code == 0) ) {
|
||||
if ( OSSwapBigToHostInt16(icmpPtr->identifier) == self.identifier ) {
|
||||
uint16_t sequenceNumber;
|
||||
|
||||
sequenceNumber = OSSwapBigToHostInt16(icmpPtr->sequenceNumber);
|
||||
if ([self validateSequenceNumber:sequenceNumber]) {
|
||||
|
||||
// Remove the IPv4 header off the front of the data we received, leaving us with
|
||||
// just the ICMP header and the ping payload.
|
||||
[packet replaceBytesInRange:NSMakeRange(0, icmpHeaderOffset) withBytes:NULL length:0];
|
||||
|
||||
*sequenceNumberPtr = sequenceNumber;
|
||||
result = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*! Checks whether an incoming IPv6 packet looks like a ping response.
|
||||
* \param packet The IPv6 packet, as returned to us by the kernel; note that this routine
|
||||
* could modify this data but does not need to in the IPv6 case.
|
||||
* \param sequenceNumberPtr A pointer to a place to start the ICMP sequence number.
|
||||
* \returns YES if the packet looks like a reasonable IPv4 ping response.
|
||||
*/
|
||||
|
||||
- (BOOL)validatePing6ResponsePacket:(NSMutableData *)packet sequenceNumber:(uint16_t *)sequenceNumberPtr {
|
||||
BOOL result;
|
||||
const ICMPHeader * icmpPtr;
|
||||
|
||||
result = NO;
|
||||
|
||||
if (packet.length >= sizeof(*icmpPtr)) {
|
||||
icmpPtr = packet.bytes;
|
||||
|
||||
// In the IPv6 case we don't check the checksum because that's hard (we need to
|
||||
// cook up an IPv6 pseudo header and we don't have the ingredients) and unnecessary
|
||||
// (the kernel has already done this check).
|
||||
|
||||
if ( (icmpPtr->type == ICMPv6TypeEchoReply) && (icmpPtr->code == 0) ) {
|
||||
if ( OSSwapBigToHostInt16(icmpPtr->identifier) == self.identifier ) {
|
||||
uint16_t sequenceNumber;
|
||||
|
||||
sequenceNumber = OSSwapBigToHostInt16(icmpPtr->sequenceNumber);
|
||||
if ([self validateSequenceNumber:sequenceNumber]) {
|
||||
*sequenceNumberPtr = sequenceNumber;
|
||||
result = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*! Checks whether an incoming packet looks like a ping response.
|
||||
* \param packet The packet, as returned to us by the kernel; note that may end up modifying
|
||||
* this data.
|
||||
* \param sequenceNumberPtr A pointer to a place to start the ICMP sequence number.
|
||||
* \returns YES if the packet looks like a reasonable IPv4 ping response.
|
||||
*/
|
||||
|
||||
- (BOOL)validatePingResponsePacket:(NSMutableData *)packet sequenceNumber:(uint16_t *)sequenceNumberPtr {
|
||||
BOOL result;
|
||||
|
||||
switch (self.hostAddressFamily) {
|
||||
case AF_INET: {
|
||||
result = [self validatePing4ResponsePacket:packet sequenceNumber:sequenceNumberPtr];
|
||||
} break;
|
||||
case AF_INET6: {
|
||||
result = [self validatePing6ResponsePacket:packet sequenceNumber:sequenceNumberPtr];
|
||||
} break;
|
||||
default: {
|
||||
assert(NO);
|
||||
result = NO;
|
||||
} break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*! Reads data from the ICMP socket.
|
||||
* \details Called by the socket handling code (SocketReadCallback) to process an ICMP
|
||||
* message waiting on the socket.
|
||||
*/
|
||||
|
||||
- (void)readData {
|
||||
int err;
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addrLen;
|
||||
ssize_t bytesRead;
|
||||
void * buffer;
|
||||
enum { kBufferSize = 65535 };
|
||||
|
||||
// 65535 is the maximum IP packet size, which seems like a reasonable bound
|
||||
// here (plus it's what <x-man-page://8/ping> uses).
|
||||
|
||||
buffer = malloc(kBufferSize);
|
||||
if (buffer == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Actually read the data. We use recvfrom(), and thus get back the source address,
|
||||
// but we don't actually do anything with it. It would be trivial to pass it to
|
||||
// the delegate but we don't need it in this example.
|
||||
|
||||
addrLen = sizeof(addr);
|
||||
bytesRead = recvfrom(CFSocketGetNative(self.socket), buffer, kBufferSize, 0, (struct sockaddr *) &addr, &addrLen);
|
||||
err = 0;
|
||||
if (bytesRead < 0) {
|
||||
err = errno;
|
||||
}
|
||||
|
||||
// Process the data we read.
|
||||
|
||||
if (bytesRead > 0) {
|
||||
NSMutableData * packet;
|
||||
id<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];
|
||||
}
|
||||
} else {
|
||||
if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(pingFoundation:didReceiveUnexpectedPacket:)] ) {
|
||||
[strongDelegate pingFoundation:self didReceiveUnexpectedPacket:packet];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// We failed to read the data, so shut everything down.
|
||||
|
||||
if (err == 0) {
|
||||
err = EPIPE;
|
||||
}
|
||||
[self didFailWithError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
|
||||
// Note that we don't loop back trying to read more data. Rather, we just
|
||||
// let CFSocket call us again.
|
||||
}
|
||||
|
||||
/*! The callback for our CFSocket object.
|
||||
* \details This simply routes the call to our `-readData` method.
|
||||
* \param s See the documentation for CFSocketCallBack.
|
||||
* \param type See the documentation for CFSocketCallBack.
|
||||
* \param address See the documentation for CFSocketCallBack.
|
||||
* \param data See the documentation for CFSocketCallBack.
|
||||
* \param info See the documentation for CFSocketCallBack; this is actually a pointer to the
|
||||
* 'owning' object.
|
||||
*/
|
||||
|
||||
static void SocketReadCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
|
||||
// This C routine is called by CFSocket when there's data waiting on our
|
||||
// ICMP socket. It just redirects the call to Objective-C code.
|
||||
PingFoundation * obj;
|
||||
|
||||
obj = (__bridge PingFoundation *) info;
|
||||
|
||||
[obj readData];
|
||||
}
|
||||
|
||||
/*! Starts the send and receive infrastructure.
|
||||
* \details This is called once we've successfully resolved `hostName` in to
|
||||
* `hostAddress`. It's responsible for setting up the socket for sending and
|
||||
* receiving pings.
|
||||
*/
|
||||
|
||||
- (void)startWithHostAddress
|
||||
{
|
||||
if (self.hostAddress == nil)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int err;
|
||||
int fd;
|
||||
|
||||
// Open the socket.
|
||||
|
||||
fd = -1;
|
||||
err = 0;
|
||||
switch (self.hostAddressFamily) {
|
||||
case AF_INET: {
|
||||
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
|
||||
if (fd < 0) {
|
||||
err = errno;
|
||||
}
|
||||
} break;
|
||||
case AF_INET6: {
|
||||
fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
|
||||
if (fd < 0) {
|
||||
err = errno;
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
err = EPROTONOSUPPORT;
|
||||
} break;
|
||||
}
|
||||
|
||||
if (err != 0) {
|
||||
[self didFailWithError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
|
||||
} else {
|
||||
CFSocketContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
|
||||
CFRunLoopSourceRef rls;
|
||||
id<PingFoundationDelegate> strongDelegate;
|
||||
|
||||
// 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;
|
||||
|
||||
rls = CFSocketCreateRunLoopSource(NULL, self.socket, 0);
|
||||
|
||||
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
|
||||
|
||||
CFRelease(rls);
|
||||
|
||||
strongDelegate = self.delegate;
|
||||
if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(pingFoundation:didStartWithAddress:)] ) {
|
||||
[strongDelegate pingFoundation:self didStartWithAddress:self.hostAddress];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*! Processes the results of our name-to-address resolution.
|
||||
* \details Called by our CFHost resolution callback (HostResolveCallback) when host
|
||||
* resolution is complete. We just latch the first appropriate address and kick
|
||||
* off the send and receive infrastructure.
|
||||
*/
|
||||
|
||||
- (void)hostResolutionDone {
|
||||
Boolean resolved;
|
||||
NSArray * addresses;
|
||||
|
||||
// Find the first appropriate address.
|
||||
|
||||
addresses = (__bridge NSArray *) CFHostGetAddressing(self.host, &resolved);
|
||||
if ( resolved && (addresses != nil) ) {
|
||||
resolved = false;
|
||||
for (NSData * address in addresses) {
|
||||
const struct sockaddr * addrPtr;
|
||||
|
||||
addrPtr = (const struct sockaddr *) address.bytes;
|
||||
if ( address.length >= sizeof(struct sockaddr) ) {
|
||||
switch (addrPtr->sa_family) {
|
||||
case AF_INET: {
|
||||
if (self.addressStyle != PingFoundationAddressStyleICMPv6) {
|
||||
self.hostAddress = address;
|
||||
resolved = true;
|
||||
}
|
||||
} break;
|
||||
case AF_INET6: {
|
||||
if (self.addressStyle != PingFoundationAddressStyleICMPv4) {
|
||||
self.hostAddress = address;
|
||||
resolved = true;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
if (resolved) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We're done resolving, so shut that down.
|
||||
|
||||
[self stopHostResolution];
|
||||
|
||||
// If all is OK, start the send and receive infrastructure, otherwise stop.
|
||||
|
||||
if (resolved) {
|
||||
[self startWithHostAddress];
|
||||
} else {
|
||||
[self didFailWithError:[NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorHostNotFound userInfo:nil]];
|
||||
}
|
||||
}
|
||||
|
||||
/*! The callback for our CFHost object.
|
||||
* \details This simply routes the call to our `-hostResolutionDone` or
|
||||
* `-didFailWithHostStreamError:` methods.
|
||||
* \param theHost See the documentation for CFHostClientCallBack.
|
||||
* \param typeInfo See the documentation for CFHostClientCallBack.
|
||||
* \param error See the documentation for CFHostClientCallBack.
|
||||
* \param info See the documentation for CFHostClientCallBack; this is actually a pointer to
|
||||
* the 'owning' object.
|
||||
*/
|
||||
|
||||
static void HostResolveCallback(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void *info) {
|
||||
// This C routine is called by CFHost when the host resolution is complete.
|
||||
// It just redirects the call to the appropriate Objective-C method.
|
||||
PingFoundation * obj;
|
||||
|
||||
obj = (__bridge PingFoundation *) info;
|
||||
|
||||
if (([obj isKindOfClass:[PingFoundation class]] && typeInfo == kCFHostAddresses))
|
||||
{
|
||||
if (theHost != obj->_host)
|
||||
{
|
||||
// unmatched, do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
if ((error != NULL) && (error->domain != 0))
|
||||
{
|
||||
[obj didFailWithHostStreamError:*error];
|
||||
}
|
||||
else
|
||||
{
|
||||
[obj hostResolutionDone];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)start
|
||||
{
|
||||
// If the user supplied us with an address, just start pinging that. Otherwise
|
||||
// start a host resolution.
|
||||
|
||||
Boolean success;
|
||||
CFHostClientContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
|
||||
CFStreamError streamError;
|
||||
|
||||
self.host = (CFHostRef)CFAutorelease(CFHostCreateWithName(NULL, (__bridge CFStringRef) self.hostName));
|
||||
|
||||
if (self.host == NULL)
|
||||
{
|
||||
// host NULL; do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
CFHostSetClient(self.host, HostResolveCallback, &context);
|
||||
CFHostScheduleWithRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
success = CFHostStartInfoResolution(self.host, kCFHostAddresses, &streamError);
|
||||
if (!success)
|
||||
{
|
||||
[self didFailWithHostStreamError:streamError];
|
||||
}
|
||||
}
|
||||
|
||||
/*! Stops the name-to-address resolution infrastructure.
|
||||
*/
|
||||
|
||||
- (void)stopHostResolution {
|
||||
// Shut down the CFHost.
|
||||
if (self.host != NULL) {
|
||||
CFHostSetClient(self.host, NULL, NULL);
|
||||
CFHostUnscheduleFromRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
self.host = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Stops the send and receive infrastructure.
|
||||
*/
|
||||
|
||||
- (void)stopSocket {
|
||||
if (self.socket != NULL) {
|
||||
CFSocketInvalidate(self.socket);
|
||||
self.socket = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
[self stopHostResolution];
|
||||
[self stopSocket];
|
||||
|
||||
// Junk the host address on stop. If the client calls -start again, we'll
|
||||
// re-resolve the host name.
|
||||
|
||||
self.hostAddress = NULL;
|
||||
}
|
||||
|
||||
@end
|
Loading…
x
Reference in New Issue
Block a user