|
|
@@ -19,7 +19,7 @@ class ViewController: UIViewController {
|
|
|
// The instance of PsiphonTunnel we'll use for connecting.
|
|
|
var psiphonTunnel: PsiphonTunnel?
|
|
|
|
|
|
- // This are the ports that we can proxy through.
|
|
|
+ // These are the ports that we can proxy through.
|
|
|
var socksProxyPort = -1
|
|
|
var httpProxyPort = -1
|
|
|
|
|
|
@@ -33,12 +33,10 @@ class ViewController: UIViewController {
|
|
|
|
|
|
override func viewDidLoad() {
|
|
|
super.viewDidLoad()
|
|
|
- // Do any additional setup after loading the view, typically from a nib.
|
|
|
|
|
|
// Start up the tunnel and begin connecting.
|
|
|
// This could be started elsewhere or earlier.
|
|
|
NSLog("Starting tunnel")
|
|
|
-
|
|
|
let embeddedServerEntries = ""
|
|
|
guard let success = self.psiphonTunnel?.start(embeddedServerEntries), success else {
|
|
|
NSLog("psiphonTunnel.start returned false")
|
|
|
@@ -50,11 +48,144 @@ class ViewController: UIViewController {
|
|
|
super.didReceiveMemoryWarning()
|
|
|
// Dispose of any resources that can be recreated.
|
|
|
}
|
|
|
+
|
|
|
+ func appendToView(_ text: String) {
|
|
|
+ let escapedText = text.replacingOccurrences(of: "\n", with: "\\n")
|
|
|
+ .replacingOccurrences(of: "\r", with: "")
|
|
|
+ self.webView.stringByEvaluatingJavaScript(from: String.init(format: "document.body.innerHTML+='<br><pre>%@</pre><br>'", arguments: [escapedText]))
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Request URL using URLSession configured to use the current proxy.
|
|
|
+ /// * parameters:
|
|
|
+ /// - url: The URL to request.
|
|
|
+ /// - completion: A callback function that will received the string obtained
|
|
|
+ /// from the request, or nil if there's an error.
|
|
|
+ /// * returns: The string obtained from the request, or nil if there's an error.
|
|
|
+ func makeRequestViaUrlSessionProxy(_ url: String, completion: @escaping (_ result: String?) -> ()) {
|
|
|
+ assert(self.httpProxyPort > 0)
|
|
|
+
|
|
|
+ let request = URLRequest(url: URL(string: url)!)
|
|
|
+
|
|
|
+ let config = URLSessionConfiguration.ephemeral
|
|
|
+ config.requestCachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
|
|
|
+ config.connectionProxyDictionary = [AnyHashable: Any]()
|
|
|
+
|
|
|
+ // Enable and set the SOCKS proxy values.
|
|
|
+ config.connectionProxyDictionary?[kCFStreamPropertySOCKSProxy as String] = 1
|
|
|
+ config.connectionProxyDictionary?[kCFStreamPropertySOCKSProxyHost as String] = "127.0.0.1"
|
|
|
+ config.connectionProxyDictionary?[kCFStreamPropertySOCKSProxyPort as String] = self.socksProxyPort
|
|
|
+
|
|
|
+ // Alternatively, the HTTP proxy can be used. Below are the settings for that.
|
|
|
+ // The HTTPS key constants are mismatched and Xcode gives deprecation warnings, but they seem to be necessary to proxy HTTPS requests. This is probably a bug on Apple's side; see: https://forums.developer.apple.com/thread/19356#131446
|
|
|
+ // config.connectionProxyDictionary?[kCFNetworkProxiesHTTPEnable as String] = 1
|
|
|
+ // config.connectionProxyDictionary?[kCFNetworkProxiesHTTPProxy as String] = "127.0.0.1"
|
|
|
+ // config.connectionProxyDictionary?[kCFNetworkProxiesHTTPPort as String] = self.httpProxyPort
|
|
|
+ // config.connectionProxyDictionary?[kCFStreamPropertyHTTPSProxyHost as String] = "127.0.0.1"
|
|
|
+ // config.connectionProxyDictionary?[kCFStreamPropertyHTTPSProxyPort as String] = self.httpProxyPort
|
|
|
+
|
|
|
+ let session = URLSession.init(configuration: config, delegate: nil, delegateQueue: OperationQueue.current)
|
|
|
+
|
|
|
+ // Create the URLSession task that will make the request via the tunnel proxy.
|
|
|
+ let task = session.dataTask(with: request) {
|
|
|
+ (data: Data?, response: URLResponse?, error: Error?) in
|
|
|
+ if error != nil {
|
|
|
+ NSLog("Client-side error in request to \(url): \(error)")
|
|
|
+ // Invoke the callback indicating error.
|
|
|
+ completion(nil)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if data == nil {
|
|
|
+ NSLog("Data from request to \(url) is nil")
|
|
|
+ // Invoke the callback indicating error.
|
|
|
+ completion(nil)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let httpResponse = response as? HTTPURLResponse
|
|
|
+ if httpResponse?.statusCode != 200 {
|
|
|
+ NSLog("Server-side error in request to \(url): \(httpResponse)")
|
|
|
+ // Invoke the callback indicating error.
|
|
|
+ completion(nil)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let encodingName = response?.textEncodingName != nil ? response?.textEncodingName : "utf-8"
|
|
|
+ let encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName as CFString!))
|
|
|
+
|
|
|
+ let stringData = String(data: data!, encoding: String.Encoding(rawValue: UInt(encoding)))
|
|
|
+
|
|
|
+ // Make sure the session is cleaned up.
|
|
|
+ session.invalidateAndCancel()
|
|
|
+
|
|
|
+ // Invoke the callback with the result.
|
|
|
+ completion(stringData)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Start the request task.
|
|
|
+ task.resume()
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Request URL using Psiphon's "URL proxy" mode.
|
|
|
+ /// For details, see the comment near the top of:
|
|
|
+ /// https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/master/psiphon/httpProxy.go
|
|
|
+ /// * parameters:
|
|
|
+ /// - url: The URL to request.
|
|
|
+ /// - completion: A callback function that will received the string obtained
|
|
|
+ /// from the request, or nil if there's an error.
|
|
|
+ /// * returns: The string obtained from the request, or nil if there's an error.
|
|
|
+ func makeRequestViaUrlProxy(_ url: String, completion: @escaping (_ result: String?) -> ()) {
|
|
|
+ assert(self.httpProxyPort > 0)
|
|
|
+
|
|
|
+ // The target URL must be encoded so as to be valid within a query parameter.
|
|
|
+ // See this SO answer for why we're using this CharacterSet (and not using: https://stackoverflow.com/a/24888789
|
|
|
+ let queryParamCharsAllowed = CharacterSet.init(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")
|
|
|
+
|
|
|
+ let encodedTargetURL = url.addingPercentEncoding(withAllowedCharacters: queryParamCharsAllowed)
|
|
|
+
|
|
|
+ let proxiedURL = "http://127.0.0.1:\(self.httpProxyPort)/tunneled/\(encodedTargetURL!)"
|
|
|
+
|
|
|
+ let task = URLSession.shared.dataTask(with: URL(string: proxiedURL)!) {
|
|
|
+ (data: Data?, response: URLResponse?, error: Error?) in
|
|
|
+ if error != nil {
|
|
|
+ NSLog("Client-side error in request to \(url): \(error)")
|
|
|
+ // Invoke the callback indicating error.
|
|
|
+ completion(nil)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if data == nil {
|
|
|
+ NSLog("Data from request to \(url) is nil")
|
|
|
+ // Invoke the callback indicating error.
|
|
|
+ completion(nil)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let httpResponse = response as? HTTPURLResponse
|
|
|
+ if httpResponse?.statusCode != 200 {
|
|
|
+ NSLog("Server-side error in request to \(url): \(httpResponse)")
|
|
|
+ // Invoke the callback indicating error.
|
|
|
+ completion(nil)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let encodingName = response?.textEncodingName != nil ? response?.textEncodingName : "utf-8"
|
|
|
+ let encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName as CFString!))
|
|
|
+
|
|
|
+ let stringData = String(data: data!, encoding: String.Encoding(rawValue: UInt(encoding)))
|
|
|
+
|
|
|
+ // Invoke the callback with the result.
|
|
|
+ completion(stringData)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Start the request task.
|
|
|
+ task.resume()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// MARK: TunneledAppDelegate implementation
|
|
|
// See the protocol definition for details about the methods.
|
|
|
-// Note that we're excluding all the optional methods that we aren't using,
|
|
|
+// Note that we're excluding all the optional methods that we aren't using,
|
|
|
// however your needs may be different.
|
|
|
extension ViewController: TunneledAppDelegate {
|
|
|
func getPsiphonConfig() -> String? {
|
|
|
@@ -81,68 +212,52 @@ extension ViewController: TunneledAppDelegate {
|
|
|
func onConnected() {
|
|
|
NSLog("onConnected")
|
|
|
|
|
|
- // After we're connected, make a tunneled request and populate the webview.
|
|
|
+ // After we're connected, make tunneled requests and populate the webview.
|
|
|
|
|
|
- DispatchQueue.main.async {
|
|
|
- assert(self.httpProxyPort > 0)
|
|
|
-
|
|
|
- // We'll check out IP to make sure we're tunneled.
|
|
|
- let urlPath: String = "https://freegeoip.net/csv/"
|
|
|
- let url = URL(string: urlPath)!
|
|
|
- let request = URLRequest(url: url)
|
|
|
-
|
|
|
- let config = URLSessionConfiguration.ephemeral
|
|
|
- config.requestCachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
|
|
|
- config.connectionProxyDictionary = [AnyHashable: Any]()
|
|
|
-
|
|
|
- // Enable and set the SOCKS proxy values.
|
|
|
- config.connectionProxyDictionary?[kCFStreamPropertySOCKSProxy as String] = 1
|
|
|
- config.connectionProxyDictionary?[kCFStreamPropertySOCKSProxyHost as String] = "127.0.0.1"
|
|
|
- config.connectionProxyDictionary?[kCFStreamPropertySOCKSProxyPort as String] = self.socksProxyPort
|
|
|
-
|
|
|
- // Alternatively, the HTTP proxy can be used. Below are the settings for that.
|
|
|
- // The HTTPS key constants are mismatched and Xcode gives deprecation warnings, but they seem to be necessary to proxy HTTPS requests. This is probably a bug on Apple's side; see: https://forums.developer.apple.com/thread/19356#131446
|
|
|
- // config.connectionProxyDictionary?[kCFNetworkProxiesHTTPEnable as String] = 1
|
|
|
- // config.connectionProxyDictionary?[kCFNetworkProxiesHTTPProxy as String] = "127.0.0.1"
|
|
|
- // config.connectionProxyDictionary?[kCFNetworkProxiesHTTPPort as String] = self.httpProxyPort
|
|
|
- // config.connectionProxyDictionary?[kCFStreamPropertyHTTPSProxyHost as String] = "127.0.0.1"
|
|
|
- // config.connectionProxyDictionary?[kCFStreamPropertyHTTPSProxyPort as String] = self.httpProxyPort
|
|
|
-
|
|
|
- let session = URLSession.init(configuration: config, delegate: nil, delegateQueue: OperationQueue.current)
|
|
|
-
|
|
|
- // Create the URLSession task that will make the request via the tunnel proxy.
|
|
|
- let task = session.dataTask(with: request) {
|
|
|
- (data: Data?, response: URLResponse?, error: Error?) in
|
|
|
- if error != nil {
|
|
|
- NSLog("Client-side error in request to \(urlPath): \(error)")
|
|
|
- return
|
|
|
- }
|
|
|
+ DispatchQueue.global(qos: .default).async {
|
|
|
+ // First we'll make a "what is my IP" request via makeRequestViaUrlSessionProxy().
|
|
|
+ let url = "https://freegeoip.net/json/"
|
|
|
+ self.makeRequestViaUrlSessionProxy(url) {
|
|
|
+ (_ result: String?) in
|
|
|
|
|
|
- let httpResponse = response as? HTTPURLResponse
|
|
|
- if httpResponse?.statusCode != 200 {
|
|
|
- NSLog("Server-side error in request to \(urlPath): \(httpResponse)")
|
|
|
+ if result == nil {
|
|
|
+ NSLog("Failed to get \(url)")
|
|
|
return
|
|
|
}
|
|
|
+
|
|
|
+ // Do a little pretty-printing.
|
|
|
+ let prettyResult = result?.replacingOccurrences(of: ",", with: ",\n ")
|
|
|
+ .replacingOccurrences(of: "{", with: "{\n ")
|
|
|
+ .replacingOccurrences(of: "}", with: "\n}")
|
|
|
|
|
|
- let encodingName = response?.textEncodingName != nil ? response?.textEncodingName : "utf-8"
|
|
|
- let encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName as CFString!))
|
|
|
-
|
|
|
- var stringData = String(data: data!, encoding: String.Encoding(rawValue: UInt(encoding)))
|
|
|
- stringData = stringData?.replacingOccurrences(of: ",", with: "\n")
|
|
|
-
|
|
|
- // Load the IP info result into the web view.
|
|
|
- self.webView.loadHTMLString("<br><pre>\(stringData!)</pre>", baseURL: url)
|
|
|
+ DispatchQueue.main.sync {
|
|
|
+ // Load the result into the view.
|
|
|
+ self.appendToView("Result from \(url):\n\(prettyResult!)")
|
|
|
+ }
|
|
|
|
|
|
- // Make sure the session is cleaned up.
|
|
|
- session.invalidateAndCancel()
|
|
|
+ // Then we'll make a different "what is my IP" request via makeRequestViaUrlProxy().
|
|
|
+ DispatchQueue.global(qos: .default).async {
|
|
|
+ let url = "http://ipinfo.io/json"
|
|
|
+ self.makeRequestViaUrlProxy(url) {
|
|
|
+ (_ result: String?) in
|
|
|
+
|
|
|
+ if result == nil {
|
|
|
+ NSLog("Failed to get \(url)")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ DispatchQueue.main.sync {
|
|
|
+ // Load the result into the view.
|
|
|
+ self.appendToView("Result from \(url):\n\(result!)")
|
|
|
+ }
|
|
|
+
|
|
|
+ // We're done with the Psiphon tunnel, so stop it.
|
|
|
+ // In a real app, we would keep this alive for as long as we need it.
|
|
|
+ self.psiphonTunnel?.stop()
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // We're done with the Psiphon tunnel, so stop it.
|
|
|
- // In a real app, we would keep this alive for as long as we need it.
|
|
|
- self.psiphonTunnel?.stop()
|
|
|
}
|
|
|
-
|
|
|
- // Start the request task.
|
|
|
- task.resume()
|
|
|
}
|
|
|
}
|
|
|
|