Commits

rhynes committed b6eba8e

Integrated tun2socks and Android VpnService implementation.

Comments (0)

Files changed (11)

Android/PsiphonAndroid/src/com/psiphon3/StatusActivity.java

 /*
- * Copyright (c) 2012, Psiphon Inc.
+ * Copyright (c) 2013, Psiphon Inc.
  * All rights reserved.
  *
  * This program is free software: you can redistribute it and/or modify

Android/PsiphonAndroidLibrary/res/values/strings.xml

     <string name="preferred_servers">Preferred servers: %d</string>
     <string name="selecting_server">Selecting server...</string>
     <string name="fetch_remote_server_list">Fetching remote server list...</string>
-    <string name="vpn_service_failed">VPN setup failed</string>
+    <string name="vpn_service_failed">VPN service failed</string>
+    <string name="vpn_service_running">VPN service running</string>
+    <string name="vpn_service_stopped">VPN service stopped</string>
+    <string name="vpn_service_revoked">VPN service revoked</string>
+    <string name="tun2socks_error">tun2socks: %s</string>
+    <string name="tun2socks_failed">VPN to SOCKS failed</string>
+    <string name="tun2socks_running">VPN to SOCKS running</string>
+    <string name="tun2socks_stopped">VPN to SOCKS stopped</string>
 </resources>

Android/PsiphonAndroidLibrary/src/com/psiphon3/psiphonlibrary/PsiphonConstants.java

 /*
- * Copyright (c) 2012, Psiphon Inc.
+ * Copyright (c) 2013, Psiphon Inc.
  * All rights reserved.
  *
  * This program is free software: you can redistribute it and/or modify
     public final static ArrayList<String> REQUIRED_CAPABILITIES_FOR_TUNNEL = new ArrayList<String>(){{ add(PsiphonConstants.RELAY_PROTOCOL); }};
     
     public final static String FEEDBACK_ATTACHMENT_FILENAME = "psiphon-android-feedback.txt";
+
+    public final static String VPN_INTERFACE_NETWORK_ADDRESS = "10.0.0.1";
+    
+    public final static String VPN_INTERFACE_NETMASK = "255.255.255.0";
+    
+    public final static int VPN_INTERFACE_MTU = 1500;
+
+    public final static int UDPGW_SERVER_PORT = 7300;
 }

Android/PsiphonAndroidLibrary/src/com/psiphon3/psiphonlibrary/Tun2Socks.java

+/*
+ * Copyright (c) 2013, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.psiphon3.psiphonlibrary;
+
+import com.psiphon3.psiphonlibrary.Utils.MyLog;
+
+import android.os.ParcelFileDescriptor;
+
+public class Tun2Socks implements Runnable
+{
+    private Thread mThread;
+    private ParcelFileDescriptor mVpnInterfaceFileDescriptor;
+    private int mVpnInterfaceMTU;
+    private String mVpnIpAddress;
+    private String mVpnNetMask;
+    private String mSocksServerAddress;
+    private String mUdpgwServerAddress;
+    
+    // Note: this class isn't a singleton, but you can't run more
+    // than one instance due to the use of global state (the lwip
+    // module, etc.) in the native code.
+    
+    public void Start(
+            ParcelFileDescriptor vpnInterfaceFileDescriptor,
+            int vpnInterfaceMTU,
+            String vpnIpAddress,
+            String vpnNetMask,
+            String socksServerAddress,
+            String udpgwServerAddress)
+    {
+        Stop();
+
+        mVpnInterfaceFileDescriptor = vpnInterfaceFileDescriptor;
+        mVpnInterfaceMTU = vpnInterfaceMTU;
+        mVpnIpAddress = vpnIpAddress;
+        mVpnNetMask = vpnNetMask;
+        mSocksServerAddress = socksServerAddress;
+        mUdpgwServerAddress = udpgwServerAddress;
+
+        mThread = new Thread(this);
+        mThread.start();
+    }
+    
+    public void Stop()
+    {
+        if (mThread != null)
+        {
+            terminateTun2Socks();
+            try
+            {
+                mThread.join();
+            }
+            catch (InterruptedException e)
+            {
+                Thread.currentThread().interrupt();
+            }
+            mThread = null;
+        }
+    }
+    
+    @Override
+    public void run()
+    {
+        runTun2Socks(
+                mVpnInterfaceFileDescriptor.detachFd(),
+                mVpnInterfaceMTU,
+                mVpnIpAddress,
+                mVpnNetMask,
+                mSocksServerAddress,
+                mUdpgwServerAddress);
+    }
+    
+    static void logTun2Socks(
+            String level,
+            String channel,
+            String msg)
+    {
+        String logMsg = level + "(" + channel + "): " + msg;
+        MyLog.e(R.string.tun2socks_error, MyLog.Sensitivity.NOT_SENSITIVE, logMsg);
+    }
+
+    private native int runTun2Socks(
+            int vpnInterfaceFileDescriptor,
+            int vpnInterfaceMTU,
+            String vpnIpAddress,
+            String vpnNetMask,
+            String socksServerAddress,
+            String udpgwServerAddress);
+
+    private native void terminateTun2Socks();
+    
+    static
+    {
+        System.loadLibrary("tun2socks");
+    }
+}

Android/PsiphonAndroidLibrary/src/com/psiphon3/psiphonlibrary/TunnelService.java

 /*
- * Copyright (c) 2012, Psiphon Inc.
+ * Copyright (c) 2013, Psiphon Inc.
  * All rights reserved.
  *
  * This program is free software: you can redistribute it and/or modify
         DnsProxy dnsProxy = null;
         boolean cleanupTransparentProxyRouting = false;
         Socket socket = null;
-        ParcelFileDescriptor vpnFileDescriptor = null;
+        Tun2Socks tun2Socks = null;
         
         try
         {
             
             // Run as Android OS VPN
             
-            // In-progress TODOs
-            // =================
-            //
-            // - VpnService.prepare() isn't in the library
-            // - VpnService subclassing
-            // - dynamic private address assignment (?)
-            // - 
-            
             if (tunnelWholeDevice && runVpnService)
             {
                 protect(socket);
                 
-                // Private IP address
-                String privateAddress = "10.0.0.1";
-                
                 String builderErrorMessage = null;
-                vpnFileDescriptor = null;
+                ParcelFileDescriptor vpnInterfaceFileDescriptor = null;
                 try
                 {
                     VpnService.Builder builder = new VpnService.Builder();
-                    vpnFileDescriptor = builder
+                    vpnInterfaceFileDescriptor = builder
                             .setSession(getString(R.string.app_name))
-                            .addAddress(privateAddress, 32) 
+                            .setMtu(PsiphonConstants.VPN_INTERFACE_MTU)
+                            .addAddress(PsiphonConstants.VPN_INTERFACE_NETWORK_ADDRESS, 32)                            
                             .addDnsServer(tunnelWholeDeviceDNSServer)
                             .establish();
-                    if (vpnFileDescriptor == null)
+                    if (vpnInterfaceFileDescriptor == null)
                     {
                         // as per http://developer.android.com/reference/android/net/VpnService.Builder.html#establish%28%29
                         builderErrorMessage = "application is not prepared or revoked";
                 {
                     builderErrorMessage = e.getMessage();                    
                 }
-                if (vpnFileDescriptor == null)
+                if (vpnInterfaceFileDescriptor == null)
                 {
                     // If we can't configure the Android OS VPN, abort
                     MyLog.e(R.string.vpn_service_failed, MyLog.Sensitivity.NOT_SENSITIVE, builderErrorMessage);
                     return runAgain;
                 }
                 
-                // TODO:
-                // vpnFileDescriptor.detachFD();
-                // Fd2Socks.run(fd)
-                // etc.
+                MyLog.e(R.string.vpn_service_running, MyLog.Sensitivity.NOT_SENSITIVE);
+
+                String socksServerAddress = "127.0.0.1:" + Integer.toString(PsiphonData.getPsiphonData().getSocksPort());
+                String udpgwServerAddress = entry.ipAddress + ":" + Integer.toString(PsiphonConstants.UDPGW_SERVER_PORT);
+                
+                tun2Socks = new Tun2Socks();
+                tun2Socks.Start(
+                        vpnInterfaceFileDescriptor,
+                        PsiphonConstants.VPN_INTERFACE_MTU,
+                        PsiphonConstants.VPN_INTERFACE_NETWORK_ADDRESS,
+                        PsiphonConstants.VPN_INTERFACE_NETMASK,
+                        socksServerAddress,
+                        udpgwServerAddress);
+                
+                // TODO: detect and report: tun2Socks.Start failed; tun2socks run() unexpected exit
+
+                MyLog.e(R.string.tun2socks_running, MyLog.Sensitivity.NOT_SENSITIVE);
             }
             
             // Don't signal unexpected disconnect until we've started
                 MyLog.v(R.string.transparent_proxy_stopped, MyLog.Sensitivity.NOT_SENSITIVE);                
             }
             
+            if (tun2Socks != null)
+            {
+                tun2Socks.Stop();
+                MyLog.v(R.string.tun2socks_stopped, MyLog.Sensitivity.NOT_SENSITIVE);                
+            }
+            
             if (socks != null)
             {
                 try
         m_tunnelThread = null;
     }
     
+    public void onRevoke()
+    {
+        MyLog.v(R.string.vpn_service_revoked, MyLog.Sensitivity.NOT_SENSITIVE);
+
+        stopTunnel();
+    }
+    
     public void setEventsInterface(Events eventsInterface)
     {
         m_eventsInterface = eventsInterface;

Android/badvpn/Android.mk

 LOCAL_CFLAGS += -DBADVPN_THREADWORK_USE_PTHREAD -DBADVPN_LINUX -DBADVPN_BREACTOR_BADVPN -D_GNU_SOURCE
 LOCAL_CFLAGS += -DBADVPN_USE_SELFPIPE -DBADVPN_USE_EPOLL
 LOCAL_CFLAGS += -DBADVPN_LITTLE_ENDIAN
+LOCAL_CFLAGS += -DPSIPHON
 
 LOCAL_C_INCLUDES:= \
         $(LOCAL_PATH)/lwip/src/include/ipv4 \
         tun2socks/SocksUdpGwClient.c \
         udpgw_client/UdpGwClient.c
 
-include $(BUILD_EXECUTABLE)
+include $(BUILD_SHARED_LIBRARY)
 

Android/badvpn/base/BLog.c

 {
     BLog_Init(stderr_log, stdout_stderr_free);
 }
+
+// ==== PSIPHON ====
+
+void PsiphonLog(const char *level, const char *channel, const char *msg);
+
+static void psiphon_log (int channel, int level, const char *msg)
+{
+    PsiphonLog(level_names[level], blog_global.channels[channel].name, msg);
+}
+
+static void psiphon_free (void)
+{
+}
+
+void BLog_InitPsiphon (void)
+{
+    BLog_Init(psiphon_log, psiphon_free);
+}
+
+// ==== PSIPHON ====

Android/badvpn/base/BLog.h

 void BLog_InitStdout (void);
 void BLog_InitStderr (void);
 
+// PSIPHON
+void BLog_InitPsiphon (void);
+
 int BLogGlobal_GetChannelByName (const char *channel_name)
 {
     int i;

Android/badvpn/tun2socks/tun2socks.c

 #include <stddef.h>
 #include <string.h>
 
+// PSIPHON
+#include "jni.h"
+
 #include <misc/version.h>
 #include <misc/loggers_string.h>
 #include <misc/loglevel.h>
     char *udpgw_remote_server_addr;
     int udpgw_max_connections;
     int udpgw_connection_buffer_size;
+
+    // ==== PSIPHON ====
+    int tun_fd;
+    int tun_mtu;
+    int set_signal;
+    // ==== PSIPHON ====
 } options;
 
 // TCP client
 // number of clients
 int num_clients;
 
+// ==== PSIPHON ====
+static void run (void);
+static void init_arguments (const char* program_name);
+// ==== PSIPHON ====
+
 static void terminate (void);
 static void print_help (const char *name);
 static void print_version (void);
 static err_t client_sent_func (void *arg, struct tcp_pcb *tpcb, u16_t len);
 static void udpgw_client_handler_received (void *unused, BAddr local_addr, BAddr remote_addr, const uint8_t *data, int data_len);
 
+
+//==== PSIPHON ====
+
+#ifdef PSIPHON
+
+JNIEnv* g_env = 0;
+
+void PsiphonLog(const char *levelStr, const char *channelStr, const char *msgStr)
+{
+    if (!g_env)
+    {
+        return;
+    }
+    // Note: we could cache the class and method references if log is called frequently
+
+    jstring level = (*g_env)->NewStringUTF(g_env, levelStr);
+    jstring channel = (*g_env)->NewStringUTF(g_env, channelStr);
+    jstring msg = (*g_env)->NewStringUTF(g_env, msgStr);
+
+    jclass cls = (*g_env)->FindClass(g_env, "com/psiphon3/psiphonlibrary/Tun2Socks");
+    jmethodID logMethod = (*g_env)->GetMethodID(g_env, cls, "logTun2Socks", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+    (*g_env)->CallStaticVoidMethod(g_env, cls, logMethod, level, channel, msg);
+
+    (*g_env)->DeleteLocalRef(g_env, level);
+    (*g_env)->DeleteLocalRef(g_env, channel);
+    (*g_env)->DeleteLocalRef(g_env, msg);
+}
+
+JNIEXPORT jint JNICALL Java_com_psiphon3_psiphonlibrary_Tun2Socks_runTun2Socks(
+    JNIEnv* env,
+    jint vpnInterfaceFileDescriptor,
+    jint vpnInterfaceMTU,
+    jstring vpnIpAddress,
+    jstring vpnNetMask,
+    jstring socksServerAddress,
+    jstring udpgwServerAddress)
+{
+    const char* vpnIpAddressStr = (*env)->GetStringUTFChars(env, vpnIpAddress, 0);
+    const char* vpnNetMaskStr = (*env)->GetStringUTFChars(env, vpnNetMask, 0);
+    const char* socksServerAddressStr = (*env)->GetStringUTFChars(env, socksServerAddress, 0);
+    const char* udpgwServerAddressStr = (*env)->GetStringUTFChars(env, udpgwServerAddress, 0);
+
+    g_env = env;
+
+    init_arguments("Psiphon tun2socks");
+
+    options.netif_ipaddr = (char*)vpnIpAddressStr;
+    options.netif_netmask = (char*)vpnNetMaskStr;
+    options.socks_server_addr = (char*)socksServerAddressStr;
+    options.udpgw_remote_server_addr = (char*)udpgwServerAddressStr;
+    options.tun_fd = vpnInterfaceFileDescriptor;
+    options.tun_mtu = vpnInterfaceMTU;
+    options.set_signal = 0;
+
+    BLog_InitPsiphon();
+
+    run();
+
+    g_env = 0;
+
+    (*env)->ReleaseStringUTFChars(env, vpnIpAddress, vpnIpAddressStr);
+    (*env)->ReleaseStringUTFChars(env, vpnNetMask, vpnNetMaskStr);
+    (*env)->ReleaseStringUTFChars(env, socksServerAddress, socksServerAddressStr);
+    (*env)->ReleaseStringUTFChars(env, udpgwServerAddress, udpgwServerAddressStr);
+
+    // TODO: return success/error
+
+    return 1;
+}
+
+JNIEXPORT jint JNICALL Java_com_psiphon3_psiphonlibrary_Tun2Socks_terminateTun2Socks(
+    JNIEnv* env)
+{
+    terminate();
+    return 0;
+}
+
+//==== PSIPHON ====
+
+#else
+
 int main (int argc, char **argv)
 {
     if (argc <= 0) {
     if (!parse_arguments(argc, argv)) {
         fprintf(stderr, "Failed to parse arguments\n");
         print_help(argv[0]);
-        goto fail0;
+        return 0;
     }
-    
+
     // handle --help and --version
     if (options.help) {
         print_version();
         print_version();
         return 0;
     }
-    
+
     // initialize logger
     switch (options.logger) {
         case LOGGER_STDOUT:
         case LOGGER_SYSLOG:
             if (!BLog_InitSyslog(options.logger_syslog_ident, options.logger_syslog_facility)) {
                 fprintf(stderr, "Failed to initialize syslog logger\n");
-                goto fail0;
+                return 0;
             }
             break;
         #endif
             ASSERT(0);
     }
     
+    run();
+
+    return 1;
+}
+
+#endif
+
+void run()
+{
     // configure logger channels
     for (int i = 0; i < BLOG_NUM_CHANNELS; i++) {
         if (options.loglevels[i] >= 0) {
     // set not quitting
     quitting = 0;
     
-    // setup signal handler
-    if (!BSignal_Init(&ss, signal_handler, NULL)) {
-        BLog(BLOG_ERROR, "BSignal_Init failed");
-        goto fail2;
+    // PSIPHON
+    if (options.set_signal) {
+        // setup signal handler
+        if (!BSignal_Init(&ss, signal_handler, NULL)) {
+            BLog(BLOG_ERROR, "BSignal_Init failed");
+            goto fail2;
+        }
     }
     
-    // init TUN device
-    if (!BTap_Init(&device, &ss, options.tundev, device_error_handler, NULL, 1)) {
-        BLog(BLOG_ERROR, "BTap_Init failed");
-        goto fail3;
+    // PSIPHON
+    if (options.tun_fd) {
+        // use supplied file descriptor
+        if (!BTap_InitWithFD(&device, &ss, options.tun_fd, options.tun_mtu, device_error_handler, NULL, 1)) {
+            BLog(BLOG_ERROR, "BTap_InitWithFD failed");
+            goto fail3;
+        }
+    } else {
+        // init TUN device
+        if (!BTap_Init(&device, &ss, options.tundev, device_error_handler, NULL, 1)) {
+            BLog(BLOG_ERROR, "BTap_Init failed");
+            goto fail3;
+        }
     }
     
     // NOTE: the order of the following is important:
     BLog_Free();
 fail0:
     DebugObjectGlobal_Finish();
-    
-    return 1;
 }
 
 void terminate (void)
     printf(GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION"\n"GLOBAL_COPYRIGHT_NOTICE"\n");
 }
 
-int parse_arguments (int argc, char *argv[])
+//==== PSIPHON ====
+
+void init_arguments (const char* program_name)
 {
-    if (argc <= 0) {
-        return 0;
-    }
-    
     options.help = 0;
     options.version = 0;
     options.logger = LOGGER_STDOUT;
     #ifndef BADVPN_USE_WINAPI
     options.logger_syslog_facility = "daemon";
-    options.logger_syslog_ident = argv[0];
+    options.logger_syslog_ident = (char*)program_name;
     #endif
     options.loglevel = -1;
     for (int i = 0; i < BLOG_NUM_CHANNELS; i++) {
     options.udpgw_remote_server_addr = NULL;
     options.udpgw_max_connections = DEFAULT_UDPGW_MAX_CONNECTIONS;
     options.udpgw_connection_buffer_size = DEFAULT_UDPGW_CONNECTION_BUFFER_SIZE;
+
+    options.tun_fd = 0;
+    options.set_signal = 1;
+}
+
+//==== PSIPHON ====
+
+int parse_arguments (int argc, char *argv[])
+{
+    if (argc <= 0) {
+        return 0;
+    }
+    
+    // PSIPHON
+    init_arguments(argv[0]);
     
     int i;
     for (i = 1; i < argc; i++) {

Android/badvpn/tuntap/BTap.c

     return 1;
 }
 
+// ==== PSIPHON ====
+
+int BTap_InitWithFD (BTap *o, BReactor *reactor, int fd, int mtu, BTap_handler_error handler_error, void *handler_error_user, int tun)
+{
+    ASSERT(tun == 0 || tun == 1)
+    
+    #ifndef BADVPN_LINUX
+
+    return 0;
+
+    #endif
+    
+    o->reactor = reactor;
+    o->handler_error = handler_error;
+    o->handler_error_user = handler_error_user;
+    o->frame_mtu = mtu;
+    o->fd = fd;
+
+    // The following is identical to BTap_Init...
+        
+    // init file descriptor object
+    BFileDescriptor_Init(&o->bfd, o->fd, (BFileDescriptor_handler)fd_handler, o);
+    if (!BReactor_AddFileDescriptor(o->reactor, &o->bfd)) {
+        BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+        goto fail1;
+    }
+    o->poll_events = 0;
+    
+    goto success;
+    
+fail1:
+    ASSERT_FORCE(close(o->fd) == 0)
+fail0:
+    return 0;
+    
+    #endif
+    
+success:
+    // init output
+    PacketRecvInterface_Init(&o->output, o->frame_mtu, (PacketRecvInterface_handler_recv)output_handler_recv, o, BReactor_PendingGroup(o->reactor));
+    
+    // set no output packet
+    o->output_packet = NULL;
+    
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(o->reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+}
+
+// ==== PSIPHON ====
+
 void BTap_Free (BTap *o)
 {
     DebugObject_Free(&o->d_obj);

Android/badvpn/tuntap/BTap.h

  */
 int BTap_Init (BTap *o, BReactor *bsys, char *devname, BTap_handler_error handler_error, void *handler_error_user, int tun) WARN_UNUSED;
 
+// PSIPHON
+int BTap_InitWithFD (BTap *o, BReactor *bsys, int fd, int mtu, BTap_handler_error handler_error, void *handler_error_user, int tun) WARN_UNUSED;
+
 /**
  * Frees the TAP device.
  *