Lots of fresh code! You have been warned!!! Do pls report trouble!
authorRick van Rein <rick@openfortress.nl>
Fri, 26 Oct 2012 14:25:42 +0000 (16:25 +0200)
committerRick van Rein <rick@openfortress.nl>
Fri, 26 Oct 2012 14:25:42 +0000 (16:25 +0200)
 - GUI works half (IP, UDP, enabler, some actions after reboot)
 - made a first version of the TCP-bypass code (does not seem to work)

AndroidManifest.xml
README
res/layout/user_interface.xml
res/values/strings.xml
src/nl/openfortress/android6bed4/Android6bed4.java
src/nl/openfortress/android6bed4/EventListener.java [deleted file]
src/nl/openfortress/android6bed4/EventMonitor.java [new file with mode: 0644]
src/nl/openfortress/android6bed4/NeighborCache.java
src/nl/openfortress/android6bed4/TunnelService.java

index 4f4bb3f..0afd4c8 100644 (file)
@@ -1,7 +1,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="nl.openfortress.android6bed4"
     android:versionCode="5"
-    android:versionName="0.5" >
+    android:versionName="0.5.2" >
 
     <uses-sdk
         android:minSdkVersion="14"
@@ -14,6 +14,7 @@
         android:icon="@drawable/ic_launcher"
         android:label="@string/app_name"
         android:theme="@style/AppTheme" >
+        
         <activity
             android:name=".Android6bed4"
             android:label="@string/title_activity_main" >
              </intent-filter>
        </service>
        
-               <receiver android:name=".EventListener"
+               <receiver android:name=".EventMonitor"
                android:enabled="true"
                android:exported="true"
                android:label="BootReceiver">
                <intent-filter>
-               <action android:name="android.intent.action.BOOT_COMPLETED"></action>
-               <!-- The other intent CONNECTIVITY_ACTION is received after registration, so it only happens while the tunnel is active -->
+               <action android:name="android.intent.action.BOOT_COMPLETED" />
+               <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
                </intent-filter>
        </receiver>
-       
+              
    </application>
 
 </manifest>
diff --git a/README b/README
index 28d7f27..46de982 100644 (file)
--- a/README
+++ b/README
@@ -8,14 +8,24 @@ are non-blocking, making it impossible to wait for its
 I/O.  Furthermore, I found no way to use the select()
 call as I would have (and actually did) in C.  As a
 result, the tunnel must poll for traffic :'-(  So if I
-missed a Java trick here, *please* let me know.
+missed a Java trick here, *please* let me know.  It
+will greatly help in speeding up the tunnel *and* make
+it less of a battery suckling.
+
+Version 0.5.2 introduces a lot of new code, so don't
+run your lifeline over it, okay?  It adds an initial
+GUI with toggles (default route does not work yet, and
+autostart after boot is a shamble too) but the IP and
+UDP settings do work.  If you change a lot quickly in
+a sequence, someone might get upset.  Also, this is a
+first stab at the TCP bypassing system.
 
 Version 0.5 introduces Neighbor Solicitation and
 Neighbor Advertisement messages to try and pass through
 NAT and firewall layers, as per draft v01.  This means
 that peer-to-peer connectivity is usually possible; the
 only ones excepted are those who are behind symmetric
-NAT; such people should get a decent ISP/router anyway!
+NAT; such people should get a decent ISP/router anyway! 
 
 Version 0.4 is the first with dynamic addresses on
 the local end.  There is no longer a need to specify
index 0b0449b..c6d9257 100644 (file)
@@ -1,22 +1,63 @@
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-    
-    <TextView xmlns:tools="http://schemas.android.com/tools"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:paddingTop="25sp"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:id="@+id/textView1"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_centerHorizontal="true"
-        android:layout_centerVertical="true"
-        android:text="@string/welcome_6bed4"
+        android:text="@string/tunserver_address"
         tools:context=".Android6bed4" />
 
-    <ProgressBar
-        android:id="@+id/progress_bar"
-        style="?android:attr/progressBarStyleHorizontal"
+    <EditText
+        android:id="@+id/tunserver_ip_string"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:inputType="phone"
+        android:hint="@string/tunserver_address_hint" />
+    
+    <TextView
+        android:paddingTop="25sp"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:id="@+id/textView2"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_alignParentLeft="true"
-        android:layout_alignParentRight="true" />    
+        android:text="@string/tunclient_port"
+        tools:context=".Android6bed4" />
+
+    <EditText
+        android:id="@+id/tunclient_port_number"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:hint="@string/tunclient_port_hint"
+        android:inputType="number" />
+    
+    <Switch
+        android:paddingTop="25sp"
+        android:id="@+id/overtake_default_route"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:onClick="onDefaultRouteClick"
+        android:text="@string/overtake_default_route_text" />
+
+    <Switch
+        android:paddingTop="25sp"
+        android:id="@+id/persist_accross_reboots"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:onClick="onPersistClick"
+        android:text="@string/persist_accross_reboots_text" />
+    
+    <Switch
+        android:id="@+id/enable_6bed4"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:onClick="onEnablerClick"
+        android:paddingTop="25sp"
+        android:text="@string/enable_button_text" />
 
