Automatic Reverse Proxy with NETransparentProxyManager (or similar?)

Hello,

I am trying to build an automatic reverse proxy for the development experience. Basically all developers are limited to running several web applications on various port on localhost, like localhost:8000 and localhost:9000. My idea is simple, trying to map http://app1.dev.local to localhost:8000 and http://app2.dev.local to localhost:9000. It could be easily achieved with a few records in /etc/hosts

Code Block
127.0.0.1 app1.dev.local
127.0.0.1 app2.dev.local


And installed haproxy/nginx on port 80 that will redirect the traffic based on the Host header.

But my idea is to use the Network Extensions to do the same. To use more native experience.

I have tried to use NETransparentProxyManager with NETunnelProviderProtocol and NEAppProxyProvider in system network extension.

In the app I have

Code Block
NETransparentProxyManager.loadAllFromPreferences { (managers, loadError) in
...
if self.manager == nil {
self.logger.log("creating NETransparentProxyManager")
let config = NETunnelProviderProtocol()
config.providerBundleIdentifier = "app.loshadki.DevVPN.systemextension"
config.serverAddress = "DevVPN"
config.disconnectOnSleep = false
self.manager = NETransparentProxyManager()
self.manager!.localizedDescription = "DevVPN"
self.manager!.isEnabled = true
self.manager!.protocolConfiguration = config
} else {
self.logger.log("NETransparentProxyManager DevVPN already exists")
}
self.manager!.saveToPreferences(completionHandler: { (saveError) in
...
do {
try self.manager?.connection.startVPNTunnel(options: ["mode": "localOnly" as NSObject])
} catch {
...
}
})
}
}


And in the system network extension

Code Block
class AppProxyProvider: NEAppProxyProvider {
override func startProxy(options: [String : Any]?, completionHandler: @escaping (Error?) -> Void) {
...
let proxySettings = NETransparentProxyNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
proxySettings.includedNetworkRules = [
NENetworkRule(destinationNetwork: NWHostEndpoint(hostname: hostnameIP, port: "80"), prefix: 32, protocol: .any),
NENetworkRule(destinationHost: NWHostEndpoint(hostname: "app1.dev.local", port: "80"), protocol: .any),
NENetworkRule(destinationHost: NWHostEndpoint(hostname: "app2.dev.local", port: "80"), protocol: .any)
]
setTunnelNetworkSettings(proxySettings) { error in
...
}
}
...
}


That actually does what I want, but only for Safari. If I try to use curl or Chrome, those two ignore the VPN and fail to resolve the DNS.

If I use curl directly on IP address, that works. So host based NENetworkRule do not work on curl or Chrome.

I have tried also to switch to NEAppProxyProviderManager instead, which has NEAppRule, and it also includes matchDomains, but when I specify those: (a) for Chome as an example matchDomains does not work, I am getting all traffic (b) for Safari and curl I cannot catch the traffic with

Code Block
NEAppRule(signingIdentifier: "com.apple.Safari", designatedRequirement: "identifier \"com.apple.Safari\" and anchor apple")


So I am curious, is it possible with the network extensions to achieve what I want? If yes, what kind of Manager should I use and Provider?

So I am curious, is it possible with the network extensions to achieve what I want?

Well, receiving flows as IP based might be what you have to work with here as this is what the OS has at the time the flow is delivered to the Network System Extension. As a way to counteract this, you could try and identify the flow via the bundle id to and see if the IP is within a range that your proxy is concerned about.

To get the bundle id the flow originated from use the sourceAppAuditToken from the NEAppProxyFlow's NEFlowMetaData property.


Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Hey Matt,

Thank you for the answer.
So in that case I would need both:
  • my own DNS server say for the *.local domains

  • and IP based network extension the way I write it in the example?

Is there are a way to inject some DNS information (similar to /etc/hosts) without actually running my private DNS server on the system? And if I would want to run my own DNS server, is it possible not to run it on the port 53?

And if I would want to run my own DNS server, is it possible not to run it on the port 53?

Sure. If you are running your own DNS server, it is up to you on how you configure this.

Regarding:

Is there are a way to inject some DNS information (similar to /etc/hosts) without
actually running my private DNS server on the system?

I have never actually tried this on /etc/hosts to see if it impacts the IP based flows passing through the proxy. It would be worth a test, but I suspect would be difficult to do on a customer based install. You could also try and use the SCDynamicStore APIs to see if this give you the flexibility with "State:/Network/Global/DNS," just make sure you do this carefully as this affects the DNS on the system.


Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Automatic Reverse Proxy with NETransparentProxyManager (or similar?)
 
 
Q