diff --git a/Kit/extensions.swift b/Kit/extensions.swift index 3693d533..06cb8073 100644 --- a/Kit/extensions.swift +++ b/Kit/extensions.swift @@ -549,3 +549,12 @@ extension NSTextView { return super.performKeyEquivalent(with: event) } } + +public extension Data { + var socketAddress: sockaddr { + return withUnsafeBytes { $0.load(as: sockaddr.self) } + } + var socketAddressInternet: sockaddr_in { + return withUnsafeBytes { $0.load(as: sockaddr_in.self) } + } +} diff --git a/Modules/Net/readers.swift b/Modules/Net/readers.swift index 09ba299c..9e615bee 100644 --- a/Modules/Net/readers.swift +++ b/Modules/Net/readers.swift @@ -547,3 +547,254 @@ public class ProcessReader: Reader<[Network_Process]> { self.callback(processes.suffix(self.numberOfProcesses).reversed()) } } + +internal class ConnectivityReaderWrapper { + weak var reader: ConnectivityReader? + + init(_ reader: ConnectivityReader) { + self.reader = reader + } +} + +// inspired by https://github.com/samiyr/SwiftyPing +internal class ConnectivityReader: Reader { + private let variablesQueue = DispatchQueue(label: "eu.exelban.ConnectivityReaderQueue") + + private let identifier = UInt16.random(in: 0.. Bool { + guard data.count >= MemoryLayout.size + MemoryLayout.size, + let headerOffset = icmpHeaderOffset(of: data) else { return false } + + let payloadSize = data.count - headerOffset - MemoryLayout.size + let icmpHeader = data.withUnsafeBytes({ $0.load(fromByteOffset: headerOffset, as: ICMPHeader.self) }) + let payload = data.subdata(in: (data.count - payloadSize).. Data? { + var header = ICMPHeader(type: 8, code: 0, checksum: 0, identifier: CFSwapInt16HostToBig(self.identifier), sequenceNumber: CFSwapInt16HostToBig(0), payload: self.fingerprint.uuid) + + let delta = MemoryLayout.size - MemoryLayout.size + var additional = [UInt8]() + if delta > 0 { + additional = (0...size) + Data(additional) + return package + } + + private func computeChecksum(header: ICMPHeader, additionalPayload: [UInt8]) -> UInt16? { + let typecode = Data([header.type, header.code]).withUnsafeBytes { $0.load(as: UInt16.self) } + var sum = UInt64(typecode) + UInt64(header.identifier) + UInt64(header.sequenceNumber) + let payload = convert(payload: header.payload) + additionalPayload + + guard payload.count % 2 == 0 else { return nil } + + var i = 0 + while i < payload.count { + guard payload.indices.contains(i + 1) else { return nil } + sum += Data([payload[i], payload[i + 1]]).withUnsafeBytes { UInt64($0.load(as: UInt16.self)) } + i += 2 + } + while sum >> 16 != 0 { + sum = (sum & 0xffff) + (sum >> 16) + } + + guard sum < UInt16.max else { return nil } + + return ~UInt16(sum) + } + + private func convert(payload: uuid_t) -> [UInt8] { + let p = payload + return [p.0, p.1, p.2, p.3, p.4, p.5, p.6, p.7, p.8, p.9, p.10, p.11, p.12, p.13, p.14, p.15].map { UInt8($0) } + } + + private func icmpHeaderOffset(of packet: Data) -> Int? { + if packet.count >= MemoryLayout.size + MemoryLayout.size { + let ipHeader = packet.withUnsafeBytes({ $0.load(as: IPHeader.self) }) + if ipHeader.versionAndHeaderLength & 0xF0 == 0x40 && ipHeader.protocol == IPPROTO_ICMP { + let headerLength = Int(ipHeader.versionAndHeaderLength) & 0x0F * MemoryLayout.size + if packet.count >= headerLength + MemoryLayout.size { + return headerLength + } + } + } + return nil + } + + private func setConn() { + let info = ConnectivityReaderWrapper(self) + let unmanagedSocketInfo = Unmanaged.passRetained(info) + var context = CFSocketContext(version: 0, info: unmanagedSocketInfo.toOpaque(), retain: nil, release: nil, copyDescription: nil) + + self.socket = CFSocketCreate(kCFAllocatorDefault, AF_INET, SOCK_DGRAM, IPPROTO_ICMP, CFSocketCallBackType.dataCallBack.rawValue, { _, callBackType, _, data, info in + guard let info = info, let data = data else { return } + if (callBackType as CFSocketCallBackType) == CFSocketCallBackType.dataCallBack { + let cfdata = Unmanaged.fromOpaque(data).takeUnretainedValue() + let wrapper = Unmanaged.fromOpaque(info).takeUnretainedValue() + wrapper.reader?.socketCallback(data: cfdata as Data) + } + }, &context) + + let handle = CFSocketGetNative(self.socket) + var value: Int32 = 1 + let err = setsockopt(handle, SOL_SOCKET, SO_NOSIGPIPE, &value, socklen_t(MemoryLayout.size(ofValue: value))) + guard err == 0 else { return } + + self.socketSource = CFSocketCreateRunLoopSource(nil, self.socket, 0) + CFRunLoopAddSource(CFRunLoopGetMain(), self.socketSource, .commonModes) + } + + // resolve host ip if hostname is provided + private func resolve() -> Data? { + var streamError = CFStreamError() + let cfhost = CFHostCreateWithName(nil, self.host as CFString).takeRetainedValue() + let status = CFHostStartInfoResolution(cfhost, .addresses, &streamError) + guard status else { return nil } + + var success: DarwinBoolean = false + guard let addresses = CFHostGetAddressing(cfhost, &success)?.takeUnretainedValue() as? [Data] else { + return nil + } + + var data: Data? + for address in addresses { + let addrin = address.socketAddress + if address.count >= MemoryLayout.size && addrin.sa_family == UInt8(AF_INET) { + data = address + break + } + } + guard let data = data, !data.isEmpty else { return nil } + + return data + } +}