Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

chore: support operating the VPN without the app#36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ethanndickson merged 7 commits intomainfromethan/vpn-no-app
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes fromall 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
6 changes: 5 additions & 1 deletionCoder Desktop/Coder Desktop/Coder_DesktopApp.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,9 +47,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
}

// This function MUST eventually call `NSApp.reply(toApplicationShouldTerminate: true)`
// or return `.terminateNow`
func applicationShouldTerminate(_: NSApplication) -> NSApplication.TerminateReply {
if !settings.stopVPNOnQuit { return .terminateNow }
Task {
await vpn.quit()
await vpn.stop()
NSApp.reply(toApplicationShouldTerminate: true)
}
return .terminateLater
}
Expand Down
29 changes: 10 additions & 19 deletionsCoder Desktop/Coder Desktop/NetworkExtension.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,13 +24,12 @@ enum NetworkExtensionState: Equatable {
/// An actor that handles configuring, enabling, and disabling the VPN tunnel via the
/// NetworkExtension APIs.
extension CoderVPNService {
// Updates the UI if a previous configuration exists
func loadNetworkExtension() async {
func hasNetworkExtensionConfig() async -> Bool {
do {
try await getTunnelManager()
neState = .disabled
_ =try await getTunnelManager()
return true
} catch {
neState = .unconfigured
return false
}
}

Expand DownExpand Up@@ -71,37 +70,29 @@ extension CoderVPNService {
}
}

funcenableNetworkExtension() async {
funcstartTunnel() async {
do {
let tm = try await getTunnelManager()
if !tm.isEnabled {
tm.isEnabled = true
try await tm.saveToPreferences()
logger.debug("saved tunnel with enabled=true")
}
try tm.connection.startVPNTunnel()
} catch {
logger.error("enable network extension: \(error)")
logger.error("start tunnel: \(error)")
neState = .failed(error.localizedDescription)
return
}
logger.debug("enabled andstarted tunnel")
logger.debug("started tunnel")
neState = .enabled
}

funcdisableNetworkExtension() async {
funcstopTunnel() async {
do {
let tm = try await getTunnelManager()
tm.connection.stopVPNTunnel()
tm.isEnabled = false

try await tm.saveToPreferences()
} catch {
logger.error("disable network extension: \(error)")
logger.error("stop tunnel: \(error)")
neState = .failed(error.localizedDescription)
return
}
logger.debug("saved tunnel with enabled=false")
logger.debug("stopped tunnel")
neState = .disabled
}

Expand Down
3 changes: 3 additions & 0 deletionsCoder Desktop/Coder Desktop/State.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -104,6 +104,8 @@ class Settings: ObservableObject {
}
}

@AppStorage(Keys.stopVPNOnQuit)varstopVPNOnQuit=true

init(store:UserDefaults=.standard){
self.store= store
_literalHeaders=Published(
Expand All@@ -116,6 +118,7 @@ class Settings: ObservableObject {
enumKeys{
staticletuseLiteralHeaders="UseLiteralHeaders"
staticletliteralHeaders="LiteralHeaders"
staticletstopVPNOnQuit="StopVPNOnQuit"
}
}

Expand Down
52 changes: 31 additions & 21 deletionsCoder Desktop/Coder Desktop/VPNService.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,7 +41,6 @@ enum VPNServiceError: Error, Equatable {
final class CoderVPNService: NSObject, VPNService {
var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "vpn")
lazy var xpc: VPNXPCInterface = .init(vpn: self)
var terminating = false
var workspaces: [UUID: String] = [:]

@Published var tunnelState: VPNServiceState = .disabled
Expand All@@ -68,8 +67,14 @@ final class CoderVPNService: NSObject, VPNService {
super.init()
installSystemExtension()
Task {
await loadNetworkExtension()
neState = if await hasNetworkExtensionConfig() {
.disabled
} else {
.unconfigured
}
}
xpc.connect()
xpc.getPeerState()
NotificationCenter.default.addObserver(
self,
selector: #selector(vpnDidUpdate(_:)),
Expand All@@ -82,6 +87,11 @@ final class CoderVPNService: NSObject, VPNService {
NotificationCenter.default.removeObserver(self)
}

func clearPeers() {
agents = [:]
workspaces = [:]
}

func start() async {
switch tunnelState {
case .disabled, .failed:
Expand All@@ -90,31 +100,18 @@ final class CoderVPNService: NSObject, VPNService {
return
}

awaitenableNetworkExtension()
// this ping is somewhat load bearing since it causesxpc to init
awaitstartTunnel()
xpc.connect()
xpc.ping()
logger.debug("network extension enabled")
}

func stop() async {
guard tunnelState == .connected else { return }
awaitdisableNetworkExtension()
awaitstopTunnel()
logger.info("network extension stopped")
}

// Instructs the service to stop the VPN and then quit once the stop event
// is read over XPC.
// MUST only be called from `NSApplicationDelegate.applicationShouldTerminate`
// MUST eventually call `NSApp.reply(toApplicationShouldTerminate: true)`
func quit() async {
guard tunnelState == .connected else {
NSApp.reply(toApplicationShouldTerminate: true)
return
}
terminating = true
await stop()
}

func configureTunnelProviderProtocol(proto: NETunnelProviderProtocol?) {
Task {
if let proto {
Expand DownExpand Up@@ -145,6 +142,22 @@ final class CoderVPNService: NSObject, VPNService {
}
}

func onExtensionPeerState(_ data: Data?) {
guard let data else {
logger.error("could not retrieve peer state from network extension, it may not be running")
return
}
logger.info("received network extension peer state")
do {
let msg = try Vpn_PeerUpdate(serializedBytes: data)
debugPrint(msg)
clearPeers()
applyPeerUpdate(with: msg)
Comment on lines +154 to +155
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Doesn't this clear call make handling thedeletedAgents anddeletedWorkspaces inapplyPeerUpdate unnecessary since there won't be any workspaces or agents left?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

update.deletedAgents andupdate.deletedWorkspaces will always be empty on agetPeerState response, yeah. The Go code already keeps track of all known agents & workspaces, so as Dean suggested I think it'd be good to just send the full current state to the NE on any update.

} catch {
logger.error("failed to decode peer update \(error)")
}
}

func applyPeerUpdate(with update: Vpn_PeerUpdate) {
// Delete agents
update.deletedAgents
Expand DownExpand Up@@ -204,9 +217,6 @@ extension CoderVPNService {
}
switch connection.status {
case .disconnected:
if terminating {
NSApp.reply(toApplicationShouldTerminate: true)
}
connection.fetchLastDisconnectError { err in
self.tunnelState = if let err {
.failed(.internalError(err.localizedDescription))
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,11 +2,17 @@ import LaunchAtLogin
import SwiftUI

structGeneralTab:View{
@EnvironmentObjectvarsettings:Settings
varbody:someView{
Form{
Section{
LaunchAtLogin.Toggle("Launch at Login")
}
Section{
Toggle(isOn: $settings.stopVPNOnQuit){
Text("Stop VPN on Quit")
}
}
}.formStyle(.grouped)
}
}
Expand Down
22 changes: 18 additions & 4 deletionsCoder Desktop/Coder Desktop/XPCInterface.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,11 +6,17 @@ import VPNLib
@objc final class VPNXPCInterface: NSObject, VPNXPCClientCallbackProtocol, @unchecked Sendable {
private var svc: CoderVPNService
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "VPNXPCInterface")
privatelet xpc: VPNXPCProtocol
privatevar xpc: VPNXPCProtocol?

init(vpn: CoderVPNService) {
svc = vpn
super.init()
}

func connect() {
guard xpc == nil else {
return
}
let networkExtDict = Bundle.main.object(forInfoDictionaryKey: "NetworkExtension") as? [String: Any]
let machServiceName = networkExtDict?["NEMachServiceName"] as? String
let xpcConn = NSXPCConnection(machServiceName: machServiceName!)
Expand All@@ -21,30 +27,38 @@ import VPNLib
}
xpc = proxy

super.init()

xpcConn.exportedObject = self
xpcConn.invalidationHandler = { [logger] in
Task { @MainActor in
logger.error("XPC connection invalidated.")
self.xpc = nil
}
}
xpcConn.interruptionHandler = { [logger] in
Task { @MainActor in
logger.error("XPC connection interrupted.")
self.xpc = nil
}
}
xpcConn.resume()
}

func ping() {
xpc.ping {
xpc?.ping {
Task { @MainActor in
self.logger.info("Connected to NE over XPC")
}
}
}

func getPeerState() {
xpc?.getPeerState { data in
Task { @MainActor in
self.svc.onExtensionPeerState(data)
}
}
}

func onPeerUpdate(_ data: Data) {
Task { @MainActor in
svc.onExtensionPeerUpdate(data)
Expand Down
2 changes: 1 addition & 1 deletionCoder Desktop/VPN/Manager.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -194,7 +194,7 @@ actor Manager {

// Retrieves the current state of all peers,
// as required when starting the app whilst the network extension is already running
funcgetPeerInfo() async throws(ManagerError) -> Vpn_PeerUpdate {
funcgetPeerState() async throws(ManagerError) -> Vpn_PeerUpdate {
logger.info("sending peer state request")
let resp: Vpn_TunnelMessage
do {
Expand Down
9 changes: 6 additions & 3 deletionsCoder Desktop/VPN/XPCInterface.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,9 +20,12 @@ import VPNLib
}
}

func getPeerInfo(with reply: @escaping () -> Void) {
// TODO: Retrieve from Manager
reply()
func getPeerState(with reply: @escaping (Data?) -> Void) {
let reply = CallbackWrapper(reply)
Task {
let data = try? await manager?.getPeerState().serializedData()
reply(data)
}
}

func ping(with reply: @escaping () -> Void) {
Expand Down
2 changes: 1 addition & 1 deletionCoder Desktop/VPNLib/XPC.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ import Foundation

@preconcurrency
@objc public protocol VPNXPCProtocol {
funcgetPeerInfo(with reply: @escaping () -> Void)
funcgetPeerState(with reply: @escaping (Data?) -> Void)
func ping(with reply: @escaping () -> Void)
}

Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp