2020-06-07 12:22:32 +02:00
|
|
|
//
|
|
|
|
|
// readers.swift
|
|
|
|
|
// Net
|
|
|
|
|
//
|
|
|
|
|
// Created by Serhiy Mytrovtsiy on 24/05/2020.
|
|
|
|
|
// Using Swift 5.0.
|
|
|
|
|
// Running on macOS 10.15.
|
|
|
|
|
//
|
|
|
|
|
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import Cocoa
|
2020-07-06 15:47:19 +02:00
|
|
|
import StatsKit
|
2020-06-07 12:22:32 +02:00
|
|
|
import ModuleKit
|
|
|
|
|
import SystemConfiguration
|
|
|
|
|
import Reachability
|
|
|
|
|
import os.log
|
|
|
|
|
import CoreWLAN
|
|
|
|
|
|
2020-07-06 19:25:41 +02:00
|
|
|
struct ipResponse: Decodable {
|
|
|
|
|
var ip: String
|
|
|
|
|
var country: String
|
|
|
|
|
var cc: String
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-28 22:31:40 +02:00
|
|
|
internal class UsageReader: Reader<Network_Usage> {
|
2020-07-06 15:47:19 +02:00
|
|
|
public var store: UnsafePointer<Store>? = nil
|
2020-07-06 19:25:41 +02:00
|
|
|
|
2020-06-07 12:22:32 +02:00
|
|
|
private var reachability: Reachability? = nil
|
2020-06-28 22:31:40 +02:00
|
|
|
private var usage: Network_Usage = Network_Usage()
|
2020-06-07 12:22:32 +02:00
|
|
|
|
2020-07-06 15:47:19 +02:00
|
|
|
private var primaryInterface: String {
|
|
|
|
|
get {
|
|
|
|
|
if let global = SCDynamicStoreCopyValue(nil, "State:/Network/Global/IPv4" as CFString), let name = global["PrimaryInterface"] as? String {
|
|
|
|
|
return name
|
|
|
|
|
}
|
2020-07-06 19:25:41 +02:00
|
|
|
return ""
|
2020-07-06 15:47:19 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var interfaceID: String {
|
|
|
|
|
get {
|
2020-07-06 19:25:41 +02:00
|
|
|
return self.store?.pointee.string(key: "Network_interface", defaultValue: self.primaryInterface) ?? self.primaryInterface
|
2020-07-06 15:47:19 +02:00
|
|
|
}
|
|
|
|
|
set {
|
2020-07-06 19:25:41 +02:00
|
|
|
self.store?.pointee.set(key: "Network_interface", value: newValue)
|
2020-07-06 15:47:19 +02:00
|
|
|
}
|
|
|
|
|
}
|
2020-06-07 12:22:32 +02:00
|
|
|
|
|
|
|
|
public override func setup() {
|
|
|
|
|
do {
|
|
|
|
|
self.reachability = try Reachability()
|
|
|
|
|
try self.reachability!.startNotifier()
|
|
|
|
|
} catch let error {
|
|
|
|
|
os_log(.error, log: log, "initialize Reachability error %s", "\(error)")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.reachability!.whenReachable = { _ in
|
2020-07-06 19:25:41 +02:00
|
|
|
self.getDetails()
|
2020-06-07 12:22:32 +02:00
|
|
|
}
|
|
|
|
|
self.reachability!.whenUnreachable = { _ in
|
|
|
|
|
self.usage.reset()
|
|
|
|
|
self.callback(self.usage)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override func read() {
|
|
|
|
|
var interfaceAddresses: UnsafeMutablePointer<ifaddrs>? = nil
|
|
|
|
|
var upload: Int64 = 0
|
|
|
|
|
var download: Int64 = 0
|
|
|
|
|
guard getifaddrs(&interfaceAddresses) == 0 else { return }
|
|
|
|
|
|
|
|
|
|
var pointer = interfaceAddresses
|
|
|
|
|
while pointer != nil {
|
|
|
|
|
defer { pointer = pointer?.pointee.ifa_next }
|
|
|
|
|
|
|
|
|
|
if String(cString: pointer!.pointee.ifa_name) != self.interfaceID {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-06 19:25:41 +02:00
|
|
|
if let ip = getLocalIP(pointer!), self.usage.laddr != ip {
|
|
|
|
|
self.usage.laddr = ip
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-07 12:22:32 +02:00
|
|
|
if let info = getBytesInfo(pointer!) {
|
|
|
|
|
upload += info.upload
|
|
|
|
|
download += info.download
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
freeifaddrs(interfaceAddresses)
|
|
|
|
|
|
|
|
|
|
if self.usage.upload != 0 && self.usage.download != 0 {
|
|
|
|
|
self.usage.upload = upload - self.usage.upload
|
|
|
|
|
self.usage.download = download - self.usage.download
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-06 19:25:41 +02:00
|
|
|
if self.usage.upload < 0 {
|
|
|
|
|
self.usage.upload = 0
|
|
|
|
|
}
|
|
|
|
|
if self.usage.download < 0 {
|
|
|
|
|
self.usage.download = 0
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-07 12:22:32 +02:00
|
|
|
self.callback(self.usage)
|
2020-07-06 19:25:41 +02:00
|
|
|
|
2020-06-07 12:22:32 +02:00
|
|
|
self.usage.upload = upload
|
|
|
|
|
self.usage.download = download
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-06 19:25:41 +02:00
|
|
|
public func getDetails() {
|
|
|
|
|
self.usage.reset()
|
2020-06-07 12:22:32 +02:00
|
|
|
|
|
|
|
|
DispatchQueue.global(qos: .background).async {
|
2020-07-06 19:25:41 +02:00
|
|
|
self.getPublicIP()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.interfaceID != "" {
|
|
|
|
|
for interface in SCNetworkInterfaceCopyAll() as NSArray {
|
|
|
|
|
if let bsdName = SCNetworkInterfaceGetBSDName(interface as! SCNetworkInterface),
|
|
|
|
|
bsdName as String == self.interfaceID,
|
|
|
|
|
let type = SCNetworkInterfaceGetInterfaceType(interface as! SCNetworkInterface),
|
|
|
|
|
let displayName = SCNetworkInterfaceGetLocalizedDisplayName(interface as! SCNetworkInterface),
|
|
|
|
|
let address = SCNetworkInterfaceGetHardwareAddressString(interface as! SCNetworkInterface) {
|
|
|
|
|
self.usage.interface = Network_interface(displayName: displayName as String, BSDName: bsdName as String, address: address as String)
|
|
|
|
|
|
|
|
|
|
switch type {
|
|
|
|
|
case kSCNetworkInterfaceTypeEthernet:
|
|
|
|
|
self.usage.connectionType = .ethernet
|
|
|
|
|
case kSCNetworkInterfaceTypeIEEE80211, kSCNetworkInterfaceTypeWWAN:
|
|
|
|
|
self.usage.connectionType = .wifi
|
|
|
|
|
case kSCNetworkInterfaceTypeBluetooth:
|
|
|
|
|
self.usage.connectionType = .bluetooth
|
|
|
|
|
default:
|
|
|
|
|
self.usage.connectionType = .other
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-06-29 21:05:38 +02:00
|
|
|
}
|
2020-06-07 12:22:32 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-06 19:25:41 +02:00
|
|
|
if let interface = CWWiFiClient.shared().interface(), self.usage.connectionType == .wifi {
|
|
|
|
|
self.usage.ssid = interface.ssid()
|
|
|
|
|
self.usage.countryCode = interface.countryCode()
|
2020-06-07 12:22:32 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func getLocalIP(_ pointer: UnsafeMutablePointer<ifaddrs>) -> String? {
|
|
|
|
|
var addr = pointer.pointee.ifa_addr.pointee
|
|
|
|
|
|
|
|
|
|
guard addr.sa_family == UInt8(AF_INET) else {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ip = [CChar](repeating: 0, count: Int(NI_MAXHOST))
|
|
|
|
|
getnameinfo(&addr, socklen_t(addr.sa_len), &ip, socklen_t(ip.count), nil, socklen_t(0), NI_NUMERICHOST)
|
|
|
|
|
|
|
|
|
|
return String(cString: ip)
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-06 19:25:41 +02:00
|
|
|
private func getPublicIP() {
|
|
|
|
|
let url = URL(string: "https://api.myip.com")
|
2020-06-07 12:22:32 +02:00
|
|
|
var address: String? = nil
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
if let url = url {
|
|
|
|
|
address = try String(contentsOf: url)
|
2020-07-06 19:25:41 +02:00
|
|
|
|
|
|
|
|
if address != nil {
|
|
|
|
|
let jsonData = address!.data(using: .utf8)
|
|
|
|
|
let response: ipResponse = try JSONDecoder().decode(ipResponse.self, from: jsonData!)
|
|
|
|
|
|
|
|
|
|
self.usage.countryCode = response.cc
|
|
|
|
|
self.usage.raddr = response.ip
|
2020-06-07 12:22:32 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch let error {
|
|
|
|
|
os_log(.error, log: log, "get public ip %s", "\(error)")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-06 19:25:41 +02:00
|
|
|
private func getBytesInfo(_ pointer: UnsafeMutablePointer<ifaddrs>) -> (upload: Int64, download: Int64)? {
|
|
|
|
|
let addr = pointer.pointee.ifa_addr.pointee
|
2020-06-29 16:56:39 +02:00
|
|
|
|
2020-07-06 19:25:41 +02:00
|
|
|
guard addr.sa_family == UInt8(AF_LINK) else {
|
2020-06-07 12:22:32 +02:00
|
|
|
return nil
|
|
|
|
|
}
|
2020-06-29 16:56:39 +02:00
|
|
|
|
2020-07-06 19:25:41 +02:00
|
|
|
let data: UnsafeMutablePointer<if_data>? = unsafeBitCast(pointer.pointee.ifa_data, to: UnsafeMutablePointer<if_data>.self)
|
|
|
|
|
return (upload: Int64(data?.pointee.ifi_obytes ?? 0), download: Int64(data?.pointee.ifi_ibytes ?? 0))
|
2020-06-07 12:22:32 +02:00
|
|
|
}
|
|
|
|
|
}
|