From 3f4d12c4045e014a68fcd9cd8344a2c80fd349f8 Mon Sep 17 00:00:00 2001 From: Roopesh Chander Date: Fri, 26 Oct 2018 18:22:06 +0530 Subject: [PATCH] VPN: DNSResolver: A class that does DNS resolution using getaddrinfo Signed-off-by: Roopesh Chander --- WireGuard/WireGuard.xcodeproj/project.pbxproj | 4 + WireGuard/WireGuard/VPN/DNSResolver.swift | 93 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 WireGuard/WireGuard/VPN/DNSResolver.swift diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj index 741e419..e8feb5c 100644 --- a/WireGuard/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 6F5D0C1521832391000F85AD /* DNSResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C1421832391000F85AD /* DNSResolver.swift */; }; 6F628C3D217F09E9003482A3 /* TunnelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */; }; 6F628C3F217F3413003482A3 /* DNSServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3E217F3413003482A3 /* DNSServer.swift */; }; 6F628C41217F47DB003482A3 /* TunnelDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */; }; @@ -28,6 +29,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 6F5D0C1421832391000F85AD /* DNSResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSResolver.swift; sourceTree = ""; }; 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelViewModel.swift; sourceTree = ""; }; 6F628C3E217F3413003482A3 /* DNSServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSServer.swift; sourceTree = ""; }; 6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelDetailTableViewController.swift; sourceTree = ""; }; @@ -121,6 +123,7 @@ isa = PBXGroup; children = ( 6F7774EE21722D97006A79B3 /* TunnelsManager.swift */, + 6F5D0C1421832391000F85AD /* DNSResolver.swift */, ); path = VPN; sourceTree = ""; @@ -247,6 +250,7 @@ 6F6899AC218099F00012E523 /* WgQuickConfigFileParser.swift in Sources */, 6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */, 6F7774EF21722D97006A79B3 /* TunnelsManager.swift in Sources */, + 6F5D0C1521832391000F85AD /* DNSResolver.swift in Sources */, 6F693A562179E556008551C1 /* Endpoint.swift in Sources */, 6F6899A62180447E0012E523 /* x25519.c in Sources */, 6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */, diff --git a/WireGuard/WireGuard/VPN/DNSResolver.swift b/WireGuard/WireGuard/VPN/DNSResolver.swift new file mode 100644 index 0000000..89b1a83 --- /dev/null +++ b/WireGuard/WireGuard/VPN/DNSResolver.swift @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018 WireGuard LLC. All rights reserved. + +import Network +import Foundation + +class DNSResolver { + let endpoints: [Endpoint] + + init(endpoints: [Endpoint]) { + self.endpoints = endpoints + } + + func resolve(completionHandler: @escaping ([Endpoint?]) -> Void) { + let endpoints = self.endpoints + DispatchQueue.global(qos: .userInitiated).async { + var resolvedEndpoints = Array(repeating: nil, count: endpoints.count) + for (i, endpoint) in endpoints.enumerated() { + let resolvedEndpoint = DNSResolver.resolveSync(endpoint: endpoint) + resolvedEndpoints[i] = resolvedEndpoint + } + DispatchQueue.main.async { + completionHandler(resolvedEndpoints) + } + } + } + + // Based on DNS resolution code by Jason Donenfeld + // in parse_endpoint() in src/tools/config.c in the WireGuard codebase + private static func resolveSync(endpoint: Endpoint) -> Endpoint? { + var hints = addrinfo( + ai_flags: 0, + ai_family: AF_UNSPEC, + ai_socktype: SOCK_DGRAM, // WireGuard is UDP-only + ai_protocol: IPPROTO_UDP, // WireGuard is UDP-only + ai_addrlen: 0, + ai_canonname: nil, + ai_addr: nil, + ai_next: nil) + var resultPointer = UnsafeMutablePointer(OpaquePointer(bitPattern: 0)) + switch (endpoint.host) { + case .name(let name, _): + // The endpoint is a hostname and needs DNS resolution + let returnValue = getaddrinfo( + name.cString(using: .utf8), // Hostname + "\(endpoint.port)".cString(using: .utf8), // Port + &hints, + &resultPointer) + if (returnValue == 0) { + // getaddrinfo succeeded + let ipv4Buffer = UnsafeMutablePointer.allocate(capacity: Int(INET_ADDRSTRLEN)) + let ipv6Buffer = UnsafeMutablePointer.allocate(capacity: Int(INET6_ADDRSTRLEN)) + var ipv4AddressString: String? = nil + var ipv6AddressString: String? = nil + while (resultPointer != nil) { + let result = resultPointer!.pointee + resultPointer = result.ai_next + if (result.ai_family == AF_INET && result.ai_addrlen == INET_ADDRSTRLEN) { + if (inet_ntop(result.ai_family, result.ai_addr, ipv4Buffer, result.ai_addrlen) != 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 == INET6_ADDRSTRLEN) { + if (ipv6AddressString != nil) { + // If we already have an IPv6 address, we can skip this one + continue + } + if (inet_ntop(result.ai_family, result.ai_addr, ipv6Buffer, result.ai_addrlen) != 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: NWEndpoint.Host.ipv4(ipv4Address), port: endpoint.port) + } else if let ipv6AddressString = ipv6AddressString, let ipv6Address = IPv6Address(ipv6AddressString) { + return Endpoint(host: NWEndpoint.Host.ipv6(ipv6Address), port: endpoint.port) + } else { + return nil + } + } else { + // getaddrinfo failed + return nil + } + default: + // The endpoint is already resolved + return endpoint + } + } +}