// // InterfaceAddress.swift // PrivadoVPN // // Created by Juraldinio on 8/30/20. // Copyright © 2020 Privado LLC. All rights reserved. // import Foundation final class InterfaceAddress { private enum Constants { static let updateInterval: TimeInterval = 3.0 } final class Record { let name: String private(set) var received: UInt64 private(set) var sent: UInt64 let statistic = Emitter() init(name: String, received: UInt64, sent: UInt64) { self.name = name self.received = received self.sent = sent } fileprivate func update(received: UInt64, sent: UInt64) { self.received = received self.sent = sent DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.statistic.invoke(self) } } } static var instance = InterfaceAddress() // MARK: - Properties private var scheduler: Timer? private var interfaceRecords = [Record]() var records: [Record] { if self.interfaceRecords.isEmpty { self.interfaceRecords = self.refresh() } return self.interfaceRecords } func record(with interface: String) -> Record? { return self.records.first(where: { $0.name == interface }) } // MARK: - Init private init() { } // MARK: - func resume() { guard !self.scheduler.isExist else { return } let scheduler = Timer.scheduledTimer(withTimeInterval: Constants.updateInterval, repeats: true) { [weak self] _ in self?.update() } self.scheduler = scheduler } func pause() { if let scheduler = self.scheduler { scheduler.invalidate() scheduler.fire() } self.scheduler = nil } @discardableResult func update() -> [Record] { var new = [Record]() self.refresh().forEach { rec in guard let record = self.interfaceRecords.first(where: { $0.name == rec.name }) else { new.append(rec) return } record.update(received: rec.received, sent: rec.sent) } self.interfaceRecords.append(contentsOf: new) return new } func getIpAddresses() -> [String] { var addresses = [String]() var ifaddr: UnsafeMutablePointer? guard getifaddrs(&ifaddr) == 0 else { return addresses } guard let firstAddr = ifaddr else { return addresses } for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) { let interface = ifptr.pointee 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, socklen_t(0), NI_NUMERICHOST) let address = String(cString: hostname) addresses.append(address) } return addresses } // MARK: - Private private func refresh() -> [Record] { var interfaceAddresses: UnsafeMutablePointer? guard getifaddrs(&interfaceAddresses) == 0 else { return [] } var pointer = interfaceAddresses var records = [Record]() while pointer.isExist { guard let current = pointer else { pointer = nil continue } guard let record = InterfaceAddress.getInterfaceRecord(from: current) else { pointer = current.pointee.ifa_next continue } records.append(record) pointer = current.pointee.ifa_next } freeifaddrs(interfaceAddresses) return records } private static func getInterfaceRecord(from infoPointer: UnsafeMutablePointer) -> Record? { let addr = infoPointer.pointee.ifa_addr.pointee guard addr.sa_family == UInt8(AF_LINK) else { return nil } let networkData = unsafeBitCast(infoPointer.pointee.ifa_data, to: UnsafeMutablePointer.self) let output = UInt64(networkData.pointee.ifi_obytes) let input = UInt64(networkData.pointee.ifi_ibytes) let name = String(cString: infoPointer.pointee.ifa_name) return Record(name: name, received: input, sent: output) } }