Search code examples
swiftmacosvpnwireguardnetworkextension

macOS VPN Network Extension Fails to Start


I'm trying to create a macOS VPN networks extension. It utilizes Wireguard via this library.

My issue is that I cannot actually seem to get the extension to connect to the VPN - I can't toggle this at all:

enter image description here

These are logs I get:

enter image description here

Bundle ID for the client app:

enter image description here

Bundle ID for the network extension:

enter image description here

Code follows; my global variables (some values hidden, but, have verified they are the correct values for the VPN I am trying to connect to):

var serverAddress: String = //
var serverPublicKey: String = //
var serverPort: String = //
private let appGroup = "6QDK6789A6.group.com.meter.MeterTunnel"
private let tunnelIdentifier = "com.meter.MeterTunnel.Tunnel"

var vpn = NetworkExtensionVPN()
var vpnStatus: VPNStatus = .disconnected

Code for setting up and connecting to the VPN (this is called from a UI event):

 func connectToVPN() async {
        guard let cfg = WireGuard.Configuration.make(
            "Meter VPN",
            appGroup: appGroup,
            clientPrivateKey: clientPrivateKey,
            clientAddress: clientAddress,
            serverPublicKey: serverPublicKey,
            serverAddress: serverAddress,
            serverPort: serverPort
        ) else {
            print("Configuration incomplete")
            return
        }

        Task {
            do {
                try await vpn.reconnect(
                    tunnelIdentifier,
                    configuration: cfg,
                    extra: nil,
                    after: .seconds(2)
                )
                print("VPN connection started successfully")
            } catch {
                print("Failed to connect to VPN: \(error.localizedDescription)")
                if let neError = error as? NEVPNError {
                    print("NEVPNError code: \(neError.code.rawValue)")
                }
            }
        }

        print("ERROR: \(String(describing: cfg.lastError))")
    }

    func disconnectFromVPN() {
        Task {
            await vpn.disconnect()
        }
    }
    
    @objc private func VPNStatusDidChange(notification: Notification) {
        vpnStatus = notification.vpnStatus
        print("VPNStatusDidChange: \(vpnStatus)")
    }

    @objc private func VPNDidFail(notification: Notification) {
        print("VPNStatusDidFail: \(notification.vpnError.localizedDescription)")
    }
    
    @objc private func VPNDidReinstall(notification: Notification) {
        print("VPNStatusDidReinstall")
    }

Status observer code set-up in the applicationDidFinishLaunching:

 NotificationCenter.default.addObserver(
                  self,
                  selector: #selector(VPNStatusDidChange(notification:)),
                  name: VPNNotification.didChangeStatus,
                  object: nil
        )
             
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(VPNDidFail(notification:)),
            name: VPNNotification.didFail,
            object: nil
        )

        NotificationCenter.default.addObserver(
            self,
            selector: #selector(VPNDidReinstall(notification:)),
            name: VPNNotification.didReinstall,
            object: nil
        )

Configuration code:

extension WireGuard {
    struct Configuration {
        static func make(
            _ title: String,
            appGroup: String,
            clientPrivateKey: String,
            clientAddress: String,
            serverPublicKey: String,
            serverAddress: String,
            serverPort: String
        ) -> WireGuard.ProviderConfiguration? {
            do {
                var builder = try WireGuard.ConfigurationBuilder(clientPrivateKey)
                builder.addresses = [clientAddress]
                try builder.addPeer(serverPublicKey, endpoint: "\(serverAddress):\(serverPort)", allowedIPs: ["10.0.0.0/8", "224.0.0.0/4"])
                builder.setKeepAlive(30, forPeer: 0)
                let cfg = builder.build()

                return WireGuard.ProviderConfiguration(title, appGroup: appGroup, configuration: cfg)
            } catch {
                print("Error creating WireGuard configuration: \(error)")
                return nil
            }
        }
    }
}

Solution

  • When I was developing Wireguard/OpenVPN for Apple, wireguardKit&OpenVPNAdapter was the only two available frameworks to use, then there is the tunnelKit that support both WireGuard and OpenVPN, life saver!

    According to your posted configurations:

    private let tunnelIdentifier = "com.meter.MeterTunnel.Tunnel"

    But both the documentation and the Demo form the tunnelkit shows that:

    enter image description here

    So the tunnelIdentifier should be the bundleIdentifier of the Network Extension, may I know why you set the constant to be "com.meter.MeterTunnel.Tunnel"?

    Another tips: You can use the console app to filter the logs inside the network extension:

    enter image description here

    I use process: BlockingExtensionas a filtering command to filter a network extension's logs named "BlockingExtension"(my network extension's target name), that's a more straight forward way to see what's actually happening with in the network extension instead of the app itself.