Browse Source

Merge branch 'master' of https://github.com/Psiphon-Labs/psiphon-tunnel-core

Rod Hynes 9 years ago
parent
commit
fbe4463729

+ 3 - 0
MobileLibrary/iOS/BUILD.md

@@ -22,6 +22,9 @@
 
 
 * The result will be in `MobileLibrary/iOS/build`.
 * The result will be in `MobileLibrary/iOS/build`.
 
 
+#### Testing
+
+Run `test-psiphon-framework.sh`.
 
 
 ## Automatic Build -- Jenkins
 ## Automatic Build -- Jenkins
 
 

+ 173 - 58
MobileLibrary/iOS/SampleApps/TunneledWebRequest/TunneledWebRequest/ViewController.swift

@@ -19,7 +19,7 @@ class ViewController: UIViewController {
     // The instance of PsiphonTunnel we'll use for connecting.
     // The instance of PsiphonTunnel we'll use for connecting.
     var psiphonTunnel: PsiphonTunnel?
     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 socksProxyPort = -1
     var httpProxyPort = -1
     var httpProxyPort = -1
     
     
@@ -33,12 +33,10 @@ class ViewController: UIViewController {
     
     
     override func viewDidLoad() {
     override func viewDidLoad() {
         super.viewDidLoad()
         super.viewDidLoad()
-        // Do any additional setup after loading the view, typically from a nib.
         
         
         // Start up the tunnel and begin connecting.
         // Start up the tunnel and begin connecting.
         // This could be started elsewhere or earlier.
         // This could be started elsewhere or earlier.
         NSLog("Starting tunnel")
         NSLog("Starting tunnel")
-        
         let embeddedServerEntries = ""
         let embeddedServerEntries = ""
         guard let success = self.psiphonTunnel?.start(embeddedServerEntries), success else {
         guard let success = self.psiphonTunnel?.start(embeddedServerEntries), success else {
             NSLog("psiphonTunnel.start returned false")
             NSLog("psiphonTunnel.start returned false")
@@ -50,11 +48,144 @@ class ViewController: UIViewController {
         super.didReceiveMemoryWarning()
         super.didReceiveMemoryWarning()
         // Dispose of any resources that can be recreated.
         // 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
 // MARK: TunneledAppDelegate implementation
 // See the protocol definition for details about the methods.
 // 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.
 // however your needs may be different.
 extension ViewController: TunneledAppDelegate {
 extension ViewController: TunneledAppDelegate {
     func getPsiphonConfig() -> String? {
     func getPsiphonConfig() -> String? {
@@ -81,68 +212,52 @@ extension ViewController: TunneledAppDelegate {
     func onConnected() {
     func onConnected() {
         NSLog("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
                     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()
         }
         }
     }
     }
     
     

+ 5 - 6
MobileLibrary/iOS/build-psiphon-framework.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 #!/usr/bin/env bash
 
 
-set -e
+set -x
 
 
 BASE_DIR=$(cd "$(dirname "$0")" ; pwd -P)
 BASE_DIR=$(cd "$(dirname "$0")" ; pwd -P)
 cd ${BASE_DIR}
 cd ${BASE_DIR}
@@ -39,7 +39,8 @@ fi
 # Not exporting this breaks go commands later if run via jenkins
 # Not exporting this breaks go commands later if run via jenkins
 export GOPATH=${PWD}/go-ios-build
 export GOPATH=${PWD}/go-ios-build
 
 
-GOMOBILE_PINNED_REV=e99a906c3a3ac5959fa4b8d08f90dd5f75d3b27c
+# When updating the pinned rev, you will have to manually delete go-ios-build
+GOMOBILE_PINNED_REV=aa9922ad4c79ee8a56cd45bf433f2aa943712b09
 GOMOBILE_PATH=${GOPATH}/src/golang.org/x/mobile/cmd/gomobile
 GOMOBILE_PATH=${GOPATH}/src/golang.org/x/mobile/cmd/gomobile
 
 
 IOS_SRC_DIR=${GOPATH}/src/github.com/Psiphon-Labs/psiphon-ios
 IOS_SRC_DIR=${GOPATH}/src/github.com/Psiphon-Labs/psiphon-ios
@@ -92,9 +93,9 @@ cd OpenSSL-for-iPhone && ./build-libssl.sh; cd -
 strip_architectures "${LIBSSL}"
 strip_architectures "${LIBSSL}"
 strip_architectures "${LIBCRYPTO}"
 strip_architectures "${LIBCRYPTO}"
 
 
-go get -d  -u -v github.com/Psiphon-Inc/openssl
+go get -d -u -v github.com/Psiphon-Inc/openssl
 rc=$?; if [[ $rc != 0 ]]; then
 rc=$?; if [[ $rc != 0 ]]; then
-  echo "FAILURE: go get -d  -u -v github.com/Psiphon-Inc/openssl"
+  echo "FAILURE: go get -d -u -v github.com/Psiphon-Inc/openssl"
   exit $rc
   exit $rc
 fi
 fi
 
 
@@ -172,8 +173,6 @@ IOS_CGO_BUILD_FLAGS='// #cgo darwin CFLAGS: -I'"${OPENSSL_INCLUDE}"'\
 
 
 LC_ALL=C sed -i -- "s|// #cgo pkg-config: libssl|${IOS_CGO_BUILD_FLAGS}|" "${OPENSSL_SRC_DIR}/build.go"
 LC_ALL=C sed -i -- "s|// #cgo pkg-config: libssl|${IOS_CGO_BUILD_FLAGS}|" "${OPENSSL_SRC_DIR}/build.go"
 
 
-gomobile init
-
 gomobile bind -target ios -ldflags="${LDFLAGS}" -o "${INTERMEDIATE_OUPUT_DIR}/${INTERMEDIATE_OUPUT_FILE}" github.com/Psiphon-Labs/psiphon-tunnel-core/MobileLibrary/psi
 gomobile bind -target ios -ldflags="${LDFLAGS}" -o "${INTERMEDIATE_OUPUT_DIR}/${INTERMEDIATE_OUPUT_FILE}" github.com/Psiphon-Labs/psiphon-tunnel-core/MobileLibrary/psi
 rc=$?; if [[ $rc != 0 ]]; then
 rc=$?; if [[ $rc != 0 ]]; then
   echo "FAILURE: gomobile bind"
   echo "FAILURE: gomobile bind"

+ 7 - 5
psiphon/httpProxy.go

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2015, Psiphon Inc.
+ * Copyright (c) 2016, Psiphon Inc.
  * All rights reserved.
  * All rights reserved.
  *
  *
  * This program is free software: you can redistribute it and/or modify
  * This program is free software: you can redistribute it and/or modify
@@ -45,12 +45,14 @@ import (
 // Android Media Player (http://developer.android.com/reference/android/media/MediaPlayer.html).
 // Android Media Player (http://developer.android.com/reference/android/media/MediaPlayer.html).
 // To make the Media Player use the Psiphon tunnel, construct a URL such as:
 // To make the Media Player use the Psiphon tunnel, construct a URL such as:
 // "http://127.0.0.1:<proxy-port>/tunneled/<origin media URL>"; and pass this to the player.
 // "http://127.0.0.1:<proxy-port>/tunneled/<origin media URL>"; and pass this to the player.
+// The <origin media URL> must be escaped in such a way that it can be used inside a URL query.
 // TODO: add ICY protocol to support certain streaming media (e.g., https://gist.github.com/tulskiy/1008126)
 // TODO: add ICY protocol to support certain streaming media (e.g., https://gist.github.com/tulskiy/1008126)
 //
 //
 // An example use case for direct, untunneled, relaying is to make use of Go's TLS
 // An example use case for direct, untunneled, relaying is to make use of Go's TLS
 // stack for HTTPS requests in cases where the native TLS stack is lacking (e.g.,
 // stack for HTTPS requests in cases where the native TLS stack is lacking (e.g.,
 // WinHTTP on Windows XP). The URL for direct relaying is:
 // WinHTTP on Windows XP). The URL for direct relaying is:
 // "http://127.0.0.1:<proxy-port>/direct/<origin URL>".
 // "http://127.0.0.1:<proxy-port>/direct/<origin URL>".
+// Again, the <origin URL> must be escaped in such a way that it can be used inside a URL query.
 //
 //
 // Origin URLs must include the scheme prefix ("http://" or "https://") and must be
 // Origin URLs must include the scheme prefix ("http://" or "https://") and must be
 // URL encoded.
 // URL encoded.
@@ -255,11 +257,11 @@ func (proxy *HttpProxy) urlProxyHandler(responseWriter http.ResponseWriter, requ
 	// Request URL should be "/tunneled/<origin URL>" or  "/direct/<origin URL>" and the
 	// Request URL should be "/tunneled/<origin URL>" or  "/direct/<origin URL>" and the
 	// origin URL must be URL encoded.
 	// origin URL must be URL encoded.
 	switch {
 	switch {
-	case strings.HasPrefix(request.URL.Path, URL_PROXY_TUNNELED_REQUEST_PATH):
-		originUrl, err = url.QueryUnescape(request.URL.Path[len(URL_PROXY_TUNNELED_REQUEST_PATH):])
+	case strings.HasPrefix(request.URL.RawPath, URL_PROXY_TUNNELED_REQUEST_PATH):
+		originUrl, err = url.QueryUnescape(request.URL.RawPath[len(URL_PROXY_TUNNELED_REQUEST_PATH):])
 		client = proxy.urlProxyTunneledClient
 		client = proxy.urlProxyTunneledClient
-	case strings.HasPrefix(request.URL.Path, URL_PROXY_DIRECT_REQUEST_PATH):
-		originUrl, err = url.QueryUnescape(request.URL.Path[len(URL_PROXY_DIRECT_REQUEST_PATH):])
+	case strings.HasPrefix(request.URL.RawPath, URL_PROXY_DIRECT_REQUEST_PATH):
+		originUrl, err = url.QueryUnescape(request.URL.RawPath[len(URL_PROXY_DIRECT_REQUEST_PATH):])
 		client = proxy.urlProxyDirectClient
 		client = proxy.urlProxyDirectClient
 	default:
 	default:
 		err = errors.New("missing origin URL")
 		err = errors.New("missing origin URL")