Update: createTCPConnectionThroughTunnel() does allow app extension to send Https Json call through the tunnel. This is good news.
My remote server did receive the json request sent by my app extension, and it shows the request was from the IP of the VPN server.
Having a hard time getting the response though. My code crashes the app extension. I don't know how to properly handle Http request/response on a NWTCPConnection.
I created a class:
class HttpClientConnection: NSObject
in which there is a method:
open func startConnection(_ provider: NETunnelProvider, _ serverNamePort: String, _ httpRequestData: String, _ size: Int ) -> Bool {
...
connection = (provider as! NEPacketTunnelProvider).createTCPConnectionThroughTunnel(to: endpoint, enableTLS:true, tlsParameters:nil, delegate:nil)
connection!.addObserver(self, forKeyPath: "state", options: .initial, context: &connection)
The observer:
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard keyPath == "state" && context?.assumingMemoryBound(to: Optional<NWTCPConnection>.self).pointee == connection else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
simpleTunnelLog("Tunnel connection state changed to \(connection!.state)")
switch connection!.state {
case .connected:
if let remoteAddress = self.connection!.remoteAddress as? NWHostEndpoint {
remoteHost = remoteAddress.hostname
}
// connect? We send the http request:
sendHttpRequest(httpRequestData, httpRequestDataSize) {_ in
simpleTunnelLog("Sent Http Request")
let response = self.receiveHttpResponse()
}
case .disconnected:
closeHttpConnectionWithError(connection!.error as NSError?)
case .cancelled:
connection!.removeObserver(self, forKeyPath:"state", context:&connection)
connection = nil
// delegate?.tunnelDidClose(self)
default:
break
}
}
This is the code I use to send out the JSON Http request in function:
open func sendHttpRequest(_ httpRequestData: String, _ size: Int, completionHandler: @escaping(Error?) -> Void) {
if let rawData = httpRequestData.data(using: .utf8) {
simpleTunnelLog("sendHttpRequest - to call write")
connection?.write(rawData, completionHandler: completionHandler) // this call crashes the app extension
}
Then, I tried to get the response. The key part is below method. It's not working, and causing memory issue. readMinimumLength() seems to come back w/o reading any data. I searched but couldn't find a good example on how to handle receiving of http response on a NWTCPConnection in Swift. I wish URLSession can be subclassed to use this connection returned by createTCPConnectionThroughTunnel().
func HttpRecvALine() -> String {
guard let targetConnection = connection else {
closeHttpConnectionWithError(SimpleTunnelError.badConnection as NSError)
return ""
}
var charThisRead = ""
var textRead = ""
while true {
targetConnection.readMinimumLength(1, maximumLength: 1) { data, error in
if let readError = error {
simpleTunnelLog("Got an error on the tunnel connection: \(readError)")
self.closeHttpConnectionWithError(readError as NSError?)
return
}
if let data = data {
charThisRead = String(decoding: data, as: UTF8.self)
textRead += charThisRead
simpleTunnelLog("textRead: \(textRead) - data\(data)") // nothing was read here.
} else {
simpleTunnelLog("No more data to read")
return
}
}
if charThisRead == "\n" {
// found end of line
simpleTunnelLog("found end of line - so far:\(textRead)")
break
}
}
return textRead
}
Any insight and pointers would be greatly appreciated.