Giter VIP home page Giter VIP logo

swiftyping's Introduction

SwiftyPing

ICMP ping client for Swift 5

SwiftyPing is an easy-to-use, one file ICMP ping client

This project is based on SwiftPing: https://github.com/ankitthakur/SwiftPing.

Usage

// Ping indefinitely
let pinger = try? SwiftyPing(host: "1.1.1.1", configuration: PingConfiguration(interval: 0.5, with: 5), queue: DispatchQueue.global())
pinger?.observer = { (response) in
    let duration = response.duration
    print(duration)
}
try? pinger?.startPinging()

// Ping once
let once = try? SwiftyPing(host: "1.1.1.1", configuration: PingConfiguration(interval: 0.5, with: 5), queue: DispatchQueue.global())
once?.observer = { (response) in
    let duration = response.duration
    print(duration)
}
once?.targetCount = 1
try? once?.startPinging()

Installation

Just drop the SwiftyPing.swift file to your project. Using SwiftyPing for a Mac application requires allowing Network->Incoming Connections and Network->Outgoing Connections in the application sandbox.

You can also use Swift Package Manager:

.Package(url: "https://github.com/samiyr/SwiftyPing.git", branch: "master")

Future development and contributions

I made this project based on what I need, so I probably won't be adding any features unless I really need them. I will maintain it (meaning bug fixes and support for new Swift versions) for some time at least. However, you can submit a pull request and I'll take a look. Please try to keep the overall coding style.

Caveats

This is low-level code, basically C code translated to Swift. This means that there are unsafe casts from raw bytes to Swift structs, for which Swift's usual type safety checks no longer apply. These can fail ungracefully (throwing an exception), and may even be used as an exploit (I'm not a security researcher and thus don't have the expertise to say for sure), so use with caution, especially if pinging untrusted hosts.

Also, while I think that the API is now stable, I don't make any guarantees – some new version might break old stuff.

License

Use pretty much however you want. Officially licensed under MIT.

swiftyping's People

Contributors

cgcym1234 avatar darrellroot avatar darrellroot3 avatar ilg avatar kususumu avatar samiyr avatar samiyrj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

swiftyping's Issues

Properly analyze the ping response

It looks like swiftyping does not do anything with the ping response. I can understand because the data that comes in is difficult to parse. On my mac, after analyzing hexdumps, I found that the sourceip of the icmp response starts at byte 60 of the buffer pointed to by the data structure.

The other key is to verify that the return icmp sequence and return icmp id match what you sent. They are at byte 72 and 74 of the data buffer.

Below is a hack, but at least it's working for me today. Feel free to email me at darrellroot AT mac.com if more data would be helpful.

Calling a function from the callback is hard because use of a C pointer makes using self. not allowed. Fortunately I was able to use a delegate.

Beware byte ordering. I'm messing up little-endian/big-endian all the time on MacOS.

        self.pingSocket = CFSocketCreate(kCFAllocatorDefault, AF_INET, SOCK_DGRAM, IPPROTO_ICMP,CFSocketCallBackType.dataCallBack.rawValue, {socket, type, address, data, info in
            //type is CFSocketCallBackType
            guard let socket = socket, let address = address, let data = data, let info = info else { return }
            //print("callback socket \(socket)")
            //print("callback address \(address)")
            //print("callback data \(data)")
            //print("callback info \(info)")
            //print("callback type \(type)")
            //print("got callback")
            let typedData = data.bindMemory(to: UInt8.self, capacity: 80)
            let sourceOctet1 = typedData.advanced(by: 60).pointee
            let sourceOctet2 = typedData.advanced(by: 61).pointee
            let sourceOctet3 = typedData.advanced(by: 62).pointee
            let sourceOctet4 = typedData.advanced(by: 63).pointee
            let sequenceHighByte = typedData.advanced(by: 72).pointee
            let sequenceLowByte = typedData.advanced(by: 73).pointee
            let idHighByte = typedData.advanced(by: 74).pointee
            let idLowByte = typedData.advanced(by: 75).pointee
            print("received icmp from \(sourceOctet1).\(sourceOctet2).\(sourceOctet3).\(sourceOctet4)")
            let sequence = UInt16(sequenceHighByte) * 256 + UInt16(sequenceLowByte)
            let id = UInt16(idHighByte) * 256 + UInt16(idLowByte)
            print("sequence \(sequence) id \(id)")
            let sourceIP: UInt32 = UInt32(sourceOctet1) * 256 * 256 * 256 + UInt32(sourceOctet2) * 256 * 256 + UInt32(sourceOctet3) * 256 + UInt32(sourceOctet4)
            //if let receivingMonitor = self.ipv4Monitors[sourceIP] {
               // receivingMonitor.receivedPing()
            let appDelegate = NSApplication.shared.delegate as! AppDelegate
            appDelegate.receivedPing(ip: sourceIP, sequence: sequence, id: id)
            //}
            return
        }, &context)

Originally posted by @darrellroot in #3 (comment)

Mac OS support

Do you have any plans to support Mac OS?
I have a project for mac, and I really need pinging
Now if I try to use this I got an error SwiftyPing.PingError.invalidLength(received: 0) and it ignoring time interval, but in Ios it working perfectly

