All of lore.kernel.org
 help / color / mirror / Atom feed
* WireGuard root-less support for android
@ 2017-11-07  4:38 Aurélien Chabot
  2017-11-07  4:38 ` [PATCH 1/4] Add wireguard-go as submodule Aurélien Chabot
                   ` (8 more replies)
  0 siblings, 9 replies; 14+ messages in thread
From: Aurélien Chabot @ 2017-11-07  4:38 UTC (permalink / raw)
  To: wireguard

Hi,

I worked on a set of change to add root-less support of WireGuard for
android. The solution I choose is to use the wireguard-go library inside
the android application. Golang as a mechanism to export some native
binding quite easily to java. The set of patch need some feedback but
it's actually working well. I'd like to know if you think this is a good
direction to take for the android application.
The patch are in the thread but I used a submodule to integrate the
wireguard-go library inside the wireguard-android so at least this need
to be change with the official url if it's get merge. 

You can also find the set of change on my github :
https://github.com/trishika/wireguard-android
https://github.com/trishika/wireguard-go

Aurélien

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH 1/4] Add wireguard-go as submodule
  2017-11-07  4:38 WireGuard root-less support for android Aurélien Chabot
@ 2017-11-07  4:38 ` Aurélien Chabot
  2017-11-07  9:34   ` Greg KH
  2017-11-07  4:38 ` [PATCH 2/4] Add support for android in wireguard-go Aurélien Chabot
                   ` (7 subsequent siblings)
  8 siblings, 1 reply; 14+ messages in thread
From: Aurélien Chabot @ 2017-11-07  4:38 UTC (permalink / raw)
  To: wireguard

Signed-off-by: Aurélien Chabot <aurelien@chabot.fr>
---
 .gitmodules  | 3 +++
 wireguard-go | 1 +
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 wireguard-go

diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..c7957ea
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "wireguard-go"]
+	path = wireguard-go
+	url = git@github.com:trishika/wireguard-go.git
diff --git a/wireguard-go b/wireguard-go
new file mode 160000
index 0000000..32d8932
--- /dev/null
+++ b/wireguard-go
@@ -0,0 +1 @@
+Subproject commit 32d8932d1b29effa42fe56822988d3888c496873
-- 
2.15.0

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 2/4] Add support for android in wireguard-go
  2017-11-07  4:38 WireGuard root-less support for android Aurélien Chabot
  2017-11-07  4:38 ` [PATCH 1/4] Add wireguard-go as submodule Aurélien Chabot
@ 2017-11-07  4:38 ` Aurélien Chabot
  2017-11-07  4:38 ` [PATCH 3/4] Add support of wireguard-go as an Android Vpn Service Aurélien Chabot
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Aurélien Chabot @ 2017-11-07  4:38 UTC (permalink / raw)
  To: wireguard

Signed-off-by: Aurélien Chabot <aurelien@chabot.fr>
---
 wireguard-go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/wireguard-go b/wireguard-go
index 32d8932..96053b4 160000
--- a/wireguard-go
+++ b/wireguard-go
@@ -1 +1 @@
-Subproject commit 32d8932d1b29effa42fe56822988d3888c496873
+Subproject commit 96053b4d07e943b252667ccacb1018768fa1e488
-- 
2.15.0

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 3/4] Add support of wireguard-go as an Android Vpn Service
  2017-11-07  4:38 WireGuard root-less support for android Aurélien Chabot
  2017-11-07  4:38 ` [PATCH 1/4] Add wireguard-go as submodule Aurélien Chabot
  2017-11-07  4:38 ` [PATCH 2/4] Add support for android in wireguard-go Aurélien Chabot
@ 2017-11-07  4:38 ` Aurélien Chabot
  2017-11-07  4:38 ` [PATCH 4/4] Add build instruction Aurélien Chabot
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Aurélien Chabot @ 2017-11-07  4:38 UTC (permalink / raw)
  To: wireguard

Signed-off-by: Aurélien Chabot <aurelien@chabot.fr>
---
 .gitignore                                         |   5 +
 app/build.gradle                                   |   1 +
 app/src/main/AndroidManifest.xml                   |  17 +-
 .../java/com/wireguard/android/AddActivity.java    |   5 +-
 .../com/wireguard/android/AndroidVpnService.java   | 147 +++++++
 .../com/wireguard/android/BaseConfigActivity.java  |  55 ++-
 .../com/wireguard/android/BaseConfigFragment.java  |   2 +-
 .../wireguard/android/BootCompletedReceiver.java   |   2 +-
 .../java/com/wireguard/android/ConfigActivity.java |   4 +-
 .../com/wireguard/android/ConfigEditFragment.java  |   6 +-
 .../com/wireguard/android/ConfigListFragment.java  |   4 +-
 .../wireguard/android/ConfigListPreference.java    |   2 +-
 .../java/com/wireguard/android/ConfigManager.java  | 412 +++++++++++++++++++
 .../com/wireguard/android/KernelVpnService.java    | 115 ++++++
 .../com/wireguard/android/QuickTileService.java    |  30 +-
 .../java/com/wireguard/android/VpnService.java     | 446 +++------------------
 app/src/main/java/com/wireguard/config/Config.java |  12 +-
 .../main/java/com/wireguard/config/Interface.java  |  12 +-
 .../java/com/wireguard/config/IpcAttribute.java    |  55 +++
 .../java/com/wireguard/config/IpcSerializable.java |   9 +
 app/src/main/java/com/wireguard/config/Peer.java   |  21 +-
 .../java/com/wireguard/crypto/KeyEncoding.java     |  12 +
 .../main/java/com/wireguard/crypto/Keypair.java    |   6 +
 app/src/main/res/layout/config_list_item.xml       |   4 +-
 app/src/main/res/values/strings.xml                |   2 +
 app/src/main/res/xml/preferences.xml               |   5 +
 settings.gradle                                    |   2 +-
 wireguardbinding/build.gradle                      |   2 +
 wireguardbinding/src/wireguard                     |   1 +
 .../src/wireguardbinding/tun_android.go            |  61 +++
 wireguardbinding/src/wireguardbinding/wireguard.go |  49 +++
 31 files changed, 1053 insertions(+), 453 deletions(-)
 create mode 100644 app/src/main/java/com/wireguard/android/AndroidVpnService.java
 create mode 100644 app/src/main/java/com/wireguard/android/ConfigManager.java
 create mode 100644 app/src/main/java/com/wireguard/android/KernelVpnService.java
 create mode 100644 app/src/main/java/com/wireguard/config/IpcAttribute.java
 create mode 100644 app/src/main/java/com/wireguard/config/IpcSerializable.java
 create mode 100644 wireguardbinding/build.gradle
 create mode 120000 wireguardbinding/src/wireguard
 create mode 100644 wireguardbinding/src/wireguardbinding/tun_android.go
 create mode 100644 wireguardbinding/src/wireguardbinding/wireguard.go

diff --git a/.gitignore b/.gitignore
index 32babdb..b9e505b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,12 @@
 .DS_Store
 Thumbs.db
 build/
+*.swp
 *.apk
 *.class
 *.dex
 *.iml
+*.a
+*.aar
+wireguardbinding/pkg
+wireguardbinding/src/golang.org
diff --git a/app/build.gradle b/app/build.gradle
index 5caf20b..384c09f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -23,4 +23,5 @@ android {
 
 dependencies {
     compile fileTree(dir: 'libs', include: ['*.jar'])
+    compile project(':wireguardbinding')
 }
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1563d31..7685dc8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,6 +4,7 @@
     android:installLocation="internalOnly">
 
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.INTERNET" />
 
     <application
         android:allowBackup="false"
@@ -48,9 +49,23 @@
                 android:name="android.service.quicksettings.ACTIVE_TILE"
                 android:value="true" />
         </service>
+
+        <service android:name=".ConfigManager" />
+
         <service
-            android:name=".VpnService"
+            android:name=".KernelVpnService"
             android:exported="false" />
+
+        <service
+             android:name=".AndroidVpnService"
+             android:permission="android.permission.BIND_VPN_SERVICE" >
+             <intent-filter>
+                 <action android:name="android.net.VpnService" />
+             </intent-filter>
+         </service>
+
+
+
     </application>
 
 </manifest>
diff --git a/app/src/main/java/com/wireguard/android/AddActivity.java b/app/src/main/java/com/wireguard/android/AddActivity.java
index 080eeca..2cb729d 100644
--- a/app/src/main/java/com/wireguard/android/AddActivity.java
+++ b/app/src/main/java/com/wireguard/android/AddActivity.java
@@ -30,8 +30,9 @@ public class AddActivity extends BaseConfigActivity {
     }
 
     @Override
-    protected void onServiceAvailable() {
-        super.onServiceAvailable();
+    protected void onConfigManagerAvailable() {
+        super.onConfigManagerAvailable();
+
         final FragmentManager fm = getFragmentManager();
         ConfigEditFragment fragment = (ConfigEditFragment) fm.findFragmentById(R.id.master_fragment);
         if (fragment == null) {
diff --git a/app/src/main/java/com/wireguard/android/AndroidVpnService.java b/app/src/main/java/com/wireguard/android/AndroidVpnService.java
new file mode 100644
index 0000000..2a83827
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/AndroidVpnService.java
@@ -0,0 +1,147 @@
+package com.wireguard.android;
+
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import com.wireguard.config.Config;
+
+import wireguardbinding.Wireguardbinding;
+
+public class AndroidVpnService extends android.net.VpnService
+        implements com.wireguard.android.VpnService {
+    private static final String TAG = "AndroidVpnService";
+
+    private static AndroidVpnService instance;
+    public static AndroidVpnService getInstance() {
+        return instance;
+    }
+
+    private final IBinder binder = new Binder();
+    private String enabledConfig;
+
+    @Override
+    public void disable(final String name) {
+        final Config config = ConfigManager.getInstance().get(name);
+        if (config == null || !config.isEnabled())
+            return;
+        new ConfigDisabler(config).execute();
+    }
+
+    @Override
+    public void enable(final String name) {
+        if (enabledConfig != null) // One config at a time
+            return;
+
+        final Config config = ConfigManager.getInstance().get(name);
+        if (config == null || config.isEnabled())
+            return;
+
+        new ConfigEnabler(config).execute();
+    }
+
+    @Override
+    public IBinder onBind(final Intent intent) {
+        instance = this;
+        return binder;
+    }
+
+    @Override
+    public void onRevoke() {
+        if (enabledConfig != null)
+            disable(enabledConfig);
+        stopSelf();
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        // Ensure the service sticks around after being unbound. This only needs to happen once.
+        startService(new Intent(this, getClass()));
+    }
+
+    @Override
+    public int onStartCommand(final Intent intent, final int flags, final int startId) {
+        instance = this;
+        return START_STICKY;
+    }
+
+    private class ConfigDisabler extends AsyncTask<Void, Void, Boolean> {
+        private final Config config;
+
+        private ConfigDisabler(final Config config) {
+            this.config = config;
+        }
+
+        @Override
+        protected Boolean doInBackground(final Void... voids) {
+            Wireguardbinding.stop();
+            return true;
+        }
+
+        @Override
+        protected void onPostExecute(final Boolean result) {
+            if (!result)
+                return;
+            ConfigManager.getInstance().setIsEnable(config.getName(), false);
+            enabledConfig = null;
+        }
+    }
+
+    private class ConfigEnabler extends AsyncTask<Void, Void, Boolean> {
+        private final Config config;
+
+        private ConfigEnabler(final Config config) {
+            this.config = config;
+        }
+
+        @Override
+        protected Boolean doInBackground(final Void... voids) {
+            // Vpn service need to be already ready
+            if(prepare(getBaseContext()) != null)
+                return false;
+
+            Builder builder = new Builder();
+
+            builder.setSession(config.getName());
+            builder.addAddress(config.getInterface().getAddress(), 32);
+            if (config.getInterface().getDns() != null)
+                builder.addDnsServer(config.getInterface().getDns());
+            builder.addRoute("0.0.0.0", 0);
+            builder.setBlocking(true);
+            ParcelFileDescriptor tun = builder.establish();
+            if (tun == null) {
+                Log.d(TAG, "Unable to create tun device");
+                return false;
+            }
+
+            Wireguardbinding.start(tun.detachFd(), config.getName());
+            long socket = 0;
+            while((socket = Wireguardbinding.socket()) == 0) {
+                Log.d(TAG, "Wait for socket");
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            protect((int) socket);
+
+            Wireguardbinding.setConf(config.toIpcString());
+
+            return true;
+        }
+
+        @Override
+        protected void onPostExecute(final Boolean result) {
+            if (!result)
+                return;
+            ConfigManager.getInstance().setIsEnable(config.getName(), true);
+            enabledConfig = config.getName();
+        }
+    }
+}
diff --git a/app/src/main/java/com/wireguard/android/BaseConfigActivity.java b/app/src/main/java/com/wireguard/android/BaseConfigActivity.java
index 17ca3b6..4417063 100644
--- a/app/src/main/java/com/wireguard/android/BaseConfigActivity.java
+++ b/app/src/main/java/com/wireguard/android/BaseConfigActivity.java
@@ -4,7 +4,6 @@ import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
 import android.os.Bundle;
 import android.os.IBinder;
 
@@ -43,12 +42,19 @@ abstract class BaseConfigActivity extends Activity {
             initialConfig = intent.getStringExtra(KEY_CURRENT_CONFIG);
             wasEditing = intent.getBooleanExtra(KEY_IS_EDITING, false);
         }
-        // Trigger starting the service as early as possible
-        if (VpnService.getInstance() != null)
-            onServiceAvailable();
+
+        // Trigger starting the services as early as possible
+        if (ConfigManager.getInstance() != null)
+            onConfigManagerAvailable();
         else
-            bindService(new Intent(this, VpnService.class), new ServiceConnectionCallbacks(),
-                    Context.BIND_AUTO_CREATE);
+            new ConfigManagerConnectionCallbacks(this);
+
+        Intent intent = AndroidVpnService.prepare(this);
+        if (intent != null) {
+            startActivityForResult(intent, 0);
+        } else {
+            onActivityResult(0, RESULT_OK, null);
+        }
     }
 
     protected abstract void onCurrentConfigChanged(Config config);
@@ -63,13 +69,25 @@ abstract class BaseConfigActivity extends Activity {
         outState.putBoolean(KEY_IS_EDITING, isEditing);
     }
 
-    protected void onServiceAvailable() {
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (resultCode == RESULT_OK) {
+            if (VpnService.Singleton.getInstance() != null)
+                onVpnServiceAvailable();
+            else
+                new VpnServiceConnectionCallbacks(this);
+        }
+    }
+
+    protected void onConfigManagerAvailable() {
         // Make sure the subclass activity is initialized before setting its config.
         if (initialConfig != null && currentConfig == null)
-            setCurrentConfig(VpnService.getInstance().get(initialConfig));
+            setCurrentConfig(ConfigManager.getInstance().get(initialConfig));
         setIsEditing(wasEditing);
     }
 
+    protected void onVpnServiceAvailable() {
+    }
+
     public void setCurrentConfig(final Config config) {
         if (currentConfig == config)
             return;
@@ -84,18 +102,25 @@ abstract class BaseConfigActivity extends Activity {
         onEditingStateChanged(isEditing);
     }
 
-    private class ServiceConnectionCallbacks implements ServiceConnection {
+    private class VpnServiceConnectionCallbacks extends VpnService.Singleton.VpnServiceConnection {
+        public VpnServiceConnectionCallbacks(Context ctx) {
+            super(ctx);
+        }
+
         @Override
         public void onServiceConnected(final ComponentName component, final IBinder binder) {
-            // We don't actually need a binding, only notification that the service is started.
-            unbindService(this);
-            onServiceAvailable();
+            onVpnServiceAvailable();
+        }
+    }
+
+    private class ConfigManagerConnectionCallbacks extends ConfigManager.ConfigManagerConnection {
+        public ConfigManagerConnectionCallbacks(Context ctx) {
+            super(ctx);
         }
 
         @Override
-        public void onServiceDisconnected(final ComponentName component) {
-            // This can never happen; the service runs in the same thread as the activity.
-            throw new IllegalStateException();
+        public void onServiceConnected(final ComponentName component, final IBinder binder) {
+            onConfigManagerAvailable();
         }
     }
 }
diff --git a/app/src/main/java/com/wireguard/android/BaseConfigFragment.java b/app/src/main/java/com/wireguard/android/BaseConfigFragment.java
index 4291264..19a45e6 100644
--- a/app/src/main/java/com/wireguard/android/BaseConfigFragment.java
+++ b/app/src/main/java/com/wireguard/android/BaseConfigFragment.java
@@ -30,7 +30,7 @@ abstract class BaseConfigFragment extends Fragment {
         else if (getArguments() != null)
             initialConfig = getArguments().getString(KEY_CURRENT_CONFIG);
         if (initialConfig != null && currentConfig == null)
-            setCurrentConfig(VpnService.getInstance().get(initialConfig));
+            setCurrentConfig(ConfigManager.getInstance().get(initialConfig));
     }
 
     @Override
diff --git a/app/src/main/java/com/wireguard/android/BootCompletedReceiver.java b/app/src/main/java/com/wireguard/android/BootCompletedReceiver.java
index 68cb5f1..49f6530 100644
--- a/app/src/main/java/com/wireguard/android/BootCompletedReceiver.java
+++ b/app/src/main/java/com/wireguard/android/BootCompletedReceiver.java
@@ -10,6 +10,6 @@ public class BootCompletedReceiver extends BroadcastReceiver {
     public void onReceive(final Context context, final Intent intent) {
         if (!intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED))
             return;
-        context.startService(new Intent(context, VpnService.class));
+        context.startService(new Intent(context, ConfigManager.class));
     }
 }
diff --git a/app/src/main/java/com/wireguard/android/ConfigActivity.java b/app/src/main/java/com/wireguard/android/ConfigActivity.java
index 61ccbc1..61f79ce 100644
--- a/app/src/main/java/com/wireguard/android/ConfigActivity.java
+++ b/app/src/main/java/com/wireguard/android/ConfigActivity.java
@@ -222,8 +222,8 @@ public class ConfigActivity extends BaseConfigActivity {
     }
 
     @Override
-    protected void onServiceAvailable() {
-        super.onServiceAvailable();
+    protected void onConfigManagerAvailable() {
+        super.onConfigManagerAvailable();
         // Allow creating fragments.
         isServiceAvailable = true;
         moveToState(getCurrentConfig(), isEditing());
diff --git a/app/src/main/java/com/wireguard/android/ConfigEditFragment.java b/app/src/main/java/com/wireguard/android/ConfigEditFragment.java
index 1edae9c..60d2720 100644
--- a/app/src/main/java/com/wireguard/android/ConfigEditFragment.java
+++ b/app/src/main/java/com/wireguard/android/ConfigEditFragment.java
@@ -113,12 +113,12 @@ public class ConfigEditFragment extends BaseConfigFragment {
     }
 
     private void saveConfig() {
-        final VpnService service = VpnService.getInstance();
+        final ConfigManager configManager = ConfigManager.getInstance();
         try {
             if (getCurrentConfig() != null)
-                service.update(getCurrentConfig().getName(), localConfig);
+                configManager.update(getCurrentConfig().getName(), localConfig);
             else
-                service.add(localConfig);
+                configManager.add(localConfig);
         } catch (final IllegalArgumentException | IllegalStateException e) {
             Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_SHORT).show();
             return;
diff --git a/app/src/main/java/com/wireguard/android/ConfigListFragment.java b/app/src/main/java/com/wireguard/android/ConfigListFragment.java
index 8586a74..c5af9a4 100644
--- a/app/src/main/java/com/wireguard/android/ConfigListFragment.java
+++ b/app/src/main/java/com/wireguard/android/ConfigListFragment.java
@@ -42,7 +42,7 @@ public class ConfigListFragment extends BaseConfigFragment {
                              final Bundle savedInstanceState) {
         final ConfigListFragmentBinding binding =
                 ConfigListFragmentBinding.inflate(inflater, parent, false);
-        binding.setConfigs(VpnService.getInstance().getConfigs());
+        binding.setConfigs(ConfigManager.getInstance().getConfigs());
         listView = binding.configList;
         listView.setMultiChoiceModeListener(new ConfigListModeListener());
         listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@@ -109,7 +109,7 @@ public class ConfigListFragment extends BaseConfigFragment {
                     if (configsToRemove.contains(getCurrentConfig()))
                         setCurrentConfig(null);
                     for (final Config config : configsToRemove)
-                        VpnService.getInstance().remove(config.getName());
+                        ConfigManager.getInstance().remove(config.getName());
                     configsToRemove.clear();
                     mode.finish();
                     return true;
diff --git a/app/src/main/java/com/wireguard/android/ConfigListPreference.java b/app/src/main/java/com/wireguard/android/ConfigListPreference.java
index 3842161..3e0194c 100644
--- a/app/src/main/java/com/wireguard/android/ConfigListPreference.java
+++ b/app/src/main/java/com/wireguard/android/ConfigListPreference.java
@@ -14,7 +14,7 @@ public class ConfigListPreference extends ListPreference {
     public ConfigListPreference(final Context context, final AttributeSet attrs,
                                 final int defStyleAttr, final int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        final Set<String> entrySet = VpnService.getInstance().getConfigs().keySet();
+        final Set<String> entrySet = ConfigManager.getInstance().getConfigs().keySet();
         final CharSequence[] entries = entrySet.toArray(new CharSequence[entrySet.size()]);
         setEntries(entries);
         setEntryValues(entries);
diff --git a/app/src/main/java/com/wireguard/android/ConfigManager.java b/app/src/main/java/com/wireguard/android/ConfigManager.java
new file mode 100644
index 0000000..04d666f
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/ConfigManager.java
@@ -0,0 +1,412 @@
+package com.wireguard.android;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.service.quicksettings.TileService;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import com.wireguard.config.Config;
+import com.wireguard.config.Peer;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+public class ConfigManager extends Service
+    implements SharedPreferences.OnSharedPreferenceChangeListener {
+
+    private static final String TAG = "ConfigManager";
+    public static final String KEY_ENABLED_CONFIGS = "enabled_configs";
+    public static final String KEY_PRIMARY_CONFIG = "primary_config";
+    public static final String KEY_RESTORE_ON_BOOT = "restore_on_boot";
+
+    private final IBinder binder = new Binder();
+    private final Set<String> enabledConfigs = new HashSet<>();
+    private String primaryName;
+    private SharedPreferences preferences;
+
+    private final ObservableTreeMap<String, Config> configurations = new ObservableTreeMap<>();
+
+    private static ConfigManager instance;
+    public static ConfigManager getInstance() {
+        return instance;
+    }
+
+    public static class ConfigManagerConnection implements ServiceConnection {
+
+        private Context ctx;
+
+        public ConfigManagerConnection(Context ctx) {
+            this.ctx = ctx;
+            ctx.bindService(new Intent(ctx, ConfigManager.class), this,
+                Context.BIND_AUTO_CREATE);
+        }
+
+        @Override
+        public void onServiceConnected(final ComponentName component, final IBinder binder) {
+            // We don't actually need a binding, only notification that the service is started.
+            ctx.unbindService(this);
+        }
+
+        @Override
+        public void onServiceDisconnected(final ComponentName component) {
+            // This can never happen; the service runs in the same thread as the activity.
+            throw new IllegalStateException();
+        }
+    }
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        instance = this;
+        return binder;
+    }
+
+    @Override
+    public void onCreate() {
+        // Ensure the service sticks around after being unbound. This only needs to happen once.
+        startService(new Intent(this, getClass()));
+        preferences = PreferenceManager.getDefaultSharedPreferences(this);
+        preferences.registerOnSharedPreferenceChangeListener(this);
+
+        onSharedPreferenceChanged(preferences, VpnService.KEY_USE_KERNEL_MODULE);
+
+        new ConfigLoader().execute(getFilesDir().listFiles(new FilenameFilter() {
+            @Override
+            public boolean accept(final File dir, final String name) {
+                return name.endsWith(".conf");
+            }
+        }));
+    }
+
+    @Override
+    public void onDestroy() {
+        preferences.unregisterOnSharedPreferenceChangeListener(this);
+    }
+
+    @Override
+    public void onSharedPreferenceChanged(final SharedPreferences preferences,
+                                          final String key) {
+        switch (key) {
+            case VpnService.KEY_USE_KERNEL_MODULE: {
+                Log.d(TAG, "Update impl config");
+                if (preferences.getBoolean(VpnService.KEY_USE_KERNEL_MODULE, false))
+                    VpnService.Singleton.setImplementation(VpnService.Singleton.VpnImplementation.KERNEL);
+                else
+                    VpnService.Singleton.setImplementation(VpnService.Singleton.VpnImplementation.ANDROID);
+
+                new VpnService.Singleton.VpnServiceConnection(ConfigManager.this);
+            } break;
+            case KEY_PRIMARY_CONFIG: {
+                boolean changed = false;
+                final String newName = preferences.getString(key, null);
+                if (primaryName != null && !primaryName.equals(newName)) {
+                    final Config oldConfig = get(primaryName);
+                    if (oldConfig != null)
+                        oldConfig.setIsPrimary(false);
+                    changed = true;
+                }
+                if (newName != null && !newName.equals(primaryName)) {
+                    final Config newConfig = get(newName);
+                    if (newConfig != null)
+                        newConfig.setIsPrimary(true);
+                    else
+                        preferences.edit().remove(KEY_PRIMARY_CONFIG).apply();
+                    changed = true;
+                }
+                primaryName = newName;
+                if (changed)
+                    updateTile();
+            } break;
+        }
+    }
+
+    @Override
+    public int onStartCommand(final Intent intent, final int flags, final int startId) {
+        instance = this;
+        return START_STICKY;
+    }
+
+    private void updateTile() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
+            return;
+        Log.v(TAG, "Requesting quick tile update");
+        TileService.requestListeningState(this, new ComponentName(this, QuickTileService.class));
+    }
+
+    /**
+     * Add a new configuration to the set of known configurations. The configuration will initially
+     * be disabled. The configuration's name must be unique within the set of known configurations.
+     *
+     * @param config The configuration to add.
+     */
+    public void add(final Config config) {
+        new ConfigUpdater(null, config).execute();
+    }
+
+    /**
+     * Remove a configuration from being managed by the service.
+     * If successful, the configuration will be removed from persistent storage.
+     * If the configuration is not known to the service, no changes will be made.
+     *
+     * @param name The name of the configuration (in the set of known configurations) to remove.
+     */
+    public void remove(final String name) {
+        final Config config = configurations.get(name);
+        if (config == null)
+            return;
+
+        configurations.remove(name);
+        new ConfigRemover(config).execute();
+    }
+
+    /**
+     * Update the attributes of the named configuration.
+     *
+     * @param name   The name of an existing configuration to update.
+     * @param config A copy of the configuration, with updated attributes.
+     */
+    public void update(final String name, final Config config) {
+        if (name == null)
+            return;
+
+        if (configurations.containsValue(config))
+            throw new IllegalArgumentException("Config " + config.getName() + " modified directly");
+
+        final Config oldConfig = configurations.get(name);
+        if (oldConfig == null)
+            return;
+
+        new ConfigUpdater(oldConfig, config).execute();
+    }
+
+    public void setIsEnable(final String name, final boolean isEnabled) {
+        if (name == null)
+            return;
+
+        Config config = get(name);
+        if (config == null)
+            return;
+        config.setIsEnabled(isEnabled);
+
+        if (isEnabled) {
+            enabledConfigs.add(name);
+            preferences.edit().putStringSet(KEY_ENABLED_CONFIGS, enabledConfigs).apply();
+        } else {
+            enabledConfigs.remove(name);
+            preferences.edit().putStringSet(KEY_ENABLED_CONFIGS, enabledConfigs).apply();
+        }
+
+        if (name.equals(primaryName))
+            updateTile();
+    }
+
+    /**
+     * Retrieve a configuration known and managed by this service. The returned object must not be
+     * modified directly.
+     *
+     * @param name The name of the configuration (in the set of known configurations) to retrieve.
+     * @return An object representing the configuration. This object must not be modified.
+     */
+    public Config get(final String name) {
+        return configurations.get(name);
+    }
+
+    /**
+     * Retrieve the set of configurations known and managed by the service. Configurations in this
+     * set must not be modified directly. If a configuration is to be updated, first create a copy
+     * of it by calling getCopy().
+     *
+     * @return The set of known configurations.
+     */
+    public ObservableSortedMap<String, Config> getConfigs() {
+        return configurations;
+    }
+
+    private class ConfigLoader extends AsyncTask<File, Void, List<Config>> {
+
+        @Override
+        protected List<Config> doInBackground(final File... files) {
+            final List<Config> configs = new LinkedList<>();
+            final List<String> interfaces = new LinkedList<>();
+            for (final File file : files) {
+                if (isCancelled())
+                    return null;
+                final String fileName = file.getName();
+                final String configName = fileName.substring(0, fileName.length() - 5);
+                Log.v(TAG, "Attempting to load config " + configName);
+                try {
+                    final Config config = new Config();
+                    config.parseFrom(openFileInput(fileName));
+                    config.setIsEnabled(interfaces.contains(configName));
+                    config.setName(configName);
+                    configs.add(config);
+                } catch (IllegalArgumentException | IOException e) {
+                    Log.w(TAG, "Failed to load config from " + fileName, e);
+                }
+            }
+            return configs;
+        }
+
+        @Override
+        protected void onPostExecute(final List<Config> configs) {
+            if (configs == null)
+                return;
+            for (final Config config : configs)
+                configurations.put(config.getName(), config);
+
+            // Run the handler to avoid duplicating the code here.
+            onSharedPreferenceChanged(preferences, KEY_PRIMARY_CONFIG);
+
+            if (VpnService.Singleton.getInstance() != null)
+                onVpnServiceAvailable();
+            else
+                new VpnServiceConnectionCallbacks(ConfigManager.this);
+        }
+    }
+
+    protected void onVpnServiceAvailable() {
+        if (preferences.getBoolean(KEY_RESTORE_ON_BOOT, false)) {
+            final Set<String> configsToEnable =
+                preferences.getStringSet(KEY_ENABLED_CONFIGS, null);
+            if (configsToEnable != null) {
+                for (final String name : configsToEnable) {
+                    final Config config = configurations.get(name);
+                    if (config != null && !config.isEnabled())
+                        VpnService.Singleton.getInstance().enable(name);
+                }
+            }
+        }
+    }
+
+    private class VpnServiceConnectionCallbacks extends VpnService.Singleton.VpnServiceConnection {
+        public VpnServiceConnectionCallbacks(Context ctx) {
+            super(ctx);
+        }
+
+        @Override
+        public void onServiceConnected(final ComponentName component, final IBinder binder) {
+            onVpnServiceAvailable();
+        }
+    }
+
+
+    private class ConfigRemover extends AsyncTask<Void, Void, Boolean> {
+        private final Config config;
+
+        private ConfigRemover(final Config config) {
+            this.config = config;
+        }
+
+        @Override
+        protected Boolean doInBackground(final Void... voids) {
+            Log.i(TAG, "Removing config " + config.getName());
+            final File configFile = new File(getFilesDir(), config.getName() + ".conf");
+            if (configFile.delete()) {
+                return true;
+            } else {
+                Log.e(TAG, "Could not delete configuration for config " + config.getName());
+                return false;
+            }
+        }
+
+        @Override
+        protected void onPostExecute(final Boolean result) {
+            if (!result)
+                return;
+            configurations.remove(config.getName());
+            if (config.getName().equals(primaryName)) {
+                // This will get picked up by the preference change listener.
+                preferences.edit().remove(KEY_PRIMARY_CONFIG).apply();
+            }
+        }
+    }
+
+    private class ConfigUpdater extends AsyncTask<Void, Void, Boolean> {
+        private Config knownConfig;
+        private final Config newConfig;
+        private final String newName;
+        private final String oldName;
+
+        private ConfigUpdater(final Config knownConfig, final Config newConfig) {
+            this.knownConfig = knownConfig;
+            this.newConfig = newConfig.copy();
+            this.newConfig.setIsEnabled(this.knownConfig.isEnabled());
+            newName = newConfig.getName();
+            // When adding a config, "old file" and "new file" are the same thing.
+            oldName = knownConfig != null ? knownConfig.getName() : newName;
+            if (newName == null || !Config.isNameValid(newName))
+                throw new IllegalArgumentException("This configuration does not have a valid name");
+            if (isAddOrRename() && configurations.containsKey(newName))
+                throw new IllegalStateException("Configuration " + newName + " already exists");
+            if (newConfig.getInterface().getPublicKey() == null)
+                throw new IllegalArgumentException("This configuration must have a valid keypair");
+            for (final Peer peer : newConfig.getPeers())
+                if (peer.getPublicKey() == null || peer.getPublicKey().isEmpty())
+                    throw new IllegalArgumentException("Each peer must have a valid public key");
+        }
+
+        @Override
+        protected Boolean doInBackground(final Void... voids) {
+            Log.i(TAG, (knownConfig == null ? "Adding" : "Updating") + " config " + newName);
+            final File newFile = new File(getFilesDir(), newName + ".conf");
+            final File oldFile = new File(getFilesDir(), oldName + ".conf");
+            if (isAddOrRename() && newFile.exists()) {
+                Log.w(TAG, "Refusing to overwrite existing config configuration");
+                return false;
+            }
+            try {
+                final FileOutputStream stream = openFileOutput(oldFile.getName(), MODE_PRIVATE);
+                stream.write(newConfig.toString().getBytes(StandardCharsets.UTF_8));
+                stream.close();
+            } catch (final IOException e) {
+                Log.e(TAG, "Could not save configuration for config " + oldName, e);
+                return false;
+            }
+            if (isRename() && !oldFile.renameTo(newFile)) {
+                Log.e(TAG, "Could not rename " + oldFile.getName() + " to " + newFile.getName());
+                return false;
+            }
+            return true;
+        }
+
+        private boolean isAddOrRename() {
+            return knownConfig == null || !newName.equals(oldName);
+        }
+
+        private boolean isRename() {
+            return knownConfig != null && !newName.equals(oldName);
+        }
+
+        @Override
+        protected void onPostExecute(final Boolean result) {
+            if (!result)
+                return;
+            if (knownConfig != null)
+                configurations.remove(oldName);
+            if (knownConfig == null)
+                knownConfig = new Config();
+            knownConfig.copyFrom(newConfig);
+            knownConfig.setIsPrimary(oldName != null && oldName.equals(primaryName));
+            configurations.put(newName, knownConfig);
+            if (isRename() && oldName != null && oldName.equals(primaryName))
+                preferences.edit().putString(KEY_PRIMARY_CONFIG, newName).apply();
+        }
+    }
+}
diff --git a/app/src/main/java/com/wireguard/android/KernelVpnService.java b/app/src/main/java/com/wireguard/android/KernelVpnService.java
new file mode 100644
index 0000000..66fe607
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/KernelVpnService.java
@@ -0,0 +1,115 @@
+package com.wireguard.android;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.wireguard.config.Config;
+
+import java.io.File;
+
+/**
+ * Service that handles config state coordination and all background processing for the application.
+ */
+
+public class KernelVpnService extends Service implements com.wireguard.android.VpnService {
+    private static final String TAG = "KernelVpnService";
+
+    private static KernelVpnService instance;
+    public static KernelVpnService getInstance() {
+        return instance;
+    }
+
+    private final IBinder binder = new Binder();
+    private RootShell rootShell;
+
+    @Override
+    public void disable(final String name) {
+        final Config config = ConfigManager.getInstance().get(name);
+        if (config == null || !config.isEnabled())
+            return;
+
+        new ConfigDisabler(config).execute();
+    }
+
+    @Override
+    public void enable(final String name) {
+        final Config config = ConfigManager.getInstance().get(name);
+        if (config == null || config.isEnabled())
+            return;
+
+        new ConfigEnabler(config).execute();
+    }
+
+    @Override
+    public IBinder onBind(final Intent intent) {
+        instance = this;
+        return binder;
+    }
+
+    @Override
+    public void onCreate() {
+        // Ensure the service sticks around after being unbound. This only needs to happen once.
+        startService(new Intent(this, getClass()));
+        rootShell = new RootShell(this);
+    }
+
+    @Override
+    public void onDestroy() {
+    }
+
+    @Override
+    public int onStartCommand(final Intent intent, final int flags, final int startId) {
+        instance = this;
+        return START_STICKY;
+    }
+
+    private class ConfigDisabler extends AsyncTask<Void, Void, Boolean> {
+        private final Config config;
+
+        private ConfigDisabler(final Config config) {
+            this.config = config;
+        }
+
+        @Override
+        protected Boolean doInBackground(final Void... voids) {
+            Log.i(TAG, "Running wg-quick down for " + config.getName());
+            final File configFile = new File(getFilesDir(), config.getName() + ".conf");
+            return rootShell.run(null, "wg-quick down '" + configFile.getPath() + "'") == 0;
+        }
+
+        @Override
+        protected void onPostExecute(final Boolean result) {
+            if (!result)
+                return;
+
+            ConfigManager.getInstance().setIsEnable(config.getName(), false);
+        }
+    }
+
+    private class ConfigEnabler extends AsyncTask<Void, Void, Boolean> {
+        private final Config config;
+
+        private ConfigEnabler(final Config config) {
+            this.config = config;
+        }
+
+        @Override
+        protected Boolean doInBackground(final Void... voids) {
+            Log.i(TAG, "Running wg-quick up for " + config.getName());
+            final File configFile = new File(getFilesDir(), config.getName() + ".conf");
+            return rootShell.run(null, "wg-quick up '" + configFile.getPath() + "'") == 0;
+        }
+
+        @Override
+        protected void onPostExecute(final Boolean result) {
+            if (!result)
+                return;
+
+            ConfigManager.getInstance().setIsEnable(config.getName(), true);
+        }
+    }
+}
diff --git a/app/src/main/java/com/wireguard/android/QuickTileService.java b/app/src/main/java/com/wireguard/android/QuickTileService.java
index ddadf29..c12a6df 100644
--- a/app/src/main/java/com/wireguard/android/QuickTileService.java
+++ b/app/src/main/java/com/wireguard/android/QuickTileService.java
@@ -3,8 +3,6 @@ package com.wireguard.android;
 import android.annotation.TargetApi;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
 import android.content.SharedPreferences;
 import android.graphics.drawable.Icon;
 import android.os.Build;
@@ -18,11 +16,12 @@ import com.wireguard.config.Config;
 @TargetApi(Build.VERSION_CODES.N)
 public class QuickTileService extends TileService {
     private Config config;
+    private ConfigManager configManager;
     private SharedPreferences preferences;
-    private VpnService service;
 
     @Override
     public void onClick() {
+        VpnService service = VpnService.Singleton.getInstance();
         if (service != null && config != null) {
             if (config.isEnabled())
                 service.disable(config.getName());
@@ -34,10 +33,9 @@ public class QuickTileService extends TileService {
     @Override
     public void onCreate() {
         preferences = PreferenceManager.getDefaultSharedPreferences(this);
-        service = VpnService.getInstance();
-        if (service == null)
-            bindService(new Intent(this, VpnService.class), new ServiceConnectionCallbacks(),
-                    Context.BIND_AUTO_CREATE);
+        configManager = ConfigManager.getInstance();
+        if (configManager == null)
+            new ConfigManagerCallbacks(this);
         TileService.requestListeningState(this, new ComponentName(this, getClass()));
     }
 
@@ -45,8 +43,8 @@ public class QuickTileService extends TileService {
     public void onStartListening() {
         // Since this is an active tile, this only gets called when we want to update the tile.
         final Tile tile = getQsTile();
-        final String configName = preferences.getString(VpnService.KEY_PRIMARY_CONFIG, null);
-        config = configName != null && service != null ? service.get(configName) : null;
+        final String configName = preferences.getString(ConfigManager.KEY_PRIMARY_CONFIG, null);
+        config = configName != null && configManager != null ? configManager.get(configName) : null;
         if (config != null) {
             tile.setLabel(config.getName());
             final int state = config.isEnabled() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
@@ -65,18 +63,14 @@ public class QuickTileService extends TileService {
         tile.updateTile();
     }
 
-    private class ServiceConnectionCallbacks implements ServiceConnection {
-        @Override
-        public void onServiceConnected(final ComponentName component, final IBinder binder) {
-            // We don't actually need a binding, only notification that the service is started.
-            unbindService(this);
-            service = VpnService.getInstance();
+    private class ConfigManagerCallbacks extends ConfigManager.ConfigManagerConnection {
+        public ConfigManagerCallbacks(Context ctx) {
+            super(ctx);
         }
 
         @Override
-        public void onServiceDisconnected(final ComponentName component) {
-            // This can never happen; the service runs in the same thread as this service.
-            throw new IllegalStateException();
+        public void onServiceConnected(final ComponentName component, final IBinder binder) {
+            configManager = ConfigManager.getInstance();
         }
     }
 }
diff --git a/app/src/main/java/com/wireguard/android/VpnService.java b/app/src/main/java/com/wireguard/android/VpnService.java
index 0fd6713..f6e9651 100644
--- a/app/src/main/java/com/wireguard/android/VpnService.java
+++ b/app/src/main/java/com/wireguard/android/VpnService.java
@@ -1,65 +1,63 @@
 package com.wireguard.android;
 
-import android.app.Service;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.AsyncTask;
-import android.os.Binder;
-import android.os.Build;
+import android.content.ServiceConnection;
 import android.os.IBinder;
 import android.preference.PreferenceManager;
-import android.service.quicksettings.TileService;
-import android.util.Log;
 
-import com.wireguard.config.Config;
-import com.wireguard.config.Peer;
+public interface VpnService {
+    String KEY_USE_KERNEL_MODULE = "use_kernel_module";
 
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
+    class Singleton {
+        enum VpnImplementation { KERNEL, ANDROID, UNKNOWN};
+        private static VpnImplementation implementation = VpnImplementation.UNKNOWN;
 
-/**
- * Service that handles config state coordination and all background processing for the application.
- */
+        public synchronized static void setImplementation(VpnImplementation implementation) {
+            Singleton.implementation = implementation;
+        }
 
-public class VpnService extends Service
-        implements SharedPreferences.OnSharedPreferenceChangeListener {
-    public static final String KEY_ENABLED_CONFIGS = "enabled_configs";
-    public static final String KEY_PRIMARY_CONFIG = "primary_config";
-    public static final String KEY_RESTORE_ON_BOOT = "restore_on_boot";
-    private static final String TAG = "VpnService";
+        public synchronized static VpnService getInstance() {
+            switch (implementation) {
+                case ANDROID:
+                    return AndroidVpnService.getInstance();
+                case KERNEL:
+                    return KernelVpnService.getInstance();
+            }
+            return null;
+        }
 
-    private static VpnService instance;
+        public static class VpnServiceConnection implements ServiceConnection {
 
-    public static VpnService getInstance() {
-        return instance;
-    }
+            private Context ctx;
 
-    private final IBinder binder = new Binder();
-    private final ObservableTreeMap<String, Config> configurations = new ObservableTreeMap<>();
-    private final Set<String> enabledConfigs = new HashSet<>();
-    private SharedPreferences preferences;
-    private String primaryName;
-    private RootShell rootShell;
+            public VpnServiceConnection(Context ctx) {
+                this.ctx = ctx;
 
-    /**
-     * Add a new configuration to the set of known configurations. The configuration will initially
-     * be disabled. The configuration's name must be unique within the set of known configurations.
-     *
-     * @param config The configuration to add.
-     */
-    public void add(final Config config) {
-        new ConfigUpdater(null, config, false).execute();
+                if (PreferenceManager.getDefaultSharedPreferences(ctx).getBoolean(KEY_USE_KERNEL_MODULE, false))
+                    ctx.bindService(new Intent(ctx, KernelVpnService.class), this,
+                        Context.BIND_AUTO_CREATE);
+                else
+                    ctx.bindService(new Intent(ctx, AndroidVpnService.class), this,
+                        Context.BIND_AUTO_CREATE);
+            }
+
+            @Override
+            public void onServiceConnected(final ComponentName component, final IBinder binder) {
+                // We don't actually need a binding, only notification that the service is started.
+                ctx.unbindService(this);
+            }
+
+            @Override
+            public void onServiceDisconnected(final ComponentName component) {
+                // This can never happen; the service runs in the same thread as the activity.
+                throw new IllegalStateException();
+            }
+        }
     }
 
+
     /**
      * Attempt to disable and tear down an interface for this configuration. The configuration's
      * enabled state will be updated the operation is successful. If this configuration is already
@@ -67,12 +65,7 @@ public class VpnService extends Service
      *
      * @param name The name of the configuration (in the set of known configurations) to disable.
      */
-    public void disable(final String name) {
-        final Config config = configurations.get(name);
-        if (config == null || !config.isEnabled())
-            return;
-        new ConfigDisabler(config).execute();
-    }
+    void disable(final String name);
 
     /**
      * Attempt to set up and enable an interface for this configuration. The configuration's enabled
@@ -81,352 +74,5 @@ public class VpnService extends Service
      *
      * @param name The name of the configuration (in the set of known configurations) to enable.
      */
-    public void enable(final String name) {
-        final Config config = configurations.get(name);
-        if (config == null || config.isEnabled())
-            return;
-        new ConfigEnabler(config).execute();
-    }
-
-    /**
-     * Retrieve a configuration known and managed by this service. The returned object must not be
-     * modified directly.
-     *
-     * @param name The name of the configuration (in the set of known configurations) to retrieve.
-     * @return An object representing the configuration. This object must not be modified.
-     */
-    public Config get(final String name) {
-        return configurations.get(name);
-    }
-
-    /**
-     * Retrieve the set of configurations known and managed by the service. Configurations in this
-     * set must not be modified directly. If a configuration is to be updated, first create a copy
-     * of it by calling getCopy().
-     *
-     * @return The set of known configurations.
-     */
-    public ObservableSortedMap<String, Config> getConfigs() {
-        return configurations;
-    }
-
-    @Override
-    public IBinder onBind(final Intent intent) {
-        instance = this;
-        return binder;
-    }
-
-    @Override
-    public void onCreate() {
-        // Ensure the service sticks around after being unbound. This only needs to happen once.
-        startService(new Intent(this, getClass()));
-        rootShell = new RootShell(this);
-        new ConfigLoader().execute(getFilesDir().listFiles(new FilenameFilter() {
-            @Override
-            public boolean accept(final File dir, final String name) {
-                return name.endsWith(".conf");
-            }
-        }));
-        preferences = PreferenceManager.getDefaultSharedPreferences(this);
-        preferences.registerOnSharedPreferenceChangeListener(this);
-    }
-
-    @Override
-    public void onDestroy() {
-        preferences.unregisterOnSharedPreferenceChangeListener(this);
-    }
-
-    @Override
-    public void onSharedPreferenceChanged(final SharedPreferences preferences,
-                                          final String key) {
-        if (!KEY_PRIMARY_CONFIG.equals(key))
-            return;
-        boolean changed = false;
-        final String newName = preferences.getString(key, null);
-        if (primaryName != null && !primaryName.equals(newName)) {
-            final Config oldConfig = configurations.get(primaryName);
-            if (oldConfig != null)
-                oldConfig.setIsPrimary(false);
-            changed = true;
-        }
-        if (newName != null && !newName.equals(primaryName)) {
-            final Config newConfig = configurations.get(newName);
-            if (newConfig != null)
-                newConfig.setIsPrimary(true);
-            else
-                preferences.edit().remove(KEY_PRIMARY_CONFIG).apply();
-            changed = true;
-        }
-        primaryName = newName;
-        if (changed)
-            updateTile();
-    }
-
-    @Override
-    public int onStartCommand(final Intent intent, final int flags, final int startId) {
-        instance = this;
-        return START_STICKY;
-    }
-
-    /**
-     * Remove a configuration from being managed by the service. If it is currently enabled, the
-     * the configuration will be disabled before removal. If successful, the configuration will be
-     * removed from persistent storage. If the configuration is not known to the service, no changes
-     * will be made.
-     *
-     * @param name The name of the configuration (in the set of known configurations) to remove.
-     */
-    public void remove(final String name) {
-        final Config config = configurations.get(name);
-        if (config == null)
-            return;
-        if (config.isEnabled())
-            new ConfigDisabler(config).execute();
-        new ConfigRemover(config).execute();
-    }
-
-    /**
-     * Update the attributes of the named configuration. If the configuration is currently enabled,
-     * it will be disabled before the update, and the service will attempt to re-enable it
-     * afterward. If successful, the updated configuration will be saved to persistent storage.
-     *
-     * @param name   The name of an existing configuration to update.
-     * @param config A copy of the configuration, with updated attributes.
-     */
-    public void update(final String name, final Config config) {
-        if (name == null)
-            return;
-        if (configurations.containsValue(config))
-            throw new IllegalArgumentException("Config " + config.getName() + " modified directly");
-        final Config oldConfig = configurations.get(name);
-        if (oldConfig == null)
-            return;
-        final boolean wasEnabled = oldConfig.isEnabled();
-        if (wasEnabled)
-            new ConfigDisabler(oldConfig).execute();
-        new ConfigUpdater(oldConfig, config, wasEnabled).execute();
-    }
-
-    private void updateTile() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
-            return;
-        Log.v(TAG, "Requesting quick tile update");
-        TileService.requestListeningState(this, new ComponentName(this, QuickTileService.class));
-    }
-
-    private class ConfigDisabler extends AsyncTask<Void, Void, Boolean> {
-        private final Config config;
-
-        private ConfigDisabler(final Config config) {
-            this.config = config;
-        }
-
-        @Override
-        protected Boolean doInBackground(final Void... voids) {
-            Log.i(TAG, "Running wg-quick down for " + config.getName());
-            final File configFile = new File(getFilesDir(), config.getName() + ".conf");
-            return rootShell.run(null, "wg-quick down '" + configFile.getPath() + "'") == 0;
-        }
-
-        @Override
-        protected void onPostExecute(final Boolean result) {
-            if (!result)
-                return;
-            config.setIsEnabled(false);
-            enabledConfigs.remove(config.getName());
-            preferences.edit().putStringSet(KEY_ENABLED_CONFIGS, enabledConfigs).apply();
-            if (config.getName().equals(primaryName))
-                updateTile();
-        }
-    }
-
-    private class ConfigEnabler extends AsyncTask<Void, Void, Boolean> {
-        private final Config config;
-
-        private ConfigEnabler(final Config config) {
-            this.config = config;
-        }
-
-        @Override
-        protected Boolean doInBackground(final Void... voids) {
-            Log.i(TAG, "Running wg-quick up for " + config.getName());
-            final File configFile = new File(getFilesDir(), config.getName() + ".conf");
-            return rootShell.run(null, "wg-quick up '" + configFile.getPath() + "'") == 0;
-        }
-
-        @Override
-        protected void onPostExecute(final Boolean result) {
-            if (!result)
-                return;
-            config.setIsEnabled(true);
-            enabledConfigs.add(config.getName());
-            preferences.edit().putStringSet(KEY_ENABLED_CONFIGS, enabledConfigs).apply();
-            if (config.getName().equals(primaryName))
-                updateTile();
-        }
-    }
-
-    private class ConfigLoader extends AsyncTask<File, Void, List<Config>> {
-        @Override
-        protected List<Config> doInBackground(final File... files) {
-            final List<Config> configs = new LinkedList<>();
-            final List<String> interfaces = new LinkedList<>();
-            final String command = "wg show interfaces";
-            if (rootShell.run(interfaces, command) == 0 && interfaces.size() == 1) {
-                // wg puts all interface names on the same line. Split them into separate elements.
-                final String nameList = interfaces.get(0);
-                Collections.addAll(interfaces, nameList.split(" "));
-                interfaces.remove(0);
-            } else {
-                interfaces.clear();
-                Log.w(TAG, "No existing WireGuard interfaces found. Maybe they are all disabled?");
-            }
-            for (final File file : files) {
-                if (isCancelled())
-                    return null;
-                final String fileName = file.getName();
-                final String configName = fileName.substring(0, fileName.length() - 5);
-                Log.v(TAG, "Attempting to load config " + configName);
-                try {
-                    final Config config = new Config();
-                    config.parseFrom(openFileInput(fileName));
-                    config.setIsEnabled(interfaces.contains(configName));
-                    config.setName(configName);
-                    configs.add(config);
-                } catch (IllegalArgumentException | IOException e) {
-                    Log.w(TAG, "Failed to load config from " + fileName, e);
-                }
-            }
-            return configs;
-        }
-
-        @Override
-        protected void onPostExecute(final List<Config> configs) {
-            if (configs == null)
-                return;
-            for (final Config config : configs)
-                configurations.put(config.getName(), config);
-            // Run the handler to avoid duplicating the code here.
-            onSharedPreferenceChanged(preferences, KEY_PRIMARY_CONFIG);
-            if (preferences.getBoolean(KEY_RESTORE_ON_BOOT, false)) {
-                final Set<String> configsToEnable =
-                        preferences.getStringSet(KEY_ENABLED_CONFIGS, null);
-                if (configsToEnable != null) {
-                    for (final String name : configsToEnable) {
-                        final Config config = configurations.get(name);
-                        if (config != null && !config.isEnabled())
-                            new ConfigEnabler(config).execute();
-                    }
-                }
-            }
-        }
-    }
-
-    private class ConfigRemover extends AsyncTask<Void, Void, Boolean> {
-        private final Config config;
-
-        private ConfigRemover(final Config config) {
-            this.config = config;
-        }
-
-        @Override
-        protected Boolean doInBackground(final Void... voids) {
-            Log.i(TAG, "Removing config " + config.getName());
-            final File configFile = new File(getFilesDir(), config.getName() + ".conf");
-            if (configFile.delete()) {
-                return true;
-            } else {
-                Log.e(TAG, "Could not delete configuration for config " + config.getName());
-                return false;
-            }
-        }
-
-        @Override
-        protected void onPostExecute(final Boolean result) {
-            if (!result)
-                return;
-            configurations.remove(config.getName());
-            if (config.getName().equals(primaryName)) {
-                // This will get picked up by the preference change listener.
-                preferences.edit().remove(KEY_PRIMARY_CONFIG).apply();
-            }
-        }
-    }
-
-    private class ConfigUpdater extends AsyncTask<Void, Void, Boolean> {
-        private Config knownConfig;
-        private final Config newConfig;
-        private final String newName;
-        private final String oldName;
-        private final Boolean shouldConnect;
-
-        private ConfigUpdater(final Config knownConfig, final Config newConfig,
-                              final Boolean shouldConnect) {
-            this.knownConfig = knownConfig;
-            this.newConfig = newConfig.copy();
-            newName = newConfig.getName();
-            // When adding a config, "old file" and "new file" are the same thing.
-            oldName = knownConfig != null ? knownConfig.getName() : newName;
-            this.shouldConnect = shouldConnect;
-            if (newName == null || !Config.isNameValid(newName))
-                throw new IllegalArgumentException("This configuration does not have a valid name");
-            if (isAddOrRename() && configurations.containsKey(newName))
-                throw new IllegalStateException("Configuration " + newName + " already exists");
-            if (newConfig.getInterface().getPublicKey() == null)
-                throw new IllegalArgumentException("This configuration must have a valid keypair");
-            for (final Peer peer : newConfig.getPeers())
-                if (peer.getPublicKey() == null || peer.getPublicKey().isEmpty())
-                    throw new IllegalArgumentException("Each peer must have a valid public key");
-        }
-
-        @Override
-        protected Boolean doInBackground(final Void... voids) {
-            Log.i(TAG, (knownConfig == null ? "Adding" : "Updating") + " config " + newName);
-            final File newFile = new File(getFilesDir(), newName + ".conf");
-            final File oldFile = new File(getFilesDir(), oldName + ".conf");
-            if (isAddOrRename() && newFile.exists()) {
-                Log.w(TAG, "Refusing to overwrite existing config configuration");
-                return false;
-            }
-            try {
-                final FileOutputStream stream = openFileOutput(oldFile.getName(), MODE_PRIVATE);
-                stream.write(newConfig.toString().getBytes(StandardCharsets.UTF_8));
-                stream.close();
-            } catch (final IOException e) {
-                Log.e(TAG, "Could not save configuration for config " + oldName, e);
-                return false;
-            }
-            if (isRename() && !oldFile.renameTo(newFile)) {
-                Log.e(TAG, "Could not rename " + oldFile.getName() + " to " + newFile.getName());
-                return false;
-            }
-            return true;
-        }
-
-        private boolean isAddOrRename() {
-            return knownConfig == null || !newName.equals(oldName);
-        }
-
-        private boolean isRename() {
-            return knownConfig != null && !newName.equals(oldName);
-        }
-
-        @Override
-        protected void onPostExecute(final Boolean result) {
-            if (!result)
-                return;
-            if (knownConfig != null)
-                configurations.remove(oldName);
-            if (knownConfig == null)
-                knownConfig = new Config();
-            knownConfig.copyFrom(newConfig);
-            knownConfig.setIsEnabled(false);
-            knownConfig.setIsPrimary(oldName != null && oldName.equals(primaryName));
-            configurations.put(newName, knownConfig);
-            if (isRename() && oldName != null && oldName.equals(primaryName))
-                preferences.edit().putString(KEY_PRIMARY_CONFIG, newName).apply();
-            if (shouldConnect)
-                new ConfigEnabler(knownConfig).execute();
-        }
-    }
+    void enable(final String name);
 }
diff --git a/app/src/main/java/com/wireguard/config/Config.java b/app/src/main/java/com/wireguard/config/Config.java
index 2a282d0..c69b2f5 100644
--- a/app/src/main/java/com/wireguard/config/Config.java
+++ b/app/src/main/java/com/wireguard/config/Config.java
@@ -25,7 +25,7 @@ import java.util.regex.Pattern;
  */
 
 public class Config extends BaseObservable
-        implements Comparable<Config>, Copyable<Config>, Observable, Parcelable {
+        implements Comparable<Config>, Copyable<Config>, Observable, Parcelable, IpcSerializable {
     public static final Parcelable.Creator<Config> CREATOR = new Parcelable.Creator<Config>() {
         @Override
         public Config createFromParcel(final Parcel in) {
@@ -186,4 +186,14 @@ public class Config extends BaseObservable
         dest.writeString(name);
         dest.writeTypedList(peers);
     }
+
+    @Override
+    public String toIpcString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(iface.toIpcString());
+        sb.append("replace_peers=true\n");
+        for (final Peer peer : peers)
+            sb.append(peer.toIpcString());
+        return sb.toString();
+    }
 }
diff --git a/app/src/main/java/com/wireguard/config/Interface.java b/app/src/main/java/com/wireguard/config/Interface.java
index b291844..0d8246c 100644
--- a/app/src/main/java/com/wireguard/config/Interface.java
+++ b/app/src/main/java/com/wireguard/config/Interface.java
@@ -15,7 +15,7 @@ import com.wireguard.crypto.Keypair;
  */
 
 public class Interface extends BaseObservable
-        implements Copyable<Interface>, Observable, Parcelable {
+        implements Copyable<Interface>, Observable, Parcelable, IpcSerializable {
     public static final Parcelable.Creator<Interface> CREATOR
             = new Parcelable.Creator<Interface>() {
         @Override
@@ -200,4 +200,14 @@ public class Interface extends BaseObservable
         dest.writeString(mtu);
         dest.writeString(privateKey);
     }
+
+    @Override
+    public String toIpcString() {
+        final StringBuilder sb = new StringBuilder();
+        if (listenPort != null)
+            sb.append(IpcAttribute.LISTEN_PORT.composeWith(listenPort));
+        if (privateKey != null)
+            sb.append(IpcAttribute.PRIVATE_KEY.composeWith(keypair.getPrivateKeyHex()));
+        return sb.toString();
+    }
 }
diff --git a/app/src/main/java/com/wireguard/config/IpcAttribute.java b/app/src/main/java/com/wireguard/config/IpcAttribute.java
new file mode 100644
index 0000000..a4a030a
--- /dev/null
+++ b/app/src/main/java/com/wireguard/config/IpcAttribute.java
@@ -0,0 +1,55 @@
+package com.wireguard.config;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The set of valid attributes for an interface or peer over WireGuard IPC configuration channel.
+ */
+
+enum IpcAttribute {
+    ALLOWED_IPS("allowed_ip"),
+    ENDPOINT("endpoint"),
+    LISTEN_PORT("listen_port"),
+    PERSISTENT_KEEPALIVE("persistent_keepalive_interval"),
+    PRE_SHARED_KEY("preshared_key"),
+    PRIVATE_KEY("private_key"),
+    PUBLIC_KEY("public_key");
+
+    private static final Map<String, IpcAttribute> map;
+
+    static {
+        map = new HashMap<>(IpcAttribute.values().length);
+        for (final IpcAttribute key : IpcAttribute.values())
+            map.put(key.getToken(), key);
+    }
+
+    public static IpcAttribute match(final String line) {
+        return map.get(line.split("\\s|=")[0]);
+    }
+
+    private final String token;
+    private final Pattern pattern;
+
+    IpcAttribute(final String token) {
+        pattern = Pattern.compile(token + "\\s*=\\s*(\\S.*)");
+        this.token = token;
+    }
+
+    public String composeWith(final String value) {
+        return token + "=" + value + "\n";
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public String parseFrom(final String line) {
+        final Matcher matcher = pattern.matcher(line);
+        if (matcher.matches())
+            return matcher.group(1);
+        return null;
+    }
+}
diff --git a/app/src/main/java/com/wireguard/config/IpcSerializable.java b/app/src/main/java/com/wireguard/config/IpcSerializable.java
new file mode 100644
index 0000000..46dfea0
--- /dev/null
+++ b/app/src/main/java/com/wireguard/config/IpcSerializable.java
@@ -0,0 +1,9 @@
+package com.wireguard.config;
+
+/**
+ * Interface for classes that can perform a serialization for the ipc link
+ */
+
+public interface IpcSerializable {
+    String toIpcString();
+}
diff --git a/app/src/main/java/com/wireguard/config/Peer.java b/app/src/main/java/com/wireguard/config/Peer.java
index 718a5c3..ce55bb2 100644
--- a/app/src/main/java/com/wireguard/config/Peer.java
+++ b/app/src/main/java/com/wireguard/config/Peer.java
@@ -7,12 +7,13 @@ import android.os.Parcel;
 import android.os.Parcelable;
 
 import com.android.databinding.library.baseAdapters.BR;
+import com.wireguard.crypto.KeyEncoding;
 
 /**
  * Represents the configuration for a WireGuard peer (a [Peer] block).
  */
 
-public class Peer extends BaseObservable implements Copyable<Peer>, Observable, Parcelable {
+public class Peer extends BaseObservable implements Copyable<Peer>, Observable, Parcelable, IpcSerializable {
     public static final Parcelable.Creator<Peer> CREATOR = new Parcelable.Creator<Peer>() {
         @Override
         public Peer createFromParcel(final Parcel in) {
@@ -175,4 +176,22 @@ public class Peer extends BaseObservable implements Copyable<Peer>, Observable,
         dest.writeString(preSharedKey);
         dest.writeString(publicKey);
     }
+
+    @Override
+    public String toIpcString() {
+        final StringBuilder sb = new StringBuilder();
+        if (publicKey != null)
+            sb.append(IpcAttribute.PUBLIC_KEY.composeWith(KeyEncoding.keyToHex(KeyEncoding.keyFromBase64(publicKey))));
+        if (endpoint != null)
+            sb.append(IpcAttribute.ENDPOINT.composeWith(endpoint));
+        if (persistentKeepalive != null)
+            sb.append(IpcAttribute.PERSISTENT_KEEPALIVE.composeWith(persistentKeepalive));
+        if (allowedIPs != null) {
+            sb.append("replace_allowed_ips=true\n");
+            sb.append(IpcAttribute.ALLOWED_IPS.composeWith(allowedIPs));
+        }
+        if (preSharedKey != null)
+            sb.append(IpcAttribute.PRE_SHARED_KEY.composeWith(preSharedKey));
+        return sb.toString();
+    }
 }
diff --git a/app/src/main/java/com/wireguard/crypto/KeyEncoding.java b/app/src/main/java/com/wireguard/crypto/KeyEncoding.java
index f83fd0b..2e48efd 100644
--- a/app/src/main/java/com/wireguard/crypto/KeyEncoding.java
+++ b/app/src/main/java/com/wireguard/crypto/KeyEncoding.java
@@ -88,4 +88,16 @@ public class KeyEncoding {
         output[KEY_LENGTH_BASE64 - 1] = '=';
         return new String(output);
     }
+
+    private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
+
+    public static String keyToHex(final byte[] bytes) {
+        char[] hexChars = new char[bytes.length * 2];
+        for ( int j = 0; j < bytes.length; j++ ) {
+            int v = bytes[j] & 0xFF;
+            hexChars[j * 2] = hexArray[v >>> 4];
+            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+        }
+        return new String(hexChars);
+    }
 }
diff --git a/app/src/main/java/com/wireguard/crypto/Keypair.java b/app/src/main/java/com/wireguard/crypto/Keypair.java
index e0d35d6..a8b1951 100644
--- a/app/src/main/java/com/wireguard/crypto/Keypair.java
+++ b/app/src/main/java/com/wireguard/crypto/Keypair.java
@@ -42,8 +42,14 @@ public class Keypair {
     public String getPrivateKey() {
         return KeyEncoding.keyToBase64(privateKey);
     }
+    public String getPrivateKeyHex() {
+        return KeyEncoding.keyToHex(privateKey);
+    }
 
     public String getPublicKey() {
         return KeyEncoding.keyToBase64(publicKey);
     }
+    public String getPublicKeyHex() {
+        return KeyEncoding.keyToHex(publicKey);
+    }
 }
diff --git a/app/src/main/res/layout/config_list_item.xml b/app/src/main/res/layout/config_list_item.xml
index d15d48d..dcc40ad 100644
--- a/app/src/main/res/layout/config_list_item.xml
+++ b/app/src/main/res/layout/config_list_item.xml
@@ -2,9 +2,7 @@
 <layout xmlns:android="http://schemas.android.com/apk/res/android">
 
     <data>
-
         <import type="android.graphics.Typeface" />
-
         <import type="com.wireguard.android.VpnService" />
 
         <variable
@@ -38,7 +36,7 @@
             android:layout_alignBaseline="@+id/config_name"
             android:layout_alignParentEnd="true"
             android:gravity="center_vertical"
-            android:onClick="@{() -> item.enabled ? VpnService.instance.disable(item.name) : VpnService.instance.enable(item.name)}"
+            android:onClick="@{() -> item.enabled ? VpnService.Singleton.getInstance().disable(item.name) : VpnService.Singleton.getInstance().enable(item.name)}"
             android:text="@string/toggle"
             android:textColor="@{item.enabled ? @android:color/holo_green_dark : @android:color/holo_red_dark}" />
     </RelativeLayout>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bc2ec7a..deb4eee 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -38,6 +38,8 @@
     <string name="public_key_description">WireGuard public key</string>
     <string name="restore_on_boot">Restore on boot</string>
     <string name="restore_on_boot_summary">Restore previously enabled configurations on boot</string>
+    <string name="use_kernel_module">Use the WireGuard kernel implementation</string>
+    <string name="use_kernel_module_summary">Require a kernel with WireGuard support and root right</string>
     <string name="save">Save</string>
     <string name="settings">Settings</string>
     <string name="status">Status</string>
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 95683a1..3d9dc96 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -9,4 +9,9 @@
         android:key="restore_on_boot"
         android:summary="@string/restore_on_boot_summary"
         android:title="@string/restore_on_boot" />
+    <CheckBoxPreference
+        android:defaultValue="false"
+        android:key="use_kernel_module"
+        android:summary="@string/use_kernel_module_summary"
+        android:title="@string/use_kernel_module" />
 </PreferenceScreen>
diff --git a/settings.gradle b/settings.gradle
index e7b4def..9f20b46 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include ':app'
+include ':app', ':wireguardbinding'
diff --git a/wireguardbinding/build.gradle b/wireguardbinding/build.gradle
new file mode 100644
index 0000000..52b2613
--- /dev/null
+++ b/wireguardbinding/build.gradle
@@ -0,0 +1,2 @@
+configurations.maybeCreate("default")
+artifacts.add("default", file('wireguardbinding.aar'))
diff --git a/wireguardbinding/src/wireguard b/wireguardbinding/src/wireguard
new file mode 120000
index 0000000..0090783
--- /dev/null
+++ b/wireguardbinding/src/wireguard
@@ -0,0 +1 @@
+../../wireguard-go/src/wireguard
\ No newline at end of file
diff --git a/wireguardbinding/src/wireguardbinding/tun_android.go b/wireguardbinding/src/wireguardbinding/tun_android.go
new file mode 100644
index 0000000..853d6da
--- /dev/null
+++ b/wireguardbinding/src/wireguardbinding/tun_android.go
@@ -0,0 +1,61 @@
+package wireguardbinding
+
+/* Implementation of the TUN device interface for android
+ */
+
+import (
+	"os"
+	"wireguard"
+)
+
+type NativeTun struct {
+	fd   *os.File
+	name string
+	events chan wireguard.TUNEvent
+}
+
+func (tun *NativeTun) Name() string {
+	return tun.name
+}
+
+func (tun *NativeTun) setMTU(n int) error {
+	return nil
+}
+
+func (tun *NativeTun) MTU() (int, error) {
+	return wireguard.DefaultMTU, nil
+}
+
+func (tun *NativeTun) Write(d []byte) (int, error) {
+	return tun.fd.Write(d)
+}
+
+func (tun *NativeTun) Read(d []byte) (int, error) {
+	return tun.fd.Read(d)
+}
+
+func (tun *NativeTun) Events() chan wireguard.TUNEvent {
+	return tun.events
+}
+
+func (tun *NativeTun) Close() error {
+	return tun.fd.Close()
+}
+
+func CreateTUN(fdint int, name string) (wireguard.TUNDevice, error) {
+
+	fd := os.NewFile(uintptr(fdint), name)
+	if fd == nil {
+		return nil, nil
+	}
+
+	device := &NativeTun{
+		fd:   fd,
+		name: name,
+		events: make(chan wireguard.TUNEvent, 5),
+	}
+
+	device.events <- wireguard.TUNEventUp
+
+	return device, nil
+}
diff --git a/wireguardbinding/src/wireguardbinding/wireguard.go b/wireguardbinding/src/wireguardbinding/wireguard.go
new file mode 100644
index 0000000..3103674
--- /dev/null
+++ b/wireguardbinding/src/wireguardbinding/wireguard.go
@@ -0,0 +1,49 @@
+package wireguardbinding
+
+import (
+	"log"
+	"wireguard"
+	"bufio"
+	"strings"
+)
+
+var device *wireguard.Device
+
+func Start(fd int, name string) {
+	log.Println("Start")
+
+	tun, err := CreateTUN(fd, name)
+	if err != nil {
+		log.Println("Failed to create tun device: ", err)
+		return
+	}
+
+	log.Println("NAME ", tun.Name())
+
+	device = wireguard.NewDevice(tun, wireguard.LogLevelDebug)
+}
+
+func Socket() int {
+	log.Println("Socket")
+	fd, _ := wireguard.GetUDPConn(device)
+	return int(fd)
+}
+
+func Stop() {
+	log.Println("Stop")
+	device.Close()
+	device = nil
+}
+
+func SetConf(conf string) {
+	log.Println("SetConf")
+	scanner := bufio.NewScanner(strings.NewReader(conf))
+	wireguard.SetOperation(device, scanner)
+}
+
+func GetConf() []string {
+	log.Println("GetConf")
+	var conf []string
+	wireguard.GetOperation(device, conf)
+	return conf
+}
-- 
2.15.0

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 4/4] Add build instruction
  2017-11-07  4:38 WireGuard root-less support for android Aurélien Chabot
                   ` (2 preceding siblings ...)
  2017-11-07  4:38 ` [PATCH 3/4] Add support of wireguard-go as an Android Vpn Service Aurélien Chabot
@ 2017-11-07  4:38 ` Aurélien Chabot
  2017-11-07  4:38 ` [PATCH 1/2] Put the code in a 'wireguard' go package Aurélien Chabot
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Aurélien Chabot @ 2017-11-07  4:38 UTC (permalink / raw)
  To: wireguard

Signed-off-by: Aurélien Chabot <aurelien@chabot.fr>
---
 build.sh | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
 create mode 100755 build.sh

diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..7fbc2b6
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+cd wireguardbinding
+
+export GOPATH=`pwd`
+export PATH=$PATH:`pwd`/bin
+
+go get golang.org/x/mobile/cmd/gomobile
+go get wireguard
+gomobile init
+gomobile bind -target=android wireguardbinding
+
+cd ..
+
+./gradlew assembleDebug
+
-- 
2.15.0

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 1/2] Put the code in a 'wireguard' go package
  2017-11-07  4:38 WireGuard root-less support for android Aurélien Chabot
                   ` (3 preceding siblings ...)
  2017-11-07  4:38 ` [PATCH 4/4] Add build instruction Aurélien Chabot
@ 2017-11-07  4:38 ` Aurélien Chabot
  2017-11-07  4:38 ` [PATCH 2/2] Add android support Aurélien Chabot
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Aurélien Chabot @ 2017-11-07  4:38 UTC (permalink / raw)
  To: wireguard

Signed-off-by: Aurélien Chabot <aurelien@chabot.fr>
---
 src/Makefile                            |  2 +-
 src/main.go                             | 23 ++++++++++++-----------
 src/{ => wireguard}/conn.go             |  2 +-
 src/{ => wireguard}/conn_default.go     |  2 +-
 src/{ => wireguard}/conn_linux.go       |  2 +-
 src/{ => wireguard}/constants.go        |  2 +-
 src/{ => wireguard}/cookie.go           |  2 +-
 src/{ => wireguard}/cookie_test.go      |  2 +-
 src/{ => wireguard}/daemon_darwin.go    |  2 +-
 src/{ => wireguard}/daemon_linux.go     |  2 +-
 src/{ => wireguard}/daemon_windows.go   |  2 +-
 src/{ => wireguard}/device.go           |  6 +++---
 src/{ => wireguard}/helper_test.go      |  2 +-
 src/{ => wireguard}/index.go            |  2 +-
 src/{ => wireguard}/ip.go               |  2 +-
 src/{ => wireguard}/kdf_test.go         |  2 +-
 src/{ => wireguard}/keypair.go          |  2 +-
 src/{ => wireguard}/logger.go           |  2 +-
 src/{ => wireguard}/misc.go             |  2 +-
 src/{ => wireguard}/noise_helpers.go    |  2 +-
 src/{ => wireguard}/noise_protocol.go   |  4 ++--
 src/{ => wireguard}/noise_test.go       |  2 +-
 src/{ => wireguard}/noise_types.go      |  2 +-
 src/{ => wireguard}/peer.go             |  2 +-
 src/{ => wireguard}/ratelimiter.go      |  2 +-
 src/{ => wireguard}/ratelimiter_test.go |  2 +-
 src/{ => wireguard}/receive.go          | 18 +++++++++---------
 src/{ => wireguard}/replay.go           |  2 +-
 src/{ => wireguard}/replay_test.go      |  2 +-
 src/{ => wireguard}/routing.go          |  2 +-
 src/{ => wireguard}/send.go             | 12 ++++++------
 src/{ => wireguard}/tai64.go            |  2 +-
 src/{ => wireguard}/timers.go           | 12 ++++++------
 src/{ => wireguard}/trie.go             |  2 +-
 src/{ => wireguard}/trie_rand_test.go   |  2 +-
 src/{ => wireguard}/trie_test.go        |  2 +-
 src/{ => wireguard}/tun.go              |  6 +++---
 src/{ => wireguard}/tun_darwin.go       |  2 +-
 src/{ => wireguard}/tun_linux.go        |  2 +-
 src/{ => wireguard}/tun_windows.go      |  2 +-
 src/{ => wireguard}/uapi.go             | 18 +++++++++---------
 src/{ => wireguard}/uapi_darwin.go      |  2 +-
 src/{ => wireguard}/uapi_linux.go       |  2 +-
 src/{ => wireguard}/uapi_windows.go     |  2 +-
 src/{ => wireguard}/xchacha20.go        |  2 +-
 src/{ => wireguard}/xchacha20_test.go   |  2 +-
 46 files changed, 88 insertions(+), 87 deletions(-)
 rename src/{ => wireguard}/conn.go (98%)
 rename src/{ => wireguard}/conn_default.go (85%)
 rename src/{ => wireguard}/conn_linux.go (99%)
 rename src/{ => wireguard}/constants.go (98%)
 rename src/{ => wireguard}/cookie.go (99%)
 rename src/{ => wireguard}/cookie_test.go (99%)
 rename src/{ => wireguard}/daemon_darwin.go (84%)
 rename src/{ => wireguard}/daemon_linux.go (96%)
 rename src/{ => wireguard}/daemon_windows.go (90%)
 rename src/{ => wireguard}/device.go (97%)
 rename src/{ => wireguard}/helper_test.go (98%)
 rename src/{ => wireguard}/index.go (98%)
 rename src/{ => wireguard}/ip.go (93%)
 rename src/{ => wireguard}/kdf_test.go (99%)
 rename src/{ => wireguard}/keypair.go (97%)
 rename src/{ => wireguard}/logger.go (97%)
 rename src/{ => wireguard}/misc.go (98%)
 rename src/{ => wireguard}/noise_helpers.go (99%)
 rename src/{ => wireguard}/noise_protocol.go (99%)
 rename src/{ => wireguard}/noise_test.go (99%)
 rename src/{ => wireguard}/noise_types.go (98%)
 rename src/{ => wireguard}/peer.go (99%)
 rename src/{ => wireguard}/ratelimiter.go (99%)
 rename src/{ => wireguard}/ratelimiter_test.go (99%)
 rename src/{ => wireguard}/receive.go (97%)
 rename src/{ => wireguard}/replay.go (98%)
 rename src/{ => wireguard}/replay_test.go (99%)
 rename src/{ => wireguard}/routing.go (98%)
 rename src/{ => wireguard}/send.go (97%)
 rename src/{ => wireguard}/tai64.go (96%)
 rename src/{ => wireguard}/timers.go (93%)
 rename src/{ => wireguard}/trie.go (99%)
 rename src/{ => wireguard}/trie_rand_test.go (99%)
 rename src/{ => wireguard}/trie_test.go (99%)
 rename src/{ => wireguard}/tun.go (95%)
 rename src/{ => wireguard}/tun_darwin.go (99%)
 rename src/{ => wireguard}/tun_linux.go (99%)
 rename src/{ => wireguard}/tun_windows.go (99%)
 rename src/{ => wireguard}/uapi.go (95%)
 rename src/{ => wireguard}/uapi_darwin.go (98%)
 rename src/{ => wireguard}/uapi_linux.go (99%)
 rename src/{ => wireguard}/uapi_windows.go (98%)
 rename src/{ => wireguard}/xchacha20.go (99%)
 rename src/{ => wireguard}/xchacha20_test.go (99%)

diff --git a/src/Makefile b/src/Makefile
index 5b23ecc..5f47aa7 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1,6 +1,6 @@
 all: wireguard-go
 
-wireguard-go: $(wildcard *.go)
+wireguard-go: main.go $(wildcard wireguard/*.go)
 	go build -o $@
 
 clean:
diff --git a/src/main.go b/src/main.go
index 196a4c6..9800c46 100644
--- a/src/main.go
+++ b/src/main.go
@@ -6,6 +6,7 @@ import (
 	"os"
 	"os/signal"
 	"runtime"
+	"wireguard"
 )
 
 func printUsage() {
@@ -46,7 +47,7 @@ func main() {
 	// daemonize the process
 
 	if !foreground {
-		err := Daemonize()
+		err := wireguard.Daemonize()
 		if err != nil {
 			log.Println("Failed to daemonize:", err)
 		}
@@ -59,7 +60,7 @@ func main() {
 
 	// open TUN device
 
-	tun, err := CreateTUN(interfaceName)
+	tun, err := wireguard.CreateTUN(interfaceName)
 	if err != nil {
 		log.Println("Failed to create tun device:", err)
 		return
@@ -70,26 +71,26 @@ func main() {
 	logLevel := func() int {
 		switch os.Getenv("LOG_LEVEL") {
 		case "debug":
-			return LogLevelDebug
+			return wireguard.LogLevelDebug
 		case "info":
-			return LogLevelInfo
+			return wireguard.LogLevelInfo
 		case "error":
-			return LogLevelError
+			return wireguard.LogLevelError
 		}
-		return LogLevelInfo
+		return wireguard.LogLevelInfo
 	}()
 
 	// create wireguard device
 
-	device := NewDevice(tun, logLevel)
+	device := wireguard.NewDevice(tun, logLevel)
 
-	logInfo := device.log.Info
-	logError := device.log.Error
+	logInfo := device.Log.Info
+	logError := device.Log.Error
 	logInfo.Println("Starting device")
 
 	// start configuration lister
 
-	uapi, err := NewUAPIListener(interfaceName)
+	uapi, err := wireguard.NewUAPIListener(interfaceName)
 	if err != nil {
 		logError.Fatal("UAPI listen error:", err)
 	}
@@ -105,7 +106,7 @@ func main() {
 				errs <- err
 				return
 			}
-			go ipcHandle(device, conn)
+			go wireguard.IpcHandle(device, conn)
 		}
 	}()
 
diff --git a/src/conn.go b/src/wireguard/conn.go
similarity index 98%
rename from src/conn.go
rename to src/wireguard/conn.go
index 2cf588d..89b79ba 100644
--- a/src/conn.go
+++ b/src/wireguard/conn.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"errors"
diff --git a/src/conn_default.go b/src/wireguard/conn_default.go
similarity index 85%
rename from src/conn_default.go
rename to src/wireguard/conn_default.go
index e7c60a8..5d7caa3 100644
--- a/src/conn_default.go
+++ b/src/wireguard/conn_default.go
@@ -1,6 +1,6 @@
 // +build !linux
 
-package main
+package wireguard
 
 import (
 	"net"
diff --git a/src/conn_linux.go b/src/wireguard/conn_linux.go
similarity index 99%
rename from src/conn_linux.go
rename to src/wireguard/conn_linux.go
index a349a9e..dafa0ae 100644
--- a/src/conn_linux.go
+++ b/src/wireguard/conn_linux.go
@@ -4,7 +4,7 @@
  * WireGuard's kernelspace implementation.
  */
 
-package main
+package wireguard
 
 import (
 	"errors"
diff --git a/src/constants.go b/src/wireguard/constants.go
similarity index 98%
rename from src/constants.go
rename to src/wireguard/constants.go
index 71dd98e..969ace9 100644
--- a/src/constants.go
+++ b/src/wireguard/constants.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"time"
diff --git a/src/cookie.go b/src/wireguard/cookie.go
similarity index 99%
rename from src/cookie.go
rename to src/wireguard/cookie.go
index a81819b..390aa80 100644
--- a/src/cookie.go
+++ b/src/wireguard/cookie.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"crypto/hmac"
diff --git a/src/cookie_test.go b/src/wireguard/cookie_test.go
similarity index 99%
rename from src/cookie_test.go
rename to src/wireguard/cookie_test.go
index 193a76e..a3488c9 100644
--- a/src/cookie_test.go
+++ b/src/wireguard/cookie_test.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"net"
diff --git a/src/daemon_darwin.go b/src/wireguard/daemon_darwin.go
similarity index 84%
rename from src/daemon_darwin.go
rename to src/wireguard/daemon_darwin.go
index 913af0e..ef1e3b1 100644
--- a/src/daemon_darwin.go
+++ b/src/wireguard/daemon_darwin.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"errors"
diff --git a/src/daemon_linux.go b/src/wireguard/daemon_linux.go
similarity index 96%
rename from src/daemon_linux.go
rename to src/wireguard/daemon_linux.go
index 730f89e..c5b46dc 100644
--- a/src/daemon_linux.go
+++ b/src/wireguard/daemon_linux.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"os"
diff --git a/src/daemon_windows.go b/src/wireguard/daemon_windows.go
similarity index 90%
rename from src/daemon_windows.go
rename to src/wireguard/daemon_windows.go
index d5ec1e8..56d9034 100644
--- a/src/daemon_windows.go
+++ b/src/wireguard/daemon_windows.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"os"
diff --git a/src/device.go b/src/wireguard/device.go
similarity index 97%
rename from src/device.go
rename to src/wireguard/device.go
index 61c87bc..2928ab5 100644
--- a/src/device.go
+++ b/src/wireguard/device.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"net"
@@ -9,7 +9,7 @@ import (
 )
 
 type Device struct {
-	log       *Logger // collection of loggers for levels
+	Log       *Logger // collection of loggers for levels
 	idCounter uint    // for assigning debug ids to peers
 	fwMark    uint32
 	tun       struct {
@@ -134,7 +134,7 @@ func NewDevice(tun TUNDevice, logLevel int) *Device {
 	device.mutex.Lock()
 	defer device.mutex.Unlock()
 
-	device.log = NewLogger(logLevel, "("+tun.Name()+") ")
+	device.Log = NewLogger(logLevel, "("+tun.Name()+") ")
 	device.peers = make(map[NoisePublicKey]*Peer)
 	device.tun.device = tun
 	device.indices.Init()
diff --git a/src/helper_test.go b/src/wireguard/helper_test.go
similarity index 98%
rename from src/helper_test.go
rename to src/wireguard/helper_test.go
index fc171e8..2689ef9 100644
--- a/src/helper_test.go
+++ b/src/wireguard/helper_test.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"bytes"
diff --git a/src/index.go b/src/wireguard/index.go
similarity index 98%
rename from src/index.go
rename to src/wireguard/index.go
index 1ba040e..1ba3aab 100644
--- a/src/index.go
+++ b/src/wireguard/index.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"crypto/rand"
diff --git a/src/ip.go b/src/wireguard/ip.go
similarity index 93%
rename from src/ip.go
rename to src/wireguard/ip.go
index 752a404..d8ca1c9 100644
--- a/src/ip.go
+++ b/src/wireguard/ip.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"net"
diff --git a/src/kdf_test.go b/src/wireguard/kdf_test.go
similarity index 99%
rename from src/kdf_test.go
rename to src/wireguard/kdf_test.go
index a89dacc..97ff844 100644
--- a/src/kdf_test.go
+++ b/src/wireguard/kdf_test.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"encoding/hex"
diff --git a/src/keypair.go b/src/wireguard/keypair.go
similarity index 97%
rename from src/keypair.go
rename to src/wireguard/keypair.go
index 7e5297b..78b8369 100644
--- a/src/keypair.go
+++ b/src/wireguard/keypair.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"crypto/cipher"
diff --git a/src/logger.go b/src/wireguard/logger.go
similarity index 97%
rename from src/logger.go
rename to src/wireguard/logger.go
index 0872ef9..317c50a 100644
--- a/src/logger.go
+++ b/src/wireguard/logger.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"io"
diff --git a/src/misc.go b/src/wireguard/misc.go
similarity index 98%
rename from src/misc.go
rename to src/wireguard/misc.go
index bbe0d68..b28e245 100644
--- a/src/misc.go
+++ b/src/wireguard/misc.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"sync/atomic"
diff --git a/src/noise_helpers.go b/src/wireguard/noise_helpers.go
similarity index 99%
rename from src/noise_helpers.go
rename to src/wireguard/noise_helpers.go
index 24302c0..c41d0bd 100644
--- a/src/noise_helpers.go
+++ b/src/wireguard/noise_helpers.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"crypto/hmac"
diff --git a/src/noise_protocol.go b/src/wireguard/noise_protocol.go
similarity index 99%
rename from src/noise_protocol.go
rename to src/wireguard/noise_protocol.go
index 9e5fdd8..b383827 100644
--- a/src/noise_protocol.go
+++ b/src/wireguard/noise_protocol.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"errors"
@@ -430,7 +430,7 @@ func (device *Device) ConsumeMessageResponse(msg *MessageResponse) *Peer {
 		aead, _ := chacha20poly1305.New(key[:])
 		_, err := aead.Open(nil, ZeroNonce[:], msg.Empty[:], hash[:])
 		if err != nil {
-			device.log.Debug.Println("failed to open")
+			device.Log.Debug.Println("failed to open")
 			return false
 		}
 		mixHash(&hash, &hash, msg.Empty[:])
diff --git a/src/noise_test.go b/src/wireguard/noise_test.go
similarity index 99%
rename from src/noise_test.go
rename to src/wireguard/noise_test.go
index 48408f9..8cdd96a 100644
--- a/src/noise_test.go
+++ b/src/wireguard/noise_test.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"bytes"
diff --git a/src/noise_types.go b/src/wireguard/noise_types.go
similarity index 98%
rename from src/noise_types.go
rename to src/wireguard/noise_types.go
index 1a944df..3ff9fbc 100644
--- a/src/noise_types.go
+++ b/src/wireguard/noise_types.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"crypto/subtle"
diff --git a/src/peer.go b/src/wireguard/peer.go
similarity index 99%
rename from src/peer.go
rename to src/wireguard/peer.go
index 6fea829..f498ae5 100644
--- a/src/peer.go
+++ b/src/wireguard/peer.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"encoding/base64"
diff --git a/src/ratelimiter.go b/src/wireguard/ratelimiter.go
similarity index 99%
rename from src/ratelimiter.go
rename to src/wireguard/ratelimiter.go
index 4f8227e..3afeb9e 100644
--- a/src/ratelimiter.go
+++ b/src/wireguard/ratelimiter.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 /* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
 
diff --git a/src/ratelimiter_test.go b/src/wireguard/ratelimiter_test.go
similarity index 99%
rename from src/ratelimiter_test.go
rename to src/wireguard/ratelimiter_test.go
index 13b6a23..386f4ec 100644
--- a/src/ratelimiter_test.go
+++ b/src/wireguard/ratelimiter_test.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"net"
diff --git a/src/receive.go b/src/wireguard/receive.go
similarity index 97%
rename from src/receive.go
rename to src/wireguard/receive.go
index 52c2718..5ec3609 100644
--- a/src/receive.go
+++ b/src/wireguard/receive.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"bytes"
@@ -94,7 +94,7 @@ func (device *Device) addToHandshakeQueue(
 
 func (device *Device) RoutineReceiveIncomming() {
 
-	logDebug := device.log.Debug
+	logDebug := device.Log.Debug
 	logDebug.Println("Routine, receive incomming, started")
 
 	for {
@@ -225,7 +225,7 @@ func (device *Device) RoutineDecryption() {
 
 	var nonce [chacha20poly1305.NonceSize]byte
 
-	logDebug := device.log.Debug
+	logDebug := device.Log.Debug
 	logDebug.Println("Routine, decryption, started for device")
 
 	for {
@@ -270,9 +270,9 @@ func (device *Device) RoutineDecryption() {
  */
 func (device *Device) RoutineHandshake() {
 
-	logInfo := device.log.Info
-	logError := device.log.Error
-	logDebug := device.log.Debug
+	logInfo := device.Log.Info
+	logError := device.Log.Error
+	logDebug := device.Log.Debug
 	logDebug.Println("Routine, handshake routine, started for device")
 
 	var temp [MessageHandshakeSize]byte
@@ -469,9 +469,9 @@ func (peer *Peer) RoutineSequentialReceiver() {
 
 	device := peer.device
 
-	logInfo := device.log.Info
-	logError := device.log.Error
-	logDebug := device.log.Debug
+	logInfo := device.Log.Info
+	logError := device.Log.Error
+	logDebug := device.Log.Debug
 	logDebug.Println("Routine, sequential receiver, started for peer", peer.id)
 
 	for {
diff --git a/src/replay.go b/src/wireguard/replay.go
similarity index 98%
rename from src/replay.go
rename to src/wireguard/replay.go
index 5d42860..c6469dd 100644
--- a/src/replay.go
+++ b/src/wireguard/replay.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 /* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
 
diff --git a/src/replay_test.go b/src/wireguard/replay_test.go
similarity index 99%
rename from src/replay_test.go
rename to src/wireguard/replay_test.go
index 228fce6..6493bd7 100644
--- a/src/replay_test.go
+++ b/src/wireguard/replay_test.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"testing"
diff --git a/src/routing.go b/src/wireguard/routing.go
similarity index 98%
rename from src/routing.go
rename to src/wireguard/routing.go
index 2a2e237..45ceb23 100644
--- a/src/routing.go
+++ b/src/wireguard/routing.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"errors"
diff --git a/src/send.go b/src/wireguard/send.go
similarity index 97%
rename from src/send.go
rename to src/wireguard/send.go
index 5c88ead..d781c40 100644
--- a/src/send.go
+++ b/src/wireguard/send.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"encoding/binary"
@@ -134,8 +134,8 @@ func (device *Device) RoutineReadFromTUN() {
 
 	elem := device.NewOutboundElement()
 
-	logDebug := device.log.Debug
-	logError := device.log.Error
+	logDebug := device.Log.Debug
+	logError := device.Log.Error
 
 	logDebug.Println("Routine, TUN Reader started")
 
@@ -201,7 +201,7 @@ func (peer *Peer) RoutineNonce() {
 	var keyPair *KeyPair
 
 	device := peer.device
-	logDebug := device.log.Debug
+	logDebug := device.Log.Debug
 	logDebug.Println("Routine, nonce worker, started for peer", peer.String())
 
 	for {
@@ -261,7 +261,7 @@ func (device *Device) RoutineEncryption() {
 
 	var nonce [chacha20poly1305.NonceSize]byte
 
-	logDebug := device.log.Debug
+	logDebug := device.Log.Debug
 	logDebug.Println("Routine, encryption worker, started")
 
 	for {
@@ -325,7 +325,7 @@ func (device *Device) RoutineEncryption() {
 func (peer *Peer) RoutineSequentialSender() {
 	device := peer.device
 
-	logDebug := device.log.Debug
+	logDebug := device.Log.Debug
 	logDebug.Println("Routine, sequential sender, started for", peer.String())
 
 	for {
diff --git a/src/tai64.go b/src/wireguard/tai64.go
similarity index 96%
rename from src/tai64.go
rename to src/wireguard/tai64.go
index 2299a37..08c1ede 100644
--- a/src/tai64.go
+++ b/src/wireguard/tai64.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"bytes"
diff --git a/src/timers.go b/src/wireguard/timers.go
similarity index 93%
rename from src/timers.go
rename to src/wireguard/timers.go
index 99695ba..82fe35d 100644
--- a/src/timers.go
+++ b/src/wireguard/timers.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"bytes"
@@ -118,7 +118,7 @@ func (peer *Peer) TimerHandshakeComplete() {
 		time.Now().UnixNano(),
 	)
 	signalSend(peer.signal.handshakeCompleted)
-	peer.device.log.Info.Println("Negotiated new handshake for", peer.String())
+	peer.device.Log.Info.Println("Negotiated new handshake for", peer.String())
 }
 
 /* Event:
@@ -139,7 +139,7 @@ func (peer *Peer) TimerEphemeralKeyCreated() {
 func (peer *Peer) RoutineTimerHandler() {
 	device := peer.device
 
-	logDebug := device.log.Debug
+	logDebug := device.Log.Debug
 	logDebug.Println("Routine, timer handler, started for peer", peer.String())
 
 	for {
@@ -228,9 +228,9 @@ func (peer *Peer) RoutineTimerHandler() {
 func (peer *Peer) RoutineHandshakeInitiator() {
 	device := peer.device
 
-	logInfo := device.log.Info
-	logError := device.log.Error
-	logDebug := device.log.Debug
+	logInfo := device.Log.Info
+	logError := device.Log.Error
+	logDebug := device.Log.Debug
 	logDebug.Println("Routine, handshake initator, started for", peer.String())
 
 	var temp [256]byte
diff --git a/src/trie.go b/src/wireguard/trie.go
similarity index 99%
rename from src/trie.go
rename to src/wireguard/trie.go
index 38fcd4a..18ae3d8 100644
--- a/src/trie.go
+++ b/src/wireguard/trie.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"errors"
diff --git a/src/trie_rand_test.go b/src/wireguard/trie_rand_test.go
similarity index 99%
rename from src/trie_rand_test.go
rename to src/wireguard/trie_rand_test.go
index 840d269..48c4dfa 100644
--- a/src/trie_rand_test.go
+++ b/src/wireguard/trie_rand_test.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"math/rand"
diff --git a/src/trie_test.go b/src/wireguard/trie_test.go
similarity index 99%
rename from src/trie_test.go
rename to src/wireguard/trie_test.go
index 9d53df3..2770eee 100644
--- a/src/trie_test.go
+++ b/src/wireguard/trie_test.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"math/rand"
diff --git a/src/tun.go b/src/wireguard/tun.go
similarity index 95%
rename from src/tun.go
rename to src/wireguard/tun.go
index 8e8c759..24208e6 100644
--- a/src/tun.go
+++ b/src/wireguard/tun.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"sync/atomic"
@@ -24,8 +24,8 @@ type TUNDevice interface {
 }
 
 func (device *Device) RoutineTUNEventReader() {
-	logInfo := device.log.Info
-	logError := device.log.Error
+	logInfo := device.Log.Info
+	logError := device.Log.Error
 
 	for event := range device.tun.device.Events() {
 		if event&TUNEventMTUUpdate != 0 {
diff --git a/src/tun_darwin.go b/src/wireguard/tun_darwin.go
similarity index 99%
rename from src/tun_darwin.go
rename to src/wireguard/tun_darwin.go
index 87f6af6..8ab8504 100644
--- a/src/tun_darwin.go
+++ b/src/wireguard/tun_darwin.go
@@ -4,7 +4,7 @@
  * Code from https://github.com/songgao/water
  */
 
-package main
+package wireguard
 
 import (
 	"encoding/binary"
diff --git a/src/tun_linux.go b/src/wireguard/tun_linux.go
similarity index 99%
rename from src/tun_linux.go
rename to src/wireguard/tun_linux.go
index accc6c6..4b7fc94 100644
--- a/src/tun_linux.go
+++ b/src/wireguard/tun_linux.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 /* Implementation of the TUN device interface for linux
  */
diff --git a/src/tun_windows.go b/src/wireguard/tun_windows.go
similarity index 99%
rename from src/tun_windows.go
rename to src/wireguard/tun_windows.go
index 0711032..4233326 100644
--- a/src/tun_windows.go
+++ b/src/wireguard/tun_windows.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"encoding/binary"
diff --git a/src/uapi.go b/src/wireguard/uapi.go
similarity index 95%
rename from src/uapi.go
rename to src/wireguard/uapi.go
index 326216b..b3984ad 100644
--- a/src/uapi.go
+++ b/src/wireguard/uapi.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"bufio"
@@ -93,9 +93,9 @@ func ipcGetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
 
 func ipcSetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
 	scanner := bufio.NewScanner(socket)
-	logInfo := device.log.Info
-	logError := device.log.Error
-	logDebug := device.log.Debug
+	logInfo := device.Log.Info
+	logError := device.Log.Error
+	logDebug := device.Log.Debug
 
 	var peer *Peer
 
@@ -332,7 +332,7 @@ func ipcSetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
 	return nil
 }
 
-func ipcHandle(device *Device, socket net.Conn) {
+func IpcHandle(device *Device, socket net.Conn) {
 
 	// create buffered read/writer
 
@@ -357,22 +357,22 @@ func ipcHandle(device *Device, socket net.Conn) {
 
 	switch op {
 	case "set=1\n":
-		device.log.Debug.Println("Config, set operation")
+		device.Log.Debug.Println("Config, set operation")
 		status = ipcSetOperation(device, buffered)
 
 	case "get=1\n":
-		device.log.Debug.Println("Config, get operation")
+		device.Log.Debug.Println("Config, get operation")
 		status = ipcGetOperation(device, buffered)
 
 	default:
-		device.log.Error.Println("Invalid UAPI operation:", op)
+		device.Log.Error.Println("Invalid UAPI operation:", op)
 		return
 	}
 
 	// write status
 
 	if status != nil {
-		device.log.Error.Println(status)
+		device.Log.Error.Println(status)
 		fmt.Fprintf(buffered, "errno=%d\n\n", status.ErrorCode())
 	} else {
 		fmt.Fprintf(buffered, "errno=0\n\n")
diff --git a/src/uapi_darwin.go b/src/wireguard/uapi_darwin.go
similarity index 98%
rename from src/uapi_darwin.go
rename to src/wireguard/uapi_darwin.go
index 63d4d8d..2223b32 100644
--- a/src/uapi_darwin.go
+++ b/src/wireguard/uapi_darwin.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"fmt"
diff --git a/src/uapi_linux.go b/src/wireguard/uapi_linux.go
similarity index 99%
rename from src/uapi_linux.go
rename to src/wireguard/uapi_linux.go
index cb9d858..5f694be 100644
--- a/src/uapi_linux.go
+++ b/src/wireguard/uapi_linux.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"errors"
diff --git a/src/uapi_windows.go b/src/wireguard/uapi_windows.go
similarity index 98%
rename from src/uapi_windows.go
rename to src/wireguard/uapi_windows.go
index a4599a5..38be24d 100644
--- a/src/uapi_windows.go
+++ b/src/wireguard/uapi_windows.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 /* UAPI on windows uses a bidirectional named pipe
  */
diff --git a/src/xchacha20.go b/src/wireguard/xchacha20.go
similarity index 99%
rename from src/xchacha20.go
rename to src/wireguard/xchacha20.go
index 5d963e0..aa5c575 100644
--- a/src/xchacha20.go
+++ b/src/wireguard/xchacha20.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a license that can be
 // found in the LICENSE file.
 
-package main
+package wireguard
 
 import (
 	"encoding/binary"
diff --git a/src/xchacha20_test.go b/src/wireguard/xchacha20_test.go
similarity index 99%
rename from src/xchacha20_test.go
rename to src/wireguard/xchacha20_test.go
index 0f41cf8..4acafe8 100644
--- a/src/xchacha20_test.go
+++ b/src/wireguard/xchacha20_test.go
@@ -1,4 +1,4 @@
-package main
+package wireguard
 
 import (
 	"encoding/hex"
-- 
2.15.0

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 2/2] Add android support
  2017-11-07  4:38 WireGuard root-less support for android Aurélien Chabot
                   ` (4 preceding siblings ...)
  2017-11-07  4:38 ` [PATCH 1/2] Put the code in a 'wireguard' go package Aurélien Chabot
@ 2017-11-07  4:38 ` Aurélien Chabot
  2017-11-07  9:57 ` WireGuard root-less support for android Jason A. Donenfeld
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Aurélien Chabot @ 2017-11-07  4:38 UTC (permalink / raw)
  To: wireguard

Signed-off-by: Aurélien Chabot <aurelien@chabot.fr>
---
 src/wireguard/conn.go      | 16 ++++++++++
 src/wireguard/device.go    |  1 +
 src/wireguard/send.go      | 77 +++++++++++++++++++++++++---------------------
 src/wireguard/tun_linux.go |  2 ++
 src/wireguard/uapi.go      | 36 +++++++++++++---------
 5 files changed, 83 insertions(+), 49 deletions(-)

diff --git a/src/wireguard/conn.go b/src/wireguard/conn.go
index 89b79ba..2706273 100644
--- a/src/wireguard/conn.go
+++ b/src/wireguard/conn.go
@@ -87,3 +87,19 @@ func closeUDPConn(device *Device) {
 	netc.mutex.Unlock()
 	signalSend(device.signal.newUDPConn)
 }
+
+func GetUDPConn(device *Device) (uintptr, error) {
+	netc := &device.net
+	netc.mutex.Lock()
+	defer netc.mutex.Unlock()
+
+	if netc.conn == nil {
+        return 0, nil
+    }
+
+    file, err := netc.conn.File()
+    if err != nil {
+        return 0, err
+    }
+    return file.Fd(), nil
+}
diff --git a/src/wireguard/device.go b/src/wireguard/device.go
index 2928ab5..5400d0e 100644
--- a/src/wireguard/device.go
+++ b/src/wireguard/device.go
@@ -205,6 +205,7 @@ func (device *Device) Close() {
 	device.RemoveAllPeers()
 	close(device.signal.stop)
 	closeUDPConn(device)
+	device.tun.device.Close()
 }
 
 func (device *Device) WaitChannel() chan struct{} {
diff --git a/src/wireguard/send.go b/src/wireguard/send.go
index d781c40..d081b90 100644
--- a/src/wireguard/send.go
+++ b/src/wireguard/send.go
@@ -141,53 +141,60 @@ func (device *Device) RoutineReadFromTUN() {
 
 	for {
 
-		// read packet
-
-		elem.packet = elem.buffer[MessageTransportHeaderSize:]
-		size, err := device.tun.device.Read(elem.packet)
-		if err != nil {
-			logError.Println("Failed to read packet from TUN device:", err)
-			device.Close()
+		select {
+		case <-device.signal.stop:
+			logDebug.Println("Routine, TUN Reader worker, stopped")
 			return
-		}
-
-		if size == 0 || size > MaxContentSize {
-			continue
-		}
 
-		elem.packet = elem.packet[:size]
+		default:
+			// read packet
 
-		// lookup peer
+			elem.packet = elem.buffer[MessageTransportHeaderSize:]
+			size, err := device.tun.device.Read(elem.packet)
+			if err != nil {
+				logError.Println("Failed to read packet from TUN device:", err)
+				device.Close()
+				return
+			}
 
-		var peer *Peer
-		switch elem.packet[0] >> 4 {
-		case ipv4.Version:
-			if len(elem.packet) < ipv4.HeaderLen {
+			if size == 0 || size > MaxContentSize {
 				continue
 			}
-			dst := elem.packet[IPv4offsetDst : IPv4offsetDst+net.IPv4len]
-			peer = device.routingTable.LookupIPv4(dst)
 
-		case ipv6.Version:
-			if len(elem.packet) < ipv6.HeaderLen {
+			elem.packet = elem.packet[:size]
+
+			// lookup peer
+
+			var peer *Peer
+			switch elem.packet[0] >> 4 {
+			case ipv4.Version:
+				if len(elem.packet) < ipv4.HeaderLen {
+					continue
+				}
+				dst := elem.packet[IPv4offsetDst : IPv4offsetDst+net.IPv4len]
+				peer = device.routingTable.LookupIPv4(dst)
+
+			case ipv6.Version:
+				if len(elem.packet) < ipv6.HeaderLen {
+					continue
+				}
+				dst := elem.packet[IPv6offsetDst : IPv6offsetDst+net.IPv6len]
+				peer = device.routingTable.LookupIPv6(dst)
+
+			default:
+				logDebug.Println("Receieved packet with unknown IP version")
+			}
+
+			if peer == nil {
 				continue
 			}
-			dst := elem.packet[IPv6offsetDst : IPv6offsetDst+net.IPv6len]
-			peer = device.routingTable.LookupIPv6(dst)
 
-		default:
-			logDebug.Println("Receieved packet with unknown IP version")
-		}
+			// insert into nonce/pre-handshake queue
 
-		if peer == nil {
-			continue
+			signalSend(peer.signal.handshakeReset)
+			addToOutboundQueue(peer.queue.nonce, elem)
+			elem = device.NewOutboundElement()
 		}
-
-		// insert into nonce/pre-handshake queue
-
-		signalSend(peer.signal.handshakeReset)
-		addToOutboundQueue(peer.queue.nonce, elem)
-		elem = device.NewOutboundElement()
 	}
 }
 
diff --git a/src/wireguard/tun_linux.go b/src/wireguard/tun_linux.go
index 4b7fc94..6f2e036 100644
--- a/src/wireguard/tun_linux.go
+++ b/src/wireguard/tun_linux.go
@@ -1,3 +1,5 @@
+// +build !android
+
 package wireguard
 
 /* Implementation of the TUN device interface for linux
diff --git a/src/wireguard/uapi.go b/src/wireguard/uapi.go
index b3984ad..ea9e29a 100644
--- a/src/wireguard/uapi.go
+++ b/src/wireguard/uapi.go
@@ -24,13 +24,30 @@ func (s *IPCError) ErrorCode() int64 {
 }
 
 func ipcGetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
+	lines := make([]string, 0, 100)
+	GetOperation(device, lines)
+
+	// send lines
+
+	for _, line := range lines {
+		_, err := socket.WriteString(line + "\n")
+		if err != nil {
+			return &IPCError{
+				Code: ipcErrorIO,
+			}
+		}
+	}
+
+	return nil
+}
+
+func GetOperation(device *Device, lines []string) {
 
 	// create lines
 
 	device.mutex.RLock()
 	device.net.mutex.RLock()
 
-	lines := make([]string, 0, 100)
 	send := func(line string) {
 		lines = append(lines, line)
 	}
@@ -76,23 +93,14 @@ func ipcGetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
 
 	device.net.mutex.RUnlock()
 	device.mutex.RUnlock()
-
-	// send lines
-
-	for _, line := range lines {
-		_, err := socket.WriteString(line + "\n")
-		if err != nil {
-			return &IPCError{
-				Code: ipcErrorIO,
-			}
-		}
-	}
-
-	return nil
 }
 
 func ipcSetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
 	scanner := bufio.NewScanner(socket)
+	return SetOperation(device, scanner)
+}
+
+func SetOperation(device *Device, scanner *bufio.Scanner) *IPCError {
 	logInfo := device.Log.Info
 	logError := device.Log.Error
 	logDebug := device.Log.Debug
-- 
2.15.0

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH 1/4] Add wireguard-go as submodule
  2017-11-07  4:38 ` [PATCH 1/4] Add wireguard-go as submodule Aurélien Chabot
