How can I use inter process communication?

Hi, I am currently building my own VPN application using NetworkExtension's PacketTunnelProvider.

I want to send information from the PacketTunnelProvider to the ViewController when a VPN connection fails and to tell the user why.

The code now is as shown below. When the startTunnel() being overwritten is executed, somehow NotificationCenter.default.post(name: NSNotification.Name.NEVPNStatusDidChange, object: nil) is executed and VPNStatusDidChange(_ notification: Notification?) in the ViewController is called and displays some message.

I tried to do the same thing by writing NotificationCenter.default.post(name: NSNotification.Name(rawValue: "testnotify"), object: nil) in the PacketTunnelProvider.swift , but it does not work. What is wrong?

Here is a part of current PacketTunnelProvider.swift

override func startTunnel(options: [String : NSObject]? = nil, completionHandler: @escaping (Error?) -> Void) {

  conf = (self.protocolConfiguration as! NETunnelProviderProtocol).providerConfiguration! as [String : AnyObject]
  self.setupWSSession()
   
  DispatchQueue.global().async {
    while (self.connectionPhase < 5) {
      Thread.sleep(forTimeInterval: 0.5)
    }
    self.tunToWS()
  }
  NotificationCenter.default.post(name: NSNotification.Name(rawValue: "testnotify"), object: nil)
}

And here is a part of ViewController.swift

override func viewDidLoad() {
    super.viewDidLoad()
    initVPNTunnelProviderManager()
    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.VPNStatusDidChange(_:)), name: NSNotification.Name.NEVPNStatusDidChange, object: nil)
    
    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.receieve(_:)), name: NSNotification.Name(rawValue: "testnotify"), object: nil)
   
}


@objc func VPNStatusDidChange(_ notification: Notification?) {
  print("VPN Status changed:")
  let status = self.vpnManager.connection.status
  switch status {
  case .connecting:
    print("Connecting...")
    connectButton.setTitle("Disconnect", for: .normal)
    break
  case .connected:
    print("Connected...")
    connectButton.setTitle("Disconnect", for: .normal)
    break
  case .disconnecting:
    print("Disconnecting...")
    break
  case .disconnected:
    print("Disconnected...")
    connectButton.setTitle("Connect", for: .normal)
    break
  case .invalid:
    print("Invliad")
    break
  case .reasserting:
    print("Reasserting...")
    break
  }
}


@objc func receive(_ notification: Notification?)  {
    print("receive Notification!")
}

Accepted Reply

I would like to know how to change the NETunnelProviderManager.connection.status available in the Container APP from the PacketTunnelProvider side.

That happens automatically based on the state of the provider that’s visible to the system, for example, whether it’s called your start-tunnel method and whether you’ve then called that method’s completion handler. If you want to provide more detailed information, you’ll need to build that on top of an IPC mechanism.

If possible, I would like to know how to communicate with PacketTunnelProvider without calling sendProviderMessage(_:responseHandler:) on the Container APP side.

In situations like this I usually have the container app always call sendProviderMessage(_:responseHandler:) with a get-latest-status request. The provider then holds on to its completion handler and, the next time the status changes, the provider completes the request with that status.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Thank you so much eskimo. Thanks to your answers, I will be able to do what I want to do!

Add a Comment

Replies

I am currently building my own VPN application

Let’s start with some basics:

  • What platform are you targeting?

  • And if it’s macOS, are you building an app extension or a system extension?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Thanks for your reply eskimo.

    My target platform is iOS. And I am using network extension (packet tunnel provider).

    I would like to know how startTunnel() and stopTunnel(), which exist in the NEPacketTunnelProvider, use NotificationCenter to send messages to the ViewController.

    Kind regards

Add a Comment

My target platform is iOS.

OK, that simplifies this considerably because your IPC options on iOS are very limited.

I would like to know how startTunnel() and stopTunnel(), which exist in the NEPacketTunnelProvider, use NotificationCenter to send messages to the ViewController.

You can’t use NotificationCenter for this because your packet tunnel provider and your view controller are running in separate processes.

What sort of notifications your trying to send? If this is simple start/stop notifications, code in your app can observe those using the connection property of your NEVPNManager instance (for a packet tunnel this is actually of type NETunnelProviderSession). This has a property status and you can observe the NEVPNStatusDidChangeNotification notification to hear about changes.

If you need something more complicated than that, build it on top of the provider message infrastructure, that is:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you very much eskimo. 

By calling sendProviderMessage(_:responseHandler:) on the Container APP side, I was able to communicate with PacketTunnelProvider's handleAppMessage(_:completionHandler:).

If possible, I would like to know how to communicate with PacketTunnelProvider without calling sendProviderMessage(_:responseHandler:) on the Container APP side. I want to tell the user what stage of the connection process they are currently in and the detailed reasons for any errors that occur.

If we use the connection property of the NEVPNManager instance for this purpose, I would like to know how to change the NETunnelProviderManager.connection.status available in the Container APP from the PacketTunnelProvider side. And I would like to know how to add my own connection status.

sincerely

I would like to know how to change the NETunnelProviderManager.connection.status available in the Container APP from the PacketTunnelProvider side.

That happens automatically based on the state of the provider that’s visible to the system, for example, whether it’s called your start-tunnel method and whether you’ve then called that method’s completion handler. If you want to provide more detailed information, you’ll need to build that on top of an IPC mechanism.

If possible, I would like to know how to communicate with PacketTunnelProvider without calling sendProviderMessage(_:responseHandler:) on the Container APP side.

In situations like this I usually have the container app always call sendProviderMessage(_:responseHandler:) with a get-latest-status request. The provider then holds on to its completion handler and, the next time the status changes, the provider completes the request with that status.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Thank you so much eskimo. Thanks to your answers, I will be able to do what I want to do!

Add a Comment

Hello guys, I'm facing the same issue. I have tried to do it in a similar way that eskimo suggested.

I want to let the user know what is going on with some checks done in the PacketTunnelProvider. I'm calling sendProviderMessage from the app and have also implemented handleAppMessage in the PacketTunnelProvider. With every change of the checks in the PacketTunnelProvider I want to let the app know about the new state without having the app periodically request the state. I tried to retain the completion block in the PacketTunnelProvider and call it when the change of the state occurs outside of the method but the extension crashed with Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1a1312858). For testing purposes, I have made use of a Timer publisher that should simulate a subscriber for the checks.

override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
      self.completionHandler = completionHandler
}
override init {
      self.cancellable = Timer.publish(every: 5, on: .current, in: .default)
            .autoconnect()
            .sink(receiveValue: { output in
                self.completionHandler?(nil)
             })
}

Please, can you provide some guidance on how to approach this properly? Thank you so much!

You can’t call the completion handler multiple times. Rather, the client needs to re-issue the request after each completion. You are effectively implementing long polling.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Great, thank you Eskimo for pointing me in the right direction.

Add a Comment