Bläddra i källkod

Add wait-for-network-connectivity functionality; misc. fix-ups

* core may be configured with a callback that the core uses to
check for network connectivity before attempting to establish
network connections. The purpose is to save CPU/battery on
mobile devices, especially.

* AndroidLibrary/AndroidApp support for network connectivity

* Add and check 'error' return value for BindToDevice

* Minor clean up to AndroidApp notice handling
Rod Hynes 11 år sedan
förälder
incheckning
0c13c194d1

+ 16 - 10
AndroidApp/app/src/main/java/ca/psiphon/psibot/Psiphon.java

@@ -50,19 +50,22 @@ public class Psiphon extends Psi.PsiphonProvider.Stub {
     // PsiphonProvider.Notice
     @Override
     public void Notice(String noticeJSON) {
-        noticeJSON = noticeJSON.trim();
-
-        android.util.Log.d("PSIPHON", noticeJSON);
-        parseMessage(noticeJSON);
-        Log.addEntry(noticeJSON);
+        handleNotice(noticeJSON);
     }
 
     // PsiphonProvider.BindToDevice
     @Override
-    public void BindToDevice(long fileDescriptor) {
-        // TODO: return result; currently no return value due to
-        // Android Library limitation.
-        mVpnService.protect((int)fileDescriptor);
+    public void BindToDevice(long fileDescriptor) throws Exception {
+        if (!mVpnService.protect((int)fileDescriptor)) {
+            throw new Exception("protect socket failed");
+        }
+    }
+
+    // PsiphonProvider.HasNetworkConnectivity
+    @Override
+    public long HasNetworkConnectivity() {
+        // TODO: change to bool return value once gobind supports that type
+        return Utils.hasNetworkConnectivity(mVpnService) ? 1 : 0;
     }
 
     public void start() throws Utils.PsibotError {
@@ -175,7 +178,7 @@ public class Psiphon extends Psi.PsiphonProvider.Stub {
         return json.toString();
     }
 
-    private synchronized void parseMessage(String noticeJSON) {
+    private synchronized void handleNotice(String noticeJSON) {
         try {
             JSONObject notice = new JSONObject(noticeJSON);
             String noticeType = notice.getString("noticeType");
@@ -191,6 +194,9 @@ public class Psiphon extends Psi.PsiphonProvider.Stub {
             } else if (noticeType.equals("Homepage")) {
                 mHomePages.add(notice.getJSONObject("data").getString("url"));
             }
+            String displayNotice = noticeType + " " + notice.getJSONObject("data").toString();
+            android.util.Log.d("PSIPHON", displayNotice);
+            Log.addEntry(displayNotice);
         } catch (JSONException e) {
             // Ignore notice
         }

+ 7 - 0
AndroidApp/app/src/main/java/ca/psiphon/psibot/Utils.java

@@ -100,6 +100,13 @@ public class Utils {
         return outputStream.toByteArray();
     }
 
+    public static boolean hasNetworkConnectivity(Context context) {
+        ConnectivityManager connectivityManager =
+                (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
+        return networkInfo != null && networkInfo.isConnected();
+    }
+
     public static String getNetworkTypeName(Context context) {
         ConnectivityManager connectivityManager =
                 (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);

+ 31 - 4
AndroidApp/app/src/main/java/go/psi/Psi.java

@@ -10,7 +10,9 @@ public abstract class Psi {
     private Psi() {} // uninstantiable
     
     public interface PsiphonProvider extends go.Seq.Object {
-        public void BindToDevice(long fileDescriptor);
+        public void BindToDevice(long fileDescriptor) throws Exception;
+        
+        public long HasNetworkConnectivity();
         
         public void Notice(String noticeJSON);
         
@@ -28,7 +30,17 @@ public abstract class Psi {
                 switch (code) {
                 case Proxy.CALL_BindToDevice: {
                     long param_fileDescriptor = in.readInt();
-                    this.BindToDevice(param_fileDescriptor);
+                    try {
+                        this.BindToDevice(param_fileDescriptor);
+                        out.writeUTF16(null);
+                    } catch (Exception e) {
+                        out.writeUTF16(e.getMessage());
+                    }
+                    return;
+                }
+                case Proxy.CALL_HasNetworkConnectivity: {
+                    long result = this.HasNetworkConnectivity();
+                    out.writeInt(result);
                     return;
                 }
                 case Proxy.CALL_Notice: {
@@ -55,12 +67,26 @@ public abstract class Psi {
                 throw new RuntimeException("cycle: cannot call proxy");
             }
         
-            public void BindToDevice(long fileDescriptor) {
+            public void BindToDevice(long fileDescriptor) throws Exception {
                 go.Seq _in = new go.Seq();
                 go.Seq _out = new go.Seq();
                 _in.writeRef(ref);
                 _in.writeInt(fileDescriptor);
                 Seq.send(DESCRIPTOR, CALL_BindToDevice, _in, _out);
+                String _err = _out.readUTF16();
+                if (_err != null) {
+                    throw new Exception(_err);
+                }
+            }
+            
+            public long HasNetworkConnectivity() {
+                go.Seq _in = new go.Seq();
+                go.Seq _out = new go.Seq();
+                long _result;
+                _in.writeRef(ref);
+                Seq.send(DESCRIPTOR, CALL_HasNetworkConnectivity, _in, _out);
+                _result = _out.readInt();
+                return _result;
             }
             
             public void Notice(String noticeJSON) {
@@ -72,7 +98,8 @@ public abstract class Psi {
             }
             
             static final int CALL_BindToDevice = 0x10a;
-            static final int CALL_Notice = 0x20a;
+            static final int CALL_HasNetworkConnectivity = 0x20a;
+            static final int CALL_Notice = 0x30a;
         }
     }
     

+ 54 - 12
AndroidLibrary/go_psi/go_psi.go

@@ -10,23 +10,65 @@ import (
 )
 
 const (
-	proxyPsiphonProviderDescriptor       = "go.psi.PsiphonProvider"
-	proxyPsiphonProviderBindToDeviceCode = 0x10a
-	proxyPsiphonProviderNoticeCode       = 0x20a
+	proxyPsiphonProviderDescriptor                 = "go.psi.PsiphonProvider"
+	proxyPsiphonProviderBindToDeviceCode           = 0x10a
+	proxyPsiphonProviderHasNetworkConnectivityCode = 0x20a
+	proxyPsiphonProviderNoticeCode                 = 0x30a
 )
 
+func proxyPsiphonProviderBindToDevice(out, in *seq.Buffer) {
+	ref := in.ReadRef()
+	v := ref.Get().(psi.PsiphonProvider)
+	param_fileDescriptor := in.ReadInt()
+	err := v.BindToDevice(param_fileDescriptor)
+	if err == nil {
+		out.WriteUTF16("")
+	} else {
+		out.WriteUTF16(err.Error())
+	}
+}
+
+func proxyPsiphonProviderHasNetworkConnectivity(out, in *seq.Buffer) {
+	ref := in.ReadRef()
+	v := ref.Get().(psi.PsiphonProvider)
+	res := v.HasNetworkConnectivity()
+	out.WriteInt(res)
+}
+
+func proxyPsiphonProviderNotice(out, in *seq.Buffer) {
+	ref := in.ReadRef()
+	v := ref.Get().(psi.PsiphonProvider)
+	param_noticeJSON := in.ReadUTF16()
+	v.Notice(param_noticeJSON)
+}
+
+func init() {
+	seq.Register(proxyPsiphonProviderDescriptor, proxyPsiphonProviderBindToDeviceCode, proxyPsiphonProviderBindToDevice)
+	seq.Register(proxyPsiphonProviderDescriptor, proxyPsiphonProviderHasNetworkConnectivityCode, proxyPsiphonProviderHasNetworkConnectivity)
+	seq.Register(proxyPsiphonProviderDescriptor, proxyPsiphonProviderNoticeCode, proxyPsiphonProviderNotice)
+}
+
 type proxyPsiphonProvider seq.Ref
 
-func (p *proxyPsiphonProvider) BindToDevice(fileDescriptor int) {
-	out := new(seq.Buffer)
-	out.WriteInt(fileDescriptor)
-	seq.Transact((*seq.Ref)(p), proxyPsiphonProviderBindToDeviceCode, out)
+func (p *proxyPsiphonProvider) BindToDevice(fileDescriptor int) error {
+	in := new(seq.Buffer)
+	in.WriteInt(fileDescriptor)
+	out := seq.Transact((*seq.Ref)(p), proxyPsiphonProviderBindToDeviceCode, in)
+	res_0 := out.ReadError()
+	return res_0
+}
+
+func (p *proxyPsiphonProvider) HasNetworkConnectivity() int {
+	in := new(seq.Buffer)
+	out := seq.Transact((*seq.Ref)(p), proxyPsiphonProviderHasNetworkConnectivityCode, in)
+	res_0 := out.ReadInt()
+	return res_0
 }
 
 func (p *proxyPsiphonProvider) Notice(noticeJSON string) {
-	out := new(seq.Buffer)
-	out.WriteUTF16(noticeJSON)
-	seq.Transact((*seq.Ref)(p), proxyPsiphonProviderNoticeCode, out)
+	in := new(seq.Buffer)
+	in.WriteUTF16(noticeJSON)
+	seq.Transact((*seq.Ref)(p), proxyPsiphonProviderNoticeCode, in)
 }
 
 func proxy_Start(out, in *seq.Buffer) {
@@ -34,9 +76,9 @@ func proxy_Start(out, in *seq.Buffer) {
 	param_embeddedServerEntryList := in.ReadUTF16()
 	var param_provider psi.PsiphonProvider
 	param_provider_ref := in.ReadRef()
-	if param_provider_ref.Num < 0 {
+	if param_provider_ref.Num < 0 { // go object
 		param_provider = param_provider_ref.Get().(psi.PsiphonProvider)
-	} else {
+	} else { // foreign object
 		param_provider = (*proxyPsiphonProvider)(param_provider_ref)
 	}
 	err := psi.Start(param_configJson, param_embeddedServerEntryList, param_provider)

+ 31 - 4
AndroidLibrary/java_psi/go/psi/Psi.java

@@ -10,7 +10,9 @@ public abstract class Psi {
     private Psi() {} // uninstantiable
     
     public interface PsiphonProvider extends go.Seq.Object {
-        public void BindToDevice(long fileDescriptor);
+        public void BindToDevice(long fileDescriptor) throws Exception;
+        
+        public long HasNetworkConnectivity();
         
         public void Notice(String noticeJSON);
         
@@ -28,7 +30,17 @@ public abstract class Psi {
                 switch (code) {
                 case Proxy.CALL_BindToDevice: {
                     long param_fileDescriptor = in.readInt();
-                    this.BindToDevice(param_fileDescriptor);
+                    try {
+                        this.BindToDevice(param_fileDescriptor);
+                        out.writeUTF16(null);
+                    } catch (Exception e) {
+                        out.writeUTF16(e.getMessage());
+                    }
+                    return;
+                }
+                case Proxy.CALL_HasNetworkConnectivity: {
+                    long result = this.HasNetworkConnectivity();
+                    out.writeInt(result);
                     return;
                 }
                 case Proxy.CALL_Notice: {
@@ -55,12 +67,26 @@ public abstract class Psi {
                 throw new RuntimeException("cycle: cannot call proxy");
             }
         
-            public void BindToDevice(long fileDescriptor) {
+            public void BindToDevice(long fileDescriptor) throws Exception {
                 go.Seq _in = new go.Seq();
                 go.Seq _out = new go.Seq();
                 _in.writeRef(ref);
                 _in.writeInt(fileDescriptor);
                 Seq.send(DESCRIPTOR, CALL_BindToDevice, _in, _out);
+                String _err = _out.readUTF16();
+                if (_err != null) {
+                    throw new Exception(_err);
+                }
+            }
+            
+            public long HasNetworkConnectivity() {
+                go.Seq _in = new go.Seq();
+                go.Seq _out = new go.Seq();
+                long _result;
+                _in.writeRef(ref);
+                Seq.send(DESCRIPTOR, CALL_HasNetworkConnectivity, _in, _out);
+                _result = _out.readInt();
+                return _result;
             }
             
             public void Notice(String noticeJSON) {
@@ -72,7 +98,8 @@ public abstract class Psi {
             }
             
             static final int CALL_BindToDevice = 0x10a;
-            static final int CALL_Notice = 0x20a;
+            static final int CALL_HasNetworkConnectivity = 0x20a;
+            static final int CALL_Notice = 0x30a;
         }
     }
     

+ 3 - 4
AndroidLibrary/psi/psi.go

@@ -34,10 +34,8 @@ import (
 
 type PsiphonProvider interface {
 	Notice(noticeJSON string)
-
-	// TODO: return 'error'; at the moment gobind doesn't
-	// work with interface function return values.
-	BindToDevice(fileDescriptor int)
+	HasNetworkConnectivity() int
+	BindToDevice(fileDescriptor int) error
 }
 
 var controller *psiphon.Controller
@@ -54,6 +52,7 @@ func Start(configJson, embeddedServerEntryList string, provider PsiphonProvider)
 	if err != nil {
 		return fmt.Errorf("error loading configuration file: %s", err)
 	}
+	config.CheckNetworkConnectivityProvider = provider
 	config.BindToDeviceProvider = provider
 
 	err = psiphon.InitDataStore(config)

+ 5 - 2
psiphon/LookupIP.go

@@ -23,6 +23,7 @@ package psiphon
 
 import (
 	"errors"
+	"fmt"
 	dns "github.com/Psiphon-Inc/dns"
 	"net"
 	"os"
@@ -62,8 +63,10 @@ func bindLookupIP(host string, config *DialConfig) (addrs []net.IP, err error) {
 	}
 	defer syscall.Close(socketFd)
 
-	// TODO: check BindToDevice result
-	config.BindToDeviceProvider.BindToDevice(socketFd)
+	err = config.BindToDeviceProvider.BindToDevice(socketFd)
+	if err != nil {
+		return nil, ContextError(fmt.Errorf("BindToDevice failed: %s", err))
+	}
 
 	// config.BindToDeviceDnsServer must be an IP address
 	ipAddr = net.ParseIP(config.BindToDeviceDnsServer)

+ 5 - 2
psiphon/TCPConn_unix.go

@@ -23,6 +23,7 @@ package psiphon
 
 import (
 	"errors"
+	"fmt"
 	"net"
 	"os"
 	"strconv"
@@ -61,8 +62,10 @@ func interruptibleTCPDial(addr string, config *DialConfig) (conn *TCPConn, err e
 	}()
 
 	if config.BindToDeviceProvider != nil {
-		// TODO: check BindToDevice result
-		config.BindToDeviceProvider.BindToDevice(socketFd)
+		err = config.BindToDeviceProvider.BindToDevice(socketFd)
+		if err != nil {
+			return nil, ContextError(fmt.Errorf("BindToDevice failed: %s", err))
+		}
 	}
 
 	// When using an upstream HTTP proxy, first connect to the proxy,

+ 7 - 2
psiphon/config.go

@@ -29,7 +29,7 @@ import (
 // TODO: allow all params to be configured
 
 const (
-	VERSION                                      = "0.0.6"
+	VERSION                                      = "0.0.7"
 	DATA_STORE_FILENAME                          = "psiphon.db"
 	CONNECTION_WORKER_POOL_SIZE                  = 10
 	TUNNEL_POOL_SIZE                             = 1
@@ -80,6 +80,7 @@ type Config struct {
 	TunnelPoolSize                     int
 	PortForwardFailureThreshold        int
 	UpstreamHttpProxyAddress           string
+	CheckNetworkConnectivityProvider   NetworkConnectivityChecker
 	BindToDeviceProvider               DeviceBinder
 	BindToDeviceDnsServer              string
 	TargetServerEntry                  string
@@ -149,8 +150,12 @@ func LoadConfig(configJson []byte) (*Config, error) {
 		config.PortForwardFailureThreshold = PORT_FORWARD_FAILURE_THRESHOLD
 	}
 
+	if config.CheckNetworkConnectivityProvider != nil {
+		return nil, ContextError(errors.New("CheckNetworkConnectivityProvider interface must be set at runtime"))
+	}
+
 	if config.BindToDeviceProvider != nil {
-		return nil, ContextError(errors.New("interface must be set at runtime"))
+		return nil, ContextError(errors.New("BindToDeviceProvider interface must be set at runtime"))
 	}
 
 	return &config, nil

+ 34 - 3
psiphon/conn.go

@@ -64,9 +64,40 @@ type DialConfig struct {
 
 // DeviceBinder defines the interface to the external BindToDevice provider
 type DeviceBinder interface {
-	// TODO: return 'error'; currently no return value due to
-	// Android Library limitation.
-	BindToDevice(fileDescriptor int)
+	BindToDevice(fileDescriptor int) error
+}
+
+// NetworkConnectivityChecker defines the interface to the external
+// HasNetworkConnectivity provider
+type NetworkConnectivityChecker interface {
+	// TODO: change to bool return value once gobind supports that type
+	HasNetworkConnectivity() int
+}
+
+// WaitForNetworkConnectivity uses a NetworkConnectivityChecker to
+// periodically check for network connectivity. It returns true if
+// no NetworkConnectivityChecker is provided (waiting is disabled)
+// or if NetworkConnectivityChecker.HasNetworkConnectivity() indicates
+// connectivity. It polls the checker once a second. If a stop is
+// broadcast, false is returned.
+func WaitForNetworkConnectivity(
+	connectivityChecker NetworkConnectivityChecker, stopBroadcast <-chan struct{}) bool {
+	if connectivityChecker == nil || 1 == connectivityChecker.HasNetworkConnectivity() {
+		return true
+	}
+	NoticeInfo("waiting for network connectivity")
+	ticker := time.NewTicker(1 * time.Second)
+	for {
+		if 1 == connectivityChecker.HasNetworkConnectivity() {
+			return true
+		}
+		select {
+		case <-ticker.C:
+			// Check again
+		case <-stopBroadcast:
+			return false
+		}
+	}
 }
 
 // Dialer is a custom dialer compatible with http.Transport.Dial.

+ 12 - 0
psiphon/controller.go

@@ -165,6 +165,12 @@ func (controller *Controller) remoteServerListFetcher() {
 
 loop:
 	for {
+		if !WaitForNetworkConnectivity(
+			controller.config.CheckNetworkConnectivityProvider,
+			controller.shutdownBroadcast) {
+			break
+		}
+
 		err := FetchRemoteServerList(
 			controller.config, controller.fetchRemotePendingConns)
 
@@ -598,6 +604,12 @@ loop:
 			continue
 		}
 
+		if !WaitForNetworkConnectivity(
+			controller.config.CheckNetworkConnectivityProvider,
+			controller.stopEstablishingBroadcast) {
+			break loop
+		}
+
 		tunnel, err := EstablishTunnel(
 			controller.config,
 			controller.sessionId,