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<UISceneSession>) {
// 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
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
================================================
FILE: vpn-client/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
================================================
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<String>
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, Error>) -> 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, Error>) -> 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, Error>) -> 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.github.kean.vpn-client</string>
</array>
</dict>
</plist>
================================================
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<Body: Codable>(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<T: Decodable>(_ 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<ByteBuffer>
public typealias OutboundOut = AddressedEnvelope<ByteBuffer>
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<String> 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>vpn-tunnel</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
</dict>
</plist>
================================================
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.github.kean.vpn-client</string>
</array>
</dict>
</plist>
================================================
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 = "<group>"; };
0C3C1B5D24702E1C00571084 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
0C3C1B5E247039EB00571084 /* VPNConfigurationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNConfigurationService.swift; sourceTree = "<group>"; };
0C3C1B6024703B5E00571084 /* TunnelDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailsView.swift; sourceTree = "<group>"; };
0C3C1B6424703D8600571084 /* PrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryButton.swift; sourceTree = "<group>"; };
0C3C1B682470404A00571084 /* Spinner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Spinner.swift; sourceTree = "<group>"; };
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 = "<group>"; };
0C405BEE2466FBFE00EB0786 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
0C405BF02466FBFE00EB0786 /* RouterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouterView.swift; sourceTree = "<group>"; };
0C405BF22466FC0000EB0786 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
0C405BF52466FC0000EB0786 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
0C405BF82466FC0000EB0786 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
0C405BFA2466FC0000EB0786 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0C571CB7247050E5006B2931 /* vpn-client.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "vpn-client.entitlements"; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
0CA5FEC02491B72E00C71D25 /* TunnelDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailsViewModel.swift; sourceTree = "<group>"; };
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 = "<group>"; };
0CAE93DE246F4927007B2E95 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0CAE93DF246F4927007B2E95 /* vpn_tunnel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = vpn_tunnel.entitlements; sourceTree = "<group>"; };
0CAE940624702557007B2E95 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
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 = "<group>"; };
/* 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 = "<group>";
};
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 = "<group>";
};
0C3C1B672470403C00571084 /* Services */ = {
isa = PBXGroup;
children = (
0C3C1B5E247039EB00571084 /* VPNConfigurationService.swift */,
0CA5FEBC2491B4B400C71D25 /* Keychain.swift */,
);
path = Services;
sourceTree = "<group>";
};
0C405BE02466FBFE00EB0786 = {
isa = PBXGroup;
children = (
0C3C1B5D24702E1C00571084 /* README.md */,
0C405BEB2466FBFE00EB0786 /* vpn-client */,
0CAE93DB246F4927007B2E95 /* vpn-tunnel */,
0C189C422479D280000EE6B3 /* vpn-server */,
0CD38AFA2486C12E00DB2626 /* vpn-protocol */,
0C405BEA2466FBFE00EB0786 /* Products */,
0C571CB8247050E5006B2931 /* Frameworks */,
);
sourceTree = "<group>";
};
0C405BEA2466FBFE00EB0786 /* Products */ = {
isa = PBXGroup;
children = (
0C405BE92466FBFE00EB0786 /* vpn-client.app */,
0CAE93DA246F4927007B2E95 /* vpn-tunnel.appex */,
0C189C412479D280000EE6B3 /* vpn-server */,
0CD38AF92486C12E00DB2626 /* libBestVPN.a */,
);
name = Products;
sourceTree = "<group>";
};
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 = "<group>";
};
0C405BF42466FC0000EB0786 /* Preview Content */ = {
isa = PBXGroup;
children = (
0C405BF52466FC0000EB0786 /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
0C571CB8247050E5006B2931 /* Frameworks */ = {
isa = PBXGroup;
children = (
0C571CC22470B343006B2931 /* libresolv.tbd */,
0C571CB9247050E5006B2931 /* NetworkExtension.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
0CA5FEBF2491B71F00C71D25 /* TunnelDetails */ = {
isa = PBXGroup;
children = (
0C3C1B6024703B5E00571084 /* TunnelDetailsView.swift */,
0CA5FEC02491B72E00C71D25 /* TunnelDetailsViewModel.swift */,
);
path = TunnelDetails;
sourceTree = "<group>";
};
0CAE93DB246F4927007B2E95 /* vpn-tunnel */ = {
isa = PBXGroup;
children = (
0CAE93DC246F4927007B2E95 /* PacketTunnelProvider.swift */,
0CAE93DE246F4927007B2E95 /* Info.plist */,
0CAE93DF246F4927007B2E95 /* vpn_tunnel.entitlements */,
);
path = "vpn-tunnel";
sourceTree = "<group>";
};
0CD38AFA2486C12E00DB2626 /* vpn-protocol */ = {
isa = PBXGroup;
children = (
0CD38AFB2486C12E00DB2626 /* Packet.swift */,
0C34DF92248D50E500FBBD11 /* Cipher.swift */,
);
path = "vpn-protocol";
sourceTree = "<group>";
};
/* 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 = "<group>";
};
/* 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: vpn.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1140"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0C405BE82466FBFE00EB0786"
BuildableName = "vpn-client.app"
BlueprintName = "vpn-client"
ReferencedContainer = "container:vpn.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0C405BE82466FBFE00EB0786"
BuildableName = "vpn-client.app"
BlueprintName = "vpn-client"
ReferencedContainer = "container:vpn.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0C405BE82466FBFE00EB0786"
BuildableName = "vpn-client.app"
BlueprintName = "vpn-client"
ReferencedContainer = "container:vpn.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: vpn.xcodeproj/xcshareddata/xcschemes/vpn-tunnel.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1140"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0CAE93D9246F4927007B2E95"
BuildableName = "vpn-tunnel.appex"
BlueprintName = "vpn-tunnel"
ReferencedContainer = "container:vpn.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0C405BE82466FBFE00EB0786"
BuildableName = "vpn-client.app"
BlueprintName = "vpn-client"
ReferencedContainer = "container:vpn.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0C405BE82466FBFE00EB0786"
BuildableName = "vpn-client.app"
BlueprintName = "vpn-client"
ReferencedContainer = "container:vpn.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0C405BE82466FBFE00EB0786"
BuildableName = "vpn-client.app"
BlueprintName = "vpn-client"
ReferencedContainer = "container:vpn.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
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
Condensed preview — 34 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (106K chars).
[
{
"path": ".gitignore",
"chars": 968,
"preview": "## System\n.DS_Store\n\n## Build generated\nbuild/\nDerivedData\nNuke.xcodeproj/xcshareddata/xcbaselines/\n\n## Various settings"
},
{
"path": "README.md",
"chars": 146,
"preview": "# VPN (WIP)\n\nA sample VPN client/server written in Swift.\n\n# License\n\nVPN is available under the MIT license. See the LI"
},
{
"path": "vpn-client/AppDelegate.swift",
"chars": 1365,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport UIKit\n\n@UIApplicationMa"
},
{
"path": "vpn-client/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 1591,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"iphone\",\n \"scale\" : \"2x\",\n \"size\" : \"20x20\"\n },\n {\n \"idiom\""
},
{
"path": "vpn-client/Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "vpn-client/Assets.xcassets/icon-vpn.imageset/Contents.json",
"chars": 510,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"icons8-vpn-status-bar-icon-50.png\",\n \"idiom\" : \"universal\",\n \"scale"
},
{
"path": "vpn-client/Base.lproj/LaunchScreen.storyboard",
"chars": 1665,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
},
{
"path": "vpn-client/Info.plist",
"chars": 1884,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "vpn-client/Preview Content/Preview Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "vpn-client/SceneDelegate.swift",
"chars": 2705,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport UIKit\nimport SwiftUI\n\nc"
},
{
"path": "vpn-client/Screens/PrimaryButton.swift",
"chars": 1312,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport SwiftUI\n\nstruct Primary"
},
{
"path": "vpn-client/Screens/PrimaryButtonView.swift",
"chars": 1181,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport SwiftUI\n\nstruct Primary"
},
{
"path": "vpn-client/Screens/RouterView.swift",
"chars": 663,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport SwiftUI\n\nstruct RouterV"
},
{
"path": "vpn-client/Screens/Spinner.swift",
"chars": 653,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport SwiftUI\nimport UIKit\n\ns"
},
{
"path": "vpn-client/Screens/SplashView.swift",
"chars": 1553,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport SwiftUI\nimport NetworkE"
},
{
"path": "vpn-client/Screens/TunnelDetails/TunnelDetailsView.swift",
"chars": 3262,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport SwiftUI\n\nstruct TunnelD"
},
{
"path": "vpn-client/Screens/TunnelDetails/TunnelDetailsViewModel.swift",
"chars": 5125,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport SwiftUI\nimport Combine\n"
},
{
"path": "vpn-client/Screens/WelcomeView.swift",
"chars": 2096,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport SwiftUI\n\nstruct Welcome"
},
{
"path": "vpn-client/Services/Keychain.swift",
"chars": 3265,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport Foundation\n\n/// Wrapper"
},
{
"path": "vpn-client/Services/VPNConfigurationService.swift",
"chars": 4343,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport Foundation\nimport Combi"
},
{
"path": "vpn-client/vpn-client.entitlements",
"chars": 430,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "vpn-protocol/Cipher.swift",
"chars": 1347,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport Foundation\nimport Crypt"
},
{
"path": "vpn-protocol/Packet.swift",
"chars": 2464,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport Foundation\nimport Crypt"
},
{
"path": "vpn-protocol/Session.swift",
"chars": 735,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport Foundation\nimport Crypt"
},
{
"path": "vpn-server/main.swift",
"chars": 4410,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport Foundation\nimport NIO\ni"
},
{
"path": "vpn-tunnel/Info.plist",
"chars": 1031,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "vpn-tunnel/PacketTunnelProvider.swift",
"chars": 10722,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2020 Alexander Grebenyuk (github.com/kean).\n\nimport NetworkExtension\nimport"
},
{
"path": "vpn-tunnel/vpn_tunnel.entitlements",
"chars": 430,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "vpn.xcodeproj/project.pbxproj",
"chars": 34304,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 52;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "vpn.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "vpn.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "vpn.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved",
"chars": 320,
"preview": "{\n \"object\": {\n \"pins\": [\n {\n \"package\": \"swift-nio\",\n \"repositoryURL\": \"https://github.com/apple"
},
{
"path": "vpn.xcodeproj/xcshareddata/xcschemes/vpn-client.xcscheme",
"chars": 2846,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1140\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "vpn.xcodeproj/xcshareddata/xcschemes/vpn-tunnel.xcscheme",
"chars": 3536,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1140\"\n wasCreatedForAppExtension = \"YES\"\n ve"
}
]
About this extraction
This page contains the full source code of the kean/VPN GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 34 files (95.1 KB), approximately 27.0k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.