@ 2017-11-07  9:34   ` Greg KH
  2017-11-07  9:53     ` Jason A. Donenfeld
  0 siblings, 1 reply; 14+ messages in thread
From: Greg KH @ 2017-11-07  9:34 UTC (permalink / raw)
  To: Aurélien Chabot; +Cc: wireguard

On Tue, Nov 07, 2017 at 03:38:12PM +1100, Aurélien Chabot wrote:
> Signed-off-by: Aurélien Chabot <aurelien@chabot.fr>
> ---
>  .gitmodules  | 3 +++
>  wireguard-go | 1 +
>  2 files changed, 4 insertions(+)
>  create mode 100644 .gitmodules
>  create mode 160000 wireguard-go
> 
> diff --git a/.gitmodules b/.gitmodules
> new file mode 100644
> index 0000000..c7957ea
> --- /dev/null
> +++ b/.gitmodules
> @@ -0,0 +1,3 @@
> +[submodule "wireguard-go"]
> +	path = wireguard-go
> +	url = git@github.com:trishika/wireguard-go.git

Ugh, submodules are a pain in git, do you really want to do it this way?
If so, why?  I thought there was a "replacement" for submodules that was
recommended to use instead.

And I mention this because I have a submodule in a project I maintain,
and I _always_ mess it up when I go to update it, and have to go read
the documentation for how to do it properly.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 1/4] Add wireguard-go as submodule
  2017-11-07  9:34   ` Greg KH
@ 2017-11-07  9:53     ` Jason A. Donenfeld
  0 siblings, 0 replies; 14+ messages in thread
