Skip to content

Commit f3123f1

Browse files
chore: pass session token to network extension (#34)
1 parent 511bafd commit f3123f1

File tree

12 files changed

+256
-159
lines changed

12 files changed

+256
-159
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,4 +290,8 @@ xcuserdata
290290
/*.gcno
291291
**/xcshareddata/WorkspaceSettings.xcsettings
292292

293+
### VSCode & Sweetpad ###
294+
.vscode/**
295+
buildServer.json
296+
293297
# End of https://www.toptal.com/developers/gitignore/api/xcode,jetbrains,macos,direnv,swift,swiftpm,objective-c

Coder Desktop/.swiftformat

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--selfrequired log,info,error,debug,critical,fault
2+
--exclude **.pb.swift
3+
--condassignment always

Coder Desktop/Coder Desktop/Coder_DesktopApp.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ struct DesktopApp: App {
1616
.environmentObject(appDelegate.settings)
1717
}
1818
.windowResizability(.contentSize)
19-
SwiftUI.Settings { SettingsView<CoderVPNService>()
20-
.environmentObject(appDelegate.vpn)
21-
.environmentObject(appDelegate.settings)
19+
SwiftUI.Settings {
20+
SettingsView<CoderVPNService>()
21+
.environmentObject(appDelegate.vpn)
22+
.environmentObject(appDelegate.settings)
2223
}
2324
.windowResizability(.contentSize)
2425
}
@@ -32,7 +33,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
3233
let settings: Settings
3334

3435
override init() {
35-
// TODO: Replace with real implementation
3636
vpn = CoderVPNService()
3737
settings = Settings()
3838
session = SecureSession(onChange: vpn.configureTunnelProviderProtocol)

Coder Desktop/Coder Desktop/State.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ class SecureSession: ObservableObject, Session {
4141
if !hasSession { return nil }
4242
let proto = NETunnelProviderProtocol()
4343
proto.providerBundleIdentifier = "\(appId).VPN"
44-
proto.passwordReference = keychain[attributes: Keys.sessionToken]?.persistentRef
44+
// HACK: We can't write to the system keychain, and the user keychain
45+
// isn't accessible, so we'll use providerConfiguration, which is over XPC.
46+
proto.providerConfiguration = ["token": sessionToken!]
4547
proto.serverAddress = baseAccessURL!.absoluteString
4648
return proto
4749
}

Coder Desktop/VPN/Manager.swift

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,11 @@ actor Manager {
7979
case let .message(msg):
8080
handleMessage(msg)
8181
case let .RPC(rpc):
82-
handleRPC(rpc)
82+
await handleRPC(rpc)
8383
}
8484
}
8585
} catch {
86-
logger.error("tunnel read loop failed: \(error)")
86+
logger.error("tunnel read loop failed: \(error.localizedDescription, privacy: .public)")
8787
try await tunnelHandle.close()
8888
// TODO: Notify app over XPC
8989
return
@@ -108,21 +108,33 @@ actor Manager {
108108
}
109109
}
110110

111-
func handleRPC(_ rpc: RPCRequest<Vpn_ManagerMessage, Vpn_TunnelMessage>) {
111+
func handleRPC(_ rpc: RPCRequest<Vpn_ManagerMessage, Vpn_TunnelMessage>) async {
112112
guard let msgType = rpc.msg.msg else {
113113
logger.critical("received rpc with no type")
114114
return
115115
}
116116
switch msgType {
117117
case let .networkSettings(ns):
118-
let neSettings = convertNetworkSettingsRequest(ns)
119-
ptp.setTunnelNetworkSettings(neSettings)
118+
do {
119+
try await ptp.applyTunnelNetworkSettings(ns)
120+
try? await rpc.sendReply(.with { resp in
121+
resp.networkSettings = .with { settings in
122+
settings.success = true
123+
}
124+
})
125+
} catch {
126+
try? await rpc.sendReply(.with { resp in
127+
resp.networkSettings = .with { settings in
128+
settings.success = false
129+
settings.errorMessage = error.localizedDescription
130+
}
131+
})
132+
}
120133
case .log, .peerUpdate, .start, .stop:
121134
logger.critical("received unexpected rpc: `\(String(describing: msgType))`")
122135
}
123136
}
124137

125-
// TODO: Call via XPC
126138
func startVPN() async throws(ManagerError) {
127139
logger.info("sending start rpc")
128140
guard let tunFd = ptp.tunnelFileDescriptor else {
@@ -149,7 +161,6 @@ actor Manager {
149161
// TODO: notify app over XPC
150162
}
151163

152-
// TODO: Call via XPC
153164
func stopVPN() async throws(ManagerError) {
154165
logger.info("sending stop rpc")
155166
let resp: Vpn_TunnelMessage
@@ -246,5 +257,5 @@ func writeVpnLog(_ log: Vpn_Log) {
246257
category: log.loggerNames.joined(separator: ".")
247258
)
248259
let fields = log.fields.map { "\($0.name): \($0.value)" }.joined(separator: ", ")
249-
logger.log(level: level, "\(log.message): \(fields)")
260+
logger.log(level: level, "\(log.message, privacy: .public): \(fields, privacy: .public)")
250261
}

Coder Desktop/VPN/PacketTunnelProvider.swift

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ let CTLIOCGINFO: UInt = 0xC064_4E03
88
class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
99
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "provider")
1010
private var manager: Manager?
11+
// a `tunnelRemoteAddress` is required, but not currently used.
12+
private var currentSettings: NEPacketTunnelNetworkSettings = .init(tunnelRemoteAddress: "127.0.0.1")
1113

1214
var tunnelFileDescriptor: Int32? {
1315
var ctlInfo = ctl_info()
@@ -41,21 +43,42 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
4143
return nil
4244
}
4345

44-
override func startTunnel(options _: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
46+
override func startTunnel(
47+
options _: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void
48+
) {
4549
logger.debug("startTunnel called")
4650
guard manager == nil else {
4751
logger.error("startTunnel called with non-nil Manager")
48-
completionHandler(nil)
52+
completionHandler(PTPError.alreadyRunning)
4953
return
5054
}
55+
guard let proto = protocolConfiguration as? NETunnelProviderProtocol,
56+
let baseAccessURL = proto.serverAddress
57+
else {
58+
logger.error("startTunnel called with nil protocolConfiguration")
59+
completionHandler(PTPError.missingConfiguration)
60+
return
61+
}
62+
// HACK: We can't write to the system keychain, and the NE can't read the user keychain.
63+
guard let token = proto.providerConfiguration?["token"] as? String else {
64+
logger.error("startTunnel called with nil token")
65+
completionHandler(PTPError.missingToken)
66+
return
67+
}
68+
logger.debug("retrieved token & access URL")
5169
let completionHandler = CallbackWrapper(completionHandler)
5270
Task {
53-
// TODO: Retrieve access URL & Token via Keychain
5471
do throws(ManagerError) {
72+
logger.debug("creating manager")
5573
manager = try await Manager(
5674
with: self,
57-
cfg: .init(apiToken: "fake-token", serverUrl: .init(string: "https://dev.coder.com")!)
75+
cfg: .init(
76+
apiToken: token, serverUrl: .init(string: baseAccessURL)!
77+
)
5878
)
79+
logger.debug("starting vpn")
80+
try await manager!.startVPN()
81+
logger.info("vpn started")
5982
completionHandler(nil)
6083
} catch {
6184
completionHandler(error)
@@ -64,15 +87,26 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
6487
}
6588
}
6689

67-
override func stopTunnel(with _: NEProviderStopReason, completionHandler: @escaping () -> Void) {
90+
override func stopTunnel(
91+
with _: NEProviderStopReason, completionHandler: @escaping () -> Void
92+
) {
6893
logger.debug("stopTunnel called")
69-
guard manager != nil else {
94+
guard let manager else {
7095
logger.error("stopTunnel called with nil Manager")
7196
completionHandler()
7297
return
7398
}
74-
manager = nil
75-
completionHandler()
99+
100+
let completionHandler = CompletionWrapper(completionHandler)
101+
Task { [manager] in
102+
do throws(ManagerError) {
103+
try await manager.stopVPN()
104+
} catch {
105+
logger.error("error stopping manager: \(error.description, privacy: .public)")
106+
}
107+
completionHandler()
108+
}
109+
self.manager = nil
76110
}
77111

78112
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
@@ -92,4 +126,33 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
92126
// Add code here to wake up.
93127
logger.debug("wake called")
94128
}
129+
130+
// Wrapper around `setTunnelNetworkSettings` that supports merging updates
131+
func applyTunnelNetworkSettings(_ diff: Vpn_NetworkSettingsRequest) async throws {
132+
logger.debug("applying settings diff: \(diff.debugDescription, privacy: .public)")
133+
134+
if diff.hasDnsSettings {
135+
currentSettings.dnsSettings = convertDnsSettings(diff.dnsSettings)
136+
}
137+
138+
if diff.mtu != 0 {
139+
currentSettings.mtu = NSNumber(value: diff.mtu)
140+
}
141+
142+
if diff.hasIpv4Settings {
143+
currentSettings.ipv4Settings = convertIPv4Settings(diff.ipv4Settings)
144+
}
145+
if diff.hasIpv6Settings {
146+
currentSettings.ipv6Settings = convertIPv6Settings(diff.ipv6Settings)
147+
}
148+
149+
logger.info("applying settings: \(self.currentSettings.debugDescription, privacy: .public)")
150+
try await setTunnelNetworkSettings(currentSettings)
151+
}
152+
}
153+
154+
enum PTPError: Error {
155+
case alreadyRunning
156+
case missingConfiguration
157+
case missingToken
95158
}

Coder Desktop/VPNLib/Convert.swift

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,61 @@
11
import NetworkExtension
22
import os
33

4-
// swiftlint:disable:next function_body_length
5-
public func convertNetworkSettingsRequest(_ req: Vpn_NetworkSettingsRequest) -> NEPacketTunnelNetworkSettings {
6-
let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: req.tunnelRemoteAddress)
7-
networkSettings.tunnelOverheadBytes = NSNumber(value: req.tunnelOverheadBytes)
8-
networkSettings.mtu = NSNumber(value: req.mtu)
4+
public func convertDnsSettings(_ req: Vpn_NetworkSettingsRequest.DNSSettings) -> NEDNSSettings {
5+
let dnsSettings = NEDNSSettings(servers: req.servers)
6+
dnsSettings.searchDomains = req.searchDomains
7+
dnsSettings.domainName = req.domainName
8+
dnsSettings.matchDomains = req.matchDomains
9+
dnsSettings.matchDomainsNoSearch = req.matchDomainsNoSearch
10+
return dnsSettings
11+
}
912

10-
if req.hasDnsSettings {
11-
let dnsSettings = NEDNSSettings(servers: req.dnsSettings.servers)
12-
dnsSettings.searchDomains = req.dnsSettings.searchDomains
13-
dnsSettings.domainName = req.dnsSettings.domainName
14-
dnsSettings.matchDomains = req.dnsSettings.matchDomains
15-
dnsSettings.matchDomainsNoSearch = req.dnsSettings.matchDomainsNoSearch
16-
networkSettings.dnsSettings = dnsSettings
13+
public func convertIPv4Settings(_ req: Vpn_NetworkSettingsRequest.IPv4Settings) -> NEIPv4Settings {
14+
let ipv4Settings = NEIPv4Settings(addresses: req.addrs, subnetMasks: req.subnetMasks)
15+
if !req.router.isEmpty {
16+
ipv4Settings.router = req.router
1717
}
18-
19-
if req.hasIpv4Settings {
20-
let ipv4Settings = NEIPv4Settings(addresses: req.ipv4Settings.addrs, subnetMasks: req.ipv4Settings.subnetMasks)
21-
ipv4Settings.router = req.ipv4Settings.router
22-
ipv4Settings.includedRoutes = req.ipv4Settings.includedRoutes.map {
23-
let route = NEIPv4Route(destinationAddress: $0.destination, subnetMask: $0.mask)
18+
ipv4Settings.includedRoutes = req.includedRoutes.map {
19+
let route = NEIPv4Route(destinationAddress: $0.destination, subnetMask: $0.mask)
20+
if !$0.router.isEmpty {
2421
route.gatewayAddress = $0.router
25-
return route
2622
}
27-
ipv4Settings.excludedRoutes = req.ipv4Settings.excludedRoutes.map {
28-
let route = NEIPv4Route(destinationAddress: $0.destination, subnetMask: $0.mask)
23+
return route
24+
}
25+
ipv4Settings.excludedRoutes = req.excludedRoutes.map {
26+
let route = NEIPv4Route(destinationAddress: $0.destination, subnetMask: $0.mask)
27+
if !$0.router.isEmpty {
2928
route.gatewayAddress = $0.router
30-
return route
3129
}
32-
networkSettings.ipv4Settings = ipv4Settings
30+
return route
3331
}
32+
return ipv4Settings
33+
}
3434

35-
if req.hasIpv6Settings {
36-
let ipv6Settings = NEIPv6Settings(
37-
addresses: req.ipv6Settings.addrs,
38-
networkPrefixLengths: req.ipv6Settings.prefixLengths.map { NSNumber(value: $0)
39-
}
35+
public func convertIPv6Settings(_ req: Vpn_NetworkSettingsRequest.IPv6Settings) -> NEIPv6Settings {
36+
let ipv6Settings = NEIPv6Settings(
37+
addresses: req.addrs,
38+
networkPrefixLengths: req.prefixLengths.map { NSNumber(value: $0) }
39+
)
40+
ipv6Settings.includedRoutes = req.includedRoutes.map {
41+
let route = NEIPv6Route(
42+
destinationAddress: $0.destination,
43+
networkPrefixLength: NSNumber(value: $0.prefixLength)
4044
)
41-
ipv6Settings.includedRoutes = req.ipv6Settings.includedRoutes.map {
42-
let route = NEIPv6Route(
43-
destinationAddress: $0.destination,
44-
networkPrefixLength: NSNumber(value: $0.prefixLength)
45-
)
45+
if !$0.router.isEmpty {
4646
route.gatewayAddress = $0.router
47-
return route
4847
}
49-
ipv6Settings.excludedRoutes = req.ipv6Settings.excludedRoutes.map {
50-
let route = NEIPv6Route(
51-
destinationAddress: $0.destination,
52-
networkPrefixLength: NSNumber(value: $0.prefixLength)
53-
)
48+
return route
49+
}
50+
ipv6Settings.excludedRoutes = req.excludedRoutes.map {
51+
let route = NEIPv6Route(
52+
destinationAddress: $0.destination,
53+
networkPrefixLength: NSNumber(value: $0.prefixLength)
54+
)
55+
if !$0.router.isEmpty {
5456
route.gatewayAddress = $0.router
55-
return route
5657
}
57-
networkSettings.ipv6Settings = ipv6Settings
58+
return route
5859
}
59-
return networkSettings
60+
return ipv6Settings
6061
}

Coder Desktop/VPNLib/Receiver.swift

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import SwiftProtobuf
66
actor Receiver<RecvMsg: Message> {
77
private let dispatch: DispatchIO
88
private let queue: DispatchQueue
9-
private var running = false
109
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "proto")
1110

1211
/// Creates an instance using the given `DispatchIO` channel and queue.
@@ -58,11 +57,7 @@ actor Receiver<RecvMsg: Message> {
5857
/// Starts reading protocol messages from the `DispatchIO` channel and returns them as an `AsyncStream` of messages.
5958
/// On read or decoding error, it logs and closes the stream.
6059
func messages() throws(ReceiveError) -> AsyncStream<RecvMsg> {
61-
if running {
62-
throw .alreadyRunning
63-
}
64-
running = true
65-
return AsyncStream(
60+
AsyncStream(
6661
unfolding: {
6762
do {
6863
let length = try await self.readLen()
@@ -83,7 +78,6 @@ actor Receiver<RecvMsg: Message> {
8378
enum ReceiveError: Error {
8479
case readError(String)
8580
case invalidLength
86-
case alreadyRunning
8781
}
8882

8983
func deserializeLen(_ data: Data) throws -> UInt32 {

Coder Desktop/VPNLib/Speaker.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ public actor Speaker<SendMsg: RPCMessage & Message, RecvMsg: RPCMessage & Messag
7979
}
8080
)
8181
receiver = Receiver(dispatch: dispatch, queue: queue)
82-
if SendMsg.self == Vpn_TunnelMessage.self {
83-
role = .tunnel
82+
role = if SendMsg.self == Vpn_TunnelMessage.self {
83+
.tunnel
8484
} else {
85-
role = .manager
85+
.manager
8686
}
8787
}
8888

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy