mirror of
https://github.com/leonboe1/QuickDrop.git
synced 2026-11-11 02:51:11 +00:00
104 lines
3.5 KiB
Swift
104 lines
3.5 KiB
Swift
//
|
|
// ApIsolationTester.swift
|
|
// QuickDrop
|
|
//
|
|
// Created by Leon Böttger on 14.05.25.
|
|
//
|
|
|
|
import Foundation
|
|
import Network
|
|
|
|
class DeviceToDeviceHeuristicScanner {
|
|
private let scanQueue = DispatchQueue(label: "DeviceToDeviceScanQueue")
|
|
private var reachableIPs = [String]()
|
|
private var scannedCount = 0
|
|
private var totalToScan = 0
|
|
private var completion: ((Bool) -> Void)?
|
|
|
|
/// Start scanning the subnet for reachable peers.
|
|
/// - Parameters:
|
|
/// - baseSubnet: e.g. `"192.168.1"` — if nil, will auto-detect
|
|
/// - completion: true if any peer device was reachable
|
|
func scan(subnet baseSubnet: String? = nil, port: UInt16 = 80, completion: @escaping (Bool) -> Void) {
|
|
self.completion = completion
|
|
let subnet = baseSubnet ?? Self.getLocalSubnetPrefix() ?? "192.168.1"
|
|
let ipsToScan = (2...254).map { "\(subnet).\($0)" }
|
|
totalToScan = ipsToScan.count
|
|
|
|
for ip in ipsToScan {
|
|
testConnection(to: ip, port: port)
|
|
}
|
|
|
|
// Timeout fallback
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 6.0) {
|
|
if self.scannedCount < self.totalToScan {
|
|
self.finish()
|
|
}
|
|
}
|
|
}
|
|
|
|
private func testConnection(to ip: String, port: UInt16) {
|
|
guard let nwPort = NWEndpoint.Port(rawValue: port) else { return }
|
|
let connection = NWConnection(host: NWEndpoint.Host(ip), port: nwPort, using: .tcp)
|
|
|
|
connection.stateUpdateHandler = { state in
|
|
switch state {
|
|
case .ready:
|
|
self.reachableIPs.append(ip)
|
|
connection.cancel()
|
|
self.hostFinished()
|
|
case .failed, .cancelled:
|
|
connection.cancel()
|
|
self.hostFinished()
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
connection.start(queue: scanQueue)
|
|
}
|
|
|
|
private func hostFinished() {
|
|
scannedCount += 1
|
|
if scannedCount == totalToScan {
|
|
finish()
|
|
}
|
|
}
|
|
|
|
private func finish() {
|
|
completion?(reachableIPs.count > 0)
|
|
}
|
|
|
|
/// Attempts to detect the local subnet prefix (e.g., "192.168.0")
|
|
private static func getLocalSubnetPrefix() -> String? {
|
|
var ifaddr: UnsafeMutablePointer<ifaddrs>?
|
|
if getifaddrs(&ifaddr) == 0 {
|
|
var ptr = ifaddr
|
|
while ptr != nil {
|
|
defer { ptr = ptr!.pointee.ifa_next }
|
|
let interface = ptr!.pointee
|
|
let addrFamily = interface.ifa_addr.pointee.sa_family
|
|
if addrFamily == UInt8(AF_INET),
|
|
let name = String(validatingUTF8: interface.ifa_name),
|
|
name == getActiveNetworkInterface() {
|
|
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
|
|
getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len),
|
|
&hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST)
|
|
if let ip = String(validatingUTF8: hostname),
|
|
let lastDot = ip.lastIndex(of: ".") {
|
|
freeifaddrs(ifaddr)
|
|
|
|
let result = String(ip[..<lastDot])
|
|
|
|
log("[LUI] Found local subnet prefix: \(result) for interface \(name)")
|
|
|
|
return result
|
|
}
|
|
}
|
|
}
|
|
freeifaddrs(ifaddr)
|
|
}
|
|
return nil
|
|
}
|
|
}
|