Building a NetworkExtension App (Part 2)

Introduction In the previous post I covered some background knowledge on circumventing the GFW. This post will first introduce NetworkExtension and related open-source iOS projects, then we'll get started on our own project. In essence, the NetworkExtension app we're building plays the role of SS-Local. About NetworkExtension NetworkExtension is a framework provided by Apple for configuring VPNs and customizing or extending core networking features. The NE framework provides APIs for customizing and extending the core networking features of iOS and macOS. Network Extension first appeared in iOS 8, but that version did not support virtual network interfaces — it could only call into the system's built-in IPSec and IKEv2 VPN protocols. In iOS 9, developers gained the ability to extend the core networking layer using , enabling non-standard, custom VPN technologies. The two most important classes are and . Potatso implemented a Shadowsocks proxy using the NE framework. Unfortunately, the author deleted the open-source code for various reasons. A number of forks exist on GitHub but they are all slow to update. The most recently runnable version I found is this one, but since I had already upgraded to Xcode 9, I had to make a series of changes. I eventually got a version that compiles and runs on Xcode 9, though it's not perfect. Feel free to use it as a learning reference. About NEKit Learning Network Extension through Potatso isn't the most beginner-friendly path, since the project has gone unmaintained for quite a while. A simpler approach is available thanks to the NEKit framework. NEKit can even work without depending on the Network Extension framework (though our project will use it). There's also a demo worth looking at. Setting Up the Project Create a New Project Create a standard Swift project called QLadder (this project will later serve as our internal iOS VPN client, so we used an enterprise certificate). The minimum supported iOS version is 9.3 — I previously tried 9.0 and ran into issues. Also note: Network Extension cannot be debugged in the simulator. You will need a developer account to request the required Capabilities. Add a PacketTunnel Target Add a new Target and select Network Extension. Then set the Provider Type to PacketTunnel. Request Entitlements If the containing app needs to share data with the extension, App Groups must be enabled. Personal VPN and Network Extensions (App Proxy, Content Filter, Packet Tunnel) also need to be enabled, of course. Third-Party Frameworks NEKit is recommended to be dragged directly into the project, or integrated via Carthage. Other third-party frameworks managed via CocoaPods: - SwiftColor - CocoaLumberjack/Swift - Alamofire Code Of the two core classes mentioned above, maps one-to-one with a VPN configuration. If an app has two VPN configurations, you get two instances in code. There are four operations we need to perform on . 1. Create a VPN configuration 2. Save the VPN configuration When this code executes, it will prompt the user for authorization. Once granted, a VPN configuration entry is added. 3. Start and stop the VPN 4. Observe and update VPN status is where the actual VPN logic lives. The project's is a subclass of it, and the following two methods must be implemented: When the object in the app calls , control flow jumps to inside the extension. takes two parameters: is a dictionary passed in from the app — its contents are entirely up to the developer. is a closure callback provided by the system. You can save it to a variable and call it when the VPN has finished starting up. also takes two parameters: represents why the VPN was stopped. iOS defines a set of constants, but in practice is rarely used. The works the same way as in . The code here is largely based on Potatso, Specht, and RabbitVpnDemo. Debugging the Network Extension Debugging app code is straightforward, but how do you debug code inside the extension? Assuming the PacketTunnel code is already in place: 1. Build and run the app. 2. Stop the run. 3. In the Xcode menu, go to -, enter , and click . 4. Launch the app on the device (not through Xcode), then tap Connect — the debugger will hit the breakpoint. Code All the code in this article can be found on my GitHub .