-</RelativeLayout>
\ No newline at end of file
+</LinearLayout>
\ No newline at end of file
index 1b0ea82..74ab8b7 100644 (file)
@@ -1,8 +1,13 @@
 <resources>
 
     <string name="app_name">6bed4</string>
-    <string name="welcome_6bed4">Welcome to the 6bed4 tunnel to IPv6</string>
     <string name="menu_settings">Settings</string>
     <string name="title_activity_main">6bed4</string>
-
+    <string name="tunserver_address">Tunnel Server Address:</string>
+    <string name="tunclient_port">Local UDP Port:</string>
+    <string name="tunserver_address_hint">145.136.0.1</string>
+    <string name="tunclient_port_hint">(random)</string>
+    <string name="persist_accross_reboots_text">Autostart after reboot</string>
+    <string name="overtake_default_route_text">Pass all IPv6 through 6bed4</string>
+    <string name="enable_button_text">Enable the 6bed4 tunnel</string>
 </resources>
\ No newline at end of file
index d18ad1f..332356f 100644 (file)
@@ -6,21 +6,135 @@ import android.net.VpnService;
 import android.net.VpnService.Builder;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
-import android.widget.ProgressBar;
+import android.view.View;
+import android.text.TextWatcher;
+import android.text.Editable;
+import android.widget.TextView;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.Switch;
 
 import java.io.IOException;
 import java.net.*;
 
 