From: Jason A. Donenfeld @ 2017-11-07  9:53 UTC (permalink / raw)
  To: Greg KH; +Cc: WireGuard mailing list

For the purposes of the Android app, a submodule is likely exactly what we want.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: WireGuard root-less support for android
  2017-11-07  4:38 WireGuard root-less support for android Aurélien Chabot
                   ` (5 preceding siblings ...)
  2017-11-07  4:38 ` [PATCH 2/2] Add android support Aurélien Chabot
@ 2017-11-07  9:57 ` Jason A. Donenfeld
  2017-11-08  5:15 ` Samuel Holland
  2017-11-08 17:44 ` Mathias Hall-Andersen
  8 siblings, 0 replies; 14+ messages in thread
From: Jason A. Donenfeld @ 2017-11-07  9:57 UTC (permalink / raw)
  To: Aurélien Chabot, Mathias, Samuel Holland; +Cc: WireGuard mailing list

Hi Aur=C3=A9lien,

Thanks for this patchset. I'll review it ASAP.

As an administrative situation, your set of Go changes will likely
need to be merged back up with the official Go repository; putting
your fork in the Android one isn't really tenable.

So please submit your changes to the wireguard-go project to this list too.

I intend to give a pass at this patchset as soon as I'm back from netdevcon=
f.

Mathias, CCd will also review the Go code, and Samuel, CCd will review
the Java code.

Thanks for this contribution!

By the way, please poke me on IRC; it'd be nice to coordinate this
terrific development you're doing; this was a pleasant surprise out of
the blue.

Jason

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: WireGuard root-less support for android
  2017-11-07  4:38 WireGuard root-less support for android Aurélien Chabot
                   ` (6 preceding siblings ...)
  2017-11-07  9:57 ` WireGuard root-less support for android Jason A. Donenfeld
@ 2017-11-08  5:15 ` Samuel Holland
  2017-11-08 17:44 ` Mathias Hall-Andersen
  8 siblings, 0 replies; 14+ messages in thread
From: Samuel Holland @ 2017-11-08  5:15 UTC (permalink / raw)
  To: Aurélien Chabot, wireguard

Hello,

On 11/06/17 22:38, Aurélien Chabot wrote:
> I worked on a set of change to add root-less support of WireGuard for
> android. The solution I choose is to use the wireguard-go library 
> inside the android application. Golang as a mechanism to export some 
> native binding quite easily to java. The set of patch need some 
> feedback but it's actually working well. I'd like to know if you 
> think this is a good direction to take for the android application.

Thanks for your contribution! This is definitely the direction we want
to work toward; the Go implementation is much more accessible to
non-rooted devices. I had assumed we would have to run wireguard-go as a
separate process (my only experience with Go-on-Android is syncthing[1],
which pretends its Go binary is a native library[2]). If we can run
wireguard-go in process, that would be much better!

[1]: https://github.com/syncthing/syncthing-android
[2]: 
https://github.com/syncthing/syncthing-android/blob/master/make-syncthing.bash

> The patch are in the thread but I used a submodule to integrate the 
> wireguard-go library inside the wireguard-android so at least this 
> need to be change with the official url if it's get merge.

Also, your patch 2 won't work as-is with the upstream version since it
won't have the same commit hash.

> You can also find the set of change on my github : 
> https://github.com/trishika/wireguard-android 
> https://github.com/trishika/wireguard-go

I've started looking through your Java changes, and they're generally
looking good. The actual wireguard-go glue can't be merged until after
the changes to the other repository (hooray submodules!), but I'll go
ahead and try to integrate your service abstraction layer. That way I
can reuse it for switching the kernel-space interface from wg-quick to
wg proper (an existing to-do item).

> Aurélien
Thanks,
Samuel

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: WireGuard root-less support for android
  2017-11-07  4:38 WireGuard root-less support for android Aurélien Chabot
                   ` (7 preceding siblings ...)
  2017-11-08  5:15 ` Samuel Holland
@ 2017-11-08 17:44 ` Mathias Hall-Andersen
  2017-11-08 21:50   ` Aurélien Chabot
  8 siblings, 1 reply; 14+ messages in thread
From: Mathias Hall-Andersen @ 2017-11-08 17:44 UTC (permalink / raw)
  To: Aurélien Chabot; +Cc: wireguard

Hi Aurélien

Thanks for contributing to the wireguard-go project.

I never anticipated for the implementation to be used as a library.
This means that I either have to:

1. Settle for an API and reconsider what is exported and not.
2. Give no guarantees about API stability

Providing the functionality as a library might be the cleanest solution
in this case.
One option is to have an internal package and a small exported API.

Is the GetUDPConn only used to wait for the device to bind?

The missing device.tun.device.Close() is indeed a bug.

I will look more at your patches during the weekend,
Thanks once again.

Mathias

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: WireGuard root-less support for android
  2017-11-08 17:44 ` Mathias Hall-Andersen
