aboutsummaryrefslogtreecommitdiffstats
path: root/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift
diff options
context:
space:
mode:
authorRoopesh Chander <roop@roopc.net>2018-11-08 15:44:13 +0530
committerRoopesh Chander <roop@roopc.net>2018-11-08 15:44:13 +0530
commitaf58bfcb00e7ebdd0c0f48d2f15df17ab3b2b8d7 (patch)
tree862cf8126080ae93f7bc9884853cd12ea055fa8b /WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift
parentMove logic to extension: Move DNSResolver to extension (diff)
downloadwireguard-apple-af58bfcb00e7ebdd0c0f48d2f15df17ab3b2b8d7.tar.xz
wireguard-apple-af58bfcb00e7ebdd0c0f48d2f15df17ab3b2b8d7.zip
Move logic to extension: Refactor PacketTunnelOptionsGenerator into a PacketTunnelSettingsGenerator
Signed-off-by: Roopesh Chander <roop@roopc.net>
Diffstat (limited to 'WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift')
-rw-r--r--WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift203
1 files changed, 203 insertions, 0 deletions
diff --git a/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift b/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift
new file mode 100644
index 0000000..d26d802
--- /dev/null
+++ b/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018 WireGuard LLC. All Rights Reserved.
+
+import Foundation
+import Network
+import NetworkExtension
+
+class PacketTunnelSettingsGenerator {
+
+ let tunnelConfiguration: TunnelConfiguration
+ let resolvedEndpoints: [Endpoint?]
+
+ init(tunnelConfiguration: TunnelConfiguration, resolvedEndpoints: [Endpoint?]) {
+ self.tunnelConfiguration = tunnelConfiguration
+ self.resolvedEndpoints = resolvedEndpoints
+ }
+
+ func generateWireGuardSettings() -> String {
+ var wgSettings = ""
+ let privateKey = tunnelConfiguration.interface.privateKey.hexEncodedString()
+ wgSettings.append("private_key=\(privateKey)\n")
+ if let listenPort = tunnelConfiguration.interface.listenPort {
+ wgSettings.append("listen_port=\(listenPort)\n")
+ }
+ if (tunnelConfiguration.peers.count > 0) {
+ wgSettings.append("replace_peers=true\n")
+ }
+ assert(tunnelConfiguration.peers.count == resolvedEndpoints.count)
+ for (i, peer) in tunnelConfiguration.peers.enumerated() {
+ wgSettings.append("public_key=\(peer.publicKey.hexEncodedString())\n")
+ if let preSharedKey = peer.preSharedKey {
+ wgSettings.append("preshared_key=\(preSharedKey.hexEncodedString())\n")
+ }
+ if let endpoint = resolvedEndpoints[i] {
+ if case .name(_, _) = endpoint.host { assert(false, "Endpoint is not resolved") }
+ wgSettings.append("endpoint=\(endpoint.stringRepresentation())\n")
+ }
+ let persistentKeepAlive = peer.persistentKeepAlive ?? 0
+ wgSettings.append("persistent_keepalive_interval=\(persistentKeepAlive)\n")
+ if (!peer.allowedIPs.isEmpty) {
+ wgSettings.append("replace_allowed_ips=true\n")
+ for ip in peer.allowedIPs {
+ wgSettings.append("allowed_ip=\(ip.stringRepresentation())\n")
+ }
+ }
+ }
+ return wgSettings
+ }
+
+ func generateNetworkSettings() -> NEPacketTunnelNetworkSettings {
+
+ // Remote address
+
+ /* 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.
+ */
+ var remoteAddress: String = "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 networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: remoteAddress)
+
+ // DNS
+
+ let dnsServerStrings = tunnelConfiguration.interface.dns.map { $0.stringRepresentation() }
+ networkSettings.dnsSettings = NEDNSSettings(servers: dnsServerStrings)
+
+ // MTU
+
+ let mtu = tunnelConfiguration.interface.mtu ?? 0
+ if (mtu == 0) {
+ // 0 imples automatic MTU, where we set overhead as 80 bytes, which is the worst case for WireGuard
+ networkSettings.tunnelOverheadBytes = 80
+ } else {
+ networkSettings.mtu = NSNumber(value: mtu)
+ }
+
+ // Addresses from interface addresses
+
+ var ipv4Addresses: [String] = []
+ var ipv4SubnetMasks: [String] = []
+
+ var ipv6Addresses: [String] = []
+ var ipv6NetworkPrefixLengths: [NSNumber] = []
+
+ for addressRange in tunnelConfiguration.interface.addresses {
+ if (addressRange.address is IPv4Address) {
+ ipv4Addresses.append("\(addressRange.address)")
+ ipv4SubnetMasks.append(PacketTunnelSettingsGenerator.ipv4SubnetMaskString(of: addressRange))
+ } else if (addressRange.address is IPv6Address) {
+ ipv6Addresses.append("\(addressRange.address)")
+ ipv6NetworkPrefixLengths.append(NSNumber(value: addressRange.networkPrefixLength))
+ }
+ }
+
+ // Included routes from AllowedIPs
+
+ var ipv4IncludedRouteAddresses: [String] = []
+ var ipv4IncludedRouteSubnetMasks: [String] = []
+
+ var ipv6IncludedRouteAddresses: [String] = []
+ var ipv6IncludedRouteNetworkPrefixLengths: [NSNumber] = []
+
+ for peer in tunnelConfiguration.peers {
+ for addressRange in peer.allowedIPs {
+ if (addressRange.address is IPv4Address) {
+ ipv4IncludedRouteAddresses.append("\(addressRange.address)")
+ ipv4IncludedRouteSubnetMasks.append(PacketTunnelSettingsGenerator.ipv4SubnetMaskString(of: addressRange))
+ } else if (addressRange.address is IPv6Address) {
+ ipv6IncludedRouteAddresses.append("\(addressRange.address)")
+ ipv6IncludedRouteNetworkPrefixLengths.append(NSNumber(value: addressRange.networkPrefixLength))
+ }
+ }
+ }
+
+ // Excluded routes from endpoints
+
+ var ipv4ExcludedRouteAddresses: [String] = []
+ var ipv4ExcludedRouteSubnetMasks: [String] = []
+
+ var ipv6ExcludedRouteAddresses: [String] = []
+ var ipv6ExcludedRouteNetworkPrefixLengths: [NSNumber] = []
+
+ for endpoint in resolvedEndpoints {
+ guard let endpoint = endpoint else { continue }
+ switch (endpoint.host) {
+ case .ipv4(let address):
+ ipv4ExcludedRouteAddresses.append("\(address)")
+ ipv4ExcludedRouteSubnetMasks.append("255.255.255.255") // A single IPv4 address
+ case .ipv6(let address):
+ ipv6ExcludedRouteAddresses.append("\(address)")
+ ipv6ExcludedRouteNetworkPrefixLengths.append(NSNumber(value: UInt8(128))) // A single IPv6 address
+ default:
+ fatalError()
+ }
+ }
+
+ // Apply IPv4 settings
+
+ let ipv4Settings = NEIPv4Settings(addresses: ipv4Addresses, subnetMasks: ipv4SubnetMasks)
+ assert(ipv4IncludedRouteAddresses.count == ipv4IncludedRouteSubnetMasks.count)
+ ipv4Settings.includedRoutes = zip(ipv4IncludedRouteAddresses, ipv4IncludedRouteSubnetMasks).map {
+ NEIPv4Route(destinationAddress: $0.0, subnetMask: $0.1)
+ }
+ assert(ipv4ExcludedRouteAddresses.count == ipv4ExcludedRouteSubnetMasks.count)
+ ipv4Settings.excludedRoutes = zip(ipv4ExcludedRouteAddresses, ipv4ExcludedRouteSubnetMasks).map {
+ NEIPv4Route(destinationAddress: $0.0, subnetMask: $0.1)
+ }
+ networkSettings.ipv4Settings = ipv4Settings
+
+ // Apply IPv6 settings
+
+ /* 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.
+ */
+ let ipv6Settings = NEIPv6Settings(addresses: ipv6Addresses, networkPrefixLengths: ipv6NetworkPrefixLengths.map { NSNumber(value: min(120, $0.intValue)) })
+ assert(ipv6IncludedRouteAddresses.count == ipv6IncludedRouteNetworkPrefixLengths.count)
+ ipv6Settings.includedRoutes = zip(ipv6IncludedRouteAddresses, ipv6IncludedRouteNetworkPrefixLengths).map {
+ NEIPv6Route(destinationAddress: $0.0, networkPrefixLength: $0.1)
+ }
+ assert(ipv6ExcludedRouteAddresses.count == ipv6ExcludedRouteNetworkPrefixLengths.count)
+ ipv6Settings.excludedRoutes = zip(ipv6ExcludedRouteAddresses, ipv6ExcludedRouteNetworkPrefixLengths).map {
+ NEIPv6Route(destinationAddress: $0.0, networkPrefixLength: $0.1)
+ }
+ networkSettings.ipv6Settings = ipv6Settings
+
+ // Done
+
+ return networkSettings
+ }
+
+ static func ipv4SubnetMaskString(of addressRange: IPAddressRange) -> String {
+ let n: UInt8 = addressRange.networkPrefixLength
+ assert(n <= 32)
+ var octets: [UInt8] = [0, 0, 0, 0]
+ let subnetMask: UInt32 = n > 0 ? ~UInt32(0) << (32 - n) : UInt32(0)
+ octets[0] = UInt8(truncatingIfNeeded: subnetMask >> 24)
+ octets[1] = UInt8(truncatingIfNeeded: subnetMask >> 16)
+ octets[2] = UInt8(truncatingIfNeeded: subnetMask >> 8)
+ octets[3] = UInt8(truncatingIfNeeded: subnetMask)
+ return octets.map { String($0) }.joined(separator: ".")
+ }
+}
+
+private extension Data {
+ func hexEncodedString() -> String {
+ return self.map { String(format: "%02x", $0) }.joined()
+ }
+}