I'm using the Network Extension framework provided by Apple to build a packet sniffing/monitoring application similar to Charles Proxy and Surge 4 for iOS.
So far, I have the basic structure of the project up and running with the Main Application triggering the PacketTunnelProvider Extension where I can see packets being forwarded via the packetFlow.readPackets(completionHandler:) method. My background isn't in networking so I'm confused on the basic structure of these kinds of apps. Do they host a server on the device that act as the proxy which intercepts network requests? Could anyone provide a diagram of the general flow of the network requests? I.e. what is the relationship between the Packet Tunnel Provider, Proxy Server, Virtual Interface, and Tunnel?
If these apps do use a local on-device server, how do you configure the NEPacketTunnelNetworkSettings to allow for a connection? I have tried incorporating a local on-device server such as GCDWebServer with no luck in establishing a link between the two.
For example, if the GCDWebServer was reachable at 192.168.1.231:8080, how would I change the code below for the client to communicate with the server?
Main App:
let proxyServer = NEProxyServer(address: "192.168.1.231", port: 8080)
let proxySettings = NEProxySettings()
proxySettings.exceptionList = []
proxySettings.httpEnabled = true
proxySettings.httpServer = proxyServer
let providerProtocol = NETunnelProviderProtocol()
providerProtocol.providerBundleIdentifier = self.tunnelBundleId
providerProtocol.serverAddress = "My Server"
providerProtocol.providerConfiguration = [:]
providerProtocol.proxySettings = proxySettings
let newManager = NETunnelProviderManager()
newManager.localizedDescription = "Custom VPN"
newManager.protocolConfiguration = providerProtocol
newManager.isEnabled = true
saveLoadManager()
self.vpnManager = newManager
PacketTunnelProviderExtension:
func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
...
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.143")
settings.ipv4Settings = NEIPv4Settings(addresses: ["198.17.203.2"], subnetMasks: ["255.255.255.255"])
settings.ipv4Settings?.includedRoutes = [NEIPv4Route.default()]
settings.ipv4Settings?.excludedRoutes = []
settings.dnsSettings = NEDNSSettings(servers: ["8.8.8.8", "8.8.4.4"])
settings.dnsSettings?.matchDomains = [""]
self.setTunnelNetworkSettings(settings) { error in
if let e = error {
NSLog("Settings error %@", e.localizedDescription)
} else {
completionHandler(error)
self.readPackets()
}
}
...
}
I'm working on the iOS version of Proxyman and my experience can help you:
Yes, you have to start a Listener on the Network Extension (not the main app) to act as a Proxy Server. You can write a simple Proxy Server by using Swift NIO or CocoaAsyncSocket.
To intercept the HTTPS traffic, it's a quite big challenge, but I won't mention here since it's out of the scope.
As the Network Extension and the Main app are two different processes, so they couldn't communicate directly like normal apps.
Thus, the flow may look like:
The Internet -> iPhone -> Your Network Extension (VPN) -> Forward to your Local Proxy Server -> Intercept or monitor -> Save to a local database (in Shared Container Group) -> Forward again to the destination server.
From the main app, you can receive the data by reading the local database.
In the Network extension, let start a Proxy Server at Host:Port, then init the NetworkSetting, like the sample:
Then start a VPN,
Then forward the package to your local proxy server:
From that, your local server can receive the packages then forwarding to the destination server.