Browse Source

Merge remote-tracking branch 'rod/master'

Adam Pritchard 11 years ago
parent
commit
ea4975608b

+ 38 - 2
AndroidApp/app/src/main/java/ca/psiphon/psibot/Client.java

@@ -22,8 +22,13 @@ package ca.psiphon.psibot;
 import android.content.Context;
 import android.os.Build;
 
+import org.json.JSONException;
+import org.json.JSONObject;
+
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -120,11 +125,16 @@ public class Client {
         if (0 != Build.CPU_ABI.compareTo("armeabi-v7a")) {
             throw new Utils.PsibotError("no client binary for this CPU");
         }
+        final String errorMessage = "failed to prepare client files";
         try {
             Utils.writeRawResourceFile(mContext, R.raw.psiphon_tunnel_core_arm, mExecutableFile, true);
-            Utils.writeRawResourceFile(mContext, R.raw.psiphon_config, mConfigFile, false);
+            writeConfigFile(mContext, mConfigFile);
         } catch (IOException e) {
-            throw new Utils.PsibotError("failed to prepare client files", e);
+            throw new Utils.PsibotError(errorMessage, e);
+        } catch (JSONException e) {
+            throw new Utils.PsibotError(errorMessage, e);
+        } catch (Utils.PsibotError e) {
+            throw new Utils.PsibotError(errorMessage, e);
         }
     }
 
@@ -163,4 +173,30 @@ public class Client {
             mTunnelStartedSignal.countDown();
         }
     }
+
+    private void writeConfigFile(Context context, File configFile)
+            throws IOException, JSONException, Utils.PsibotError {
+        // If we can obtain a DNS resolver for the active network,
+        // prefer that for DNS resolution in BindToDevice mode.
+        String dnsResolver = null;
+        try {
+            dnsResolver = Utils.getFirstActiveNetworkDnsResolver(context);
+        } catch (Utils.PsibotError e) {
+            Log.addEntry("failed to get active network DNS resolver: " + e.getMessage());
+            // Proceed with default value in config file
+        }
+
+        // Load settings from the raw resource JSON config file and
+        // update as necessary. Then write JSON to disk for the Go client.
+        String configFileContents = Utils.readInputStreamToString(
+                context.getResources().openRawResource(R.raw.psiphon_config));
+        JSONObject json = new JSONObject(configFileContents);
+        json.put("BindToDeviceServiceAddress", "@" + SocketProtector.SOCKET_PROTECTOR_ADDRESS);
+        if (dnsResolver != null) {
+            json.put("BindToDeviceDnsServer", dnsResolver);
+        }
+        Utils.copyStream(
+                new ByteArrayInputStream(json.toString().getBytes("UTF-8")),
+                new FileOutputStream(configFile));
+    }
 }

+ 0 - 1
AndroidApp/app/src/main/java/ca/psiphon/psibot/Log.java

