diff --git a/GlassVPN/PacketTunnelProvider.swift b/GlassVPN/PacketTunnelProvider.swift index 10699fd..0a19055 100644 --- a/GlassVPN/PacketTunnelProvider.swift +++ b/GlassVPN/PacketTunnelProvider.swift @@ -1,5 +1,6 @@ import NetworkExtension +let swcdUserAgent: Data = "User-Agent: swcd".data(using: .ascii)! fileprivate var hook : GlassVPNHook! // MARK: ObserverFactory @@ -15,8 +16,18 @@ class LDObserverFactory: ObserverFactory { override func signal(_ event: ProxySocketEvent) { switch event { case .receivedRequest(let session, let socket): + if socket.isCancelled || + (hook.forceDisconnectUnresolvable && session.ipAddress.isEmpty) { + hook.silentlyPrevented(session.host) + socket.forceDisconnect() + return + } let kill = hook.processDNSRequest(session.host) if kill { socket.forceDisconnect() } + case .readData(let data, on: let socket): + if hook.forceDisconnectSWCD, data.range(of: swcdUserAgent) != nil { + socket.disconnect() + } default: break } diff --git a/main/Common Classes/PrefsShared.swift b/main/Common Classes/PrefsShared.swift index a86018c..5350a88 100644 --- a/main/Common Classes/PrefsShared.swift +++ b/main/Common Classes/PrefsShared.swift @@ -37,6 +37,14 @@ extension PrefsShared { get { CurrentRecordingState(rawValue: Int("CurrentlyRecording")) ?? .Off } set { Int("CurrentlyRecording", newValue.rawValue) } } + static var ForceDisconnectUnresolvableDNS: Bool { + get { PrefsShared.Bool("ForceDisconnectUnresolvableDNS") } + set { PrefsShared.Bool("ForceDisconnectUnresolvableDNS", newValue) } + } + static var ForceDisconnectSWCD: Bool { + get { PrefsShared.Bool("ForceDisconnectSWCD") } + set { PrefsShared.Bool("ForceDisconnectSWCD", newValue) } + } } diff --git a/main/GUI/Base.lproj/Recordings.storyboard b/main/GUI/Base.lproj/Recordings.storyboard index a546c42..6d22fe6 100644 --- a/main/GUI/Base.lproj/Recordings.storyboard +++ b/main/GUI/Base.lproj/Recordings.storyboard @@ -414,15 +414,15 @@ diff --git a/main/GUI/Base.lproj/Settings.storyboard b/main/GUI/Base.lproj/Settings.storyboard index b9f6832..49a2dc4 100644 --- a/main/GUI/Base.lproj/Settings.storyboard +++ b/main/GUI/Base.lproj/Settings.storyboard @@ -1,5 +1,5 @@ - + @@ -243,25 +243,93 @@ - + - + - - - + + @@ -282,6 +350,8 @@ + + @@ -364,7 +434,7 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l - + @@ -391,7 +461,7 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l - + @@ -418,7 +488,7 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l - + @@ -476,7 +546,7 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l - + @@ -602,7 +672,7 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l - + @@ -832,6 +902,6 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l - + diff --git a/main/GlassVPN.swift b/main/GlassVPN.swift index e1acb49..ea6d504 100644 --- a/main/GlassVPN.swift +++ b/main/GlassVPN.swift @@ -161,4 +161,12 @@ struct VPNAppMessage { static func isRecording(_ state: CurrentRecordingState) -> Self { .init("recording-now:\(state.rawValue)") } + /// Triggered whenever user taps on the switch in settings + static func disconnectUnresolvable(_ state: Bool) -> Self { + .init("disconnect-unresolvable:\(state ? 1 : 0)") + } + /// Triggered whenever user taps on the switch in settings + static func disconnectSWCD(_ state: Bool) -> Self { + .init("disconnect-swcd:\(state ? 1 : 0)") + } } diff --git a/main/GlassVPNHook.swift b/main/GlassVPNHook.swift index 1023411..b692e62 100644 --- a/main/GlassVPNHook.swift +++ b/main/GlassVPNHook.swift @@ -10,6 +10,10 @@ class GlassVPNHook { private var cachedNotify: CachedConnectionAlert! private var currentlyRecording: Bool = false + public var forceDisconnectUnresolvable: Bool = false + public var forceDisconnectSWCD: Bool = false + + init() { reset() } /// Reload from stored settings and rebuilt binary search tree @@ -18,6 +22,8 @@ class GlassVPNHook { setAutoDelete(PrefsShared.AutoDeleteLogsDays) cachedNotify = CachedConnectionAlert() currentlyRecording = PrefsShared.CurrentlyRecording != .Off + forceDisconnectUnresolvable = PrefsShared.ForceDisconnectUnresolvableDNS + forceDisconnectSWCD = PrefsShared.ForceDisconnectSWCD } /// Invalidate auto-delete timer and release stored properties. You should nullify this instance afterwards. @@ -28,6 +34,8 @@ class GlassVPNHook { autoDeleteTimer?.invalidate() cachedNotify = nil currentlyRecording = false + forceDisconnectUnresolvable = false + forceDisconnectSWCD = false } /// Call this method from `PacketTunnelProvider.handleAppMessage(_:completionHandler:)` @@ -50,6 +58,12 @@ class GlassVPNHook { let newState = CurrentRecordingState(rawValue: Int(value) ?? 0) currentlyRecording = newState != .Off return + case "disconnect-unresolvable": + forceDisconnectUnresolvable = value == "1" + return + case "disconnect-swcd": + forceDisconnectSWCD = value == "1" + return default: break } } @@ -79,6 +93,11 @@ class GlassVPNHook { return blockActive } + func silentlyPrevented(_ domain: String) { + // TODO: persist in a separate db/table? + NSLog("[VPN.INFO] preventing connection to \(domain)") + } + /// Build binary tree for reverse DNS lookup private func reloadDomainFilter() { let tmp = AppDB?.loadFilters()?.map({ diff --git a/main/Settings/TVCSettings.swift b/main/Settings/TVCSettings.swift index d7888c7..e0a9129 100644 --- a/main/Settings/TVCSettings.swift +++ b/main/Settings/TVCSettings.swift @@ -3,6 +3,8 @@ import UIKit class TVCSettings: UITableViewController { @IBOutlet var vpnToggle: UISwitch! + @IBOutlet var unresolvableToggle: UISwitch! + @IBOutlet var swcdToggle: UISwitch! @IBOutlet var cellDomainsIgnored: UITableViewCell! @IBOutlet var cellDomainsBlocked: UITableViewCell! @IBOutlet var cellPrivacyAutoDelete: UITableViewCell! @@ -14,6 +16,7 @@ class TVCSettings: UITableViewController { reloadVPNState() reloadLoggingFilterUI() reloadPrivacyUI() + reloadAdvancedUI() NotifyVPNStateChanged.observe(call: #selector(reloadVPNState), on: self) NotifyDNSFilterChanged.observe(call: #selector(reloadLoggingFilterUI), on: self) } @@ -191,6 +194,21 @@ extension TVCSettings { // MARK: - Advanced extension TVCSettings { + private func reloadAdvancedUI() { + unresolvableToggle.isOn = PrefsShared.ForceDisconnectUnresolvableDNS + swcdToggle.isOn = PrefsShared.ForceDisconnectSWCD + } + + @IBAction private func togglePreventUnresolvable(_ sender: UISwitch) { + PrefsShared.ForceDisconnectUnresolvableDNS = sender.isOn + GlassVPN.send(.disconnectUnresolvable(sender.isOn)) + } + + @IBAction private func togglePreventSWCD(_ sender: UISwitch) { + PrefsShared.ForceDisconnectSWCD = sender.isOn + GlassVPN.send(.disconnectSWCD(sender.isOn)) + } + @IBAction private func exportDB() { AppDB?.vacuum() let sheet = UIActivityViewController(activityItems: [URL.internalDB()], applicationActivities: nil)