diff --git a/ShadowsocksX-NG.xcodeproj/project.pbxproj b/ShadowsocksX-NG.xcodeproj/project.pbxproj index 1b234d7..98e996b 100644 --- a/ShadowsocksX-NG.xcodeproj/project.pbxproj +++ b/ShadowsocksX-NG.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 083BF8331D82731900831C68 /* SimplePing.m in Sources */ = {isa = PBXBuildFile; fileRef = 083BF8321D82731900831C68 /* SimplePing.m */; }; 083BF8361D82742200831C68 /* NetWorkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083BF8341D82742200831C68 /* NetWorkMonitor.swift */; }; 083BF8371D82742200831C68 /* PingClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083BF8351D82742200831C68 /* PingClient.swift */; }; 083BF8391D82759600831C68 /* StatusItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083BF8381D82759600831C68 /* StatusItemView.swift */; }; @@ -20,7 +19,6 @@ 08F162BE1EBEDF0100C52869 /* chn.acl in Resources */ = {isa = PBXBuildFile; fileRef = 08F162BD1EBEDF0100C52869 /* chn.acl */; }; 08F162C01EBEE6F800C52869 /* backchn.acl in Resources */ = {isa = PBXBuildFile; fileRef = 08F162BF1EBEE6F800C52869 /* backchn.acl */; }; 08F162C21EBEEC0F00C52869 /* gfwlist.acl in Resources */ = {isa = PBXBuildFile; fileRef = 08F162C11EBEEC0F00C52869 /* gfwlist.acl */; }; - 08FFAC621E437F6E00BDF137 /* PingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FFAC611E437F6E00BDF137 /* PingTest.swift */; }; 258E511BA910B0521B24DAB8 /* Pods_ShadowsocksX_NG.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 283ED1A8E9B711AC65670031 /* Pods_ShadowsocksX_NG.framework */; }; 9B07EFA71D048BBB0052D9DF /* ss-local in Resources */ = {isa = PBXBuildFile; fileRef = 9B07EFA61D048BBB0052D9DF /* ss-local */; }; 9B07EFAC1D048E880052D9DF /* menu_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9B07EFA81D048E880052D9DF /* menu_icon@2x.png */; }; @@ -107,8 +105,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 083BF8311D82731900831C68 /* SimplePing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimplePing.h; sourceTree = ""; }; - 083BF8321D82731900831C68 /* SimplePing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimplePing.m; sourceTree = ""; }; 083BF8341D82742200831C68 /* NetWorkMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetWorkMonitor.swift; sourceTree = ""; }; 083BF8351D82742200831C68 /* PingClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PingClient.swift; sourceTree = ""; }; 083BF8381D82759600831C68 /* StatusItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusItemView.swift; sourceTree = ""; }; @@ -123,7 +119,6 @@ 08F162BD1EBEDF0100C52869 /* chn.acl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = chn.acl; sourceTree = ""; }; 08F162BF1EBEE6F800C52869 /* backchn.acl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = backchn.acl; sourceTree = ""; }; 08F162C11EBEEC0F00C52869 /* gfwlist.acl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = gfwlist.acl; sourceTree = ""; }; - 08FFAC611E437F6E00BDF137 /* PingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PingTest.swift; sourceTree = ""; }; 19083CFCED87354F006967FF /* Pods_ShadowsocksX_NGUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShadowsocksX_NGUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1E7783AEDB4A3BDDC9FF16AC /* libPods-proxy_conf_helper.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-proxy_conf_helper.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 283ED1A8E9B711AC65670031 /* Pods_ShadowsocksX_NG.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShadowsocksX_NG.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -237,15 +232,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 083BF8301D82731900831C68 /* SimplePing */ = { - isa = PBXGroup; - children = ( - 083BF8311D82731900831C68 /* SimplePing.h */, - 083BF8321D82731900831C68 /* SimplePing.m */, - ); - path = SimplePing; - sourceTree = ""; - }; 33F4F299F89330966F4526E4 /* Pods */ = { isa = PBXGroup; children = ( @@ -322,7 +308,6 @@ 9B0BFFE71D0460A70040E62B /* ShadowsocksX-NG */ = { isa = PBXGroup; children = ( - 083BF8301D82731900831C68 /* SimplePing */, 9BB706A51D1B982300551F0E /* SWBApplication.m */, 9BB706A61D1B982300551F0E /* SWBApplication.h */, 9B3FFF511D09DBA20019A709 /* ShadowsocksX-NG-Bridging-Header.h */, @@ -344,7 +329,6 @@ 9B3FFF201D08826E0019A709 /* PACUtils.swift */, 083BF8341D82742200831C68 /* NetWorkMonitor.swift */, 083BF8351D82742200831C68 /* PingClient.swift */, - 08FFAC611E437F6E00BDF137 /* PingTest.swift */, 9B3FFF131D0705810019A709 /* Notifications.swift */, 9B3FFF1D1D0732660019A709 /* Utils.m */, 9B3FFF1F1D0734060019A709 /* Utils.h */, @@ -483,7 +467,6 @@ TargetAttributes = { 9B0BFFE41D0460A70040E62B = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = 2E8Q4BTFR2; LastSwiftMigration = 0800; ProvisioningStyle = Automatic; }; @@ -709,8 +692,6 @@ 08BDBB481E2361BD00C4BBC2 /* VersionChecker.swift in Sources */, 083BF8371D82742200831C68 /* PingClient.swift in Sources */, 083BF8361D82742200831C68 /* NetWorkMonitor.swift in Sources */, - 08FFAC621E437F6E00BDF137 /* PingTest.swift in Sources */, - 083BF8331D82731900831C68 /* SimplePing.m in Sources */, 9B3FFF211D08826E0019A709 /* PACUtils.swift in Sources */, 9BA04B261D24044D005AAD7F /* ProxyPreferencesController.swift in Sources */, 9B3FFF141D0705810019A709 /* Notifications.swift in Sources */, @@ -927,7 +908,6 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = 2E8Q4BTFR2; HEADER_SEARCH_PATHS = ( "$(SRCROOT)/libcork/include/", "$(SRCROOT)/libipset/include/", @@ -958,7 +938,6 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = 2E8Q4BTFR2; HEADER_SEARCH_PATHS = ( "$(SRCROOT)/libcork/include/", "$(SRCROOT)/libipset/include/", diff --git a/ShadowsocksX-NG/AppDelegate.swift b/ShadowsocksX-NG/AppDelegate.swift index 72b53ac..192c6d0 100644 --- a/ShadowsocksX-NG/AppDelegate.swift +++ b/ShadowsocksX-NG/AppDelegate.swift @@ -517,6 +517,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele if let latency = p.latency{ serverMenuText += " - \(latency) ms" } + else{ + if !neverSpeedTestBefore { + serverMenuText += " - failed" + } + } } } @@ -624,6 +629,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele item.title = p.title() if let latency = p.latency{ item.title += " - \(latency) ms" + }else{ + if !neverSpeedTestBefore { + item.title += " - failed" + } } if mgr.activeProfileId == p.uuid { item.state = 1 diff --git a/ShadowsocksX-NG/PingClient.swift b/ShadowsocksX-NG/PingClient.swift index b8080b4..b17c1a5 100644 --- a/ShadowsocksX-NG/PingClient.swift +++ b/ShadowsocksX-NG/PingClient.swift @@ -8,6 +8,7 @@ import Foundation + fileprivate func < (lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): @@ -22,116 +23,208 @@ fileprivate func < (lhs: T?, rhs: T?) -> Bool { public typealias SimplePingClientCallback = (String?)->() -open class SimplePingClient: NSObject { - static let singletonPC = SimplePingClient() - - fileprivate var resultCallback: SimplePingClientCallback? - fileprivate var pingClinet: SimplePing? - fileprivate var dateReference: Date? - - open static func pingHostname(_ hostname: String, andResultCallback callback: SimplePingClientCallback?) { - singletonPC.pingHostname(hostname, andResultCallback: callback) - } - - open func pingHostname(_ hostname: String, andResultCallback callback: SimplePingClientCallback?) { - resultCallback = callback - pingClinet = SimplePing(hostName: hostname) - pingClinet?.delegate = self - pingClinet?.start() - } -} - -extension SimplePingClient: SimplePingDelegate { - public func simplePing(_ pinger: SimplePing, didStartWithAddress address: Data) { - pinger.send(with: nil) - - - } - - @nonobjc public func simplePing(_ pinger: SimplePing, didFailWithError error: NSError) { - resultCallback?(nil) - } - - public func simplePing(_ pinger: SimplePing, didSendPacket packet: Data, sequenceNumber: UInt16) { - dateReference = Date() - } - - @nonobjc public func simplePing(_ pinger: SimplePing, didFailToSendPacket packet: Data, sequenceNumber: UInt16, error: NSError) { - pinger.stop() - resultCallback?(nil) - } - - public func simplePing(_ pinger: SimplePing, didReceiveUnexpectedPacket packet: Data) { - pinger.stop() - resultCallback?(nil) - } - - public func simplePing(_ pinger: SimplePing, didReceivePingResponsePacket packet: Data, sequenceNumber: UInt16 ){ - pinger.stop() - - guard let dateReference = dateReference else{return } - - //timeIntervalSinceDate returns seconds, so we convert to milis - let latency = Date().timeIntervalSince(dateReference) * 1000 - resultCallback?(String(format: "%.f", latency)) - } -} - class PingServers:NSObject{ static let instance = PingServers() - + let SerMgr = ServerProfileManager.instance var fastest:String? var fastest_id : Int=0 + + // func ping(_ i:Int=0){ + // if i == 0{ + // fastest_id = 0 + // fastest = nil + // } + // + // if i >= SerMgr.profiles.count{ + // DispatchQueue.main.async { + // // do the UI update HERE + // let notice = NSUserNotification() + // notice.title = "Ping测试完成!" + // notice.subtitle = "最快的是\(self.SerMgr.profiles[self.fastest_id].remark) \(self.SerMgr.profiles[self.fastest_id].serverHost) \(self.SerMgr.profiles[self.fastest_id].latency!)ms" + // NSUserNotificationCenter.default.deliver(notice) + // } + // return + // } + // let host = self.SerMgr.profiles[i].serverHost + // SimplePingClient.pingHostname(host) { latency in + // DispatchQueue.global().async { + // print("[Ping Result]-\(host) latency is \(latency ?? "fail")") + // self.SerMgr.profiles[i].latency = latency ?? "fail" + // + // if latency != nil { + // if self.fastest == nil{ + // self.fastest = latency + // self.fastest_id = i + // }else{ + // if Int(latency!) < Int(self.fastest!) { + // self.fastest = latency + // self.fastest_id = i + // } + // } + // DispatchQueue.main.async { + // // do the UI update HERE + // (NSApplication.shared().delegate as! AppDelegate).updateServersMenu() + // (NSApplication.shared().delegate as! AppDelegate).updateRunningModeMenu() + // } + // } + // } + // self.ping(i+1) + // } + // } + + func runCommand(cmd : String, args : String...) -> (output: [String], error: [String], exitCode: Int32) { + + var output : [String] = [] + var error : [String] = [] + + let task = Process() + task.launchPath = cmd + task.arguments = args + + let outpipe = Pipe() + task.standardOutput = outpipe + let errpipe = Pipe() + task.standardError = errpipe + + task.launch() + + let outdata = outpipe.fileHandleForReading.readDataToEndOfFile() + if var string = String(data: outdata, encoding: .utf8) { + string = string.trimmingCharacters(in: .newlines) + output = string.components(separatedBy: "\n") + } + + let errdata = errpipe.fileHandleForReading.readDataToEndOfFile() + if var string = String(data: errdata, encoding: .utf8) { + string = string.trimmingCharacters(in: .newlines) + error = string.components(separatedBy: "\n") + } + + task.waitUntilExit() + let status = task.terminationStatus + + return (output, error, status) + } + + func getlatencyFromString(result:String) -> Double?{ + var res = result + if !result.contains("round-trip min/avg/max/stddev =") { + return nil + } + res.removeSubrange(res.range(of: "round-trip min/avg/max/stddev = ")!) + res = String(res.characters.dropLast(3)) + res = res.components(separatedBy: "/")[1] + let latency = Double(res) + return latency + } + + func pingSingleHost(host:String,completionHandler:@escaping (Double?) -> Void){ + DispatchQueue.global(qos: .userInteractive).async { + if let outputString = self.runCommand(cmd: "/sbin/ping", args: "-c","1","-t","1.5",host).output.last{ + completionHandler(self.getlatencyFromString(result: outputString)) + } + } + } + + func ping(_ i:Int=0){ - if i == 0{ - fastest_id = 0 - fastest = nil - } - - if i >= SerMgr.profiles.count{ - DispatchQueue.main.async { - // do the UI update HERE - let notice = NSUserNotification() - notice.title = "Ping测试完成!" - notice.subtitle = "最快的是\(self.SerMgr.profiles[self.fastest_id].remark) \(self.SerMgr.profiles[self.fastest_id].serverHost) \(self.SerMgr.profiles[self.fastest_id].latency!)ms" - NSUserNotificationCenter.default.deliver(notice) - } - return - } - let host = self.SerMgr.profiles[i].serverHost - SimplePingClient.pingHostname(host) { latency in - DispatchQueue.global().async { - print("[Ping Result]-\(host) latency is \(latency ?? "fail")") - self.SerMgr.profiles[i].latency = latency ?? "fail" - - if latency != nil { - if self.fastest == nil{ - self.fastest = latency - self.fastest_id = i - }else{ - if Int(latency!) < Int(self.fastest!) { - self.fastest = latency - self.fastest_id = i + + neverSpeedTestBefore = false + + var result:[(Int,Double)] = [] + + for k in 0.. Void +@discardableResult func delay(_ time: TimeInterval, task: @escaping ()->()) -> Task? { + + func dispatch_later(block: @escaping ()->()) { + let t = DispatchTime.now() + time + DispatchQueue.main.asyncAfter(deadline: t, execute: block) + } + + + + var closure: (()->Void)? = task + var result: Task? + + let delayedClosure: Task = { + cancel in + if let internalClosure = closure { + if (cancel == false) { + DispatchQueue.main.async(execute: internalClosure) + } + } + closure = nil + result = nil + } + + result = delayedClosure + + dispatch_later { + if let delayedClosure = result { + delayedClosure(false) + } + } + + return result; + +} +func synchronized(lock: AnyObject, closure: () -> ()) { + objc_sync_enter(lock) + closure() + objc_sync_exit(lock) +} +func cancel(_ task: Task?) { + task?(true) +} +var neverSpeedTestBefore:Bool = true diff --git a/ShadowsocksX-NG/PingTest.swift b/ShadowsocksX-NG/PingTest.swift deleted file mode 100644 index d243132..0000000 --- a/ShadowsocksX-NG/PingTest.swift +++ /dev/null @@ -1,166 +0,0 @@ -// -// PingTest.swift -// ShadowsocksX-NG -// -// Created by 秦宇航 on 16/9/12. -// Copyright © 2016年 qiuyuzhou. All rights reserved. -// - -import Foundation - -class PingTest: NSObject, SimplePingDelegate { - static let instance = PingTest(hostName: "") - //then 写一个init函数,传入hostName来init - var hostName = "baidu.com" - - var pinger: SimplePing? - var sendTimer: Timer? - var dateReference: Date? - - /// Called by the table view selection delegate callback to start the ping. - init(hostName:String) { - self.hostName = hostName - } - func start() { - - NSLog("start ping %@",self.hostName) - - let pinger = SimplePing(hostName: self.hostName) - self.pinger = pinger - - pinger.delegate = self - pinger.start() - } - - /// Called by the table view selection delegate callback to stop the ping. - - func stop() { - NSLog("stop") - self.pinger?.stop() - self.pinger = nil - - self.sendTimer?.invalidate() - self.sendTimer = nil - } - - /// Sends a ping. - /// - /// Called to send a ping, both directly (as soon as the SimplePing object starts up) and - /// via a timer (to continue sending pings periodically). - - func sendPing() { - self.pinger!.send(with: nil) - } - - // MARK: pinger delegate callback - - func simplePing(_ pinger: SimplePing, didStartWithAddress address: Data) { - NSLog("pinging %@",hostName) - - // Send the first ping straight away. - - self.sendPing() - - // And start a timer to send the subsequent pings. - - assert(self.sendTimer == nil) - self.sendTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(PingTest.sendPing), userInfo: nil, repeats: true) - } - - func simplePing(_ pinger: SimplePing, didFailWithError error: Error) { - NSLog("failed: %@", PingTest.shortErrorFromError(error as NSError)) - - self.stop() - } - - func simplePing(_ pinger: SimplePing, didSendPacket packet: Data, sequenceNumber: UInt16) { - NSLog("#%u sent", sequenceNumber) - dateReference = Date() - } - - func simplePing(_ pinger: SimplePing, didFailToSendPacket packet: Data, sequenceNumber: UInt16, error: Error) { - NSLog("#%u send failed: %@", sequenceNumber, PingTest.shortErrorFromError(error as NSError)) - } - - func simplePing(_ pinger: SimplePing, didReceivePingResponsePacket packet: Data, sequenceNumber: UInt16) { - - pinger.stop() - - guard let dateReference = dateReference else { return } - - //timeIntervalSinceDate returns seconds, so we convert to milis - DispatchQueue.main.async(execute: { - let latency = Date().timeIntervalSince(dateReference) * 1000 - NSLog("#%u received, host=%@, size=%zu, latency=%.f", sequenceNumber, self.hostName , packet.count,latency) - let userNote = NSUserNotification() - userNote.title = "\(self.hostName) ping \(latency)".localized - userNote.subtitle = "Address can't not be recognized".localized - NSUserNotificationCenter.default - .deliver(userNote) - - }) - } - - func simplePing(_ pinger: SimplePing, didReceiveUnexpectedPacket packet: Data) { - NSLog("unexpected packet, size=%zu", packet.count) - } - - // MARK: utilities - - /// Returns the string representation of the supplied address. - /// - /// - parameter address: Contains a `(struct sockaddr)` with the address to render. - /// - /// - returns: A string representation of that address. - - static func displayAddressForAddress(_ address: Data) -> String { - var hostStr = [Int8](repeating: 0, count: Int(NI_MAXHOST)) - - let success = getnameinfo( - (address as NSData).bytes.bindMemory(to: sockaddr.self, capacity: address.count), - socklen_t(address.count), - &hostStr, - socklen_t(hostStr.count), - nil, - 0, - NI_NUMERICHOST - ) == 0 - let result: String - if success { - result = String(cString: hostStr) - } else { - result = "?" - } - return result - } - - /// Returns a short error string for the supplied error. - /// - /// - parameter error: The error to render. - /// - /// - returns: A short string representing that error. - - static func shortErrorFromError(_ error: NSError) -> String { - if error.domain == kCFErrorDomainCFNetwork as String && error.code == Int(CFNetworkErrors.cfHostErrorUnknown.rawValue) { - if let failureObj = error.userInfo[kCFGetAddrInfoFailureKey as AnyHashable] { - if let failureNum = failureObj as? NSNumber { - if failureNum.int32Value != 0 { - let f = gai_strerror(failureNum.int32Value) - if f != nil { - return String(cString: f!) - } - } - } - } - } - if let result = error.localizedFailureReason { - return result - } - return error.localizedDescription - } - class func pingArray(){//hostList : [String]) -> [[String : String]]{ -// return [["apple.com":"20"],["baidu.com":"20"]] - } -} - - diff --git a/ShadowsocksX-NG/ShadowsocksX-NG-Bridging-Header.h b/ShadowsocksX-NG/ShadowsocksX-NG-Bridging-Header.h index fce5bee..5afcbed 100644 --- a/ShadowsocksX-NG/ShadowsocksX-NG-Bridging-Header.h +++ b/ShadowsocksX-NG/ShadowsocksX-NG-Bridging-Header.h @@ -9,4 +9,4 @@ #import "Utils.h" #import "ProxyConfHelper.h" #import "ProxyConfTool.h" -#import "SimplePing.h" + diff --git a/ShadowsocksX-NG/SimplePing/SimplePing.h b/ShadowsocksX-NG/SimplePing/SimplePing.h deleted file mode 100644 index d49e00b..0000000 --- a/ShadowsocksX-NG/SimplePing/SimplePing.h +++ /dev/null @@ -1,272 +0,0 @@ -/* - 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 - -- (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, 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; - -/*! 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)sendPingWithData:(nullable NSData *)data; - -/*! 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 - -/*! 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. - */ - -- (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address; - -/*! 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; - -/*! 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; - -@end - -#pragma mark * 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 SimplePing might want to use - * it parse received ping packets. - */ - -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); - -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/ShadowsocksX-NG/SimplePing/SimplePing.m b/ShadowsocksX-NG/SimplePing/SimplePing.m deleted file mode 100644 index 0e60f82..0000000 --- a/ShadowsocksX-NG/SimplePing/SimplePing.m +++ /dev/null @@ -1,782 +0,0 @@ -/* - 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" - -#include -#include -#include - -#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 * SimplePing - -@interface SimplePing () - -// 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 SimplePing - -- (instancetype)initWithHostName:(NSString *)hostName { - NSParameterAssert(hostName != nil); - self = [super init]; - if (self != nil) { - self->_hostName = [hostName copy]; - self->_identifier = (uint16_t) arc4random(); - } - return self; -} - -- (void)dealloc { - [self stop]; - // Double check that -stop took care of _host and _socket. - assert(self->_host == NULL); - assert(self->_socket == NULL); -} - -- (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 strongDelegate; - - assert(error != nil); - - // 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(simplePing:didFailWithError:)] ) { - [strongDelegate simplePing: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]; - 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. - - 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]; - 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); - } - - 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); - - // 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(simplePing:didSendPacket:sequenceNumber:)] ) { - [strongDelegate simplePing: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(simplePing:didFailToSendPacket:sequenceNumber:error:)] ) { - [strongDelegate simplePing: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 uses). - - buffer = malloc(kBufferSize); - assert(buffer != NULL); - - // 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 strongDelegate; - uint16_t sequenceNumber; - - packet = [NSMutableData dataWithBytes:buffer length:(NSUInteger) bytesRead]; - assert(packet != nil); - - // 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(simplePing:didReceivePingResponsePacket:sequenceNumber:)] ) { - [strongDelegate simplePing:self didReceivePingResponsePacket:packet sequenceNumber:sequenceNumber]; - } - } else { - if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didReceiveUnexpectedPacket:)] ) { - [strongDelegate simplePing: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. - SimplePing * obj; - - obj = (__bridge SimplePing *) info; - assert([obj isKindOfClass:[SimplePing class]]); - - #pragma unused(s) - assert(s == obj.socket); - #pragma unused(type) - assert(type == kCFSocketReadCallBack); - #pragma unused(address) - assert(address == nil); - #pragma unused(data) - assert(data == nil); - - [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 { - int err; - int fd; - - assert(self.hostAddress != nil); - - // 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 strongDelegate; - - // Wrap it in a CFSocket and schedule it on the runloop. - - 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 ); - fd = -1; - - rls = CFSocketCreateRunLoopSource(NULL, self.socket, 0); - assert(rls != NULL); - - CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); - - CFRelease(rls); - - strongDelegate = self.delegate; - if ( (strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didStartWithAddress:)] ) { - [strongDelegate simplePing:self didStartWithAddress:self.hostAddress]; - } - } - assert(fd == -1); -} - -/*! 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 != SimplePingAddressStyleICMPv6) { - self.hostAddress = address; - resolved = true; - } - } break; - case AF_INET6: { - if (self.addressStyle != SimplePingAddressStyleICMPv4) { - 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. - SimplePing * obj; - - obj = (__bridge SimplePing *) info; - assert([obj isKindOfClass:[SimplePing class]]); - - #pragma unused(theHost) - assert(theHost == obj.host); - #pragma unused(typeInfo) - assert(typeInfo == kCFHostAddresses); - - if ( (error != NULL) && (error->domain != 0) ) { - [obj didFailWithHostStreamError:*error]; - } else { - [obj hostResolutionDone]; - } -} - -- (void)start { - Boolean success; - CFHostClientContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; - CFStreamError streamError; - - assert(self.host == NULL); - assert(self.hostAddress == nil); - - self.host = (CFHostRef) CFAutorelease( CFHostCreateWithName(NULL, (__bridge CFStringRef) self.hostName) ); - assert(self.host != NULL); - - 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