Timeout not working?

While this seems to be the best code around to send plain ICMP pings efficiently on iOS, I have a problem with the timeout.

I tried to set the timeout to 1s:

let once = SwiftyPing(host: "1.1.1.1", configuration: PingConfiguration(interval: 1,with: 1), queue: DispatchQueue.global())
        once?.observer = { (_, response) in
            let duration = response.duration
            print(duration)
            once?.stop()
            print("stopped")
        }
        once?.start()

However, I don't get a timeout. If there is e.g. a 2s latency, the observer just normally gets called after 2s printing the latency.
How can I get a real timeout, i.e. that my function above or another one gets called on timeout?

Note: I used the fix in #7 to have just a single ping.

M1 chip error

Hi team,

M1-chip macs cannot build this lib, could you help fix it ?

Thanks

Checksum Error and Possible Fix

I am encountering the classic fatal error: Negative value is not representable on the penultimate line of SwiftyPing@computeCheckSum. The value being wrapped by UInt16 is less than zero for some reason, and so cannot be cast to a UInt. I encounter this approximately one in every thousand test pings to 8.8.8.8 (google)

I have manually patched my version of this with a simple max(*, 0) as follows:

let answer = UInt16(max(Int32(UInt16.max) + ~checksum, 0)) + 1

It Works™ - however, I am unsure as to the ramifications of this in terms of checksum validity, as i don't fully understand what is happening in the method itself, and as such am uncomfortable PRing this directly in.

Feature Request: Resolve Hostname from IP

Can you add the option to resolve the host name of the IP that is being pinged? I see that you are already doing the other way around and resolving the IP from Host Name.

finished triggering twice when using MacCatalyst

Not entirely sure why, but when referencing a ping instance inside observer logic, when entering background via NotificationCenter, it gets triggered twice.

Error triggering example:

let pinger: SwiftyPing
let config = PingConfiguration(interval: 1, with: 5)
if IPv4Address(address) != nil {
    pinger = try .init(ipv4Address: address, config: config, queue: .global())
} else {
    pinger = try .init(host: address, configuration: config, queue: .global())
}
pinger.targetCount = 3
pinger.observer = { _ in
    if false {
        pinger.haltPinging()
    }
}
pinger.finished = { (response) in 
   print("Foo")
}

Not triggering example:

let pinger: SwiftyPing
let config = PingConfiguration(interval: 1, with: 5)
if IPv4Address(address) != nil {
    pinger = try .init(ipv4Address: address, config: config, queue: .global())
} else {
    pinger = try .init(host: address, configuration: config, queue: .global())
}
pinger.targetCount = 3
pinger.observer = { _ in
    print("Yeah, no idea why")
}
pinger.finished = { (response) in 
   print("Foo")
}

From what I'm seeing in the code private func addAppStateNotifications() { does have a #if os(iOS) so I'll open a PR adding to that with a && !targetEnvironment(macCatalyst) which should do the trick as I'm assuming there's no need to halt pinging when moving to background on Mac, but it doesn't quite explain why the heck capturing a reference to the ping instance even when never using it makes it trigger twice.

Using takeRetainedValue is a bad idea and leads to crashes (iOS 15)

I start getting exception errors on the second line, even though I'm passing clearly "facebook.com" to it.

let cfhost = CFHostCreateWithName(nil, host as CFString).takeRetainedValue()
let status = CFHostStartInfoResolution(cfhost, .addresses, &streamError)

takeRetainedValue is saying that I'm responsible for keeping that string variable in scope. But that value could be getting deallocated while it's still being used, when the app goes in background mode for example.

It could be nil by the time it gets to line two. The solution could be maybe to hold onto it as a class variable instead.

Why are there no release tags?

Release tags have become the defacto way to track which version of an SPM package you're using. Is there a reason they're not being used for SwiftyPing?

Having to pin to a commit carries a lot of manual overhead that could be avoided with tags.

Works fine with Wifi but not 4G

Hello,

The following code works fine on Wifi but throws PingError.hostNotFound exception on 4G.
That makes no sense though, because the same host IP works on Wifi, but not on 4G.

do {
    let ping = try SwiftyPing(host: serverIP, configuration: PingConfiguration(interval: 0.5, with: 1), queue: DispatchQueue.global())
    ping.finished = { (result) in
        completed(true, result.roundtrip?.average ?? 0.0)
    }
    ping.targetCount = count
    try ping.startPinging()
} catch let error {
    print("Error: \(error.localizedDescription)")
    completed(false, 0.0)
}

Compare checksums

Currently the checksums are not compared, as there are some problems here. I think the checksum calculation is working as expected, but there might be a problem with sending the buffer pointer to the checksum calculation properly, i.e. such that the ICMP header buffer has a 0 checksum.

Bringing app to foreground crash

I'm wondering if you had this issue before, maybe I'm doing something wrong. It crashes when bringing up the app to foreground after being in the background for a while and setting handleBackgroundTransitions = true

