Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ios/vpn/NewNode VPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
3CA15F262692D91B00924A2F /* MMWormholeSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 3CA15F172692D91B00924A2F /* MMWormholeSession.m */; };
3CA15F272692D91B00924A2F /* MMWormholeCoordinatedFileTransiting.m in Sources */ = {isa = PBXBuildFile; fileRef = 3CA15F192692D91B00924A2F /* MMWormholeCoordinatedFileTransiting.m */; };
3CA15F282692D91B00924A2F /* MMWormholeCoordinatedFileTransiting.m in Sources */ = {isa = PBXBuildFile; fileRef = 3CA15F192692D91B00924A2F /* MMWormholeCoordinatedFileTransiting.m */; };
883006382963526F008D495F /* NetworkExtension+NewNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883006362963526F008D495F /* NetworkExtension+NewNode.swift */; };
883006392963526F008D495F /* TunnelManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883006372963526F008D495F /* TunnelManager.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -111,6 +113,8 @@
3CA15F182692D91B00924A2F /* MMWormholeFileTransiting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MMWormholeFileTransiting.h; path = MMWormhole/Source/MMWormholeFileTransiting.h; sourceTree = "<group>"; };
3CA15F192692D91B00924A2F /* MMWormholeCoordinatedFileTransiting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MMWormholeCoordinatedFileTransiting.m; path = MMWormhole/Source/MMWormholeCoordinatedFileTransiting.m; sourceTree = "<group>"; };
3CA15F1A2692D91B00924A2F /* MMWormholeSessionContextTransiting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MMWormholeSessionContextTransiting.h; path = MMWormhole/Source/MMWormholeSessionContextTransiting.h; sourceTree = "<group>"; };
883006362963526F008D495F /* NetworkExtension+NewNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NetworkExtension+NewNode.swift"; sourceTree = "<group>"; };
883006372963526F008D495F /* TunnelManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelManager.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -167,6 +171,8 @@
3C50125F22D4AF5100F8487C /* Info.plist */,
3C50125322D4AF5000F8487C /* AppDelegate.swift */,
3C50125522D4AF5000F8487C /* ViewController.swift */,
883006362963526F008D495F /* NetworkExtension+NewNode.swift */,
883006372963526F008D495F /* TunnelManager.swift */,
0F5C8ABB2664FB8800D013D7 /* InfoViewController.swift */,
0FBB0AF92661789F00D89502 /* GradientView.swift */,
0F0A7FF22667C9F20093B85D /* FontUpdater.swift */,
Expand Down Expand Up @@ -327,11 +333,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
883006392963526F008D495F /* TunnelManager.swift in Sources */,
3C50125622D4AF5000F8487C /* ViewController.swift in Sources */,
3C50125422D4AF5000F8487C /* AppDelegate.swift in Sources */,
3CA15F212692D91B00924A2F /* MMWormholeSessionContextTransiting.m in Sources */,
3CA15F1D2692D91B00924A2F /* MMWormholeSessionMessageTransiting.m in Sources */,
3CA15F1B2692D91B00924A2F /* MMWormholeFileTransiting.m in Sources */,
883006382963526F008D495F /* NetworkExtension+NewNode.swift in Sources */,
3CA15F252692D91B00924A2F /* MMWormholeSession.m in Sources */,
0F5C8ABC2664FB8800D013D7 /* InfoViewController.swift in Sources */,
3CA15F232692D91B00924A2F /* MMWormholeSessionFileTransiting.m in Sources */,
Expand Down
83 changes: 83 additions & 0 deletions ios/vpn/NewNode VPN/NetworkExtension+NewNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// NetworkExtension+NewNode.swift
// NewNode VPN
//
// Created by Anton Ilinykh on 02.01.2023.
// Copyright © 2023 Clostra. All rights reserved.
//

import Foundation
import NetworkExtension
import os.log


