├── .gitignore
├── AppProxy
├── AppProxy.entitlements
├── AppProxyProvider.swift
├── ClientAppProxyConnection.swift
└── Info.plist
├── FilterControlProvider
├── ControlExtension.swift
├── ControlFilterExtension.entitlements
└── Info.plist
├── FilterDataProvider
├── DataExtension.swift
├── DataFilterExtension.entitlements
└── Info.plist
├── LICENSE.txt
├── PacketTunnel
├── Info.plist
├── PacketTunnel.entitlements
└── PacketTunnelProvider.swift
├── README.md
├── SimpleTunnel.xcodeproj
└── project.pbxproj
├── SimpleTunnel
├── AddEditConfiguration.swift
├── AppDelegate.swift
├── Base.lproj
│ ├── LaunchScreen.xib
│ └── Main.storyboard
├── ClientTunnelConnection.swift
├── ConfigurationListController.swift
├── ConfigurationParametersViewController.swift
├── ConnectionRuleAddEditController.swift
├── ConnectionRuleListController.swift
├── ContentFilterController.swift
├── EnumPickerController.swift
├── Images.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── GrayDot.imageset
│ │ ├── Contents.json
│ │ ├── GrayDot.png
│ │ ├── GrayDot@1x.png
│ │ └── GrayDot@3x.png
│ ├── GreenDot.imageset
│ │ ├── Contents.json
│ │ ├── GreenDot.png
│ │ ├── GreenDot@1x.png
│ │ └── GreenDot@3x.png
│ └── RedDot.imageset
│ │ ├── Contents.json
│ │ ├── RedDot.png
│ │ ├── RedDot@1x.png
│ │ └── RedDot@3x.png
├── Info.plist
├── ListViewController.swift
├── OnDemandRuleAddEditController.swift
├── OnDemandRuleListController.swift
├── ProxyAutoConfigScriptController.swift
├── ProxyServerAddEditController.swift
├── ProxySettingsController.swift
├── SimpleTunnel.entitlements
├── StatusViewController.swift
├── StringListController.swift
├── SwitchCell.swift
└── TextFieldCell.swift
├── SimpleTunnelServices
├── ClientTunnel.swift
├── Connection.swift
├── FilterUtilities.swift
├── Info.plist
├── SimpleTunnelServices.h
├── Tunnel.swift
└── util.swift
└── tunnel_server
├── AddressPool.swift
├── ServerConfiguration.swift
├── ServerConnection.swift
├── ServerTunnel.swift
├── ServerTunnelConnection.swift
├── ServerUtils.h
├── ServerUtils.m
├── UDPServerConnection.swift
├── config.plist
├── main.swift
└── tunnel_server-Bridging-Header.h
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | .build/
41 |
42 | # CocoaPods
43 | #
44 | # We recommend against adding the Pods directory to your .gitignore. However
45 | # you should judge for yourself, the pros and cons are mentioned at:
46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
47 | #
48 | # Pods/
49 |
50 | # Carthage
51 | #
52 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
53 | # Carthage/Checkouts
54 |
55 | Carthage/Build
56 |
57 | # fastlane
58 | #
59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
60 | # screenshots whenever they are needed.
61 | # For more information about the recommended setup visit:
62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
63 |
64 | fastlane/report.xml
65 | fastlane/Preview.html
66 | fastlane/screenshots
67 | fastlane/test_output
68 |
--------------------------------------------------------------------------------
/AppProxy/AppProxy.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | keychain-access-groups
6 |
7 | com.apple.managed.vpn.shared
8 |
9 | com.apple.developer.networking.networkextension
10 |
11 | packet-tunnel-provider
12 | app-proxy-provider
13 | content-filter-provider
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/AppProxy/AppProxyProvider.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the AppProxyProvider class. The AppProxyProvider class is a sub-class of NEAppProxyProvider, and is the integration point between the Network Extension framework and the SimpleTunnel tunneling protocol.
7 | */
8 |
9 | import NetworkExtension
10 | import SimpleTunnelServices
11 |
12 | /// A NEAppProxyProvider sub-class that implements the client side of the SimpleTunnel tunneling protocol.
13 | class AppProxyProvider: NEAppProxyProvider, TunnelDelegate {
14 |
15 | // MARK: Properties
16 |
17 | /// A reference to the tunnel object.
18 | var tunnel: ClientTunnel?
19 |
20 | /// The completion handler to call when the tunnel is fully established.
21 | var pendingStartCompletion: ((NSError?) -> Void)?
22 |
23 | /// The completion handler to call when the tunnel is fully disconnected.
24 | var pendingStopCompletion: ((Void) -> Void)?
25 |
26 | // MARK: NEAppProxyProvider
27 |
28 | /// Begin the process of establishing the tunnel.
29 | override func startProxy(options: [String : Any]?, completionHandler: @escaping (Error?) -> Void) {
30 |
31 | let newTunnel = ClientTunnel()
32 | newTunnel.delegate = self
33 |
34 | if let error = newTunnel.startTunnel(self) {
35 | completionHandler(error as NSError)
36 | return
37 | }
38 |
39 | pendingStartCompletion = completionHandler
40 | tunnel = newTunnel
41 | }
42 |
43 | /// Begin the process of stopping the tunnel.
44 | override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
45 |
46 | // Clear out any pending start completion handler.
47 | pendingStartCompletion = nil
48 |
49 | pendingStopCompletion = completionHandler
50 | tunnel?.closeTunnel()
51 | }
52 |
53 | /// Handle a new flow of network data created by an application.
54 | override func handleNewFlow(_ flow: (NEAppProxyFlow?)) -> Bool {
55 | var newConnection: ClientAppProxyConnection?
56 |
57 | guard let clientTunnel = tunnel else { return false }
58 |
59 | if let TCPFlow = flow as? NEAppProxyTCPFlow {
60 | newConnection = ClientAppProxyTCPConnection(tunnel: clientTunnel, newTCPFlow: TCPFlow)
61 | }
62 | else if let UDPFlow = flow as? NEAppProxyUDPFlow {
63 | newConnection = ClientAppProxyUDPConnection(tunnel: clientTunnel, newUDPFlow: UDPFlow)
64 | }
65 |
66 | guard newConnection != nil else { return false }
67 |
68 | newConnection!.open()
69 |
70 | return true
71 | }
72 |
73 | // MARK: TunnelDelegate
74 |
75 | /// Handle the event of the tunnel being fully established.
76 | func tunnelDidOpen(_ targetTunnel: Tunnel) {
77 | guard let clientTunnel = targetTunnel as? ClientTunnel else {
78 | pendingStartCompletion?(SimpleTunnelError.internalError as NSError)
79 | pendingStartCompletion = nil
80 | return
81 | }
82 | simpleTunnelLog("Tunnel opened, fetching configuration")
83 | clientTunnel.sendFetchConfiguation()
84 | }
85 |
86 | /// Handle the event of the tunnel being fully disconnected.
87 | func tunnelDidClose(_ targetTunnel: Tunnel) {
88 |
89 | // Call the appropriate completion handler depending on the current pending tunnel operation.
90 | if pendingStartCompletion != nil {
91 | pendingStartCompletion?(tunnel?.lastError)
92 | pendingStartCompletion = nil
93 | }
94 | else if pendingStopCompletion != nil {
95 | pendingStopCompletion?()
96 | pendingStopCompletion = nil
97 | }
98 | else {
99 | // No completion handler, so cancel the proxy.
100 | cancelProxyWithError(tunnel?.lastError)
101 | }
102 | tunnel = nil
103 | }
104 |
105 | /// Handle the server sending a configuration.
106 | func tunnelDidSendConfiguration(_ targetTunnel: Tunnel, configuration: [String : AnyObject]) {
107 | simpleTunnelLog("Server sent configuration: \(configuration)")
108 |
109 | guard let tunnelAddress = tunnel?.remoteHost else {
110 | let error = SimpleTunnelError.badConnection
111 | pendingStartCompletion?(error as NSError)
112 | pendingStartCompletion = nil
113 | return
114 | }
115 |
116 | guard let DNSDictionary = configuration[SettingsKey.DNS.rawValue] as? [String: AnyObject], let DNSServers = DNSDictionary[SettingsKey.Servers.rawValue] as? [String] else {
117 | self.pendingStartCompletion?(nil)
118 | self.pendingStartCompletion = nil
119 | return
120 | }
121 |
122 | let newSettings = NETunnelNetworkSettings(tunnelRemoteAddress: tunnelAddress)
123 |
124 | newSettings.dnsSettings = NEDNSSettings(servers: DNSServers)
125 | if let DNSSearchDomains = DNSDictionary[SettingsKey.SearchDomains.rawValue] as? [String] {
126 | newSettings.dnsSettings?.searchDomains = DNSSearchDomains
127 | }
128 |
129 | simpleTunnelLog("Calling setTunnelNetworkSettings")
130 |
131 | self.setTunnelNetworkSettings(newSettings) { error in
132 | if error != nil {
133 | let startError = SimpleTunnelError.badConfiguration
134 | self.pendingStartCompletion?(startError as NSError)
135 | self.pendingStartCompletion = nil
136 | }
137 | else {
138 | self.pendingStartCompletion?(nil)
139 | self.pendingStartCompletion = nil
140 | }
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/AppProxy/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | AppProxy
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | XPC!
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | NSExtension
26 |
27 | NSExtensionPointIdentifier
28 | com.apple.networkextension.app-proxy
29 | NSExtensionPrincipalClass
30 | $(PRODUCT_MODULE_NAME).AppProxyProvider
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/FilterControlProvider/ControlExtension.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ControlExtension class. The ControlExtension class is a sub-class of NEFilterControlProvider, and is responsible for downloading content filter rules from a web service.
7 | */
8 |
9 | import NetworkExtension
10 | import Foundation
11 | import SimpleTunnelServices
12 |
13 | /// A NEFitlerControlProvider sub-class that implements logic for downloading rules from a web server.
14 | class ControlExtension : NEFilterControlProvider {
15 |
16 | // MARK: Properties
17 |
18 | /// The default rules, in the event that
19 | let defaultRules: [String: [String: AnyObject]] = [
20 | "www.apple.com" : [
21 | "kRule" : FilterRuleAction.block.rawValue as AnyObject,
22 | "kRemediationKey" : "Remediate1" as AnyObject
23 | ]
24 | ]
25 |
26 | /// An integer to use as the context for key-value observing.
27 | var observerContext = 0
28 |
29 | // MARK: Interface
30 |
31 | /// Update the filter based on changes to the configuration
32 | func updateFromConfiguration() {
33 | guard let serverAddress = filterConfiguration.serverAddress else { return }
34 |
35 | FilterUtilities.defaults?.setValue(defaultRules, forKey: "rules")
36 | FilterUtilities.fetchRulesFromServer(filterConfiguration.serverAddress)
37 |
38 | let remediationURL = "https://\(serverAddress)/remediate/?url=\(NEFilterProviderRemediationURLFlowURLHostname)&organization=\(NEFilterProviderRemediationURLOrganization)&username=\(NEFilterProviderRemediationURLUsername)"
39 |
40 | simpleTunnelLog("Remediation url is \(remediationURL)")
41 |
42 | remediationMap =
43 | [
44 | NEFilterProviderRemediationMapRemediationURLs : [ "Remediate1" : remediationURL as NSObject ],
45 | NEFilterProviderRemediationMapRemediationButtonTexts :
46 | [
47 | "RemediateButton1" : "Request Access" as NSObject,
48 | "RemediateButton2" : "\"" as NSObject,
49 | "RemediateButton3" : "Request Access 3" as NSObject,
50 | ]
51 | ]
52 |
53 | self.urlAppendStringMap = [ "SafeYes" : "safe=yes", "Adult" : "adult=yes"]
54 |
55 | simpleTunnelLog("Remediation map set")
56 | }
57 |
58 | // MARK: Initializers
59 |
60 | override init() {
61 | super.init()
62 | updateFromConfiguration()
63 |
64 | FilterUtilities.defaults?.setValue(defaultRules, forKey: "rules")
65 | FilterUtilities.fetchRulesFromServer(self.filterConfiguration.serverAddress)
66 |
67 | self.addObserver(self, forKeyPath: "filterConfiguration", options: [.initial, .new], context: &observerContext)
68 | }
69 |
70 | // MARK: NSObject
71 |
72 | /// Observe changes to the configuration.
73 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
74 | if keyPath == "filterConfiguration" && context == &observerContext {
75 | simpleTunnelLog("configuration changed")
76 | updateFromConfiguration()
77 | } else {
78 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
79 | }
80 | }
81 |
82 | // MARK: NEFilterControlProvider
83 |
84 | /// Handle a new flow of network data
85 | override func handleNewFlow(_ flow: NEFilterFlow, completionHandler: @escaping (NEFilterControlVerdict) -> Void) {
86 | simpleTunnelLog("Handle new flow called")
87 | var controlVerdict = NEFilterControlVerdict.updateRules()
88 | let (ruleType, hostname, _) = FilterUtilities.getRule(flow)
89 |
90 | switch ruleType {
91 | case .needMoreRulesAndAllow:
92 | simpleTunnelLog("\(hostname) is set to be Allowed")
93 | controlVerdict = NEFilterControlVerdict.allow(withUpdateRules: false)
94 |
95 | case .needMoreRulesAndBlock:
96 | simpleTunnelLog("\(hostname) is set to be blocked")
97 | controlVerdict = NEFilterControlVerdict.drop(withUpdateRules: false)
98 |
99 | default:
100 | simpleTunnelLog("\(hostname) is not set for need more rules")
101 | }
102 |
103 | completionHandler(controlVerdict)
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/FilterControlProvider/ControlFilterExtension.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | keychain-access-groups
6 |
7 | com.apple.managed.vpn.shared
8 |
9 | com.apple.developer.networking.networkextension
10 |
11 | packet-tunnel-provider
12 | app-proxy-provider
13 | content-filter-provider
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/FilterControlProvider/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | FilterControlProvider
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | XPC!
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | NSExtension
26 |
27 | NSExtensionPointIdentifier
28 | com.apple.networkextension.filter-control
29 | NSExtensionPrincipalClass
30 | FilterControlProvider.ControlExtension
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/FilterDataProvider/DataFilterExtension.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | keychain-access-groups
6 |
7 | com.apple.managed.vpn.shared
8 |
9 | com.apple.developer.networking.networkextension
10 |
11 | packet-tunnel-provider
12 | app-proxy-provider
13 | content-filter-provider
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/FilterDataProvider/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | FilterDataProvider
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | XPC!
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | NSExtension
26 |
27 | NSExtensionPointIdentifier
28 | com.apple.networkextension.filter-data
29 | NSExtensionPrincipalClass
30 | FilterDataProvider.DataExtension
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Sample code project: SimpleTunnel: Customized Networking Using the NetworkExtension Framework
2 | Version: 1.3
3 |
4 | IMPORTANT: This Apple software is supplied to you by Apple
5 | Inc. ("Apple") in consideration of your agreement to the following
6 | terms, and your use, installation, modification or redistribution of
7 | this Apple software constitutes acceptance of these terms. If you do
8 | not agree with these terms, please do not use, install, modify or
9 | redistribute this Apple software.
10 |
11 | In consideration of your agreement to abide by the following terms, and
12 | subject to these terms, Apple grants you a personal, non-exclusive
13 | license, under Apple's copyrights in this original Apple software (the
14 | "Apple Software"), to use, reproduce, modify and redistribute the Apple
15 | Software, with or without modifications, in source and/or binary forms;
16 | provided that if you redistribute the Apple Software in its entirety and
17 | without modifications, you must retain this notice and the following
18 | text and disclaimers in all such redistributions of the Apple Software.
19 | Neither the name, trademarks, service marks or logos of Apple Inc. may
20 | be used to endorse or promote products derived from the Apple Software
21 | without specific prior written permission from Apple. Except as
22 | expressly stated in this notice, no other rights or licenses, express or
23 | implied, are granted by Apple herein, including but not limited to any
24 | patent rights that may be infringed by your derivative works or by other
25 | works in which the Apple Software may be incorporated.
26 |
27 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE
28 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
29 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
30 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
31 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
32 |
33 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
34 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
35 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
36 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
37 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
38 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
39 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
40 | POSSIBILITY OF SUCH DAMAGE.
41 |
42 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
43 |
--------------------------------------------------------------------------------
/PacketTunnel/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | PacketTunnel
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | XPC!
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | NSExtension
26 |
27 | NSExtensionPointIdentifier
28 | com.apple.networkextension.packet-tunnel
29 | NSExtensionPrincipalClass
30 | $(PRODUCT_MODULE_NAME).PacketTunnelProvider
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/PacketTunnel/PacketTunnel.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.networking.networkextension
6 |
7 | packet-tunnel-provider
8 | app-proxy-provider
9 | content-filter-provider
10 |
11 | keychain-access-groups
12 |
13 | $(AppIdentifierPrefix)com.telekom.SimpleTunnel
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/PacketTunnel/PacketTunnelProvider.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the PacketTunnelProvider class. The PacketTunnelProvider class is a sub-class of NEPacketTunnelProvider, and is the integration point between the Network Extension framework and the SimpleTunnel tunneling protocol.
7 | */
8 |
9 | import NetworkExtension
10 | import SimpleTunnelServices
11 |
12 | /// A packet tunnel provider object.
13 | class PacketTunnelProvider: NEPacketTunnelProvider, TunnelDelegate, ClientTunnelConnectionDelegate {
14 |
15 | // MARK: Properties
16 |
17 | /// A reference to the tunnel object.
18 | var tunnel: ClientTunnel?
19 |
20 | /// The single logical flow of packets through the tunnel.
21 | var tunnelConnection: ClientTunnelConnection?
22 |
23 | /// The completion handler to call when the tunnel is fully established.
24 | var pendingStartCompletion: ((Error?) -> Void)?
25 |
26 | /// The completion handler to call when the tunnel is fully disconnected.
27 | var pendingStopCompletion: ((Void) -> Void)?
28 |
29 | // MARK: NEPacketTunnelProvider
30 |
31 | /// Begin the process of establishing the tunnel.
32 | override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
33 | let newTunnel = ClientTunnel()
34 | newTunnel.delegate = self
35 |
36 | if let error = newTunnel.startTunnel(self) {
37 | completionHandler(error as NSError)
38 | }
39 | else {
40 | // Save the completion handler for when the tunnel is fully established.
41 | pendingStartCompletion = completionHandler
42 | tunnel = newTunnel
43 | }
44 | }
45 |
46 | /// Begin the process of stopping the tunnel.
47 | override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
48 | // Clear out any pending start completion handler.
49 | pendingStartCompletion = nil
50 |
51 | // Save the completion handler for when the tunnel is fully disconnected.
52 | pendingStopCompletion = completionHandler
53 | tunnel?.closeTunnel()
54 | }
55 |
56 | /// Handle IPC messages from the app.
57 | override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
58 | guard let messageString = NSString(data: messageData, encoding: String.Encoding.utf8.rawValue) else {
59 | completionHandler?(nil)
60 | return
61 | }
62 |
63 | simpleTunnelLog("Got a message from the app: \(messageString)")
64 |
65 | let responseData = "Hello app".data(using: String.Encoding.utf8)
66 | completionHandler?(responseData)
67 | }
68 |
69 | // MARK: TunnelDelegate
70 |
71 | /// Handle the event of the tunnel connection being established.
72 | func tunnelDidOpen(_ targetTunnel: Tunnel) {
73 | // Open the logical flow of packets through the tunnel.
74 | let newConnection = ClientTunnelConnection(tunnel: tunnel!, clientPacketFlow: packetFlow, connectionDelegate: self)
75 | newConnection.open()
76 | tunnelConnection = newConnection
77 | }
78 |
79 | /// Handle the event of the tunnel connection being closed.
80 | func tunnelDidClose(_ targetTunnel: Tunnel) {
81 | if pendingStartCompletion != nil {
82 | // Closed while starting, call the start completion handler with the appropriate error.
83 | pendingStartCompletion?(tunnel?.lastError)
84 | pendingStartCompletion = nil
85 | }
86 | else if pendingStopCompletion != nil {
87 | // Closed as the result of a call to stopTunnelWithReason, call the stop completion handler.
88 | pendingStopCompletion?()
89 | pendingStopCompletion = nil
90 | }
91 | else {
92 | // Closed as the result of an error on the tunnel connection, cancel the tunnel.
93 | cancelTunnelWithError(tunnel?.lastError)
94 | }
95 | tunnel = nil
96 | }
97 |
98 | /// Handle the server sending a configuration.
99 | func tunnelDidSendConfiguration(_ targetTunnel: Tunnel, configuration: [String : AnyObject]) {
100 | }
101 |
102 | // MARK: ClientTunnelConnectionDelegate
103 |
104 | /// Handle the event of the logical flow of packets being established through the tunnel.
105 | func tunnelConnectionDidOpen(_ connection: ClientTunnelConnection, configuration: [NSObject: AnyObject]) {
106 |
107 | // Create the virtual interface settings.
108 | guard let settings = createTunnelSettingsFromConfiguration(configuration) else {
109 | pendingStartCompletion?(SimpleTunnelError.internalError as NSError)
110 | pendingStartCompletion = nil
111 | return
112 | }
113 |
114 | // Set the virtual interface settings.
115 | setTunnelNetworkSettings(settings) { error in
116 | var startError: NSError?
117 | if let error = error {
118 | simpleTunnelLog("Failed to set the tunnel network settings: \(error)")
119 | startError = SimpleTunnelError.badConfiguration as NSError
120 | }
121 | else {
122 | // Now we can start reading and writing packets to/from the virtual interface.
123 | self.tunnelConnection?.startHandlingPackets()
124 | }
125 |
126 | // Now the tunnel is fully established, call the start completion handler.
127 | self.pendingStartCompletion?(startError)
128 | self.pendingStartCompletion = nil
129 | }
130 | }
131 |
132 | /// Handle the event of the logical flow of packets being torn down.
133 | func tunnelConnectionDidClose(_ connection: ClientTunnelConnection, error: NSError?) {
134 | tunnelConnection = nil
135 | tunnel?.closeTunnelWithError(error)
136 | }
137 |
138 | /// Create the tunnel network settings to be applied to the virtual interface.
139 | func createTunnelSettingsFromConfiguration(_ configuration: [NSObject: AnyObject]) -> NEPacketTunnelNetworkSettings? {
140 | guard let tunnelAddress = tunnel?.remoteHost,
141 | let address = getValueFromPlist(configuration, keyArray: [.IPv4, .Address]) as? String,
142 | let netmask = getValueFromPlist(configuration, keyArray: [.IPv4, .Netmask]) as? String
143 | else { return nil }
144 |
145 | let newSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: tunnelAddress)
146 | var fullTunnel = true
147 |
148 | newSettings.iPv4Settings = NEIPv4Settings(addresses: [address], subnetMasks: [netmask])
149 |
150 | if let routes = getValueFromPlist(configuration, keyArray: [.IPv4, .Routes]) as? [[String: AnyObject]] {
151 | var includedRoutes = [NEIPv4Route]()
152 | for route in routes {
153 | if let netAddress = route[SettingsKey.Address.rawValue] as? String,
154 | let netMask = route[SettingsKey.Netmask.rawValue] as? String
155 | {
156 | includedRoutes.append(NEIPv4Route(destinationAddress: netAddress, subnetMask: netMask))
157 | }
158 | }
159 | newSettings.iPv4Settings?.includedRoutes = includedRoutes
160 | fullTunnel = false
161 | }
162 | else {
163 | // No routes specified, use the default route.
164 | newSettings.iPv4Settings?.includedRoutes = [NEIPv4Route.default()]
165 | }
166 |
167 | if let DNSDictionary = configuration[SettingsKey.DNS.rawValue as NSString] as? [String: AnyObject],
168 | let DNSServers = DNSDictionary[SettingsKey.Servers.rawValue] as? [String]
169 | {
170 | newSettings.dnsSettings = NEDNSSettings(servers: DNSServers)
171 | if let DNSSearchDomains = DNSDictionary[SettingsKey.SearchDomains.rawValue] as? [String] {
172 | newSettings.dnsSettings?.searchDomains = DNSSearchDomains
173 | if !fullTunnel {
174 | newSettings.dnsSettings?.matchDomains = DNSSearchDomains
175 | }
176 | }
177 | }
178 |
179 | newSettings.tunnelOverheadBytes = 150
180 |
181 | return newSettings
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SimpleTunnel: Customized Networking Using the NetworkExtension Framework
2 |
3 | The SimpleTunnel project contains working examples of the four extension points provided by the Network Extension framework:
4 |
5 | 1. Packet Tunnel Provider
6 |
7 | The Packet Tunnel Provider extension point is used to implemnt the client side of a custom network tunneling protocol that encapsulates network data in the form of IP packets. The PacketTunnel target in the SimpleTunnel project produces a sample Packet Tunnel Provider extension.
8 |
9 | 2. App Proxy Provider
10 |
11 | The App Proxy Provider extension point is used to implement the client side of a custom network proxy protocol that encapsulates network data in the form of flows of application network data. Both TCP or stream-based and UDP or datagram-based flows of data are supported. The AppProxy target in the SimpleTunnel project produces a sample App Proxy Provider extension.
12 |
13 | 3. Filter Data Provider and Filter Control Provider
14 |
15 | The two Filter Provider extension points are used to implement a dynamic, on-device network content filter. The Filter Data Provider extension is responsible for examining network data and making pass/block decisions. The Filter Data Provider extension sandbox prevents the extension from communicating over the network or writing to disk to prevent any leakage of network data. The Filter Control Provider extension can communicate using the network and write to the disk. It is responsible for updating the filtering rules on behalf of the Filter Data Provider extension.
16 |
17 | The FilterDataProvider target in the SimpleTunnel project produces a sample Filter Data Provider extension. The FilterControlProvider target in the SimpleTunnel project produces a sample Filter Control Provider extension.e
18 |
19 | All of the sample extensions are packaged into the SimpleTunnel app. The SimpleTunnel app contains code demonstrating how to configure and control the various types of Network Extension providers. The SimpleTunnel target in the SimpleTunnel project produces the SimpleTunnel app and all of the sample extensions.
20 |
21 | The SimpleTunnel project contains both the client and server sides of a custom network tunneling protocol. The Packet Tunnel Provider and App Proxy Provider extensions implement the client side. The tunnel_server target produces a OS X command-line binary that implements the server side. The server is configured using a plist. A sample plist is included in the tunnel_server source. To run the server, use this command:
22 |
23 | sudo tunnel_server
24 |
25 | # Requirements
26 |
27 | ### Runtime
28 |
29 | The NEProvider family of APIs require the following entitlement:
30 |
31 | com.apple.developer.networking.networkextension
32 |
33 | packet-tunnel-provider
34 | app-proxy-provider
35 | content-filter-provider
36 |
37 |
38 |
39 | The SimpleTunnel.app and the provider extensions will not run if they are not code signed with this entitlement.
40 |
41 | You can request this entitlement by sending an email to networkextension@apple.com.
42 |
43 | The SimpleTunnel iOS products require iOS 9.0 or newer.
44 | The SimpleTunnel OS X products require OS X 11.0 or newer.
45 |
46 | ### Build
47 |
48 | SimpleTunnel requires Xcode 8.0 or newer.
49 | The SimpleTunnel iOS targets require the iOS 9.0 SDK or newer.
50 | The SimpleTunnel OS X targets require the OS X 11.0 SDK or newer.
51 |
52 | Copyright (C) 2016 Apple Inc. All rights reserved.
53 |
--------------------------------------------------------------------------------
/SimpleTunnel/AddEditConfiguration.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the AddEditConfiguration class, which is responsible for controlling a view used to create or edit a VPN configuration.
7 | */
8 |
9 | import UIKit
10 | import NetworkExtension
11 | import Security
12 | import SimpleTunnelServices
13 |
14 | /// A view controller object for a table view containing input fields used to specify configuration parameters for a VPN configuration.
15 | class AddEditConfiguration: ConfigurationParametersViewController {
16 |
17 | // MARK: Properties
18 |
19 | /// A table view cell containing the text field where the name of the configuration is entered.
20 | @IBOutlet weak var nameCell: TextFieldCell!
21 |
22 | /// A table view cell containing the text field where the server address of the configuration is entered.
23 | @IBOutlet weak var serverAddressCell: TextFieldCell!
24 |
25 | /// A table view cell containing the text field where the username of the configuration is entered.
26 | @IBOutlet weak var usernameCell: TextFieldCell!
27 |
28 | /// A table view cell containing the text field where the password of the configuration is entered.
29 | @IBOutlet weak var passwordCell: TextFieldCell!
30 |
31 | /// A table view cell containing a switch used to enable and disable Connect On Demand for the configuration.
32 | @IBOutlet weak var onDemandCell: SwitchCell!
33 |
34 | /// A table view cell containing a switch used to enable and disable proxy settings for the configuration.
35 | @IBOutlet weak var proxiesCell: SwitchCell!
36 |
37 | /// A table view cell containing a switch used to enable and disable Disconnect On Sleep for the configuration.
38 | @IBOutlet weak var disconnectOnSleepCell: SwitchCell!
39 |
40 | /// A table view cell that when tapped transitions the app to a view where the Connect On Demand rules are managed.
41 | @IBOutlet weak var onDemandRulesCell: UITableViewCell!
42 |
43 | /// A table view cell that when tapped transitions the app to a view where the proxy settings are managed.
44 | @IBOutlet weak var proxySettingsCell: UITableViewCell!
45 |
46 | /// The NEVPNManager object corresponding to the configuration being added or edited.
47 | var targetManager: NEVPNManager = NEVPNManager.shared()
48 |
49 | // MARK: UIViewController
50 |
51 | /// Handle the event of the view being loaded into memory.
52 | override func viewDidLoad() {
53 | super.viewDidLoad()
54 |
55 | // Set up the table view cells
56 |
57 | cells = [
58 | nameCell,
59 | serverAddressCell,
60 | usernameCell,
61 | passwordCell,
62 | onDemandCell,
63 | proxiesCell,
64 | disconnectOnSleepCell
65 | ].flatMap { $0 }
66 |
67 | // The switch in proxiesCell controls the display of proxySettingsCell
68 | proxiesCell.dependentCells = [ proxySettingsCell ]
69 | proxiesCell.getIndexPath = {
70 | return self.getIndexPathOfCell(self.proxiesCell)
71 | }
72 | proxiesCell.valueChanged = {
73 | self.updateCellsWithDependentsOfCell(self.proxiesCell)
74 | }
75 |
76 | // The switch in onDemandCell controls the display of onDemandRulesCell
77 | onDemandCell.dependentCells = [ onDemandRulesCell ]
78 | onDemandCell.getIndexPath = {
79 | return self.getIndexPathOfCell(self.onDemandCell)
80 | }
81 | onDemandCell.valueChanged = {
82 | self.updateCellsWithDependentsOfCell(self.onDemandCell)
83 | self.targetManager.isOnDemandEnabled = self.onDemandCell.isOn
84 | }
85 |
86 | disconnectOnSleepCell.valueChanged = {
87 | self.targetManager.protocolConfiguration?.disconnectOnSleep = self.disconnectOnSleepCell.isOn
88 | }
89 |
90 | nameCell.valueChanged = {
91 | self.targetManager.localizedDescription = self.nameCell.textField.text
92 | }
93 |
94 | serverAddressCell.valueChanged = {
95 | self.targetManager.protocolConfiguration?.serverAddress = self.serverAddressCell.textField.text
96 | }
97 |
98 | usernameCell.valueChanged = {
99 | self.targetManager.protocolConfiguration?.username = self.usernameCell.textField.text
100 | }
101 | }
102 |
103 | /// Handle the event of the view being displayed.
104 | override func viewWillAppear(_ animated: Bool) {
105 | super.viewWillAppear(animated)
106 |
107 | tableView.reloadData()
108 |
109 | // Set the text fields and switches per the settings in the configuration.
110 |
111 | nameCell.textField.text = targetManager.localizedDescription
112 | serverAddressCell.textField.text = targetManager.protocolConfiguration?.serverAddress
113 | usernameCell.textField.text = targetManager.protocolConfiguration?.username
114 |
115 | if let passRef = targetManager.protocolConfiguration?.passwordReference {
116 | passwordCell.textField.text = getPasswordWithPersistentReference(passRef)
117 | }
118 | else {
119 | passwordCell.textField.text = nil
120 | }
121 |
122 | disconnectOnSleepCell.isOn = targetManager.protocolConfiguration?.disconnectOnSleep ?? false
123 |
124 | onDemandCell.isOn = targetManager.isOnDemandEnabled
125 |
126 | onDemandRulesCell.detailTextLabel?.text = getDescriptionForListValue(targetManager.onDemandRules, itemDescription: "rule")
127 |
128 | proxiesCell.isOn = targetManager.protocolConfiguration?.proxySettings != nil
129 | }
130 |
131 | /// Set up the destination view controller of a segue away from this view controller.
132 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
133 | guard let identifier = segue.identifier else { return }
134 |
135 | switch identifier {
136 | case "edit-proxy-settings":
137 | // The user tapped on the proxy settings cell.
138 | guard let controller = segue.destination as? ProxySettingsController else { break }
139 | if targetManager.protocolConfiguration?.proxySettings == nil {
140 | targetManager.protocolConfiguration?.proxySettings = NEProxySettings()
141 | }
142 | controller.targetConfiguration = targetManager.protocolConfiguration ?? NETunnelProviderProtocol()
143 |
144 | case "edit-on-demand-rules":
145 | // The user tapped on the Connect On Demand rules cell.
146 | guard let controller = segue.destination as? OnDemandRuleListController else { break }
147 | controller.targetManager = targetManager
148 |
149 | default:
150 | break
151 | }
152 | }
153 |
154 | // MARK: Interface
155 |
156 | /// Set the target configuration and the title to display in the view.
157 | func setTargetManager(_ manager: NEVPNManager?, title: String?) {
158 | if let newManager = manager {
159 | // A manager was given, so an existing configuration is being edited.
160 | targetManager = newManager
161 | }
162 | else {
163 | // No manager was given, create a new configuration.
164 | let newManager = NETunnelProviderManager()
165 | newManager.protocolConfiguration = NETunnelProviderProtocol()
166 | newManager.localizedDescription = "Demo VPN"
167 | newManager.protocolConfiguration?.serverAddress = "TunnelServer"
168 | targetManager = newManager
169 | }
170 | navigationItem.title = title
171 | }
172 |
173 | /// Save the configuration to the Network Extension preferences.
174 | @IBAction func saveTargetManager(_ sender: AnyObject) {
175 | if !proxiesCell.isOn {
176 | targetManager.protocolConfiguration?.proxySettings = nil
177 | }
178 | targetManager.saveToPreferences { error in
179 | if let saveError = error {
180 | simpleTunnelLog("Failed to save the configuration: \(saveError)")
181 | return
182 | }
183 |
184 | // Transition back to the configuration list view.
185 | self.performSegue(withIdentifier: "save-configuration", sender: sender)
186 | }
187 | }
188 |
189 | /// Save a password in the keychain.
190 | func savePassword(_ password: String, inKeychainItem: Data?) -> Data? {
191 | guard let passwordData = password.data(using: String.Encoding.utf8, allowLossyConversion: false) else { return nil }
192 | var status = errSecSuccess
193 |
194 | if let persistentReference = inKeychainItem {
195 | // A persistent reference was given, update the corresponding keychain item.
196 | let query: [NSObject: AnyObject] = [
197 | kSecValuePersistentRef : persistentReference as AnyObject,
198 | kSecReturnAttributes : kCFBooleanTrue
199 | ]
200 | var result: AnyObject?
201 |
202 | // Get the current attributes for the item.
203 | status = SecItemCopyMatching(query as CFDictionary, &result)
204 |
205 | if let attributes = result as? [NSObject: AnyObject] , status == errSecSuccess {
206 | // Update the attributes with the new data.
207 | var updateQuery = [NSObject: AnyObject]()
208 | updateQuery[kSecClass] = kSecClassGenericPassword
209 | updateQuery[kSecAttrService] = attributes[kSecAttrService]
210 |
211 | var newAttributes = attributes
212 | newAttributes[kSecValueData] = passwordData as AnyObject?
213 |
214 | status = SecItemUpdate(updateQuery as CFDictionary, newAttributes as CFDictionary)
215 | if status == errSecSuccess {
216 | return persistentReference
217 | }
218 | }
219 | }
220 |
221 | if inKeychainItem == nil || status != errSecSuccess {
222 | // No persistent reference was provided, or the update failed. Add a new keychain item.
223 |
224 | let attributes: [NSObject: AnyObject] = [
225 | kSecAttrService : UUID().uuidString as AnyObject,
226 | kSecValueData : passwordData as AnyObject,
227 | kSecAttrAccessible : kSecAttrAccessibleAlways,
228 | kSecClass : kSecClassGenericPassword,
229 | kSecReturnPersistentRef : kCFBooleanTrue
230 | ]
231 |
232 | var result: AnyObject?
233 | status = SecItemAdd(attributes as CFDictionary, &result)
234 |
235 | if let newPersistentReference = result as? Data , status == errSecSuccess {
236 | return newPersistentReference
237 | }
238 | }
239 | return nil
240 | }
241 |
242 | /// Remove a password from the keychain.
243 | func removePasswordWithPersistentReference(_ persistentReference: Data) {
244 | let query: [NSObject: AnyObject] = [
245 | kSecClass : kSecClassGenericPassword,
246 | kSecValuePersistentRef : persistentReference as AnyObject
247 | ]
248 |
249 | let status = SecItemDelete(query as CFDictionary)
250 | if status != errSecSuccess {
251 | simpleTunnelLog("Failed to delete a password: \(status)")
252 | }
253 | }
254 |
255 | /// Get a password from the keychain.
256 | func getPasswordWithPersistentReference(_ persistentReference: Data) -> String? {
257 | var result: String?
258 | let query: [NSObject: AnyObject] = [
259 | kSecClass : kSecClassGenericPassword,
260 | kSecReturnData : kCFBooleanTrue,
261 | kSecValuePersistentRef : persistentReference as AnyObject
262 | ]
263 |
264 | var returnValue: AnyObject?
265 | let status = SecItemCopyMatching(query as CFDictionary, &returnValue)
266 |
267 | if let passwordData = returnValue as? Data , status == errSecSuccess {
268 | result = String(data: passwordData, encoding: String.Encoding.utf8)
269 | }
270 | return result
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/SimpleTunnel/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the UIApplicationDelegate class for the SimpleTunnel app.
7 | */
8 |
9 | import UIKit
10 | import NetworkExtension
11 |
12 | /// The UIApplicationDelegate for the SimpleTunnel app.
13 | @UIApplicationMain
14 | class AppDelegate: UIResponder, UIApplicationDelegate {
15 |
16 | // MARK: Properties
17 |
18 | /// The main UIWindow object for SimpleTunnel.
19 | var window: UIWindow?
20 |
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/SimpleTunnel/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/SimpleTunnel/ClientTunnelConnection.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ClientTunnelConnection class. The ClientTunnelConnection class handles the encapsulation and decapsulation of IP packets in the client side of the SimpleTunnel tunneling protocol.
7 | */
8 |
9 | import Foundation
10 | import SimpleTunnelServices
11 | import NetworkExtension
12 |
13 | // MARK: Protocols
14 |
15 | /// The delegate protocol for ClientTunnelConnection.
16 | protocol ClientTunnelConnectionDelegate {
17 | /// Handle the connection being opened.
18 | func tunnelConnectionDidOpen(_ connection: ClientTunnelConnection, configuration: [NSObject: AnyObject])
19 | /// Handle the connection being closed.
20 | func tunnelConnectionDidClose(_ connection: ClientTunnelConnection, error: NSError?)
21 | }
22 |
23 | /// An object used to tunnel IP packets using the SimpleTunnel protocol.
24 | class ClientTunnelConnection: Connection {
25 |
26 | // MARK: Properties
27 |
28 | /// The connection delegate.
29 | let delegate: ClientTunnelConnectionDelegate
30 |
31 | /// The flow of IP packets.
32 | let packetFlow: NEPacketTunnelFlow
33 |
34 | // MARK: Initializers
35 |
36 | init(tunnel: ClientTunnel, clientPacketFlow: NEPacketTunnelFlow, connectionDelegate: ClientTunnelConnectionDelegate) {
37 | delegate = connectionDelegate
38 | packetFlow = clientPacketFlow
39 | let newConnectionIdentifier = arc4random()
40 | super.init(connectionIdentifier: Int(newConnectionIdentifier), parentTunnel: tunnel)
41 | }
42 |
43 | // MARK: Interface
44 |
45 | /// Open the connection by sending a "connection open" message to the tunnel server.
46 | func open() {
47 | guard let clientTunnel = tunnel as? ClientTunnel else { return }
48 |
49 | let properties = createMessagePropertiesForConnection(identifier, commandType: .open, extraProperties:[
50 | TunnelMessageKey.TunnelType.rawValue: TunnelLayer.ip.rawValue as AnyObject
51 | ])
52 |
53 | clientTunnel.sendMessage(properties) { error in
54 | if let error = error {
55 | self.delegate.tunnelConnectionDidClose(self, error: error)
56 | }
57 | }
58 | }
59 |
60 | /// Handle packets coming from the packet flow.
61 | func handlePackets(_ packets: [Data], protocols: [NSNumber]) {
62 | guard let clientTunnel = tunnel as? ClientTunnel else { return }
63 |
64 | let properties = createMessagePropertiesForConnection(identifier, commandType: .packets, extraProperties:[
65 | TunnelMessageKey.Packets.rawValue: packets as AnyObject,
66 | TunnelMessageKey.Protocols.rawValue: protocols as AnyObject
67 | ])
68 |
69 | clientTunnel.sendMessage(properties) { error in
70 | if let sendError = error {
71 | self.delegate.tunnelConnectionDidClose(self, error: sendError)
72 | return
73 | }
74 |
75 | // Read more packets.
76 | self.packetFlow.readPackets { inPackets, inProtocols in
77 | self.handlePackets(inPackets, protocols: inProtocols)
78 | }
79 | }
80 | }
81 |
82 | /// Make the initial readPacketsWithCompletionHandler call.
83 | func startHandlingPackets() {
84 | packetFlow.readPackets { inPackets, inProtocols in
85 | self.handlePackets(inPackets, protocols: inProtocols)
86 | }
87 | }
88 |
89 | // MARK: Connection
90 |
91 | /// Handle the event of the connection being established.
92 | override func handleOpenCompleted(_ resultCode: TunnelConnectionOpenResult, properties: [NSObject: AnyObject]) {
93 | guard resultCode == .success else {
94 | delegate.tunnelConnectionDidClose(self, error: SimpleTunnelError.badConnection as NSError)
95 | return
96 | }
97 |
98 | // Pass the tunnel network settings to the delegate.
99 | if let configuration = properties[TunnelMessageKey.Configuration.rawValue as NSString] as? [NSObject: AnyObject] {
100 | delegate.tunnelConnectionDidOpen(self, configuration: configuration)
101 | }
102 | else {
103 | delegate.tunnelConnectionDidOpen(self, configuration: [:])
104 | }
105 | }
106 |
107 | /// Send packets to the virtual interface to be injected into the IP stack.
108 | override func sendPackets(_ packets: [Data], protocols: [NSNumber]) {
109 | packetFlow.writePackets(packets, withProtocols: protocols)
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/SimpleTunnel/ConfigurationListController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ConfigurationListController class, which is responsible for controlling a list of VPN configurations.
7 | */
8 |
9 | import UIKit
10 | import NetworkExtension
11 | import SimpleTunnelServices
12 |
13 | /// The view controller object for the list of packet tunnel configurations.
14 | class ConfigurationListController: ListViewController {
15 |
16 | // MARK: Properties
17 |
18 | /// The image to display for configurations that are disabled.
19 | let disabledImage = UIImage(named: "GrayDot")
20 |
21 | /// The image to display for configurations that are enabled but are disconnected.
22 | let disconnectedImage = UIImage(named: "RedDot")
23 |
24 | /// The image to display for configurations that are active (or not disconnected).
25 | let notDisconnectedImage = UIImage(named: "GreenDot")
26 |
27 | /// A list of NEVPNManager objects for the packet tunnel configurations.
28 | var managers = [NEVPNManager]()
29 |
30 | // MARK: UIViewController
31 |
32 | /// Handle the event of the view loading into memory.
33 | override func viewDidLoad() {
34 | isAddEnabled = true
35 | super.viewDidLoad()
36 | }
37 |
38 | /// Handle the event of the view being displayed.
39 | override func viewWillAppear(_ animated: Bool) {
40 | super.viewWillAppear(animated)
41 |
42 | // Re-load all of the configurations.
43 | reloadManagers()
44 | }
45 |
46 | // MARK: Interface
47 |
48 | /// Re-load all of the packet tunnel configurations from the Network Extension preferences
49 | func reloadManagers() {
50 | NETunnelProviderManager.loadAllFromPreferences() { newManagers, error in
51 | guard let vpnManagers = newManagers else { return }
52 |
53 | self.stopObservingStatus()
54 | self.managers = vpnManagers
55 | self.observeStatus()
56 |
57 | // If there are no configurations, automatically go into editing mode.
58 | if self.managers.count == 0 && !self.isEditing {
59 | self.setEditing(true, animated: false)
60 | }
61 |
62 | self.tableView.reloadData()
63 | }
64 | }
65 |
66 | /// Register for configuration change notifications.
67 | func observeStatus() {
68 | for (index, manager) in managers.enumerated() {
69 | NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: manager.connection, queue: OperationQueue.main, using: { notification in
70 | self.tableView.reloadRows(at: [ IndexPath(row: index, section: 0) ], with: .fade)
71 | })
72 | }
73 | }
74 |
75 | /// De-register for configuration change notifications.
76 | func stopObservingStatus() {
77 | for manager in managers {
78 | NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NEVPNStatusDidChange, object: manager.connection)
79 | }
80 | }
81 |
82 | /// Unwind segue handler.
83 | @IBAction func handleUnwind(_ sender: UIStoryboardSegue) {
84 | }
85 |
86 | // MARK: ListViewController
87 |
88 | /// Set up the destination view controller of a segue away from this view controller.
89 | override func listSetupSegue(_ segue: UIStoryboardSegue, forItemAtIndex index: Int) {
90 |
91 | guard let identifier = segue.identifier else { return }
92 |
93 | switch identifier {
94 | case "start-new-configuration":
95 | // The user tapped on the "Add Configuration..." table row.
96 | guard let addEditController = segue.destination as? AddEditConfiguration else { break }
97 |
98 | addEditController.setTargetManager(nil, title: "Add Configuration")
99 |
100 | case "edit-configuration":
101 | // The user tapped on the disclosure button of a configuration while in editing mode.
102 | guard let addEditController = segue.destination as? AddEditConfiguration , index < managers.count else { break }
103 |
104 | addEditController.setTargetManager(managers[index], title: managers[index].localizedDescription)
105 |
106 | case "introspect-status":
107 | // The user tapped on a configuration while not in editing mode.
108 | guard let statusViewController = segue.destination as? StatusViewController , index < managers.count else { break }
109 |
110 | statusViewController.targetManager = managers[index]
111 |
112 | default:
113 | break
114 | }
115 | }
116 |
117 | /// The current number of configurations.
118 | override var listCount: Int {
119 | return managers.count
120 | }
121 |
122 | /// Returns the localized description of the configuration at the given index.
123 | override func listTextForItemAtIndex(_ index: Int) -> String {
124 | return managers[index].localizedDescription ?? "NoName"
125 | }
126 |
127 | /// Returns the appropriate image for the configuration at the given index.
128 | override func listImageForItemAtIndex(_ index: Int) -> UIImage? {
129 | let manager = managers[index]
130 |
131 | guard manager.isEnabled else { return disabledImage }
132 |
133 | switch manager.connection.status {
134 | case .invalid: fallthrough
135 | case .disconnected: return disconnectedImage
136 | default: return notDisconnectedImage
137 | }
138 | }
139 |
140 | /// Returns UITableViewCellAccessoryType.DisclosureIndicator.
141 | override var listAccessoryType: UITableViewCellAccessoryType {
142 | return .disclosureIndicator
143 | }
144 |
145 | /// Returns UITableViewCellAccessoryType.DetailButton.
146 | override var listEditingAccessoryType: UITableViewCellAccessoryType {
147 | return .detailButton
148 | }
149 |
150 | /// Handle a user tap on the "Delete" button for a configuration.
151 | override func listRemoveItemAtIndex(_ index: Int) {
152 |
153 | // Remove the configuration from the Network Extension preferences.
154 | managers[index].removeFromPreferences {
155 | error in
156 | if let error = error {
157 | simpleTunnelLog("Failed to remove manager: \(error)")
158 | }
159 | }
160 | managers.remove(at: index)
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/SimpleTunnel/ConfigurationParametersViewController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ConfigurationParametersViewController, which is a UITableViewController sub-class that contains a table of configuration parameters.
7 | */
8 |
9 | import UIKit
10 |
11 | /// A table view controller for a view that contains a table of configuration parameter input fields.
12 | class ConfigurationParametersViewController: UITableViewController {
13 |
14 | // MARK: Properties
15 |
16 | /// The cells to display in the table.
17 | var cells = [UITableViewCell]()
18 |
19 | // MARK: UITableViewDataSource
20 |
21 | /// Returns the number of sections in the table (always 1).
22 | override func numberOfSections(in tableView: UITableView) -> Int {
23 | return 1
24 | }
25 |
26 | /// Returns the number of cells currently in the cells list.
27 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
28 | return cells.count
29 | }
30 |
31 | /// Returns the cell at the given index in the cells list.
32 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
33 | return cells[(indexPath as NSIndexPath).row]
34 | }
35 |
36 | // MARK: Interface
37 |
38 | /// Insert or remove cells into the cells list per the current value of a SwitchCell object.
39 | func updateCellsWithDependentsOfCell(_ cell: SwitchCell) {
40 | if let indexPath = getIndexPathOfCell(cell)
41 | , !cell.dependentCells.isEmpty
42 | {
43 | let index = (indexPath as NSIndexPath).row + 1
44 | if cell.isOn {
45 | cells.insert(contentsOf: cell.dependentCells, at: index)
46 | }
47 | else {
48 | let removeRange = index..<(index + cell.dependentCells.count)
49 | cells.removeSubrange(removeRange)
50 | }
51 | }
52 | }
53 |
54 | /// Return the index of a given cell in the cells list.
55 | func getIndexPathOfCell(_ cell: UITableViewCell) -> IndexPath? {
56 | if let row = cells.index(where: { $0 == cell }) {
57 | return IndexPath(row: row, section: 0)
58 | }
59 | return nil
60 | }
61 |
62 | /// Construct a description string for a list of items, given a description of a single item.
63 | func getDescriptionForListValue(_ listValue: [AnyObject]?, itemDescription: String, placeHolder: String = "Optional") -> String {
64 | if let list = listValue , !list.isEmpty {
65 | return "\(list.count) \(itemDescription)" + (list.count > 1 ? "s" : "")
66 | } else {
67 | return placeHolder
68 | }
69 | }
70 |
71 | /// Construct a description string for a list of strings, given a description of a single string.
72 | func getDescriptionForStringList(_ stringList: [String]?, itemDescription: String, placeHolder: String = "Optional") -> String {
73 | if let list = stringList , !list.isEmpty {
74 |
75 | return (list.count <= 3 ? list.joined(separator: ", ") : "\(list.count) \(itemDescription)s")
76 | } else {
77 | return placeHolder
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/SimpleTunnel/ConnectionRuleAddEditController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ConnectionRuleAddEditController class, which controls a view that is used to add or edit a Connect On Demand connection rule.
7 | */
8 |
9 | import UIKit
10 | import NetworkExtension
11 |
12 | // MARK: Extensions
13 |
14 | /// Make NEEvaluateConnectionRuleAction convertible to a string.
15 | extension NEEvaluateConnectionRuleAction: CustomStringConvertible {
16 | public var description: String {
17 | switch self {
18 | case .connectIfNeeded: return "Connect If Needed"
19 | case .neverConnect: return "Never Connect"
20 | }
21 | }
22 | }
23 |
24 | /// A view controller object for a view that contains input fields used to define a Evaluate Connection rule.
25 | class ConnectionRuleAddEditController: ConfigurationParametersViewController {
26 |
27 | // MARK: Properties
28 |
29 | /// A table cell that when tapped allows the user to select the action for the rule.
30 | @IBOutlet weak var actionCell: UITableViewCell!
31 |
32 | /// A table cell that when tapped allows the user to set the DNS Domains match condition for the rule.
33 | @IBOutlet weak var domainsCell: UITableViewCell!
34 |
35 | /// A table cell that when tapped allows the user to set the DNS Servers to be used when the rule matches.
36 | @IBOutlet weak var requiredDNSCell: UITableViewCell!
37 |
38 | /// A table cell that contains a text field where the user inputs the rule's probe URL.
39 | @IBOutlet weak var requiredURLProbeCell: TextFieldCell!
40 |
41 | /// The connection rule being edited or added.
42 | var targetRule = NEEvaluateConnectionRule(matchDomains: [], andAction: .connectIfNeeded)
43 |
44 | /// A block to execute when the user is finished editing the connection rule.
45 | var addRuleHandler: (NEEvaluateConnectionRule) -> Void = { rule in return }
46 |
47 | // MARK: UIViewController
48 |
49 | /// Handle the event when the view is loaded into memory.
50 | override func viewDidLoad() {
51 | super.viewDidLoad()
52 |
53 | cells = [
54 | actionCell,
55 | domainsCell,
56 | requiredDNSCell,
57 | requiredURLProbeCell
58 | ].flatMap { $0 }
59 |
60 | requiredURLProbeCell.valueChanged = {
61 | if let enteredText = self.requiredURLProbeCell.textField.text {
62 | self.targetRule.probeURL = URL(string: enteredText)
63 | }
64 | else {
65 | self.targetRule.probeURL = nil
66 | }
67 | }
68 | }
69 |
70 | /// Handle the event when the view is being displayed.
71 | override func viewWillAppear(_ animated: Bool) {
72 | super.viewWillAppear(animated)
73 |
74 | tableView.reloadData()
75 |
76 | actionCell.detailTextLabel?.text = targetRule.action.description
77 |
78 | domainsCell.detailTextLabel?.text = getDescriptionForStringList(targetRule.matchDomains, itemDescription: "domain", placeHolder: "Required")
79 |
80 | requiredDNSCell.detailTextLabel?.text = getDescriptionForStringList(targetRule.useDNSServers, itemDescription: "server")
81 |
82 | requiredURLProbeCell.textField.text = targetRule.probeURL?.absoluteString ?? nil
83 | }
84 |
85 | /// Set up the destination view controller for a segue away from this view controller.
86 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
87 | guard let identifier = segue.identifier else { return }
88 |
89 | switch identifier {
90 | case "edit-use-dns-servers":
91 | // The user tapped on the Use DNS Servers table cell.
92 | guard let stringListController = segue.destination as? StringListController else { break }
93 |
94 | stringListController.setTargetStrings(targetRule.useDNSServers, title: "Use DNS Servers", addTitle: "Add a server address...") { newAddresses in
95 | self.targetRule.useDNSServers = newAddresses
96 | }
97 |
98 | case "edit-connection-rule-action":
99 | // The user tapped on the Action table cell.
100 | guard let enumController = segue.destination as? EnumPickerController else { break }
101 |
102 | let enumValues: [NEEvaluateConnectionRuleAction] = [ .connectIfNeeded, .neverConnect, ],
103 | stringValues = enumValues.flatMap { $0.description },
104 | currentSelection = enumValues.index { $0 == targetRule.action }
105 |
106 | enumController.setValues(stringValues, title: "Action", currentSelection: currentSelection) { newRow in
107 | let newAction = enumValues[newRow]
108 | guard self.targetRule.action != newAction else { return }
109 |
110 | let newRule = NEEvaluateConnectionRule(matchDomains: self.targetRule.matchDomains, andAction: newAction)
111 | newRule.useDNSServers = self.targetRule.useDNSServers
112 | newRule.probeURL = self.targetRule.probeURL
113 |
114 | self.targetRule = newRule
115 | }
116 |
117 | case "edit-connection-rule-match-domains":
118 | // The user tapped on the Match Domains table cell.
119 | guard let stringListController = segue.destination as? StringListController else { break }
120 |
121 | stringListController.setTargetStrings(targetRule.matchDomains, title: "Match Domains", addTitle: "Add a domain...") { newStrings in
122 | let newRule = NEEvaluateConnectionRule(matchDomains: newStrings, andAction: self.targetRule.action)
123 | newRule.useDNSServers = self.targetRule.useDNSServers
124 | newRule.probeURL = self.targetRule.probeURL
125 | self.targetRule = newRule
126 | }
127 |
128 | default:
129 | break
130 | }
131 | }
132 |
133 | // MARK: Interface
134 |
135 | /// Set the target connection rule, the title of the view, and the block to execute when the user if finished editing the rule.
136 | func setTargetRule(_ rule: NEEvaluateConnectionRule?, title: String, saveHandler: @escaping (NEEvaluateConnectionRule) -> Void) {
137 | if let newRule = rule {
138 | targetRule = newRule
139 | } else {
140 | targetRule = NEEvaluateConnectionRule(matchDomains: [], andAction: .connectIfNeeded)
141 | }
142 | navigationItem.title = title
143 | addRuleHandler = saveHandler
144 | }
145 |
146 | /// Handle the user tapping on the "Done" button.
147 | @IBAction func saveTargetRule(_ sender: AnyObject) {
148 | addRuleHandler(targetRule)
149 | performSegue(withIdentifier: "save-connection-rule", sender: sender)
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/SimpleTunnel/ConnectionRuleListController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ConnectionRuleListController class, which controls a list of Connect On Demand connection rules.
7 | */
8 |
9 | import UIKit
10 | import NetworkExtension
11 |
12 | /// A view controller object for a view that displays a editable list of Evaluate Connection rules.
13 | class ConnectionRuleListController: ListViewController {
14 |
15 | // MARK: Properties
16 |
17 | /// The Connect On Demand rule containing the connection rules being displayed.
18 | var targetRule = NEOnDemandRuleEvaluateConnection()
19 |
20 | /// The number of connection rules.
21 | override var listCount: Int {
22 | return targetRule.connectionRules?.count ?? 0
23 | }
24 |
25 | /// Returns UITableViewCellAccessoryType.DetailButton.
26 | override var listAccessoryType: UITableViewCellAccessoryType {
27 | return .detailButton
28 | }
29 |
30 | /// Returns UITableViewCellAccessoryType.DetailButton.
31 | override var listEditingAccessoryType: UITableViewCellAccessoryType {
32 | return .detailButton
33 | }
34 |
35 | /// The text to display in the "add a new item" table cell.
36 | override var listAddButtonText: String {
37 | return "Add Connection Rule..."
38 | }
39 |
40 | // MARK: UIViewController
41 |
42 | /// Handle the event where the view is loaded into memory.
43 | override func viewDidLoad() {
44 | isAddEnabled = true
45 | isAlwaysEditing = true
46 | super.viewDidLoad()
47 | }
48 |
49 | // MARK: ListViewController
50 |
51 | /// Set up the destination view controller for a segue triggered by the user tapping on a cell.
52 | override func listSetupSegue(_ segue: UIStoryboardSegue, forItemAtIndex index: Int) {
53 | guard let identifier = segue.identifier,
54 | let ruleAddEditController = segue.destination as? ConnectionRuleAddEditController
55 | else { return }
56 |
57 | switch identifier {
58 | case "edit-connection-rule":
59 | // The user tapped on a rule in the connection rule list.
60 | guard let connectionRule = targetRule.connectionRules?[index] else { break }
61 | ruleAddEditController.setTargetRule(connectionRule, title: "Connection Rule") { newRule in
62 | self.targetRule.connectionRules?[index] = newRule
63 | }
64 |
65 | case "add-connection-rule":
66 | // The user tapped on the "add a new rule" cell.
67 | ruleAddEditController.setTargetRule(nil, title: "Add Connection Rule") { newRule in
68 | self.targetRule.connectionRules?.append(newRule)
69 | }
70 |
71 | default:
72 | break
73 | }
74 | }
75 |
76 | /// Return the description of the rule at the given index in the list.
77 | override func listTextForItemAtIndex(_ index: Int) -> String {
78 | return targetRule.connectionRules?[index].action.description ?? ""
79 | }
80 |
81 | /// Remove the rule at the given index in the list.
82 | override func listRemoveItemAtIndex(_ index: Int) {
83 | targetRule.connectionRules?.remove(at: index)
84 | }
85 |
86 | // MARK: Interface
87 |
88 | /// Handle an unwind segue back to this view controller.
89 | @IBAction func handleUnwind(_ sender: UIStoryboardSegue) {
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/SimpleTunnel/ContentFilterController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ContentFilterController class, which controls a view used to start and stop a content filter, and display current filtering rules.
7 | */
8 |
9 | import UIKit
10 | import NetworkExtension
11 | import SimpleTunnelServices
12 |
13 | /// A view controller object for a view that displays Content Filter configuration and rules.
14 | class ContentFilterController: UITableViewController {
15 |
16 | // MARK: Properties
17 |
18 | /// A table view cell that contains the status of the filter.
19 | @IBOutlet weak var statusCell: SwitchCell!
20 |
21 | /// A table view cell that contains the
22 | @IBOutlet weak var rulesServerCell: TextFieldCell!
23 |
24 | /// A variable to pass as the context to addObserver()
25 | var rulesContext = 0
26 |
27 | /// The current list of filtering rules
28 | var currentRules = [(String, String)]()
29 |
30 | // MARK: Initializers
31 |
32 | deinit {
33 | var context = self
34 | FilterUtilities.defaults?.removeObserver(self, forKeyPath: "rules", context:&context)
35 | }
36 |
37 | // MARK: UIViewController
38 |
39 | /// Handle the event where the view is loaded into memory.
40 | override func viewDidLoad() {
41 | FilterUtilities.defaults?.addObserver(self, forKeyPath: "rules", options: NSKeyValueObservingOptions.initial, context:&rulesContext)
42 |
43 | statusCell.valueChanged = {
44 | if self.statusCell.isOn && NEFilterManager.shared().providerConfiguration == nil {
45 | let newConfiguration = NEFilterProviderConfiguration()
46 | newConfiguration.username = "TestUser"
47 | newConfiguration.organization = "Acme Inc."
48 | newConfiguration.filterBrowsers = true
49 | newConfiguration.filterSockets = true
50 | newConfiguration.serverAddress = self.rulesServerCell.textField.text ?? "my.great.filter.server"
51 | NEFilterManager.shared().providerConfiguration = newConfiguration
52 | }
53 | NEFilterManager.shared().isEnabled = self.statusCell.isOn
54 | NEFilterManager.shared().saveToPreferences { error in
55 | if let saveError = error {
56 | simpleTunnelLog("Failed to save the filter configuration: \(saveError)")
57 | self.statusCell.isOn = false
58 | return
59 | }
60 | self.rulesServerCell.textField.text = NEFilterManager.shared().providerConfiguration?.serverAddress
61 | FilterUtilities.defaults?.setValue(NEFilterManager.shared().providerConfiguration?.serverAddress, forKey: "serverAddress")
62 | }
63 | }
64 |
65 | rulesServerCell.valueChanged = {
66 | guard let serverIPAddress = self.rulesServerCell.textField.text , !serverIPAddress.isEmpty else { return }
67 |
68 | NEFilterManager.shared().providerConfiguration?.serverAddress = serverIPAddress
69 | NEFilterManager.shared().saveToPreferences { error in
70 | if let saveError = error {
71 | simpleTunnelLog("Failed to save the filter configuration: \(saveError)")
72 | return
73 | }
74 |
75 | FilterUtilities.defaults?.setValue(serverIPAddress, forKey: "serverAddress")
76 | }
77 | }
78 | }
79 |
80 | /// Handle the event where the view is loaded into memory.
81 | override func viewWillAppear(_ animated: Bool) {
82 | super.viewWillAppear(animated)
83 | NEFilterManager.shared().loadFromPreferences { error in
84 | if let loadError = error {
85 | simpleTunnelLog("Failed to load the filter configuration: \(loadError)")
86 | self.statusCell.isOn = false
87 | return
88 | }
89 |
90 | self.statusCell.isOn = NEFilterManager.shared().isEnabled
91 | self.rulesServerCell.textField.text = NEFilterManager.shared().providerConfiguration?.serverAddress
92 |
93 | self.reloadRules()
94 | }
95 | }
96 |
97 | // MARK: NSObject
98 |
99 | /// Handle changes to the rules
100 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
101 | if context == &rulesContext && keyPath == "rules" {
102 | reloadRules()
103 | } else {
104 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
105 | }
106 | }
107 |
108 | // MARK: UITableViewDataSource
109 |
110 | /// Return the number of sections to display.
111 | override func numberOfSections(in tableView: UITableView) -> Int {
112 | return !currentRules.isEmpty ? 2 : 1
113 | }
114 |
115 | /// Return the number of rows in a section.
116 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
117 | switch section {
118 | case 0: return 2
119 | case 1: return currentRules.count
120 | default: return 0
121 | }
122 | }
123 |
124 | /// Return the cell for given index path.
125 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
126 | if (indexPath as NSIndexPath).section == 0 {
127 | return (indexPath as NSIndexPath).row == 0 ? statusCell : rulesServerCell
128 | }
129 | else if let cell = tableView.dequeueReusableCell(withIdentifier: "rule-cell") {
130 | let (hostString, actionString) = currentRules[(indexPath as NSIndexPath).row]
131 | cell.textLabel?.text = hostString
132 | cell.detailTextLabel?.text = actionString
133 | return cell
134 | }
135 | else {
136 | return UITableViewCell()
137 | }
138 | }
139 |
140 | /// Return the title for a section in the table.
141 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
142 | return section == 1 ? "Current Rules" : nil
143 | }
144 |
145 | // MARK: Interface
146 |
147 | /// Re-load the current filerting rules from the defaults.
148 | func reloadRules() {
149 | currentRules = [(String, String)]()
150 |
151 | guard let rules = FilterUtilities.defaults?.object(forKey: "rules") as? [String : [String : AnyObject]] else { return }
152 |
153 | for (hostname, ruleInfo) in rules {
154 | guard let ruleActionNum = ruleInfo["kRule"] as? Int,
155 | let ruleAction = FilterRuleAction(rawValue: ruleActionNum)
156 | else { continue }
157 |
158 | currentRules.append((hostname as String, ruleAction.description))
159 | }
160 | tableView.reloadData()
161 | }
162 |
163 | /// Download a new set of filtering rules from the server.
164 | @IBAction func fetchRulesButtonTouchUpInside(_ sender: UIButton) {
165 | FilterUtilities.fetchRulesFromServer(NEFilterManager.shared().providerConfiguration?.serverAddress)
166 | reloadRules()
167 | }
168 |
169 | }
170 |
--------------------------------------------------------------------------------
/SimpleTunnel/EnumPickerController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the EnumPickerController class, which is used to control a view containing a UIPickerView.
7 | */
8 |
9 | import UIKit
10 |
11 | /// A view controller for a view that contains a picker view for selecting a value in an enum.
12 | class EnumPickerController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
13 |
14 | // MARK: Properties
15 |
16 | /// The values that can be selected in the picker view.
17 | var enumValues = [String]()
18 |
19 | /// The index of the currently-selected value.
20 | var currentValue: Int?
21 |
22 | /// The picker view.
23 | @IBOutlet weak var enumPicker: UIPickerView!
24 |
25 | /// The title to display for the view.
26 | var enumTitle: String?
27 |
28 | /// A block to execute when the selected value changes.
29 | var selectionChangeHandler: (Int) -> Void = { newRow in return }
30 |
31 | // MARK: Interface
32 |
33 | /// Set the enum values to display, the title of the view, the index of the currently-selected value, and a block to execute when the selected value changes.
34 | func setValues(_ values: [String], title: String, currentSelection: Int?, selectionChanged: @escaping (Int) -> Void) {
35 | enumValues = values
36 | enumTitle = title
37 | currentValue = currentSelection
38 | selectionChangeHandler = selectionChanged
39 | }
40 |
41 | // MARK: UIViewController
42 |
43 | /// Handle the event when the view is being displayed.
44 | override func viewWillAppear(_ animated: Bool) {
45 | super.viewWillAppear(animated)
46 | navigationItem.title = enumTitle
47 |
48 | enumPicker.reloadAllComponents()
49 | if let row = currentValue {
50 | enumPicker.selectRow(row, inComponent: 0, animated: false)
51 | }
52 | }
53 |
54 | // MARK: UIPickerViewDataSource
55 |
56 | /// Return the number of components in the picker, always returns 1.
57 | func numberOfComponents(in pickerView: UIPickerView) -> Int {
58 | return 1
59 | }
60 |
61 | /// Returns the number of enum values.
62 | func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
63 | return enumValues.count
64 | }
65 |
66 | // MARK: UIPickerViewDelegate
67 |
68 | /// Returns the enum value at the given row.
69 | func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
70 | return NSAttributedString(string: enumValues[row])
71 | }
72 |
73 | /// Handle the user selecting a value in the picker.
74 | func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
75 | selectionChangeHandler(row)
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/SimpleTunnel/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/SimpleTunnel/Images.xcassets/GrayDot.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "GrayDot@1x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "GrayDot.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "GrayDot@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/SimpleTunnel/Images.xcassets/GrayDot.imageset/GrayDot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/below/SimpleTunnel/4fc75ccc8240e31a704ddad7c034fead15bc912e/SimpleTunnel/Images.xcassets/GrayDot.imageset/GrayDot.png
--------------------------------------------------------------------------------
/SimpleTunnel/Images.xcassets/GrayDot.imageset/GrayDot@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/below/SimpleTunnel/4fc75ccc8240e31a704ddad7c034fead15bc912e/SimpleTunnel/Images.xcassets/GrayDot.imageset/GrayDot@1x.png
--------------------------------------------------------------------------------
/SimpleTunnel/Images.xcassets/GrayDot.imageset/GrayDot@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/below/SimpleTunnel/4fc75ccc8240e31a704ddad7c034fead15bc912e/SimpleTunnel/Images.xcassets/GrayDot.imageset/GrayDot@3x.png
--------------------------------------------------------------------------------
/SimpleTunnel/Images.xcassets/GreenDot.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "GreenDot@1x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "GreenDot.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "GreenDot@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/SimpleTunnel/Images.xcassets/GreenDot.imageset/GreenDot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/below/SimpleTunnel/4fc75ccc8240e31a704ddad7c034fead15bc912e/SimpleTunnel/Images.xcassets/GreenDot.imageset/GreenDot.png
--------------------------------------------------------------------------------
/SimpleTunnel/Images.xcassets/GreenDot.imageset/GreenDot@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/below/SimpleTunnel/4fc75ccc8240e31a704ddad7c034fead15bc912e/SimpleTunnel/Images.xcassets/GreenDot.imageset/GreenDot@1x.png
--------------------------------------------------------------------------------
/SimpleTunnel/Images.xcassets/GreenDot.imageset/GreenDot@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/below/SimpleTunnel/4fc75ccc8240e31a704ddad7c034fead15bc912e/SimpleTunnel/Images.xcassets/GreenDot.imageset/GreenDot@3x.png
--------------------------------------------------------------------------------
/SimpleTunnel/Images.xcassets/RedDot.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "RedDot@1x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "RedDot.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "RedDot@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/SimpleTunnel/Images.xcassets/RedDot.imageset/RedDot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/below/SimpleTunnel/4fc75ccc8240e31a704ddad7c034fead15bc912e/SimpleTunnel/Images.xcassets/RedDot.imageset/RedDot.png
--------------------------------------------------------------------------------
/SimpleTunnel/Images.xcassets/RedDot.imageset/RedDot@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/below/SimpleTunnel/4fc75ccc8240e31a704ddad7c034fead15bc912e/SimpleTunnel/Images.xcassets/RedDot.imageset/RedDot@1x.png
--------------------------------------------------------------------------------
/SimpleTunnel/Images.xcassets/RedDot.imageset/RedDot@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/below/SimpleTunnel/4fc75ccc8240e31a704ddad7c034fead15bc912e/SimpleTunnel/Images.xcassets/RedDot.imageset/RedDot@3x.png
--------------------------------------------------------------------------------
/SimpleTunnel/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 | UIInterfaceOrientationPortraitUpsideDown
39 |
40 | UISupportedInterfaceOrientations~ipad
41 |
42 | UIInterfaceOrientationPortrait
43 | UIInterfaceOrientationPortraitUpsideDown
44 | UIInterfaceOrientationLandscapeLeft
45 | UIInterfaceOrientationLandscapeRight
46 |
47 | UIVPNPlugin
48 |
49 | ${PLUGIN_BUNDLE_ID}
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/SimpleTunnel/ListViewController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ListViewController class, which is responsible for controlling a list items.
7 | */
8 |
9 | import UIKit
10 |
11 | /// A view controller base class for a view that displays a list of items in table form.
12 | class ListViewController: UITableViewController {
13 |
14 | // MARK: Properties
15 |
16 | /// A flag indicating that UI controls for adding an item to the list should be displayed.
17 | var isAddEnabled = false
18 |
19 | /// A flag indicating that UI controls for adding an item to the list should always be displayed.
20 | var isAlwaysEditing = false
21 |
22 | /// A custom cell for adding items to the list.
23 | var addCell: UITableViewCell?
24 |
25 | /// The number of items currently in the list.
26 | var listCount: Int { return 0 }
27 |
28 | /// The type of table view cell accessory image to display for each item in the list.
29 | var listAccessoryType: UITableViewCellAccessoryType {
30 | return .none
31 | }
32 |
33 | /// The type of table view cell accessory image to display for each item in the list while the list is being edited.
34 | var listEditingAccessoryType: UITableViewCellAccessoryType {
35 | return .none
36 | }
37 |
38 | /// The type of selection feedback to display items in the list.
39 | var listCellSelectionStyle: UITableViewCellSelectionStyle {
40 | return .default
41 | }
42 |
43 | /// The text to display in the "add a new item" cell.
44 | var listAddButtonText: String {
45 | return "Add Configuration..."
46 | }
47 |
48 | // MARK: Initializers
49 |
50 | required init?(coder aDecoder: NSCoder) {
51 | super.init(coder: aDecoder)
52 | }
53 |
54 | init() {
55 | super.init(style: .grouped)
56 | }
57 |
58 | // MARK: UIViewController
59 |
60 | /// Handle the event of the view being loaded into memory.
61 | override func viewDidLoad() {
62 | super.viewDidLoad()
63 | if isAlwaysEditing {
64 | tableView.isEditing = true
65 | }
66 | else {
67 | navigationItem.rightBarButtonItem = editButtonItem
68 | }
69 | }
70 |
71 | /// Handle the event of the view being displayed.
72 | override func viewWillAppear(_ animated: Bool) {
73 | super.viewWillAppear(animated)
74 | tableView.reloadData()
75 | }
76 |
77 | /// Prepare for a segue away from this view controller.
78 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
79 | guard let cell = sender as? UITableViewCell,
80 | let indexPath = tableView.indexPath(for: cell)
81 | else { return }
82 |
83 | listSetupSegue(segue, forItemAtIndex: (indexPath as NSIndexPath).row)
84 | }
85 |
86 | // MARK: UITableView
87 |
88 | /// Enable editing mode on the UITableView.
89 | override func setEditing(_ editing: Bool, animated: Bool) {
90 | super.setEditing(editing, animated: animated)
91 |
92 | if isAddEnabled {
93 | // Insert or delete the last row in the table (i.e., the "add a new item" row).
94 | let indexPath = IndexPath(item: listCount, section: 0)
95 | if editing {
96 | tableView.insertRows(at: [indexPath], with: .bottom)
97 | }
98 | else {
99 | tableView.deleteRows(at: [indexPath], with: .bottom)
100 | }
101 | }
102 | }
103 |
104 | // MARK: UITableViewDataSource
105 |
106 | /// Always returns 1.
107 | override func numberOfSections(in tableView: UITableView) -> Int {
108 | return 1
109 | }
110 |
111 | /// Returns the number of items in the list, incremented by 1 if the list is in edit mode.
112 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
113 | var count = listCount
114 | if tableView.isEditing {
115 | count += 1
116 | }
117 | return count
118 | }
119 |
120 | /// Returns a cell displaying details about the item at the given index path.
121 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
122 | var cell = UITableViewCell()
123 | if (indexPath as NSIndexPath).item < listCount {
124 | // Return a cell containing details about an item in the list.
125 | if let itemCell = tableView.dequeueReusableCell(withIdentifier: "item-cell") {
126 | cell = itemCell
127 | cell.textLabel?.text = listTextForItemAtIndex((indexPath as NSIndexPath).row)
128 | cell.accessoryType = listAccessoryType
129 | cell.editingAccessoryType = listEditingAccessoryType
130 | cell.imageView?.image = listImageForItemAtIndex((indexPath as NSIndexPath).row)
131 | }
132 | }
133 | else if tableView.isEditing && (indexPath as NSIndexPath).item == listCount {
134 | // The list is in edit mode, return the appropriate "add" cell.
135 | if addCell != nil {
136 | cell = addCell!
137 | }
138 | else if let addButtonCell = tableView.dequeueReusableCell(withIdentifier: "add-button") {
139 | cell = addButtonCell
140 | cell.textLabel?.text = listAddButtonText
141 | cell.editingAccessoryType = .none
142 | }
143 | }
144 |
145 | return cell
146 | }
147 |
148 | /// Always returns true, all cells can be edited in the list
149 | override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
150 | return true
151 | }
152 |
153 | /// Make changes to the list per the given editing style and target row.
154 | override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
155 | switch editingStyle {
156 | case .delete:
157 | listRemoveItemAtIndex((indexPath as NSIndexPath).row)
158 | tableView.deleteRows(at: [ indexPath ], with: .bottom)
159 | default:
160 | break
161 | }
162 | }
163 |
164 | // MARK: UITableViewDelegate
165 |
166 | /// Return the editing style for a row in the table. Returns "Delete" editing style for all items except for the last item, which uses the "Insert" style.
167 | override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
168 | if (indexPath as NSIndexPath).item < listCount {
169 | return .delete
170 | }
171 | else {
172 | return .insert
173 | }
174 | }
175 |
176 | // MARK: Interface
177 |
178 | /// Prepare to segue away from this view controller as the result of the user tapping on an item in the list.
179 | func listSetupSegue(_ segue: UIStoryboardSegue, forItemAtIndex index: Int) {
180 | }
181 |
182 | /// Insert a new item into the list.
183 | func listInsertItemAtIndex(_ index: Int) {
184 | tableView.insertRows(at: [ IndexPath(row: index, section: 0) ], with: .bottom)
185 | }
186 |
187 | /// Get the text to display for an item in the list.
188 | func listTextForItemAtIndex(_ index: Int) -> String {
189 | return ""
190 | }
191 |
192 | /// Get the image to display for an item in the list.
193 | func listImageForItemAtIndex(_ index: Int) -> UIImage? {
194 | return nil
195 | }
196 |
197 | /// Remove an item from the list.
198 | func listRemoveItemAtIndex(_ index: Int) {
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/SimpleTunnel/OnDemandRuleAddEditController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the OnDemandRuleAddEditController class, which controls a view that is used to create or edit a Connect On Demand rule.
7 | */
8 |
9 | import UIKit
10 | import NetworkExtension
11 |
12 | // MARK: Extensions
13 |
14 | /// Make NEOnDemandRuleAction convertible to a string
15 | extension NEOnDemandRuleAction: CustomStringConvertible {
16 | public var description: String {
17 | switch self {
18 | case .connect: return "Connect"
19 | case .disconnect: return "Disconnect"
20 | case .ignore: return "Maintain"
21 | case .evaluateConnection: return "Evaluate Connection"
22 | }
23 | }
24 | }
25 |
26 | /// Make NEOnDemandRuleInterfaceType convertible to a string
27 | extension NEOnDemandRuleInterfaceType: CustomStringConvertible {
28 | public var description: String {
29 | switch self {
30 | case .any: return "Any"
31 | case .wiFi: return "Wi-Fi"
32 | case .cellular: return "Cellular"
33 | default: return ""
34 | }
35 | }
36 | }
37 |
38 | /// A view controller object containing input fields used to create or edit a Connect On Demand rule.
39 | class OnDemandRuleAddEditController: ConfigurationParametersViewController {
40 |
41 | // MARK: Properties
42 |
43 | /// A table view cell that when tapped allows the user to select the rule action.
44 | @IBOutlet weak var actionCell: UITableViewCell!
45 |
46 | /// A table view cell that when tapped allows the user to define the DNS Search Domains match condition.
47 | @IBOutlet weak var DNSSearchDomainsCell: UITableViewCell!
48 |
49 | /// A table view cell that when tapped allows the user to define the DNS Server match condition.
50 | @IBOutlet weak var DNSServersCell: UITableViewCell!
51 |
52 | /// A table view cell that when tapped allows the user to define the network interface type match condition.
53 | @IBOutlet weak var interfaceTypeCell: UITableViewCell!
54 |
55 | /// A table view cell that when tapped allows the user to define the SSID match condition.
56 | @IBOutlet weak var SSIDsCell: UITableViewCell!
57 |
58 | /// A table view cell that when tapped allows the user to define the URL probe match condition.
59 | @IBOutlet weak var URLProbeCell: TextFieldCell!
60 |
61 | /// A table view cell that when tapped allows the user to define the connection match rules.
62 | @IBOutlet weak var connectionRulesCell: UITableViewCell!
63 |
64 | /// The Connect On Demand rule being added or edited.
65 | var targetRule: NEOnDemandRule = NEOnDemandRuleEvaluateConnection()
66 |
67 | /// The block to execute when the user finishes editing the rule.
68 | var addRuleHandler: (NEOnDemandRule) -> Void = { rule in return }
69 |
70 | // MARK: UIViewController
71 |
72 | /// Handle the event when the view is loaded into memory.
73 | override func viewDidLoad() {
74 | super.viewDidLoad()
75 |
76 | // Set up the table cells.
77 |
78 | cells = [
79 | actionCell,
80 | DNSSearchDomainsCell,
81 | DNSServersCell,
82 | interfaceTypeCell,
83 | SSIDsCell,
84 | URLProbeCell
85 | ].flatMap { $0 }
86 |
87 | URLProbeCell.valueChanged = {
88 | if let enteredText = self.URLProbeCell.textField.text {
89 | self.targetRule.probeURL = URL(string: enteredText)
90 | }
91 | else {
92 | self.targetRule.probeURL = nil
93 | }
94 | }
95 | }
96 |
97 | /// Handle the event when the view is being displayed.
98 | override func viewWillAppear(_ animated: Bool) {
99 | super.viewWillAppear(animated)
100 |
101 | tableView.reloadData()
102 |
103 | // Set the cell contents per the current rule settings.
104 |
105 | updateConnectionRulesCell()
106 |
107 | actionCell.detailTextLabel?.text = targetRule.action.description
108 |
109 | DNSSearchDomainsCell.detailTextLabel?.text = getDescriptionForStringList(targetRule.dnsSearchDomainMatch, itemDescription: "domain")
110 |
111 | DNSServersCell.detailTextLabel?.text = getDescriptionForStringList(targetRule.dnsServerAddressMatch, itemDescription: "server")
112 |
113 | interfaceTypeCell.detailTextLabel?.text = targetRule.interfaceTypeMatch.description
114 |
115 | SSIDsCell.detailTextLabel?.text = getDescriptionForStringList(targetRule.ssidMatch, itemDescription: "SSID")
116 |
117 | if let evaluateRule = targetRule as? NEOnDemandRuleEvaluateConnection {
118 | connectionRulesCell.detailTextLabel?.text = getDescriptionForListValue(evaluateRule.connectionRules, itemDescription: "rule", placeHolder: "Required")
119 | }
120 |
121 | URLProbeCell.textField.text = targetRule.probeURL?.absoluteString ?? nil
122 | }
123 |
124 | /// Set up the destination view controller of a segue away from this view controller.
125 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
126 | guard let identifier = segue.identifier else { return }
127 |
128 | switch identifier {
129 | case "edit-ssids":
130 | // The user tapped on the SSIDs cell.
131 | guard let stringListController = segue.destination as? StringListController else { break }
132 |
133 | stringListController.setTargetStrings(targetRule.ssidMatch, title: "Match SSIDs", addTitle: "Add a SSID...") { newSSIDs in
134 | self.targetRule.ssidMatch = newSSIDs
135 | }
136 |
137 | case "edit-interface-type-match":
138 | // The user tapped on the Interface Type cell.
139 | guard let enumController = segue.destination as? EnumPickerController else { break }
140 |
141 | let enumValues: [NEOnDemandRuleInterfaceType] = [ .any, .wiFi, .cellular ],
142 | stringValues = enumValues.flatMap { $0.description },
143 | currentSelection = enumValues.index { $0 == targetRule.interfaceTypeMatch }
144 |
145 | enumController.setValues(stringValues, title: "Interface Type", currentSelection: currentSelection) { newRow in
146 | self.targetRule.interfaceTypeMatch = enumValues[newRow]
147 | }
148 |
149 | case "edit-dns-servers":
150 | // The user tapped on the DNS Servers cell.
151 | guard let stringListController = segue.destination as? StringListController else { break }
152 |
153 | stringListController.setTargetStrings(targetRule.dnsServerAddressMatch, title: "Match DNS Servers", addTitle: "Add a server address...") { newAddresses in
154 | self.targetRule.dnsServerAddressMatch = newAddresses
155 | }
156 |
157 | case "edit-dns-search-domains":
158 | // The user tapped on the DNS Search Domains cell.
159 | guard let stringListController = segue.destination as? StringListController else { break }
160 |
161 | stringListController.setTargetStrings(targetRule.dnsSearchDomainMatch, title: "Match DNS Search Domains", addTitle: "Add a search domain...") { newStrings in
162 | self.targetRule.dnsSearchDomainMatch = newStrings
163 | }
164 |
165 | case "edit-on-demand-action":
166 | // The user tapped on the Action cell.
167 | guard let enumController = segue.destination as? EnumPickerController else { break }
168 |
169 | let enumValues: [NEOnDemandRuleAction] = [ .evaluateConnection, .disconnect, .connect, .ignore ],
170 | stringValues = enumValues.flatMap { $0.description },
171 | currentSelection = enumValues.index { $0 == targetRule.action }
172 |
173 | enumController.setValues(stringValues, title: "Action", currentSelection: currentSelection) { newRow in
174 | self.changeTargetRuleType(enumValues[newRow])
175 | }
176 |
177 | case "edit-connection-rules":
178 | // The user tapped on the Connection Rules cell.
179 | guard let connRuleListController = segue.destination as? ConnectionRuleListController,
180 | let rule = targetRule as? NEOnDemandRuleEvaluateConnection
181 | else { break }
182 |
183 | if rule.connectionRules == nil {
184 | rule.connectionRules = []
185 | }
186 |
187 | connRuleListController.targetRule = rule
188 |
189 | default:
190 | break
191 | }
192 | }
193 |
194 | /// Set the target rule to add or edit, the title of the view, and the block to execute when the user is finished editing the rule.
195 | func setTargetRule(_ rule: NEOnDemandRule?, title: String, saveRuleHandler: @escaping (NEOnDemandRule) -> Void) {
196 | if let newRule = rule {
197 | // Edit a copy of the given rule.
198 | targetRule = newRule.copy() as! NEOnDemandRule
199 | } else {
200 | targetRule = NEOnDemandRuleEvaluateConnection()
201 | }
202 | navigationItem.title = title
203 | addRuleHandler = saveRuleHandler
204 | }
205 |
206 | /// Set the target rule to a new rule with all the same match conditions as the current target rule, but with a different action.
207 | func changeTargetRuleType(_ newAction: NEOnDemandRuleAction) {
208 | guard targetRule.action != newAction else { return }
209 | let newRule: NEOnDemandRule
210 |
211 | switch newAction {
212 | case .evaluateConnection:
213 | newRule = NEOnDemandRuleEvaluateConnection()
214 |
215 | case .connect:
216 | newRule = NEOnDemandRuleConnect()
217 |
218 | case .disconnect:
219 | newRule = NEOnDemandRuleDisconnect()
220 |
221 | case .ignore:
222 | newRule = NEOnDemandRuleIgnore()
223 | }
224 |
225 | newRule.dnsSearchDomainMatch = targetRule.dnsSearchDomainMatch
226 | newRule.dnsServerAddressMatch = targetRule.dnsServerAddressMatch
227 | newRule.interfaceTypeMatch = targetRule.interfaceTypeMatch
228 | newRule.ssidMatch = targetRule.ssidMatch
229 | newRule.probeURL = targetRule.probeURL
230 |
231 | targetRule = newRule
232 |
233 | updateConnectionRulesCell()
234 | }
235 |
236 | /// Show or hide the connection rules cell based on the action of the target rule.
237 | func updateConnectionRulesCell() {
238 | guard let actionIndexPath = self.getIndexPathOfCell(actionCell) else { return }
239 |
240 | if let rulesIndexPath = self.getIndexPathOfCell(connectionRulesCell) {
241 | // The connection rules cell is being displayed. If the action is not "Evaluate Connection", then remove the connection rules cell.
242 | if targetRule.action != .evaluateConnection {
243 | cells.remove(at: (rulesIndexPath as NSIndexPath).row)
244 | self.tableView.deleteRows(at: [ rulesIndexPath ], with: .bottom)
245 | }
246 | } else {
247 | // The connection rules cell is not being displayed. If the action is "Evaluate Connection", then insert the connection rules cell.
248 | if targetRule.action == .evaluateConnection {
249 | cells.insert(connectionRulesCell, at: (actionIndexPath as NSIndexPath).row + 1)
250 | let indexPaths = [ IndexPath(row: (actionIndexPath as NSIndexPath).row + 1, section: (actionIndexPath as NSIndexPath).section) ]
251 | self.tableView.insertRows(at: indexPaths, with: .bottom)
252 | }
253 | }
254 | }
255 |
256 | /// Handle the user tapping on the "Done" button.
257 | @IBAction func saveTargetRule(_ sender: AnyObject) {
258 | addRuleHandler(targetRule)
259 |
260 | // Transition back to the Connect On Demand rule list view.
261 | self.performSegue(withIdentifier: "save-on-demand-rule", sender: sender)
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/SimpleTunnel/OnDemandRuleListController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the OnDemandRuleListController class, which is responsible for controlling a list of Connect On Demand rules.
7 | */
8 |
9 | import UIKit
10 | import NetworkExtension
11 |
12 | /// A view controller for a view displaying a list of Connect On Demand rules.
13 | class OnDemandRuleListController: ListViewController {
14 |
15 | // MARK: Properties
16 |
17 | /// The VPN configuration containing the Connect On Demand rules.
18 | var targetManager: NEVPNManager = NEVPNManager.shared()
19 |
20 | /// The text to display in the list's "add new item" row.
21 | override var listAddButtonText: String {
22 | return "Add On Demand Rule..."
23 | }
24 |
25 | /// The number of Connect On Demand rules.
26 | override var listCount: Int {
27 | return targetManager.onDemandRules?.count ?? 0
28 | }
29 |
30 | /// Returns UITableViewCellAccessoryType.DetailButton
31 | override var listAccessoryType: UITableViewCellAccessoryType {
32 | return .detailButton
33 | }
34 |
35 | /// Returns UITableViewCellAccessoryType.DetailButton
36 | override var listEditingAccessoryType: UITableViewCellAccessoryType {
37 | return .detailButton
38 | }
39 |
40 | // MARK: UIViewController
41 |
42 | /// Handle the event when the view is loaded into memory.
43 | override func viewDidLoad() {
44 | isAddEnabled = true
45 | isAlwaysEditing = true
46 | super.viewDidLoad()
47 | }
48 |
49 | // MARK: Interface
50 |
51 | /// Handle unwind segues to this view controller.
52 | @IBAction func handleUnwind(_ sender: UIStoryboardSegue) {
53 | }
54 |
55 | // MARK: ListViewController
56 |
57 | /// Set up the destination view controller of a segue away from this view controller.
58 | override func listSetupSegue(_ segue: UIStoryboardSegue, forItemAtIndex index: Int) {
59 | guard let identifier = segue.identifier,
60 | let ruleAddEditController = segue.destination as? OnDemandRuleAddEditController
61 | else { return }
62 |
63 | switch identifier {
64 | case "edit-on-demand-rule":
65 | // The user tapped on the editing accessory of a rule in the list.
66 | guard let rule = targetManager.onDemandRules?[index] else { break }
67 | ruleAddEditController.setTargetRule(rule, title: "On Demand Rule") { newRule in
68 | self.targetManager.onDemandRules?[index] = newRule
69 | }
70 |
71 | case "add-on-demand-rule":
72 | // The user tapped on the "add a new rule" row.
73 | ruleAddEditController.setTargetRule(nil, title: "Add On Demand Rule") { newRule in
74 | if self.targetManager.onDemandRules == nil {
75 | self.targetManager.onDemandRules = [NEOnDemandRule]()
76 | }
77 | self.targetManager.onDemandRules?.append(newRule)
78 | }
79 |
80 | default:
81 | break
82 | }
83 | }
84 |
85 | /// Return a description of the rule at the given index in the list.
86 | override func listTextForItemAtIndex(_ index: Int) -> String {
87 | return targetManager.onDemandRules?[index].action.description ?? ""
88 | }
89 |
90 | /// Remove a rule from the list.
91 | override func listRemoveItemAtIndex(_ index: Int) {
92 | targetManager.onDemandRules?.remove(at: index)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/SimpleTunnel/ProxyAutoConfigScriptController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ProxyAutoConfigScriptController class, which controls a view used to input the text of a Proxy Auto-Configuration Script.
7 | */
8 |
9 | import UIKit
10 | import NetworkExtension
11 |
12 | /// A view controller object for a view that contains a text box where the user can enter a Proxy Auto-Configuration (PAC) script.
13 | class ProxyAutoConfigScriptController: UIViewController {
14 |
15 | // MARK: Properties
16 |
17 | /// The text view containing the script.
18 | @IBOutlet weak var scriptText: UITextView!
19 |
20 | /// The block to call when the user taps on the "Done" button.
21 | var saveScriptCallback: (String?) -> Void = { script in return }
22 |
23 | // MARK: Interface
24 |
25 | /// Call the saveScriptCallback and transition back to the proxy settings view.
26 | @IBAction func saveScript(_ sender: AnyObject) {
27 | saveScriptCallback(scriptText.text)
28 | performSegue(withIdentifier: "save-proxy-script", sender: sender)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/SimpleTunnel/ProxyServerAddEditController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ProxyServerAddEditController class, which controls a view used to create or edit a proxy server configuration.
7 | */
8 |
9 | import UIKit
10 | import NetworkExtension
11 |
12 | /// A view controller object for a view that contains input fields used to define HTTP proxy server settings.
13 | class ProxyServerAddEditController: ConfigurationParametersViewController {
14 |
15 | // MARK: Properties
16 |
17 | /// A table view cell containing a text input field where the user enters the proxy server address.
18 | @IBOutlet weak var addressCell: TextFieldCell!
19 |
20 | /// A table view cell containing a text input field where the user enters the proxy server port number.
21 | @IBOutlet weak var portCell: TextFieldCell!
22 |
23 | /// A table view cell containing a text input field where the user enters the username portion of the proxy credential.
24 | @IBOutlet weak var usernameCell: TextFieldCell!
25 |
26 | /// A table view cell containing a text input field where the user enters the password portion of the proxy credential.
27 | @IBOutlet weak var passwordCell: TextFieldCell!
28 |
29 | /// A table view cell containing a switch that toggles authentication for the proxy server.
30 | @IBOutlet weak var authenticationSwitchCell: SwitchCell!
31 |
32 | /// The NEProxyServer object containing the proxy server settings.
33 | var targetServer = NEProxyServer(address: "", port: 0)
34 |
35 | /// The block to call when the user taps on the "Done" button.
36 | var saveChangesCallback: (NEProxyServer) -> Void = { server in return }
37 |
38 | // MARK: UIViewController
39 |
40 | /// Handle the event where the view is loaded into memory.
41 | override func viewDidLoad() {
42 | super.viewDidLoad()
43 |
44 | cells = [
45 | addressCell,
46 | portCell,
47 | authenticationSwitchCell
48 | ].flatMap { $0 }
49 |
50 | authenticationSwitchCell.dependentCells = [ usernameCell, passwordCell ]
51 | authenticationSwitchCell.getIndexPath = {
52 | return self.getIndexPathOfCell(self.authenticationSwitchCell)
53 | }
54 | authenticationSwitchCell.valueChanged = {
55 | self.updateCellsWithDependentsOfCell(self.authenticationSwitchCell)
56 | self.targetServer.authenticationRequired = self.authenticationSwitchCell.isOn
57 | }
58 | }
59 |
60 | /// Handle the event when the view is being displayed.
61 | override func viewWillAppear(_ animated: Bool) {
62 | super.viewWillAppear(animated)
63 |
64 | tableView.reloadData()
65 |
66 | addressCell.textField.text = !targetServer.address.isEmpty ? targetServer.address : nil
67 | portCell.textField.text = targetServer.port > 0 ? String(targetServer.port) : nil
68 | passwordCell.textField.text = targetServer.password
69 | usernameCell.textField.text = targetServer.username
70 | authenticationSwitchCell.isOn = targetServer.authenticationRequired
71 | }
72 |
73 | // MARK: Interface
74 |
75 | /// Set the NEProxyServer object to modify, the title of the view, and a block to call when the user is done modify the proxy server settings.
76 | func setTargetServer(_ server: NEProxyServer?, title: String, saveHandler: @escaping (NEProxyServer) -> Void) {
77 | targetServer = server ?? NEProxyServer(address: "", port: 0)
78 | navigationItem.title = title
79 | saveChangesCallback = saveHandler
80 | }
81 |
82 | /// Gather all of the inputs from the user and call saveChangesCallback. This function is called when the user taps on the "Done" button.
83 | @IBAction func saveProxyServer(_ sender: AnyObject) {
84 | guard let address = addressCell.textField.text,
85 | let portString = portCell.textField.text,
86 | let port = Int(portString)
87 | , !address.isEmpty && !portString.isEmpty
88 | else { return }
89 |
90 | let result = NEProxyServer(address: address, port: port)
91 | result.username = usernameCell.textField.text
92 | result.password = passwordCell.textField.text
93 | result.authenticationRequired = authenticationSwitchCell.isOn
94 | saveChangesCallback(result)
95 | // Go back to the main proxy settings view.
96 | performSegue(withIdentifier: "save-proxy-server-settings", sender: sender)
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/SimpleTunnel/ProxySettingsController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ProxySettingsController class, which controls a view used to edit proxy settings.
7 | */
8 |
9 | import UIKit
10 | import NetworkExtension
11 |
12 | /// A view controller object for a view that contains input fields used to define a HTTP proxy settings.
13 | class ProxySettingsController: ConfigurationParametersViewController {
14 |
15 | // MARK: Properties
16 |
17 | /// A table view cell containing a switch that toggles Proxy Auto Configuration
18 | @IBOutlet weak var pacSwitchCell: SwitchCell!
19 |
20 | /// A table view cell containing a text input field where the user can enter the URL for a Proxy Auto Configuration script.
21 | @IBOutlet weak var pacURLCell: TextFieldCell!
22 |
23 | /// A table view cell that when tapped on allows the user to enter a Proxy Auto Configuration script.
24 | @IBOutlet weak var pacScriptCell: UITableViewCell!
25 |
26 | /// A table view cell that when tapped on allows the user to modify the static HTTP proxy settings.
27 | @IBOutlet weak var HTTPCell: UITableViewCell!
28 |
29 | /// A table view cell containing a switch that toggles the use of a static HTTP proxy.
30 | @IBOutlet weak var HTTPSwitchCell: SwitchCell!
31 |
32 | /// A table view cell that when tapped on allows the user to modify the static HTTPS proxy settings.
33 | @IBOutlet weak var HTTPSCell: UITableViewCell!
34 |
35 | /// A table view cell containing a switch that toggles the use of a static HTTPS proxy.
36 | @IBOutlet weak var HTTPSSwitchCell: SwitchCell!
37 |
38 | /// A table view cell containing a switch that toggles the exclusion of HTTP requests for "simple" (single-label) hosts from using the HTTP proxy settings.
39 | @IBOutlet weak var excludeSimpleCell: SwitchCell!
40 |
41 | /// A table view cell that when tapped on allows the user to define patterns for host names that will not use the HTTP proxy settings.
42 | @IBOutlet weak var exceptionsCell: UITableViewCell!
43 |
44 | /// A table view cell that when tapped on allows the user to define the domains of hosts that will use the HTTP proxy settings.
45 | @IBOutlet weak var matchDomainsCell: UITableViewCell!
46 |
47 | /// The VPN configuration containing the proxy settings.
48 | var targetConfiguration = NEVPNProtocol()
49 |
50 | // MARK: UIViewController
51 |
52 | /// Handle the event where the view is loaded into memory.
53 | override func viewDidLoad() {
54 | super.viewDidLoad()
55 |
56 | cells = [
57 | pacSwitchCell,
58 | HTTPSwitchCell,
59 | HTTPSSwitchCell,
60 | excludeSimpleCell,
61 | exceptionsCell,
62 | matchDomainsCell
63 | ].flatMap { $0 }
64 |
65 | pacSwitchCell.dependentCells = [ pacURLCell, pacScriptCell ]
66 | pacSwitchCell.getIndexPath = {
67 | return self.getIndexPathOfCell(self.pacSwitchCell)
68 | }
69 | pacSwitchCell.valueChanged = {
70 | self.updateCellsWithDependentsOfCell(self.pacSwitchCell)
71 | self.targetConfiguration.proxySettings?.autoProxyConfigurationEnabled = self.pacSwitchCell.isOn
72 | }
73 |
74 | pacURLCell.valueChanged = {
75 | if let enteredText = self.pacURLCell.textField.text {
76 | self.targetConfiguration.proxySettings?.proxyAutoConfigurationURL = URL(string: enteredText)
77 | }
78 | else {
79 | self.targetConfiguration.proxySettings?.proxyAutoConfigurationURL = nil
80 | }
81 | }
82 |
83 | HTTPSwitchCell.dependentCells = [ HTTPCell ]
84 | HTTPSwitchCell.getIndexPath = {
85 | return self.getIndexPathOfCell(self.HTTPSwitchCell)
86 | }
87 | HTTPSwitchCell.valueChanged = {
88 | self.updateCellsWithDependentsOfCell(self.HTTPSwitchCell)
89 | self.targetConfiguration.proxySettings?.httpEnabled = self.HTTPSwitchCell.isOn
90 | }
91 |
92 | HTTPSSwitchCell.dependentCells = [ HTTPSCell ]
93 | HTTPSSwitchCell.getIndexPath = {
94 | return self.getIndexPathOfCell(self.HTTPSSwitchCell)
95 | }
96 | HTTPSSwitchCell.valueChanged = {
97 | self.updateCellsWithDependentsOfCell(self.HTTPSSwitchCell)
98 | self.targetConfiguration.proxySettings?.httpsEnabled = self.HTTPSSwitchCell.isOn
99 | }
100 |
101 | excludeSimpleCell.valueChanged = {
102 | self.targetConfiguration.proxySettings?.excludeSimpleHostnames = self.excludeSimpleCell.isOn
103 | }
104 | }
105 |
106 | /// Handle the event when the view is being displayed.
107 | override func viewWillAppear(_ animated: Bool) {
108 | super.viewWillAppear(animated)
109 |
110 | tableView.reloadData()
111 |
112 | pacSwitchCell.isOn = targetConfiguration.proxySettings?.autoProxyConfigurationEnabled ?? false
113 | pacURLCell.textField.text = targetConfiguration.proxySettings?.proxyAutoConfigurationURL?.absoluteString
114 |
115 | if let script = targetConfiguration.proxySettings?.proxyAutoConfigurationJavaScript {
116 | pacScriptCell.detailTextLabel?.text = script.isEmpty ? "Optional" : "..."
117 | }
118 | else {
119 | pacScriptCell.detailTextLabel?.text = "Optional"
120 | }
121 |
122 | HTTPSwitchCell.isOn = targetConfiguration.proxySettings?.httpEnabled ?? false
123 | if let server = targetConfiguration.proxySettings?.httpServer {
124 | HTTPCell.detailTextLabel?.text = "\(server.address):\(server.port)"
125 | }
126 | else {
127 | HTTPCell.detailTextLabel?.text = nil
128 | }
129 |
130 | HTTPSSwitchCell.isOn = targetConfiguration.proxySettings?.httpsEnabled ?? false
131 | if let server = targetConfiguration.proxySettings?.httpsServer {
132 | HTTPSCell.detailTextLabel?.text = "\(server.address):\(server.port)"
133 | }
134 | else {
135 | HTTPSCell.detailTextLabel?.text = nil
136 | }
137 |
138 | excludeSimpleCell.isOn = targetConfiguration.proxySettings?.excludeSimpleHostnames ?? false
139 |
140 | exceptionsCell.detailTextLabel?.text = self.getDescriptionForStringList(targetConfiguration.proxySettings?.exceptionList, itemDescription: "exception")
141 |
142 | matchDomainsCell.detailTextLabel?.text = self.getDescriptionForStringList(targetConfiguration.proxySettings?.matchDomains, itemDescription: "domain")
143 | }
144 |
145 | /// Set up the destination view controller for a segue away from this view controller.
146 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
147 | guard let identifier = segue.identifier else { return }
148 |
149 | switch identifier {
150 | case "edit-match-domains":
151 | // The user tapped on the "match domains" cell.
152 | guard let stringListController = segue.destination as? StringListController else { break }
153 | stringListController.setTargetStrings(targetConfiguration.proxySettings?.matchDomains, title: "Proxy Match Domains", addTitle: "Add a match domain...") { newStrings in
154 | self.targetConfiguration.proxySettings?.matchDomains = newStrings
155 | }
156 |
157 | case "edit-exceptions":
158 | // The user tapped on the "exceptions" cell.
159 | guard let stringListController = segue.destination as? StringListController else { break }
160 | stringListController.setTargetStrings(targetConfiguration.proxySettings?.exceptionList, title: "Proxy Exception Patterns", addTitle: "Add an exception pattern...") { newStrings in
161 | self.targetConfiguration.proxySettings?.exceptionList = newStrings
162 | }
163 |
164 | case "edit-https-proxy-server":
165 | // The user tapped on the "HTTPS server" cell.
166 | guard let proxyServerController = segue.destination as? ProxyServerAddEditController else { break }
167 |
168 | proxyServerController.setTargetServer(targetConfiguration.proxySettings?.httpsServer, title: "HTTPS Proxy Server") { newServer in
169 | self.targetConfiguration.proxySettings?.httpsServer = newServer
170 | }
171 |
172 | case "edit-http-proxy-server":
173 | // The user tapped on the "HTTP server" cell.
174 | guard let proxyServerController = segue.destination as? ProxyServerAddEditController else { break }
175 |
176 | proxyServerController.setTargetServer(targetConfiguration.proxySettings?.httpServer, title: "HTTP Proxy Server") { newServer in
177 | self.targetConfiguration.proxySettings?.httpServer = newServer
178 | }
179 |
180 | case "edit-pac-script":
181 | // The user tapped on the "proxy auto-configuration script" cell.
182 | guard let pacScriptController = segue.destination as? ProxyAutoConfigScriptController else { break }
183 |
184 | pacScriptController.scriptText?.text = targetConfiguration.proxySettings?.proxyAutoConfigurationJavaScript
185 | pacScriptController.saveScriptCallback = { newScript in
186 | if let script = newScript , !script.isEmpty {
187 | self.targetConfiguration.proxySettings?.proxyAutoConfigurationJavaScript = script
188 | }
189 | else {
190 | self.targetConfiguration.proxySettings?.proxyAutoConfigurationJavaScript = nil
191 | }
192 | }
193 |
194 | default:
195 | break
196 | }
197 | }
198 |
199 | // MARK: Interface
200 |
201 | /// Handle an unwind segue back to this view controller.
202 | @IBAction func handleUnwind(_ sender: UIStoryboardSegue) {
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/SimpleTunnel/SimpleTunnel.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.networking.networkextension
6 |
7 | packet-tunnel-provider
8 | app-proxy-provider
9 | content-filter-provider
10 |
11 | com.apple.developer.networking.vpn.api
12 |
13 | allow-vpn
14 |
15 | keychain-access-groups
16 |
17 | $(AppIdentifierPrefix)com.telekom.SimpleTunnel
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/SimpleTunnel/StatusViewController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the StatusViewController class, which controls a view used to start and stop a VPN connection, and display the status of the VPN connection.
7 | */
8 |
9 | import UIKit
10 | import NetworkExtension
11 | import SimpleTunnelServices
12 |
13 | // MARK: Extensions
14 |
15 | /// Make NEVPNStatus convertible to a string
16 | extension NEVPNStatus: CustomStringConvertible {
17 | public var description: String {
18 | switch self {
19 | case .disconnected: return "Disconnected"
20 | case .invalid: return "Invalid"
21 | case .connected: return "Connected"
22 | case .connecting: return "Connecting"
23 | case .disconnecting: return "Disconnecting"
24 | case .reasserting: return "Reconnecting"
25 | }
26 | }
27 | }
28 |
29 | /// A view controller object for a view that displays VPN status information and allows the user to start and stop the VPN.
30 | class StatusViewController: UITableViewController {
31 |
32 | // MARK: Properties
33 |
34 | /// A switch that toggles the enabled state of the VPN configuration.
35 | @IBOutlet weak var enabledSwitch: UISwitch!
36 |
37 | /// A switch that starts and stops the VPN.
38 | @IBOutlet weak var startStopToggle: UISwitch!
39 |
40 | /// A label that contains the current status of the VPN.
41 | @IBOutlet weak var statusLabel: UILabel!
42 |
43 | /// The target VPN configuration.
44 | var targetManager = NEVPNManager.shared()
45 |
46 | // MARK: UIViewController
47 |
48 | /// Handle the event where the view is being displayed.
49 | override func viewWillAppear(_ animated: Bool) {
50 | super.viewWillAppear(animated)
51 |
52 | // Initialize the UI
53 | enabledSwitch.isOn = targetManager.isEnabled
54 | startStopToggle.isOn = (targetManager.connection.status != .disconnected && targetManager.connection.status != .invalid)
55 | statusLabel.text = targetManager.connection.status.description
56 | navigationItem.title = targetManager.localizedDescription
57 |
58 | // Register to be notified of changes in the status.
59 | NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: targetManager.connection, queue: OperationQueue.main, using: { notification in
60 | self.statusLabel.text = self.targetManager.connection.status.description
61 | self.startStopToggle.isOn = (self.targetManager.connection.status != .disconnected && self.targetManager.connection.status != .disconnecting && self.targetManager.connection.status != .invalid)
62 | })
63 |
64 | // Disable the start/stop toggle if the configuration is not enabled.
65 | startStopToggle.isEnabled = enabledSwitch.isOn
66 |
67 | // Send a simple IPC message to the provider, handle the response.
68 | if let session = targetManager.connection as? NETunnelProviderSession,
69 | let message = "Hello Provider".data(using: String.Encoding.utf8)
70 | , targetManager.connection.status != .invalid
71 | {
72 | do {
73 | try session.sendProviderMessage(message) { response in
74 | if response != nil {
75 | let responseString = NSString(data: response!, encoding: String.Encoding.utf8.rawValue)
76 | simpleTunnelLog("Received response from the provider: \(String(describing: responseString))")
77 | } else {
78 | simpleTunnelLog("Got a nil response from the provider")
79 | }
80 | }
81 | } catch {
82 | simpleTunnelLog("Failed to send a message to the provider")
83 | }
84 | }
85 | }
86 |
87 | /// Handle the event where the view is being hidden.
88 | override func viewWillDisappear(_ animated: Bool) {
89 | super.viewWillDisappear(animated)
90 |
91 | // Stop watching for status change notifications.
92 | NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NEVPNStatusDidChange, object: targetManager.connection)
93 | }
94 |
95 | /// Handle the user toggling the "enabled" switch.
96 | @IBAction func enabledToggled(_ sender: AnyObject) {
97 | targetManager.isEnabled = enabledSwitch.isOn
98 | targetManager.saveToPreferences { error in
99 | guard error == nil else {
100 | self.enabledSwitch.isOn = self.targetManager.isEnabled
101 | self.startStopToggle.isEnabled = self.enabledSwitch.isOn
102 | return
103 | }
104 |
105 | self.targetManager.loadFromPreferences { error in
106 | self.enabledSwitch.isOn = self.targetManager.isEnabled
107 | self.startStopToggle.isEnabled = self.enabledSwitch.isOn
108 | }
109 | }
110 | }
111 |
112 | /// Handle the user toggling the "VPN" switch.
113 | @IBAction func startStopToggled(_ sender: AnyObject) {
114 | if targetManager.connection.status == .disconnected || targetManager.connection.status == .invalid {
115 | do {
116 | try targetManager.connection.startVPNTunnel()
117 | }
118 | catch {
119 | simpleTunnelLog("Failed to start the VPN: \(error)")
120 | }
121 | }
122 | else {
123 | targetManager.connection.stopVPNTunnel()
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/SimpleTunnel/StringListController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the StringListController class, which controls a list of strings.
7 | */
8 |
9 | import UIKit
10 |
11 | /// A view controller of a view that displays an editable list of strings.
12 | class StringListController: ListViewController {
13 |
14 | // MARK: Properties
15 |
16 | /// The current list of strings.
17 | var targetStrings = [String]()
18 |
19 | /// The text to display in the "add a string" text field.
20 | var addText: String?
21 |
22 | /// The title to display for the list.
23 | var listTitle: String?
24 |
25 | /// The block to execute when the list of strings changes.
26 | var stringsChangedHandler: ([String]) -> Void = { strings in return }
27 |
28 | /// A table view cell containing a text field used to enter new strings to be added to the list.
29 | @IBOutlet weak var addStringCell: TextFieldCell!
30 |
31 | /// The number of strings in the list.
32 | override var listCount: Int {
33 | return targetStrings.count
34 | }
35 |
36 | /// Returns UITableViewCellSelectionStyle.None
37 | override var listCellSelectionStyle: UITableViewCellSelectionStyle {
38 | return .none
39 | }
40 |
41 | // MARK: UIViewController
42 |
43 | /// Handle the event when the view is loaded into memory.
44 | override func viewDidLoad() {
45 | isAddEnabled = true
46 | isAlwaysEditing = true
47 |
48 | addStringCell.valueChanged = {
49 | guard let enteredText = self.addStringCell.textField.text else { return }
50 |
51 | self.targetStrings.append(enteredText)
52 | self.listInsertItemAtIndex(self.targetStrings.count - 1)
53 | self.addStringCell.textField.text = ""
54 | self.stringsChangedHandler(self.targetStrings)
55 | }
56 |
57 | // Set addStringCell as a custom "add a new item" cell.
58 | addCell = addStringCell
59 |
60 | super.viewDidLoad()
61 | }
62 |
63 | /// Handle the event when the view is being displayed.
64 | override func viewWillAppear(_ animated: Bool) {
65 | super.viewWillAppear(animated)
66 | addStringCell.textField.placeholder = addText
67 | navigationItem.title = listTitle
68 | }
69 |
70 | // MARK: ListViewController
71 |
72 | /// Return the string at the given index.
73 | override func listTextForItemAtIndex(_ index: Int) -> String {
74 | return targetStrings[index]
75 | }
76 |
77 | /// Remove the string at the given index.
78 | override func listRemoveItemAtIndex(_ index: Int) {
79 | targetStrings.remove(at: index)
80 | stringsChangedHandler(targetStrings)
81 | }
82 |
83 | // MARK: Interface
84 |
85 | /// Set the list of strings, the title to display for the list, the text used to prompt the user for a new string, and a block to execute when the list of strings changes.
86 | func setTargetStrings(_ strings: [String]?, title: String, addTitle: String, saveHandler: @escaping ([String]) -> Void) {
87 | targetStrings = strings ?? [String]()
88 | listTitle = title
89 | addText = addTitle
90 | stringsChangedHandler = saveHandler
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/SimpleTunnel/SwitchCell.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the SwitchCell class, which is a UITableViewCell sub-class for a cell that contains a UISwitch.
7 | */
8 |
9 | import UIKit
10 |
11 | /// A custom table cell object that contains a switch
12 | class SwitchCell: UITableViewCell {
13 |
14 | // MARK: Properties
15 |
16 | /// The switch contained in the cell.
17 | @IBOutlet weak var toggle: UISwitch!
18 |
19 | /// The table view controller of the table view that contains this cell. Used when inserting and removing cells that are conditionally displayed by the switch.
20 | @IBOutlet weak var tableViewController: UITableViewController!
21 |
22 | /// A list of cells that are inserted/removed from the table based on the value of the switch.
23 | var dependentCells = [UITableViewCell]()
24 |
25 | /// A block to call to get the index path of this cell int its containing table.
26 | var getIndexPath: ((Void) -> IndexPath?)?
27 |
28 | /// A block to call when the value of the switch changes.
29 | var valueChanged: ((Void) -> Void)?
30 |
31 | /// A boolean that toggles the switch.
32 | var isOn: Bool {
33 | get {
34 | return toggle.isOn
35 | }
36 | set (newValue) {
37 | let changed = toggle.isOn != newValue
38 | toggle.isOn = newValue
39 | if changed {
40 | handleValueChanged()
41 | }
42 | }
43 | }
44 |
45 | // MARK: Interface
46 |
47 | /// Handle the user toggling the switch.
48 | @IBAction func toggled(_ sender: AnyObject) {
49 | handleValueChanged()
50 | }
51 |
52 | /// Handle changes in the switch's value.
53 | func handleValueChanged() {
54 | valueChanged?()
55 |
56 | guard let switchIndexPath = getIndexPath?() else { return }
57 |
58 | guard dependentCells.count > 0 else { return }
59 | var indexPaths = [IndexPath]()
60 |
61 | // Create index paths for the dependent cells based on the index path of this cell.
62 | for row in 1...dependentCells.count {
63 | indexPaths.append(IndexPath(row: (switchIndexPath as NSIndexPath).row + row, section: (switchIndexPath as NSIndexPath).section))
64 | }
65 |
66 | guard !indexPaths.isEmpty else { return }
67 |
68 | if toggle.isOn {
69 | tableViewController.tableView.insertRows(at: indexPaths, with: .bottom)
70 | } else {
71 | tableViewController.tableView.deleteRows(at: indexPaths, with: .bottom)
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/SimpleTunnel/TextFieldCell.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the TextFieldCell class, which is a UITableViewCell sub-class for a cell that contains a UITextField.
7 | */
8 |
9 | import UIKit
10 |
11 | /// A custom table view object that contains a text field.
12 | class TextFieldCell : UITableViewCell, UITextFieldDelegate {
13 |
14 | // MARK: Properties
15 |
16 | /// The text input field.
17 | @IBOutlet weak var textField: UITextField!
18 |
19 | /// The block to call when the value of the text field changes.
20 | var valueChanged: ((Void) -> Void)?
21 |
22 | // MARK: UITextFieldDelegate
23 |
24 | /// Handle the event of the user finishing changing the value of the text field.
25 | func textFieldDidEndEditing(_ textField: UITextField) {
26 | textField.resignFirstResponder()
27 |
28 | valueChanged?()
29 | }
30 |
31 | /// Dismiss the keyboard
32 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
33 | textField.resignFirstResponder()
34 | return true
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/SimpleTunnelServices/ClientTunnel.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ClientTunnel class. The ClientTunnel class implements the client side of the SimpleTunnel tunneling protocol.
7 | */
8 |
9 | import Foundation
10 | import NetworkExtension
11 |
12 | /// Make NEVPNStatus convertible to a string
13 | extension NWTCPConnectionState: CustomStringConvertible {
14 | public var description: String {
15 | switch self {
16 | case .cancelled: return "Cancelled"
17 | case .connected: return "Connected"
18 | case .connecting: return "Connecting"
19 | case .disconnected: return "Disconnected"
20 | case .invalid: return "Invalid"
21 | case .waiting: return "Waiting"
22 | }
23 | }
24 | }
25 |
26 | /// The client-side implementation of the SimpleTunnel protocol.
27 | open class ClientTunnel: Tunnel {
28 |
29 | // MARK: Properties
30 |
31 | /// The tunnel connection.
32 | open var connection: NWTCPConnection?
33 |
34 | /// The last error that occurred on the tunnel.
35 | open var lastError: NSError?
36 |
37 | /// The previously-received incomplete message data.
38 | var previousData: NSMutableData?
39 |
40 | /// The address of the tunnel server.
41 | open var remoteHost: String?
42 |
43 | // MARK: Interface
44 |
45 | /// Start the TCP connection to the tunnel server.
46 | open func startTunnel(_ provider: NETunnelProvider) -> SimpleTunnelError? {
47 |
48 | guard let serverAddress = provider.protocolConfiguration.serverAddress else {
49 | return .badConfiguration
50 | }
51 |
52 | let endpoint: NWEndpoint
53 |
54 | if let colonRange = serverAddress.rangeOfCharacter(from: CharacterSet(charactersIn: ":"), options: [], range: nil) {
55 | // The server is specified in the configuration as :.
56 |
57 | let hostname = serverAddress.substring(with: serverAddress.startIndex...size, maximumLength: MemoryLayout.size) { data, error in
95 | if let readError = error {
96 | simpleTunnelLog("Got an error on the tunnel connection: \(readError)")
97 | self.closeTunnelWithError(readError as NSError?)
98 | return
99 | }
100 |
101 | let lengthData = data
102 |
103 | guard lengthData?.count == MemoryLayout.size else {
104 | simpleTunnelLog("Length data length (\(String(describing: lengthData?.count))) != sizeof(UInt32) (\(MemoryLayout.size)")
105 | self.closeTunnelWithError(SimpleTunnelError.internalError as NSError)
106 | return
107 | }
108 |
109 | var totalLength: UInt32 = 0
110 | (lengthData! as NSData).getBytes(&totalLength, length: MemoryLayout.size)
111 |
112 | if totalLength > UInt32(Tunnel.maximumMessageSize) {
113 | simpleTunnelLog("Got a length that is too big: \(totalLength)")
114 | self.closeTunnelWithError(SimpleTunnelError.internalError as NSError)
115 | return
116 | }
117 |
118 | totalLength -= UInt32(MemoryLayout.size)
119 |
120 | // Second, read the packet payload.
121 | targetConnection.readMinimumLength(Int(totalLength), maximumLength: Int(totalLength)) { data, error in
122 | if let payloadReadError = error {
123 | simpleTunnelLog("Got an error on the tunnel connection: \(payloadReadError)")
124 | self.closeTunnelWithError(payloadReadError as NSError?)
125 | return
126 | }
127 |
128 | let payloadData = data
129 |
130 | guard payloadData?.count == Int(totalLength) else {
131 | simpleTunnelLog("Payload data length (\(String(describing: payloadData?.count))) != payload length (\(totalLength)")
132 | self.closeTunnelWithError(SimpleTunnelError.internalError as NSError)
133 | return
134 | }
135 |
136 | _ = self.handlePacket(payloadData!)
137 |
138 | self.readNextPacket()
139 | }
140 | }
141 | }
142 |
143 | /// Send a message to the tunnel server.
144 | open func sendMessage(_ messageProperties: [String: AnyObject], completionHandler: @escaping (NSError?) -> Void) {
145 | guard let messageData = serializeMessage(messageProperties) else {
146 | completionHandler(SimpleTunnelError.internalError as NSError)
147 | return
148 | }
149 |
150 | connection?.write(messageData, completionHandler: completionHandler as! (Error?) -> Void)
151 | }
152 |
153 | // MARK: NSObject
154 |
155 | /// Handle changes to the tunnel connection state.
156 | open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
157 | guard keyPath == "state" && context?.assumingMemoryBound(to: Optional.self).pointee == connection else {
158 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
159 | return
160 | }
161 |
162 | simpleTunnelLog("Tunnel connection state changed to \(connection!.state)")
163 |
164 | switch connection!.state {
165 | case .connected:
166 | if let remoteAddress = self.connection!.remoteAddress as? NWHostEndpoint {
167 | remoteHost = remoteAddress.hostname
168 | }
169 |
170 | // Start reading messages from the tunnel connection.
171 | readNextPacket()
172 |
173 | // Let the delegate know that the tunnel is open
174 | delegate?.tunnelDidOpen(self)
175 |
176 | case .disconnected:
177 | closeTunnelWithError(connection!.error as NSError?)
178 |
179 | case .cancelled:
180 | connection!.removeObserver(self, forKeyPath:"state", context:&connection)
181 | connection = nil
182 | delegate?.tunnelDidClose(self)
183 |
184 | default:
185 | break
186 | }
187 | }
188 |
189 | // MARK: Tunnel
190 |
191 | /// Close the tunnel.
192 | override open func closeTunnel() {
193 | super.closeTunnel()
194 | // Close the tunnel connection.
195 | if let TCPConnection = connection {
196 | TCPConnection.cancel()
197 | }
198 |
199 | }
200 |
201 | /// Write data to the tunnel connection.
202 | override func writeDataToTunnel(_ data: Data, startingAtOffset: Int) -> Int {
203 | connection?.write(data) { error in
204 | if error != nil {
205 | self.closeTunnelWithError(error as NSError?)
206 | }
207 | }
208 | return data.count
209 | }
210 |
211 | /// Handle a message received from the tunnel server.
212 | override func handleMessage(_ commandType: TunnelCommand, properties: [String: AnyObject], connection: Connection?) -> Bool {
213 | var success = true
214 |
215 | switch commandType {
216 | case .openResult:
217 | // A logical connection was opened successfully.
218 | guard let targetConnection = connection,
219 | let resultCodeNumber = properties[TunnelMessageKey.ResultCode.rawValue] as? Int,
220 | let resultCode = TunnelConnectionOpenResult(rawValue: resultCodeNumber)
221 | else
222 | {
223 | success = false
224 | break
225 | }
226 |
227 | targetConnection.handleOpenCompleted(resultCode, properties:properties as [NSObject : AnyObject])
228 |
229 | case .fetchConfiguration:
230 | guard let configuration = properties[TunnelMessageKey.Configuration.rawValue] as? [String: AnyObject]
231 | else { break }
232 |
233 | delegate?.tunnelDidSendConfiguration(self, configuration: configuration)
234 |
235 | default:
236 | simpleTunnelLog("Tunnel received an invalid command")
237 | success = false
238 | }
239 | return success
240 | }
241 |
242 | /// Send a FetchConfiguration message on the tunnel connection.
243 | open func sendFetchConfiguation() {
244 | let properties = createMessagePropertiesForConnection(0, commandType: .fetchConfiguration)
245 | if !sendMessage(properties) {
246 | simpleTunnelLog("Failed to send a fetch configuration message")
247 | }
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/SimpleTunnelServices/Connection.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the Connection class. The Connection class is an abstract base class that handles a single flow of network data in the SimpleTunnel tunneling protocol.
7 | */
8 |
9 |
10 | import Foundation
11 |
12 | /// The directions in which a flow can be closed for further data.
13 | public enum TunnelConnectionCloseDirection: Int, CustomStringConvertible {
14 | case none = 1
15 | case read = 2
16 | case write = 3
17 | case all = 4
18 |
19 | public var description: String {
20 | switch self {
21 | case .none: return "none"
22 | case .read: return "reads"
23 | case .write: return "writes"
24 | case .all: return "reads and writes"
25 | }
26 | }
27 | }
28 |
29 | /// The results of opening a connection.
30 | public enum TunnelConnectionOpenResult: Int {
31 | case success = 0
32 | case invalidParam
33 | case noSuchHost
34 | case refused
35 | case timeout
36 | case internalError
37 | }
38 |
39 | /// A logical connection (or flow) of network data in the SimpleTunnel protocol.
40 | open class Connection: NSObject {
41 |
42 | // MARK: Properties
43 |
44 | /// The connection identifier.
45 | open let identifier: Int
46 |
47 | /// The tunnel that contains the connection.
48 | open var tunnel: Tunnel?
49 |
50 | /// The list of data that needs to be written to the connection when possible.
51 | let savedData = SavedData()
52 |
53 | /// The direction(s) in which the connection is closed.
54 | var currentCloseDirection = TunnelConnectionCloseDirection.none
55 |
56 | /// Indicates if the tunnel is being used by this connection exclusively.
57 | let isExclusiveTunnel: Bool
58 |
59 | /// Indicates if the connection cannot be read from.
60 | open var isClosedForRead: Bool {
61 | return currentCloseDirection != .none && currentCloseDirection != .write
62 | }
63 |
64 | /// Indicates if the connection cannot be written to.
65 | open var isClosedForWrite: Bool {
66 | return currentCloseDirection != .none && currentCloseDirection != .read
67 | }
68 |
69 | /// Indicates if the connection is fully closed.
70 | open var isClosedCompletely: Bool {
71 | return currentCloseDirection == .all
72 | }
73 |
74 | // MARK: Initializers
75 |
76 | public init(connectionIdentifier: Int, parentTunnel: Tunnel) {
77 | tunnel = parentTunnel
78 | identifier = connectionIdentifier
79 | isExclusiveTunnel = false
80 | super.init()
81 | if let t = tunnel {
82 | // Add this connection to the tunnel's set of connections.
83 | t.addConnection(self)
84 | }
85 |
86 | }
87 |
88 | public init(connectionIdentifier: Int) {
89 | isExclusiveTunnel = true
90 | identifier = connectionIdentifier
91 | }
92 |
93 | // MARK: Interface
94 |
95 | /// Set a new tunnel for the connection.
96 | func setNewTunnel(_ newTunnel: Tunnel) {
97 | tunnel = newTunnel
98 | if let t = tunnel {
99 | t.addConnection(self)
100 | }
101 | }
102 |
103 | /// Close the connection.
104 | open func closeConnection(_ direction: TunnelConnectionCloseDirection) {
105 | if direction != .none && direction != currentCloseDirection {
106 | currentCloseDirection = .all
107 | }
108 | else {
109 | currentCloseDirection = direction
110 | }
111 |
112 | guard let currentTunnel = tunnel , currentCloseDirection == .all else { return }
113 |
114 | if isExclusiveTunnel {
115 | currentTunnel.closeTunnel()
116 | }
117 | else {
118 | currentTunnel.dropConnection(self)
119 | tunnel = nil
120 | }
121 | }
122 |
123 | /// Abort the connection.
124 | open func abort(_ error: Int = 0) {
125 | savedData.clear()
126 | }
127 |
128 | /// Send data on the connection.
129 | open func sendData(_ data: Data) {
130 | }
131 |
132 | /// Send data and the destination host and port on the connection.
133 | open func sendDataWithEndPoint(_ data: Data, host: String, port: Int) {
134 | }
135 |
136 | /// Send a list of IP packets and their associated protocols on the connection.
137 | open func sendPackets(_ packets: [Data], protocols: [NSNumber]) {
138 | }
139 |
140 | /// Send an indication to the remote end of the connection that the caller will not be reading any more data from the connection for a while.
141 | open func suspend() {
142 | }
143 |
144 | /// Send an indication to the remote end of the connection that the caller is going to start reading more data from the connection.
145 | open func resume() {
146 | }
147 |
148 | /// Handle the "open completed" message sent by the SimpleTunnel server.
149 | open func handleOpenCompleted(_ resultCode: TunnelConnectionOpenResult, properties: [NSObject: AnyObject]) {
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/SimpleTunnelServices/FilterUtilities.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the FilterUtilities class. FilterUtilities objects contain functions and data that is used by both the SimpleTunnel UI and the SimpleTunnel content filter providers.
7 | */
8 |
9 | import Foundation
10 | import NetworkExtension
11 |
12 | /// Content Filter actions.
13 | public enum FilterRuleAction : Int, CustomStringConvertible {
14 | case block = 1
15 | case allow = 2
16 | case needMoreRulesAndBlock = 3
17 | case needMoreRulesAndAllow = 4
18 | case needMoreRulesFromDataAndBlock = 5
19 | case needMoreRulesFromDataAndAllow = 6
20 | case examineData = 7
21 | case redirectToSafeURL = 8
22 | case remediate = 9
23 |
24 | public var description: String {
25 | switch self {
26 | case .block: return "Block"
27 | case .examineData: return "Examine Data"
28 | case .needMoreRulesAndAllow: return "Ask for more rules, then allow"
29 | case .needMoreRulesAndBlock: return "Ask for more rules, then block"
30 | case .needMoreRulesFromDataAndAllow: return "Ask for more rules, examine data, then allow"
31 | case .needMoreRulesFromDataAndBlock: return "Ask for more rules, examine data, then block"
32 | case .redirectToSafeURL: return "Redirect"
33 | case .remediate: return "Remediate"
34 | case .allow: return "Allow"
35 | }
36 | }
37 | }
38 |
39 | /// A class containing utility properties and functions for Content Filtering.
40 | open class FilterUtilities {
41 |
42 | // MARK: Properties
43 |
44 | /// A reference to the SimpleTunnel user defaults.
45 | open static let defaults = UserDefaults(suiteName: "group.com.example.apple-samplecode.SimpleTunnel")
46 |
47 | // MARK: Initializers
48 |
49 | /// Get rule parameters for a flow from the SimpleTunnel user defaults.
50 | open class func getRule(_ flow: NEFilterFlow) -> (FilterRuleAction, String, [String: AnyObject]) {
51 | let hostname = FilterUtilities.getFlowHostname(flow)
52 |
53 | guard !hostname.isEmpty else { return (.allow, hostname, [:]) }
54 |
55 | guard let hostNameRule = (defaults?.object(forKey: "rules") as AnyObject).object(forKey: hostname) as? [String: AnyObject] else {
56 | simpleTunnelLog("\(hostname) is set for NO RULES")
57 | return (.allow, hostname, [:])
58 | }
59 |
60 | guard let ruleTypeInt = hostNameRule["kRule"] as? Int,
61 | let ruleType = FilterRuleAction(rawValue: ruleTypeInt)
62 | else { return (.allow, hostname, [:]) }
63 |
64 | return (ruleType, hostname, hostNameRule)
65 | }
66 |
67 | /// Get the hostname from a browser flow.
68 | open class func getFlowHostname(_ flow: NEFilterFlow) -> String {
69 | guard let browserFlow : NEFilterBrowserFlow = flow as? NEFilterBrowserFlow,
70 | let url = browserFlow.url,
71 | let hostname = url.host
72 | , flow is NEFilterBrowserFlow
73 | else { return "" }
74 | return hostname
75 | }
76 |
77 | /// Download a fresh set of rules from the rules server.
78 | open class func fetchRulesFromServer(_ serverAddress: String?) {
79 | simpleTunnelLog("fetch rules called")
80 |
81 | guard serverAddress != nil else { return }
82 | simpleTunnelLog("Fetching rules from \(String(describing: serverAddress))")
83 |
84 | guard let infoURL = URL(string: "http://\(serverAddress!)/rules/") else { return }
85 | simpleTunnelLog("Rules url is \(infoURL)")
86 |
87 | let content: String
88 | do {
89 | content = try String(contentsOf: infoURL, encoding: String.Encoding.utf8)
90 | }
91 | catch {
92 | simpleTunnelLog("Failed to fetch the rules from \(infoURL)")
93 | return
94 | }
95 |
96 | let contentArray = content.components(separatedBy: "
")
97 | simpleTunnelLog("Content array is \(contentArray)")
98 | var urlRules = [String: [String: AnyObject]]()
99 |
100 | for rule in contentArray {
101 | if rule.isEmpty {
102 | continue
103 | }
104 | let ruleArray = rule.components(separatedBy: " ")
105 |
106 | guard !ruleArray.isEmpty else { continue }
107 |
108 | var redirectKey = "SafeYes"
109 | var remediateKey = "Remediate1"
110 | var remediateButtonKey = "RemediateButton1"
111 | var actionString = "9"
112 |
113 | let urlString = ruleArray[0]
114 | let ruleArrayCount = ruleArray.count
115 |
116 | if ruleArrayCount > 1 {
117 | actionString = ruleArray[1]
118 | }
119 | if ruleArrayCount > 2 {
120 | redirectKey = ruleArray[2]
121 | }
122 | if ruleArrayCount > 3 {
123 | remediateKey = ruleArray[3]
124 | }
125 | if ruleArrayCount > 4 {
126 | remediateButtonKey = ruleArray[4]
127 | }
128 |
129 |
130 | urlRules[urlString] = [
131 | "kRule" : actionString as AnyObject,
132 | "kRedirectKey" : redirectKey as AnyObject,
133 | "kRemediateKey" : remediateKey as AnyObject,
134 | "kRemediateButtonKey" : remediateButtonKey as AnyObject,
135 | ]
136 | }
137 | defaults?.setValue(urlRules, forKey:"rules")
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/SimpleTunnelServices/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/SimpleTunnelServices/SimpleTunnelServices.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file is the main header file for the SimpleTunnelServices module.
7 | */
8 |
9 | @import Foundation;
10 |
11 | //! Project version number for SimpleTunnelServices.
12 | FOUNDATION_EXPORT double SimpleTunnelServicesVersionNumber;
13 |
14 | //! Project version string for SimpleTunnelServices.
15 | FOUNDATION_EXPORT const unsigned char SimpleTunnelServicesVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
--------------------------------------------------------------------------------
/SimpleTunnelServices/util.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains some utility classes and functions used by various parts of the SimpleTunnel project.
7 | */
8 |
9 | import Foundation
10 | import Darwin
11 |
12 | /// SimpleTunnel errors
13 | public enum SimpleTunnelError: Error {
14 | case badConfiguration
15 | case badConnection
16 | case internalError
17 | }
18 |
19 | /// A queue of blobs of data
20 | class SavedData {
21 |
22 | // MARK: Properties
23 |
24 | /// Each item in the list contains a data blob and an offset (in bytes) within the data blob of the data that is yet to be written.
25 | var chain = [(data: Data, offset: Int)]()
26 |
27 | /// A convenience property to determine if the list is empty.
28 | var isEmpty: Bool {
29 | return chain.isEmpty
30 | }
31 |
32 | // MARK: Interface
33 |
34 | /// Add a data blob and offset to the end of the list.
35 | func append(_ data: Data, offset: Int) {
36 | chain.append(data: data, offset: offset)
37 | }
38 |
39 | /// Write as much of the data in the list as possible to a stream
40 | func writeToStream(_ stream: OutputStream) -> Bool {
41 | var result = true
42 | var stopIndex: Int?
43 |
44 | for (chainIndex, record) in chain.enumerated() {
45 | let written = writeData(record.data, toStream: stream, startingAtOffset:record.offset)
46 | if written < 0 {
47 | result = false
48 | break
49 | }
50 | if written < (record.data.count - record.offset) {
51 | // Failed to write all of the remaining data in this blob, update the offset.
52 | chain[chainIndex] = (record.data, record.offset + written)
53 | stopIndex = chainIndex
54 | break
55 | }
56 | }
57 |
58 | if let removeEnd = stopIndex {
59 | // We did not write all of the data, remove what was written.
60 | if removeEnd > 0 {
61 | chain.removeSubrange(0...size)
95 | sin6.sin6_family = sa_family_t(AF_INET6)
96 | sin6.sin6_port = in_port_t(0)
97 | sin6.sin6_addr = in6addr_any
98 | sin6.sin6_scope_id = __uint32_t(0)
99 | sin6.sin6_flowinfo = __uint32_t(0)
100 | }
101 |
102 | convenience init(otherAddress: SocketAddress6) {
103 | self.init()
104 | sin6 = otherAddress.sin6
105 | }
106 |
107 | /// Set the IPv6 address from a string.
108 | func setFromString(_ str: String) -> Bool {
109 | return str.withCString({ cs in inet_pton(AF_INET6, cs, &sin6.sin6_addr) }) == 1
110 | }
111 |
112 | /// Set the port.
113 | func setPort(_ port: Int) {
114 | sin6.sin6_port = in_port_t(UInt16(port).bigEndian)
115 | }
116 | }
117 |
118 | /// An object containing a sockaddr_in structure.
119 | class SocketAddress {
120 |
121 | // MARK: Properties
122 |
123 | /// The sockaddr_in structure.
124 | var sin: sockaddr_in
125 |
126 | /// The IPv4 address in string form.
127 | var stringValue: String? {
128 | return withUnsafePointer(to: &sin) { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { saToString($0) } }
129 | }
130 |
131 | // MARK: Initializers
132 |
133 | init() {
134 | sin = sockaddr_in(sin_len:__uint8_t(MemoryLayout.size), sin_family:sa_family_t(AF_INET), sin_port:in_port_t(0), sin_addr:in_addr(s_addr: 0), sin_zero:(Int8(0), Int8(0), Int8(0), Int8(0), Int8(0), Int8(0), Int8(0), Int8(0)))
135 | }
136 |
137 | convenience init(otherAddress: SocketAddress) {
138 | self.init()
139 | sin = otherAddress.sin
140 | }
141 |
142 | /// Set the IPv4 address from a string.
143 | func setFromString(_ str: String) -> Bool {
144 | return str.withCString({ cs in inet_pton(AF_INET, cs, &sin.sin_addr) }) == 1
145 | }
146 |
147 | /// Set the port.
148 | func setPort(_ port: Int) {
149 | sin.sin_port = in_port_t(UInt16(port).bigEndian)
150 | }
151 |
152 | /// Increment the address by a given amount.
153 | func increment(_ amount: UInt32) {
154 | let networkAddress = sin.sin_addr.s_addr.byteSwapped + amount
155 | sin.sin_addr.s_addr = networkAddress.byteSwapped
156 | }
157 |
158 | /// Get the difference between this address and another address.
159 | func difference(_ otherAddress: SocketAddress) -> Int64 {
160 | return Int64(sin.sin_addr.s_addr.byteSwapped) - Int64(otherAddress.sin.sin_addr.s_addr.byteSwapped)
161 | }
162 | }
163 |
164 | // MARK: Utility Functions
165 |
166 | /// Convert a sockaddr structure to a string.
167 | func saToString(_ sa: UnsafePointer) -> String? {
168 | var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST))
169 | var portBuffer = [CChar](repeating: 0, count: Int(NI_MAXSERV))
170 |
171 | guard getnameinfo(sa, socklen_t(sa.pointee.sa_len), &hostBuffer, socklen_t(hostBuffer.count), &portBuffer, socklen_t(portBuffer.count), NI_NUMERICHOST | NI_NUMERICSERV) == 0
172 | else { return nil }
173 |
174 | return String(cString: hostBuffer)
175 | }
176 |
177 | /// Write a blob of data to a stream starting from a particular offset.
178 | func writeData(_ data: Data, toStream stream: OutputStream, startingAtOffset offset: Int) -> Int {
179 | var written = 0
180 | var currentOffset = offset
181 | while stream.hasSpaceAvailable && currentOffset < data.count {
182 |
183 | let writeResult = stream.write((data as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count) + currentOffset, maxLength: data.count - currentOffset)
184 | guard writeResult >= 0 else { return writeResult }
185 |
186 | written += writeResult
187 | currentOffset += writeResult
188 | }
189 |
190 | return written
191 | }
192 |
193 | /// Create a SimpleTunnel protocol message dictionary.
194 | public func createMessagePropertiesForConnection(_ connectionIdentifier: Int, commandType: TunnelCommand, extraProperties: [String: AnyObject] = [:]) -> [String: AnyObject] {
195 | // Start out with the "extra properties" that the caller specified.
196 | var properties = extraProperties
197 |
198 | // Add in the standard properties common to all messages.
199 | properties[TunnelMessageKey.Identifier.rawValue] = connectionIdentifier as AnyObject?
200 | properties[TunnelMessageKey.Command.rawValue] = commandType.rawValue as AnyObject?
201 |
202 | return properties
203 | }
204 |
205 | /// Keys in the tunnel server configuration plist.
206 | public enum SettingsKey: String {
207 | case IPv4 = "IPv4"
208 | case DNS = "DNS"
209 | case Proxies = "Proxies"
210 | case Pool = "Pool"
211 | case StartAddress = "StartAddress"
212 | case EndAddress = "EndAddress"
213 | case Servers = "Servers"
214 | case SearchDomains = "SearchDomains"
215 | case Address = "Address"
216 | case Netmask = "Netmask"
217 | case Routes = "Routes"
218 | }
219 |
220 | /// Get a value from a plist given a list of keys.
221 | public func getValueFromPlist(_ plist: [NSObject: AnyObject], keyArray: [SettingsKey]) -> AnyObject? {
222 | var subPlist = plist
223 | for (index, key) in keyArray.enumerated() {
224 | if index == keyArray.count - 1 {
225 | return subPlist[key.rawValue as NSString]
226 | }
227 | else if let subSubPlist = subPlist[key.rawValue as NSString] as? [NSObject: AnyObject] {
228 | subPlist = subSubPlist
229 | }
230 | else {
231 | break
232 | }
233 | }
234 |
235 | return nil
236 | }
237 |
238 | /// Create a new range by incrementing the start of the given range by a given ammount.
239 | func rangeByMovingStartOfRange(_ range: Range, byCount: Int) -> CountableRange {
240 | return (range.lowerBound + byCount)..= 0 else {
56 | simpleTunnelLog("start address (\(startAddress)) is greater than end address (\(endAddress))")
57 | return
58 | }
59 |
60 | baseAddress.sin = start.sin
61 | size = UInt64(difference)
62 | inUseMask = [Bool](repeating: false, count: Int(size))
63 | }
64 |
65 | /// Allocate an address from the pool.
66 | func allocateAddress() -> String? {
67 | var result: String?
68 |
69 | queue.sync() {
70 | let address = SocketAddress(otherAddress: self.baseAddress)
71 |
72 | // Look for an address that is not currently allocated
73 | for (index, inUse) in self.inUseMask.enumerated() {
74 | if !inUse {
75 | address.increment(UInt32(index))
76 | self.inUseMask[index] = true
77 | result = address.stringValue
78 | break
79 | }
80 | }
81 | }
82 |
83 | simpleTunnelLog("Allocated address \(String(describing: result))")
84 | return result
85 | }
86 |
87 | /// Deallocate an address in the pool.
88 | func deallocateAddress(addrString: String) {
89 | queue.sync() {
90 | let address = SocketAddress()
91 |
92 | guard address.setFromString(addrString) else { return }
93 |
94 | let difference = address.difference(self.baseAddress)
95 | if difference >= 0 && difference < Int64(self.inUseMask.count) {
96 | self.inUseMask[Int(difference)] = false
97 | }
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/tunnel_server/ServerConfiguration.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ServerConfiguration class. The ServerConfiguration class is used to parse the SimpleTunnel server configuration.
7 | */
8 |
9 | import Foundation
10 | import SystemConfiguration
11 |
12 | /// An object containing configuration settings for the SimpleTunnel server.
13 | class ServerConfiguration {
14 |
15 | // MARK: Properties
16 |
17 | /// A dictionary containing configuration parameters.
18 | var configuration: [String: AnyObject]
19 |
20 | /// A pool of IP addresses to allocate to clients.
21 | var addressPool: AddressPool?
22 |
23 | // MARK: Initializers
24 |
25 | init() {
26 | configuration = [String: AnyObject]()
27 | addressPool = nil
28 | }
29 |
30 | // MARK: Interface
31 |
32 | /// Read the configuration settings from a plist on disk.
33 | func loadFromFileAtPath(path: String) -> Bool {
34 |
35 | guard let fileStream = InputStream(fileAtPath: path) else {
36 | simpleTunnelLog("Failed to open \(path) for reading")
37 | return false
38 | }
39 |
40 | fileStream.open()
41 |
42 | var newConfiguration: [String: AnyObject]
43 | do {
44 | newConfiguration = try PropertyListSerialization.propertyList(with: fileStream, options: .mutableContainers, format: nil) as! [String: AnyObject]
45 | }
46 | catch {
47 | simpleTunnelLog("Failed to read the configuration from \(path): \(error)")
48 | return false
49 | }
50 |
51 | guard let startAddress = getValueFromPlist(newConfiguration as [NSObject : AnyObject], keyArray: [.IPv4, .Pool, .StartAddress]) as? String else {
52 | simpleTunnelLog("Missing v4 start address")
53 | return false
54 | }
55 | guard let endAddress = getValueFromPlist(newConfiguration as [NSObject : AnyObject], keyArray: [.IPv4, .Pool, .EndAddress]) as? String else {
56 | simpleTunnelLog("Missing v4 end address")
57 | return false
58 | }
59 |
60 | addressPool = AddressPool(startAddress: startAddress, endAddress: endAddress)
61 |
62 | // The configuration dictionary gets sent to clients as the tunnel settings dictionary. Remove the IP pool parameters.
63 | if let value = newConfiguration[SettingsKey.IPv4.rawValue] as? [NSObject: AnyObject] {
64 | var IPv4Dictionary = value
65 |
66 | IPv4Dictionary.removeValue(forKey: SettingsKey.Pool.rawValue as NSObject)
67 | newConfiguration[SettingsKey.IPv4.rawValue] = IPv4Dictionary as AnyObject
68 | }
69 |
70 | if !newConfiguration.keys.contains(where: { $0 == SettingsKey.DNS.rawValue }) {
71 | // The configuration does not specify any DNS configuration, so get the current system default resolver.
72 | let (DNSServers, DNSSearchDomains) = ServerConfiguration.copyDNSConfigurationFromSystem()
73 |
74 | newConfiguration[SettingsKey.DNS.rawValue] = [
75 | SettingsKey.Servers.rawValue: DNSServers,
76 | SettingsKey.SearchDomains.rawValue: DNSSearchDomains
77 | ] as AnyObject
78 | }
79 |
80 | configuration = newConfiguration
81 |
82 | return true
83 | }
84 |
85 | /// Copy the default resolver configuration from the system on which the server is running.
86 | class func copyDNSConfigurationFromSystem() -> ([String], [String]) {
87 | let globalDNSKey = SCDynamicStoreKeyCreateNetworkGlobalEntity(kCFAllocatorDefault, kSCDynamicStoreDomainState, kSCEntNetDNS)
88 | var DNSServers = [String]()
89 | var DNSSearchDomains = [String]()
90 |
91 | // The default resolver configuration can be obtained from State:/Network/Global/DNS in the dynamic store.
92 |
93 | if let globalDNS = SCDynamicStoreCopyValue(nil, globalDNSKey) as? [NSObject: AnyObject],
94 | let servers = globalDNS[kSCPropNetDNSServerAddresses as NSObject] as? [String]
95 | {
96 | if let searchDomains = globalDNS[kSCPropNetDNSSearchDomains as NSObject] as? [String] {
97 | DNSSearchDomains = searchDomains
98 | }
99 | DNSServers = servers
100 | }
101 |
102 | return (DNSServers, DNSSearchDomains)
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/tunnel_server/ServerConnection.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ServerConnection class. The ServerConnection class encapsulates and decapsulates a stream of network data in the server side of the SimpleTunnel tunneling protocol.
7 | */
8 |
9 | import Foundation
10 |
11 | /// An object representing the server side of a logical flow of TCP network data in the SimpleTunnel tunneling protocol.
12 | class ServerConnection: Connection, StreamDelegate {
13 |
14 | // MARK: Properties
15 |
16 | /// The stream used to read network data from the connection.
17 | var readStream: InputStream?
18 |
19 | /// The stream used to write network data to the connection.
20 | var writeStream: OutputStream?
21 |
22 | // MARK: Interface
23 |
24 | /// Open the connection to a host and port.
25 | func open(host: String, port: Int) -> Bool {
26 | simpleTunnelLog("Connection \(identifier) connecting to \(host):\(port)")
27 |
28 | Stream.getStreamsToHost(withName: host, port: port, inputStream: &readStream, outputStream: &writeStream)
29 |
30 | guard let newReadStream = readStream, let newWriteStream = writeStream else {
31 | return false
32 | }
33 |
34 | for stream in [newReadStream, newWriteStream] {
35 | stream.delegate = self
36 | stream.open()
37 | stream.schedule(in: .main, forMode: RunLoopMode.defaultRunLoopMode)
38 | }
39 |
40 | return true
41 | }
42 |
43 | // MARK: Connection
44 |
45 | /// Close the connection.
46 | override func closeConnection(_ direction: TunnelConnectionCloseDirection) {
47 | super.closeConnection(direction)
48 |
49 | if let stream = writeStream, isClosedForWrite && savedData.isEmpty {
50 | if let error = stream.streamError {
51 | simpleTunnelLog("Connection \(identifier) write stream error: \(error)")
52 | }
53 |
54 | stream.remove(from: .main, forMode: RunLoopMode.defaultRunLoopMode)
55 | stream.close()
56 | stream.delegate = nil
57 | writeStream = nil
58 | }
59 |
60 | if let stream = readStream, isClosedForRead {
61 | if let error = stream.streamError {
62 | simpleTunnelLog("Connection \(identifier) read stream error: \(error)")
63 | }
64 |
65 | stream.remove(from: .main, forMode: RunLoopMode.defaultRunLoopMode)
66 | stream.close()
67 | stream.delegate = nil
68 | readStream = nil
69 | }
70 | }
71 |
72 | /// Abort the connection.
73 | override func abort(_ error: Int = 0) {
74 | super.abort(error)
75 | closeConnection(.all)
76 | }
77 |
78 | /// Stop reading from the connection.
79 | override func suspend() {
80 | if let stream = readStream {
81 | stream.remove(from: .main, forMode: RunLoopMode.defaultRunLoopMode)
82 | }
83 | }
84 |
85 | /// Start reading from the connection.
86 | override func resume() {
87 | if let stream = readStream {
88 | stream.schedule(in: .main, forMode: RunLoopMode.defaultRunLoopMode)
89 | }
90 | }
91 |
92 | /// Send data over the connection.
93 | override func sendData(_ data: Data) {
94 | guard let stream = writeStream else { return }
95 | var written = 0
96 |
97 | if savedData.isEmpty {
98 | written = writeData(data as Data, toStream: stream, startingAtOffset: 0)
99 |
100 | if written < data.count {
101 | // We could not write all of the data to the connection. Tell the client to stop reading data for this connection.
102 | stream.remove(from: .main, forMode: RunLoopMode.defaultRunLoopMode)
103 | tunnel?.sendSuspendForConnection(identifier)
104 | }
105 | }
106 |
107 | if written < data.count {
108 | savedData.append(data as Data, offset: written)
109 | }
110 | }
111 |
112 | // MARK: NSStreamDelegate
113 |
114 | /// Handle an event on a stream.
115 | func stream(aStream: Stream, handleEvent eventCode: Stream.Event) {
116 | switch aStream {
117 |
118 | case writeStream!:
119 | switch eventCode {
120 | case [.hasSpaceAvailable]:
121 | if !savedData.isEmpty {
122 | guard savedData.writeToStream(writeStream!) else {
123 | tunnel?.sendCloseType(.all, forConnection: identifier)
124 | abort()
125 | break
126 | }
127 |
128 | if savedData.isEmpty {
129 | writeStream?.remove(from: .main, forMode: RunLoopMode.defaultRunLoopMode)
130 | if isClosedForWrite {
131 | closeConnection(.write)
132 | }
133 | else {
134 | tunnel?.sendResumeForConnection(identifier)
135 | }
136 | }
137 | }
138 | else {
139 | writeStream?.remove(from: .main, forMode: RunLoopMode.defaultRunLoopMode)
140 | }
141 |
142 | case [.endEncountered]:
143 | tunnel?.sendCloseType(.read, forConnection: identifier)
144 | closeConnection(.write)
145 |
146 | case [.errorOccurred]:
147 | tunnel?.sendCloseType(.all, forConnection: identifier)
148 | abort()
149 |
150 | default:
151 | break
152 | }
153 |
154 | case readStream!:
155 | switch eventCode {
156 | case [.hasBytesAvailable]:
157 | if let stream = readStream {
158 | while stream.hasBytesAvailable {
159 | var readBuffer = [UInt8](repeating: 0, count: 8192)
160 | let bytesRead = stream.read(&readBuffer, maxLength: readBuffer.count)
161 |
162 | if bytesRead < 0 {
163 | abort()
164 | break
165 | }
166 |
167 | if bytesRead == 0 {
168 | simpleTunnelLog("\(identifier): got EOF, sending close")
169 | tunnel?.sendCloseType(.write, forConnection: identifier)
170 | closeConnection(.read)
171 | break
172 | }
173 |
174 | let readData = NSData(bytes: readBuffer, length: bytesRead)
175 | tunnel?.sendData(readData as Data, forConnection: identifier)
176 | }
177 | }
178 |
179 | case [.endEncountered]:
180 | tunnel?.sendCloseType(.write, forConnection: identifier)
181 | closeConnection(.read)
182 |
183 | case [.errorOccurred]:
184 | if let serverTunnel = tunnel as? ServerTunnel {
185 | serverTunnel.sendOpenResultForConnection(connectionIdentifier: identifier, resultCode: .timeout)
186 | serverTunnel.sendCloseType(.all, forConnection: identifier)
187 | abort()
188 | }
189 |
190 | case [.openCompleted]:
191 | if let serverTunnel = tunnel as? ServerTunnel {
192 | serverTunnel.sendOpenResultForConnection(connectionIdentifier: identifier, resultCode: .success)
193 | }
194 |
195 | default:
196 | break
197 | }
198 | default:
199 | break
200 | }
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/tunnel_server/ServerTunnel.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ServerTunnel class. The ServerTunnel class implements the server side of the SimpleTunnel tunneling protocol.
7 | */
8 |
9 | import Foundation
10 | import SystemConfiguration
11 |
12 | /// The server-side implementation of the SimpleTunnel protocol.
13 | class ServerTunnel: Tunnel, TunnelDelegate, StreamDelegate {
14 |
15 | // MARK: Properties
16 |
17 | /// The stream used to read data from the tunnel TCP connection.
18 | var readStream: InputStream?
19 |
20 | /// The stream used to write data to the tunnel TCP connection.
21 | var writeStream: OutputStream?
22 |
23 | /// A buffer where the data for the current packet is accumulated.
24 | let packetBuffer = NSMutableData()
25 |
26 | /// The number of bytes remaining to be read for the current packet.
27 | var packetBytesRemaining = 0
28 |
29 | /// The server configuration parameters.
30 | static var configuration = ServerConfiguration()
31 |
32 | /// The delegate for the network service published by the server.
33 | static var serviceDelegate = ServerDelegate()
34 |
35 | // MARK: Initializers
36 |
37 | init(newReadStream: InputStream, newWriteStream: OutputStream) {
38 | super.init()
39 | delegate = self
40 |
41 | for stream in [newReadStream, newWriteStream] {
42 | stream.delegate = self
43 | stream.open()
44 | stream.schedule(in: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode)
45 | }
46 | readStream = newReadStream
47 | writeStream = newWriteStream
48 | }
49 |
50 | // MARK: Class Methods
51 |
52 | /// Start the network service.
53 | class func startListeningOnPort(port: Int32) -> NetService {
54 | let service = NetService(domain:Tunnel.serviceDomain, type:Tunnel.serviceType, name: "", port: port)
55 |
56 | simpleTunnelLog("Starting network service on port \(port)")
57 |
58 | service.delegate = ServerTunnel.serviceDelegate
59 | service.publish(options: NetService.Options.listenForConnections)
60 | service.schedule(in: .main, forMode: RunLoopMode.defaultRunLoopMode)
61 |
62 | return service
63 | }
64 |
65 | /// Load the configuration from disk.
66 | class func initializeWithConfigurationFile(path: String) -> Bool {
67 | return ServerTunnel.configuration.loadFromFileAtPath(path: path)
68 | }
69 |
70 | // MARK: Interface
71 |
72 | /// Handle a bytes available event on the read stream.
73 | func handleBytesAvailable() -> Bool {
74 |
75 | guard let stream = readStream else { return false }
76 | var readBuffer = [UInt8](repeating: 0, count: Tunnel.maximumMessageSize)
77 |
78 | repeat {
79 | var toRead = 0
80 | var bytesRead = 0
81 |
82 | if packetBytesRemaining == 0 {
83 | // Currently reading the total length of the packet.
84 | toRead = MemoryLayout.size - packetBuffer.length
85 | }
86 | else {
87 | // Currently reading the packet payload.
88 | toRead = packetBytesRemaining > readBuffer.count ? readBuffer.count : packetBytesRemaining
89 | }
90 |
91 | bytesRead = stream.read(&readBuffer, maxLength: toRead)
92 |
93 | guard bytesRead > 0 else {
94 | return false
95 | }
96 |
97 | packetBuffer.append(readBuffer, length: bytesRead)
98 |
99 | if packetBytesRemaining == 0 {
100 | // Reading the total length, see if the 4 length bytes have been received.
101 | if packetBuffer.length == MemoryLayout.size {
102 | var totalLength: UInt32 = 0
103 | packetBuffer.getBytes(&totalLength, length: MemoryLayout.size(ofValue: totalLength))
104 |
105 | guard totalLength <= UInt32(Tunnel.maximumMessageSize) else { return false }
106 |
107 | // Compute the length of the payload.
108 | packetBytesRemaining = Int(totalLength) - MemoryLayout.size(ofValue: totalLength)
109 | packetBuffer.length = 0
110 | }
111 | }
112 | else {
113 | // Read a portion of the payload.
114 | packetBytesRemaining -= bytesRead
115 | if packetBytesRemaining == 0 {
116 | // The entire packet has been received, process it.
117 | if !handlePacket(packetBuffer as Data) {
118 | return false
119 | }
120 | packetBuffer.length = 0
121 | }
122 | }
123 | } while stream.hasBytesAvailable
124 |
125 | return true
126 | }
127 |
128 | /// Send an "Open Result" message to the client.
129 | func sendOpenResultForConnection(connectionIdentifier: Int, resultCode: TunnelConnectionOpenResult) {
130 | let properties = createMessagePropertiesForConnection(connectionIdentifier, commandType: .openResult, extraProperties:[
131 | TunnelMessageKey.ResultCode.rawValue: resultCode.rawValue as AnyObject
132 | ])
133 |
134 | if !sendMessage(properties) {
135 | simpleTunnelLog("Failed to send an open result for connection \(connectionIdentifier)")
136 | }
137 | }
138 |
139 | /// Handle a "Connection Open" message received from the client.
140 | func handleConnectionOpen(properties: [String: AnyObject]) {
141 | guard let connectionIdentifier = properties[TunnelMessageKey.Identifier.rawValue] as? Int,
142 | let tunnelLayerNumber = properties[TunnelMessageKey.TunnelType.rawValue] as? Int,
143 | let tunnelLayer = TunnelLayer(rawValue: tunnelLayerNumber)
144 | else { return }
145 |
146 | switch tunnelLayer {
147 | case .app:
148 |
149 | guard let flowKindNumber = properties[TunnelMessageKey.AppProxyFlowType.rawValue] as? Int,
150 | let flowKind = AppProxyFlowKind(rawValue: flowKindNumber)
151 | else { break }
152 |
153 | switch flowKind {
154 | case .tcp:
155 | guard let host = properties[TunnelMessageKey.Host.rawValue] as? String,
156 | let port = properties[TunnelMessageKey.Port.rawValue] as? NSNumber
157 | else { break }
158 | let newConnection = ServerConnection(connectionIdentifier: connectionIdentifier, parentTunnel: self)
159 | guard newConnection.open(host: host, port: port.intValue) else {
160 | newConnection.closeConnection(.all)
161 | break
162 | }
163 |
164 | case .udp:
165 | let _ = UDPServerConnection(connectionIdentifier: connectionIdentifier, parentTunnel: self)
166 | sendOpenResultForConnection(connectionIdentifier: connectionIdentifier, resultCode: .success)
167 | }
168 |
169 | case .ip:
170 | let newConnection = ServerTunnelConnection(connectionIdentifier: connectionIdentifier, parentTunnel: self)
171 | guard newConnection.open() else {
172 | newConnection.closeConnection(.all)
173 | break
174 | }
175 | }
176 | }
177 |
178 | // MARK: NSStreamDelegate
179 |
180 | /// Handle a stream event.
181 | func stream(aStream: Stream, handleEvent eventCode: Stream.Event) {
182 | switch aStream {
183 |
184 | case writeStream!:
185 | switch eventCode {
186 | case [.hasSpaceAvailable]:
187 | // Send any buffered data.
188 | if !savedData.isEmpty {
189 | guard savedData.writeToStream(writeStream!) else {
190 | closeTunnel()
191 | delegate?.tunnelDidClose(self)
192 | break
193 | }
194 |
195 | if savedData.isEmpty {
196 | for connection in connections.values {
197 | connection.resume()
198 | }
199 | }
200 | }
201 |
202 | case [.errorOccurred]:
203 | closeTunnel()
204 | delegate?.tunnelDidClose(self)
205 |
206 | default:
207 | break
208 | }
209 |
210 | case readStream!:
211 | var needCloseTunnel = false
212 | switch eventCode {
213 | case [.hasBytesAvailable]:
214 | needCloseTunnel = !handleBytesAvailable()
215 |
216 | case [.openCompleted]:
217 | delegate?.tunnelDidOpen(self)
218 |
219 | case [.errorOccurred], [.endEncountered]:
220 | needCloseTunnel = true
221 |
222 | default:
223 | break
224 | }
225 |
226 | if needCloseTunnel {
227 | closeTunnel()
228 | delegate?.tunnelDidClose(self)
229 | }
230 |
231 | default:
232 | break
233 | }
234 |
235 | }
236 |
237 | // MARK: Tunnel
238 |
239 | /// Close the tunnel.
240 | override func closeTunnel() {
241 |
242 | if let stream = readStream {
243 | if let error = stream.streamError {
244 | simpleTunnelLog("Tunnel read stream error: \(error)")
245 | }
246 |
247 | let socketData = CFReadStreamCopyProperty(stream, CFStreamPropertyKey.socketNativeHandle) as? NSData
248 |
249 | stream.remove(from: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode)
250 | stream.close()
251 | stream.delegate = nil
252 | readStream = nil
253 |
254 | if let data = socketData {
255 | var socket: CFSocketNativeHandle = 0
256 | data.getBytes(&socket, length: MemoryLayout.size(ofValue: socket))
257 | close(socket)
258 | }
259 | }
260 |
261 | if let stream = writeStream {
262 | if let error = stream.streamError {
263 | simpleTunnelLog("Tunnel write stream error: \(error)")
264 | }
265 |
266 | stream.remove(from: .main, forMode: RunLoopMode.defaultRunLoopMode)
267 | stream.close()
268 | stream.delegate = nil
269 | }
270 |
271 | super.closeTunnel()
272 | }
273 |
274 | /// Handle a message received from the client.
275 | override func handleMessage(_ commandType: TunnelCommand, properties: [String: AnyObject], connection: Connection?) -> Bool {
276 | switch commandType {
277 | case .open:
278 | handleConnectionOpen(properties: properties)
279 |
280 | case .fetchConfiguration:
281 | var personalized = ServerTunnel.configuration.configuration
282 | personalized.removeValue(forKey: SettingsKey.IPv4.rawValue)
283 | let messageProperties = createMessagePropertiesForConnection(0, commandType: .fetchConfiguration, extraProperties: [TunnelMessageKey.Configuration.rawValue: personalized as AnyObject])
284 | let _ = sendMessage(messageProperties)
285 |
286 | default:
287 | break
288 | }
289 | return true
290 | }
291 |
292 | /// Write data to the tunnel connection.
293 | override func writeDataToTunnel(_ data: Data, startingAtOffset: Int) -> Int {
294 | guard let stream = writeStream else { return -1 }
295 | return writeData(data as Data, toStream: stream, startingAtOffset:startingAtOffset)
296 | }
297 |
298 | // MARK: TunnelDelegate
299 |
300 | /// Handle the "tunnel open" event.
301 | func tunnelDidOpen(_ targetTunnel: Tunnel) {
302 | }
303 |
304 | /// Handle the "tunnel closed" event.
305 | func tunnelDidClose(_ targetTunnel: Tunnel) {
306 | }
307 |
308 | /// Handle the "tunnel did send configuration" event.
309 | func tunnelDidSendConfiguration(_ targetTunnel: Tunnel, configuration: [String : AnyObject]) {
310 | }
311 | }
312 |
313 | /// An object that servers as the delegate for the network service published by the server.
314 | class ServerDelegate : NSObject, NetServiceDelegate {
315 |
316 | // MARK: NSNetServiceDelegate
317 |
318 | /// Handle the "failed to publish" event.
319 | func netService(sender: NetService, didNotPublish errorDict: [String : NSNumber]) {
320 | simpleTunnelLog("Failed to publish network service")
321 | exit(1)
322 | }
323 |
324 | /// Handle the "published" event.
325 | func netServiceDidPublish(sender: NetService) {
326 | simpleTunnelLog("Network service published successfully")
327 | }
328 |
329 | /// Handle the "new connection" event.
330 | func netService(_ sender: NetService, didAcceptConnectionWith inputStream: InputStream, outputStream: OutputStream) {
331 | simpleTunnelLog("Accepted a new connection")
332 | _ = ServerTunnel(newReadStream: inputStream, newWriteStream: outputStream)
333 | }
334 |
335 | /// Handle the "stopped" event.
336 | func netServiceDidStop(sender: NetService) {
337 | simpleTunnelLog("Network service stopped")
338 | exit(0)
339 | }
340 | }
341 |
342 |
--------------------------------------------------------------------------------
/tunnel_server/ServerTunnelConnection.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the ServerTunnelConnection class. The ServerTunnelConnection class handles the encapsulation and decapsulation of IP packets in the server side of the SimpleTunnel tunneling protocol.
7 | */
8 |
9 | import Foundation
10 | import Darwin
11 |
12 | /// An object that provides a bridge between a logical flow of packets in the SimpleTunnel protocol and a UTUN interface.
13 | class ServerTunnelConnection: Connection {
14 |
15 | // MARK: Properties
16 |
17 | /// The virtual address of the tunnel.
18 | var tunnelAddress: String?
19 |
20 | /// The name of the UTUN interface.
21 | var utunName: String?
22 |
23 | /// A dispatch source for the UTUN interface socket.
24 | var utunSource: DispatchSourceProtocol?
25 |
26 | /// A flag indicating if reads from the UTUN interface are suspended.
27 | var isSuspended = false
28 |
29 | // MARK: Interface
30 |
31 | /// Send an "open result" message with optionally the tunnel settings.
32 | func sendOpenResult(result: TunnelConnectionOpenResult, extraProperties: [String: AnyObject] = [:]) {
33 | guard let serverTunnel = tunnel else { return }
34 |
35 | var resultProperties = extraProperties
36 | resultProperties[TunnelMessageKey.ResultCode.rawValue] = result.rawValue as AnyObject
37 |
38 | let properties = createMessagePropertiesForConnection(identifier, commandType: .openResult, extraProperties: resultProperties)
39 |
40 | let _ = serverTunnel.sendMessage(properties)
41 | }
42 |
43 | /// "Open" the connection by setting up the UTUN interface.
44 | func open() -> Bool {
45 |
46 | // Allocate the tunnel virtual address.
47 | guard let address = ServerTunnel.configuration.addressPool?.allocateAddress() else {
48 | simpleTunnelLog("Failed to allocate a tunnel address")
49 | sendOpenResult(result: .refused)
50 | return false
51 | }
52 |
53 | // Create the virtual interface and assign the address.
54 | guard setupVirtualInterface(address: address) else {
55 | simpleTunnelLog("Failed to set up the virtual interface")
56 | ServerTunnel.configuration.addressPool?.deallocateAddress(addrString: address)
57 | sendOpenResult(result: .internalError)
58 | return false
59 | }
60 |
61 | tunnelAddress = address
62 |
63 | var response = [String: AnyObject]()
64 |
65 | // Create a copy of the configuration, so that it can be personalized with the tunnel virtual interface.
66 | var personalized = ServerTunnel.configuration.configuration
67 | guard let IPv4Dictionary = personalized[SettingsKey.IPv4.rawValue] as? [NSObject: AnyObject] else {
68 | simpleTunnelLog("No IPv4 Settings available")
69 | sendOpenResult(result: .internalError)
70 | return false
71 | }
72 |
73 | // Set up the "IPv4" sub-dictionary to contain the tunne virtual address and network mask.
74 | var newIPv4Dictionary = IPv4Dictionary
75 | newIPv4Dictionary[SettingsKey.Address.rawValue as NSObject] = tunnelAddress as AnyObject
76 | newIPv4Dictionary[SettingsKey.Netmask.rawValue as NSObject] = "255.255.255.255" as AnyObject
77 | personalized[SettingsKey.IPv4.rawValue] = newIPv4Dictionary as AnyObject
78 | response[TunnelMessageKey.Configuration.rawValue] = personalized as AnyObject
79 |
80 | // Send the personalized configuration along with the "open result" message.
81 | sendOpenResult(result: .success, extraProperties: response)
82 |
83 | return true
84 | }
85 |
86 | /// Create a UTUN interface.
87 | func createTUNInterface() -> Int32 {
88 |
89 | let utunSocket = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL)
90 | guard utunSocket >= 0 else {
91 | simpleTunnelLog("Failed to open a kernel control socket")
92 | return -1
93 | }
94 |
95 | let controlIdentifier = getUTUNControlIdentifier(utunSocket)
96 | guard controlIdentifier > 0 else {
97 | simpleTunnelLog("Failed to get the control ID for the utun kernel control")
98 | close(utunSocket)
99 | return -1
100 | }
101 |
102 | // Connect the socket to the UTUN kernel control.
103 | var socketAddressControl = sockaddr_ctl(sc_len: UInt8(MemoryLayout.size), sc_family: UInt8(AF_SYSTEM), ss_sysaddr: UInt16(AF_SYS_CONTROL), sc_id: controlIdentifier, sc_unit: 0, sc_reserved: (0, 0, 0, 0, 0))
104 |
105 | let connectResult = withUnsafePointer(to: &socketAddressControl) {
106 | $0.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout.size, {
107 | connect(utunSocket, $0, socklen_t(MemoryLayout.size(ofValue: socketAddressControl)))
108 | })
109 | }
110 |
111 | if let errorString = String(utf8String: strerror(errno)), connectResult < 0 {
112 | simpleTunnelLog("Failed to create a utun interface: \(errorString)")
113 | close(utunSocket)
114 | return -1
115 | }
116 |
117 | return utunSocket
118 | }
119 |
120 | /// Get the name of a UTUN interface the associated socket.
121 | func getTUNInterfaceName(utunSocket: Int32) -> String? {
122 | var buffer = [Int8](repeating: 0, count: Int(IFNAMSIZ))
123 | var bufferSize: socklen_t = socklen_t(buffer.count)
124 | let resultCode = getsockopt(utunSocket, SYSPROTO_CONTROL, getUTUNNameOption(), &buffer, &bufferSize)
125 | if let errorString = String(utf8String: strerror(errno)), resultCode < 0 {
126 | simpleTunnelLog("getsockopt failed while getting the utun interface name: \(errorString)")
127 | return nil
128 | }
129 | return String (utf8String: &buffer)
130 | }
131 |
132 | /// Set up the UTUN interface, start reading packets.
133 | func setupVirtualInterface(address: String) -> Bool {
134 | let utunSocket = createTUNInterface()
135 | guard let interfaceName = getTUNInterfaceName(utunSocket: utunSocket), utunSocket >= 0 &&
136 | setUTUNAddress(interfaceName, address)
137 | else { return false }
138 |
139 | startTunnelSource(utunSocket: utunSocket)
140 | utunName = interfaceName
141 | return true
142 | }
143 |
144 | /// Read packets from the UTUN interface.
145 | func readPackets() {
146 | guard let source = utunSource else { return }
147 | var packets = [NSData]()
148 | var protocols = [NSNumber]()
149 |
150 | // We use a 2-element iovec list. The first iovec points to the protocol number of the packet, the second iovec points to the buffer where the packet should be read.
151 | var buffer = [UInt8](repeating:0, count: Tunnel.packetSize)
152 | var protocolNumber: UInt32 = 0
153 | var iovecList = [ iovec(iov_base: &protocolNumber, iov_len: MemoryLayout.size(ofValue: protocolNumber)), iovec(iov_base: &buffer, iov_len: buffer.count) ]
154 | let iovecListPointer = UnsafeBufferPointer(start: &iovecList, count: iovecList.count)
155 | let utunSocket = Int32(source.handle)
156 |
157 | repeat {
158 | let readCount = readv(utunSocket, iovecListPointer.baseAddress, Int32(iovecListPointer.count))
159 |
160 | guard readCount > 0 || errno == EAGAIN else {
161 | if let errorString = String(utf8String: strerror(errno)), readCount < 0 {
162 | simpleTunnelLog("Got an error on the utun socket: \(errorString)")
163 | }
164 | source.cancel()
165 | break
166 | }
167 |
168 | guard readCount > MemoryLayout.size(ofValue: protocolNumber) else { break }
169 |
170 | if protocolNumber.littleEndian == protocolNumber {
171 | protocolNumber = protocolNumber.byteSwapped
172 | }
173 | protocols.append(NSNumber(value: protocolNumber))
174 | packets.append(NSData(bytes: &buffer, length: readCount - MemoryLayout.size(ofValue: protocolNumber)))
175 |
176 | // Buffer up packets so that we can include multiple packets per message. Once we reach a per-message maximum send a "packets" message.
177 | if packets.count == Tunnel.maximumPacketsPerMessage {
178 | tunnel?.sendPackets(packets as [Data], protocols: protocols, forConnection: identifier)
179 | packets = [NSData]()
180 | protocols = [NSNumber]()
181 | if isSuspended { break } // If the entire message could not be sent and the connection is suspended, stop reading packets.
182 | }
183 | } while true
184 |
185 | // If there are unsent packets left over, send them now.
186 | if packets.count > 0 {
187 | tunnel?.sendPackets(packets as [Data], protocols: protocols, forConnection: identifier)
188 | }
189 | }
190 |
191 | /// Start reading packets from the UTUN interface.
192 | func startTunnelSource(utunSocket: Int32) {
193 | guard setSocketNonBlocking(utunSocket) else { return }
194 |
195 | let newSource : DispatchSourceRead = DispatchSource.makeReadSource(fileDescriptor: utunSocket, queue: DispatchQueue.main)
196 | newSource.setCancelHandler {
197 | close(utunSocket)
198 | return
199 | }
200 |
201 | newSource.setEventHandler {
202 | self.readPackets()
203 | }
204 |
205 | newSource.resume()
206 | utunSource = newSource
207 | }
208 |
209 | // MARK: Connection
210 |
211 | /// Abort the connection.
212 | override func abort(_ error: Int = 0) {
213 | super.abort(error)
214 | closeConnection(.all)
215 | }
216 |
217 | /// Close the connection.
218 | override func closeConnection(_ direction: TunnelConnectionCloseDirection) {
219 | super.closeConnection(direction)
220 |
221 | if currentCloseDirection == .all {
222 | if utunSource != nil {
223 | utunSource!.cancel()
224 | }
225 | // De-allocate the address.
226 | if tunnelAddress != nil {
227 | ServerTunnel.configuration.addressPool?.deallocateAddress(addrString: tunnelAddress!)
228 | }
229 | utunName = nil
230 | }
231 | }
232 |
233 | /// Stop reading packets from the UTUN interface.
234 | override func suspend() {
235 | isSuspended = true
236 | if let source = utunSource {
237 | source.suspend()
238 | }
239 | }
240 |
241 | /// Resume reading packets from the UTUN interface.
242 | override func resume() {
243 | isSuspended = false
244 | if let source = utunSource {
245 | source.resume()
246 | readPackets()
247 | }
248 | }
249 |
250 | /// Write packets and associated protocols to the UTUN interface.
251 | override func sendPackets(_ packets: [Data], protocols: [NSNumber]) {
252 | guard let source = utunSource else { return }
253 | let utunSocket = Int32(source.handle)
254 |
255 | for (index, packet) in packets.enumerated() {
256 | guard index < protocols.count else { break }
257 |
258 | var protocolNumber = protocols[index].uint32Value.bigEndian
259 |
260 | packet.withUnsafeBytes({ (pointer : UnsafePointer) in
261 | let buffer = UnsafeMutableRawPointer(mutating: pointer)
262 | var iovecList = [ iovec(iov_base: &protocolNumber, iov_len: MemoryLayout.size(ofValue: protocolNumber)), iovec(iov_base: buffer, iov_len: packet.count) ]
263 |
264 | let writeCount = writev(utunSocket, &iovecList, Int32(iovecList.count))
265 | if writeCount < 0 {
266 | if let errorString = String(utf8String: strerror(errno)) {
267 | simpleTunnelLog("Got an error while writing to utun: \(errorString)")
268 | }
269 | }
270 | else if writeCount < packet.count + MemoryLayout.size(ofValue: protocolNumber) {
271 | simpleTunnelLog("Wrote \(writeCount) bytes of a \(packet.count) byte packet to utun")
272 | }
273 | })
274 |
275 | }
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/tunnel_server/ServerUtils.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file declares utility Objective-C functions used by the SimpleTunnel server.
7 | */
8 |
9 | #ifndef ServerUtils_h
10 | #define ServerUtils_h
11 |
12 | /// Get the identifier for the UTUN interface.
13 | UInt32 getUTUNControlIdentifier(int socket);
14 |
15 | /// Setup a socket as non-blocking.
16 | BOOL setUTUNAddress(NSString *ifname, NSString *address);
17 |
18 | /// Set the IP address on a UTUN interface.
19 | int getUTUNNameOption(void);
20 |
21 | /// Get value of the UTUN iterface name socket option.
22 | BOOL setSocketNonBlocking(int socket);
23 |
24 | #endif /* ServerUtils_h */
25 |
--------------------------------------------------------------------------------
/tunnel_server/ServerUtils.m:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains utility Objective-C code used by the SimpleTunnel server.
7 | */
8 |
9 | @import Foundation;
10 | @import Darwin;
11 |
12 | #import
13 | #import "tunnel_server-Bridging-Header.h"
14 |
15 | UInt32
16 | getUTUNControlIdentifier(int socket)
17 | {
18 | struct ctl_info kernelControlInfo;
19 |
20 | bzero(&kernelControlInfo, sizeof(kernelControlInfo));
21 | strlcpy(kernelControlInfo.ctl_name, UTUN_CONTROL_NAME, sizeof(kernelControlInfo.ctl_name));
22 |
23 | if (ioctl(socket, CTLIOCGINFO, &kernelControlInfo)) {
24 | printf("ioctl failed on kernel control socket: %s\n", strerror(errno));
25 | return 0;
26 | }
27 |
28 | return kernelControlInfo.ctl_id;
29 | }
30 |
31 | BOOL
32 | setSocketNonBlocking(int socket)
33 | {
34 | int currentFlags = fcntl(socket, F_GETFL);
35 | if (currentFlags < 0) {
36 | printf("fcntl(F_GETFL) failed: %s\n", strerror(errno));
37 | return NO;
38 | }
39 |
40 | currentFlags |= O_NONBLOCK;
41 |
42 | if (fcntl(socket, F_SETFL, currentFlags) < 0) {
43 | printf("fcntl(F_SETFL) failed: %s\n", strerror(errno));
44 | return NO;
45 | }
46 |
47 | return YES;
48 | }
49 |
50 | BOOL
51 | setUTUNAddress(NSString *interfaceName, NSString *addressString)
52 | {
53 | struct in_addr address;
54 |
55 | if (inet_pton(AF_INET, [addressString UTF8String], &address) == 1) {
56 | struct ifaliasreq interfaceAliasRequest __attribute__ ((aligned (4)));
57 | struct in_addr mask = { 0xffffffff };
58 | int socketDescriptor = socket(AF_INET, SOCK_DGRAM, 0);
59 |
60 | if (socketDescriptor < 0) {
61 | printf("Failed to create a DGRAM socket: %s\n", strerror(errno));
62 | return NO;
63 | }
64 |
65 | memset(&interfaceAliasRequest, 0, sizeof(interfaceAliasRequest));
66 |
67 | strlcpy(interfaceAliasRequest.ifra_name, [interfaceName UTF8String], sizeof(interfaceAliasRequest.ifra_name));
68 |
69 | interfaceAliasRequest.ifra_addr.sa_family = AF_INET;
70 | interfaceAliasRequest.ifra_addr.sa_len = sizeof(struct sockaddr_in);
71 | memcpy(&((struct sockaddr_in *)&interfaceAliasRequest.ifra_addr)->sin_addr, &address, sizeof(address));
72 |
73 | interfaceAliasRequest.ifra_broadaddr.sa_family = AF_INET;
74 | interfaceAliasRequest.ifra_broadaddr.sa_len = sizeof(struct sockaddr_in);
75 | memcpy(&((struct sockaddr_in *)&interfaceAliasRequest.ifra_broadaddr)->sin_addr, &address, sizeof(address));
76 |
77 | interfaceAliasRequest.ifra_mask.sa_family = AF_INET;
78 | interfaceAliasRequest.ifra_mask.sa_len = sizeof(struct sockaddr_in);
79 | memcpy(&((struct sockaddr_in *)&interfaceAliasRequest.ifra_mask)->sin_addr, &mask, sizeof(mask));
80 |
81 | if (ioctl(socketDescriptor, SIOCAIFADDR, &interfaceAliasRequest) < 0) {
82 | printf("Failed to set the address of %s interface address to %s: %s\n", [interfaceName UTF8String], [addressString UTF8String], strerror(errno));
83 | close(socketDescriptor);
84 | return NO;
85 | }
86 |
87 | close(socketDescriptor);
88 | }
89 |
90 | return YES;
91 | }
92 |
93 | int
94 | getUTUNNameOption(void)
95 | {
96 | return UTUN_OPT_IFNAME;
97 | }
--------------------------------------------------------------------------------
/tunnel_server/UDPServerConnection.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the UDPServerConnection class. The UDPServerConnection class handles the encapsulation and decapsulation of datagrams in the server side of the SimpleTunnel tunneling protocol.
7 | */
8 |
9 | import Foundation
10 | import Darwin
11 |
12 | /// An object representing the server side of a logical flow of UDP network data in the SimpleTunnel tunneling protocol.
13 | class UDPServerConnection: Connection {
14 |
15 | // MARK: Properties
16 |
17 | /// The address family of the UDP socket.
18 | var addressFamily: Int32 = AF_UNSPEC
19 |
20 | /// A dispatch source for reading data from the UDP socket.
21 | var responseSource: DispatchSourceProtocol?
22 |
23 | // MARK: Initializers
24 |
25 | override init(connectionIdentifier: Int, parentTunnel: Tunnel) {
26 | super.init(connectionIdentifier: connectionIdentifier, parentTunnel: parentTunnel)
27 | }
28 |
29 | deinit {
30 | if let source = responseSource {
31 | source.cancel()
32 | }
33 | }
34 |
35 | // MARK: Interface
36 |
37 | /// Convert a sockaddr structure into an IP address string and port.
38 | func getEndpointFromSocketAddress(socketAddressPointer: UnsafePointer) -> (host: String, port: Int)? {
39 | let socketAddress = UnsafePointer(socketAddressPointer).pointee
40 |
41 | switch Int32(socketAddress.sa_family) {
42 | case AF_INET:
43 |
44 | return socketAddressPointer.withMemoryRebound(to: sockaddr_in.self, capacity: MemoryLayout.size) { (socketAddressInetPointer: UnsafePointer) -> (host: String, port: Int) in
45 | let length = Int(INET_ADDRSTRLEN) + 2
46 | var buffer = [CChar](repeating: 0, count: length)
47 | var sin_addr = socketAddressInetPointer.pointee.sin_addr
48 | let hostCString = inet_ntop(AF_INET, &sin_addr, &buffer, socklen_t(length))
49 | let port = Int(UInt16(socketAddressInetPointer.pointee.sin_port).byteSwapped)
50 | return (String(cString: hostCString!), port)
51 | }
52 | case AF_INET6:
53 |
54 | return socketAddressPointer.withMemoryRebound(to: sockaddr_in6.self, capacity: MemoryLayout.size) { (socketAddressInet6Pointer : UnsafePointer) -> (host: String, port: Int) in
55 | let length = Int(INET6_ADDRSTRLEN) + 2
56 | var buffer = [CChar](repeating: 0, count: length)
57 | var sin6_addr = socketAddressInet6Pointer.pointee.sin6_addr
58 | let hostCString = inet_ntop(AF_INET6, &sin6_addr, &buffer, socklen_t(length))
59 | let port = Int(UInt16(socketAddressInet6Pointer.pointee.sin6_port).byteSwapped)
60 | return (String(cString: hostCString!), port)
61 | }
62 |
63 | default:
64 | return nil
65 | }
66 | }
67 |
68 | /// Create a UDP socket
69 | func createSocketWithAddressFamilyFromAddress(address: String) -> Bool {
70 | var sin = sockaddr_in()
71 | var sin6 = sockaddr_in6()
72 | var newSocket: Int32 = -1
73 |
74 | if address.withCString({ cstring in inet_pton(AF_INET6, cstring, &sin6.sin6_addr) }) == 1 {
75 | // IPv6 peer.
76 | newSocket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)
77 | addressFamily = AF_INET6
78 | }
79 | else if address.withCString({ cstring in inet_pton(AF_INET, cstring, &sin.sin_addr) }) == 1 {
80 | // IPv4 peer.
81 | newSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
82 | addressFamily = AF_INET
83 | }
84 |
85 | guard newSocket > 0 else { return false }
86 |
87 | let newResponseSource = DispatchSource.makeReadSource(fileDescriptor: newSocket, queue: DispatchQueue.main)
88 |
89 | newResponseSource.setCancelHandler {
90 | simpleTunnelLog("closing udp socket for connection \(self.identifier)")
91 | let UDPSocket = Int32(newResponseSource.handle)
92 | close(UDPSocket)
93 | }
94 |
95 | newResponseSource.setEventHandler {
96 | guard let source = self.responseSource else { return }
97 |
98 | var socketAddress = sockaddr_storage()
99 | var socketAddressLength = socklen_t(MemoryLayout.size)
100 | let response = [UInt8](repeating: 0, count: 4096)
101 | let UDPSocket = Int32(source.handle)
102 |
103 | let bytesRead = withUnsafeMutablePointer(to: &socketAddress, {
104 | $0.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout.size, {
105 | recvfrom(UDPSocket, UnsafeMutableRawPointer(mutating: response), response.count, 0, $0, &socketAddressLength)
106 | })
107 | })
108 |
109 | guard bytesRead >= 0 else {
110 | if let errorString = String(utf8String: strerror(errno)) {
111 | simpleTunnelLog("recvfrom failed: \(errorString)")
112 | }
113 | self.closeConnection(.all)
114 | return
115 | }
116 |
117 | guard bytesRead > 0 else {
118 | simpleTunnelLog("recvfrom returned EOF")
119 | self.closeConnection(.all)
120 | return
121 | }
122 |
123 | guard let endpoint = withUnsafePointer(to: &socketAddress, {
124 | $0.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout.size, {
125 | return self.getEndpointFromSocketAddress(socketAddressPointer: $0)
126 | })
127 | })
128 | else {
129 | simpleTunnelLog("Failed to get the address and port from the socket address received from recvfrom")
130 | self.closeConnection(.all)
131 | return
132 | }
133 |
134 | let responseDatagram = Data(bytes: UnsafeRawPointer(response), count: bytesRead)
135 | simpleTunnelLog("UDP connection id \(self.identifier) received = \(bytesRead) bytes from host = \(endpoint.host) port = \(endpoint.port)")
136 | self.tunnel?.sendDataWithEndPoint(responseDatagram, forConnection: self.identifier, host: endpoint.host, port: endpoint.port)
137 | }
138 |
139 | newResponseSource.resume()
140 | responseSource = newResponseSource
141 |
142 | return true
143 | }
144 |
145 | /// Send a datagram to a given host and port.
146 | override func sendDataWithEndPoint(_ data: Data, host: String, port: Int) {
147 |
148 | if responseSource == nil {
149 | guard createSocketWithAddressFamilyFromAddress(address: host) else {
150 | simpleTunnelLog("UDP ServerConnection initialization failed.")
151 | return
152 | }
153 | }
154 |
155 | guard let source = responseSource else { return }
156 | let UDPSocket = Int32(source.handle)
157 | let sent: Int
158 |
159 | switch addressFamily {
160 | case AF_INET:
161 | let serverAddress = SocketAddress()
162 | guard serverAddress.setFromString(host) else {
163 | simpleTunnelLog("Failed to convert \(host) into an IPv4 address")
164 | return
165 | }
166 | serverAddress.setPort(port)
167 |
168 | sent = withUnsafePointer(to: &serverAddress.sin, {
169 | $0.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout.size, { (sockaddrPointer) in
170 | data.withUnsafeBytes({ (bytes) in
171 | sendto(UDPSocket, bytes, data.count, 0, sockaddrPointer, socklen_t(serverAddress.sin.sin_len))
172 | })
173 | })
174 | })
175 |
176 | case AF_INET6:
177 | let serverAddress = SocketAddress6()
178 | guard serverAddress.setFromString(host) else {
179 | simpleTunnelLog("Failed to convert \(host) into an IPv6 address")
180 | return
181 | }
182 | serverAddress.setPort(port)
183 |
184 | sent = withUnsafePointer(to: &serverAddress.sin6, {
185 | $0.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout.size, { (sockaddrPointer) in
186 | data.withUnsafeBytes({ (bytes) in
187 | sendto(UDPSocket, bytes, data.count, 0, sockaddrPointer, socklen_t(serverAddress.sin6.sin6_len))
188 | })
189 | })
190 | })
191 |
192 | default:
193 | return
194 | }
195 |
196 | guard sent > 0 else {
197 | if let errorString = String(utf8String: strerror(errno)) {
198 | simpleTunnelLog("UDP connection id \(identifier) failed to send data to host = \(host) port \(port). error = \(errorString)")
199 | }
200 | closeConnection(.all)
201 | return
202 | }
203 |
204 | if sent == data.count {
205 | // Success
206 | simpleTunnelLog("UDP connection id \(identifier) sent \(data.count) bytes to host = \(host) port \(port)")
207 | }
208 | }
209 |
210 | /// Close the connection.
211 | override func closeConnection(_ direction: TunnelConnectionCloseDirection) {
212 | super.closeConnection(direction)
213 |
214 | if let source = responseSource, isClosedForWrite && isClosedForRead {
215 | source.cancel()
216 | responseSource = nil
217 | }
218 | }
219 | }
220 |
221 |
222 |
223 |
224 |
225 |
--------------------------------------------------------------------------------
/tunnel_server/config.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IPv4
6 |
7 | Pool
8 |
9 | EndAddress
10 | 192.168.2.10
11 | StartAddress
12 | 192.168.2.2
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/tunnel_server/main.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the main code for the SimpleTunnel server.
7 | */
8 |
9 | import Foundation
10 |
11 | /// Dispatch source to catch and handle SIGINT
12 | let interruptSignalSource = DispatchSource.makeSignalSource(signal: SIGINT, queue: DispatchQueue.main)
13 |
14 | /// Dispatch source to catch and handle SIGTERM
15 | let termSignalSource = DispatchSource.makeSignalSource(signal: SIGTERM, queue: DispatchQueue.main)
16 |
17 | /// Basic sanity check of the parameters.
18 | if ProcessInfo().arguments.count < 3 {
19 | print("Usage: \(ProcessInfo().arguments[0]) ")
20 | exit(1)
21 | }
22 |
23 | func ignore(_: Int32) {
24 | }
25 | signal(SIGTERM, ignore)
26 | signal(SIGINT, ignore)
27 |
28 | let portString = ProcessInfo().arguments[1]
29 | let configurationPath = ProcessInfo().arguments[2]
30 | let networkService: NetService
31 |
32 | // Initialize the server.
33 |
34 | if !ServerTunnel.initializeWithConfigurationFile(path: configurationPath) {
35 | exit(1)
36 | }
37 |
38 | if let portNumber = Int(portString) {
39 | networkService = ServerTunnel.startListeningOnPort(port: Int32(portNumber))
40 | }
41 | else {
42 | print("Invalid port: \(String(describing: portString))")
43 | exit(1)
44 | }
45 |
46 | // Set up signal handling.
47 |
48 | interruptSignalSource.setEventHandler {
49 | networkService.stop()
50 | return
51 | }
52 | interruptSignalSource.resume()
53 |
54 | termSignalSource.setEventHandler {
55 | networkService.stop()
56 | return
57 | }
58 | termSignalSource.resume()
59 |
60 | RunLoop.main.run()
61 |
--------------------------------------------------------------------------------
/tunnel_server/tunnel_server-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file exposes utility Objective-C functions to Swift.
7 | */
8 |
9 | @import Foundation;
10 |
11 | #import "ServerUtils.h"
12 |
--------------------------------------------------------------------------------