@ 2017-11-08 21:50   ` Aurélien Chabot
  2018-01-23  1:52     ` Aurélien Chabot
  0 siblings, 1 reply; 14+ messages in thread
From: Aurélien Chabot @ 2017-11-08 21:50 UTC (permalink / raw)
  To: Mathias Hall-Andersen; +Cc: wireguard

[-- Attachment #1: Type: text/plain, Size: 1129 bytes --]

The GetUDPConn is used to forward the socket id to the Java layer. The
android API allow to protect the socket from being route to the VPN, making
the job of using the VPN as default route easy.

About the close, good to know I am not missing something. I might spit the
patch then, so the bug fix doesn't get stuck with the rest.

Aurélien

On Thu, Nov 9, 2017, 04:48 Mathias Hall-Andersen <mathias@hall-andersen.dk>
wrote:

> Hi Aurélien
>
> Thanks for contributing to the wireguard-go project.
>
> I never anticipated for the implementation to be used as a library.
> This means that I either have to:
>
> 1. Settle for an API and reconsider what is exported and not.
> 2. Give no guarantees about API stability
>
> Providing the functionality as a library might be the cleanest solution
> in this case.
> One option is to have an internal package and a small exported API.
>
> Is the GetUDPConn only used to wait for the device to bind?
>
> The missing device.tun.device.Close() is indeed a bug.
>
> I will look more at your patches during the weekend,
> Thanks once again.
>
> Mathias
>

[-- Attachment #2: Type: text/html, Size: 1448 bytes --]

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: WireGuard root-less support for android
  2017-11-08 21:50   ` Aurélien Chabot
@ 2018-01-23  1:52     ` Aurélien Chabot
  0 siblings, 0 replies; 14+ messages in thread
From: Aurélien Chabot @ 2018-01-23  1:52 UTC (permalink / raw)
  To: Mathias Hall-Andersen; +Cc: wireguard

[-- Attachment #1: Type: text/plain, Size: 1438 bytes --]

Hi Mathias,

Did you had thought about the support of wireguard-go for android?
I'll be happy to rebase the work I did before on wireguard-go to get that a
new go.

Aurélien

On Thu, Nov 9, 2017 at 8:50 AM Aurélien Chabot <aurelien@chabot.fr> wrote:

> The GetUDPConn is used to forward the socket id to the Java layer. The
> android API allow to protect the socket from being route to the VPN, making
> the job of using the VPN as default route easy.
>
> About the close, good to know I am not missing something. I might spit the
> patch then, so the bug fix doesn't get stuck with the rest.
>
> Aurélien
>
> On Thu, Nov 9, 2017, 04:48 Mathias Hall-Andersen <mathias@hall-andersen.dk>
> wrote:
>
>> Hi Aurélien
>>
>> Thanks for contributing to the wireguard-go project.
>>
>> I never anticipated for the implementation to be used as a library.
>> This means that I either have to:
>>
>> 1. Settle for an API and reconsider what is exported and not.
>> 2. Give no guarantees about API stability
>>
>> Providing the functionality as a library might be the cleanest solution
>> in this case.
>> One option is to have an internal package and a small exported API.
>>
>> Is the GetUDPConn only used to wait for the device to bind?
>>
>> The missing device.tun.device.Close() is indeed a bug.
>>
>> I will look more at your patches during the weekend,
>> Thanks once again.
>>
>> Mathias
>>
>

[-- Attachment #2: Type: text/html, Size: 2065 bytes --]

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2018-01-23  1:48 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-11-07  4:38 WireGuard root-less support for android Aurélien Chabot
2017-11-07  4:38 ` [PATCH 1/4] Add wireguard-go as submodule Aurélien Chabot
2017-11-07  9:34   ` Greg KH
2017-11-07  9:53     ` Jason A. Donenfeld
2017-11-07  4:38 ` [PATCH 2/4] Add support for android in wireguard-go Aurélien Chabot
2017-11-07  4:38 ` [PATCH 3/4] Add support of wireguard-go as an Android Vpn Service Aurélien Chabot
2017-11-07  4:38 ` [PATCH 4/4] Add build instruction Aurélien Chabot
2017-11-07  4:38 ` [PATCH 1/2] Put the code in a 'wireguard' go package Aurélien Chabot
2017-11-07  4:38 ` [PATCH 2/2] Add android support Aurélien Chabot
2017-11-07  9:57 ` WireGuard root-less support for android Jason A. Donenfeld
2017-11-08  5:15 ` Samuel Holland
2017-11-08 17:44 ` Mathias Hall-Andersen
2017-11-08 21:50   ` Aurélien Chabot
2018-01-23  1:52     ` Aurélien Chabot

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.