extension NETunnelProviderManager {
static func loadManager(completion: @escaping (Result<NETunnelProviderManager, Error>) -> Void) {
loadAllFromPreferences { managers, error in
guard let managers = managers else {
let error = error ?? NSError(domain: "unknown error", code: 0)
os_log("failed to load managers: %@", type: .error, error.localizedDescription)
completion(.failure(error))
return
}

if let manager = managers.first {
completion(.success(manager))
} else {
let manager = NETunnelProviderManager()
let providerProtocol = NETunnelProviderProtocol()
providerProtocol.providerBundleIdentifier = "com.newnode.vpn.tunnel"
providerProtocol.serverAddress = "NewNode"
manager.protocolConfiguration = providerProtocol
manager.isEnabled = true
manager.localizedDescription = "NewNode"

manager.saveToPreferences { error in
if let error = error {
os_log("failed to save to preferences: %@", type: .error, error.localizedDescription)
completion(.failure(error))
return
}
manager.loadFromPreferences { error in
if let error = error {
os_log("failed to load from preferences: %@", type: .error, error.localizedDescription)
completion(.failure(error))
} else {
completion(.success(manager))
}
}
}
}
}
}
}


extension NEVPNStatus {
var description: String {
switch self {
case .invalid: return "invalid"
case .disconnected: return "disconnected"
case .connecting: return "connecting"
case .connected: return "connected"
case .reasserting: return "reconnecting"
case .disconnecting: return "disconnecting"
@unknown default: return ""
}
}
}


extension NEVPNError {
var description: String {
switch code {
case .configurationDisabled: return "configurationDisabled"
case .configurationInvalid: return "configurationInvalid"
case .configurationStale: return "configurationStale"
case .configurationUnknown: return "configurationUnknown"
case .connectionFailed: return "connectionFailed"
case .configurationReadWriteFailed: return "configurationReadWriteFailed"
@unknown default: return "unknown"
}
}
}
99 changes: 99 additions & 0 deletions ios/vpn/NewNode VPN/TunnelManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// TunnelManager.swift
// NewNode VPN
//
// Created by Anton Ilinykh on 01.01.2023.
// Copyright © 2023 Clostra. All rights reserved.
//

import Foundation
import NetworkExtension
import os.log

enum TunnelManagerState: String {
case invalid
case connecting
case connected
case reasserting
case disconnecting
case disconnected
}

protocol TunnelManagerDelegate {
func tunnelDidChangeState(_ state: TunnelManagerState)
}

protocol TunnelManager {
func connect()
func disconnect()
}

final class TunnelManagerImpl: TunnelManager {
private let delegate: TunnelManagerDelegate
private var connection: NEVPNConnection?

init(delegate: TunnelManagerDelegate) {
self.delegate = delegate

NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: nil, queue: .main, using: { note in
guard let session = note.object as? NETunnelProviderSession else {
os_log("unexpected vpn status notification state", type: .error)
return
}
os_log("connection state changed to %s", session.status.description)
switch session.status {
case .connecting:
delegate.tunnelDidChangeState(.connecting)
case .connected:
delegate.tunnelDidChangeState(.connected)
case .disconnecting:
delegate.tunnelDidChangeState(.disconnecting)
case .disconnected:
delegate.tunnelDidChangeState(.disconnected)
case .invalid:
delegate.tunnelDidChangeState(.invalid)
case .reasserting:
delegate.tunnelDidChangeState(.reasserting)
@unknown default:
delegate.tunnelDidChangeState(.invalid)
}
})
}

func disconnect() {
connection?.stopVPNTunnel()
}

func connect() {
delegate.tunnelDidChangeState(.connecting)
NETunnelProviderManager.loadManager { [weak self] result in
switch(result) {
case .success(let manager):
do {
try manager.connection.startVPNTunnel()
self?.connection = manager.connection
} catch {
/* The `confugarationDisabled` error ocurrs when the `manager`
* turns to `disabled` state. It may happen when user
* turn on another VPN or toggles the switch in settings
*/
if let err = error as? NEVPNError, err.code == .configurationDisabled {
manager.isEnabled = true
manager.saveToPreferences() { error in
if let error = error {
os_log("failed to save to preferences: %@", type: .error, error.localizedDescription)
} else {
self?.connect()
}
}
}
os_log("failed to start vpn: %@", type: .error, error.localizedDescription)
self?.delegate.tunnelDidChangeState(.invalid)
}
case .failure(let error):
os_log("failed to load manager: %@", type: .error, error.localizedDescription)
self?.delegate.tunnelDidChangeState(.invalid)
}
}
}
}
Loading