diff --git a/WireGuard/WireGuardNetworkExtension/DNSResolver.swift b/WireGuard/WireGuardNetworkExtension/DNSResolver.swift index 0a76c20..1ab5623 100644 --- a/WireGuard/WireGuardNetworkExtension/DNSResolver.swift +++ b/WireGuard/WireGuardNetworkExtension/DNSResolver.swift @@ -45,7 +45,6 @@ class DNSResolver { let resolvedEndpoint = tuple.1 if let endpoint = endpoint { if resolvedEndpoint == nil { - // DNS resolution failed guard let hostname = endpoint.hostname() else { fatalError() } hostnamesWithDnsResolutionFailure.append(hostname) } @@ -57,81 +56,97 @@ class DNSResolver { } return resolvedEndpoints } -} -extension DNSResolver { - // Based on DNS resolution code by Jason Donenfeld - // in parse_endpoint() in src/tools/config.c in the WireGuard codebase - - //swiftlint:disable:next cyclomatic_complexity private static func resolveSync(endpoint: Endpoint) -> Endpoint? { switch endpoint.host { case .name(let name, _): var resultPointer = UnsafeMutablePointer(OpaquePointer(bitPattern: 0)) + var hints = addrinfo( + ai_flags: AI_ALL, // We set this to ALL so that we get v4 addresses even on DNS64 networks + ai_family: AF_UNSPEC, + ai_socktype: SOCK_DGRAM, + ai_protocol: IPPROTO_UDP, + ai_addrlen: 0, + ai_canonname: nil, + ai_addr: nil, + ai_next: nil) + if getaddrinfo(name, "\(endpoint.port)", &hints, &resultPointer) != 0 { + return nil + } + var next = resultPointer + var ipv4Address: IPv4Address? + var ipv6Address: IPv6Address? + while next != nil { + let result = next!.pointee + next = result.ai_next + if result.ai_family == AF_INET && result.ai_addrlen == MemoryLayout.size { + var sa4 = UnsafeRawPointer(result.ai_addr)!.assumingMemoryBound(to: sockaddr_in.self).pointee + ipv4Address = IPv4Address(Data(bytes: &sa4.sin_addr, count: MemoryLayout.size)) + break // If we found an IPv4 address, we can stop + } else if result.ai_family == AF_INET6 && result.ai_addrlen == MemoryLayout.size { + var sa6 = UnsafeRawPointer(result.ai_addr)!.assumingMemoryBound(to: sockaddr_in6.self).pointee + ipv6Address = IPv6Address(Data(bytes: &sa6.sin6_addr, count: MemoryLayout.size)) + continue // If we already have an IPv6 address, we can skip this one + } + } + freeaddrinfo(resultPointer) - // The endpoint is a hostname and needs DNS resolution - if addressInfo(for: name, port: endpoint.port, resultPointer: &resultPointer) == 0 { - // getaddrinfo succeeded - let ipv4Buffer = UnsafeMutablePointer.allocate(capacity: Int(INET_ADDRSTRLEN)) - let ipv6Buffer = UnsafeMutablePointer.allocate(capacity: Int(INET6_ADDRSTRLEN)) - var ipv4AddressString: String? - var ipv6AddressString: String? - while resultPointer != nil { - let result = resultPointer!.pointee - resultPointer = result.ai_next - if result.ai_family == AF_INET && result.ai_addrlen == MemoryLayout.size { - var sa4 = UnsafeRawPointer(result.ai_addr)!.assumingMemoryBound(to: sockaddr_in.self).pointee - if inet_ntop(result.ai_family, &sa4.sin_addr, ipv4Buffer, socklen_t(INET_ADDRSTRLEN)) != nil { - ipv4AddressString = String(cString: ipv4Buffer) - // If we found an IPv4 address, we can stop - break - } - } else if result.ai_family == AF_INET6 && result.ai_addrlen == MemoryLayout.size { - if ipv6AddressString != nil { - // If we already have an IPv6 address, we can skip this one - continue - } - var sa6 = UnsafeRawPointer(result.ai_addr)!.assumingMemoryBound(to: sockaddr_in6.self).pointee - if inet_ntop(result.ai_family, &sa6.sin6_addr, ipv6Buffer, socklen_t(INET6_ADDRSTRLEN)) != nil { - ipv6AddressString = String(cString: ipv6Buffer) - } - } - } - ipv4Buffer.deallocate() - ipv6Buffer.deallocate() - // We prefer an IPv4 address over an IPv6 address - if let ipv4AddressString = ipv4AddressString, let ipv4Address = IPv4Address(ipv4AddressString) { - return Endpoint(host: .ipv4(ipv4Address), port: endpoint.port) - } else if let ipv6AddressString = ipv6AddressString, let ipv6Address = IPv6Address(ipv6AddressString) { - return Endpoint(host: .ipv6(ipv6Address), port: endpoint.port) - } else { - return nil - } + // We prefer an IPv4 address over an IPv6 address + if let ipv4Address = ipv4Address { + return Endpoint(host: .ipv4(ipv4Address), port: endpoint.port) + } else if let ipv6Address = ipv6Address { + return Endpoint(host: .ipv6(ipv6Address), port: endpoint.port) } else { - // getaddrinfo failed return nil } default: - // The endpoint is already resolved return endpoint } } +} - private static func addressInfo(for name: String, port: NWEndpoint.Port, resultPointer: inout UnsafeMutablePointer?) -> Int32 { +extension Endpoint { + func withReresolvedIP() -> Endpoint { + var ret = self + let hostname: String + switch host { + case .name(let name, _): + hostname = name + case .ipv4(let address): + hostname = "\(address)" + case .ipv6(let address): + hostname = "\(address)" + } + + var resultPointer = UnsafeMutablePointer(OpaquePointer(bitPattern: 0)) var hints = addrinfo( - ai_flags: 0, + ai_flags: 0, // We set this to zero so that we actually resolve this using DNS64 ai_family: AF_UNSPEC, - ai_socktype: SOCK_DGRAM, // WireGuard is UDP-only - ai_protocol: IPPROTO_UDP, // WireGuard is UDP-only + ai_socktype: SOCK_DGRAM, + ai_protocol: IPPROTO_UDP, ai_addrlen: 0, ai_canonname: nil, ai_addr: nil, ai_next: nil) - - return getaddrinfo( - name.cString(using: .utf8), // Hostname - "\(port)".cString(using: .utf8), // Port - &hints, - &resultPointer) + if getaddrinfo(hostname, "\(port)", &hints, &resultPointer) != 0 || resultPointer == nil { + return ret + } + let result = resultPointer!.pointee + if result.ai_family == AF_INET && result.ai_addrlen == MemoryLayout.size { + var sa4 = UnsafeRawPointer(result.ai_addr)!.assumingMemoryBound(to: sockaddr_in.self).pointee + let addr = IPv4Address(Data(bytes: &sa4.sin_addr, count: MemoryLayout.size)) + ret = Endpoint(host: .ipv4(addr!), port: port) + } else if result.ai_family == AF_INET6 && result.ai_addrlen == MemoryLayout.size { + var sa6 = UnsafeRawPointer(result.ai_addr)!.assumingMemoryBound(to: sockaddr_in6.self).pointee + let addr = IPv6Address(Data(bytes: &sa6.sin6_addr, count: MemoryLayout.size)) + ret = Endpoint(host: .ipv6(addr!), port: port) + } + freeaddrinfo(resultPointer) + if ret.host != host { + wg_log(.debug, message: "DNS64: mapped \(host) to \(ret.host)") + } else { + wg_log(.debug, message: "DNS64: mapped \(host) to itself.") + } + return ret } } diff --git a/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift b/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift index 67b1f4d..b00f197 100644 --- a/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift +++ b/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift @@ -17,6 +17,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { networkMonitor?.cancel() } + //swiftlint:disable:next function_body_length override func startTunnel(options: [String: NSObject]?, completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) { let activationAttemptId = options?["activationAttemptId"] as? String let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId) @@ -65,6 +66,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { if getsockopt(fileDescriptor, 2 /* SYSPROTO_CONTROL */, 2 /* UTUN_OPT_IFNAME */, ifnamePtr, &ifnameSize) == 0 { self.ifname = String(cString: ifnamePtr) } + ifnamePtr.deallocate() wg_log(.info, message: "Tunnel interface is \(self.ifname ?? "unknown")") let handle = self.packetTunnelSettingsGenerator!.uapiConfiguration().withGoString { return wgTurnOn($0, fileDescriptor) } if handle < 0 { diff --git a/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift b/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift index 5946843..4fd84fc 100644 --- a/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift +++ b/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift @@ -18,7 +18,7 @@ class PacketTunnelSettingsGenerator { var wgSettings = "" for (index, peer) in tunnelConfiguration.peers.enumerated() { wgSettings.append("public_key=\(peer.publicKey.hexEncodedString())\n") - if let endpoint = resolvedEndpoints[index] { + if let endpoint = resolvedEndpoints[index]?.withReresolvedIP() { if case .name(_, _) = endpoint.host { assert(false, "Endpoint is not resolved") } wgSettings.append("endpoint=\(endpoint.stringRepresentation)\n") } @@ -42,7 +42,7 @@ class PacketTunnelSettingsGenerator { if let preSharedKey = peer.preSharedKey { wgSettings.append("preshared_key=\(preSharedKey.hexEncodedString())\n") } - if let endpoint = resolvedEndpoints[index] { + if let endpoint = resolvedEndpoints[index]?.withReresolvedIP() { if case .name(_, _) = endpoint.host { assert(false, "Endpoint is not resolved") } wgSettings.append("endpoint=\(endpoint.stringRepresentation)\n") } @@ -63,18 +63,7 @@ class PacketTunnelSettingsGenerator { * make sense. So, we fill it in with this placeholder, which is not * a valid IP address that will actually route over the Internet. */ - var remoteAddress = "0.0.0.0" - let endpointsCompact = resolvedEndpoints.compactMap { $0 } - if endpointsCompact.count == 1 { - switch endpointsCompact.first!.host { - case .ipv4(let address): - remoteAddress = "\(address)" - case .ipv6(let address): - remoteAddress = "\(address)" - default: - break - } - } + let remoteAddress = "0.0.0.0" let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: remoteAddress) @@ -93,16 +82,13 @@ class PacketTunnelSettingsGenerator { let (ipv4Routes, ipv6Routes) = routes() let (ipv4IncludedRoutes, ipv6IncludedRoutes) = includedRoutes() - let (ipv4ExcludedRoutes, ipv6ExcludedRoutes) = excludedRoutes() let ipv4Settings = NEIPv4Settings(addresses: ipv4Routes.map { $0.destinationAddress }, subnetMasks: ipv4Routes.map { $0.destinationSubnetMask }) ipv4Settings.includedRoutes = ipv4IncludedRoutes - ipv4Settings.excludedRoutes = ipv4ExcludedRoutes networkSettings.ipv4Settings = ipv4Settings let ipv6Settings = NEIPv6Settings(addresses: ipv6Routes.map { $0.destinationAddress }, networkPrefixLengths: ipv6Routes.map { $0.destinationNetworkPrefixLength }) ipv6Settings.includedRoutes = ipv6IncludedRoutes - ipv6Settings.excludedRoutes = ipv6ExcludedRoutes networkSettings.ipv6Settings = ipv6Settings return networkSettings @@ -152,24 +138,6 @@ class PacketTunnelSettingsGenerator { } return (ipv4IncludedRoutes, ipv6IncludedRoutes) } - - private func excludedRoutes() -> ([NEIPv4Route], [NEIPv6Route]) { - var ipv4ExcludedRoutes = [NEIPv4Route]() - var ipv6ExcludedRoutes = [NEIPv6Route]() - for endpoint in resolvedEndpoints { - guard let endpoint = endpoint else { continue } - switch endpoint.host { - case .ipv4(let address): - ipv4ExcludedRoutes.append(NEIPv4Route(destinationAddress: "\(address)", subnetMask: "255.255.255.255")) - case .ipv6(let address): - ipv6ExcludedRoutes.append(NEIPv6Route(destinationAddress: "\(address)", networkPrefixLength: NSNumber(value: UInt8(128)))) - default: - fatalError() - } - } - return (ipv4ExcludedRoutes, ipv6ExcludedRoutes) - } - } private extension Data {