-public class Android6bed4 extends Activity {
+public class Android6bed4 extends Activity implements TextWatcher {
 
        private DatagramSocket uplink = null;
-       private TunnelService downlink = null;
-       
-       private ProgressBar debug;
+               
+       private boolean persist = false;
        
        public InetSocketAddress publicserver;
        
+       private Intent mktun_intent;
+
+       private boolean interactive = false;
+
+       private int local_port = 0;
+       
+       
+       /*
+        * User interface, with method names as specified in the XML file layout/user_interface.xml
+        */
+       
+       /*
+        * User interface: onPersistClick is called when the checkbox is toggled that changes reboot behaviour.
+        */
+       public void onPersistClick (View vw) {
+               persist = ((Switch) vw).isChecked ();
+               teardown_tunnel ();
+               if (!persist) {
+                       //
+                       // Switching off the boot persistency is done immediately
+                       //TODO// Store "persistent := false"
+               } else {
+                       //
+                       // Skip:
+                       // Switching on boot persistency is only done after success
+               }
+       }
+
+       /*
+        * User interface: onDefaultRouteClick is called when the switch is toggled between a /64 and a /0 prefix.
+        */
+       public void onDefaultRouteClick (View vw) {
+               boolean defaultroute = ((Switch) vw).isChecked ();
+               teardown_tunnel ();
+       }
+       
+       /*
+        * User interface: onEnablerClick is called when the switch is toggled that enables/disables the tunnel.
+        */
+       public void onEnablerClick (View vw) {
+               boolean enable = ((Switch) vw).isChecked ();
+               teardown_tunnel ();
+               if (enable) {
+                       //
+                       // Enable the 6bed4 tunnel
+                       try {
+                               TextView tv = (TextView) findViewById (R.id.tunserver_ip_string);
+                               String pubsrv_ip = tv.getText ().toString ().trim ();
+                               if (pubsrv_ip.length () == 0) {
+                                       pubsrv_ip = "145.136.0.1";
+                               }
+                               publicserver = new InetSocketAddress (Inet4Address.getByName (pubsrv_ip), 25788);
+                       } catch (Throwable thr) {
+                               publicserver = null;
+                               Switch sv = (Switch) findViewById (R.id.enable_6bed4);
+                               sv.setChecked (false);
+                               return;
+                       }
+                       //
+                       // Extract local UDP port from GUI (or already have it setup, for better recycling)
+                       int new_port;
+                       try {
+                               TextView tv = (TextView) findViewById (R.id.tunclient_port_number);
+                               new_port = new Integer (tv.getText ().toString ().trim ()).intValue () & 0xfffe;
+                       } catch (Throwable thr) {
+                               new_port = local_port;
+                       }
+                       try {
+                               DatagramSocket new_uplink;
+                               if ((new_port > 0) && (new_port <= 65535) && ((new_port & 0x0001) == 0x0000)) {
+                                       new_uplink = new DatagramSocket (new_port);
+                               } else {
+                                       new_uplink = new DatagramSocket ();
+                                       new_port = new_uplink.getPort ();
+                               }
+                               if (uplink != null) {
+                                       uplink.close ();
+                               }
+                               local_port = new_port;
+                               uplink = new_uplink;
+                       } catch (SocketException se) {
+                               uplink = null;
+                               Switch sv = (Switch) findViewById (R.id.enable_6bed4);
+                               sv.setChecked (false);
+                               return;
+                       }
+                       try {
+                               setup_tunnel ((Inet6Address) Inet6Address.getByName ("::1"));   /* TODO: Note here, not with a static IPv6 address */
+                               if (!interactive) {
+                                       finish ();
+                               }
+                       } catch (UnknownHostException uhe) {
+                               Switch sv = (Switch) findViewById (R.id.enable_6bed4);
+                               sv.setChecked (false);
+                               return;
+                       }
+                       //TODO// Wait for 1s or so, reporting the state?  Or link back from tunsvc to view?
+               }
+       }
+       
+       /*
+        * User interface: Change to either text field -- meaning, disable any current tunnel (and the enabler widget).
+        */
+       public void afterTextChanged (Editable s) { ; }
+       public void onTextChanged (CharSequence s, int start, int count, int after) { ; }
+       public void beforeTextChanged (CharSequence s, int start, int count, int after) {
+               teardown_tunnel ();
+       }
+       
        /*
         * State management interface, called from Android OS.  Thee onXXX() conform to a state diagram.
         * 
@@ -28,67 +142,76 @@ public class Android6bed4 extends Activity {
         */
        protected void onCreate (Bundle savedInstanceState) {
                super.onCreate (savedInstanceState);
+               TunnelService tunsvc = TunnelService.theTunnelService ();
+               if (tunsvc != null) {
+                       uplink = tunsvc.uplink;
+               }
                try {
-                       publicserver = new InetSocketAddress (Inet4Address.getByName ("145.136.0.1"), 25788); /* TODO:FIXED */
-               } catch (UnknownHostException uhe) {
-                       publicserver = null;
+                       uplink = new DatagramSocket (); //TODO// Remove port (to unfix it)
+               } catch (IOException ioe) {
+                       uplink = null;
                }
+               mktun_intent = VpnService.prepare (this);
+               boolean have_tunnel = (TunnelService.theTunnelService () != null);
+               interactive = (mktun_intent == null) || (tunsvc != null);
+               //TODO:USE?// savedInstanceState.getBoolean ("persist_accross_reboots", false);
+               //TODO:USE?// savedInstanceState.getBoolean ("overtake_default_route", true);
+               //TODO:USE?// savedInstanceState.getString ("tunserver_ip", "145.136.0.1");
+               //TODO:USE?// savedInstanceState.getInt ("tunclient_port", 0);
+               TextView tv;
+               Switch sv;
                setContentView (R.layout.user_interface);
-               debug = (ProgressBar) findViewById (R.id.progress_bar);
-               
+               tv = (TextView) findViewById (R.id.tunserver_ip_string);
+               tv.addTextChangedListener (this);
+               tv = (TextView) findViewById (R.id.tunclient_port_number);
+               tv.addTextChangedListener (this);
+               sv = (Switch) findViewById (R.id.enable_6bed4);
+               sv.setChecked (have_tunnel);
+               sv = (Switch) findViewById (R.id.overtake_default_route);
+               sv.setChecked (true);
+               sv = (Switch) findViewById (R.id.persist_accross_reboots);
+               sv.setChecked (false);
        }
        
        protected void onStart () {
                super.onStart ();
-               if (debug != null) debug.setProgress (10);
-               try {
-                       uplink = new DatagramSocket (0xdebe); /* TODO:FIXED */
-               } catch (SocketException se) {
-                       uplink = null;
-               }
-               try {
-                       setupTunnelService ((Inet6Address) Inet6Address.getByName ("::1"));     /* TODO: Note here, not with a static IPv6 address */
-               } catch (UnknownHostException uhe) {
+               if (!interactive) {
+                       //
+                       // This is a new start of the VPN
+                       try {
+                               //TODO// extract public server address from params 
+                               publicserver = new InetSocketAddress (Inet4Address.getByName ("145.136.0.1"), 25788); /* TODO:FIXED */
+                       } catch (UnknownHostException uhe) {
+                               publicserver = null;
+                       }
+                       try {
+                               setup_tunnel ((Inet6Address) Inet6Address.getByName ("::1"));   /* TODO: Note here, not with a static IPv6 address */
+                       } catch (UnknownHostException uhe) {
+                               interactive = true;
+                       }
                }
-               if (debug != null) debug.setProgress (20);
-               if (debug != null) debug.setProgress (30);
        }
        
        protected void onResume () {
                super.onResume ();
                /* TODO: Check if the IPv6 address is still the same?  Also run after onStart! */
-               if (debug != null) debug.setProgress (50);
        }
 
        protected void onPause () {
                super.onPause ();
                /* TODO: Nothing to do I suppose... try to avoid loosing IPv6 address? */
-               if (debug != null) debug.setProgress (70);              
        }
        
        protected void onStop () {
                super.onStop ();
-               if (uplink != null) {
-                       uplink.close ();
-                       uplink = null;
-               }
-               if (debug != null) debug.setProgress (80);
        }
 
        protected void onRestart () {
                super.onRestart ();
-               /* TODO: Nothing? */
-               if (debug != null) debug.setProgress (90);
        }
        
        protected void onDestroy () {
                super.onDestroy ();
-               if (downlink != null) {
-                       downlink.teardown ();
-                       downlink = null;
-               }
-               /* TODO: Nothing? */
-               if (debug != null) debug.setProgress (100);
        }
 
 
@@ -96,13 +219,17 @@ public class Android6bed4 extends Activity {
         ***   Internal affairs -- tunnel service setup.    ***
         ***                                                ***/
 
-       public void setupTunnelService (Inet6Address addr6bed4) {
+       public void setup_tunnel (Inet6Address addr6bed4) {
                //
                // Prepare the context for the VPN
-               Intent mktun_intent = VpnService.prepare (this);
+               if (mktun_intent == null) {
+                       mktun_intent = VpnService.prepare (this);
+               }
+               //TODO:ALT// Intent mktun_intent = TunnelService.prepare (this);
                if (mktun_intent != null) {
                        // This is a new start of the VPN
                        this.startActivityForResult (mktun_intent, 0);
+                       mktun_intent = null;
                } else {
                        // Already started, apparently OK, so proceed to startup
                        this.onActivityResult (0, Activity.RESULT_OK, null);
@@ -113,25 +240,32 @@ public class Android6bed4 extends Activity {
                if (resultcode == Activity.RESULT_OK) {
                        //
                        // Cleanup any prior tunnel file descriptors
-                       if (downlink != null) {
-                               downlink.teardown ();
-                       }
+                       teardown_tunnel ();
                        //
                        // Setup a new tunnel
                        // TODO: Due to this statement, two tunnel interfaces get created;
                        //       without it, none are created.  Not sure what to think
                        //       of it... need to leave it like this for now.
-                       downlink = new TunnelService (uplink, publicserver);
+                       TunnelService downlink = new TunnelService (uplink, publicserver);
                        if (downlink != null) {
                                downlink = new TunnelService (); /*TODO:HUH?*/
-                               //
-                               // Given the successful startup, avoid future cleanups so the
-                               // TunnelService can continue to use these resources.  It has
-                               // taken responsibility over their cleanip after returning.
-                               uplink = null;
-                               downlink = null;
-                               publicserver = null;
                        }
+                       if (!interactive) {
+                               finish ();
+                       }
+               } else {
+                       //
+                       // Result is not OK -- tear down the tunnel
+                       teardown_tunnel ();
+               }
+       }
+
+       private void teardown_tunnel () {
+               TunnelService tunsvc = TunnelService.theTunnelService ();
+               if (tunsvc != null) {
+                       tunsvc.teardown ();
+                       Switch enabler = (Switch) findViewById (R.id.enable_6bed4);
+                       enabler.setChecked (false);
                }
        }
        
diff --git a/src/nl/openfortress/android6bed4/EventListener.java b/src/nl/openfortress/android6bed4/EventListener.java
deleted file mode 100644 (file)
index e298c8f..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-
-package nl.openfortress.android6bed4;
-
-
-import java.net.NetworkInterface;
-import java.net.InetAddress;
-import java.net.SocketException;
-import java.util.Enumeration;
-import java.util.Collection;
-import java.util.Vector;
-
-
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.Context;
-import android.net.ConnectivityManager;
-
-
-/* The EventListener class picks up signals from the Android OS,
- * and processes them internally.
- * 
- * Upon receiving BOOT_COMPLETED, it will see if the user wishes
- * to start 6bed4 at boot time. Since this presents a popup, this
- * is a setting that must be explicitly configured.  More details:
- * http://stackoverflow.com/questions/5051687/broadcastreceiver-not-receiving-boot-completed
- * 
- * Upon receiving CONNECTIVITY_ACTION, it will see if there is a
- * default route over IPv6 that does not a 6bed4 address.  See:
- * http://stackoverflow.com/questions/5276032/connectivity-action-intent-recieved-twice-when-wifi-connected
- * http://developer.android.com/reference/android/net/ConnectivityManager.html 
- */
-class EventListener extends BroadcastReceiver {
-       
-       private TunnelService netcfg_target;
-       
-       public void onReceive (Context ctx, Intent intent) {
-               String act = intent.getAction ();
-               
-               if (act.equals (ConnectivityManager.CONNECTIVITY_ACTION)) {
-                       //
-                       // The Network Configuration has changed.
-                       // The NetworkInfo for the affected network is sent as an extra
-                       if (netcfg_target == null) {
-                               return;
-                       }
-                       try {
-                               Collection <byte[]> ipv6list = new Vector <byte[]> ();
-                               Enumeration <NetworkInterface> nif_iter = NetworkInterface.getNetworkInterfaces ();
-                               while (nif_iter.hasMoreElements ()) { 
-                                       NetworkInterface nif = nif_iter.nextElement ();
-                                       try {
-                                               if (nif.isUp ()) {
-                                                       Enumeration <InetAddress> adr_iter = nif.getInetAddresses ();
-                                                       while (adr_iter.hasMoreElements ()) {
-                                                               InetAddress adr = adr_iter.nextElement ();
-                                                               if (adr.isLoopbackAddress () || adr.isLinkLocalAddress () || adr.isAnyLocalAddress () || adr.isSiteLocalAddress () || adr.isMulticastAddress ()) {
-                                                                       continue;
-                                                               }
-                                                               byte ipv6addr [] = adr.getAddress ();
-                                                               if (ipv6addr.length != 16) {
-                                                                       continue;
-                                                               }
-                                                               ipv6list.add (ipv6addr);
-                                                       }
-                                               }
-                                       } catch (SocketException se) {
-                                               ;       /* Ignore */
-                                       }
-                                       // Now tell the tunnel service about ipv6list
-                                       netcfg_target.notify_ipv6_addresses (ipv6list);
-                               }
-                       } catch (SocketException se) {
-                               ;       /* Ignore */
-                       }
-                       
-               } else if (act.equals (Intent.ACTION_BOOT_COMPLETED)) {
-                       //
-                       // The system has finished booting.  Consider starting 6bed4
-                       // at this point (if the user is willing to deal with the popup
-                       // from the VPN toolkit at this point.
-                       Intent main = new Intent (Intent.ACTION_MAIN);
-                       main.addCategory (Intent.CATEGORY_HOME);
-                       ctx.startActivity (main);
-               }
-               
-       }
-       
-       /* Register this object as a network monitor reporting to the
-        * given TunnelService object when the set of local IPv6 addresses
-        * changes.  Note that the TunnelService is supposed to be smart
-        * enough to avoid endless loops; the monitor simply reports if a
-        * notification comes in, even if it is caused by 6bed4.
-        */
-       public void register_network_monitor (TunnelService target) {
-               if (netcfg_target != null) {
-                       unregister_network_monitor ();
-               }
-               netcfg_target = target;
-               IntentFilter intflt = new IntentFilter (Context.CONNECTIVITY_SERVICE);
-               /* TODO -- how to get to the current context?!?
-               Context ctx = getApplicationContext ();
-               ctx.registerReceiver (this, intflt);
-               */
-
-       }
-       
-       /* Unregister this object as a network monitor reporting to a
-        * once-setup TunnelService.
-        */
-       public void unregister_network_monitor () {
-               //TODO// how to unregister for CONNECTIVITY_SERVICE broadcasts?
-               netcfg_target = null;
-       }
-       
-}
\ No newline at end of file
diff --git a/src/nl/openfortress/android6bed4/EventMonitor.java b/src/nl/openfortress/android6bed4/EventMonitor.java
new file mode 100644 (file)
index 0000000..df13313
--- /dev/null
@@ -0,0 +1,104 @@
+
+package nl.openfortress.android6bed4;
+
+
+import java.net.Inet6Address;
+import java.net.NetworkInterface;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.util.Enumeration;
+import java.util.Collection;
+import java.util.Vector;
+
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.Context;
+import android.content.ComponentName;
+import android.net.ConnectivityManager;
+import android.net.VpnService;
+import android.util.Log;
+
+
+/* The EventMonitor class picks up signals from the Android OS,
+ * and processes them internally.
+ * 
+ * Upon receiving BOOT_COMPLETED, it will see if the user wishes
+ * to start 6bed4 at boot time. Since this presents a popup, this
+ * is a setting that must be explicitly configured.  More details:
+ * http://stackoverflow.com/questions/5051687/broadcastreceiver-not-receiving-boot-completed
+ * 
+ * Upon receiving CONNECTIVITY_ACTION, it will see if there is a
+ * default route over IPv6 that does not a 6bed4 address.  See:
+ * http://stackoverflow.com/questions/5276032/connectivity-action-intent-recieved-twice-when-wifi-connected
+ * http://developer.android.com/reference/android/net/ConnectivityManager.html 
+ */
+public class EventMonitor extends BroadcastReceiver {
+       
+       public final static String TAG = "android6bed4.EventMonitor";
+       
+       public void onReceive (Context ctx, Intent intent) {
+               String act = intent.getAction ();
+               TunnelService tunsvc = TunnelService.theTunnelService ();
+       
+/* TODO: MALFUNCTIONING NETWORK ADDRESS PICKUPS?
+               if (act.equals (ConnectivityManager.CONNECTIVITY_ACTION)) {
+                       //
+                       // The Network Configuration has changed.
+                       // The NetworkInfo for the affected network is sent as an extra
+                       if (tunsvc == null) {
+                               return;
+                       }
+                       try {
+                               Collection <byte[]> ipv6list = new Vector <byte[]> ();
+                               Enumeration <NetworkInterface> nif_iter = NetworkInterface.getNetworkInterfaces ();
+                               while (nif_iter.hasMoreElements ()) { 
+                                       NetworkInterface nif = nif_iter.nextElement ();
+                                       try {
+                                               if (nif.isUp ()) {
+                                                       Enumeration <InetAddress> adr_iter = nif.getInetAddresses ();
+                                                       while (adr_iter.hasMoreElements ()) {
+                                                               InetAddress adr = adr_iter.nextElement ();
+                                                               if (adr.isLoopbackAddress () || adr.isLinkLocalAddress () || adr.isAnyLocalAddress () || adr.isSiteLocalAddress () || adr.isMulticastAddress ()) {
+                                                                       continue;
+                                                               }
+                                                               byte ipv6addr [] = adr.getAddress ();
+                                                               if (ipv6addr.length != 16) {
+                                                                       continue;
+                                                               }
+                                                               ipv6list.add (ipv6addr);
+                                                               Log.v (TAG, "Tunnel Service notified of IPv6 address list");
+                                                       }
+                                               }
+                                       } catch (SocketException se) {
+                                               ;       // Ignore
+                                       }
+                                       // Now tell the tunnel service about ipv6list
+                                       tunsvc.notify_ipv6_addresses (ipv6list);
+                               }
+                       } catch (SocketException se) {
+                               ;       // Ignore
+                       }
+                       Log.v (TAG, "Network Interface Change Processed");
+                       
+               } else 
+*/
+               if (act.equals (Intent.ACTION_BOOT_COMPLETED)) {
+                       //
+                       // The system has finished booting.  Consider starting 6bed4
+                       // at this point (if the user is willing to deal with the popup
+                       // from the VPN toolkit at this point.
+                       Intent main = new Intent (Intent.ACTION_MAIN);
+                       main.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                       // main.addCategory (Intent.CATEGORY_HOME);
+                       main.addCategory (Intent.CATEGORY_LAUNCHER);
+                       main.setComponent (new ComponentName (ctx, Android6bed4.class));
+                       ctx.startActivity (main);
+                       Log.v (TAG, "Boot action completed");
+               }
+               
+       }
+
+}
\ No newline at end of file
index a8f0fcd..f751bd1 100644 (file)
@@ -104,7 +104,7 @@ class NeighborCache {
         * to find a more evolved value; the latter will be incorporated into
         * the procedures of the Neighbor Cache, including the various queues.
         */
-       private Hashtable <Neighbor, Neighbor> neighbor_cache;
+       private Hashtable <Integer, Neighbor> neighbor_cache;
        
        
        /* The public server, used as default return value for queires after
@@ -139,7 +139,7 @@ class NeighborCache {
                for (int i=0; i < NUM_NGB_STATES; i++) {
                        queues [i] = new NeighborQueue (this, i);
                }
-               neighbor_cache = new Hashtable <Neighbor,Neighbor> ();
+               neighbor_cache = new Hashtable <Integer,Neighbor> ();
        }
 
        /* Remove an expired entry from the neighbor cache.
@@ -171,7 +171,7 @@ class NeighborCache {
         *  still tried towards the remote peer, until something comes back
         *  directly to acknowledge contact.  When something comes back over
         *  a direct route, this should be reported to the neighbor cache
-        *  through received_neighbor_direct_acknowledgement() so it can
+        *  through received_peer_direct_acknowledgement() so it can
         *  learn that future traffic can be directly sent to the peer.
         *  
         * A common case for which this holds is TCP connection setup:
@@ -211,12 +211,12 @@ class NeighborCache {
                Neighbor ngb;
                byte seeker_key [] = new byte [6];
                address2key (addr, addrofs, seeker_key);        //TODO// Only used for seeker creation?
-               Neighbor seeker = new Neighbor (seeker_key);
+               int seeker_hash = key2hash (seeker_key);
                synchronized (neighbor_cache) {
-                       ngb = neighbor_cache.get (seeker);
+                       ngb = neighbor_cache.get (seeker_hash);
                        if (ngb == null) {
-                               ngb = new Neighbor (seeker_key);        //TODO// Possibly seeker.clone ()
-                               neighbor_cache.put (ngb, ngb);
+                               ngb = new Neighbor (seeker_key, playful);       //TODO// Possibly seeker.clone ()
+                               neighbor_cache.put (seeker_hash, ngb);
                                puppy = true;
                        }
                }
@@ -259,16 +259,21 @@ class NeighborCache {
                        // There is no work to be done to that end.
                        return ngb.target;
                default:
-                       return null;
+                       return public_server;
                }
        }
        
        /* Send a Neighbor Solicitation to the peer.  When this succeeds at
         * penetrating to the remote peer's 6bed4 stack, then a Neighbor
         * Advertisement will return, and be reported through the method
-        * NeighborCache.received_neighbor_direct_acknowledgement() so the
+        * NeighborCache.received_peer_direct_acknowledgement() so the
         * cache can update its state, and support direct sending to the
         * remote peer.  
+        * 
+        * Even though this kind of message may be sent after failed
+        * playful attempts, it still does not rule out the possibility
+        * of any such attempts returning, so the "playful" flag for
+        * the Neighbor solicited is not changed.
         */
        public void solicit_neighbor (Neighbor ngb) {
                byte key [] = new byte [6];
@@ -314,18 +319,30 @@ class NeighborCache {
        /* The TunnelServer reports having received a neighbor advertisement
         * by calling this function.  This may signal the cache that the
         * corresponding cache entry needs updating.
+        * 
+        * The "playful" flag is used to indicate that the acknowledgement
+        * arrived as part of a playful attempt in lookup_neighbor().  This
+        * information is used to determine if resends have taken place
+        * through the tunnel server.  If such resends were sent, then it
+        * is not safe anymore to assume that a one-to-one exchange has
+        * taken place, and it becomes necessary to rely on the generic
+        * mechanism of Neighbor Discovery to detect the ability for direct
+        * contact between peers.
         */
-       public void received_neighbor_direct_acknowledgement (byte addr [], int addrofs) {
+       public void received_peer_direct_acknowledgement (byte addr [], int addrofs, boolean playful) {
                Neighbor ngb;
                byte key [] = new byte [6];
                address2key (addr, addrofs, key);
-               Neighbor seeker = new Neighbor (key);
+               int seeker_hash = key2hash (key);
                synchronized (neighbor_cache) {
-                       ngb = neighbor_cache.get (seeker);
+                       ngb = neighbor_cache.get (seeker_hash);
                }
                if (ngb == null) {
                        return;
                }
+               if (playful && !ngb.playful) {
+                       return;
+               }
                int nst = ngb.state;
                switch (nst) {
                case ATTEMPT1:
@@ -377,11 +394,12 @@ class NeighborCache {
         * 
         * TODO: Inner class -- does that mean that the context is copied into this object?  That'd be wasteful!
         */
-       class Neighbor {
+       static class Neighbor {
                protected int state;
                protected InetSocketAddress target;
                protected long timeout;
                byte key [];
+               boolean playful;
                
                /* The hashCode for this object depends solely on the key value.
                 * Integrate all the bits of the key for optimal spreading.  The
@@ -414,17 +432,18 @@ class NeighborCache {
                }
                
                /* Construct a new Neighbor based on its 6-byte key */
-               public Neighbor (byte fromkey []) {
+               public Neighbor (byte fromkey [], boolean create_playfully) {
                        key = new byte [6];
                        for (int i=0; i<6; i++) {
                                key [i] = fromkey [i];
                        }
                        state = NeighborCache.ATTEMPT1;
                        target = key2socketaddress (key);
+                       playful = create_playfully;
                }
        }
        
-       /* The NeighborQueue subclass represents a timer queue.  The objects in this
+       /* The NeighborQueue inner class represents a timer queue.  The objects in this
         * queue each have their own timeouts, as specified by state_timing_millis.
         * New entries are attached to the back, timeouts are picked up at the front.
         * 
@@ -660,7 +679,7 @@ class NeighborCache {
        /* Map an address to a 6-byte key in the provided space.  Assume that the
         * address is known to be a 6bed4 address.
         */
-       public void address2key (byte addr [], int addrofs, byte key []) {
+       private static void address2key (byte addr [], int addrofs, byte key []) {
                key [0] = (byte) (addr [addrofs +  8] ^ 0x02);
                key [1] = addr [addrofs +  9];
                key [2] = addr [addrofs + 10];
@@ -671,7 +690,7 @@ class NeighborCache {
 
        /* Map an InetSocketAddress to a 6-byte key in the provided space.
         */
-       public void socketaddress2key (InetSocketAddress sa, byte key []) {
+       private static void socketaddress2key (InetSocketAddress sa, byte key []) {
                int port = sa.getPort ();
                byte addr [] = sa.getAddress ().getAddress ();
                key [0] = (byte) (port & 0xff);
@@ -684,7 +703,7 @@ class NeighborCache {
 
        /* Map a key to an IPv6 address, following the 6bed4 structures.
         */
-       public void key2ipv6address (byte key [], byte addr [], int addrofs) {
+       private void key2ipv6address (byte key [], byte addr [], int addrofs) {
                for (int i=0; i<8; i++) {
                        addr [addrofs + i] = address_6bed4 [i];
                }
@@ -700,7 +719,7 @@ class NeighborCache {
 
        /* Map a key to an InetSocketAddress.
         */
-       public InetSocketAddress key2socketaddress (byte key []) {
+       private static InetSocketAddress key2socketaddress (byte key []) {
                int port = (int) (key [0] & 0xff);
                port = port | (((int) (key [1] << 8)) & 0xff00);
                byte v4addr [] = new byte [4];
@@ -716,7 +735,7 @@ class NeighborCache {
                }
        }
        
-       public int key2hash (byte key []) {
+       private static int key2hash (byte key []) {
                int retval;
                retval = key [0] ^ key [3];
                retval <<= 8;
index 6b539e1..39c854a 100644 (file)
@@ -2,6 +2,7 @@ package nl.openfortress.android6bed4;
 
 import android.net.VpnService;
 import android.os.ParcelFileDescriptor;
+import android.util.Log;
 
 import java.net.*;
 import java.io.*;
@@ -11,14 +12,17 @@ import java.util.Iterator;
 
 public final class TunnelService extends VpnService {
 
+       private static final String TAG = "android6bed4.TunnelService";
+       
+       static private TunnelService singular_instance = null;
+       
        static private ParcelFileDescriptor fio = null;
        static private FileInputStream  downlink_rd = null;
        static private FileOutputStream downlink_wr = null;
 
        static private InetSocketAddress tunserver = null;
-       static private DatagramSocket uplink = null;
+       static public DatagramSocket uplink = null;
 
-       static private EventListener netmon = null;
        static private NeighborCache ngbcache = null;
        
        static private boolean new_setup_defaultroute = true;
@@ -159,6 +163,7 @@ public final class TunnelService extends VpnService {
                                        }
                                        //TODO// syslog (LOG_INFO, "%s: Assigning address %s to tunnel\n", program, v6prefix);
                                        change_local_netconfig ();  //TODO// parameters?
+                                       Log.i (TAG, "Assigned address to tunnel");
                                }
                                return;
                        //
@@ -239,7 +244,7 @@ public final class TunnelService extends VpnService {
                                //
                                // Not checked here: IPv4/UDP source versus IPv6 source address (already done)
                                //
-                               ngbcache.received_neighbor_direct_acknowledgement (pkt, OFS_ICMP6_NGBADV_TARGET);
+                               ngbcache.received_peer_direct_acknowledgement (pkt, OFS_ICMP6_NGBADV_TARGET, false);
                                return;
                        //
                        // Route Redirect messages are not supported in 6bed4 draft v01
@@ -281,8 +286,15 @@ public final class TunnelService extends VpnService {
                        if (downlink_wr != null) {
                                downlink_wr.write (pkt, 0, pktlen);
                        }
-                       boolean tcpack = (pkt [OFS_IP6_NXTHDR] == IPPROTO_TCP) && ((pkt [OFS_TCP6_FLAGS] & TCP_FLAG_ACK) != 0x00);
-                       //TODO// report back if this is a success, that is, a tcpack packet
+                       //
+                       // If this is a successful peering attempt, that is, a tcpack packet, report that back
+                       // Note that the UDP/IPv4 source has already been validated against the IPv6 source
+                       boolean tcpack = (pktlen >= 40 + 20) && (pkt [OFS_IP6_NXTHDR] == IPPROTO_TCP) && ((pkt [OFS_TCP6_FLAGS] & TCP_FLAG_ACK) != 0x00);
+                       if (tcpack) {
+                               if (ngbcache.is6bed4 (pkt, OFS_IP6_SRC)) {
+                                       ngbcache.received_peer_direct_acknowledgement (pkt, OFS_IP6_SRC, true);
+                               }
+                       }
                }
                
                public void handle_4to6 (DatagramPacket datagram)
@@ -349,7 +361,7 @@ public final class TunnelService extends VpnService {
                        InetSocketAddress target;
                        if ((ngbcache != null) && ngbcache.is6bed4 (pkt, 24)) {
                                boolean tcpsyn = (pkt [OFS_IP6_NXTHDR] == IPPROTO_TCP) && ((pkt [OFS_TCP6_FLAGS] & TCP_FLAG_SYN) != 0x00);
-                               target = ngbcache.lookup_neighbor (pkt, 24, false);  //TODO// use tcpsyn as "playful" parameter
+                               target = ngbcache.lookup_neighbor (pkt, 24, tcpsyn);
                        } else {
                                target = tunserver;
                        }
@@ -539,7 +551,9 @@ public final class TunnelService extends VpnService {
                                        DatagramPacket rtrsol = new DatagramPacket (router_solicitation, router_solicitation.length, tunserver);
                                        uplink.send (rtrsol);
                                } catch (IOException ioe) {
-                                       throw new RuntimeException ("Network failure", ioe);
+                                       /* Network is probably down, so don't
+                                        * throw new RuntimeException ("Network failure", ioe);
+                                        */
                                }
                        }
                }
@@ -553,8 +567,10 @@ public final class TunnelService extends VpnService {
                        if ((keepalive_packet != null) && (uplink != null)) {
                                try {
                                        uplink.send (keepalive_packet);
+                                       Log.i (TAG, "Sent KeepAlive (empty UDP) to Tunnel Server");
                                } catch (IOException ioe) {
-                                       ;       /* Better luck next time? */
+                                       ;       /* Network is probably down; order reconnect to tunnel server */
+                                       have_lladdr = false;
                                }
                        }
                }
@@ -570,10 +586,15 @@ public final class TunnelService extends VpnService {
                                        maintenance_time_cycle = maintenance_time_cycle_max;
                                }
                                //TODO// syslog (LOG_INFO, "Sent Router Advertisement to Public 6bed4 Service, next attempt in %d seconds\n", maintenance_time_cycle);
+                               Log.i (TAG, "Sent Router Advertisement to Tunnel Server");
                        } else {
                                //TODO// syslog (LOG_INFO, "Sending a KeepAlive message (empty UDP) to the 6bed4 Router\n");
                                keepalive ();
-                               maintenance_time_cycle = maintenance_time_cycle_max;
+                               if (have_lladdr) {
+                                       maintenance_time_cycle = maintenance_time_cycle_max;
+                               } else {
+                                       maintenance_time_cycle = 1;
+                               }
                        }
                        maintenance_time_millis = System.currentTimeMillis () + 1000 * (long) maintenance_time_cycle;
                }
@@ -641,7 +662,10 @@ public final class TunnelService extends VpnService {
                        throw new RuntimeException ("6bed4 address rejected", uhe);
                }
                if (new_setup_defaultroute) {
+                       Log.i (TAG, "Creating default route through 6bed4 tunnel");
                        builder.addRoute ("::", 0);
+               } else {
+                       Log.i (TAG, "Skipping default route through 6bed4 tunnel");
                }
                if (fio != null) {
                        try {
@@ -698,11 +722,8 @@ public final class TunnelService extends VpnService {
                        // notifyAll ();
                }
                //
-               // Setup a network monitor that will watch for broadcast events with network changes
-               if (netmon == null) {
-                       netmon = new EventListener ();
-               }
-               netmon.register_network_monitor (this);
+               // Setup a link to the instance of this singular class, for use in the EventMonitor
+               singular_instance = this;
                //
                // Create the worker thread that will pass information back and forth
                if (worker == null) {
@@ -721,13 +742,14 @@ public final class TunnelService extends VpnService {
                }
        }
        
+       public static TunnelService theTunnelService () {
+               return singular_instance;
+       }
+       
        synchronized public void teardown () {
                try {
                        synchronized (this) {
-                               if (netmon != null) {
-                                       netmon.unregister_network_monitor ();
-                                       netmon = null;
-                               }
+                               singular_instance = null;
                                if (worker != null) {
                                        worker.interrupt ();
                                        worker = null;