@@ -59,7 +59,6 @@ public class Log {
         if (message == null) {
             message = "(null)";
         }
-
         final Entry entry = new Entry(message);
 
         // Update the in-memory entry list on the UI thread (also

+ 4 - 3
AndroidApp/app/src/main/java/ca/psiphon/psibot/Service.java

@@ -148,6 +148,7 @@ public class Service extends VpnService {
         Locale previousLocale = Locale.getDefault();
         ParcelFileDescriptor vpnInterfaceFileDescriptor = null;
 
+        final String errorMessage = "establishVpn failed";
         try {
             String subnet = Utils.getPrivateAddressSubnet(privateIpAddress);
             int prefixLength = Utils.getPrivateAddressPrefixLength(privateIpAddress);
@@ -174,11 +175,11 @@ public class Service extends VpnService {
                 throw new Utils.PsibotError("application is not prepared or is revoked");
             }
         } catch(IllegalArgumentException e) {
-            throw new Utils.PsibotError(e);
+            throw new Utils.PsibotError(errorMessage, e);
         } catch(IllegalStateException e) {
-            throw new Utils.PsibotError(e);
+            throw new Utils.PsibotError(errorMessage, e);
         } catch(SecurityException e) {
-            throw new Utils.PsibotError(e);
+            throw new Utils.PsibotError(errorMessage, e);
         } finally {
             // Restore the original locale.
             Locale.setDefault(previousLocale);

+ 136 - 15
AndroidApp/app/src/main/java/ca/psiphon/psibot/Utils.java

@@ -19,21 +19,28 @@
 
 package ca.psiphon.psibot;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.LinkProperties;
 import android.net.NetworkInfo;
+import android.os.Build;
 
 import org.apache.http.conn.util.InetAddressUtils;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
@@ -60,6 +67,17 @@ public class Utils {
         }
     }
 
+    public static void writeRawResourceFile(
+            Context context, int resId, File file, boolean setExecutable) throws IOException {
+        file.delete();
+        Utils.copyStream(
+                context.getResources().openRawResource(resId),
+                new FileOutputStream(file));
+        if (setExecutable && !file.setExecutable(true)) {
+            throw new IOException("failed to set file as executable");
+        }
+    }
+
     public static void copyStream(
             InputStream inputStream, OutputStream outputStream) throws IOException {
         try {
@@ -74,22 +92,19 @@ public class Utils {
         }
     }
 
-    public static void writeRawResourceFile(
-            Context context, int resId, File file, boolean setExecutable) throws IOException {
-        file.delete();
-        // TODO: is this compression redundant?
-        /*
-        InputStream zippedAsset = context.getResources().openRawResource(resId);
-        ZipInputStream zipStream = new ZipInputStream(zippedAsset);
-        zipStream.getNextEntry();
-        Utils.copyStream(zipStream, new FileOutputStream(file));
-        */
-        Utils.copyStream(
-                context.getResources().openRawResource(resId),
-                new FileOutputStream(file));
-        if (setExecutable && !file.setExecutable(true)) {
-            throw new IOException("failed to set file as executable");
+    public static String readInputStreamToString(InputStream inputStream) throws IOException {
+        return new String(readInputStreamToBytes(inputStream), "UTF-8");
+    }
+
+    public static byte[] readInputStreamToBytes(InputStream inputStream) throws IOException {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        int readCount;
+        byte[] buffer = new byte[16384];
+        while ((readCount = inputStream.read(buffer, 0, buffer.length)) != -1) {
+            outputStream.write(buffer, 0, readCount);
         }
+        outputStream.flush();
+        return outputStream.toByteArray();
     }
 
     public static String getNetworkTypeName(Context context) {
@@ -210,4 +225,110 @@ public class Utils {
         }
         return null;
     }
+
+    public static String getFirstActiveNetworkDnsResolver(Context context)
+            throws Utils.PsibotError {
+        Collection<InetAddress> dnsResolvers = Utils.getActiveNetworkDnsResolvers(context);
+        if (!dnsResolvers.isEmpty()) {
+            // strip the leading slash e.g., "/192.168.1.1"
+            String dnsResolver = dnsResolvers.iterator().next().toString();
+            if (dnsResolver.startsWith("/")) {
+                dnsResolver = dnsResolver.substring(1);
+            }
+            return dnsResolver;
+        }
+        throw new Utils.PsibotError("no active network DNS resolver");
+    }
+
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public static Collection<InetAddress> getActiveNetworkDnsResolvers(Context context)
+            throws Utils.PsibotError {
+        final String errorMessage = "getActiveNetworkDnsResolvers failed";
+        ArrayList<InetAddress> dnsAddresses = new ArrayList<InetAddress>();
+        try {
+            /*
+
+            Hidden API
+            - only available in Android 4.0+
+            - no guarantee will be available beyond 4.2, or on all vendor devices
+
+            core/java/android/net/ConnectivityManager.java:
+
+                /** {@hide} * /
+                public LinkProperties getActiveLinkProperties() {
+                    try {
+                        return mService.getActiveLinkProperties();
+                    } catch (RemoteException e) {
+                        return null;
+                    }
+                }
+
+            services/java/com/android/server/ConnectivityService.java:
+
+                /*
+                 * Return LinkProperties for the active (i.e., connected) default
+                 * network interface.  It is assumed that at most one default network
+                 * is active at a time. If more than one is active, it is indeterminate
+                 * which will be returned.
+                 * @return the ip properties for the active network, or {@code null} if
+                 * none is active
+                 * /
+                @Override
+                public LinkProperties getActiveLinkProperties() {
+                    return getLinkProperties(mActiveDefaultNetwork);
+                }
+
+                @Override
+                public LinkProperties getLinkProperties(int networkType) {
+                    enforceAccessPermission();
+                    if (isNetworkTypeValid(networkType)) {
+                        final NetworkStateTracker tracker = mNetTrackers[networkType];
+                        if (tracker != null) {
+                            return tracker.getLinkProperties();
+                        }
+                    }
+                    return null;
+                }
+
+            core/java/android/net/LinkProperties.java:
+
+                public Collection<InetAddress> getDnses() {
+                    return Collections.unmodifiableCollection(mDnses);
+                }
+
+            */
+
+            ConnectivityManager connectivityManager =
+                    (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
+            Class<?> LinkPropertiesClass = Class.forName("android.net.LinkProperties");
+            Method getActiveLinkPropertiesMethod = ConnectivityManager.class.getMethod("getActiveLinkProperties", new Class []{});
+            Object linkProperties = getActiveLinkPropertiesMethod.invoke(connectivityManager);
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+                Method getDnsesMethod = LinkPropertiesClass.getMethod("getDnses", new Class []{});
+                Collection<?> dnses = (Collection<?>)getDnsesMethod.invoke(linkProperties);
+                for (Object dns : dnses) {
+                    dnsAddresses.add((InetAddress)dns);
+                }
+            } else {
+                // LinkProperties is public in API 21 (and the DNS function signature has changed)
+                for (InetAddress dns : ((LinkProperties)linkProperties).getDnsServers()) {
+                    dnsAddresses.add(dns);
+                }
+            }
+        } catch (ClassNotFoundException e) {
+            throw new Utils.PsibotError(errorMessage, e);
+        } catch (NoSuchMethodException e) {
+            throw new Utils.PsibotError(errorMessage, e);
+        } catch (IllegalArgumentException e) {
+            throw new Utils.PsibotError(errorMessage, e);
+        } catch (IllegalAccessException e) {
+            throw new Utils.PsibotError(errorMessage, e);
+        } catch (InvocationTargetException e) {
+            throw new Utils.PsibotError(errorMessage, e);
+        } catch (NullPointerException e) {
+            throw new Utils.PsibotError(errorMessage, e);
+        }
+
+        return dnsAddresses;
+    }
 }

+ 1 - 1
AndroidApp/app/src/main/res/raw/psiphon_config

@@ -9,6 +9,6 @@
     "EgressRegion" : "",
     "TunnelProtocol" : "",
     "ConnectionWorkerPoolSize" : 10,
-    "BindToDeviceServiceAddress" : "@/psibot/socketProtector",
+    "BindToDeviceServiceAddress" : "",
     "BindToDeviceDnsServer" : "8.8.4.4"
 }

BIN
AndroidApp/app/src/main/res/raw/psiphon_tunnel_core_arm