How to embed a NetworkExtension into a static framework?
Probably not. First up, please define what you mean by “static framework”. Are you talking about an XCFramework? Or something else?Is this possible to do?
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
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!
I’m sorry but I don’t understand this. In Xcode, when you create a new target, you have two choices:An iOS cocoa touch framework - static, as in device only (dynamic also
works with simulators).
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.
It won’t be that simple. There are two further complications that spring to mind:Then the developer just has to set the extension project's bundleID to
have the same prefix as their custom app.
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.
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
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
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"
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.
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 { }