diff --git a/Modules/Net/main.swift b/Modules/Net/main.swift index 53064c5e..8555c4cb 100644 --- a/Modules/Net/main.swift +++ b/Modules/Net/main.swift @@ -161,6 +161,12 @@ public class Network: Module { self.settingsView.usageResetCallback = { [unowned self] in self.setUsageReset() } + self.settingsView.ICMPHostCallback = { [unowned self] isDisabled in + if isDisabled { + self.popupView.resetConnectivityView() + self.connectivityCallback(false) + } + } if let reader = self.usageReader { self.addReader(reader) diff --git a/Modules/Net/popup.swift b/Modules/Net/popup.swift index 5c4550f2..66cbfe48 100644 --- a/Modules/Net/popup.swift +++ b/Modules/Net/popup.swift @@ -410,6 +410,10 @@ internal class Popup: NSStackView, Popup_p { }) } + public func resetConnectivityView() { + self.connectivityField?.stringValue = localizedString("Unknown") + } + // MARK: - helpers private func topValueView(_ view: NSView, title: String, color: NSColor) -> (NSView, NSTextField, NSTextField, ColorView) { diff --git a/Modules/Net/readers.swift b/Modules/Net/readers.swift index 9e615bee..9d1aec5f 100644 --- a/Modules/Net/readers.swift +++ b/Modules/Net/readers.swift @@ -563,7 +563,10 @@ internal class ConnectivityReader: Reader { private let identifier = UInt16.random(in: 0.. { } override func read() { + guard !self.host.isEmpty else { return } + if self.lastHost != self.host { + self.addr = self.resolve() + } + guard !self.isPinging && self.active, let socket = self.socket, let addr = self.addr, let data = self.request() else { return } self.isPinging = true @@ -714,7 +722,6 @@ internal class ConnectivityReader: Reader { 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 @@ -726,7 +733,6 @@ internal class ConnectivityReader: Reader { while sum >> 16 != 0 { sum = (sum & 0xffff) + (sum >> 16) } - guard sum < UInt16.max else { return nil } return ~UInt16(sum) @@ -754,7 +760,6 @@ internal class ConnectivityReader: Reader { 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 { @@ -763,28 +768,24 @@ internal class ConnectivityReader: Reader { 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? { + self.lastHost = self.host 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 @@ -794,7 +795,6 @@ internal class ConnectivityReader: Reader { } } guard let data = data, !data.isEmpty else { return nil } - return data } } diff --git a/Modules/Net/settings.swift b/Modules/Net/settings.swift index 9245bb7a..c33e199e 100644 --- a/Modules/Net/settings.swift +++ b/Modules/Net/settings.swift @@ -13,16 +13,18 @@ import Cocoa import Kit import SystemConfiguration -internal class Settings: NSStackView, Settings_v { +internal class Settings: NSStackView, Settings_v, NSTextFieldDelegate { private var numberOfProcesses: Int = 8 private var readerType: String = "interface" private var usageReset: String = AppUpdateInterval.atStart.rawValue private var VPNModeState: Bool = false private var widgetActivationThreshold: Int = 0 + private var ICMPHost: String = "1.1.1.1" public var callback: (() -> Void) = {} public var callbackWhenUpdateNumberOfProcesses: (() -> Void) = {} public var usageResetCallback: (() -> Void) = {} + public var ICMPHostCallback: ((_ newState: Bool) -> Void) = { _ in } private let title: String private var button: NSPopUpButton? @@ -44,6 +46,7 @@ internal class Settings: NSStackView, Settings_v { self.usageReset = Store.shared.string(key: "\(self.title)_usageReset", defaultValue: self.usageReset) self.VPNModeState = Store.shared.bool(key: "\(self.title)_VPNMode", defaultValue: self.VPNModeState) self.widgetActivationThreshold = Store.shared.int(key: "\(self.title)_widgetActivationThreshold", defaultValue: self.widgetActivationThreshold) + self.ICMPHost = Store.shared.string(key: "\(self.title)_ICMPHost", defaultValue: self.ICMPHost) super.init(frame: NSRect(x: 0, y: 0, width: 0, height: 0)) @@ -104,6 +107,8 @@ internal class Settings: NSStackView, Settings_v { state: self.VPNModeState )) } + + self.addArrangedSubview(self.connectivityHost()) } private func interfaceSelector() -> NSView { @@ -207,6 +212,44 @@ internal class Settings: NSStackView, Settings_v { return view } + func connectivityHost() -> NSView { + let view: NSStackView = NSStackView() + view.translatesAutoresizingMaskIntoConstraints = false + view.heightAnchor.constraint(equalToConstant: Constants.Settings.row).isActive = true + view.orientation = .horizontal + view.alignment = .centerY + view.distribution = .fill + view.spacing = 0 + + let titleField: NSTextField = LabelField(frame: NSRect(x: 0, y: 0, width: 0, height: 0), localizedString("Connectivity host (ICMP)")) + titleField.font = NSFont.systemFont(ofSize: 12, weight: .regular) + titleField.textColor = .textColor + + let valueField: NSTextField = NSTextField() + valueField.font = NSFont.systemFont(ofSize: 12, weight: .regular) + valueField.textColor = .textColor + valueField.isEditable = true + valueField.isSelectable = true + valueField.isBezeled = false + valueField.wantsLayer = true + valueField.canDrawSubviewsIntoLayer = true + valueField.usesSingleLineMode = true + valueField.maximumNumberOfLines = 1 + valueField.focusRingType = .none + valueField.delegate = self + valueField.stringValue = self.ICMPHost + valueField.placeholderString = localizedString("Leave empty to disable check") + valueField.alignment = .natural + + view.addArrangedSubview(titleField) + view.addArrangedSubview(NSView()) + view.addArrangedSubview(valueField) + + valueField.widthAnchor.constraint(equalToConstant: 200).isActive = true + + return view + } + @objc func handleSelection(_ sender: NSPopUpButton) { guard let item = sender.selectedItem, let id = item.identifier?.rawValue else { return } @@ -273,4 +316,12 @@ internal class Settings: NSStackView, Settings_v { self.widgetActivationThreshold = value Store.shared.set(key: "\(self.title)_widgetActivationThreshold", value: widgetActivationThreshold) } + + func controlTextDidChange(_ notification: Notification) { + if let textField = notification.object as? NSTextField { + self.ICMPHost = textField.stringValue + Store.shared.set(key: "\(self.title)_ICMPHost", value: self.ICMPHost) + self.ICMPHostCallback(self.ICMPHost.isEmpty) + } + } }