Repository: kean/VPN Branch: main Commit: 9ec64909ea44 Files: 34 Total size: 95.1 KB Directory structure: gitextract_dp8p67_4/ ├── .gitignore ├── README.md ├── vpn-client/ │ ├── AppDelegate.swift │ ├── Assets.xcassets/ │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── icon-vpn.imageset/ │ │ └── Contents.json │ ├── Base.lproj/ │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── Preview Content/ │ │ └── Preview Assets.xcassets/ │ │ └── Contents.json │ ├── SceneDelegate.swift │ ├── Screens/ │ │ ├── PrimaryButton.swift │ │ ├── PrimaryButtonView.swift │ │ ├── RouterView.swift │ │ ├── Spinner.swift │ │ ├── SplashView.swift │ │ ├── TunnelDetails/ │ │ │ ├── TunnelDetailsView.swift │ │ │ └── TunnelDetailsViewModel.swift │ │ └── WelcomeView.swift │ ├── Services/ │ │ ├── Keychain.swift │ │ └── VPNConfigurationService.swift │ └── vpn-client.entitlements ├── vpn-protocol/ │ ├── Cipher.swift │ ├── Packet.swift │ └── Session.swift ├── vpn-server/ │ └── main.swift ├── vpn-tunnel/ │ ├── Info.plist │ ├── PacketTunnelProvider.swift │ └── vpn_tunnel.entitlements └── vpn.xcodeproj/ ├── project.pbxproj ├── project.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ ├── IDEWorkspaceChecks.plist │ └── swiftpm/ │ └── Package.resolved └── xcshareddata/ └── xcschemes/ ├── vpn-client.xcscheme └── vpn-tunnel.xcscheme ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ ## System .DS_Store ## Build generated build/ DerivedData Nuke.xcodeproj/xcshareddata/xcbaselines/ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata ## Other *.xccheckout *.moved-aside *.xcuserstate *.xcscmblueprint ## Obj-C/Swift specific *.hmap *.ipa ## Playgrounds timeline.xctimeline playground.xcworkspace ## Swift Package Manager # # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ .build/ ## CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ ## Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. Carthage ================================================ FILE: README.md ================================================ # VPN (WIP) A sample VPN client/server written in Swift. # License VPN is available under the MIT license. See the LICENSE file for more info. ================================================ FILE: vpn-client/AppDelegate.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } // MARK: UISceneSession Lifecycle func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } } ================================================ FILE: vpn-client/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: vpn-client/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: vpn-client/Assets.xcassets/icon-vpn.imageset/Contents.json ================================================ { "images" : [ { "filename" : "icons8-vpn-status-bar-icon-50.png", "idiom" : "universal", "scale" : "1x" }, { "filename" : "icons8-vpn-status-bar-icon-100.png", "idiom" : "universal", "scale" : "2x" }, { "filename" : "icons8-vpn-status-bar-icon-101.png", "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: vpn-client/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: vpn-client/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 LSRequiresIPhoneOS UIApplicationSceneManifest UIApplicationSupportsMultipleScenes UISceneConfigurations UIWindowSceneSessionRoleApplication UISceneConfigurationName Default Configuration UISceneDelegateClassName $(PRODUCT_MODULE_NAME).SceneDelegate UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: vpn-client/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: vpn-client/SceneDelegate.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import UIKit import SwiftUI class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). // Create the SwiftUI view that provides the window contents. let contentView = RouterView() // Use a UIHostingController as window root view controller. if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.rootViewController = UIHostingController(rootView: contentView) self.window = window window.makeKeyAndVisible() } } func sceneDidDisconnect(_ scene: UIScene) { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. // Release any resources associated with this scene that can be re-created the next time the scene connects. // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). } func sceneDidBecomeActive(_ scene: UIScene) { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. } func sceneWillResignActive(_ scene: UIScene) { // Called when the scene will move from an active state to an inactive state. // This may occur due to temporary interruptions (ex. an incoming phone call). } func sceneWillEnterForeground(_ scene: UIScene) { // Called as the scene transitions from the background to the foreground. // Use this method to undo the changes made on entering the background. } func sceneDidEnterBackground(_ scene: UIScene) { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. } } ================================================ FILE: vpn-client/Screens/PrimaryButton.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import SwiftUI struct PrimaryButton: View { let title: String let action: () -> Void @Binding var isLoading: Bool var body: some View { Button(action: self.action) { ZStack { Spinner(isAnimating: $isLoading, color: .white, style: .medium) Text(title) .opacity(isLoading ? 0 : 1) } } .disabled(isLoading) .padding() .frame(maxWidth: .infinity) .foregroundColor(Color.white) .background(Color.blue) .cornerRadius(8) } } struct PrimaryButtonView_Previews: PreviewProvider { static var previews: some View { Group { PrimaryButton(title: "Action", action: {}, isLoading: .constant(false)) .previewLayout(.fixed(width: 300, height: 80)) PrimaryButton(title: "Action", action: {}, isLoading: .constant(false)) .previewLayout(.fixed(width: 300, height: 80)) .environment(\.colorScheme, .dark) PrimaryButton(title: "Action", action: {}, isLoading: .constant(true)) .previewLayout(.fixed(width: 300, height: 80)) } } } ================================================ FILE: vpn-client/Screens/PrimaryButtonView.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import SwiftUI struct PrimaryButton: View { let title: String let action: () -> Void @Binding var isLoading: Bool var body: some View { Button(action: self.action) { ZStack { Spinner(isAnimating: .constant(true), style: .medium) Text(title) } } .padding() .foregroundColor(Color.white) .background(Color.blue) .cornerRadius(8) } } struct PrimaryButtonView_Previews: PreviewProvider { static var previews: some View { Group { PrimaryButton(title: "Action", action: {}, isLoading: .constant(false)) .previewLayout(.fixed(width: 300, height: 80)) PrimaryButton(title: "Action", action: {}, isLoading: .constant(false)) .previewLayout(.fixed(width: 300, height: 80)) .environment(\.colorScheme, .dark) PrimaryButton(title: "Action", action: {}, isLoading: .constant(true)) .previewLayout(.fixed(width: 300, height: 80)) } } } ================================================ FILE: vpn-client/Screens/RouterView.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import SwiftUI struct RouterView: View { @ObservedObject var service: VPNConfigurationService = .shared var body: some View { if !service.isStarted { return AnyView(SplashView()) } else { if let tunnel = service.tunnel { return AnyView(TunnelDetailsView(model: TunnelViewModel(tunnel: tunnel))) } else { return AnyView(WelcomeView()) } } } } struct RouterView_Previews: PreviewProvider { static var previews: some View { RouterView() } } ================================================ FILE: vpn-client/Screens/Spinner.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import SwiftUI import UIKit struct Spinner: UIViewRepresentable { @Binding var isAnimating: Bool let color: UIColor let style: UIActivityIndicatorView.Style func makeUIView(context: Context) -> UIActivityIndicatorView { let indicator = UIActivityIndicatorView(style: style) indicator.hidesWhenStopped = true indicator.color = color return indicator } func updateUIView(_ uiView: UIActivityIndicatorView, context: Context) { isAnimating ? uiView.startAnimating() : uiView.stopAnimating() } } ================================================ FILE: vpn-client/Screens/SplashView.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import SwiftUI import NetworkExtension struct SplashView: View { @ObservedObject var service: VPNConfigurationService = .shared @State private var isLoading = false @State private var isShowingError = false @State private var errorMessage = "" var body: some View { VStack { Text("BestVPN") .font(.largeTitle) .fontWeight(.heavy) Spacer().frame(height: 32) HStack { Text("Loading profiles…") Spinner(isAnimating: $isLoading, color: .label, style: .medium) } } .onAppear(perform: refresh) .alert(isPresented: $isShowingError) { Alert( title: Text("Failed to load profiles"), message: Text(errorMessage), primaryButton: .default(Text("Retry"), action: refresh), secondaryButton: .cancel() ) } } private func refresh() { isLoading = true service.refresh { self.isLoading = false switch $0 { case .success: break // Handled by RouterView case let .failure(error): self.errorMessage = error.localizedDescription self.isShowingError = true } } } } struct SplashView_Previews: PreviewProvider { static var previews: some View { SplashView() } } ================================================ FILE: vpn-client/Screens/TunnelDetails/TunnelDetailsView.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import SwiftUI struct TunnelDetailsView: View { @ObservedObject var model: TunnelViewModel var body: some View { NavigationView { Form { Section(header: Text("Settings")) { TextInputView(title: "Username", text: $model.username) TextInputView(title: "Password", text: $model.password) TextInputView(title: "Server", text: $model.server) Button(action: model.buttonSaveTapped) { Text("Save") } .foregroundColor(Color.blue) } Section(header: Text("Status")) { Toggle(isOn: $model.isEnabled, label: { Text("Enabled") }) if model.isEnabled { Text("Status: ") + Text(model.status).bold() if model.isStarted { Button(action: model.buttonStopTapped) { Text("Stop") } .foregroundColor(Color.orange) } else { Button(action: model.buttonStartTapped) { Text("Start") } .foregroundColor(Color.blue) } } } Section { ButtonRemoveProfile(model: model) } } .disabled(model.isLoading) .alert(isPresented: $model.isShowingError) { Alert( title: Text(self.model.errorTitle), message: Text(self.model.errorMessage), dismissButton: .cancel() ) } .navigationBarItems(trailing: Spinner(isAnimating: $model.isLoading, color: .label, style: .medium) ) .navigationBarTitle("VPN Status") } } } private struct TextInputView: View { let title: String let text: Binding var body: some View { HStack(alignment: .center) { Text(title) .font(.callout) TextField(title, text: text) .multilineTextAlignment(.trailing) .foregroundColor(Color.gray) } } } private struct ButtonRemoveProfile: View { let model: TunnelViewModel @State private var isConfirmationPresented = false var body: some View { Button(action: { self.isConfirmationPresented = true }) { Text("Remove Profile") } .foregroundColor(.red) .alert(isPresented: $isConfirmationPresented) { Alert( title: Text("Are you sure you want to remove the profile?"), primaryButton: .destructive(Text("Remove profile"), action: { self.isConfirmationPresented = false self.model.buttonRemoveProfileTapped() }), secondaryButton: .cancel() ) } } } struct TunnelView_Previews: PreviewProvider { static var previews: some View { TunnelDetailsView(model: .init(tunnel: .init())) } } ================================================ FILE: vpn-client/Screens/TunnelDetails/TunnelDetailsViewModel.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import SwiftUI import Combine import NetworkExtension final class TunnelViewModel: ObservableObject { @Published var username = "" @Published var password = "" @Published var server = "" @Published var isEnabled = false @Published var isStarted = false @Published private(set) var status: String = "Unknown" @Published var isLoading = false @Published var isShowingError = false @Published private(set) var errorTitle = "" @Published private(set) var errorMessage = "" private let service: VPNConfigurationService private let tunnel: NETunnelProviderManager private var observers = [AnyObject]() private var bag = [AnyCancellable]() init(service: VPNConfigurationService = .shared, tunnel: NETunnelProviderManager) { self.service = service self.tunnel = tunnel self.refresh() observers.append(NotificationCenter.default .addObserver(forName: .NEVPNStatusDidChange, object: tunnel.connection, queue: .main) { [weak self] _ in self?.refresh() }) observers.append(NotificationCenter.default .addObserver(forName: .NEVPNConfigurationChange, object: tunnel, queue: .main) { [weak self] _ in self?.refresh() }) $isEnabled.sink { [weak self] in self?.setEnabled($0) }.store(in: &bag) } private func refresh() { self.status = tunnel.connection.status.description let username = tunnel.protocolConfiguration?.username ?? "" self.username = username self.password = tunnel.protocolConfiguration?.passwordReference.flatMap { Keychain.password(for: username, reference: $0) } ?? "" self.server = tunnel.protocolConfiguration?.serverAddress ?? "" self.isEnabled = tunnel.isEnabled self.isStarted = tunnel.connection.status != .disconnected && tunnel.connection.status != .invalid } private func setEnabled(_ isEnabled: Bool) { guard isEnabled != tunnel.isEnabled else { return } tunnel.isEnabled = isEnabled saveToPreferences() } func buttonStartTapped() { do { try tunnel.connection.startVPNTunnel(options: [:] as [String : NSObject]) } catch { self.showError(title: "Failed to start VPN tunnel", message: error.localizedDescription) } } func buttonStopTapped() { tunnel.connection.stopVPNTunnel() } func buttonRemoveProfileTapped() { isLoading = true service.removeProfile { [weak self] in guard let self = self else { return } self.isLoading = false switch $0 { case .success: break // Do nothing, router will show what's next case let .failure(error): self.showError(title: "Failed to install a profile", message: error.localizedDescription) } } } private func saveToPreferences() { isLoading = true tunnel.saveToPreferences { [weak self] error in guard let self = self else { return } self.isLoading = false if let error = error { self.showError(title: "Failed to update VPN configuration", message: error.localizedDescription) self.errorMessage = error.localizedDescription return } } } private func showError(title: String, message: String) { self.errorTitle = title self.errorMessage = message self.isShowingError = true } func buttonSaveTapped() { let proto = tunnel.protocolConfiguration as! NETunnelProviderProtocol proto.username = self.username proto.passwordReference = { let keychain = Keychain(group: "group.com.github.kean.vpn-client") keychain.set(password: self.password, for: username) return keychain.passwordReference(for: username) }() proto.serverAddress = server tunnel.protocolConfiguration = proto saveToPreferences() } private func sayHelloToTunnel() { // Send a simple IPC message to the provider, handle the response. guard let session = tunnel.connection as? NETunnelProviderSession, let message = "Hello Provider".data(using: String.Encoding.utf8), tunnel.connection.status != .invalid else { return } do { try session.sendProviderMessage(message) { response in if response != nil { let responseString = NSString(data: response!, encoding: String.Encoding.utf8.rawValue) NSLog("Received response from the provider: \(String(describing: responseString))") } else { NSLog("Got a nil response from the provider") } } } catch { NSLog("Failed to send a message to the provider") } } } ================================================ FILE: vpn-client/Screens/WelcomeView.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import SwiftUI struct WelcomeView: View { let service: VPNConfigurationService = .shared @State private var isLoading = false @State private var isShowingError = false @State private var errorMessage = "" var body: some View { NavigationView { VStack { header Spacer(minLength: 0) buttonInstall Spacer().frame(height: 16) }.padding() } } private var header: some View { VStack { Text("BestVPN") .font(.largeTitle) .fontWeight(.heavy) HStack(spacing: 20) { Image("icon-vpn") .resizable() .frame(width: 50, height: 50) VStack(alignment: .leading) { Text("Custom VPN solution") .font(.headline) Text("Powered by Network Extension and SwiftNIO") } } } } private var buttonInstall: some View { PrimaryButton( title: "Install VPN Profile", action: self.installProfile, isLoading: $isLoading ).alert(isPresented: $isShowingError) { Alert( title: Text("Failed to install a profile"), message: Text(errorMessage), dismissButton: .cancel() ) } } private func installProfile() { isLoading = true service.installProfile { result in self.isLoading = false switch result { case .success: break // Do nothing, router will show what's next case let .failure(error): self.errorMessage = error.localizedDescription self.isShowingError = true } } } } struct WelcomeView_Previews: PreviewProvider { static var previews: some View { WelcomeView() } } ================================================ FILE: vpn-client/Services/Keychain.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import Foundation /// Wrapper for easy keychain access and modification. public final class Keychain { private let accessGroup: String public init(group: String) { self.accessGroup = group } @discardableResult public func set(password: String, for username: String, label: String? = nil) -> OSStatus { removePassword(for: username) var query = [String: Any]() query[kSecAttrAccessGroup as String] = accessGroup query[kSecClass as String] = kSecClassGenericPassword if let label = label { query[kSecAttrLabel as String] = label } query[kSecAttrAccount as String] = username query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock query[kSecValueData as String] = password.data(using: .utf8) return SecItemAdd(query as CFDictionary, nil) } @discardableResult public func removePassword(for username: String) -> Bool { var query = [String: Any]() query[kSecAttrAccessGroup as String] = accessGroup query[kSecClass as String] = kSecClassGenericPassword query[kSecAttrAccount as String] = username let status = SecItemDelete(query as CFDictionary) return status == errSecSuccess } public func password(for username: String) throws -> String? { var query = [String: Any]() query[kSecAttrAccessGroup as String] = accessGroup query[kSecClass as String] = kSecClassGenericPassword query[kSecAttrAccount as String] = username query[kSecMatchLimit as String] = kSecMatchLimitOne query[kSecReturnData as String] = true var result: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &result) guard status == errSecSuccess, let data = result as? Data else { return nil } return String(data: data, encoding: .utf8) } public func passwordReference(for username: String) -> Data? { var query = [String: Any]() query[kSecAttrAccessGroup as String] = accessGroup query[kSecClass as String] = kSecClassGenericPassword query[kSecAttrAccount as String] = username query[kSecMatchLimit as String] = kSecMatchLimitOne query[kSecReturnPersistentRef as String] = true var result: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &result) guard status == errSecSuccess, let data = result as? Data else { return nil } return data } public static func password(for username: String, reference: Data) -> String? { var query = [String: Any]() query[kSecClass as String] = kSecClassGenericPassword query[kSecAttrAccount as String] = username query[kSecMatchItemList as String] = [reference] query[kSecReturnData as String] = true var result: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &result) guard status == errSecSuccess, let data = result as? Data else { return nil } return String(data: data, encoding: .utf8) } } ================================================ FILE: vpn-client/Services/VPNConfigurationService.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import Foundation import Combine import NetworkExtension import UIKit final class VPNConfigurationService: ObservableObject { @Published private(set) var isStarted = false /// If not nil, the tunnel is displayed. @Published private(set) var tunnel: NETunnelProviderManager? static let shared = VPNConfigurationService() private var observer: AnyObject? private init() { observer = NotificationCenter.default.addObserver( forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [weak self] _ in self?.refresh() } } private func refresh() { refresh { _ in } } func refresh(_ completion: @escaping (Result) -> Void) { // Read all of the VPN configurations created by the app that have // previously been saved to the Network Extension preferences. NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in guard let self = self else { return } // There is only one VPN configuration the app provides self.tunnel = managers?.first if let error = error { completion(.failure(error)) } else { self.isStarted = true completion(.success(())) } } } func installProfile(_ completion: @escaping (Result) -> Void) { let tunnel = makeManager() tunnel.saveToPreferences { [weak self] error in if let error = error { return completion(.failure(error)) } // See https://forums.developer.apple.com/thread/25928 tunnel.loadFromPreferences { [weak self] error in self?.tunnel = tunnel completion(.success(())) } } } private func makeManager() -> NETunnelProviderManager { let manager = NETunnelProviderManager() manager.localizedDescription = "BestVPN" let proto = NETunnelProviderProtocol() // WARNING: This must match the bundle identifier of the app extension // containing packet tunnel provider. proto.providerBundleIdentifier = "com.github.kean.vpn-client.vpn-tunnel" // WARNING: This must send the actual VPN server address, for the demo // purposes, I'm passing the address of the server in my local network. // The address is going to be different in your network. proto.serverAddress = "192.168.0.13:9999" proto.username = "kean" proto.passwordReference = { let keychain = Keychain(group: "group.com.github.kean.vpn-client") keychain.set(password: "123456", for: "kean") return keychain.passwordReference(for: "kean") }() manager.protocolConfiguration = proto // Uncomment this to configure on-demand rules to make sure the tunnel // starts automatically when needed. // let onDemandRule = NEOnDemandRuleConnect() // onDemandRule.interfaceTypeMatch = .any // manager.isOnDemandEnabled = true // manager.onDemandRules = [onDemandRule] // Enable the manager bu default. manager.isEnabled = true return manager } private func statusUpdated() { } func removeProfile(_ completion: @escaping (Result) -> Void) { assert(tunnel != nil, "Tunnel is missing") tunnel?.removeFromPreferences { error in if let error = error { return completion(.failure(error)) } self.tunnel = nil completion(.success(())) } } } // MARK: - Extensions /// Make NEVPNStatus convertible to a string extension NEVPNStatus: CustomStringConvertible { public var description: String { switch self { case .disconnected: return "Disconnected" case .invalid: return "Invalid" case .connected: return "Connected" case .connecting: return "Connecting" case .disconnecting: return "Disconnecting" case .reasserting: return "Reconnecting" @unknown default: return "Unknown" } } } ================================================ FILE: vpn-client/vpn-client.entitlements ================================================ com.apple.developer.networking.networkextension packet-tunnel-provider com.apple.security.application-groups group.com.github.kean.vpn-client ================================================ FILE: vpn-protocol/Cipher.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import Foundation import CryptoKit // WARNING: This is a simplified example, don't use it as a reference. public enum Cipher { /// WARNING: This is simplified example, don't do this in production! /// /// A "pre-shared" symmetric key. public static let key = SymmetricKey(data: Data(base64Encoded: "KGiEbfJODclOCzUVfWXBO7Y/ohnEVxf7+RnwaAA1/78=")!) // WARNING: This is a simplified example, don't use it as a reference. public static func encrypt(_ data: Data, key: SymmetricKey) throws -> Data { try ChaChaPoly.seal(data, using: key).combined } // WARNING: This is a simplified example, don't use it as a reference. public static func decrypt(_ data: Data, key: SymmetricKey) throws -> Data { try ChaChaPoly.open(ChaChaPoly.SealedBox(combined: data), using: key) } } public extension SymmetricKey { /// A Data instance created safely from the contiguous bytes without making any copies. var dataRepresentation: Data { return self.withUnsafeBytes { bytes in let cfdata = CFDataCreateWithBytesNoCopy(nil, bytes.baseAddress?.assumingMemoryBound(to: UInt8.self), bytes.count, kCFAllocatorNull) return ((cfdata as NSData?) as Data?) ?? Data() } } } ================================================ FILE: vpn-protocol/Packet.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import Foundation import CryptoKit public enum PacketCode: UInt8 { /// A control packet containing client authentication request (JSON). case clientAuthRequest = 0x01 /// A control packet containing server authentication response (JSON). case serverAuthResponse = 0x02 /// A data packet containing encrypted IP packets (raw bytes). case data = 0x03 /// Initilizes the code code with the given UDP packet contents. public init(datagram: Data) throws { guard datagram.count > 0 else { throw PacketParsingError.notEnoughData } guard let code = PacketCode(rawValue: datagram[0]) else { throw PacketParsingError.invalidPacketCode } self = code } } public enum PacketParsingError: Error { case notEnoughData case invalidPacketCode } public struct Header { public let code: PacketCode public static let length = 1 public init(code: PacketCode) { self.code = code } } public enum Body { public struct ClientAuthRequest: Codable { public let login: String public let password: String public init(login: String, password: String) { self.login = login self.password = password } } public struct ServerAuthResponse: Codable { public let isOK: Bool public init(isOK: Bool) { self.isOK = isOK } } public typealias Data = Foundation.Data } // MARK: - Encoder public enum MessageEncoder { public static func encode(header: Header, body: Body, key: SymmetricKey) throws -> Data { try encode(header: header, body: JSONEncoder().encode(body), key: key) } public static func encode(header: Header, body: Data, key: SymmetricKey) throws -> Data { var data = Data() // Header data.append(header.code.rawValue) // Body let body = try Cipher.encrypt(body, key: key) data.append(body) return data } } public enum MessageDecoder { /// Decrypts and decodes the body of the given datagram. public static func decode(_ type: T.Type, datagram: Data, key: SymmetricKey) throws -> T { let data = try Cipher.decrypt(datagram[Header.length...], key: key) return try JSONDecoder().decode(type, from: data) } } ================================================ FILE: vpn-protocol/Session.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import Foundation import CryptoKit // WARNING: This is a simplified example, don't use it as a reference. public final class Cipher { private let key: SymmetricKey public init(key: SymmetricKey) { self.key = key } // WARNING: This is a simplified example, don't use it as a reference. public func encrypt(_ data: Data) throws -> Data { try ChaChaPoly.seal(data, using: key).combined } // WARNING: This is a simplified example, don't use it as a reference. public func decrypt(_ data: Data) throws -> Data { try ChaChaPoly.open(ChaChaPoly.SealedBox(combined: data), using: key) } } ================================================ FILE: vpn-server/main.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import Foundation import NIO import BestVPN private final class VPNDatagramHandler: ChannelInboundHandler { public typealias InboundIn = AddressedEnvelope public typealias OutboundOut = AddressedEnvelope public func channelRead(context: ChannelHandlerContext, data: NIOAny) { let inputEnvelope = unwrapInboundIn(data) let inputData = Data(inputEnvelope.data.readableBytesView) // TODO: this probably isn't optimal do { let code = try PacketCode(datagram: inputData) switch code { case .clientAuthRequest: let outputData = try MessageEncoder.encode( header: Header(code: .serverAuthResponse), body: Body.ServerAuthResponse(isOK: true), key: Cipher.key ) var buffer = context.channel.allocator.buffer(capacity: outputData.count) buffer.writeBytes(outputData) let outputEnvelope = AddressedEnvelope(remoteAddress: inputEnvelope.remoteAddress, data: buffer) context.write(wrapOutboundOut(outputEnvelope), promise: nil) case .data: // TODO: Inject packets into a virtual interface break default: break } } catch { // TODO: Handle errors } } public func channelReadComplete(context: ChannelHandlerContext) { // As we are not really interested getting notified on success or failure we just pass nil as promise to // reduce allocations. context.flush() } public func errorCaught(context: ChannelHandlerContext, error: Error) { print("error: ", error) // As we are not really interested getting notified on success or failure we just pass nil as promise to // reduce allocations. context.close(promise: nil) } } // We don't need more than one thread, as we're creating only one datagram channel. let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) var bootstrap = DatagramBootstrap(group: group) // Specify backlog and enable SO_REUSEADDR .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) // Set the handlers that are applied to the bound channel .channelInitializer { channel in // Ensure we don't read faster than we can write by adding the BackPressureHandler into the pipeline. channel.pipeline.addHandler(VPNDatagramHandler()) } defer { try! group.syncShutdownGracefully() } var arguments = CommandLine.arguments.dropFirst(0) // just to get an ArraySlice from [String] if arguments.dropFirst().first == .some("--enable-gathering-reads") { bootstrap = bootstrap.channelOption(ChannelOptions.datagramVectorReadMessageCount, value: 30) bootstrap = bootstrap.channelOption(ChannelOptions.recvAllocator, value: FixedSizeRecvByteBufferAllocator(capacity: 30 * 2048)) arguments = arguments.dropFirst() } let arg1 = arguments.dropFirst().first let arg2 = arguments.dropFirst().dropFirst().first #warning("TEMP:") //let defaultHost = "::1" let defaultHost = "192.168.0.13" let defaultPort = 9999 enum BindTo { case ip(host: String, port: Int) case unixDomainSocket(path: String) } let bindTarget: BindTo switch (arg1, arg1.flatMap(Int.init), arg2.flatMap(Int.init)) { case (.some(let h), _ , .some(let p)): /* we got two arguments, let's interpret that as host and port */ bindTarget = .ip(host: h, port: p) case (.some(let portString), .none, _): /* couldn't parse as number, expecting unix domain socket path */ bindTarget = .unixDomainSocket(path: portString) case (_, .some(let p), _): /* only one argument --> port */ bindTarget = .ip(host: defaultHost, port: p) default: bindTarget = .ip(host: defaultHost, port: defaultPort) } let channel = try { () -> Channel in switch bindTarget { case .ip(let host, let port): return try bootstrap.bind(host: host, port: port).wait() case .unixDomainSocket(let path): return try bootstrap.bind(unixDomainSocketPath: path).wait() } }() print("Server started and listening on \(channel.localAddress!)") // This will never unblock as we don't close the channel try channel.closeFuture.wait() print("Server closed") ================================================ FILE: vpn-tunnel/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName vpn-tunnel CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 NSExtension NSExtensionPointIdentifier com.apple.networkextension.packet-tunnel NSExtensionPrincipalClass $(PRODUCT_MODULE_NAME).PacketTunnelProvider ================================================ FILE: vpn-tunnel/PacketTunnelProvider.swift ================================================ // The MIT License (MIT) // // Copyright (c) 2020 Alexander Grebenyuk (github.com/kean). import NetworkExtension import BestVPN import CryptoKit import os.log class PacketTunnelProvider: NEPacketTunnelProvider { private var configuration: Configuration! private var udpSession: NWUDPSession! private var key: SymmetricKey! private var observer: AnyObject? #warning("TODO: do we need this queue?") private let queue = DispatchQueue(label: "com.github.packet-tunnel-provider") private let log = OSLog(subsystem: "vpn-tunnel-ptp", category: "default") private var pendingCompletion: ((Error?) -> Void)? private weak var timeoutTimer: Timer? override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { os_log(.default, log: log, "Starting tunnel, options: %{private}@", "\(String(describing: options))") do { guard let proto = protocolConfiguration as? NETunnelProviderProtocol else { throw NEVPNError(.configurationInvalid) } self.configuration = try Configuration(proto: proto) } catch { os_log(.error, log: log, "Failed to read the configuration", error.localizedDescription) completionHandler(error) } os_log(.default, log: log, "Read configuration %{private}@", "\(String(describing: configuration))") // "Get" a pre-shared symmetric key. // // WARNING: Don't do this in production! This code uses the same key // every time for simplicity. In practice, you are going to want to // either pre-share it properly, or implement proper TLS handshake. self.key = Cipher.key // Remember the completion handler so that we could call it when the // the connection with the server is established. // // When the Packet Tunnel Provider executes the completionHandler block // with a nil error parameter, it signals to the system that it is ready // to begin handling network data. Therefore, the Packet Tunnel Provider // should call `setTunnelNetworkSettings(_:completionHandler:)` and wait // for it to complete before executing the completionHandler block. // // The domain and code of the NSError object passed to the completionHandler // block are defined by the Packet Tunnel Provider (`NEVPNError`). self.pendingCompletion = completionHandler self.startTunnel() } private func startTunnel() { self.startUDPSession() self.timeoutTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: false) { [weak self] _ in guard let self = self else { return } self.pendingCompletion?(NEVPNError(.connectionFailed)) self.pendingCompletion = nil } } private func startUDPSession() { os_log(.default, log: log, "Starting UDP session, hostname: %{public}@, port: %{public}@", configuration.hostname, configuration.port) let endpoint = NWHostEndpoint(hostname: configuration.hostname, port: configuration.port) self.udpSession = createUDPSession(to: endpoint, from: nil) self.observer = udpSession.observe(\.state, options: [.new]) { [weak self] session, _ in guard let self = self else { return } os_log(.default, log: self.log, "Session did update state: %{public}@", session.state.description) self.queue.async { self.udpSession(session, didUpdateState: session.state) } } } private func udpSession(_ session: NWUDPSession, didUpdateState state: NWUDPSessionState) { switch state { case .ready: guard pendingCompletion != nil else { return } session.setReadHandler({ [weak self] datagrams, error in guard let self = self else { return } self.queue.async { self.didReceiveDatagrams(datagrams: datagrams ?? [], error: error) } }, maxDatagrams: Int.max) do { try self.authenticate(username: configuration.username, password: configuration.password) } catch { // TODO: handle errors os_log(.default, log: self.log, "Did fail to authenticate: %{public}@", "\(error)") } case .failed: guard pendingCompletion != nil else { return } pendingCompletion?(NEVPNError(.connectionFailed)) pendingCompletion = nil default: break } } private func didReceiveDatagrams(datagrams: [Data], error: Error?) { for datagram in datagrams { do { try self.didReceiveDatagram(datagram: datagram) } catch { // TODO: handle error os_log(.default, log: self.log, "UDP session read handler error: %{public}@", "\(error)") } } if let error = error { // TODO: handle error os_log(.default, log: self.log, "UDP session read handler error: %{public}@", "\(error)") } } private func didReceiveDatagram(datagram: Data) throws { let code = try PacketCode(datagram: datagram) os_log(.default, log: self.log, "Did receive datagram with code: %{public}@", "\(code)") switch code { case .serverAuthResponse: let response = try MessageDecoder.decode(Body.ServerAuthResponse.self, datagram: datagram, key: key) os_log(.default, log: self.log, "Did receive auth response: %{private}@", "\(response)") if response.isOK { // TODO: In reality, you would pass a resolved IP address, in our // case we already provide an IP address in the configurtaion self.didSetupTunnel(address: configuration.hostname) } else { // TODO: Handle error } self.timeoutTimer?.invalidate() case .data: let data = try MessageDecoder.decode(Data.self, datagram: datagram, key: key) let proto = protocolNumber(for: data) self.packetFlow.writePackets([data], withProtocols: [proto]) default: break } } private func didSetupTunnel(address: String) { os_log(.default, log: self.log, "Did setup tunnel with address: %{public}@", "\(address)") let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: address) // TODO: Configure DNS/split-tunnel/etc settings if needed setTunnelNetworkSettings(settings) { error in os_log(.default, log: self.log, "Did setup tunnel settings: %{public}@, error: %{public}@", "\(settings)", "\(String(describing: error))") self.pendingCompletion?(error) self.pendingCompletion = nil self.didStartTunnel() } } private func authenticate(username: String, password: String) throws { let datagram = try MessageEncoder.encode( header: Header(code: .clientAuthRequest), body: Body.ClientAuthRequest(login: username, password: password), key: key ) udpSession.writeDatagram(datagram) { error in if let error = error { // TODO: Handle errors os_log(.default, log: self.log, "Failed to write auth request datagram, error: %{public}@", "\(error)") } } } private func didStartTunnel() { readPackets() } private func readPackets() { packetFlow.readPacketObjects { packets in do { let datagrams = try packets.map { try MessageEncoder.encode( header: Header(code: .data), body: $0.data, key: key ) } self.session.writeMultipleDatagrams(datagrams) { error in // TODO: Handle errors } } catch { // TODO: Handle errors } self.readPackets() } } override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { // Add code here to start the process of stopping the tunnel. completionHandler() } override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { // Add code here to handle the message. if let handler = completionHandler { handler(messageData) } } override func sleep(completionHandler: @escaping () -> Void) { // Add code here to get ready to sleep. completionHandler() } override func wake() { // Add code here to wake up. } } private struct Configuration { let username: String let password: String let hostname: String let port: String init(proto: NETunnelProviderProtocol) throws { guard let fullServerAddress = proto.serverAddress else { throw NEVPNError(.configurationInvalid) } let serverAddressParts = fullServerAddress.split(separator: ":") guard serverAddressParts.count == 2 else { throw NEVPNError(.configurationInvalid) } self.hostname = String(serverAddressParts[0]) self.port = String(serverAddressParts[1]) guard let username = proto.username else { throw NEVPNError(.configurationInvalid) } self.username = username guard let password = proto.passwordReference.flatMap({ Keychain.password(for: username, reference: $0) }) else { throw NEVPNError(.configurationInvalid) } self.password = password } } extension NWUDPSessionState: CustomStringConvertible { public var description: String { switch self { case .cancelled: return ".cancelled" case .failed: return ".failed" case .invalid: return ".invalid" case .preparing: return ".preparing" case .ready: return ".ready" case .waiting: return ".waiting" @unknown default: return "unknown" } } } private func protocolNumber(for packet: Data) -> NSNumber { guard !packet.isEmpty else { return AF_INET as NSNumber } // 'packet' contains the decrypted incoming IP packet data // The first 4 bits identify the IP version let ipVersion = (packet[0] & 0xf0) >> 4 return (ipVersion == 6) ? AF_INET6 as NSNumber : AF_INET as NSNumber } ================================================ FILE: vpn-tunnel/vpn_tunnel.entitlements ================================================ com.apple.developer.networking.networkextension packet-tunnel-provider com.apple.security.application-groups group.com.github.kean.vpn-client ================================================ FILE: vpn.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 52; objects = { /* Begin PBXBuildFile section */ 0C189C482479D399000EE6B3 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C189C432479D280000EE6B3 /* main.swift */; }; 0C230CDA24884C35005FEEBD /* libBestVPN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0CD38AF92486C12E00DB2626 /* libBestVPN.a */; }; 0C34DF93248D50E500FBBD11 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C34DF92248D50E500FBBD11 /* Cipher.swift */; }; 0C3C1B5F247039EB00571084 /* VPNConfigurationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3C1B5E247039EB00571084 /* VPNConfigurationService.swift */; }; 0C3C1B6124703B5E00571084 /* TunnelDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3C1B6024703B5E00571084 /* TunnelDetailsView.swift */; }; 0C3C1B6524703D8600571084 /* PrimaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3C1B6424703D8600571084 /* PrimaryButton.swift */; }; 0C3C1B692470404A00571084 /* Spinner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3C1B682470404A00571084 /* Spinner.swift */; }; 0C405BED2466FBFE00EB0786 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C405BEC2466FBFE00EB0786 /* AppDelegate.swift */; }; 0C405BEF2466FBFE00EB0786 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C405BEE2466FBFE00EB0786 /* SceneDelegate.swift */; }; 0C405BF12466FBFE00EB0786 /* RouterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C405BF02466FBFE00EB0786 /* RouterView.swift */; }; 0C405BF32466FC0000EB0786 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0C405BF22466FC0000EB0786 /* Assets.xcassets */; }; 0C405BF62466FC0000EB0786 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0C405BF52466FC0000EB0786 /* Preview Assets.xcassets */; }; 0C405BF92466FC0000EB0786 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0C405BF72466FC0000EB0786 /* LaunchScreen.storyboard */; }; 0C571CBA247050E5006B2931 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0C571CB9247050E5006B2931 /* NetworkExtension.framework */; }; 0C571CBC24705809006B2931 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C571CBB24705809006B2931 /* SplashView.swift */; }; 0C571CC42470B4D9006B2931 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0C571CB9247050E5006B2931 /* NetworkExtension.framework */; }; 0C5F7022248DA579000297AB /* libBestVPN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0CD38AF92486C12E00DB2626 /* libBestVPN.a */; }; 0CA5FEBD2491B4B400C71D25 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA5FEBC2491B4B400C71D25 /* Keychain.swift */; }; 0CA5FEBE2491B4B400C71D25 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA5FEBC2491B4B400C71D25 /* Keychain.swift */; }; 0CA5FEC12491B72E00C71D25 /* TunnelDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA5FEC02491B72E00C71D25 /* TunnelDetailsViewModel.swift */; }; 0CAE93DD246F4927007B2E95 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CAE93DC246F4927007B2E95 /* PacketTunnelProvider.swift */; }; 0CAE93E2246F4927007B2E95 /* vpn-tunnel.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0CAE93DA246F4927007B2E95 /* vpn-tunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 0CAE940724702557007B2E95 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CAE940624702557007B2E95 /* WelcomeView.swift */; }; 0CD38AFC2486C12E00DB2626 /* Packet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD38AFB2486C12E00DB2626 /* Packet.swift */; }; 0CD38B06248735A700DB2626 /* NIO in Frameworks */ = {isa = PBXBuildFile; productRef = 0CD38B05248735A700DB2626 /* NIO */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 0C230CDB24884C35005FEEBD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0C405BE12466FBFE00EB0786 /* Project object */; proxyType = 1; remoteGlobalIDString = 0CD38AF82486C12E00DB2626; remoteInfo = "vpn-protocol"; }; 0C5F7023248DA579000297AB /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0C405BE12466FBFE00EB0786 /* Project object */; proxyType = 1; remoteGlobalIDString = 0CD38AF82486C12E00DB2626; remoteInfo = "vpn-protocol"; }; 0CAE93E0246F4927007B2E95 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0C405BE12466FBFE00EB0786 /* Project object */; proxyType = 1; remoteGlobalIDString = 0CAE93D9246F4927007B2E95; remoteInfo = "vpn-tunnel"; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 0C189C3F2479D280000EE6B3 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; 0CAE93D5246F4906007B2E95 /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( 0CAE93E2246F4927007B2E95 /* vpn-tunnel.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; }; 0CD38AF72486C12E00DB2626 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = "include/$(PRODUCT_NAME)"; dstSubfolderSpec = 16; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0C189C412479D280000EE6B3 /* vpn-server */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "vpn-server"; sourceTree = BUILT_PRODUCTS_DIR; }; 0C189C432479D280000EE6B3 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = main.swift; path = "vpn-server/main.swift"; sourceTree = SOURCE_ROOT; }; 0C34DF92248D50E500FBBD11 /* Cipher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; 0C3C1B5D24702E1C00571084 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 0C3C1B5E247039EB00571084 /* VPNConfigurationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNConfigurationService.swift; sourceTree = ""; }; 0C3C1B6024703B5E00571084 /* TunnelDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailsView.swift; sourceTree = ""; }; 0C3C1B6424703D8600571084 /* PrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryButton.swift; sourceTree = ""; }; 0C3C1B682470404A00571084 /* Spinner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Spinner.swift; sourceTree = ""; }; 0C405BE92466FBFE00EB0786 /* vpn-client.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "vpn-client.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 0C405BEC2466FBFE00EB0786 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 0C405BEE2466FBFE00EB0786 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 0C405BF02466FBFE00EB0786 /* RouterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouterView.swift; sourceTree = ""; }; 0C405BF22466FC0000EB0786 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0C405BF52466FC0000EB0786 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 0C405BF82466FC0000EB0786 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 0C405BFA2466FC0000EB0786 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0C571CB7247050E5006B2931 /* vpn-client.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "vpn-client.entitlements"; sourceTree = ""; }; 0C571CB9247050E5006B2931 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; 0C571CBB24705809006B2931 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = ""; }; 0C571CC22470B343006B2931 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; }; 0CA5FEBC2491B4B400C71D25 /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; 0CA5FEC02491B72E00C71D25 /* TunnelDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailsViewModel.swift; sourceTree = ""; }; 0CAE93DA246F4927007B2E95 /* vpn-tunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "vpn-tunnel.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 0CAE93DC246F4927007B2E95 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = ""; }; 0CAE93DE246F4927007B2E95 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0CAE93DF246F4927007B2E95 /* vpn_tunnel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = vpn_tunnel.entitlements; sourceTree = ""; }; 0CAE940624702557007B2E95 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; 0CD38AF92486C12E00DB2626 /* libBestVPN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBestVPN.a; sourceTree = BUILT_PRODUCTS_DIR; }; 0CD38AFB2486C12E00DB2626 /* Packet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Packet.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 0C189C3E2479D280000EE6B3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 0C5F7022248DA579000297AB /* libBestVPN.a in Frameworks */, 0CD38B06248735A700DB2626 /* NIO in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 0C405BE62466FBFE00EB0786 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 0C571CBA247050E5006B2931 /* NetworkExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 0CAE93D7246F4927007B2E95 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 0C230CDA24884C35005FEEBD /* libBestVPN.a in Frameworks */, 0C571CC42470B4D9006B2931 /* NetworkExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 0CD38AF62486C12E00DB2626 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0C189C422479D280000EE6B3 /* vpn-server */ = { isa = PBXGroup; children = ( 0C189C432479D280000EE6B3 /* main.swift */, ); path = "vpn-server"; sourceTree = ""; }; 0C3C1B662470403600571084 /* Screens */ = { isa = PBXGroup; children = ( 0CA5FEBF2491B71F00C71D25 /* TunnelDetails */, 0CAE940624702557007B2E95 /* WelcomeView.swift */, 0C405BF02466FBFE00EB0786 /* RouterView.swift */, 0C3C1B6424703D8600571084 /* PrimaryButton.swift */, 0C3C1B682470404A00571084 /* Spinner.swift */, 0C571CBB24705809006B2931 /* SplashView.swift */, ); path = Screens; sourceTree = ""; }; 0C3C1B672470403C00571084 /* Services */ = { isa = PBXGroup; children = ( 0C3C1B5E247039EB00571084 /* VPNConfigurationService.swift */, 0CA5FEBC2491B4B400C71D25 /* Keychain.swift */, ); path = Services; sourceTree = ""; }; 0C405BE02466FBFE00EB0786 = { isa = PBXGroup; children = ( 0C3C1B5D24702E1C00571084 /* README.md */, 0C405BEB2466FBFE00EB0786 /* vpn-client */, 0CAE93DB246F4927007B2E95 /* vpn-tunnel */, 0C189C422479D280000EE6B3 /* vpn-server */, 0CD38AFA2486C12E00DB2626 /* vpn-protocol */, 0C405BEA2466FBFE00EB0786 /* Products */, 0C571CB8247050E5006B2931 /* Frameworks */, ); sourceTree = ""; }; 0C405BEA2466FBFE00EB0786 /* Products */ = { isa = PBXGroup; children = ( 0C405BE92466FBFE00EB0786 /* vpn-client.app */, 0CAE93DA246F4927007B2E95 /* vpn-tunnel.appex */, 0C189C412479D280000EE6B3 /* vpn-server */, 0CD38AF92486C12E00DB2626 /* libBestVPN.a */, ); name = Products; sourceTree = ""; }; 0C405BEB2466FBFE00EB0786 /* vpn-client */ = { isa = PBXGroup; children = ( 0C571CB7247050E5006B2931 /* vpn-client.entitlements */, 0C405BEC2466FBFE00EB0786 /* AppDelegate.swift */, 0C405BEE2466FBFE00EB0786 /* SceneDelegate.swift */, 0C3C1B672470403C00571084 /* Services */, 0C3C1B662470403600571084 /* Screens */, 0C405BF22466FC0000EB0786 /* Assets.xcassets */, 0C405BF72466FC0000EB0786 /* LaunchScreen.storyboard */, 0C405BFA2466FC0000EB0786 /* Info.plist */, 0C405BF42466FC0000EB0786 /* Preview Content */, ); path = "vpn-client"; sourceTree = ""; }; 0C405BF42466FC0000EB0786 /* Preview Content */ = { isa = PBXGroup; children = ( 0C405BF52466FC0000EB0786 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; 0C571CB8247050E5006B2931 /* Frameworks */ = { isa = PBXGroup; children = ( 0C571CC22470B343006B2931 /* libresolv.tbd */, 0C571CB9247050E5006B2931 /* NetworkExtension.framework */, ); name = Frameworks; sourceTree = ""; }; 0CA5FEBF2491B71F00C71D25 /* TunnelDetails */ = { isa = PBXGroup; children = ( 0C3C1B6024703B5E00571084 /* TunnelDetailsView.swift */, 0CA5FEC02491B72E00C71D25 /* TunnelDetailsViewModel.swift */, ); path = TunnelDetails; sourceTree = ""; }; 0CAE93DB246F4927007B2E95 /* vpn-tunnel */ = { isa = PBXGroup; children = ( 0CAE93DC246F4927007B2E95 /* PacketTunnelProvider.swift */, 0CAE93DE246F4927007B2E95 /* Info.plist */, 0CAE93DF246F4927007B2E95 /* vpn_tunnel.entitlements */, ); path = "vpn-tunnel"; sourceTree = ""; }; 0CD38AFA2486C12E00DB2626 /* vpn-protocol */ = { isa = PBXGroup; children = ( 0CD38AFB2486C12E00DB2626 /* Packet.swift */, 0C34DF92248D50E500FBBD11 /* Cipher.swift */, ); path = "vpn-protocol"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 0C189C402479D280000EE6B3 /* vpn-server */ = { isa = PBXNativeTarget; buildConfigurationList = 0C189C452479D280000EE6B3 /* Build configuration list for PBXNativeTarget "vpn-server" */; buildPhases = ( 0C189C3D2479D280000EE6B3 /* Sources */, 0C189C3E2479D280000EE6B3 /* Frameworks */, 0C189C3F2479D280000EE6B3 /* CopyFiles */, ); buildRules = ( ); dependencies = ( 0C5F7024248DA579000297AB /* PBXTargetDependency */, ); name = "vpn-server"; packageProductDependencies = ( 0CD38B05248735A700DB2626 /* NIO */, ); productName = "vpn-server"; productReference = 0C189C412479D280000EE6B3 /* vpn-server */; productType = "com.apple.product-type.tool"; }; 0C405BE82466FBFE00EB0786 /* vpn-client */ = { isa = PBXNativeTarget; buildConfigurationList = 0C405BFD2466FC0100EB0786 /* Build configuration list for PBXNativeTarget "vpn-client" */; buildPhases = ( 0C405BE52466FBFE00EB0786 /* Sources */, 0C405BE62466FBFE00EB0786 /* Frameworks */, 0C405BE72466FBFE00EB0786 /* Resources */, 0CAE93D5246F4906007B2E95 /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( 0CAE93E1246F4927007B2E95 /* PBXTargetDependency */, ); name = "vpn-client"; productName = VPNClient; productReference = 0C405BE92466FBFE00EB0786 /* vpn-client.app */; productType = "com.apple.product-type.application"; }; 0CAE93D9246F4927007B2E95 /* vpn-tunnel */ = { isa = PBXNativeTarget; buildConfigurationList = 0CAE93E3246F4927007B2E95 /* Build configuration list for PBXNativeTarget "vpn-tunnel" */; buildPhases = ( 0CAE93D6246F4927007B2E95 /* Sources */, 0CAE93D7246F4927007B2E95 /* Frameworks */, 0CAE93D8246F4927007B2E95 /* Resources */, ); buildRules = ( ); dependencies = ( 0C230CDC24884C35005FEEBD /* PBXTargetDependency */, ); name = "vpn-tunnel"; productName = "vpn-tunnel"; productReference = 0CAE93DA246F4927007B2E95 /* vpn-tunnel.appex */; productType = "com.apple.product-type.app-extension"; }; 0CD38AF82486C12E00DB2626 /* vpn-protocol */ = { isa = PBXNativeTarget; buildConfigurationList = 0CD38AFD2486C12E00DB2626 /* Build configuration list for PBXNativeTarget "vpn-protocol" */; buildPhases = ( 0CD38AF52486C12E00DB2626 /* Sources */, 0CD38AF62486C12E00DB2626 /* Frameworks */, 0CD38AF72486C12E00DB2626 /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = "vpn-protocol"; productName = "vpn-protocol"; productReference = 0CD38AF92486C12E00DB2626 /* libBestVPN.a */; productType = "com.apple.product-type.library.static"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 0C405BE12466FBFE00EB0786 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1150; LastUpgradeCheck = 1140; ORGANIZATIONNAME = kean; TargetAttributes = { 0C189C402479D280000EE6B3 = { CreatedOnToolsVersion = 11.5; }; 0C405BE82466FBFE00EB0786 = { CreatedOnToolsVersion = 11.4.1; }; 0CAE93D9246F4927007B2E95 = { CreatedOnToolsVersion = 11.4.1; }; 0CD38AF82486C12E00DB2626 = { CreatedOnToolsVersion = 11.5; }; }; }; buildConfigurationList = 0C405BE42466FBFE00EB0786 /* Build configuration list for PBXProject "vpn" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 0C405BE02466FBFE00EB0786; packageReferences = ( 0CD38B04248735A700DB2626 /* XCRemoteSwiftPackageReference "swift-nio" */, ); productRefGroup = 0C405BEA2466FBFE00EB0786 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 0C405BE82466FBFE00EB0786 /* vpn-client */, 0CAE93D9246F4927007B2E95 /* vpn-tunnel */, 0C189C402479D280000EE6B3 /* vpn-server */, 0CD38AF82486C12E00DB2626 /* vpn-protocol */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 0C405BE72466FBFE00EB0786 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 0C405BF92466FC0000EB0786 /* LaunchScreen.storyboard in Resources */, 0C405BF62466FC0000EB0786 /* Preview Assets.xcassets in Resources */, 0C405BF32466FC0000EB0786 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 0CAE93D8246F4927007B2E95 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 0C189C3D2479D280000EE6B3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 0C189C482479D399000EE6B3 /* main.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 0C405BE52466FBFE00EB0786 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 0C405BED2466FBFE00EB0786 /* AppDelegate.swift in Sources */, 0C405BEF2466FBFE00EB0786 /* SceneDelegate.swift in Sources */, 0C3C1B6124703B5E00571084 /* TunnelDetailsView.swift in Sources */, 0CA5FEC12491B72E00C71D25 /* TunnelDetailsViewModel.swift in Sources */, 0C3C1B5F247039EB00571084 /* VPNConfigurationService.swift in Sources */, 0C405BF12466FBFE00EB0786 /* RouterView.swift in Sources */, 0CAE940724702557007B2E95 /* WelcomeView.swift in Sources */, 0C3C1B6524703D8600571084 /* PrimaryButton.swift in Sources */, 0C3C1B692470404A00571084 /* Spinner.swift in Sources */, 0C571CBC24705809006B2931 /* SplashView.swift in Sources */, 0CA5FEBD2491B4B400C71D25 /* Keychain.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 0CAE93D6246F4927007B2E95 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 0CAE93DD246F4927007B2E95 /* PacketTunnelProvider.swift in Sources */, 0CA5FEBE2491B4B400C71D25 /* Keychain.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 0CD38AF52486C12E00DB2626 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 0C34DF93248D50E500FBBD11 /* Cipher.swift in Sources */, 0CD38AFC2486C12E00DB2626 /* Packet.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 0C230CDC24884C35005FEEBD /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 0CD38AF82486C12E00DB2626 /* vpn-protocol */; targetProxy = 0C230CDB24884C35005FEEBD /* PBXContainerItemProxy */; }; 0C5F7024248DA579000297AB /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 0CD38AF82486C12E00DB2626 /* vpn-protocol */; targetProxy = 0C5F7023248DA579000297AB /* PBXContainerItemProxy */; }; 0CAE93E1246F4927007B2E95 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 0CAE93D9246F4927007B2E95 /* vpn-tunnel */; targetProxy = 0CAE93E0246F4927007B2E95 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 0C405BF72466FC0000EB0786 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 0C405BF82466FC0000EB0786 /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 0C189C462479D280000EE6B3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_VERSION = 5.0; }; name = Debug; }; 0C189C472479D280000EE6B3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_VERSION = 5.0; }; name = Release; }; 0C405BFB2466FC0100EB0786 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 0C405BFC2466FC0100EB0786 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 0C405BFE2466FC0100EB0786 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "vpn-client/vpn-client.entitlements"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"vpn-client/Preview Content\""; DEVELOPMENT_TEAM = NR8DLKJ7E6; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "vpn-client/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.github.kean.vpn-client"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 0C405BFF2466FC0100EB0786 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "vpn-client/vpn-client.entitlements"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"vpn-client/Preview Content\""; DEVELOPMENT_TEAM = NR8DLKJ7E6; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "vpn-client/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.github.kean.vpn-client"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; 0CAE93E4246F4927007B2E95 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_ENTITLEMENTS = "vpn-tunnel/vpn_tunnel.entitlements"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = NR8DLKJ7E6; INFOPLIST_FILE = "vpn-tunnel/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.github.kean.vpn-client.vpn-tunnel"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 0CAE93E5246F4927007B2E95 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_ENTITLEMENTS = "vpn-tunnel/vpn_tunnel.entitlements"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = NR8DLKJ7E6; INFOPLIST_FILE = "vpn-tunnel/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.github.kean.vpn-client.vpn-tunnel"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; 0CD38AFE2486C12E00DB2626 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; IPHONEOS_DEPLOYMENT_TARGET = 13.0; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = BestVPN; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 0CD38AFF2486C12E00DB2626 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; IPHONEOS_DEPLOYMENT_TARGET = 13.0; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = BestVPN; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 0C189C452479D280000EE6B3 /* Build configuration list for PBXNativeTarget "vpn-server" */ = { isa = XCConfigurationList; buildConfigurations = ( 0C189C462479D280000EE6B3 /* Debug */, 0C189C472479D280000EE6B3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 0C405BE42466FBFE00EB0786 /* Build configuration list for PBXProject "vpn" */ = { isa = XCConfigurationList; buildConfigurations = ( 0C405BFB2466FC0100EB0786 /* Debug */, 0C405BFC2466FC0100EB0786 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 0C405BFD2466FC0100EB0786 /* Build configuration list for PBXNativeTarget "vpn-client" */ = { isa = XCConfigurationList; buildConfigurations = ( 0C405BFE2466FC0100EB0786 /* Debug */, 0C405BFF2466FC0100EB0786 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 0CAE93E3246F4927007B2E95 /* Build configuration list for PBXNativeTarget "vpn-tunnel" */ = { isa = XCConfigurationList; buildConfigurations = ( 0CAE93E4246F4927007B2E95 /* Debug */, 0CAE93E5246F4927007B2E95 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 0CD38AFD2486C12E00DB2626 /* Build configuration list for PBXNativeTarget "vpn-protocol" */ = { isa = XCConfigurationList; buildConfigurations = ( 0CD38AFE2486C12E00DB2626 /* Debug */, 0CD38AFF2486C12E00DB2626 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 0CD38B04248735A700DB2626 /* XCRemoteSwiftPackageReference "swift-nio" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/apple/swift-nio"; requirement = { kind = upToNextMajorVersion; minimumVersion = 2.17.0; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 0CD38B05248735A700DB2626 /* NIO */ = { isa = XCSwiftPackageProductDependency; package = 0CD38B04248735A700DB2626 /* XCRemoteSwiftPackageReference "swift-nio" */; productName = NIO; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 0C405BE12466FBFE00EB0786 /* Project object */; } ================================================ FILE: vpn.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: vpn.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: vpn.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved ================================================ { "object": { "pins": [ { "package": "swift-nio", "repositoryURL": "https://github.com/apple/swift-nio", "state": { "branch": null, "revision": "c5fa0b456524cd73dc3ddbb263d4f46c20b86ca3", "version": "2.17.0" } } ] }, "version": 1 } ================================================ FILE: vpn.xcodeproj/xcshareddata/xcschemes/vpn-client.xcscheme ================================================ ================================================ FILE: vpn.xcodeproj/xcshareddata/xcschemes/vpn-tunnel.xcscheme ================================================