168 lines
4.7 KiB
Swift
168 lines
4.7 KiB
Swift
//
|
|
// 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<Record>()
|
|
|
|
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<ifaddrs>?
|
|
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<ifaddrs>?
|
|
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<ifaddrs>) -> 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<if_data>.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)
|
|
}
|
|
}
|