mirror of
https://github.com/qinyuhang/ShadowsocksX-NG-R.git
synced 2026-10-18 03:33:40 +00:00
@@ -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 = "<group>"; };
|
||||
083BF8321D82731900831C68 /* SimplePing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimplePing.m; sourceTree = "<group>"; };
|
||||
083BF8341D82742200831C68 /* NetWorkMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetWorkMonitor.swift; sourceTree = "<group>"; };
|
||||
083BF8351D82742200831C68 /* PingClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PingClient.swift; sourceTree = "<group>"; };
|
||||
083BF8381D82759600831C68 /* StatusItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusItemView.swift; sourceTree = "<group>"; };
|
||||
@@ -123,7 +119,6 @@
|
||||
08F162BD1EBEDF0100C52869 /* chn.acl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = chn.acl; sourceTree = "<group>"; };
|
||||
08F162BF1EBEE6F800C52869 /* backchn.acl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = backchn.acl; sourceTree = "<group>"; };
|
||||
08F162C11EBEEC0F00C52869 /* gfwlist.acl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = gfwlist.acl; sourceTree = "<group>"; };
|
||||
08FFAC611E437F6E00BDF137 /* PingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PingTest.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>";
|
||||
};
|
||||
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/",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
|
||||
import Foundation
|
||||
|
||||
fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (l?, r?):
|
||||
@@ -22,116 +23,208 @@ fileprivate func < <T : Comparable>(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..<SerMgr.profiles.count {
|
||||
let host = self.SerMgr.profiles[k].serverHost
|
||||
pingSingleHost(host: host, completionHandler: {
|
||||
if let latency = $0{
|
||||
self.SerMgr.profiles[k].latency = String(latency)
|
||||
|
||||
synchronized(lock: result as AnyObject){
|
||||
result.append(k,latency)
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
// do the UI update HERE
|
||||
(NSApplication.shared().delegate as! AppDelegate).updateServersMenu()
|
||||
(NSApplication.shared().delegate as! AppDelegate).updateRunningModeMenu()
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
// do the UI update HERE
|
||||
(NSApplication.shared().delegate as! AppDelegate).updateServersMenu()
|
||||
(NSApplication.shared().delegate as! AppDelegate).updateRunningModeMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
self.ping(i+1)
|
||||
|
||||
|
||||
|
||||
})
|
||||
}
|
||||
// after two seconds ,time out
|
||||
delay(3){
|
||||
DispatchQueue.main.async {
|
||||
// do the UI update HERE
|
||||
if let min = result.min(by: {$0.1 < $1.1}){
|
||||
self.fastest = String(describing: min.1)
|
||||
self.fastest_id = min.0
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typealias Task = (_ cancel : Bool) -> 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
|
||||
|
||||
@@ -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"]]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,4 +9,4 @@
|
||||
#import "Utils.h"
|
||||
#import "ProxyConfHelper.h"
|
||||
#import "ProxyConfTool.h"
|
||||
#import "SimplePing.h"
|
||||
|
||||
|
||||
@@ -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 <AssertMacros.h> // 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<SimplePingDelegate> 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 <NSObject>
|
||||
|
||||
@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
|
||||
@@ -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 <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <errno.h>
|
||||
|
||||
#pragma mark * IPv4 and ICMPv4 On-The-Wire Format
|
||||
|
||||
/*! Describes the on-the-wire header format for an IPv4 packet.
|
||||
* \details This defines the header structure of IPv4 packets on the wire. We need
|
||||
* this in order to skip this header in the IPv4 case, where the kernel passes
|
||||
* it to us for no obvious reason.
|
||||
*/
|
||||
|
||||
struct IPv4Header {
|
||||
uint8_t versionAndHeaderLength;
|
||||
uint8_t differentiatedServices;
|
||||
uint16_t totalLength;
|
||||
uint16_t identification;
|
||||
uint16_t flagsAndFragmentOffset;
|
||||
uint8_t timeToLive;
|
||||
uint8_t protocol;
|
||||
uint16_t headerChecksum;
|
||||
uint8_t sourceAddress[4];
|
||||
uint8_t destinationAddress[4];
|
||||
// options...
|
||||
// data...
|
||||
};
|
||||
typedef struct IPv4Header IPv4Header;
|
||||
|
||||
__Check_Compile_Time(sizeof(IPv4Header) == 20);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, versionAndHeaderLength) == 0);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, differentiatedServices) == 1);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, totalLength) == 2);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, identification) == 4);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, flagsAndFragmentOffset) == 6);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, timeToLive) == 8);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, protocol) == 9);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, headerChecksum) == 10);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, sourceAddress) == 12);
|
||||
__Check_Compile_Time(offsetof(IPv4Header, destinationAddress) == 16);
|
||||
|
||||
/*! Calculates an IP checksum.
|
||||
* \details This is the standard BSD checksum code, modified to use modern types.
|
||||
* \param buffer A pointer to the data to checksum.
|
||||
* \param bufferLen The length of that data.
|
||||
* \returns The checksum value, in network byte order.
|
||||
*/
|
||||
|
||||
static uint16_t in_cksum(const void *buffer, size_t bufferLen) {
|
||||
//
|
||||
size_t bytesLeft;
|
||||
int32_t sum;
|
||||
const uint16_t * cursor;
|
||||
union {
|
||||
uint16_t us;
|
||||
uint8_t uc[2];
|
||||
} last;
|
||||
uint16_t answer;
|
||||
|
||||
bytesLeft = bufferLen;
|
||||
sum = 0;
|
||||
cursor = buffer;
|
||||
|
||||
/*
|
||||
* Our algorithm is simple, using a 32 bit accumulator (sum), we add
|
||||
* sequential 16 bit words to it, and at the end, fold back all the
|
||||
* carry bits from the top 16 bits into the lower 16 bits.
|
||||
*/
|
||||
while (bytesLeft > 1) {
|
||||
sum += *cursor;
|
||||
cursor += 1;
|
||||
bytesLeft -= 2;
|
||||
}
|
||||
|
||||
/* mop up an odd byte, if necessary */
|
||||
if (bytesLeft == 1) {
|
||||
last.uc[0] = * (const uint8_t *) cursor;
|
||||
last.uc[1] = 0;
|
||||
sum += last.us;
|
||||
}
|
||||
|
||||
/* add back carry outs from top 16 bits to low 16 bits */
|
||||
sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
|
||||
sum += (sum >> 16); /* add carry */
|
||||
answer = (uint16_t) ~sum; /* truncate to 16 bits */
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
#pragma mark * 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<SimplePingDelegate> 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<SimplePingDelegate> 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 <x-man-page://8/ping> 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<SimplePingDelegate> 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<SimplePingDelegate> 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
|
||||
Reference in New Issue
Block a user