diff --git a/CHANGELOG.md b/CHANGELOG.md index 524374f..040362d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SoftEther sends an incomplete PUSH_REPLY. [#86](https://github.com/keeshux/tunnelkit/issues/86) - Authentication/Decrypt errors with TLS wrapping. [#88](https://github.com/keeshux/tunnelkit/issues/88), [#61](https://github.com/keeshux/tunnelkit/issues/61) +- Broken DNS when no servers provided. [#84](https://github.com/keeshux/tunnelkit/issues/84) ## 1.6.2 (2019-04-17) diff --git a/TunnelKit.podspec b/TunnelKit.podspec index d1888e7..4bfd0b2 100644 --- a/TunnelKit.podspec +++ b/TunnelKit.podspec @@ -23,6 +23,7 @@ Pod::Spec.new do |s| "APPLICATION_EXTENSION_API_ONLY" => "YES" } p.dependency "SwiftyBeaver" p.dependency "OpenSSL-Apple", "~> 1.1.0i.2" + p.libraries = "resolv" end s.subspec "AppExtension" do |p| diff --git a/TunnelKit.xcodeproj/project.pbxproj b/TunnelKit.xcodeproj/project.pbxproj index 1acd58e..19a6fda 100644 --- a/TunnelKit.xcodeproj/project.pbxproj +++ b/TunnelKit.xcodeproj/project.pbxproj @@ -67,6 +67,12 @@ 0E3B65772249254000EFF4DA /* tunnelbear.key in Resources */ = {isa = PBXBuildFile; fileRef = 0E3B65712249247E00EFF4DA /* tunnelbear.key */; }; 0E3E0F212108A8CC00B371C1 /* SessionProxy+SessionReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionProxy+SessionReply.swift */; }; 0E3E0F222108A8CC00B371C1 /* SessionProxy+SessionReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionProxy+SessionReply.swift */; }; + 0E411B9B2271F90700E0852C /* DNS.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E411B992271F90700E0852C /* DNS.h */; }; + 0E411B9C2271F90700E0852C /* DNS.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E411B992271F90700E0852C /* DNS.h */; }; + 0E411B9D2271F90700E0852C /* DNS.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E411B9A2271F90700E0852C /* DNS.m */; }; + 0E411B9E2271F90700E0852C /* DNS.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E411B9A2271F90700E0852C /* DNS.m */; }; + 0E411BA02271FA3300E0852C /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E411B9F2271FA3300E0852C /* libresolv.tbd */; }; + 0E411BA22271FA3C00E0852C /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E411BA12271FA3C00E0852C /* libresolv.tbd */; }; 0E48AC642271ADA9009B1A98 /* PacketStream.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E48AC622271ADA8009B1A98 /* PacketStream.h */; }; 0E48AC652271ADA9009B1A98 /* PacketStream.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E48AC622271ADA8009B1A98 /* PacketStream.h */; }; 0E48AC662271ADA9009B1A98 /* PacketStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E48AC632271ADA9009B1A98 /* PacketStream.m */; }; @@ -311,6 +317,10 @@ 0E3B656E224923EC00EFF4DA /* tunnelbear.enc.1.ovpn */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = tunnelbear.enc.1.ovpn; sourceTree = ""; }; 0E3B65712249247E00EFF4DA /* tunnelbear.key */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = tunnelbear.key; sourceTree = ""; }; 0E3E0F202108A8CC00B371C1 /* SessionProxy+SessionReply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionProxy+SessionReply.swift"; sourceTree = ""; }; + 0E411B992271F90700E0852C /* DNS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DNS.h; sourceTree = ""; }; + 0E411B9A2271F90700E0852C /* DNS.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DNS.m; sourceTree = ""; }; + 0E411B9F2271FA3300E0852C /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/lib/libresolv.tbd; sourceTree = DEVELOPER_DIR; }; + 0E411BA12271FA3C00E0852C /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; }; 0E48AC622271ADA8009B1A98 /* PacketStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PacketStream.h; sourceTree = ""; }; 0E48AC632271ADA9009B1A98 /* PacketStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PacketStream.m; sourceTree = ""; }; 0E500EA522493B5B00CAE560 /* tunnelbear.enc.1.key */ = {isa = PBXFileReference; lastKnownFileType = text; path = tunnelbear.enc.1.key; sourceTree = ""; }; @@ -435,6 +445,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 0E411BA22271FA3C00E0852C /* libresolv.tbd in Frameworks */, B4C2A996F52241B77E7762BD /* Pods_TunnelKit_TunnelKit_iOS.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -443,6 +454,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 0E411BA02271FA3300E0852C /* libresolv.tbd in Frameworks */, C20E3DDE8043C59193100CD3 /* Pods_TunnelKit_TunnelKit_macOS.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -645,6 +657,8 @@ 0EFEB4352006D3C800F81029 /* DataPath.h */, 0EFEB44C2006D3C800F81029 /* DataPath.m */, 0EE7A79D20F6488400B42E6A /* DataPathCrypto.h */, + 0E411B992271F90700E0852C /* DNS.h */, + 0E411B9A2271F90700E0852C /* DNS.m */, 0E011F7C2196D97200BA59EE /* EndpointProtocol.swift */, 0EFEB4362006D3C800F81029 /* Errors.h */, 0EFEB44B2006D3C800F81029 /* Errors.m */, @@ -709,6 +723,8 @@ 1B27D1F0B446D5907FAF40E1 /* Frameworks */ = { isa = PBXGroup; children = ( + 0E411B9F2271FA3300E0852C /* libresolv.tbd */, + 0E411BA12271FA3C00E0852C /* libresolv.tbd */, 276657B3FED3840178C53D6B /* Pods_TunnelKit_TunnelKit_iOS.framework */, A23792F839E9B80C5EEB9D11 /* Pods_TunnelKit_TunnelKit_macOS.framework */, BF76B5FEAAFD5056FFCC5DA2 /* Pods_TunnelKit_TunnelKitHost.framework */, @@ -751,6 +767,7 @@ 0E58BF502240F98F006FB157 /* CompressionAlgorithmNative.h in Headers */, 0E07596320EF733F00F38FD8 /* CryptoMacros.h in Headers */, 0EFEB46E2006D3C800F81029 /* TLSBox.h in Headers */, + 0E411B9B2271F90700E0852C /* DNS.h in Headers */, 0E07596B20EF79AB00F38FD8 /* Crypto.h in Headers */, 0EFEB46B2006D3C800F81029 /* CryptoBox.h in Headers */, 0EFEB4592006D3C800F81029 /* Allocation.h in Headers */, @@ -780,6 +797,7 @@ 0E58BF512240F98F006FB157 /* CompressionAlgorithmNative.h in Headers */, 0E07596420EF733F00F38FD8 /* CryptoMacros.h in Headers */, 0EEC49EA20B5F7F6008FEB91 /* ZeroingData.h in Headers */, + 0E411B9C2271F90700E0852C /* DNS.h in Headers */, 0E07596C20EF79AB00F38FD8 /* Crypto.h in Headers */, 0EEC49E120B5F7EA008FEB91 /* Allocation.h in Headers */, 0EEC49E320B5F7F6008FEB91 /* DataPath.h in Headers */, @@ -1230,6 +1248,7 @@ 0EBBF2F3208505D300E36B40 /* NEUDPInterface.swift in Sources */, 0EFEB4682006D3C800F81029 /* MSS.m in Sources */, 0E48AC662271ADA9009B1A98 /* PacketStream.m in Sources */, + 0E411B9D2271F90700E0852C /* DNS.m in Sources */, 0EFEB45B2006D3C800F81029 /* TLSBox.m in Sources */, 0EFEB4792006D3C800F81029 /* TunnelKitProvider+Interaction.swift in Sources */, 0E58BF3922405410006FB157 /* minilzo.c in Sources */, @@ -1299,6 +1318,7 @@ 0EFEB49C2006D7F300F81029 /* Data+Manipulation.swift in Sources */, 0EBBF2F4208505D400E36B40 /* NEUDPInterface.swift in Sources */, 0E48AC672271ADA9009B1A98 /* PacketStream.m in Sources */, + 0E411B9E2271F90700E0852C /* DNS.m in Sources */, 0EFEB4902006D7F300F81029 /* TunnelInterface.swift in Sources */, 0EFEB49E2006D7F300F81029 /* Allocation.m in Sources */, 0E58BF3A22405410006FB157 /* minilzo.c in Sources */, diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift index b977ffc..326cde8 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift @@ -578,8 +578,10 @@ extension TunnelKitProvider { } else { log.info("\tDefault gateway: no") } - if let dnsServers = sessionConfiguration.dnsServers { - log.info("\tDNS servers: \(dnsServers.maskedDescription)") + if let dnsServers = sessionConfiguration.dnsServers, !dnsServers.isEmpty { + log.info("\tDNS: \(dnsServers.maskedDescription)") + } else { + log.info("\tDNS: default") } if let searchDomain = sessionConfiguration.searchDomain { log.info("\tSearch domain: \(searchDomain.maskedDescription)") diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift index e38ee0c..dd957c9 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift @@ -37,6 +37,7 @@ import NetworkExtension import SwiftyBeaver +import __TunnelKitNative private let log = SwiftyBeaver.self @@ -75,6 +76,9 @@ open class TunnelKitProvider: NEPacketTunnelProvider { /// The number of milliseconds between data count updates. Set to 0 to disable updates (default). public var dataCountInterval = 0 + /// A list of fallback DNS servers when none provided (defaults to "1.1.1.1"). + public var fallbackDNSServers = ["1.1.1.1"] + // MARK: Constants private let memoryLog = MemoryDestination() @@ -472,12 +476,13 @@ extension TunnelKitProvider: SessionProxyDelegate { log.info("\tRemote: \(remoteAddress.maskedDescription)") log.info("\tIPv4: \(reply.options.ipv4?.description ?? "not configured")") log.info("\tIPv6: \(reply.options.ipv6?.description ?? "not configured")") - if let dnsServers = reply.options.dnsServers { + // FIXME: refine logging of other routing policies + log.info("\tDefault gateway: \(reply.options.routingPolicies?.maskedDescription ?? "not configured")") + if let dnsServers = reply.options.dnsServers, !dnsServers.isEmpty { log.info("\tDNS: \(dnsServers.map { $0.maskedDescription })") } else { log.info("\tDNS: not configured") } - log.info("\tRouting policies: \(reply.options.routingPolicies?.maskedDescription ?? "not configured")") log.info("\tDomain: \(reply.options.searchDomain?.maskedDescription ?? "not configured")") if reply.options.httpProxy != nil || reply.options.httpsProxy != nil { @@ -570,16 +575,37 @@ extension TunnelKitProvider: SessionProxyDelegate { ipv6Settings?.includedRoutes = routes ipv6Settings?.excludedRoutes = [] } - - var dnsServers = cfg.sessionConfiguration.dnsServers - if dnsServers?.isEmpty ?? true { - dnsServers = reply.options.dnsServers + + var dnsSettings: NEDNSSettings? + var dnsServers = cfg.sessionConfiguration.dnsServers ?? [] + if let replyDNSServers = reply.options.dnsServers { + dnsServers.append(contentsOf: replyDNSServers) } - // FIXME: default to DNS servers from current network instead - let dnsSettings = NEDNSSettings(servers: dnsServers ?? []) + + // fall back to system-wide DNS servers + if dnsServers.isEmpty { + log.warning("DNS: No servers provided, falling back to \(fallbackDNSServers)") + dnsServers = fallbackDNSServers + + // XXX: no quick way to make this work on Safari, even if ping and lookup work in iNetTools +// let systemServers = DNS().systemServers() +// log.warning("DNS: No servers provided, falling back to system settings: \(systemServers)") +// dnsServers = systemServers +// +// // make DNS reachable outside VPN (yes, a controlled leak to keep things operational) +// for address in dnsServers { +// if address.contains(":") { +// ipv6Settings?.excludedRoutes?.append(NEIPv6Route(destinationAddress: address, networkPrefixLength: 128)) +// } else { +// ipv4Settings?.excludedRoutes?.append(NEIPv4Route(destinationAddress: address, subnetMask: "255.255.255.255")) +// } +// } + } + + dnsSettings = NEDNSSettings(servers: dnsServers) if let searchDomain = cfg.sessionConfiguration.searchDomain ?? reply.options.searchDomain { - dnsSettings.domainName = searchDomain - dnsSettings.searchDomains = [searchDomain] + dnsSettings?.domainName = searchDomain + dnsSettings?.searchDomains = [searchDomain] } var proxySettings: NEProxySettings? diff --git a/TunnelKit/Sources/Core/DNS.h b/TunnelKit/Sources/Core/DNS.h new file mode 100644 index 0000000..4b03521 --- /dev/null +++ b/TunnelKit/Sources/Core/DNS.h @@ -0,0 +1,36 @@ +// +// DNS.h +// TunnelKit +// +// Created by Davide De Rosa on 4/25/19. +// Copyright (c) 2019 Davide De Rosa. All rights reserved. +// +// https://github.com/keeshux +// +// This file is part of TunnelKit. +// +// TunnelKit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TunnelKit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TunnelKit. If not, see . +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DNS : NSObject + +- (NSArray *)systemServers; + +@end + +NS_ASSUME_NONNULL_END diff --git a/TunnelKit/Sources/Core/DNS.m b/TunnelKit/Sources/Core/DNS.m new file mode 100644 index 0000000..c9ef5fc --- /dev/null +++ b/TunnelKit/Sources/Core/DNS.m @@ -0,0 +1,82 @@ +// +// DNS.m +// TunnelKit +// +// Created by Davide De Rosa on 4/25/19. +// Copyright (c) 2019 Davide De Rosa. All rights reserved. +// +// https://github.com/keeshux +// +// This file is part of TunnelKit. +// +// TunnelKit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TunnelKit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TunnelKit. If not, see . +// + +// adapted from: https://stackoverflow.com/questions/31256024/get-dns-server-ip-from-iphone-settings + +#import +#import + +#import "DNS.h" + +@implementation DNS { + res_state _state; +} + +- (instancetype)init +{ + if (self = [super init]) { + _state = malloc(sizeof(struct __res_state)); + if (EXIT_SUCCESS != res_ninit(_state)) { + free(_state); + return nil; + } + } + return self; +} + +- (void)dealloc +{ + res_ndestroy(_state); + free(_state); +} + +- (NSArray *)systemServers +{ + NSMutableArray *addresses = [[NSMutableArray alloc] init]; + + union res_sockaddr_union servers[NI_MAXSERV]; + const int found = res_9_getservers(_state, servers, NI_MAXSERV); + char hostBuffer[NI_MAXHOST]; + + for (int i = 0; i < found; ++i) { + union res_sockaddr_union s = servers[i]; + if (s.sin.sin_len <= 0) { + continue; + } + if (EXIT_SUCCESS == getnameinfo((struct sockaddr *)&s.sin, // Pointer to your struct sockaddr + (socklen_t)s.sin.sin_len, // Size of this struct + (char *)&hostBuffer, // Pointer to hostname string + sizeof(hostBuffer), // Size of this string + nil, // Pointer to service name string + 0, // Size of this string + NI_NUMERICHOST)) { // Flags given + [addresses addObject:[NSString stringWithUTF8String:hostBuffer]]; + } + } + + return addresses; +} + +@end diff --git a/TunnelKit/Sources/Core/module.modulemap b/TunnelKit/Sources/Core/module.modulemap index b78163c..416d448 100644 --- a/TunnelKit/Sources/Core/module.modulemap +++ b/TunnelKit/Sources/Core/module.modulemap @@ -49,6 +49,7 @@ module __TunnelKitNative { header "CompressionAlgorithmNative.h" header "DataPath.h" header "DataPathCrypto.h" + header "DNS.h" header "LZO.h" export * } diff --git a/TunnelKitTests/DNSTests.swift b/TunnelKitTests/DNSTests.swift index 0835edb..59c186c 100644 --- a/TunnelKitTests/DNSTests.swift +++ b/TunnelKitTests/DNSTests.swift @@ -25,6 +25,7 @@ import XCTest import TunnelKit +import __TunnelKitNative class DNSTests: XCTestCase { @@ -46,4 +47,8 @@ class DNSTests: XCTestCase { XCTAssertEqual(DNSResolver.string(fromIPv4: DNSResolver.ipv4(fromString: addr)!), addr) XCTAssertEqual(DNSResolver.ipv4(fromString: DNSResolver.string(fromIPv4: ip)), ip) } + + func testSystem() { + print("DNS: \(DNS().systemServers())") + } }