From 14ce1ff7ae0df1657623235bcaa703b0deb5d6f5 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Wed, 5 Jul 2023 22:06:17 -0400 Subject: [PATCH] getTunnelNetworkSettings in Swift --- .../OutlineTunnelSources/OutlineTunnel.swift | 47 ++++++++++++++++++- .../Sources/OutlineTunnelSources/Subnet.swift | 29 ------------ .../PacketTunnelProvider.m | 35 ++------------ .../OutlineTunnelTest/OutlineTunnelTest.swift | 16 ++++++- 4 files changed, 63 insertions(+), 64 deletions(-) diff --git a/src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnelSources/OutlineTunnel.swift b/src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnelSources/OutlineTunnel.swift index 0640d6ef6f..601e7ffcb2 100644 --- a/src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnelSources/OutlineTunnel.swift +++ b/src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnelSources/OutlineTunnel.swift @@ -13,6 +13,7 @@ // limitations under the License. import Foundation +import NetworkExtension // Serializable class to wrap a tunnel's configuration. // Properties must be kept in sync with ServerConfig in www/types/outlinePlugin.d.ts @@ -64,8 +65,21 @@ public class OutlineTunnel: NSObject, Codable { } // Helper function that we can call from Objective-C. - @objc public static func getAddressForVpn() -> String { - return selectVpnAddress(interfaceAddresses: getNetworkInterfaceAddresses()) + @objc public static func getTunnelNetworkSettings(tunnelRemoteAddress: String) -> NEPacketTunnelNetworkSettings { + // The remote address is not used for routing, but for display in Settings > VPN > Outline. + let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: tunnelRemoteAddress) + + // Configure VPN address and routing. + let vpnAddress = selectVpnAddress(interfaceAddresses: getNetworkInterfaceAddresses()) + let ipv4Settings = NEIPv4Settings(addresses: [vpnAddress], subnetMasks: ["255.255.255.0"]) + ipv4Settings.includedRoutes = [NEIPv4Route.default()] + ipv4Settings.excludedRoutes = getExcludedIpv4Routes() + settings.ipv4Settings = ipv4Settings + + // Configure with Cloudflare, Quad9, and OpenDNS resolver addresses. + settings.dnsSettings = NEDNSSettings(servers: ["1.1.1.1", "9.9.9.9", "208.67.222.222", "208.67.220.220"]) + + return settings } } @@ -123,3 +137,32 @@ func selectVpnAddress(interfaceAddresses: [String]) -> String { // Select a random subnet from the remaining candidates. return candidates.randomElement()?.value ?? "" } + +let kExcludedSubnets = [ + "10.0.0.0/8", + "100.64.0.0/10", + "169.254.0.0/16", + "172.16.0.0/12", + "192.0.0.0/24", + "192.0.2.0/24", + "192.31.196.0/24", + "192.52.193.0/24", + "192.88.99.0/24", + "192.168.0.0/16", + "192.175.48.0/24", + "198.18.0.0/15", + "198.51.100.0/24", + "203.0.113.0/24", + "240.0.0.0/4" +] + +func getExcludedIpv4Routes() -> [NEIPv4Route] { + var excludedIpv4Routes = [NEIPv4Route]() + for cidrSubnet in kExcludedSubnets { + if let subnet = Subnet.parse(cidrSubnet) { + let route = NEIPv4Route(destinationAddress: subnet.address, subnetMask: subnet.mask) + excludedIpv4Routes.append(route) + } + } + return excludedIpv4Routes +} diff --git a/src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnelSources/Subnet.swift b/src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnelSources/Subnet.swift index 2ba3990709..30bbfb8921 100644 --- a/src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnelSources/Subnet.swift +++ b/src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnelSources/Subnet.swift @@ -19,24 +19,6 @@ import Foundation // target of the OutlineAppleLib Swift Package. @objcMembers public class Subnet: NSObject { - public static let kReservedSubnets = [ - "10.0.0.0/8", - "100.64.0.0/10", - "169.254.0.0/16", - "172.16.0.0/12", - "192.0.0.0/24", - "192.0.2.0/24", - "192.31.196.0/24", - "192.52.193.0/24", - "192.88.99.0/24", - "192.168.0.0/16", - "192.175.48.0/24", - "198.18.0.0/15", - "198.51.100.0/24", - "203.0.113.0/24", - "240.0.0.0/4" - ] - // Parses a CIDR subnet into a Subnet object. Returns nil on failure. public static func parse(_ cidrSubnet: String) -> Subnet? { let components = cidrSubnet.components(separatedBy: "/") @@ -51,17 +33,6 @@ public class Subnet: NSObject { return Subnet(address: components[0], prefix: prefix) } - // Returns a list of reserved Subnets. - public static func getReservedSubnets() -> [Subnet] { - var subnets: [Subnet] = [] - for cidrSubnet in kReservedSubnets { - if let subnet = self.parse(cidrSubnet) { - subnets.append(subnet) - } - } - return subnets - } - public var address: String public var prefix: UInt16 public var mask: String diff --git a/src/cordova/apple/OutlineAppleLib/Sources/PacketTunnelProviderSources/PacketTunnelProvider.m b/src/cordova/apple/OutlineAppleLib/Sources/PacketTunnelProviderSources/PacketTunnelProvider.m index 5e5ada5655..00ded12b74 100644 --- a/src/cordova/apple/OutlineAppleLib/Sources/PacketTunnelProviderSources/PacketTunnelProvider.m +++ b/src/cordova/apple/OutlineAppleLib/Sources/PacketTunnelProviderSources/PacketTunnelProvider.m @@ -140,7 +140,7 @@ - (void)startTunnelWithOptions:(NSDictionary *)options userInfo:nil]); } - [self connectTunnel:[self getTunnelNetworkSettings] + [self connectTunnel:[OutlineTunnel getTunnelNetworkSettingsWithTunnelRemoteAddress:self.hostNetworkAddress] completion:^(NSError *_Nullable error) { if (error != nil) { [self execAppCallbackForAction:kActionStart errorCode:vpnPermissionNotGranted]; @@ -197,7 +197,7 @@ - (void)handleAppMessage:(NSData *)messageData completionHandler:(void (^)(NSDat } DDLogInfo(@"Received app message: %@", action); void (^callbackWrapper)(NSNumber *) = ^void(NSNumber *errorCode) { - NSString *tunnelId; + NSString *tunnelId = @""; if (self.tunnelConfig != nil) { tunnelId = self.tunnelConfig.id; } @@ -293,33 +293,6 @@ - (void)connectTunnel:(NEPacketTunnelNetworkSettings *)settings }]; } -- (NEPacketTunnelNetworkSettings *) getTunnelNetworkSettings { - NSString *vpnAddress = [OutlineTunnel getAddressForVpn]; - NEIPv4Settings *ipv4Settings = [[NEIPv4Settings alloc] initWithAddresses:@[ vpnAddress ] - subnetMasks:@[ @"255.255.255.0" ]]; - ipv4Settings.includedRoutes = @[[NEIPv4Route defaultRoute]]; - ipv4Settings.excludedRoutes = [self getExcludedIpv4Routes]; - - // The remote address is not used for routing, but for display in Settings > VPN > Outline. - NEPacketTunnelNetworkSettings *settings = - [[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:self.hostNetworkAddress]; - settings.IPv4Settings = ipv4Settings; - // Configure with Cloudflare, Quad9, and OpenDNS resolver addresses. - settings.DNSSettings = [[NEDNSSettings alloc] - initWithServers:@[ @"1.1.1.1", @"9.9.9.9", @"208.67.222.222", @"208.67.220.220" ]]; - return settings; -} - -- (NSArray *)getExcludedIpv4Routes { - NSMutableArray *excludedIpv4Routes = [[NSMutableArray alloc] init]; - for (Subnet *subnet in [Subnet getReservedSubnets]) { - NEIPv4Route *route = [[NEIPv4Route alloc] initWithDestinationAddress:subnet.address - subnetMask:subnet.mask]; - [excludedIpv4Routes addObject:route]; - } - return excludedIpv4Routes; -} - // Registers KVO for the `defaultPath` property to receive network connectivity changes. - (void)listenForNetworkChanges { [self stopListeningForNetworkChanges]; @@ -446,7 +419,7 @@ - (void)reconnectTunnel:(bool)configChanged { } if (!configChanged && [activeHostNetworkAddress isEqualToString:self.hostNetworkAddress]) { // Nothing changed. Connect the tunnel with the current settings. - [self connectTunnel:[self getTunnelNetworkSettings] + [self connectTunnel:[OutlineTunnel getTunnelNetworkSettingsWithTunnelRemoteAddress:self.hostNetworkAddress] completion:^(NSError *_Nullable error) { if (error != nil) { [self cancelTunnelWithError:error]; @@ -484,7 +457,7 @@ - (void)reconnectTunnel:(bool)configChanged { userInfo:nil]]; return; } - [self connectTunnel:[self getTunnelNetworkSettings] + [self connectTunnel:[OutlineTunnel getTunnelNetworkSettingsWithTunnelRemoteAddress:self.hostNetworkAddress] completion:^(NSError *_Nullable error) { if (error != nil) { [self execAppCallbackForAction:kActionStart errorCode:vpnStartFailure]; diff --git a/src/cordova/apple/OutlineAppleLib/Tests/OutlineTunnelTest/OutlineTunnelTest.swift b/src/cordova/apple/OutlineAppleLib/Tests/OutlineTunnelTest/OutlineTunnelTest.swift index 85c096c0c6..01c6900e34 100644 --- a/src/cordova/apple/OutlineAppleLib/Tests/OutlineTunnelTest/OutlineTunnelTest.swift +++ b/src/cordova/apple/OutlineAppleLib/Tests/OutlineTunnelTest/OutlineTunnelTest.swift @@ -20,9 +20,21 @@ final class OutlineTunnelTest: XCTestCase { XCTAssertEqual("172.16.9.1", selectVpnAddress(interfaceAddresses:["10.111.222.1", "192.168.20.2", "169.254.19.1"])) XCTAssertEqual("192.168.20.1", selectVpnAddress(interfaceAddresses:["10.111.222.1", "172.16.9.2", "169.254.19.1"])) XCTAssertEqual("169.254.19.0", selectVpnAddress(interfaceAddresses:["10.111.222.1", "172.16.9.2", "192.168.20.2"])) + XCTAssertTrue(kVpnSubnetCandidates.values.contains(selectVpnAddress(interfaceAddresses: getNetworkInterfaceAddresses()))) } - func testGetAddressForVpn() { - XCTAssertTrue(kVpnSubnetCandidates.values.contains(OutlineTunnel.getAddressForVpn())) + func testGetTunnelNetworkSettings() { + let settings = OutlineTunnel.getTunnelNetworkSettings(tunnelRemoteAddress: "1.2.3.4") + + XCTAssertEqual("1.2.3.4", settings.tunnelRemoteAddress) + + XCTAssertEqual(1, settings.ipv4Settings?.addresses.count) + XCTAssertTrue(kVpnSubnetCandidates.values.contains(settings.ipv4Settings?.addresses[0] ?? "")) + XCTAssertEqual(["255.255.255.0"], settings.ipv4Settings?.subnetMasks) + + XCTAssertEqual([NEIPv4Route.default()], settings.ipv4Settings?.includedRoutes) + XCTAssertEqual(15, settings.ipv4Settings?.excludedRoutes?.count ?? 0) + + XCTAssertEqual(["1.1.1.1", "9.9.9.9", "208.67.222.222", "208.67.220.220"], settings.dnsSettings?.servers) } }