ios: PCA Phobos VPN for iPhone (source)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Array {
|
||||
|
||||
/// Returns an array containing the results of mapping the given closure over the sequence’s
|
||||
/// elements concurrently.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - queue: The queue for performing concurrent computations.
|
||||
/// If the given queue is serial, the values are mapped in a serial fashion.
|
||||
/// Pass `nil` to perform computations on the current queue.
|
||||
/// - transform: the block to perform concurrent computations over the given element.
|
||||
/// - Returns: an array of concurrently computed values.
|
||||
func concurrentMap<U>(queue: DispatchQueue?, _ transform: (Element) -> U) -> [U] {
|
||||
var result = [U?](repeating: nil, count: self.count)
|
||||
let resultQueue = DispatchQueue(label: "ConcurrentMapQueue")
|
||||
|
||||
let execute = queue?.sync ?? { $0() }
|
||||
|
||||
execute {
|
||||
DispatchQueue.concurrentPerform(iterations: self.count) { index in
|
||||
let value = transform(self[index])
|
||||
resultQueue.sync {
|
||||
result[index] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.map { $0! }
|
||||
}
|
||||
}
|
||||
153
ios/WireGuardKit/Sources/WireGuardKit/DNSResolver.swift
Normal file
153
ios/WireGuardKit/Sources/WireGuardKit/DNSResolver.swift
Normal file
@@ -0,0 +1,153 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Network
|
||||
import Foundation
|
||||
|
||||
enum DNSResolver {}
|
||||
|
||||
extension DNSResolver {
|
||||
|
||||
/// Concurrent queue used for DNS resolutions
|
||||
private static let resolverQueue = DispatchQueue(label: "DNSResolverQueue", qos: .default, attributes: .concurrent)
|
||||
|
||||
static func resolveSync(endpoints: [Endpoint?]) -> [Result<Endpoint, DNSResolutionError>?] {
|
||||
let isAllEndpointsAlreadyResolved = endpoints.allSatisfy { maybeEndpoint -> Bool in
|
||||
return maybeEndpoint?.hasHostAsIPAddress() ?? true
|
||||
}
|
||||
|
||||
if isAllEndpointsAlreadyResolved {
|
||||
return endpoints.map { endpoint in
|
||||
return endpoint.map { .success($0) }
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints.concurrentMap(queue: resolverQueue) { endpoint -> Result<Endpoint, DNSResolutionError>? in
|
||||
guard let endpoint = endpoint else { return nil }
|
||||
|
||||
if endpoint.hasHostAsIPAddress() {
|
||||
return .success(endpoint)
|
||||
} else {
|
||||
return Result { try DNSResolver.resolveSync(endpoint: endpoint) }
|
||||
.mapError { error -> DNSResolutionError in
|
||||
// swiftlint:disable:next force_cast
|
||||
return error as! DNSResolutionError
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func resolveSync(endpoint: Endpoint) throws -> Endpoint {
|
||||
guard case .name(let name, _) = endpoint.host else {
|
||||
return endpoint
|
||||
}
|
||||
|
||||
var hints = addrinfo()
|
||||
hints.ai_flags = AI_ALL // We set this to ALL so that we get v4 addresses even on DNS64 networks
|
||||
hints.ai_family = AF_UNSPEC
|
||||
hints.ai_socktype = SOCK_DGRAM
|
||||
hints.ai_protocol = IPPROTO_UDP
|
||||
|
||||
var resultPointer: UnsafeMutablePointer<addrinfo>?
|
||||
defer {
|
||||
resultPointer.flatMap { freeaddrinfo($0) }
|
||||
}
|
||||
|
||||
let errorCode = getaddrinfo(name, "\(endpoint.port)", &hints, &resultPointer)
|
||||
if errorCode != 0 {
|
||||
throw DNSResolutionError(errorCode: errorCode, address: name)
|
||||
}
|
||||
|
||||
var ipv4Address: IPv4Address?
|
||||
var ipv6Address: IPv6Address?
|
||||
|
||||
var next: UnsafeMutablePointer<addrinfo>? = resultPointer
|
||||
let iterator = AnyIterator { () -> addrinfo? in
|
||||
let result = next?.pointee
|
||||
next = result?.ai_next
|
||||
return result
|
||||
}
|
||||
|
||||
for addrInfo in iterator {
|
||||
if let maybeIpv4Address = IPv4Address(addrInfo: addrInfo) {
|
||||
ipv4Address = maybeIpv4Address
|
||||
break // If we found an IPv4 address, we can stop
|
||||
} else if let maybeIpv6Address = IPv6Address(addrInfo: addrInfo) {
|
||||
ipv6Address = maybeIpv6Address
|
||||
continue // If we already have an IPv6 address, we can skip this one
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Must never happen
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Endpoint {
|
||||
func withReresolvedIP() throws -> Endpoint {
|
||||
#if os(iOS)
|
||||
let hostname: String
|
||||
switch host {
|
||||
case .name(let name, _):
|
||||
hostname = name
|
||||
case .ipv4(let address):
|
||||
hostname = "\(address)"
|
||||
case .ipv6(let address):
|
||||
hostname = "\(address)"
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
|
||||
var hints = addrinfo()
|
||||
hints.ai_family = AF_UNSPEC
|
||||
hints.ai_socktype = SOCK_DGRAM
|
||||
hints.ai_protocol = IPPROTO_UDP
|
||||
hints.ai_flags = 0 // We set this to zero so that we actually resolve this using DNS64
|
||||
|
||||
var result: UnsafeMutablePointer<addrinfo>?
|
||||
defer {
|
||||
result.flatMap { freeaddrinfo($0) }
|
||||
}
|
||||
|
||||
let errorCode = getaddrinfo(hostname, "\(self.port)", &hints, &result)
|
||||
if errorCode != 0 {
|
||||
throw DNSResolutionError(errorCode: errorCode, address: hostname)
|
||||
}
|
||||
|
||||
let addrInfo = result!.pointee
|
||||
if let ipv4Address = IPv4Address(addrInfo: addrInfo) {
|
||||
return Endpoint(host: .ipv4(ipv4Address), port: port)
|
||||
} else if let ipv6Address = IPv6Address(addrInfo: addrInfo) {
|
||||
return Endpoint(host: .ipv6(ipv6Address), port: port)
|
||||
} else {
|
||||
fatalError()
|
||||
}
|
||||
#elseif os(macOS)
|
||||
return self
|
||||
#else
|
||||
#error("Unimplemented")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// An error type describing DNS resolution error
|
||||
public struct DNSResolutionError: LocalizedError {
|
||||
public let errorCode: Int32
|
||||
public let address: String
|
||||
|
||||
init(errorCode: Int32, address: String) {
|
||||
self.errorCode = errorCode
|
||||
self.address = address
|
||||
}
|
||||
|
||||
public var errorDescription: String? {
|
||||
return String(cString: gai_strerror(errorCode))
|
||||
}
|
||||
}
|
||||
35
ios/WireGuardKit/Sources/WireGuardKit/DNSServer.swift
Normal file
35
ios/WireGuardKit/Sources/WireGuardKit/DNSServer.swift
Normal file
@@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
public struct DNSServer {
|
||||
public let address: IPAddress
|
||||
|
||||
public init(address: IPAddress) {
|
||||
self.address = address
|
||||
}
|
||||
}
|
||||
|
||||
extension DNSServer: Equatable {
|
||||
public static func == (lhs: DNSServer, rhs: DNSServer) -> Bool {
|
||||
return lhs.address.rawValue == rhs.address.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
extension DNSServer {
|
||||
public var stringRepresentation: String {
|
||||
return "\(address)"
|
||||
}
|
||||
|
||||
public init?(from addressString: String) {
|
||||
if let addr = IPv4Address(addressString) {
|
||||
address = addr
|
||||
} else if let addr = IPv6Address(addressString) {
|
||||
address = addr
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
101
ios/WireGuardKit/Sources/WireGuardKit/Endpoint.swift
Normal file
101
ios/WireGuardKit/Sources/WireGuardKit/Endpoint.swift
Normal file
@@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
public struct Endpoint {
|
||||
public let host: NWEndpoint.Host
|
||||
public let port: NWEndpoint.Port
|
||||
|
||||
public init(host: NWEndpoint.Host, port: NWEndpoint.Port) {
|
||||
self.host = host
|
||||
self.port = port
|
||||
}
|
||||
}
|
||||
|
||||
extension Endpoint: Equatable {
|
||||
public static func == (lhs: Endpoint, rhs: Endpoint) -> Bool {
|
||||
return lhs.host == rhs.host && lhs.port == rhs.port
|
||||
}
|
||||
}
|
||||
|
||||
extension Endpoint: Hashable {
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(host)
|
||||
hasher.combine(port)
|
||||
}
|
||||
}
|
||||
|
||||
extension Endpoint {
|
||||
public var stringRepresentation: String {
|
||||
switch host {
|
||||
case .name(let hostname, _):
|
||||
return "\(hostname):\(port)"
|
||||
case .ipv4(let address):
|
||||
return "\(address):\(port)"
|
||||
case .ipv6(let address):
|
||||
return "[\(address)]:\(port)"
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
public init?(from string: String) {
|
||||
// Separation of host and port is based on 'parse_endpoint' function in
|
||||
// https://git.zx2c4.com/wireguard-tools/tree/src/config.c
|
||||
guard !string.isEmpty else { return nil }
|
||||
let startOfPort: String.Index
|
||||
let hostString: String
|
||||
if string.first! == "[" {
|
||||
// Look for IPv6-style endpoint, like [::1]:80
|
||||
let startOfHost = string.index(after: string.startIndex)
|
||||
guard let endOfHost = string.dropFirst().firstIndex(of: "]") else { return nil }
|
||||
let afterEndOfHost = string.index(after: endOfHost)
|
||||
if afterEndOfHost == string.endIndex { return nil }
|
||||
guard string[afterEndOfHost] == ":" else { return nil }
|
||||
startOfPort = string.index(after: afterEndOfHost)
|
||||
hostString = String(string[startOfHost ..< endOfHost])
|
||||
} else {
|
||||
// Look for an IPv4-style endpoint, like 127.0.0.1:80
|
||||
guard let endOfHost = string.firstIndex(of: ":") else { return nil }
|
||||
startOfPort = string.index(after: endOfHost)
|
||||
hostString = String(string[string.startIndex ..< endOfHost])
|
||||
}
|
||||
guard let endpointPort = NWEndpoint.Port(String(string[startOfPort ..< string.endIndex])) else { return nil }
|
||||
let invalidCharacterIndex = hostString.unicodeScalars.firstIndex { char in
|
||||
return !CharacterSet.urlHostAllowed.contains(char)
|
||||
}
|
||||
guard invalidCharacterIndex == nil else { return nil }
|
||||
host = NWEndpoint.Host(hostString)
|
||||
port = endpointPort
|
||||
}
|
||||
}
|
||||
|
||||
extension Endpoint {
|
||||
public func hasHostAsIPAddress() -> Bool {
|
||||
switch host {
|
||||
case .name:
|
||||
return false
|
||||
case .ipv4:
|
||||
return true
|
||||
case .ipv6:
|
||||
return true
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
public func hostname() -> String? {
|
||||
switch host {
|
||||
case .name(let hostname, _):
|
||||
return hostname
|
||||
case .ipv4:
|
||||
return nil
|
||||
case .ipv6:
|
||||
return nil
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
extension IPv4Address {
|
||||
init?(addrInfo: addrinfo) {
|
||||
guard addrInfo.ai_family == AF_INET else { return nil }
|
||||
|
||||
let addressData = addrInfo.ai_addr.withMemoryRebound(to: sockaddr_in.self, capacity: 1) { ptr -> Data in
|
||||
return Data(bytes: &ptr.pointee.sin_addr, count: MemoryLayout<in_addr>.size)
|
||||
}
|
||||
|
||||
self.init(addressData)
|
||||
}
|
||||
}
|
||||
|
||||
extension IPv6Address {
|
||||
init?(addrInfo: addrinfo) {
|
||||
guard addrInfo.ai_family == AF_INET6 else { return nil }
|
||||
|
||||
let addressData = addrInfo.ai_addr.withMemoryRebound(to: sockaddr_in6.self, capacity: 1) { ptr -> Data in
|
||||
return Data(bytes: &ptr.pointee.sin6_addr, count: MemoryLayout<in6_addr>.size)
|
||||
}
|
||||
|
||||
self.init(addressData)
|
||||
}
|
||||
}
|
||||
115
ios/WireGuardKit/Sources/WireGuardKit/IPAddressRange.swift
Normal file
115
ios/WireGuardKit/Sources/WireGuardKit/IPAddressRange.swift
Normal file
@@ -0,0 +1,115 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
public struct IPAddressRange {
|
||||
public let address: IPAddress
|
||||
public let networkPrefixLength: UInt8
|
||||
|
||||
init(address: IPAddress, networkPrefixLength: UInt8) {
|
||||
self.address = address
|
||||
self.networkPrefixLength = networkPrefixLength
|
||||
}
|
||||
}
|
||||
|
||||
extension IPAddressRange: Equatable {
|
||||
public static func == (lhs: IPAddressRange, rhs: IPAddressRange) -> Bool {
|
||||
return lhs.address.rawValue == rhs.address.rawValue && lhs.networkPrefixLength == rhs.networkPrefixLength
|
||||
}
|
||||
}
|
||||
|
||||
extension IPAddressRange: Hashable {
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(address.rawValue)
|
||||
hasher.combine(networkPrefixLength)
|
||||
}
|
||||
}
|
||||
|
||||
extension IPAddressRange {
|
||||
public var stringRepresentation: String {
|
||||
return "\(address)/\(networkPrefixLength)"
|
||||
}
|
||||
|
||||
public init?(from string: String) {
|
||||
guard let parsed = IPAddressRange.parseAddressString(string) else { return nil }
|
||||
address = parsed.0
|
||||
networkPrefixLength = parsed.1
|
||||
}
|
||||
|
||||
private static func parseAddressString(_ string: String) -> (IPAddress, UInt8)? {
|
||||
let endOfIPAddress = string.lastIndex(of: "/") ?? string.endIndex
|
||||
let addressString = String(string[string.startIndex ..< endOfIPAddress])
|
||||
let address: IPAddress
|
||||
if let addr = IPv4Address(addressString) {
|
||||
address = addr
|
||||
} else if let addr = IPv6Address(addressString) {
|
||||
address = addr
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let maxNetworkPrefixLength: UInt8 = address is IPv4Address ? 32 : 128
|
||||
var networkPrefixLength: UInt8
|
||||
if endOfIPAddress < string.endIndex { // "/" was located
|
||||
let indexOfNetworkPrefixLength = string.index(after: endOfIPAddress)
|
||||
guard indexOfNetworkPrefixLength < string.endIndex else { return nil }
|
||||
let networkPrefixLengthSubstring = string[indexOfNetworkPrefixLength ..< string.endIndex]
|
||||
guard let npl = UInt8(networkPrefixLengthSubstring) else { return nil }
|
||||
networkPrefixLength = min(npl, maxNetworkPrefixLength)
|
||||
} else {
|
||||
networkPrefixLength = maxNetworkPrefixLength
|
||||
}
|
||||
|
||||
return (address, networkPrefixLength)
|
||||
}
|
||||
|
||||
public func subnetMask() -> IPAddress {
|
||||
if address is IPv4Address {
|
||||
let mask = networkPrefixLength > 0 ? ~UInt32(0) << (32 - networkPrefixLength) : UInt32(0)
|
||||
let bytes = Data([
|
||||
UInt8(truncatingIfNeeded: mask >> 24),
|
||||
UInt8(truncatingIfNeeded: mask >> 16),
|
||||
UInt8(truncatingIfNeeded: mask >> 8),
|
||||
UInt8(truncatingIfNeeded: mask >> 0)
|
||||
])
|
||||
return IPv4Address(bytes)!
|
||||
}
|
||||
if address is IPv6Address {
|
||||
var bytes = Data(repeating: 0, count: 16)
|
||||
for i in 0..<Int(networkPrefixLength/8) {
|
||||
bytes[i] = 0xff
|
||||
}
|
||||
let nibble = networkPrefixLength % 32
|
||||
if nibble != 0 {
|
||||
let mask = ~UInt32(0) << (32 - nibble)
|
||||
let i = Int(networkPrefixLength / 32 * 4)
|
||||
bytes[i + 0] = UInt8(truncatingIfNeeded: mask >> 24)
|
||||
bytes[i + 1] = UInt8(truncatingIfNeeded: mask >> 16)
|
||||
bytes[i + 2] = UInt8(truncatingIfNeeded: mask >> 8)
|
||||
bytes[i + 3] = UInt8(truncatingIfNeeded: mask >> 0)
|
||||
}
|
||||
return IPv6Address(bytes)!
|
||||
}
|
||||
fatalError()
|
||||
}
|
||||
|
||||
public func maskedAddress() -> IPAddress {
|
||||
let subnet = subnetMask().rawValue
|
||||
var masked = Data(address.rawValue)
|
||||
if subnet.count != masked.count {
|
||||
fatalError()
|
||||
}
|
||||
for i in 0..<subnet.count {
|
||||
masked[i] &= subnet[i]
|
||||
}
|
||||
if subnet.count == 4 {
|
||||
return IPv4Address(masked)!
|
||||
}
|
||||
if subnet.count == 16 {
|
||||
return IPv6Address(masked)!
|
||||
}
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
public struct InterfaceConfiguration {
|
||||
public var privateKey: PrivateKey
|
||||
public var addresses = [IPAddressRange]()
|
||||
public var listenPort: UInt16?
|
||||
public var mtu: UInt16?
|
||||
public var dns = [DNSServer]()
|
||||
public var dnsSearch = [String]()
|
||||
|
||||
public init(privateKey: PrivateKey) {
|
||||
self.privateKey = privateKey
|
||||
}
|
||||
}
|
||||
|
||||
extension InterfaceConfiguration: Equatable {
|
||||
public static func == (lhs: InterfaceConfiguration, rhs: InterfaceConfiguration) -> Bool {
|
||||
let lhsAddresses = lhs.addresses.filter { $0.address is IPv4Address } + lhs.addresses.filter { $0.address is IPv6Address }
|
||||
let rhsAddresses = rhs.addresses.filter { $0.address is IPv4Address } + rhs.addresses.filter { $0.address is IPv6Address }
|
||||
|
||||
return lhs.privateKey == rhs.privateKey &&
|
||||
lhsAddresses == rhsAddresses &&
|
||||
lhs.listenPort == rhs.listenPort &&
|
||||
lhs.mtu == rhs.mtu &&
|
||||
lhs.dns == rhs.dns &&
|
||||
lhs.dnsSearch == rhs.dnsSearch
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
import NetworkExtension
|
||||
|
||||
#if SWIFT_PACKAGE
|
||||
import WireGuardKitC
|
||||
#endif
|
||||
|
||||
/// A type alias for `Result` type that holds a tuple with source and resolved endpoint.
|
||||
typealias EndpointResolutionResult = Result<(Endpoint, Endpoint), DNSResolutionError>
|
||||
|
||||
class PacketTunnelSettingsGenerator {
|
||||
let tunnelConfiguration: TunnelConfiguration
|
||||
let resolvedEndpoints: [Endpoint?]
|
||||
|
||||
init(tunnelConfiguration: TunnelConfiguration, resolvedEndpoints: [Endpoint?]) {
|
||||
self.tunnelConfiguration = tunnelConfiguration
|
||||
self.resolvedEndpoints = resolvedEndpoints
|
||||
}
|
||||
|
||||
func endpointUapiConfiguration() -> (String, [EndpointResolutionResult?]) {
|
||||
var resolutionResults = [EndpointResolutionResult?]()
|
||||
var wgSettings = ""
|
||||
|
||||
assert(tunnelConfiguration.peers.count == resolvedEndpoints.count)
|
||||
for (peer, resolvedEndpoint) in zip(self.tunnelConfiguration.peers, self.resolvedEndpoints) {
|
||||
wgSettings.append("public_key=\(peer.publicKey.hexKey)\n")
|
||||
|
||||
let result = resolvedEndpoint.map(Self.reresolveEndpoint)
|
||||
if case .success((_, let resolvedEndpoint)) = result {
|
||||
if case .name = resolvedEndpoint.host { assert(false, "Endpoint is not resolved") }
|
||||
wgSettings.append("endpoint=\(resolvedEndpoint.stringRepresentation)\n")
|
||||
}
|
||||
resolutionResults.append(result)
|
||||
}
|
||||
|
||||
return (wgSettings, resolutionResults)
|
||||
}
|
||||
|
||||
func uapiConfiguration() -> (String, [EndpointResolutionResult?]) {
|
||||
var resolutionResults = [EndpointResolutionResult?]()
|
||||
var wgSettings = ""
|
||||
wgSettings.append("private_key=\(tunnelConfiguration.interface.privateKey.hexKey)\n")
|
||||
if let listenPort = tunnelConfiguration.interface.listenPort {
|
||||
wgSettings.append("listen_port=\(listenPort)\n")
|
||||
}
|
||||
if !tunnelConfiguration.peers.isEmpty {
|
||||
wgSettings.append("replace_peers=true\n")
|
||||
}
|
||||
assert(tunnelConfiguration.peers.count == resolvedEndpoints.count)
|
||||
for (peer, resolvedEndpoint) in zip(self.tunnelConfiguration.peers, self.resolvedEndpoints) {
|
||||
wgSettings.append("public_key=\(peer.publicKey.hexKey)\n")
|
||||
if let preSharedKey = peer.preSharedKey?.hexKey {
|
||||
wgSettings.append("preshared_key=\(preSharedKey)\n")
|
||||
}
|
||||
|
||||
let result = resolvedEndpoint.map(Self.reresolveEndpoint)
|
||||
if case .success((_, let resolvedEndpoint)) = result {
|
||||
if case .name = resolvedEndpoint.host { assert(false, "Endpoint is not resolved") }
|
||||
wgSettings.append("endpoint=\(resolvedEndpoint.stringRepresentation)\n")
|
||||
}
|
||||
resolutionResults.append(result)
|
||||
|
||||
let persistentKeepAlive = peer.persistentKeepAlive ?? 0
|
||||
wgSettings.append("persistent_keepalive_interval=\(persistentKeepAlive)\n")
|
||||
if !peer.allowedIPs.isEmpty {
|
||||
wgSettings.append("replace_allowed_ips=true\n")
|
||||
peer.allowedIPs.forEach { wgSettings.append("allowed_ip=\($0.stringRepresentation)\n") }
|
||||
}
|
||||
}
|
||||
return (wgSettings, resolutionResults)
|
||||
}
|
||||
|
||||
func generateNetworkSettings() -> NEPacketTunnelNetworkSettings {
|
||||
/* iOS requires a tunnel endpoint, whereas in WireGuard it's valid for
|
||||
* a tunnel to have no endpoint, or for there to be many endpoints, in
|
||||
* which case, displaying a single one in settings doesn't really
|
||||
* make sense. So, we fill it in with this placeholder, which is not
|
||||
* a valid IP address that will actually route over the Internet.
|
||||
*/
|
||||
let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
|
||||
|
||||
if !tunnelConfiguration.interface.dnsSearch.isEmpty || !tunnelConfiguration.interface.dns.isEmpty {
|
||||
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
|
||||
}
|
||||
networkSettings.dnsSettings = dnsSettings
|
||||
}
|
||||
|
||||
let mtu = tunnelConfiguration.interface.mtu ?? 0
|
||||
|
||||
/* 0 means automatic MTU. In theory, we should just do
|
||||
* `networkSettings.tunnelOverheadBytes = 80` but in
|
||||
* practice there are too many broken networks out there.
|
||||
* Instead set it to 1280. Boohoo. Maybe someday we'll
|
||||
* add a nob, maybe, or iOS will do probing for us.
|
||||
*/
|
||||
if mtu == 0 {
|
||||
#if os(iOS)
|
||||
networkSettings.mtu = NSNumber(value: 1280)
|
||||
#elseif os(macOS)
|
||||
networkSettings.tunnelOverheadBytes = 80
|
||||
#else
|
||||
#error("Unimplemented")
|
||||
#endif
|
||||
} else {
|
||||
networkSettings.mtu = NSNumber(value: mtu)
|
||||
}
|
||||
|
||||
let (ipv4Addresses, ipv6Addresses) = addresses()
|
||||
let (ipv4IncludedRoutes, ipv6IncludedRoutes) = includedRoutes()
|
||||
|
||||
let ipv4Settings = NEIPv4Settings(addresses: ipv4Addresses.map { $0.destinationAddress }, subnetMasks: ipv4Addresses.map { $0.destinationSubnetMask })
|
||||
ipv4Settings.includedRoutes = ipv4IncludedRoutes
|
||||
networkSettings.ipv4Settings = ipv4Settings
|
||||
|
||||
let ipv6Settings = NEIPv6Settings(addresses: ipv6Addresses.map { $0.destinationAddress }, networkPrefixLengths: ipv6Addresses.map { $0.destinationNetworkPrefixLength })
|
||||
ipv6Settings.includedRoutes = ipv6IncludedRoutes
|
||||
networkSettings.ipv6Settings = ipv6Settings
|
||||
|
||||
return networkSettings
|
||||
}
|
||||
|
||||
private func addresses() -> ([NEIPv4Route], [NEIPv6Route]) {
|
||||
var ipv4Routes = [NEIPv4Route]()
|
||||
var ipv6Routes = [NEIPv6Route]()
|
||||
for addressRange in tunnelConfiguration.interface.addresses {
|
||||
if addressRange.address is IPv4Address {
|
||||
ipv4Routes.append(NEIPv4Route(destinationAddress: "\(addressRange.address)", subnetMask: "\(addressRange.subnetMask())"))
|
||||
} else if addressRange.address is IPv6Address {
|
||||
/* Big fat ugly hack for broken iOS networking stack: the smallest prefix that will have
|
||||
* any effect on iOS is a /120, so we clamp everything above to /120. This is potentially
|
||||
* very bad, if various network parameters were actually relying on that subnet being
|
||||
* intentionally small. TODO: talk about this with upstream iOS devs.
|
||||
*/
|
||||
ipv6Routes.append(NEIPv6Route(destinationAddress: "\(addressRange.address)", networkPrefixLength: NSNumber(value: min(120, addressRange.networkPrefixLength))))
|
||||
}
|
||||
}
|
||||
return (ipv4Routes, ipv6Routes)
|
||||
}
|
||||
|
||||
private func includedRoutes() -> ([NEIPv4Route], [NEIPv6Route]) {
|
||||
var ipv4IncludedRoutes = [NEIPv4Route]()
|
||||
var ipv6IncludedRoutes = [NEIPv6Route]()
|
||||
|
||||
for addressRange in tunnelConfiguration.interface.addresses {
|
||||
if addressRange.address is IPv4Address {
|
||||
let route = NEIPv4Route(destinationAddress: "\(addressRange.maskedAddress())", subnetMask: "\(addressRange.subnetMask())")
|
||||
route.gatewayAddress = "\(addressRange.address)"
|
||||
ipv4IncludedRoutes.append(route)
|
||||
} else if addressRange.address is IPv6Address {
|
||||
let route = NEIPv6Route(destinationAddress: "\(addressRange.maskedAddress())", networkPrefixLength: NSNumber(value: addressRange.networkPrefixLength))
|
||||
route.gatewayAddress = "\(addressRange.address)"
|
||||
ipv6IncludedRoutes.append(route)
|
||||
}
|
||||
}
|
||||
|
||||
for peer in tunnelConfiguration.peers {
|
||||
for addressRange in peer.allowedIPs {
|
||||
if addressRange.address is IPv4Address {
|
||||
ipv4IncludedRoutes.append(NEIPv4Route(destinationAddress: "\(addressRange.address)", subnetMask: "\(addressRange.subnetMask())"))
|
||||
} else if addressRange.address is IPv6Address {
|
||||
ipv6IncludedRoutes.append(NEIPv6Route(destinationAddress: "\(addressRange.address)", networkPrefixLength: NSNumber(value: addressRange.networkPrefixLength)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return (ipv4IncludedRoutes, ipv6IncludedRoutes)
|
||||
}
|
||||
|
||||
private class func reresolveEndpoint(endpoint: Endpoint) -> EndpointResolutionResult {
|
||||
return Result { (endpoint, try endpoint.withReresolvedIP()) }
|
||||
.mapError { error -> DNSResolutionError in
|
||||
// swiftlint:disable:next force_cast
|
||||
return error as! DNSResolutionError
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct PeerConfiguration {
|
||||
public var publicKey: PublicKey
|
||||
public var preSharedKey: PreSharedKey?
|
||||
public var allowedIPs = [IPAddressRange]()
|
||||
public var endpoint: Endpoint?
|
||||
public var persistentKeepAlive: UInt16?
|
||||
public var rxBytes: UInt64?
|
||||
public var txBytes: UInt64?
|
||||
public var lastHandshakeTime: Date?
|
||||
|
||||
public init(publicKey: PublicKey) {
|
||||
self.publicKey = publicKey
|
||||
}
|
||||
}
|
||||
|
||||
extension PeerConfiguration: Equatable {
|
||||
public static func == (lhs: PeerConfiguration, rhs: PeerConfiguration) -> Bool {
|
||||
return lhs.publicKey == rhs.publicKey &&
|
||||
lhs.preSharedKey == rhs.preSharedKey &&
|
||||
Set(lhs.allowedIPs) == Set(rhs.allowedIPs) &&
|
||||
lhs.endpoint == rhs.endpoint &&
|
||||
lhs.persistentKeepAlive == rhs.persistentKeepAlive
|
||||
}
|
||||
}
|
||||
|
||||
extension PeerConfiguration: Hashable {
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(publicKey)
|
||||
hasher.combine(preSharedKey)
|
||||
hasher.combine(Set(allowedIPs))
|
||||
hasher.combine(endpoint)
|
||||
hasher.combine(persistentKeepAlive)
|
||||
|
||||
}
|
||||
}
|
||||
114
ios/WireGuardKit/Sources/WireGuardKit/PrivateKey.swift
Normal file
114
ios/WireGuardKit/Sources/WireGuardKit/PrivateKey.swift
Normal file
@@ -0,0 +1,114 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
#if SWIFT_PACKAGE
|
||||
import WireGuardKitC
|
||||
#endif
|
||||
|
||||
/// The class describing a private key used by WireGuard.
|
||||
public class PrivateKey: BaseKey {
|
||||
/// Derived public key
|
||||
public var publicKey: PublicKey {
|
||||
return rawValue.withUnsafeBytes { (privateKeyBufferPointer: UnsafeRawBufferPointer) -> PublicKey in
|
||||
var publicKeyData = Data(repeating: 0, count: Int(WG_KEY_LEN))
|
||||
let privateKeyBytes = privateKeyBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
|
||||
publicKeyData.withUnsafeMutableBytes { (publicKeyBufferPointer: UnsafeMutableRawBufferPointer) in
|
||||
let publicKeyBytes = publicKeyBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
curve25519_derive_public_key(publicKeyBytes, privateKeyBytes)
|
||||
}
|
||||
|
||||
return PublicKey(rawValue: publicKeyData)!
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize new private key
|
||||
convenience public init() {
|
||||
var privateKeyData = Data(repeating: 0, count: Int(WG_KEY_LEN))
|
||||
privateKeyData.withUnsafeMutableBytes { (rawBufferPointer: UnsafeMutableRawBufferPointer) in
|
||||
let privateKeyBytes = rawBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
curve25519_generate_private_key(privateKeyBytes)
|
||||
}
|
||||
self.init(rawValue: privateKeyData)!
|
||||
}
|
||||
}
|
||||
|
||||
/// The class describing a public key used by WireGuard.
|
||||
public class PublicKey: BaseKey {}
|
||||
|
||||
/// The class describing a pre-shared key used by WireGuard.
|
||||
public class PreSharedKey: BaseKey {}
|
||||
|
||||
/// The base key implementation. Should not be used directly.
|
||||
public class BaseKey: RawRepresentable, Equatable, Hashable {
|
||||
/// Raw key representation
|
||||
public let rawValue: Data
|
||||
|
||||
/// Hex encoded representation
|
||||
public var hexKey: String {
|
||||
return rawValue.withUnsafeBytes { (rawBufferPointer: UnsafeRawBufferPointer) -> String in
|
||||
let inBytes = rawBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
var outBytes = [CChar](repeating: 0, count: Int(WG_KEY_LEN_HEX))
|
||||
key_to_hex(&outBytes, inBytes)
|
||||
return String(cString: outBytes, encoding: .ascii)!
|
||||
}
|
||||
}
|
||||
|
||||
/// Base64 encoded representation
|
||||
public var base64Key: String {
|
||||
return rawValue.withUnsafeBytes { (rawBufferPointer: UnsafeRawBufferPointer) -> String in
|
||||
let inBytes = rawBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
var outBytes = [CChar](repeating: 0, count: Int(WG_KEY_LEN_BASE64))
|
||||
key_to_base64(&outBytes, inBytes)
|
||||
return String(cString: outBytes, encoding: .ascii)!
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the key with existing raw representation
|
||||
required public init?(rawValue: Data) {
|
||||
if rawValue.count == WG_KEY_LEN {
|
||||
self.rawValue = rawValue
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the key with hex representation
|
||||
public convenience init?(hexKey: String) {
|
||||
var bytes = Data(repeating: 0, count: Int(WG_KEY_LEN))
|
||||
let success = bytes.withUnsafeMutableBytes { (bufferPointer: UnsafeMutableRawBufferPointer) -> Bool in
|
||||
return key_from_hex(bufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self), hexKey)
|
||||
}
|
||||
if success {
|
||||
self.init(rawValue: bytes)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the key with base64 representation
|
||||
public convenience init?(base64Key: String) {
|
||||
var bytes = Data(repeating: 0, count: Int(WG_KEY_LEN))
|
||||
let success = bytes.withUnsafeMutableBytes { (bufferPointer: UnsafeMutableRawBufferPointer) -> Bool in
|
||||
return key_from_base64(bufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self), base64Key)
|
||||
}
|
||||
if success {
|
||||
self.init(rawValue: bytes)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public static func == (lhs: BaseKey, rhs: BaseKey) -> Bool {
|
||||
return lhs.rawValue.withUnsafeBytes { (lhsBytes: UnsafeRawBufferPointer) -> Bool in
|
||||
return rhs.rawValue.withUnsafeBytes { (rhsBytes: UnsafeRawBufferPointer) -> Bool in
|
||||
return key_eq(
|
||||
lhsBytes.baseAddress!.assumingMemoryBound(to: UInt8.self),
|
||||
rhsBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
public final class TunnelConfiguration {
|
||||
public var name: String?
|
||||
public var interface: InterfaceConfiguration
|
||||
public let peers: [PeerConfiguration]
|
||||
|
||||
public init(name: String?, interface: InterfaceConfiguration, peers: [PeerConfiguration]) {
|
||||
self.interface = interface
|
||||
self.peers = peers
|
||||
self.name = name
|
||||
|
||||
let peerPublicKeysArray = peers.map { $0.publicKey }
|
||||
let peerPublicKeysSet = Set<PublicKey>(peerPublicKeysArray)
|
||||
if peerPublicKeysArray.count != peerPublicKeysSet.count {
|
||||
fatalError("Two or more peers cannot have the same public key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TunnelConfiguration: Equatable {
|
||||
public static func == (lhs: TunnelConfiguration, rhs: TunnelConfiguration) -> Bool {
|
||||
return lhs.name == rhs.name &&
|
||||
lhs.interface == rhs.interface &&
|
||||
Set(lhs.peers) == Set(rhs.peers)
|
||||
}
|
||||
}
|
||||
487
ios/WireGuardKit/Sources/WireGuardKit/WireGuardAdapter.swift
Normal file
487
ios/WireGuardKit/Sources/WireGuardKit/WireGuardAdapter.swift
Normal file
@@ -0,0 +1,487 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
|
||||
#if SWIFT_PACKAGE
|
||||
import WireGuardKitGo
|
||||
import WireGuardKitC
|
||||
#endif
|
||||
|
||||
public enum WireGuardAdapterError: Error {
|
||||
/// Failure to locate tunnel file descriptor.
|
||||
case cannotLocateTunnelFileDescriptor
|
||||
|
||||
/// Failure to perform an operation in such state.
|
||||
case invalidState
|
||||
|
||||
/// Failure to resolve endpoints.
|
||||
case dnsResolution([DNSResolutionError])
|
||||
|
||||
/// Failure to set network settings.
|
||||
case setNetworkSettings(Error)
|
||||
|
||||
/// Failure to start WireGuard backend.
|
||||
case startWireGuardBackend(Int32)
|
||||
}
|
||||
|
||||
/// Enum representing internal state of the `WireGuardAdapter`
|
||||
private enum State {
|
||||
/// The tunnel is stopped
|
||||
case stopped
|
||||
|
||||
/// The tunnel is up and running
|
||||
case started(_ handle: Int32, _ settingsGenerator: PacketTunnelSettingsGenerator)
|
||||
|
||||
/// The tunnel is temporarily shutdown due to device going offline
|
||||
case temporaryShutdown(_ settingsGenerator: PacketTunnelSettingsGenerator)
|
||||
}
|
||||
|
||||
public class WireGuardAdapter {
|
||||
public typealias LogHandler = (WireGuardLogLevel, String) -> Void
|
||||
|
||||
/// Network routes monitor.
|
||||
private var networkMonitor: NWPathMonitor?
|
||||
|
||||
/// Packet tunnel provider.
|
||||
private weak var packetTunnelProvider: NEPacketTunnelProvider?
|
||||
|
||||
/// Log handler closure.
|
||||
private let logHandler: LogHandler
|
||||
|
||||
/// Private queue used to synchronize access to `WireGuardAdapter` members.
|
||||
private let workQueue = DispatchQueue(label: "WireGuardAdapterWorkQueue")
|
||||
|
||||
/// Adapter state.
|
||||
private var state: State = .stopped
|
||||
|
||||
/// Tunnel device file descriptor.
|
||||
private var tunnelFileDescriptor: Int32? {
|
||||
var ctlInfo = ctl_info()
|
||||
withUnsafeMutablePointer(to: &ctlInfo.ctl_name) {
|
||||
$0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: $0.pointee)) {
|
||||
_ = strcpy($0, "com.apple.net.utun_control")
|
||||
}
|
||||
}
|
||||
for fd: Int32 in 0...1024 {
|
||||
var addr = sockaddr_ctl()
|
||||
var ret: Int32 = -1
|
||||
var len = socklen_t(MemoryLayout.size(ofValue: addr))
|
||||
withUnsafeMutablePointer(to: &addr) {
|
||||
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
||||
ret = getpeername(fd, $0, &len)
|
||||
}
|
||||
}
|
||||
if ret != 0 || addr.sc_family != AF_SYSTEM {
|
||||
continue
|
||||
}
|
||||
if ctlInfo.ctl_id == 0 {
|
||||
ret = ioctl(fd, CTLIOCGINFO, &ctlInfo)
|
||||
if ret != 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if addr.sc_id == ctlInfo.ctl_id {
|
||||
return fd
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Returns a WireGuard version.
|
||||
class var backendVersion: String {
|
||||
guard let ver = wgVersion() else { return "unknown" }
|
||||
let str = String(cString: ver)
|
||||
free(UnsafeMutableRawPointer(mutating: ver))
|
||||
return str
|
||||
}
|
||||
|
||||
/// Returns the tunnel device interface name, or nil on error.
|
||||
/// - Returns: String.
|
||||
public var interfaceName: String? {
|
||||
guard let tunnelFileDescriptor = self.tunnelFileDescriptor else { return nil }
|
||||
|
||||
var buffer = [UInt8](repeating: 0, count: Int(IFNAMSIZ))
|
||||
|
||||
return buffer.withUnsafeMutableBufferPointer { mutableBufferPointer in
|
||||
guard let baseAddress = mutableBufferPointer.baseAddress else { return nil }
|
||||
|
||||
var ifnameSize = socklen_t(IFNAMSIZ)
|
||||
let result = getsockopt(
|
||||
tunnelFileDescriptor,
|
||||
2 /* SYSPROTO_CONTROL */,
|
||||
2 /* UTUN_OPT_IFNAME */,
|
||||
baseAddress,
|
||||
&ifnameSize)
|
||||
|
||||
if result == 0 {
|
||||
return String(cString: baseAddress)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/// Designated initializer.
|
||||
/// - Parameter packetTunnelProvider: an instance of `NEPacketTunnelProvider`. Internally stored
|
||||
/// as a weak reference.
|
||||
/// - Parameter logHandler: a log handler closure.
|
||||
public init(with packetTunnelProvider: NEPacketTunnelProvider, logHandler: @escaping LogHandler) {
|
||||
self.packetTunnelProvider = packetTunnelProvider
|
||||
self.logHandler = logHandler
|
||||
|
||||
setupLogHandler()
|
||||
}
|
||||
|
||||
deinit {
|
||||
// Force remove logger to make sure that no further calls to the instance of this class
|
||||
// can happen after deallocation.
|
||||
wgSetLogger(nil, nil)
|
||||
|
||||
// Cancel network monitor
|
||||
networkMonitor?.cancel()
|
||||
|
||||
// Shutdown the tunnel
|
||||
if case .started(let handle, _) = self.state {
|
||||
wgTurnOff(handle)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Public methods
|
||||
|
||||
/// Returns a runtime configuration from WireGuard.
|
||||
/// - Parameter completionHandler: completion handler.
|
||||
public func getRuntimeConfiguration(completionHandler: @escaping (String?) -> Void) {
|
||||
workQueue.async {
|
||||
guard case .started(let handle, _) = self.state else {
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
if let settings = wgGetConfig(handle) {
|
||||
completionHandler(String(cString: settings))
|
||||
free(settings)
|
||||
} else {
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the tunnel tunnel.
|
||||
/// - Parameters:
|
||||
/// - tunnelConfiguration: tunnel configuration.
|
||||
/// - completionHandler: completion handler.
|
||||
public func start(tunnelConfiguration: TunnelConfiguration, completionHandler: @escaping (WireGuardAdapterError?) -> Void) {
|
||||
workQueue.async {
|
||||
guard case .stopped = self.state else {
|
||||
completionHandler(.invalidState)
|
||||
return
|
||||
}
|
||||
|
||||
let networkMonitor = NWPathMonitor()
|
||||
networkMonitor.pathUpdateHandler = { [weak self] path in
|
||||
self?.didReceivePathUpdate(path: path)
|
||||
}
|
||||
networkMonitor.start(queue: self.workQueue)
|
||||
|
||||
do {
|
||||
let settingsGenerator = try self.makeSettingsGenerator(with: tunnelConfiguration)
|
||||
try self.setNetworkSettings(settingsGenerator.generateNetworkSettings())
|
||||
|
||||
let (wgConfig, resolutionResults) = settingsGenerator.uapiConfiguration()
|
||||
self.logEndpointResolutionResults(resolutionResults)
|
||||
|
||||
self.state = .started(
|
||||
try self.startWireGuardBackend(wgConfig: wgConfig),
|
||||
settingsGenerator
|
||||
)
|
||||
self.networkMonitor = networkMonitor
|
||||
completionHandler(nil)
|
||||
} catch let error as WireGuardAdapterError {
|
||||
networkMonitor.cancel()
|
||||
completionHandler(error)
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop the tunnel.
|
||||
/// - Parameter completionHandler: completion handler.
|
||||
public func stop(completionHandler: @escaping (WireGuardAdapterError?) -> Void) {
|
||||
workQueue.async {
|
||||
switch self.state {
|
||||
case .started(let handle, _):
|
||||
wgTurnOff(handle)
|
||||
|
||||
case .temporaryShutdown:
|
||||
break
|
||||
|
||||
case .stopped:
|
||||
completionHandler(.invalidState)
|
||||
return
|
||||
}
|
||||
|
||||
self.networkMonitor?.cancel()
|
||||
self.networkMonitor = nil
|
||||
|
||||
self.state = .stopped
|
||||
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
|
||||
/// Update runtime configuration.
|
||||
/// - Parameters:
|
||||
/// - tunnelConfiguration: tunnel configuration.
|
||||
/// - completionHandler: completion handler.
|
||||
public func update(tunnelConfiguration: TunnelConfiguration, completionHandler: @escaping (WireGuardAdapterError?) -> Void) {
|
||||
workQueue.async {
|
||||
if case .stopped = self.state {
|
||||
completionHandler(.invalidState)
|
||||
return
|
||||
}
|
||||
|
||||
// Tell the system that the tunnel is going to reconnect using new WireGuard
|
||||
// configuration.
|
||||
// This will broadcast the `NEVPNStatusDidChange` notification to the GUI process.
|
||||
self.packetTunnelProvider?.reasserting = true
|
||||
defer {
|
||||
self.packetTunnelProvider?.reasserting = false
|
||||
}
|
||||
|
||||
do {
|
||||
let settingsGenerator = try self.makeSettingsGenerator(with: tunnelConfiguration)
|
||||
try self.setNetworkSettings(settingsGenerator.generateNetworkSettings())
|
||||
|
||||
switch self.state {
|
||||
case .started(let handle, _):
|
||||
let (wgConfig, resolutionResults) = settingsGenerator.uapiConfiguration()
|
||||
self.logEndpointResolutionResults(resolutionResults)
|
||||
|
||||
wgSetConfig(handle, wgConfig)
|
||||
#if os(iOS)
|
||||
wgDisableSomeRoamingForBrokenMobileSemantics(handle)
|
||||
#endif
|
||||
|
||||
self.state = .started(handle, settingsGenerator)
|
||||
|
||||
case .temporaryShutdown:
|
||||
self.state = .temporaryShutdown(settingsGenerator)
|
||||
|
||||
case .stopped:
|
||||
fatalError()
|
||||
}
|
||||
|
||||
completionHandler(nil)
|
||||
} catch let error as WireGuardAdapterError {
|
||||
completionHandler(error)
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private methods
|
||||
|
||||
/// Setup WireGuard log handler.
|
||||
private func setupLogHandler() {
|
||||
let context = Unmanaged.passUnretained(self).toOpaque()
|
||||
wgSetLogger(context) { context, logLevel, message in
|
||||
guard let context = context, let message = message else { return }
|
||||
|
||||
let unretainedSelf = Unmanaged<WireGuardAdapter>.fromOpaque(context)
|
||||
.takeUnretainedValue()
|
||||
|
||||
let swiftString = String(cString: message).trimmingCharacters(in: .newlines)
|
||||
let tunnelLogLevel = WireGuardLogLevel(rawValue: logLevel) ?? .verbose
|
||||
|
||||
unretainedSelf.logHandler(tunnelLogLevel, swiftString)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set network tunnel configuration.
|
||||
/// This method ensures that the call to `setTunnelNetworkSettings` does not time out, as in
|
||||
/// certain scenarios the completion handler given to it may not be invoked by the system.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - networkSettings: an instance of type `NEPacketTunnelNetworkSettings`.
|
||||
/// - Throws: an error of type `WireGuardAdapterError`.
|
||||
/// - Returns: `PacketTunnelSettingsGenerator`.
|
||||
private func setNetworkSettings(_ networkSettings: NEPacketTunnelNetworkSettings) throws {
|
||||
var systemError: Error?
|
||||
let condition = NSCondition()
|
||||
|
||||
// Activate the condition
|
||||
condition.lock()
|
||||
defer { condition.unlock() }
|
||||
|
||||
self.packetTunnelProvider?.setTunnelNetworkSettings(networkSettings) { error in
|
||||
systemError = error
|
||||
condition.signal()
|
||||
}
|
||||
|
||||
// Packet tunnel's `setTunnelNetworkSettings` times out in certain
|
||||
// scenarios & never calls the given callback.
|
||||
let setTunnelNetworkSettingsTimeout: TimeInterval = 5 // seconds
|
||||
|
||||
if condition.wait(until: Date().addingTimeInterval(setTunnelNetworkSettingsTimeout)) {
|
||||
if let systemError = systemError {
|
||||
throw WireGuardAdapterError.setNetworkSettings(systemError)
|
||||
}
|
||||
} else {
|
||||
self.logHandler(.error, "setTunnelNetworkSettings timed out after 5 seconds; proceeding anyway")
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve peers of the given tunnel configuration.
|
||||
/// - Parameter tunnelConfiguration: tunnel configuration.
|
||||
/// - Throws: an error of type `WireGuardAdapterError`.
|
||||
/// - Returns: The list of resolved endpoints.
|
||||
private func resolvePeers(for tunnelConfiguration: TunnelConfiguration) throws -> [Endpoint?] {
|
||||
let endpoints = tunnelConfiguration.peers.map { $0.endpoint }
|
||||
let resolutionResults = DNSResolver.resolveSync(endpoints: endpoints)
|
||||
let resolutionErrors = resolutionResults.compactMap { result -> DNSResolutionError? in
|
||||
if case .failure(let error) = result {
|
||||
return error
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
assert(endpoints.count == resolutionResults.count)
|
||||
guard resolutionErrors.isEmpty else {
|
||||
throw WireGuardAdapterError.dnsResolution(resolutionErrors)
|
||||
}
|
||||
|
||||
let resolvedEndpoints = resolutionResults.map { result -> Endpoint? in
|
||||
// swiftlint:disable:next force_try
|
||||
return try! result?.get()
|
||||
}
|
||||
|
||||
return resolvedEndpoints
|
||||
}
|
||||
|
||||
/// Start WireGuard backend.
|
||||
/// - Parameter wgConfig: WireGuard configuration
|
||||
/// - Throws: an error of type `WireGuardAdapterError`
|
||||
/// - Returns: tunnel handle
|
||||
private func startWireGuardBackend(wgConfig: String) throws -> Int32 {
|
||||
guard let tunnelFileDescriptor = self.tunnelFileDescriptor else {
|
||||
throw WireGuardAdapterError.cannotLocateTunnelFileDescriptor
|
||||
}
|
||||
|
||||
let handle = wgTurnOn(wgConfig, tunnelFileDescriptor)
|
||||
if handle < 0 {
|
||||
throw WireGuardAdapterError.startWireGuardBackend(handle)
|
||||
}
|
||||
#if os(iOS)
|
||||
wgDisableSomeRoamingForBrokenMobileSemantics(handle)
|
||||
#endif
|
||||
return handle
|
||||
}
|
||||
|
||||
/// Resolves the hostnames in the given tunnel configuration and return settings generator.
|
||||
/// - Parameter tunnelConfiguration: an instance of type `TunnelConfiguration`.
|
||||
/// - Throws: an error of type `WireGuardAdapterError`.
|
||||
/// - Returns: an instance of type `PacketTunnelSettingsGenerator`.
|
||||
private func makeSettingsGenerator(with tunnelConfiguration: TunnelConfiguration) throws -> PacketTunnelSettingsGenerator {
|
||||
return PacketTunnelSettingsGenerator(
|
||||
tunnelConfiguration: tunnelConfiguration,
|
||||
resolvedEndpoints: try self.resolvePeers(for: tunnelConfiguration)
|
||||
)
|
||||
}
|
||||
|
||||
/// Log DNS resolution results.
|
||||
/// - Parameter resolutionErrors: an array of type `[DNSResolutionError]`.
|
||||
private func logEndpointResolutionResults(_ resolutionResults: [EndpointResolutionResult?]) {
|
||||
for case .some(let result) in resolutionResults {
|
||||
switch result {
|
||||
case .success((let sourceEndpoint, let resolvedEndpoint)):
|
||||
if sourceEndpoint.host == resolvedEndpoint.host {
|
||||
self.logHandler(.verbose, "DNS64: mapped \(sourceEndpoint.host) to itself.")
|
||||
} else {
|
||||
self.logHandler(.verbose, "DNS64: mapped \(sourceEndpoint.host) to \(resolvedEndpoint.host)")
|
||||
}
|
||||
case .failure(let resolutionError):
|
||||
self.logHandler(.error, "Failed to resolve endpoint \(resolutionError.address): \(resolutionError.errorDescription ?? "(nil)")")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper method used by network path monitor.
|
||||
/// - Parameter path: new network path
|
||||
private func didReceivePathUpdate(path: Network.NWPath) {
|
||||
self.logHandler(.verbose, "Network change detected with \(path.status) route and interface order \(path.availableInterfaces)")
|
||||
|
||||
#if os(macOS)
|
||||
if case .started(let handle, _) = self.state {
|
||||
wgBumpSockets(handle)
|
||||
}
|
||||
#elseif os(iOS)
|
||||
switch self.state {
|
||||
case .started(let handle, let settingsGenerator):
|
||||
if path.status.isSatisfiable {
|
||||
let (wgConfig, resolutionResults) = settingsGenerator.endpointUapiConfiguration()
|
||||
self.logEndpointResolutionResults(resolutionResults)
|
||||
|
||||
wgSetConfig(handle, wgConfig)
|
||||
wgDisableSomeRoamingForBrokenMobileSemantics(handle)
|
||||
wgBumpSockets(handle)
|
||||
} else {
|
||||
self.logHandler(.verbose, "Connectivity offline, pausing backend.")
|
||||
|
||||
self.state = .temporaryShutdown(settingsGenerator)
|
||||
wgTurnOff(handle)
|
||||
}
|
||||
|
||||
case .temporaryShutdown(let settingsGenerator):
|
||||
guard path.status.isSatisfiable else { return }
|
||||
|
||||
self.logHandler(.verbose, "Connectivity online, resuming backend.")
|
||||
|
||||
do {
|
||||
try self.setNetworkSettings(settingsGenerator.generateNetworkSettings())
|
||||
|
||||
let (wgConfig, resolutionResults) = settingsGenerator.uapiConfiguration()
|
||||
self.logEndpointResolutionResults(resolutionResults)
|
||||
|
||||
self.state = .started(
|
||||
try self.startWireGuardBackend(wgConfig: wgConfig),
|
||||
settingsGenerator
|
||||
)
|
||||
} catch {
|
||||
self.logHandler(.error, "Failed to restart backend: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
case .stopped:
|
||||
// no-op
|
||||
break
|
||||
}
|
||||
#else
|
||||
#error("Unsupported")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// A enum describing WireGuard log levels defined in `api-apple.go`.
|
||||
public enum WireGuardLogLevel: Int32 {
|
||||
case verbose = 0
|
||||
case error = 1
|
||||
}
|
||||
|
||||
private extension Network.NWPath.Status {
|
||||
/// Returns `true` if the path is potentially satisfiable.
|
||||
var isSatisfiable: Bool {
|
||||
switch self {
|
||||
case .requiresConnection, .satisfied:
|
||||
return true
|
||||
case .unsatisfied:
|
||||
return false
|
||||
@unknown default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
21
ios/WireGuardKit/Sources/WireGuardKitC/WireGuardKitC.h
Normal file
21
ios/WireGuardKit/Sources/WireGuardKitC/WireGuardKitC.h
Normal file
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
#include <sys/types.h>
|
||||
#include "key.h"
|
||||
#include "x25519.h"
|
||||
|
||||
/* From <sys/kern_control.h> */
|
||||
#define CTLIOCGINFO 0xc0644e03UL
|
||||
struct ctl_info {
|
||||
u_int32_t ctl_id;
|
||||
char ctl_name[96];
|
||||
};
|
||||
struct sockaddr_ctl {
|
||||
u_char sc_len;
|
||||
u_char sc_family;
|
||||
u_int16_t ss_sysaddr;
|
||||
u_int32_t sc_id;
|
||||
u_int32_t sc_unit;
|
||||
u_int32_t sc_reserved[5];
|
||||
};
|
||||
124
ios/WireGuardKit/Sources/WireGuardKitC/key.c
Normal file
124
ios/WireGuardKit/Sources/WireGuardKitC/key.c
Normal file
@@ -0,0 +1,124 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
/*
|
||||
* Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*
|
||||
* This is a specialized constant-time base64/hex implementation that resists side-channel attacks.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "key.h"
|
||||
|
||||
static inline void encode_base64(char dest[static 4], const uint8_t src[static 3])
|
||||
{
|
||||
const uint8_t input[] = { (src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63 };
|
||||
|
||||
for (unsigned int i = 0; i < 4; ++i)
|
||||
dest[i] = input[i] + 'A'
|
||||
+ (((25 - input[i]) >> 8) & 6)
|
||||
- (((51 - input[i]) >> 8) & 75)
|
||||
- (((61 - input[i]) >> 8) & 15)
|
||||
+ (((62 - input[i]) >> 8) & 3);
|
||||
|
||||
}
|
||||
|
||||
void key_to_base64(char base64[static WG_KEY_LEN_BASE64], const uint8_t key[static WG_KEY_LEN])
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < WG_KEY_LEN / 3; ++i)
|
||||
encode_base64(&base64[i * 4], &key[i * 3]);
|
||||
encode_base64(&base64[i * 4], (const uint8_t[]){ key[i * 3 + 0], key[i * 3 + 1], 0 });
|
||||
base64[WG_KEY_LEN_BASE64 - 2] = '=';
|
||||
base64[WG_KEY_LEN_BASE64 - 1] = '\0';
|
||||
}
|
||||
|
||||
static inline int decode_base64(const char src[static 4])
|
||||
{
|
||||
int val = 0;
|
||||
|
||||
for (unsigned int i = 0; i < 4; ++i)
|
||||
val |= (-1
|
||||
+ ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & (src[i] - 64))
|
||||
+ ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & (src[i] - 70))
|
||||
+ ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5))
|
||||
+ ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63)
|
||||
+ ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64)
|
||||
) << (18 - 6 * i);
|
||||
return val;
|
||||
}
|
||||
|
||||
bool key_from_base64(uint8_t key[static WG_KEY_LEN], const char *base64)
|
||||
{
|
||||
unsigned int i;
|
||||
volatile uint8_t ret = 0;
|
||||
int val;
|
||||
|
||||
if (strlen(base64) != WG_KEY_LEN_BASE64 - 1 || base64[WG_KEY_LEN_BASE64 - 2] != '=')
|
||||
return false;
|
||||
|
||||
for (i = 0; i < WG_KEY_LEN / 3; ++i) {
|
||||
val = decode_base64(&base64[i * 4]);
|
||||
ret |= (uint32_t)val >> 31;
|
||||
key[i * 3 + 0] = (val >> 16) & 0xff;
|
||||
key[i * 3 + 1] = (val >> 8) & 0xff;
|
||||
key[i * 3 + 2] = val & 0xff;
|
||||
}
|
||||
val = decode_base64((const char[]){ base64[i * 4 + 0], base64[i * 4 + 1], base64[i * 4 + 2], 'A' });
|
||||
ret |= ((uint32_t)val >> 31) | (val & 0xff);
|
||||
key[i * 3 + 0] = (val >> 16) & 0xff;
|
||||
key[i * 3 + 1] = (val >> 8) & 0xff;
|
||||
|
||||
return 1 & ((ret - 1) >> 8);
|
||||
}
|
||||
|
||||
void key_to_hex(char hex[static WG_KEY_LEN_HEX], const uint8_t key[static WG_KEY_LEN])
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < WG_KEY_LEN; ++i) {
|
||||
hex[i * 2] = 87U + (key[i] >> 4) + ((((key[i] >> 4) - 10U) >> 8) & ~38U);
|
||||
hex[i * 2 + 1] = 87U + (key[i] & 0xf) + ((((key[i] & 0xf) - 10U) >> 8) & ~38U);
|
||||
}
|
||||
hex[i * 2] = '\0';
|
||||
}
|
||||
|
||||
bool key_from_hex(uint8_t key[static WG_KEY_LEN], const char *hex)
|
||||
{
|
||||
uint8_t c, c_acc, c_alpha0, c_alpha, c_num0, c_num, c_val;
|
||||
volatile uint8_t ret = 0;
|
||||
|
||||
if (strlen(hex) != WG_KEY_LEN_HEX - 1)
|
||||
return false;
|
||||
|
||||
for (unsigned int i = 0; i < WG_KEY_LEN_HEX - 1; i += 2) {
|
||||
c = (uint8_t)hex[i];
|
||||
c_num = c ^ 48U;
|
||||
c_num0 = (c_num - 10U) >> 8;
|
||||
c_alpha = (c & ~32U) - 55U;
|
||||
c_alpha0 = ((c_alpha - 10U) ^ (c_alpha - 16U)) >> 8;
|
||||
ret |= ((c_num0 | c_alpha0) - 1) >> 8;
|
||||
c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha);
|
||||
c_acc = c_val * 16U;
|
||||
|
||||
c = (uint8_t)hex[i + 1];
|
||||
c_num = c ^ 48U;
|
||||
c_num0 = (c_num - 10U) >> 8;
|
||||
c_alpha = (c & ~32U) - 55U;
|
||||
c_alpha0 = ((c_alpha - 10U) ^ (c_alpha - 16U)) >> 8;
|
||||
ret |= ((c_num0 | c_alpha0) - 1) >> 8;
|
||||
c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha);
|
||||
key[i / 2] = c_acc | c_val;
|
||||
}
|
||||
|
||||
return 1 & ((ret - 1) >> 8);
|
||||
}
|
||||
|
||||
bool key_eq(const uint8_t key1[static WG_KEY_LEN], const uint8_t key2[static WG_KEY_LEN])
|
||||
{
|
||||
volatile uint8_t acc = 0;
|
||||
for (unsigned int i = 0; i < WG_KEY_LEN; ++i) {
|
||||
acc |= key1[i] ^ key2[i];
|
||||
asm volatile("" : "=r"(acc) : "0"(acc));
|
||||
}
|
||||
return 1 & ((acc - 1) >> 8);
|
||||
}
|
||||
24
ios/WireGuardKit/Sources/WireGuardKitC/key.h
Normal file
24
ios/WireGuardKit/Sources/WireGuardKitC/key.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/*
|
||||
* Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef KEY_H
|
||||
#define KEY_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define WG_KEY_LEN (32)
|
||||
#define WG_KEY_LEN_BASE64 (45)
|
||||
#define WG_KEY_LEN_HEX (65)
|
||||
|
||||
void key_to_base64(char base64[static WG_KEY_LEN_BASE64], const uint8_t key[static WG_KEY_LEN]);
|
||||
bool key_from_base64(uint8_t key[static WG_KEY_LEN], const char *base64);
|
||||
|
||||
void key_to_hex(char hex[static WG_KEY_LEN_HEX], const uint8_t key[static WG_KEY_LEN]);
|
||||
bool key_from_hex(uint8_t key[static WG_KEY_LEN], const char *hex);
|
||||
|
||||
bool key_eq(const uint8_t key1[static WG_KEY_LEN], const uint8_t key2[static WG_KEY_LEN]);
|
||||
|
||||
#endif
|
||||
4
ios/WireGuardKit/Sources/WireGuardKitC/module.modulemap
Normal file
4
ios/WireGuardKit/Sources/WireGuardKitC/module.modulemap
Normal file
@@ -0,0 +1,4 @@
|
||||
module WireGuardKitC {
|
||||
umbrella header "WireGuardKitC.h"
|
||||
export *
|
||||
}
|
||||
178
ios/WireGuardKit/Sources/WireGuardKitC/x25519.c
Normal file
178
ios/WireGuardKit/Sources/WireGuardKitC/x25519.c
Normal file
@@ -0,0 +1,178 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*
|
||||
* Curve25519 ECDH functions, based on TweetNaCl but cleaned up.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <CommonCrypto/CommonRandom.h>
|
||||
|
||||
#include "x25519.h"
|
||||
|
||||
typedef int64_t fe[16];
|
||||
|
||||
static inline void carry(fe o)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; ++i) {
|
||||
o[(i + 1) % 16] += (i == 15 ? 38 : 1) * (o[i] >> 16);
|
||||
o[i] &= 0xffff;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void cswap(fe p, fe q, int b)
|
||||
{
|
||||
int i;
|
||||
int64_t t, c = ~(b - 1);
|
||||
|
||||
for (i = 0; i < 16; ++i) {
|
||||
t = c & (p[i] ^ q[i]);
|
||||
p[i] ^= t;
|
||||
q[i] ^= t;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void pack(uint8_t *o, const fe n)
|
||||
{
|
||||
int i, j, b;
|
||||
fe m, t;
|
||||
|
||||
memcpy(t, n, sizeof(t));
|
||||
carry(t);
|
||||
carry(t);
|
||||
carry(t);
|
||||
for (j = 0; j < 2; ++j) {
|
||||
m[0] = t[0] - 0xffed;
|
||||
for (i = 1; i < 15; ++i) {
|
||||
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
|
||||
m[i - 1] &= 0xffff;
|
||||
}
|
||||
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
|
||||
b = (m[15] >> 16) & 1;
|
||||
m[14] &= 0xffff;
|
||||
cswap(t, m, 1 - b);
|
||||
}
|
||||
for (i = 0; i < 16; ++i) {
|
||||
o[2 * i] = t[i] & 0xff;
|
||||
o[2 * i + 1] = t[i] >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void unpack(fe o, const uint8_t *n)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; ++i)
|
||||
o[i] = n[2 * i] + ((int64_t)n[2 * i + 1] << 8);
|
||||
o[15] &= 0x7fff;
|
||||
}
|
||||
|
||||
static inline void add(fe o, const fe a, const fe b)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; ++i)
|
||||
o[i] = a[i] + b[i];
|
||||
}
|
||||
|
||||
static inline void subtract(fe o, const fe a, const fe b)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; ++i)
|
||||
o[i] = a[i] - b[i];
|
||||
}
|
||||
|
||||
static inline void multmod(fe o, const fe a, const fe b)
|
||||
{
|
||||
int i, j;
|
||||
int64_t t[31] = { 0 };
|
||||
|
||||
for (i = 0; i < 16; ++i) {
|
||||
for (j = 0; j < 16; ++j)
|
||||
t[i + j] += a[i] * b[j];
|
||||
}
|
||||
for (i = 0; i < 15; ++i)
|
||||
t[i] += 38 * t[i + 16];
|
||||
memcpy(o, t, sizeof(fe));
|
||||
carry(o);
|
||||
carry(o);
|
||||
}
|
||||
|
||||
static inline void invert(fe o, const fe i)
|
||||
{
|
||||
fe c;
|
||||
int a;
|
||||
|
||||
memcpy(c, i, sizeof(c));
|
||||
for (a = 253; a >= 0; --a) {
|
||||
multmod(c, c, c);
|
||||
if (a != 2 && a != 4)
|
||||
multmod(c, c, i);
|
||||
}
|
||||
memcpy(o, c, sizeof(fe));
|
||||
}
|
||||
|
||||
static void curve25519_shared_secret(uint8_t shared_secret[32], const uint8_t private_key[32], const uint8_t public_key[32])
|
||||
{
|
||||
static const fe a24 = { 0xdb41, 1 };
|
||||
uint8_t z[32];
|
||||
int64_t r;
|
||||
int i;
|
||||
fe a = { 1 }, b, c = { 0 }, d = { 1 }, e, f, x;
|
||||
|
||||
memcpy(z, private_key, sizeof(z));
|
||||
|
||||
z[31] = (z[31] & 127) | 64;
|
||||
z[0] &= 248;
|
||||
|
||||
unpack(x, public_key);
|
||||
memcpy(b, x, sizeof(b));
|
||||
|
||||
for (i = 254; i >= 0; --i) {
|
||||
r = (z[i >> 3] >> (i & 7)) & 1;
|
||||
cswap(a, b, (int)r);
|
||||
cswap(c, d, (int)r);
|
||||
add(e, a, c);
|
||||
subtract(a, a, c);
|
||||
add(c, b, d);
|
||||
subtract(b, b, d);
|
||||
multmod(d, e, e);
|
||||
multmod(f, a, a);
|
||||
multmod(a, c, a);
|
||||
multmod(c, b, e);
|
||||
add(e, a, c);
|
||||
subtract(a, a, c);
|
||||
multmod(b, a, a);
|
||||
subtract(c, d, f);
|
||||
multmod(a, c, a24);
|
||||
add(a, a, d);
|
||||
multmod(c, c, a);
|
||||
multmod(a, d, f);
|
||||
multmod(d, b, x);
|
||||
multmod(b, e, e);
|
||||
cswap(a, b, (int)r);
|
||||
cswap(c, d, (int)r);
|
||||
}
|
||||
invert(c, c);
|
||||
multmod(a, a, c);
|
||||
pack(shared_secret, a);
|
||||
}
|
||||
|
||||
void curve25519_derive_public_key(uint8_t public_key[32], const uint8_t private_key[32])
|
||||
{
|
||||
static const uint8_t basepoint[32] = { 9 };
|
||||
|
||||
curve25519_shared_secret(public_key, private_key, basepoint);
|
||||
}
|
||||
|
||||
void curve25519_generate_private_key(uint8_t private_key[32])
|
||||
{
|
||||
assert(CCRandomGenerateBytes(private_key, 32) == kCCSuccess);
|
||||
private_key[31] = (private_key[31] & 127) | 64;
|
||||
private_key[0] &= 248;
|
||||
}
|
||||
7
ios/WireGuardKit/Sources/WireGuardKitC/x25519.h
Normal file
7
ios/WireGuardKit/Sources/WireGuardKitC/x25519.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#ifndef X25519_H
|
||||
#define X25519_H
|
||||
|
||||
void curve25519_derive_public_key(unsigned char public_key[32], const unsigned char private_key[32]);
|
||||
void curve25519_generate_private_key(unsigned char private_key[32]);
|
||||
|
||||
#endif
|
||||
1
ios/WireGuardKit/Sources/WireGuardKitGo/dummy.c
Normal file
1
ios/WireGuardKit/Sources/WireGuardKitGo/dummy.c
Normal file
@@ -0,0 +1 @@
|
||||
// Empty
|
||||
5
ios/WireGuardKit/Sources/WireGuardKitGo/module.modulemap
Normal file
5
ios/WireGuardKit/Sources/WireGuardKitGo/module.modulemap
Normal file
@@ -0,0 +1,5 @@
|
||||
module WireGuardKitGo {
|
||||
umbrella header "wireguard.h"
|
||||
link "wg-go"
|
||||
export *
|
||||
}
|
||||
23
ios/WireGuardKit/Sources/WireGuardKitGo/wireguard.h
Normal file
23
ios/WireGuardKit/Sources/WireGuardKitGo/wireguard.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef WIREGUARD_H
|
||||
#define WIREGUARD_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef void(*logger_fn_t)(void *context, int level, const char *msg);
|
||||
extern void wgSetLogger(void *context, logger_fn_t logger_fn);
|
||||
extern int wgTurnOn(const char *settings, int32_t tun_fd);
|
||||
extern void wgTurnOff(int handle);
|
||||
extern int64_t wgSetConfig(int handle, const char *settings);
|
||||
extern char *wgGetConfig(int handle);
|
||||
extern void wgBumpSockets(int handle);
|
||||
extern void wgDisableSomeRoamingForBrokenMobileSemantics(int handle);
|
||||
extern const char *wgVersion();
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user