-
Notifications
You must be signed in to change notification settings - Fork 0
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement the CoderVPN NetworkExtension #2
Labels
enhancement
New feature or request
Comments
This was referenced Sep 19, 2024
@ethanndickson here is the code from my prototype that opens the dylib and calls functions import Foundation
import os
let StartSymbol = "coderStartVPN"
let StopSymbol = "coderStopVPN"
class CoderDaemon {
private let logger = Logger(subsystem: "com.coder.Coder.CoderPacketTunnelProvider", category: "daemon-ctrl")
private let logPipe: Pipe
// dylib handle and symbols
private let startVPN: coderStartVPNFunc
private let stopVPN: coderStopVPNFunc
private let dylibHandle: UnsafeMutableRawPointer?
// tunnel handle in Coder dylib
private let tunnelHandle: Int32
struct Config {
var URL: String
var APIToken: String
var Workspace: String
}
enum DaemonError: Error {
case FileActions(String, Int32, String?)
case DyLib(String)
case Symbol(String, String)
case StartError
}
init(tunnelFD: Int32, config: Config) throws {
logger.debug("tunnel file descriptor is \(tunnelFD)")
let envs = ["CODER_URL=\(config.URL)", "CODER_SESSION_TOKEN=\(config.APIToken)"]
dylibHandle = dlopen("/var/root/Downloads/coder_darwin_arm64.dylib", RTLD_NOW | RTLD_LOCAL)
guard dylibHandle != nil else {
var errStr = "UNKNOWN"
let e = dlerror()
if e != nil {
errStr = String.init(cString: e!)
}
throw DaemonError.DyLib(errStr)
}
let startSym = dlsym(dylibHandle, StartSymbol)
guard startSym != nil else {
var errStr = "UNKNOWN"
let e = dlerror()
if e != nil {
errStr = String.init(cString: e!)
}
throw DaemonError.Symbol(StartSymbol, errStr)
}
startVPN = unsafeBitCast(startSym, to: coderStartVPNFunc.self)
let stopSym = dlsym(dylibHandle, StopSymbol)
guard stopSym != nil else {
var errStr = "UNKNOWN"
let e = dlerror()
if e != nil {
errStr = String.init(cString: e!)
}
throw DaemonError.Symbol(StopSymbol, errStr)
}
stopVPN = unsafeBitCast(stopSym, to: coderStopVPNFunc.self)
logPipe = Pipe()
let pipeLogger = ReadLogger(handle: logPipe.fileHandleForReading, level: OSLogType.info)
Task {
await pipeLogger.start()
}
tunnelHandle = startVPN(
config.URL,
config.APIToken,
config.Workspace,
tunnelFD,
logPipe.fileHandleForWriting.fileDescriptor)
guard tunnelHandle >= 0 else {
throw DaemonError.StartError
}
}
func stop() {
logger.debug("stopping Coder VPN ")
let status = stopVPN(tunnelHandle)
if status < 0 {
logger.error("failed to stop VPN tunnel in dylib")
}
dlclose(dylibHandle)
}
} With the following bridging header #ifndef CoderPacketTunnelProvider_Bridging_Header_h
#define CoderPacketTunnelProvider_Bridging_Header_h
#include "coder_darwin_arm64.h"
typedef int(*coderStartVPNFunc)(const char*, const char*, const char*, int, int);
typedef int(*coderStopVPNFunc)(int);
#endif /* CoderPacketTunnelProvider_Bridging_Header_h */
|
And, this is my prototype Packet Tunnel Provider: import NetworkExtension
import os
/* From <sys/kern_control.h> */
let CTLIOCGINFO: UInt = 0xc0644e03
class PacketTunnelProvider: NEPacketTunnelProvider {
private let logger = Logger(subsystem: "com.coder.Coder.CoderPacketTunnelProvider", category: "network-extension")
private var daemon: CoderDaemon?
enum TunnelError: Error {
case NoTunnelFileDescriptor
case FileActions
case MissingOption(String)
case MissingProtocolConfiguration
}
private var tunnelFileDescriptor: Int32? {
var ctlInfo = ctl_info()
withUnsafeMutablePointer(to: &ctlInfo.ctl_name) {
$0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: $0.pointee)) {
_ = strcpy($0, "com.apple.net.utun_control")
}
}
for fd: Int32 in 0...1024 {
var addr = sockaddr_ctl()
var ret: Int32 = -1
var len = socklen_t(MemoryLayout.size(ofValue: addr))
withUnsafeMutablePointer(to: &addr) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
ret = getpeername(fd, $0, &len)
}
}
if ret != 0 || addr.sc_family != AF_SYSTEM {
continue
}
if ctlInfo.ctl_id == 0 {
ret = ioctl(fd, CTLIOCGINFO, &ctlInfo)
if ret != 0 {
continue
}
}
if addr.sc_id == ctlInfo.ctl_id {
return fd
}
}
return nil
}
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
logger.debug("startTunnel called")
let uid = getuid()
logger.debug("UID: \(uid)")
let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
networkSettings.mtu = 1280
let ipv6Settings = NEIPv6Settings(addresses: ["fd7a:115c:a1e0::2"], networkPrefixLengths: [48])
networkSettings.ipv6Settings = ipv6Settings
let dnsSettings = NEDNSSettings(servers: ["fd7a:115c:a1e0::53"])
dnsSettings.matchDomains = ["coderlan."]
networkSettings.dnsSettings = dnsSettings
self.setTunnelNetworkSettings(networkSettings, completionHandler: {_ in
self.logger.debug("setTunnelNetworkSettings complete")
})
guard let tunnelFD = tunnelFileDescriptor else {
logger.error("failed to get tunnel file descriptor")
completionHandler(TunnelError.NoTunnelFileDescriptor)
return
}
do {
guard let proto = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfig = proto.providerConfiguration else {
completionHandler(TunnelError.MissingProtocolConfiguration)
return
}
//let u = providerConfig[kCoderURL] as! String
//self.pingURL(url: u)
let cfg = try PacketTunnelProvider.optionsToConfig(options: providerConfig)
try daemon = CoderDaemon(tunnelFD: tunnelFD, config: cfg)
} catch let error {
completionHandler(error)
return
}
completionHandler(nil)
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
// Add code here to start the process of stopping the tunnel.
logger.debug("stopTunnel called")
daemon?.stop()
completionHandler()
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
// Add code here to handle the message.
if let handler = completionHandler {
handler(messageData)
}
}
override func sleep(completionHandler: @escaping () -> Void) {
// Add code here to get ready to sleep.
completionHandler()
}
override func wake() {
// Add code here to wake up.
}
static func optionsToConfig(options: [String : Any]) throws -> CoderDaemon.Config {
guard let url = options[kCoderURL] as? String else {
throw TunnelError.MissingOption(kCoderURL)
}
guard let token = options[kCoderAPIToken] as? String else {
throw TunnelError.MissingOption(kCoderAPIToken)
}
guard let workspace = options[kCoderWorkspace] as? String else {
throw TunnelError.MissingOption(kCoderWorkspace)
}
return CoderDaemon.Config(URL: url, APIToken: token, Workspace: workspace)
}
}
|
ethanndickson
added a commit
to coder/coder
that referenced
this issue
Dec 18, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To implement the CoderVPN feature, we'll use a Network Extension PacketTunnelProvider. It extends the abstract base class NEPacketTunnelProvider. From this process, we will contact the Coder Server and download a dynamic library (dylib), written in Go using cgo for C FFI bindings (coder/coder#14734)
After downloading the CoderVPN library, we should check the digital signature on it before exec’ing it. We should verify the following fields:
After verifying the digital signature, the NetworkExtension creates a pair of pipes to communicate with the CoderVPN library (via #1 ), and opens the library via
dlopen
. Then it starts the VPN, passing the pipes.Over the CoderVPN Protocol it receives
The text was updated successfully, but these errors were encountered: