diff --git a/MMLanScan/External Libs/MacFinder/MacFinder.h b/MMLanScan/External Libs/MacFinder/MacFinder.h index d640190..c9375ca 100644 --- a/MMLanScan/External Libs/MacFinder/MacFinder.h +++ b/MMLanScan/External Libs/MacFinder/MacFinder.h @@ -8,11 +8,7 @@ #import -#if TARGET_IPHONE_SIMULATOR -#include -#else #include "route.h" -#endif #include "if_ether.h" #include diff --git a/MMLanScan/External Libs/MacFinder/Network Headers/route.h b/MMLanScan/External Libs/MacFinder/Network Headers/route.h index 0bd7965..009ec15 100644 --- a/MMLanScan/External Libs/MacFinder/Network Headers/route.h +++ b/MMLanScan/External Libs/MacFinder/Network Headers/route.h @@ -73,8 +73,6 @@ * retransmission behavior and are included in the routing structure. */ -#if TARGET_IPHONE_SIMULATOR -#else struct rt_metrics { u_int32_t rmx_locks; /* Kernel leaves these values alone */ u_int32_t rmx_mtu; /* MTU for this path */ @@ -88,7 +86,6 @@ struct rt_metrics { u_int32_t rmx_pksent; /* packets sent using this route */ u_int32_t rmx_filler[4]; /* will be used for T/TCP later */ }; -#endif /* * rmx_rtt and rmx_rttvar are stored as microseconds; @@ -153,8 +150,6 @@ struct rtstat { /* * Structures for routing messages. */ -#if TARGET_IPHONE_SIMULATOR -#else struct rt_msghdr { u_short rtm_msglen; /* to skip over non-understood messages */ u_char rtm_version; /* future binary compatibility */ @@ -169,10 +164,7 @@ struct rt_msghdr { u_int32_t rtm_inits; /* which metrics we are initializing */ struct rt_metrics rtm_rmx; /* metrics themselves */ }; -#endif -#if TARGET_IPHONE_SIMULATOR -#else struct rt_msghdr2 { u_short rtm_msglen; /* to skip over non-understood messages */ u_char rtm_version; /* future binary compatibility */ @@ -187,7 +179,6 @@ struct rt_msghdr2 { u_int32_t rtm_inits; /* which metrics we are initializing */ struct rt_metrics rtm_rmx; /* metrics themselves */ }; -#endif #define RTM_VERSION 5 /* Up the ante and ignore older versions */ diff --git a/MMLanScan/External Libs/SimplePing/SimplePing.h b/MMLanScan/External Libs/SimplePing/SimplePing.h index b4576c7..d49e00b 100755 --- a/MMLanScan/External Libs/SimplePing/SimplePing.h +++ b/MMLanScan/External Libs/SimplePing/SimplePing.h @@ -1,163 +1,246 @@ /* - File: SimplePing.h - - Contains: Implements ping. - - Written by: DTS - - Copyright: Copyright (c) 2010-2012 Apple Inc. All Rights Reserved. - - Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. - ("Apple") in consideration of your agreement to the following - terms, and your use, installation, modification or - redistribution of this Apple software constitutes acceptance of - these terms. If you do not agree with these terms, please do - not use, install, modify or redistribute this Apple software. - - In consideration of your agreement to abide by the following - terms, and subject to these terms, Apple grants you a personal, - non-exclusive license, under Apple's copyrights in this - original Apple software (the "Apple Software"), to use, - reproduce, modify and redistribute the Apple Software, with or - without modifications, in source and/or binary forms; provided - that if you redistribute the Apple Software in its entirety and - without modifications, you must retain this notice and the - following text and disclaimers in all such redistributions of - the Apple Software. Neither the name, trademarks, service marks - or logos of Apple Inc. may be used to endorse or promote - products derived from the Apple Software without specific prior - written permission from Apple. Except as expressly stated in - this notice, no other rights or licenses, express or implied, - are granted by Apple herein, including but not limited to any - patent rights that may be infringed by your derivative works or - by other works in which the Apple Software may be incorporated. - - The Apple Software is provided by Apple on an "AS IS" basis. - APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING - WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, - MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING - THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN - COMBINATION WITH YOUR PRODUCTS. - - IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, - INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY - OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION - OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY - OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR - OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF - SUCH DAMAGE. - -*/ - -#import - -#if TARGET_OS_EMBEDDED || TARGET_IPHONE_SIMULATOR - #import -#else - #import -#endif - -#include - -#pragma mark * SimplePing - -// The SimplePing class is a very simple class that lets you send and receive pings. + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + An object wrapper around the low-level BSD Sockets ping function. + */ + +@import Foundation; + +#include // for __Check_Compile_Time + +NS_ASSUME_NONNULL_BEGIN @protocol SimplePingDelegate; +/*! Controls the IP address version used by SimplePing instances. + */ + +typedef NS_ENUM(NSInteger, SimplePingAddressStyle) { + SimplePingAddressStyleAny, ///< Use the first IPv4 or IPv6 address found; the default. + SimplePingAddressStyleICMPv4, ///< Use the first IPv4 address found. + SimplePingAddressStyleICMPv6 ///< Use the first IPv6 address found. +}; + +/*! An object wrapper around the low-level BSD Sockets ping function. + * \details To use the class create an instance, set the delegate and call `-start` + * to start the instance on the current run loop. If things go well you'll soon get the + * `-simplePing:didStartWithAddress:` delegate callback. From there you can can call + * `-sendPingWithData:` to send a ping and you'll receive the + * `-simplePing:didReceivePingResponsePacket:sequenceNumber:` and + * `-simplePing:didReceiveUnexpectedPacket:` delegate callbacks as ICMP packets arrive. + * + * The class can be used from any thread but the use of any single instance must be + * confined to a specific thread and that thread must run its run loop. + */ + @interface SimplePing : NSObject -+ (SimplePing *)simplePingWithHostName:(NSString *)hostName; // chooses first IPv4 address -+ (SimplePing *)simplePingWithHostAddress:(NSData *)hostAddress; // contains (struct sockaddr) +- (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; -@property (nonatomic, weak, readwrite) id delegate; +/*! 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, copy, readonly ) NSString * hostName; -@property (nonatomic, copy, readonly ) NSData * hostAddress; -@property (nonatomic, assign, readonly ) uint16_t identifier; -@property (nonatomic, assign, readonly ) uint16_t nextSequenceNumber; +@property (nonatomic, weak, readwrite, nullable) id delegate; + +/*! Controls the IP address version used by the object. + * \details You should set this value before starting the object. + */ + +@property (nonatomic, assign, readwrite) SimplePingAddressStyle 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 + * `-simplePing:didStartWithAddress:` is called. + */ + +@property (nonatomic, copy, readonly, nullable) 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; + +/*! Starts the object. + * \details You should set up the delegate and any ping parameters before calling this. + * + * If things go well you'll soon get the `-simplePing:didStartWithAddress:` delegate + * callback, at which point you can start sending pings (via `-sendPingWithData:`) and + * will start receiving ICMP packets (either ping responses, via the + * `-simplePing:didReceivePingResponsePacket:sequenceNumber:` delegate callback, or + * unsolicited ICMP packets, via the `-simplePing:didReceiveUnexpectedPacket:` delegate + * callback). + * + * If the object fails to start, typically because `hostName` doesn't resolve, you'll get + * the `-simplePing:didFailWithError:` delegate callback. + * + * It is not correct to start an already started object. + */ - (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 -simplePing:didStartWithAddress: delegate - // callback. +/*! Sends a ping packet containing the specified data. + * \details Sends an actual ping. + * + * The object must be started when you call this method and, on starting the object, you must + * wait for the `-simplePing:didStartWithAddress:` delegate callback before calling it. + * \param data Some data to include in the ping packet, after the ICMP header, or nil if you + * want the packet to include a standard 56 byte payload (resulting in a standard 64 byte + * ping). + */ -- (void)stop; - // Stops the pinger object. You should call this when you're done - // pinging. +- (void)sendPingWithData:(nullable NSData *)data; -+ (const struct ICMPHeader *)icmpInPacket:(NSData *)packet; - // Given a valid IP packet contains an ICMP , returns the address of the ICMP header that - // follows the IP header. This doesn't do any significant validation of the packet. +/*! Stops the object. + * \details You should call this when you're done pinging. + * + * It's safe to call this on an object that's stopped. + */ + +- (void)stop; @end +/*! A delegate protocol for the SimplePing class. + */ + @protocol SimplePingDelegate @optional -- (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address; -- (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error; -- (void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet; -- (void)simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet error:(NSError *)error; -- (void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet; - - (void)simplePing:(SimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet; - // Called whenever the SimplePing object receives an ICMP packet that does not - // look like a response to one of our pings. +/*! A SimplePing 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, `-simplePing: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. + */ -@end - -#pragma mark * IP and ICMP On-The-Wire Format +- (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address; -// The following declarations specify the structure of ping packets on the wire. +/*! A SimplePing 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)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error; -// IP header structure: +/*! A SimplePing delegate callback, called when the object has successfully sent a ping packet. + * \details Each call to `-sendPingWithData:` will result in either a + * `-simplePing:didSendPacket:sequenceNumber:` delegate callback or a + * `-simplePing: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)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber; + +/*! A SimplePing delegate callback, called when the object fails to send a ping packet. + * \details Each call to `-sendPingWithData:` will result in either a + * `-simplePing:didSendPacket:sequenceNumber:` delegate callback or a + * `-simplePing: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 `-simplePing:didSendPacket:sequenceNumber:` + * for details. + * \param sequenceNumber The ICMP sequence number of that packet. + * \param error Describes the failure. + */ + +- (void)simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber error:(NSError *)error; + +/*! A SimplePing 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)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber; + +/*! A SimplePing 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 + * `-simplePing: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)simplePing:(SimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet; -struct IPHeader { - 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 IPHeader IPHeader; - -check_compile_time(sizeof(IPHeader) == 20); -check_compile_time(offsetof(IPHeader, versionAndHeaderLength) == 0); -check_compile_time(offsetof(IPHeader, differentiatedServices) == 1); -check_compile_time(offsetof(IPHeader, totalLength) == 2); -check_compile_time(offsetof(IPHeader, identification) == 4); -check_compile_time(offsetof(IPHeader, flagsAndFragmentOffset) == 6); -check_compile_time(offsetof(IPHeader, timeToLive) == 8); -check_compile_time(offsetof(IPHeader, protocol) == 9); -check_compile_time(offsetof(IPHeader, headerChecksum) == 10); -check_compile_time(offsetof(IPHeader, sourceAddress) == 12); -check_compile_time(offsetof(IPHeader, destinationAddress) == 16); - -// ICMP type and code combinations: +@end -enum { - kICMPTypeEchoReply = 0, // code is always 0 - kICMPTypeEchoRequest = 8 // code is always 0 -}; +#pragma mark * ICMP On-The-Wire Format -// ICMP header structure: +/*! 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 SimplePing might want to use + * it parse received ping packets. + */ struct ICMPHeader { uint8_t type; @@ -169,9 +252,21 @@ struct ICMPHeader { }; 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); +__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); + +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. +}; + +NS_ASSUME_NONNULL_END diff --git a/MMLanScan/External Libs/SimplePing/SimplePing.m b/MMLanScan/External Libs/SimplePing/SimplePing.m old mode 100755 new mode 100644 index fa24b41..0e60f82 --- a/MMLanScan/External Libs/SimplePing/SimplePing.m +++ b/MMLanScan/External Libs/SimplePing/SimplePing.m @@ -1,55 +1,10 @@ - /* - File: SimplePing.m - - Contains: Implements ping. - - Written by: DTS - - Copyright: Copyright (c) 2010-2012 Apple Inc. All Rights Reserved. - - Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. - ("Apple") in consideration of your agreement to the following - terms, and your use, installation, modification or - redistribution of this Apple software constitutes acceptance of - these terms. If you do not agree with these terms, please do - not use, install, modify or redistribute this Apple software. - - In consideration of your agreement to abide by the following - terms, and subject to these terms, Apple grants you a personal, - non-exclusive license, under Apple's copyrights in this - original Apple software (the "Apple Software"), to use, - reproduce, modify and redistribute the Apple Software, with or - without modifications, in source and/or binary forms; provided - that if you redistribute the Apple Software in its entirety and - without modifications, you must retain this notice and the - following text and disclaimers in all such redistributions of - the Apple Software. Neither the name, trademarks, service marks - or logos of Apple Inc. may be used to endorse or promote - products derived from the Apple Software without specific prior - written permission from Apple. Except as expressly stated in - this notice, no other rights or licenses, express or implied, - are granted by Apple herein, including but not limited to any - patent rights that may be infringed by your derivative works or - by other works in which the Apple Software may be incorporated. - - The Apple Software is provided by Apple on an "AS IS" basis. - APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING - WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, - MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING - THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN - COMBINATION WITH YOUR PRODUCTS. - - IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, - INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY - OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION - OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY - OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR - OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF - SUCH DAMAGE. - -*/ +/* + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + An object wrapper around the low-level BSD Sockets ping function. + */ #import "SimplePing.h" @@ -57,11 +12,51 @@ INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED #include #include -#pragma mark * ICMP On-The-Wire Format - -static uint16_t in_cksum(const void *buffer, size_t bufferLen) - // This is the standard BSD checksum code, modified to use modern types. -{ +#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; @@ -105,113 +100,144 @@ static uint16_t in_cksum(const void *buffer, size_t bufferLen) @interface SimplePing () -@property (nonatomic, copy, readwrite) NSData * hostAddress; -@property (nonatomic, assign, readwrite) uint16_t nextSequenceNumber; +// read/write versions of public properties -- (void)stopHostResolution; -- (void)stopDataTransfer; +@property (nonatomic, copy, readwrite, nullable) NSData * hostAddress; +@property (nonatomic, assign, readwrite ) uint16_t nextSequenceNumber; -@end +// private properties -@implementation SimplePing -{ - CFHostRef _host; - CFSocketRef _socket; -} +/*! 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)); -@synthesize hostName = _hostName; -@synthesize hostAddress = _hostAddress; +/*! A socket object for ICMP send and receive. + */ -@synthesize delegate = _delegate; -@synthesize identifier = _identifier; -@synthesize nextSequenceNumber = _nextSequenceNumber; +@property (nonatomic, strong, readwrite, nullable) CFSocketRef socket __attribute__ ((NSObject)); -- (id)initWithHostName:(NSString *)hostName address:(NSData *)hostAddress - // The initialiser common to both of our construction class methods. -{ - assert( (hostName != nil) == (hostAddress == nil) ); +@end + +@implementation SimplePing + +- (instancetype)initWithHostName:(NSString *)hostName { + NSParameterAssert(hostName != nil); self = [super init]; if (self != nil) { - self->_hostName = [hostName copy]; - self->_hostAddress = [hostAddress copy]; - self->_identifier = (uint16_t) arc4random(); + self->_hostName = [hostName copy]; + self->_identifier = (uint16_t) arc4random(); } return self; } -- (void)dealloc -{ - // -stop takes care of _host and _socket. +- (void)dealloc { [self stop]; + // Double check that -stop took care of _host and _socket. assert(self->_host == NULL); assert(self->_socket == NULL); } -+ (SimplePing *)simplePingWithHostName:(NSString *)hostName - // See comment in header. -{ - return [[SimplePing alloc] initWithHostName:hostName address:nil]; -} - -+ (SimplePing *)simplePingWithHostAddress:(NSData *)hostAddress - // See comment in header. -{ - return [[SimplePing alloc] initWithHostName:NULL address:hostAddress]; +- (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; } -- (void)noop -{ -} +/*! Shuts down the pinger object and tell the delegate about the error. + * \param error Describes the failure. + */ -- (void)didFailWithError:(NSError *)error - // Shut down the pinger object and tell the delegate about the error. -{ +- (void)didFailWithError:(NSError *)error { + id strongDelegate; + assert(error != nil); // We retain ourselves temporarily because it's common for the delegate method - // to release its last reference to use, which causes -dealloc to be called here. + // 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. - [self performSelector:@selector(noop) withObject:nil afterDelay:0.0]; + CFAutorelease( CFBridgingRetain( self )); [self stop]; - if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(simplePing:didFailWithError:)] ) { - [self.delegate simplePing:self didFailWithError:error]; + strongDelegate = self.delegate; + if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didFailWithError:)] ) { + [strongDelegate simplePing:self didFailWithError:error]; } } -- (void)didFailWithHostStreamError:(CFStreamError)streamError - // Convert the CFStreamError to an NSError and then call through to - // -didFailWithError: to shut down the pinger object and tell the - // delegate about the 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 = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithInteger:streamError.error], kCFGetAddrInfoFailureKey, - nil - ]; + userInfo = @{(id) kCFGetAddrInfoFailureKey: @(streamError.error)}; } else { userInfo = nil; } - error = [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorUnknown userInfo:userInfo]; - assert(error != nil); + error = [NSError errorWithDomain:(NSString *) kCFErrorDomainCFNetwork code:kCFHostErrorUnknown userInfo:userInfo]; [self didFailWithError:error]; } -- (void)sendPingWithData:(NSData *)data - // See comment in header. -{ - int err; - NSData * payload; - NSMutableData * packet; - ICMPHeader * icmpPtr; - ssize_t bytesSent; +/*! 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]; + assert(packet != nil); + + 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 strongDelegate; + + // data may be nil + NSParameterAssert(self.hostAddress != nil); // gotta wait for -simplePing:didStartWithAddress: // Construct the ping packet. @@ -220,45 +246,38 @@ - (void)sendPingWithData:(NSData *)data payload = [[NSString stringWithFormat:@"%28zd bottles of beer on the wall", (ssize_t) 99 - (size_t) (self.nextSequenceNumber % 100) ] dataUsingEncoding:NSASCIIStringEncoding]; assert(payload != nil); + // 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. + assert([payload length] == 56); } - packet = [NSMutableData dataWithLength:sizeof(*icmpPtr) + [payload length]]; + 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; + } assert(packet != nil); - icmpPtr = [packet mutableBytes]; - icmpPtr->type = kICMPTypeEchoRequest; - icmpPtr->code = 0; - icmpPtr->checksum = 0; - icmpPtr->identifier = OSSwapHostToBigInt16(self.identifier); - icmpPtr->sequenceNumber = OSSwapHostToBigInt16(self.nextSequenceNumber); - memcpy(&icmpPtr[1], [payload bytes], [payload length]); - - // The IP checksum 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]); - - CFSocketNativeHandle sock = CFSocketGetNative(self->_socket); - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 10000; // 0.1 sec - setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (void *)&tv, sizeof(tv)); - // Send the packet. - if (self->_socket == NULL) { + if (self.socket == NULL) { bytesSent = -1; err = EBADF; } else { bytesSent = sendto( - CFSocketGetNative(self->_socket), - [packet bytes], - [packet length], - 0, - (struct sockaddr *) [self.hostAddress bytes], - (socklen_t) [self.hostAddress length] + CFSocketGetNative(self.socket), + packet.bytes, + packet.length, + 0, + self.hostAddress.bytes, + (socklen_t) self.hostAddress.length ); err = 0; if (bytesSent < 0) { @@ -268,12 +287,13 @@ - (void)sendPingWithData:(NSData *)data // Handle the results of the send. - if ( (bytesSent > 0) && (((NSUInteger) bytesSent) == [packet length]) ) { + strongDelegate = self.delegate; + if ( (bytesSent > 0) && (((NSUInteger) bytesSent) == packet.length) ) { // Complete success. Tell the client. - if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(simplePing:didSendPacket:)] ) { - [self.delegate simplePing:self didSendPacket:packet]; + if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didSendPacket:sequenceNumber:)] ) { + [strongDelegate simplePing:self didSendPacket:packet sequenceNumber:self.nextSequenceNumber]; } } else { NSError * error; @@ -284,52 +304,80 @@ - (void)sendPingWithData:(NSData *)data err = ENOBUFS; // This is not a hugely descriptor error, alas. } error = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]; - if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(simplePing:didFailToSendPacket:error:)] ) { - [self.delegate simplePing:self didFailToSendPacket:packet error:error]; + if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didFailToSendPacket:sequenceNumber:error:)] ) { + [strongDelegate simplePing:self didFailToSendPacket:packet sequenceNumber:self.nextSequenceNumber error:error]; } } self.nextSequenceNumber += 1; + if (self.nextSequenceNumber == 0) { + self.nextSequenceNumberHasWrapped = YES; + } } -+ (NSUInteger)icmpHeaderOffsetInPacket:(NSData *)packet - // Returns the offset of the ICMPHeader within an IP packet. -{ - NSUInteger result; - const struct IPHeader * ipPtr; - size_t ipHeaderLength; +/*! 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(IPHeader) + sizeof(ICMPHeader))) { - ipPtr = (const IPHeader *) [packet bytes]; - assert((ipPtr->versionAndHeaderLength & 0xF0) == 0x40); // IPv4 - assert(ipPtr->protocol == 1); // ICMP - ipHeaderLength = (ipPtr->versionAndHeaderLength & 0x0F) * sizeof(uint32_t); - if ([packet length] >= (ipHeaderLength + sizeof(ICMPHeader))) { - result = ipHeaderLength; + 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; } -+ (const struct ICMPHeader *)icmpInPacket:(NSData *)packet - // See comment in header. -{ - const struct ICMPHeader * result; - NSUInteger icmpHeaderOffset; - - result = nil; - icmpHeaderOffset = [self icmpHeaderOffsetInPacket:packet]; - if (icmpHeaderOffset != NSNotFound) { - result = (const struct ICMPHeader *) (((const uint8_t *)[packet bytes]) + icmpHeaderOffset); +/*! 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; } - return result; } -- (BOOL)isValidPingResponsePacket:(NSMutableData *)packet - // Returns true if the packet looks like a valid ping response packet destined - // for us. -{ +/*! 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; @@ -338,19 +386,28 @@ - (BOOL)isValidPingResponsePacket:(NSMutableData *)packet result = NO; - icmpHeaderOffset = [[self class] icmpHeaderOffsetInPacket:packet]; + icmpHeaderOffset = [[self class] icmpHeaderOffsetInIPv4Packet:packet]; if (icmpHeaderOffset != NSNotFound) { - icmpPtr = (struct ICMPHeader *) (((uint8_t *)[packet mutableBytes]) + icmpHeaderOffset); + icmpPtr = (struct ICMPHeader *) (((uint8_t *) packet.mutableBytes) + icmpHeaderOffset); receivedChecksum = icmpPtr->checksum; icmpPtr->checksum = 0; - calculatedChecksum = in_cksum(icmpPtr, [packet length] - icmpHeaderOffset); + calculatedChecksum = in_cksum(icmpPtr, packet.length - icmpHeaderOffset); icmpPtr->checksum = receivedChecksum; if (receivedChecksum == calculatedChecksum) { - if ( (icmpPtr->type == kICMPTypeEchoReply) && (icmpPtr->code == 0) ) { + if ( (icmpPtr->type == ICMPv4TypeEchoReply) && (icmpPtr->code == 0) ) { if ( OSSwapBigToHostInt16(icmpPtr->identifier) == self.identifier ) { - if ( OSSwapBigToHostInt16(icmpPtr->sequenceNumber) < self.nextSequenceNumber ) { + 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; } } @@ -361,10 +418,72 @@ - (BOOL)isValidPingResponsePacket:(NSMutableData *)packet return result; } -- (void)readData - // Called by the socket handling code (SocketReadCallback) to process an ICMP - // messages waiting on the socket. -{ +/*! 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; @@ -378,10 +497,12 @@ - (void)readData buffer = malloc(kBufferSize); assert(buffer != NULL); - // Actually read the data. + // 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); + bytesRead = recvfrom(CFSocketGetNative(self.socket), buffer, kBufferSize, 0, (struct sockaddr *) &addr, &addrLen); err = 0; if (bytesRead < 0) { err = errno; @@ -390,20 +511,23 @@ - (void)readData // Process the data we read. if (bytesRead > 0) { - NSMutableData * packet; + NSMutableData * packet; + id strongDelegate; + uint16_t sequenceNumber; packet = [NSMutableData dataWithBytes:buffer length:(NSUInteger) bytesRead]; assert(packet != nil); // We got some data, pass it up to our client. - if ( [self isValidPingResponsePacket:packet] ) { - if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(simplePing:didReceivePingResponsePacket:)] ) { - [self.delegate simplePing:self didReceivePingResponsePacket:packet]; + strongDelegate = self.delegate; + if ( [self validatePingResponsePacket:packet sequenceNumber:&sequenceNumber] ) { + if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didReceivePingResponsePacket:sequenceNumber:)] ) { + [strongDelegate simplePing:self didReceivePingResponsePacket:packet sequenceNumber:sequenceNumber]; } } else { - if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(simplePing:didReceiveUnexpectedPacket:)] ) { - [self.delegate simplePing:self didReceiveUnexpectedPacket:packet]; + if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didReceiveUnexpectedPacket:)] ) { + [strongDelegate simplePing:self didReceiveUnexpectedPacket:packet]; } } } else { @@ -422,17 +546,26 @@ - (void)readData // let CFSocket call us again. } -static void SocketReadCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) +/*! 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. -{ SimplePing * obj; obj = (__bridge SimplePing *) info; assert([obj isKindOfClass:[SimplePing class]]); #pragma unused(s) - assert(s == obj->_socket); + assert(s == obj.socket); #pragma unused(type) assert(type == kCFSocketReadCallBack); #pragma unused(address) @@ -443,31 +576,35 @@ static void SocketReadCallback(CFSocketRef s, CFSocketCallBackType type, CFDataR [obj readData]; } -- (void)startWithHostAddress - // We have a host address, so let's actually start pinging it. -{ +/*! 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 { int err; int fd; - const struct sockaddr * addrPtr; assert(self.hostAddress != nil); // Open the socket. - addrPtr = (const struct sockaddr *) [self.hostAddress bytes]; - fd = -1; err = 0; - switch (addrPtr->sa_family) { + switch (self.hostAddressFamily) { case AF_INET: { fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); if (fd < 0) { err = errno; } } break; - case AF_INET6: - assert(NO); - // fall through + case AF_INET6: { + fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); + if (fd < 0) { + err = errno; + } + } break; default: { err = EPROTONOSUPPORT; } break; @@ -476,53 +613,71 @@ - (void)startWithHostAddress if (err != 0) { [self didFailWithError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]]; } else { - CFSocketContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; - CFRunLoopSourceRef rls; + CFSocketContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; + CFRunLoopSourceRef rls; + id strongDelegate; // Wrap it in a CFSocket and schedule it on the runloop. - self->_socket = CFSocketCreateWithNative(NULL, fd, kCFSocketReadCallBack, SocketReadCallback, &context); - assert(self->_socket != NULL); + self.socket = (CFSocketRef) CFAutorelease( CFSocketCreateWithNative(NULL, fd, kCFSocketReadCallBack, SocketReadCallback, &context) ); + assert(self.socket != NULL); // The socket will now take care of cleaning up our file descriptor. - assert( CFSocketGetSocketFlags(self->_socket) & kCFSocketCloseOnInvalidate ); + assert( CFSocketGetSocketFlags(self.socket) & kCFSocketCloseOnInvalidate ); fd = -1; - rls = CFSocketCreateRunLoopSource(NULL, self->_socket, 0); + rls = CFSocketCreateRunLoopSource(NULL, self.socket, 0); assert(rls != NULL); CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); CFRelease(rls); - if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(simplePing:didStartWithAddress:)] ) { - [self.delegate simplePing:self didStartWithAddress:self.hostAddress]; + strongDelegate = self.delegate; + if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didStartWithAddress:)] ) { + [strongDelegate simplePing:self didStartWithAddress:self.hostAddress]; } } assert(fd == -1); } -- (void)hostResolutionDone - // Called by our CFHost resolution callback (HostResolveCallback) when host - // resolution is complete. We just latch the first IPv4 address and kick - // off the pinging process. -{ +/*! 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 IPv4 address. + // Find the first appropriate address. - addresses = (__bridge NSArray *) CFHostGetAddressing(self->_host, &resolved); + 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) && addrPtr->sa_family == AF_INET) { - self.hostAddress = address; - resolved = true; + addrPtr = (const struct sockaddr *) address.bytes; + if ( address.length >= sizeof(struct sockaddr) ) { + switch (addrPtr->sa_family) { + case AF_INET: { + if (self.addressStyle != SimplePingAddressStyleICMPv6) { + self.hostAddress = address; + resolved = true; + } + } break; + case AF_INET6: { + if (self.addressStyle != SimplePingAddressStyleICMPv4) { + self.hostAddress = address; + resolved = true; + } + } break; + } + } + if (resolved) { break; } } @@ -532,7 +687,7 @@ - (void)hostResolutionDone [self stopHostResolution]; - // If all is OK, start pinging, otherwise shut down the pinger completely. + // If all is OK, start the send and receive infrastructure, otherwise stop. if (resolved) { [self startWithHostAddress]; @@ -541,19 +696,26 @@ - (void)hostResolutionDone } } -static void HostResolveCallback(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void *info) +/*! 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. -{ SimplePing * obj; - // NSLog(@">HostResolveCallback"); - obj = (__bridge SimplePing *) info; assert([obj isKindOfClass:[SimplePing class]]); #pragma unused(theHost) - assert(theHost == obj->_host); + assert(theHost == obj.host); #pragma unused(typeInfo) assert(typeInfo == kCFHostAddresses); @@ -564,70 +726,57 @@ static void HostResolveCallback(CFHostRef theHost, CFHostInfoType typeInfo, cons } } -- (void)start - // See comment in header. -{ - // If the user supplied us with an address, just start pinging that. Otherwise - // start a host resolution. +- (void)start { + Boolean success; + CFHostClientContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; + CFStreamError streamError; - if (self->_hostAddress != nil) { - [self startWithHostAddress]; - } else { - Boolean success; - CFHostClientContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; - CFStreamError streamError; - - assert(self->_host == NULL); + assert(self.host == NULL); + assert(self.hostAddress == nil); - self->_host = CFHostCreateWithName(NULL, (__bridge CFStringRef) self.hostName); - assert(self->_host != NULL); - - CFHostSetClient(self->_host, HostResolveCallback, &context); - - CFHostScheduleWithRunLoop(self->_host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); - - // NSLog(@">CFHostStartInfoResolution"); - success = CFHostStartInfoResolution(self->_host, kCFHostAddresses, &streamError); - // NSLog(@"_host != NULL) { - CFHostSetClient(self->_host, NULL, NULL); - CFHostUnscheduleFromRunLoop(self->_host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); - CFRelease(self->_host); - self->_host = NULL; + if (self.host != NULL) { + CFHostSetClient(self.host, NULL, NULL); + CFHostUnscheduleFromRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + self.host = NULL; } } -- (void)stopDataTransfer - // Shut down anything to do with sending and receiving pings. -{ - if (self->_socket != NULL) { - CFSocketInvalidate(self->_socket); - CFRelease(self->_socket); - self->_socket = NULL; +/*! Stops the send and receive infrastructure. + */ + +- (void)stopSocket { + if (self.socket != NULL) { + CFSocketInvalidate(self.socket); + self.socket = NULL; } } -- (void)stop - // See comment in header. -{ +- (void)stop { [self stopHostResolution]; - [self stopDataTransfer]; + [self stopSocket]; - // If we were started with a host name, junk the host address on stop. If the - // client calls -start again, we'll re-resolve the host name. + // Junk the host address on stop. If the client calls -start again, we'll + // re-resolve the host name. - if (self.hostName != nil) { - self.hostAddress = NULL; - } + self.hostAddress = NULL; } @end diff --git a/MMLanScan/Misc/PingOperation.m b/MMLanScan/Misc/PingOperation.m index aac9f3a..d9f320b 100644 --- a/MMLanScan/Misc/PingOperation.m +++ b/MMLanScan/Misc/PingOperation.m @@ -10,6 +10,7 @@ #import "MMDevice.h" #import "LANProperties.h" #import "MacFinder.h" +#import "SimplePing_withoutHost.h" static const float PING_TIMEOUT = 1; @@ -38,7 +39,7 @@ -(instancetype)initWithIPToPing:(NSString*)ip andCompletionHandler:(nullable voi if (self) { self.name = ip; _ipStr= ip; - _simplePing = [SimplePing simplePingWithHostName:ip]; + _simplePing = [SimplePing simplePingWithIPAddress:ip]; _simplePing.delegate = self; _result = result; _isExecuting = NO; @@ -80,7 +81,8 @@ -(void)start { } -(void)ping { - [self.simplePing start]; + + [self.simplePing sendPingWithoutHostResolving]; } - (void)finishedPing { @@ -146,20 +148,20 @@ - (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error { [self finishedPing]; } -- (void)simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet error:(NSError *)error { +-(void)simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber error:(NSError *)error { [pingTimer invalidate]; errorMessage = error; [self finishedPing]; } -- (void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet { +-(void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber { [pingTimer invalidate]; [self finishedPing]; } -- (void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet { +-(void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber { //This timer will fired pingTimeOut in case the SimplePing don't answer in the specific time pingTimer = [NSTimer scheduledTimerWithTimeInterval:PING_TIMEOUT target:self selector:@selector(pingTimeOut:) userInfo:nil repeats:NO]; } diff --git a/MMLanScan/Misc/SimplePing_withoutHost.h b/MMLanScan/Misc/SimplePing_withoutHost.h new file mode 100644 index 0000000..6d47461 --- /dev/null +++ b/MMLanScan/Misc/SimplePing_withoutHost.h @@ -0,0 +1,16 @@ +// +// SimplePing_withoutHost.h +// MMLanScanDemo +// +// Created by Jelle Alten on 10-06-17. +// Copyright © 2017 Miksoft. All rights reserved. +// + +#import "SimplePing.h" + +@interface SimplePing(withoutHost) + ++ (SimplePing*) simplePingWithIPAddress:(NSString*) ipAddress; +- (void)sendPingWithoutHostResolving; + +@end diff --git a/MMLanScan/Misc/SimplePing_withoutHost.m b/MMLanScan/Misc/SimplePing_withoutHost.m new file mode 100644 index 0000000..34e8809 --- /dev/null +++ b/MMLanScan/Misc/SimplePing_withoutHost.m @@ -0,0 +1,87 @@ +// +// SimplePing_withoutHost.m +// MMLanScanDemo +// +// Created by Jelle Alten on 10-06-17. +// Copyright © 2017 Miksoft. All rights reserved. +// + +#import "SimplePing_withoutHost.h" + +#include + +@interface SimplePing() + +- (void) setHostAddress:(NSData*) address; +- (void)startWithHostAddress; + +@end + +@implementation SimplePing(withoutHost) + ++ (SimplePing*) simplePingWithIPAddress:(NSString*) ipAddress { + + NSData* addrData = [self addrDataFromIP:ipAddress]; + return [[SimplePing alloc] initWithAddress:addrData]; +} + ++ (NSData *)addrDataFromIP:(NSString*) ipStr { + struct sockaddr_in remoteAddr; + remoteAddr.sin_len = sizeof(remoteAddr); + remoteAddr.sin_family = AF_INET; + remoteAddr.sin_port = htons(161); + const char *ipCStr = [ipStr UTF8String]; + inet_pton(AF_INET, ipCStr, &remoteAddr.sin_addr); + + return [NSData dataWithBytes:&remoteAddr length:remoteAddr.sin_len]; +} + + +- (instancetype) initWithAddress:(NSData*) address { + + if (self = [self initWithHostName:@""] ) { + + [self setAddress:address]; + return self; + } else { + + return nil; + } +} + +- (void)sendPingWithoutHostResolving { + + [self startWithHostAddress]; +} + + +- (void) setAddress:(NSData*) address { + + 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 != SimplePingAddressStyleICMPv6) { + + if ([self respondsToSelector:@selector(setHostAddress:)]) { + + [(id)self setHostAddress:address]; + } + } + } break; + case AF_INET6: { + if (self.addressStyle != SimplePingAddressStyleICMPv4) { + + if ([self respondsToSelector:@selector(setHostAddress:)]) { + + [(id)self setHostAddress:address]; + } + } + } break; + } + } +} + +@end diff --git a/MMLanScanDemo/MMLanScanDemo.xcodeproj/project.pbxproj b/MMLanScanDemo/MMLanScanDemo.xcodeproj/project.pbxproj index 6b14992..3f34d0b 100644 --- a/MMLanScanDemo/MMLanScanDemo.xcodeproj/project.pbxproj +++ b/MMLanScanDemo/MMLanScanDemo.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 279335FB1D5D2129001DFE84 /* SimplePing.m in Sources */ = {isa = PBXBuildFile; fileRef = 279335F31D5D2129001DFE84 /* SimplePing.m */; }; 279336081D5D2BDC001DFE84 /* NetworkCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 279336071D5D2BDC001DFE84 /* NetworkCalculator.m */; }; 27DBF0001E45FEF100685616 /* MacFinder.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DBEFFF1E45FEF100685616 /* MacFinder.m */; }; + 60E138DF1EEBF547007A0FA5 /* SimplePing_withoutHost.m in Sources */ = {isa = PBXBuildFile; fileRef = 60E138DE1EEBF547007A0FA5 /* SimplePing_withoutHost.m */; }; 27F0DFCF1F105E1B0043C777 /* MMDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F0DFCE1F105E1B0043C777 /* MMDevice.m */; }; 82194E9D1DCC7F49003F2BA1 /* MainPresenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 82194E9C1DCC7F49003F2BA1 /* MainPresenter.m */; }; 8224B5B81DCB7AAC00AE606F /* MACOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 8224B5B51DCB7AAC00AE606F /* MACOperation.m */; }; @@ -60,6 +61,8 @@ 27DBF0041E45FF4B00685616 /* route.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = route.h; sourceTree = ""; }; 27F0DFCD1F105E1B0043C777 /* MMDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMDevice.h; sourceTree = ""; }; 27F0DFCE1F105E1B0043C777 /* MMDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMDevice.m; sourceTree = ""; }; + 60E138DD1EEBF547007A0FA5 /* SimplePing_withoutHost.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SimplePing_withoutHost.h; path = ../../MMLanScan/Misc/SimplePing_withoutHost.h; sourceTree = ""; }; + 60E138DE1EEBF547007A0FA5 /* SimplePing_withoutHost.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SimplePing_withoutHost.m; path = ../../MMLanScan/Misc/SimplePing_withoutHost.m; sourceTree = ""; }; 82194E9B1DCC7F49003F2BA1 /* MainPresenter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainPresenter.h; sourceTree = ""; }; 82194E9C1DCC7F49003F2BA1 /* MainPresenter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainPresenter.m; sourceTree = ""; }; 8224B5B41DCB7AAC00AE606F /* MACOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MACOperation.h; path = ../../MMLanScan/Misc/MACOperation.h; sourceTree = ""; }; @@ -180,6 +183,7 @@ 279336001D5D2171001DFE84 /* Libraries */ = { isa = PBXGroup; children = ( + 60E138DC1EEBF511007A0FA5 /* Categories */, 270DDA011E4655C50041A61B /* Data */, 27F479D61DCBF1D500672012 /* LAN Scan Libs */, 27F479D41DCBF19D00672012 /* Operations */, @@ -253,6 +257,15 @@ name = "LAN Scan Libs"; sourceTree = ""; }; + 60E138DC1EEBF511007A0FA5 /* Categories */ = { + isa = PBXGroup; + children = ( + 60E138DD1EEBF547007A0FA5 /* SimplePing_withoutHost.h */, + 60E138DE1EEBF547007A0FA5 /* SimplePing_withoutHost.m */, + ); + name = Categories; + sourceTree = ""; + }; 82194E9A1DCC7F22003F2BA1 /* MainVC */ = { isa = PBXGroup; children = ( @@ -347,6 +360,7 @@ 27DBF0001E45FEF100685616 /* MacFinder.m in Sources */, 279335F81D5D2129001DFE84 /* MMLANScanner.m in Sources */, 279335FB1D5D2129001DFE84 /* SimplePing.m in Sources */, + 60E138DF1EEBF547007A0FA5 /* SimplePing_withoutHost.m in Sources */, 279335C71D5D1CB9001DFE84 /* main.m in Sources */, 279335F71D5D2129001DFE84 /* LANProperties.m in Sources */, 8224B5B91DCB7AAC00AE606F /* PingOperation.m in Sources */,