Crashed: com.apple.main-thread
EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x0000000121bec008
Crashed: com.apple.main-thread
0 libswiftCore.dylib 0x192f1162c swift_release + 44
1 libswiftCore.dylib 0x192bac8a8 Array.makeUniqueAndReserveCapacityIfNotUnique() + 212
2 libswiftCore.dylib 0x192bacac8 Array.append(
:) + 40
3 MyApp 0x100cd8984 SwiftyPing.informObserver(of:) + 435 (SwiftyPing.swift:435)
4 MyApp 0x100cdaa1c SwiftyPing.socket(socket:didReadData:) + 571 (SwiftyPing.swift:571)
5 MyApp 0x100cd6558 closure #1 in closure #1 in SwiftyPing.createSocket() + 281 (SwiftyPing.swift:281)
6 MyApp 0x100cd660c @objc closure #1 in closure #1 in SwiftyPing.createSocket() + 84 (:84)
7 CoreFoundation 0x18efb78dc __CFSocketPerformV0 + 812
8 CoreFoundation 0x18efac9e8 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION + 28
9 CoreFoundation 0x18efac8e4 __CFRunLoopDoSource0 + 208
10 CoreFoundation 0x18efabbe8 __CFRunLoopDoSources0 + 268
11 CoreFoundation 0x18efa5bc8 __CFRunLoopRun + 820
12 CoreFoundation 0x18efa5360 CFRunLoopRunSpecific + 600
13 GraphicsServices 0x1a65e3734 GSEventRunModal + 164
14 UIKitCore 0x191a20584 -[UIApplication _run] + 1072
15 UIKitCore 0x191a25df4 UIApplicationMain + 168
16 MyApp 0x100c7b7ec main + 34 (AppDelegate.swift:34)
17 libdyld.dylib 0x18ec61cf8 start + 4

Memory Leak - Ping Configuration

I am experiencing a memory leak related to the PingConfiguration struct. I am following the example in the Read-Me, sending a single ping, but after every ping, the PingConfiguration is not deallocated. Could be something I am doing wrong, but I think I am just following the basic example...

Bool func

Hello Samiyr,

thank you for your project, nice work! I would like to ask how is best way to write a Boll func which checks if the ping is OK or NOT? Exist someone variable witch this result?
I try this:

` func test () -> Bool {

    var ping: Bool = false
    
    let once = try? SwiftyPing(host: "38.8.8.8", configuration: PingConfiguration(interval: 0.2, with: 1), queue: DispatchQueue.global())
    once?.observer = { (response) in
        let err = response.error
        if err == nil { ping = true }
        print (err)
        
    }
    
    once?.targetCount = 1
    try? once?.startPinging()
   
  print (ping)          //there is probleb because ping is still false
  return ping

}
`

Carlos

Crash on socket error with serial queues

In SwiftyPing.swift we find the following function:

    private func informObserver(of response: PingResponse) {
        if killswitch { return }
        currentQueue.sync {
            self.observer?(response)
            self.delegate?.didReceive(response: response)
        }
    }

This is called from sendPing, which has code like this:

currentQueue.async {
     ...
     if socketError != .success {
         ...
        self.informObserver(of: response)

This causes a crash when currentQueue is a serial queue. The crash is due to calling currentQueue.sync from code that is already running on currentQueue.

Example code to reproduce the issue:

import UIKit
import SwiftyPing

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    let pingerQueue = DispatchQueue(label: "SerialPingQueue")

    var pinger: SwiftyPing? = nil

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        do {
            pinger = try SwiftyPing(host: "0", // The name "0" triggers a socket error
                                    configuration: PingConfiguration(),
                                    queue: pingerQueue)
            pinger?.observer = { _ in print("PING") }
            try pinger?.startPinging()
        } catch let err{
            print("ERROR", err)
        }

        return true
    }
}

Multiple simultaneous pings are not working.

When multiple ping sessions simultaneously established, every SwiftyPing instance receives responses from every of parallel pings resulting PingError.identifierMismatch responses. I noticed that behaviour with as many as 37 SwiftyPings in action.
To fix that need to check that distinct response source address in IP header matches SwiftyPing.destination and only then to throw that exceptoin.

[help wanted] Concurrent pinging fails when one of the IP addresses return a valid response.

While pinging multiple IP's at once with no reach and timeout, it works as expected, but once an IP address is reachable, the concurrency starts to behave unexpectedly.

Here's what i'm working with:

class IpPinger {
    
    func ping(index: Int) {
        let once = try? SwiftyPing(host: "192.168.1.\(index)", configuration: PingConfiguration(interval: 0.5, with: 5), queue: DispatchQueue.global())
        once?.observer = { (response) in
            if response.ipHeader == nil {
                print("not reachable \(response.ipAddress)")
                once?.haltPinging()
                if index+10 <= 254 {
                    self.ping(index: index+10)
                }
            } else {
                print(response.ipAddress)
                once?.haltPinging()
                if index+10 <= 254 {
                    self.ping(index: index+10)
                }
            }
        }
        once?.targetCount = 1
        try? once?.startPinging()
    }
    
    func concurrentPing() {
        DispatchQueue.concurrentPerform(iterations: 10, execute: { index in
            self.ping(index: index+1)
        })
    }
    
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.