diff --git a/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift b/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift index 5d5216cb5..368ba1e1d 100644 --- a/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift +++ b/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift @@ -61,7 +61,7 @@ extension TunnelConfiguration { let keyWithCase = trimmedLine[.. = ["address", "allowedips", "dns"] + let keysWithMultipleEntriesAllowed: Set = ["address", "allowedips", "dns", "dnsmatchdomains"] if let presentValue = attributes[key] { if keysWithMultipleEntriesAllowed.contains(key) { attributes[key] = presentValue + "," + value @@ -71,7 +71,7 @@ extension TunnelConfiguration { } else { attributes[key] = value } - let interfaceSectionKeys: Set = ["privatekey", "listenport", "address", "dns", "mtu"] + let interfaceSectionKeys: Set = ["privatekey", "listenport", "address", "dns", "dnsmatchdomains", "mtu"] let peerSectionKeys: Set = ["publickey", "presharedkey", "allowedips", "endpoint", "persistentkeepalive"] if parserState == .inInterfaceSection { guard interfaceSectionKeys.contains(key) else { @@ -139,6 +139,10 @@ extension TunnelConfiguration { let dnsString = dnsLine.joined(separator: ", ") output.append("DNS = \(dnsString)\n") } + if !interface.dnsMatchDomains.isEmpty { + let dnsMatchString = interface.dnsMatchDomains.joined(separator: ", ") + output.append("DNSMatchDomains = \(dnsMatchString)\n") + } if let mtu = interface.mtu { output.append("MTU = \(mtu)\n") } @@ -201,6 +205,9 @@ extension TunnelConfiguration { interface.dns = dnsServers interface.dnsSearch = dnsSearch } + if let dnsMatchString = attributes["dnsmatchdomains"] { + interface.dnsMatchDomains = dnsMatchString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) + } if let mtuString = attributes["mtu"] { guard let mtu = UInt16(mtuString) else { throw ParseError.interfaceHasInvalidMTU(mtuString) diff --git a/Sources/WireGuardApp/Base.lproj/Localizable.strings b/Sources/WireGuardApp/Base.lproj/Localizable.strings index 40023ecb4..0eb308c0b 100644 --- a/Sources/WireGuardApp/Base.lproj/Localizable.strings +++ b/Sources/WireGuardApp/Base.lproj/Localizable.strings @@ -82,6 +82,7 @@ "tunnelInterfaceListenPort" = "Listen port"; "tunnelInterfaceMTU" = "MTU"; "tunnelInterfaceDNS" = "DNS servers"; +"tunnelInterfaceDNSMatchDomains" = "DNS match domains"; "tunnelInterfaceStatus" = "Status"; "tunnelSectionTitlePeer" = "Peer"; diff --git a/Sources/WireGuardApp/Tunnel/TunnelConfiguration+UapiConfig.swift b/Sources/WireGuardApp/Tunnel/TunnelConfiguration+UapiConfig.swift index cdc81cee4..f2ff76331 100644 --- a/Sources/WireGuardApp/Tunnel/TunnelConfiguration+UapiConfig.swift +++ b/Sources/WireGuardApp/Tunnel/TunnelConfiguration+UapiConfig.swift @@ -75,6 +75,7 @@ extension TunnelConfiguration { interfaceConfiguration?.addresses = base?.interface.addresses ?? [] interfaceConfiguration?.dns = base?.interface.dns ?? [] interfaceConfiguration?.dnsSearch = base?.interface.dnsSearch ?? [] + interfaceConfiguration?.dnsMatchDomains = base?.interface.dnsMatchDomains ?? [] interfaceConfiguration?.mtu = base?.interface.mtu if let interfaceConfiguration = interfaceConfiguration { diff --git a/Sources/WireGuardApp/UI/TunnelViewModel.swift b/Sources/WireGuardApp/UI/TunnelViewModel.swift index b65c8ccfd..23b4f4d34 100644 --- a/Sources/WireGuardApp/UI/TunnelViewModel.swift +++ b/Sources/WireGuardApp/UI/TunnelViewModel.swift @@ -14,6 +14,7 @@ class TunnelViewModel { case listenPort case mtu case dns + case dnsMatchDomains case status case toggleStatus @@ -27,6 +28,7 @@ class TunnelViewModel { case .listenPort: return tr("tunnelInterfaceListenPort") case .mtu: return tr("tunnelInterfaceMTU") case .dns: return tr("tunnelInterfaceDNS") + case .dnsMatchDomains: return tr("tunnelInterfaceDNSMatchDomains") case .status: return tr("tunnelInterfaceStatus") case .toggleStatus: return "" } @@ -144,6 +146,9 @@ class TunnelViewModel { dns.append(contentsOf: config.dnsSearch) scratchpad[.dns] = dns.joined(separator: ", ") } + if !config.dnsMatchDomains.isEmpty { + scratchpad[.dnsMatchDomains] = config.dnsMatchDomains.joined(separator: ", ") + } return scratchpad } @@ -207,6 +212,9 @@ class TunnelViewModel { config.dns = dnsServers config.dnsSearch = dnsSearch } + if let dnsMatchString = scratchpad[.dnsMatchDomains] { + config.dnsMatchDomains = dnsMatchString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) + } guard errorMessages.isEmpty else { return .error(errorMessages.first!) } diff --git a/Sources/WireGuardApp/UI/iOS/ViewController/TunnelDetailTableViewController.swift b/Sources/WireGuardApp/UI/iOS/ViewController/TunnelDetailTableViewController.swift index 196de0c69..1f2c1f955 100644 --- a/Sources/WireGuardApp/UI/iOS/ViewController/TunnelDetailTableViewController.swift +++ b/Sources/WireGuardApp/UI/iOS/ViewController/TunnelDetailTableViewController.swift @@ -15,7 +15,7 @@ class TunnelDetailTableViewController: UITableViewController { static let interfaceFields: [TunnelViewModel.InterfaceField] = [ .name, .publicKey, .addresses, - .listenPort, .mtu, .dns + .listenPort, .mtu, .dns, .dnsMatchDomains ] static let peerFields: [TunnelViewModel.PeerField] = [ diff --git a/Sources/WireGuardApp/UI/iOS/ViewController/TunnelEditTableViewController.swift b/Sources/WireGuardApp/UI/iOS/ViewController/TunnelEditTableViewController.swift index e44cf8d4c..508caa6f7 100644 --- a/Sources/WireGuardApp/UI/iOS/ViewController/TunnelEditTableViewController.swift +++ b/Sources/WireGuardApp/UI/iOS/ViewController/TunnelEditTableViewController.swift @@ -34,7 +34,7 @@ class TunnelEditTableViewController: UITableViewController { let interfaceFieldsBySection: [[TunnelViewModel.InterfaceField]] = [ [.name], [.privateKey, .publicKey, .generateKeyPair], - [.addresses, .listenPort, .mtu, .dns] + [.addresses, .listenPort, .mtu, .dns, .dnsMatchDomains] ] let peerFields: [TunnelViewModel.PeerField] = [ @@ -246,6 +246,9 @@ extension TunnelEditTableViewController { case .dns: cell.placeholderText = tunnelViewModel.peersData.contains(where: { $0.shouldStronglyRecommendDNS }) ? tr("tunnelEditPlaceholderTextStronglyRecommended") : tr("tunnelEditPlaceholderTextOptional") cell.keyboardType = .numbersAndPunctuation + case .dnsMatchDomains: + cell.placeholderText = tr("tunnelEditPlaceholderTextOptional") + cell.keyboardType = .numbersAndPunctuation case .listenPort, .mtu: cell.placeholderText = tr("tunnelEditPlaceholderTextAutomatic") cell.keyboardType = .numberPad diff --git a/Sources/WireGuardApp/UI/macOS/View/highlighter.c b/Sources/WireGuardApp/UI/macOS/View/highlighter.c index d89feda1e..5caa09d3f 100644 --- a/Sources/WireGuardApp/UI/macOS/View/highlighter.c +++ b/Sources/WireGuardApp/UI/macOS/View/highlighter.c @@ -343,6 +343,7 @@ enum field { ListenPort, Address, DNS, + DNSMatchDomains, MTU, #ifndef MOBILE_WGQUICK_SUBSET FwMark, @@ -377,6 +378,7 @@ static enum field get_field(string_span_t s) check_enum(ListenPort); check_enum(Address); check_enum(DNS); + check_enum(DNSMatchDomains); check_enum(MTU); check_enum(PublicKey); check_enum(PresharedKey); @@ -453,6 +455,12 @@ static void highlight_multivalue_value(struct highlight_span_array *ret, const s else append_highlight_span(ret, parent.s, s, HighlightError); break; + case DNSMatchDomains: + if (is_valid_hostname(s)) + append_highlight_span(ret, parent.s, s, HighlightHost); + else + append_highlight_span(ret, parent.s, s, HighlightError); + break; case Address: case AllowedIPs: { size_t slash; @@ -563,6 +571,7 @@ static void highlight_value(struct highlight_span_array *ret, const string_span_ } case Address: case DNS: + case DNSMatchDomains: case AllowedIPs: highlight_multivalue(ret, parent, s, section); break; diff --git a/Sources/WireGuardApp/UI/macOS/ViewController/TunnelDetailTableViewController.swift b/Sources/WireGuardApp/UI/macOS/ViewController/TunnelDetailTableViewController.swift index 6ad8cf3f5..2d87659ce 100644 --- a/Sources/WireGuardApp/UI/macOS/ViewController/TunnelDetailTableViewController.swift +++ b/Sources/WireGuardApp/UI/macOS/ViewController/TunnelDetailTableViewController.swift @@ -35,7 +35,7 @@ class TunnelDetailTableViewController: NSViewController { static let interfaceFields: [TunnelViewModel.InterfaceField] = [ .name, .status, .publicKey, .addresses, - .listenPort, .mtu, .dns, .toggleStatus + .listenPort, .mtu, .dns, .dnsMatchDomains, .toggleStatus ] static let peerFields: [TunnelViewModel.PeerField] = [ diff --git a/Sources/WireGuardKit/InterfaceConfiguration.swift b/Sources/WireGuardKit/InterfaceConfiguration.swift index 4fb8f1b4c..521d4b88d 100644 --- a/Sources/WireGuardKit/InterfaceConfiguration.swift +++ b/Sources/WireGuardKit/InterfaceConfiguration.swift @@ -11,6 +11,7 @@ public struct InterfaceConfiguration { public var mtu: UInt16? public var dns = [DNSServer]() public var dnsSearch = [String]() + public var dnsMatchDomains = [String]() public init(privateKey: PrivateKey) { self.privateKey = privateKey @@ -27,6 +28,7 @@ extension InterfaceConfiguration: Equatable { lhs.listenPort == rhs.listenPort && lhs.mtu == rhs.mtu && lhs.dns == rhs.dns && - lhs.dnsSearch == rhs.dnsSearch + lhs.dnsSearch == rhs.dnsSearch && + lhs.dnsMatchDomains == rhs.dnsMatchDomains } } diff --git a/Sources/WireGuardKit/PacketTunnelSettingsGenerator.swift b/Sources/WireGuardKit/PacketTunnelSettingsGenerator.swift index c53a82cde..b059510f8 100644 --- a/Sources/WireGuardKit/PacketTunnelSettingsGenerator.swift +++ b/Sources/WireGuardKit/PacketTunnelSettingsGenerator.swift @@ -87,9 +87,28 @@ class PacketTunnelSettingsGenerator { let dnsServerStrings = tunnelConfiguration.interface.dns.map { $0.stringRepresentation } let dnsSettings = NEDNSSettings(servers: dnsServerStrings) dnsSettings.searchDomains = tunnelConfiguration.interface.dnsSearch + if !tunnelConfiguration.interface.dns.isEmpty { - dnsSettings.matchDomains = [""] // All DNS queries must first go through the tunnel's DNS + dnsSettings.matchDomainsNoSearch = true + if tunnelConfiguration.interface.dnsMatchDomains.isEmpty { + // Add "" so that all DNS queries must first go through the tunnel's DNS. + // NEDNSSettings.searchDomains does not work so we add the searches to matchDomains, + // which does work. + dnsSettings.matchDomains = [""] + tunnelConfiguration.interface.dnsSearch + dnsSettings.matchDomainsNoSearch = false + } else { + // Don't add dnsSearch here because that would cause domains that aren't + // in dnsMatchDomains to be matched. + dnsSettings.matchDomains = tunnelConfiguration.interface.dnsMatchDomains + for domain in tunnelConfiguration.interface.dnsMatchDomains { + if tunnelConfiguration.interface.dnsSearch.contains(domain) { + dnsSettings.matchDomainsNoSearch = false + break + } + } + } } + networkSettings.dnsSettings = dnsSettings }