How to embed a NetworkExtension into a static framework?

We have a working POC app that uses a NetworkExtension that we have created to manage a VPN connection, and it works as expected. We would like to provide a static framework (binary) that has this network extension embedded into it such that our customers can build their own application that will use our VPN management extension. I cannot see how/where in build settings to embed the extension into the framework. Is this possible to do?

Is this possible to do?

Probably not. First up, please define what you mean by “static framework”. Are you talking about an XCFramework? Or something else?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
An iOS cocoa touch framework - static, as in device only (dynamic also works with simulators). I "think" I might have an idea for a solution: Create the "guts" of a packet tunnel extension as a framework, and then create a project for a skeletal extension that includes that framework (as well as the NetworkExtension framework). Then the developer just has to set the extension project's bundleID to have the same prefix as their custom app. All we would need to distribute is the binary framework and a sample project.

The goal is to make it easy for a developer to incorporate the functionality of a packet tunnel extension into an existing app, while shielding them from the contents of the extension. If you have suggestions for I workable solution, I'd love to try it.

Thanks!

An iOS cocoa touch framework - static, as in device only (dynamic also
works with simulators).

I’m sorry but I don’t understand this. In Xcode, when you create a new target, you have two choices:
  • iOS > Framework creates a framework; there’s nothing ‘static’ about it.

  • iOS > Static Library creates a static library; it’s nothing ‘framework-y’ about that.

Then the developer just has to set the extension project's bundleID to
have the same prefix as their custom app.

It won’t be that simple. There are two further complications that spring to mind:
  • A completely codeless NE provider is not going to work. There needs to be a least some code in the provider for it to bring in the framework so that NE can find and instantiate the NE provider class at runtime (via Info.plist > NSExtension > NSExtensionPrincipalClass).

  • Your clients are going to have to deal with entitlements. Modern versions of Xcode make that relatively straightforward (via Signing & Capabilities > Network Extensions) but I still see a lot of folks stumble at this hurdle. Oh, and remember that these entitlements have to be set on both the NE provider target and the container app target.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
Yes, sorry - I got tripped up on the terminology. I meant not a universal framework.

As far as the code in the extension to pull in the framework, yes I anticipated that - we mostly want to obscure the bulk of the inner workings. And yes, they'll need to deal with entitlements.

I'll try to have all most of this taken care of with the sample project for both the extension and the app.

Thanks
So close.. (I think!)
I have a new network extension, and the target class just inherits from the class that we've been using in our app.

Here is the target class:
Code Block import NetworkExtension
import IWiNS_SDK
class PacketTunnelProvider: PacketTun {
}



PacketTun is now included in our framework (IWiNS_SDK), with the class and relevant functions declared public. All entitlements seem in order.

Here is the extension info.plist:
Code Block <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>IWiNS-VpnTunnel-V2</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
</dict>
</plist>


Here is the entitlements file:
Code Block <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>com.apple.developer.networking.networkextension</key>
  <array>
    <string>packet-tunnel-provider</string>
  </array>
  <key>com.apple.security.app-sandbox</key>
  <true/>
  <key>com.apple.security.application-groups</key>
  <array>
    <string>group.com.cablelabs.vpndemoappgroup</string>
  </array>
  <key>com.apple.security.network.client</key>
  <true/>
  <key>com.apple.security.network.server</key>
  <true/>
</dict>
</plist>



When the app comes up for the first time, we create a new VPN Configuration, which looks OK in the iOS->Settings->VPN page.

But when I try to connect, I get the error in the console: "The VPN app used by the VPN configuration is not installed"

Any suggestions?

Thank you for your replies.


Code Block
default 13:11:22.260673-0700 IWiNS-ReferenceApp Saving configuration IWiNS with existing signature {length = 20, bytes = 0x9327ba20312d9952f7be79a6903171614d8ac4de}
default 13:11:22.279160-0700 IWiNS-ReferenceApp Received a com.apple.neconfigurationchanged notification with token 100
default 13:11:22.279539-0700 IWiNS-ReferenceApp Successfully saved configuration IWiNS
default 13:11:37.428236-0700 IWiNS-ReferenceApp Attempting to start VPN
default 13:11:37.438298-0700 IWiNS-ReferenceApp Last disconnect error for IWiNS changed from "The VPN app used by the VPN configuration is not installed" to "none"
default 13:11:37.441893-0700 IWiNS-ReferenceApp Received configuration update from daemon (initial)
default 13:11:37.449975-0700 IWiNS-ReferenceApp Last disconnect error for IWiNS changed from "none" to "The VPN app used by the VPN configuration is not installed"

Update: Thinking that this runtime error may not have anything to do with the tunnel code being refactored into an SDK, I tried to recreate the working network extension as a brand new target, with the same settings, and I am seeing the same error.

So I think it has to do with how the app is associated with the network extension. I verified that the new network extension's bundleID has a prefix that matches the application bundleID, but I am now wondering if there is something in the application configuration (or code) that needs to be set to specify the name of the network extension.
Solved!

When creating a VPN Configuration from within the application, I needed to set the bundleID of the configuration to match the bundleID of the network extension.

I now have a skeletal xcode project containing targets for a sample application and a network extension. The primitives for creating a TunnelProviderManager (creating VPN Configurations) and start/stopping the tunnel are embedded in an object that lives in the framework that I created, an SDK of sorts.

The guts of the network extension implementation are also in the framework, which contains this class:

Code Block import Foundation
import NetworkExtension
open class PacketTun: NEPacketTunnelProvider {
.
.
.
}

Then, for the PrincipalClass defined for the NetworkExtension target, I have a class derived from the implementation found in the framework, which has no additional implementation

Code Block import NetworkExtension
import IWiNS_SDK
class PacketTunnelProvider: PacketTun {
}



How to embed a NetworkExtension into a static framework?
 
 
Q