VPN client: start Nebula controller locally
diff --git a/charts/vpn-mesh-config/templates/api.yaml b/charts/vpn-mesh-config/templates/api.yaml
index 9da26d7..a747f4f 100644
--- a/charts/vpn-mesh-config/templates/api.yaml
+++ b/charts/vpn-mesh-config/templates/api.yaml
@@ -27,6 +27,8 @@
     metadata:
       labels:
         app: nebula-api
+      annotations:
+        checksum/config: {{ include (print $.Template.BasePath "/lighthouse-config-template.yaml") . | sha256sum }}
     spec:
       volumes:
       - name: config
diff --git a/charts/vpn-mesh-config/templates/lighthouse-config-template.yaml b/charts/vpn-mesh-config/templates/lighthouse-config-template.yaml
index cdf18df..016ecb0 100644
--- a/charts/vpn-mesh-config/templates/lighthouse-config-template.yaml
+++ b/charts/vpn-mesh-config/templates/lighthouse-config-template.yaml
@@ -14,6 +14,8 @@
     lighthouse:
       am_lighthouse: false
       interval: 60
+      hosts:
+      - "{{ .Values.lighthouse.internalIP }}"
     listen:
       host: "[::]"
       port: 4242
@@ -28,7 +30,7 @@
       tx_queue: 500
       mtu: 1300
     logging:
-      level: info
+      level: debug
       format: text
     firewall:
       conntrack:
diff --git a/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudActivity.java b/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudActivity.java
index ec1c76d..330e8cd 100644
--- a/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudActivity.java
+++ b/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudActivity.java
@@ -1,6 +1,8 @@
 package me.lekva.pcloud;
 
+import android.content.Intent;
 import android.content.res.Configuration;
+import android.net.VpnService;
 import android.os.Bundle;
 
 import androidx.activity.result.ActivityResultLauncher;
@@ -13,6 +15,8 @@
 import org.gioui.GioView;
 
 public class PCloudActivity extends AppCompatActivity {
+    private static final int VPN_START_CODE = 0x10;
+
     private GioView view;
 
     private final ActivityResultLauncher<ScanOptions> barcodeLauncher = registerForActivityResult(new ScanContract(),
@@ -77,5 +81,19 @@
         return null;
     }
 
+    public String startVpn(String ipCidr) {
+        Intent intent = VpnService.prepare(this);
+        if (intent != null) {
+            System.out.println("#### STARTVPN");
+            intent.setAction(PCloudVPNService.ACTION_CONNECT);
+            startActivityForResult(intent, VPN_START_CODE);
+        } else {
+            intent = new Intent(this, PCloudVPNService.class);
+            startService(intent);
+        }
+
+        return null;
+    }
+
     private native void qrcodeScanned(String contents);
 }
diff --git a/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudVPNService.java b/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudVPNService.java
index 43ce99f..1566ed1 100644
--- a/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudVPNService.java
+++ b/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudVPNService.java
@@ -1,10 +1,13 @@
 package me.lekva.pcloud;
 
+import android.app.PendingIntent;
 import android.app.Service;
 import android.content.Intent;
 import android.net.VpnService;
+import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
+import android.system.OsConstants;
 
 import androidx.annotation.NonNull;
 
@@ -24,7 +27,7 @@
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
-        if (intent != null && intent.getAction().equals(ACTION_DISCONNECT)) {
+        if (intent != null && ACTION_DISCONNECT.equals(intent.getAction())) {
             stopVpn();
             return Service.START_NOT_STICKY;
         } else {
@@ -40,6 +43,7 @@
 
     private void startVpn() {
         System.out.println("--- START");
+        connect();
     }
 
     private void stopVpn() {
@@ -52,4 +56,23 @@
         System.out.println(getString(message.what));
         return true;
     }
+
+    private PendingIntent configIntent() {
+        return PendingIntent.getActivity(this, 0, new Intent(this, PCloudActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+
+    public VpnService.Builder newBuilder() {
+        VpnService.Builder builder = new VpnService.Builder()
+                .setConfigureIntent(configIntent())
+                .allowFamily(OsConstants.AF_INET)
+                .allowFamily(OsConstants.AF_INET6);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+            builder.setMetered(false); // Inherit the metered status from the underlying networks.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+            builder.setUnderlyingNetworks(null); // Use all available networks.
+        return builder;
+    }
+
+    private native void connect();
+    private native void disconnect();
 }
diff --git a/core/client/cmd/pcloud/app.go b/core/client/cmd/pcloud/app.go
index 5c49644..0f0dc47 100644
--- a/core/client/cmd/pcloud/app.go
+++ b/core/client/cmd/pcloud/app.go
@@ -5,4 +5,6 @@
 type App interface {
 	LaunchBarcodeScanner() error
 	OnView(app.ViewEvent) error
+	StartVPN(config []byte) error
+	Connect(service interface{}) error
 }
diff --git a/core/client/cmd/pcloud/app_android.go b/core/client/cmd/pcloud/app_android.go
index 8dd7c73..c66b401 100644
--- a/core/client/cmd/pcloud/app_android.go
+++ b/core/client/cmd/pcloud/app_android.go
@@ -2,16 +2,25 @@
 
 import (
 	"errors"
+	"fmt"
+	"time"
 	"unsafe"
 
 	"gioui.org/app"
 	"github.com/giolekva/pcloud/core/client/jni"
+	"github.com/sirupsen/logrus"
+	"github.com/slackhq/nebula"
+	"github.com/slackhq/nebula/cert"
+	nc "github.com/slackhq/nebula/config"
 )
 
 type androidApp struct {
-	jvm      *jni.JVM
-	appCtx   jni.Object
-	activity jni.Object
+	jvm          *jni.JVM
+	appCtx       jni.Object // PCloudApp
+	activity     jni.Object // PCloudActivity
+	service      jni.Object // PCloudVPNService
+	nebulaConfig []byte
+	ctrl         *nebula.Control
 }
 
 func createApp() App {
@@ -73,3 +82,161 @@
 	}
 	return ctx, nil
 }
+
+func (a *androidApp) StartVPN(config []byte) error {
+	fmt.Println("2222222")
+	a.nebulaConfig = config
+	fmt.Println(string(a.nebulaConfig))
+	return jni.Do(a.jvm, func(env *jni.Env) error {
+		fmt.Println(123123)
+		cls := jni.GetObjectClass(env, a.activity)
+		m := jni.GetMethodID(env, cls, "startVpn", "(Ljava/lang/String;)Ljava/lang/String;")
+		jConfig := jni.JavaString(env, string(config))
+		_, err := jni.CallObjectMethod(env, a.activity, m, jni.Value(jConfig))
+		fmt.Println(123123123)
+		return err
+
+	})
+}
+
+func (a *androidApp) Connect(serv interface{}) error {
+	s, ok := serv.(jni.Object)
+	if !ok {
+		return fmt.Errorf("Unexpected service type: %T", serv)
+	}
+	jni.Do(a.jvm, func(env *jni.Env) error {
+		if jni.IsSameObject(env, s, a.service) {
+			// We already have a reference.
+			jni.DeleteGlobalRef(env, s)
+			return nil
+		}
+		if a.service != 0 {
+			jni.DeleteGlobalRef(env, a.service)
+		}
+		// netns.SetAndroidProtectFunc(func(fd int) error {
+		// 	return jni.Do(a.jvm, func(env *jni.Env) error {
+		// 		// Call https://developer.android.com/reference/android/net/VpnService#protect(int)
+		// 		// to mark fd as a socket that should bypass the VPN and use the underlying network.
+		// 		cls := jni.GetObjectClass(env, s)
+		// 		m := jni.GetMethodID(env, cls, "protect", "(I)Z")
+		// 		ok, err := jni.CallBooleanMethod(env, s, m, jni.Value(fd))
+		// 		// TODO(bradfitz): return an error back up to netns if this fails, once
+		// 		// we've had some experience with this and analyzed the logs over a wide
+		// 		// range of Android phones. For now we're being paranoid and conservative
+		// 		// and do the JNI call to protect best effort, only logging if it fails.
+		// 		// The risk of returning an error is that it breaks users on some Android
+		// 		// versions even when they're not using exit nodes. I'd rather the
+		// 		// relatively few number of exit node users file bug reports if Tailscale
+		// 		// doesn't work and then we can look for this log print.
+		// 		if err != nil || !ok {
+		// 			log.Printf("[unexpected] VpnService.protect(%d) = %v, %v", fd, ok, err)
+		// 		}
+		// 		return nil // even on error. see big TODO above.
+		// 	})
+		// })
+		a.service = s
+		return nil
+	})
+	return a.buildVPNConfigurationAndConnect()
+}
+
+func (a *androidApp) buildVPNConfigurationAndConnect() error {
+	if string(a.nebulaConfig) == "" {
+		return nil
+	}
+	fmt.Println(333333333)
+	fmt.Println(string(a.nebulaConfig))
+	// return nil
+	// if err := a.callVoidMethod(a.appCtx, "prepareVPN", "(Landroid/app/Activity;I)V",
+	// 	jni.Value(act), jni.Value(requestPrepareVPN)); err != nil {
+	// 	return nil
+	// }
+
+	config := nc.NewC(logrus.StandardLogger())
+	if err := config.LoadString(string(a.nebulaConfig)); err != nil {
+		return err
+	}
+	pki := config.GetMap("pki", nil)
+	hostCert, _, err := cert.UnmarshalNebulaCertificateFromPEM([]byte(pki["cert"].(string)))
+	if err != nil {
+		panic(err)
+	}
+	t := time.Now()
+	fmt.Println("#########3")
+	fmt.Println(t.String())
+	fmt.Println(hostCert.Details.NotBefore.String())
+	fmt.Println(hostCert.Details.NotAfter.String())
+	fmt.Println(t.Before(hostCert.Details.NotBefore))
+	fmt.Println(t.After(hostCert.Details.NotAfter))
+	fmt.Println("#########3")
+	// return nil
+	return jni.Do(a.jvm, func(env *jni.Env) error {
+		fmt.Println("---------")
+		cls := jni.GetObjectClass(env, a.service)
+		m := jni.GetMethodID(env, cls, "newBuilder", "()Landroid/net/VpnService$Builder;")
+		b, err := jni.CallObjectMethod(env, a.service, m)
+		if err != nil {
+			return fmt.Errorf("PCloudVPNService.newBuilder: %v", err)
+		}
+		fmt.Println("---------")
+		bcls := jni.GetObjectClass(env, b)
+		addAddress := jni.GetMethodID(env, bcls, "addAddress", "(Ljava/lang/String;I)Landroid/net/VpnService$Builder;")
+		addRoute := jni.GetMethodID(env, bcls, "addRoute", "(Ljava/lang/String;I)Landroid/net/VpnService$Builder;")
+		for _, ipNet := range hostCert.Details.Ips {
+			ip := ipNet.IP.String()
+			prefix, _ := ipNet.Mask.Size()
+			_, err := jni.CallObjectMethod(
+				env,
+				b,
+				addAddress,
+				jni.Value(jni.JavaString(env, ip)),
+				jni.Value(jni.Value(prefix)))
+			if err != nil {
+				return err
+			}
+			_, err = jni.CallObjectMethod(
+				env,
+				b,
+				addRoute,
+				jni.Value(jni.JavaString(env, ip)),
+				jni.Value(jni.Value(prefix)))
+		}
+		tun := config.GetMap("tun", nil)
+		setMtu := jni.GetMethodID(env, bcls, "setMtu", "(I)Landroid/net/VpnService$Builder;")
+		if _, err := jni.CallObjectMethod(env, b, setMtu, jni.Value(tun["mtu"].(int))); err != nil {
+			return err
+		}
+		establish := jni.GetMethodID(env, bcls, "establish", "()Landroid/os/ParcelFileDescriptor;")
+		parcelFD, err := jni.CallObjectMethod(env, b, establish)
+		if err != nil {
+			return err
+		}
+		parcelCls := jni.GetObjectClass(env, parcelFD)
+		// detachFd := jni.GetMethodID(env, parcelCls, "detachFd", "()I")
+		// tunFD, err := jni.CallIntMethod(env, parcelFD, detachFd)
+		// if err != nil {
+		// 	return fmt.Errorf("detachFd: %v", err)
+		// }
+		// fd := int(tunFD)
+		// protect := jni.GetMethodID(env, cls, "protect", "(I)Z")
+		// ok, err := jni.CallBooleanMethod(env, a.service, protect, jni.Value(fd))
+		// if err != nil || !ok {
+		// 	return fmt.Errorf("protect: %v %v", err, ok)
+		// }
+		getFd := jni.GetMethodID(env, parcelCls, "getFd", "()I")
+		tunFD, err := jni.CallIntMethod(env, parcelFD, getFd)
+		if err != nil {
+			return err
+		}
+		fd := int(tunFD)
+		fmt.Println("===========")
+		ctrl, err := nebula.Main(config, false, "pcloud", logrus.StandardLogger(), &fd)
+		if err != nil {
+			return err
+		}
+		ctrl.Start()
+		a.ctrl = ctrl
+		return nil
+	})
+
+}
diff --git a/core/client/cmd/pcloud/app_darwin.go b/core/client/cmd/pcloud/app_darwin.go
index a60ea35..c7b670f 100644
--- a/core/client/cmd/pcloud/app_darwin.go
+++ b/core/client/cmd/pcloud/app_darwin.go
@@ -20,3 +20,11 @@
 func (a *darwinApp) OnView(e app.ViewEvent) error {
 	return nil
 }
+
+func (a *darwinApp) StartVPN(config []byte) error {
+	return nil
+}
+
+func (a *darwinApp) Connect(serv interface{}) error {
+	return nil
+}
diff --git a/core/client/cmd/pcloud/callbacks_android.go b/core/client/cmd/pcloud/callbacks_android.go
index df399f9..b7060aa 100644
--- a/core/client/cmd/pcloud/callbacks_android.go
+++ b/core/client/cmd/pcloud/callbacks_android.go
@@ -17,3 +17,15 @@
 	code := jni.GoString(jenv, jni.String(contents))
 	p.InviteQRCodeScanned([]byte(code))
 }
+
+//export Java_me_lekva_pcloud_PCloudVPNService_connect
+func Java_me_lekva_pcloud_PCloudVPNService_connect(env *C.JNIEnv, this C.jobject) {
+	jenv := (*jni.Env)(unsafe.Pointer(env))
+	p.ConnectRequested(jni.NewGlobalRef(jenv, jni.Object(this)))
+}
+
+//export Java_me_lekva_pcloud_PCloudVPNService_disconnect
+func Java_me_lekva_pcloud_PCloudVPNService_disconnect(env *C.JNIEnv, this C.jobject) {
+	jenv := (*jni.Env)(unsafe.Pointer(env))
+	p.DisconnectRequested(jni.NewGlobalRef(jenv, jni.Object(this)))
+}
diff --git a/core/client/cmd/pcloud/main.go b/core/client/cmd/pcloud/main.go
index 1d70e03..1e43fa5 100644
--- a/core/client/cmd/pcloud/main.go
+++ b/core/client/cmd/pcloud/main.go
@@ -7,6 +7,7 @@
 	"fmt"
 	"image"
 	"image/png"
+	"time"
 
 	"gioui.org/app"
 	"gioui.org/io/system"
@@ -27,6 +28,9 @@
 
 	inviteQrCh        chan image.Image
 	inviteQrScannedCh chan []byte
+
+	onConnectCh    chan interface{}
+	onDisconnectCh chan interface{}
 }
 
 func newProcessor() *processor {
@@ -36,6 +40,8 @@
 		ui:                NewUI(),
 		inviteQrCh:        make(chan image.Image, 1),
 		inviteQrScannedCh: make(chan []byte, 1),
+		onConnectCh:       make(chan interface{}, 1),
+		onDisconnectCh:    make(chan interface{}, 1),
 	}
 }
 
@@ -43,6 +49,17 @@
 	p.inviteQrScannedCh <- code
 }
 
+func (p *processor) ConnectRequested(service interface{}) {
+	go func() {
+		time.Sleep(1 * time.Second)
+		p.onConnectCh <- service
+	}()
+}
+
+func (p *processor) DisconnectRequested(service interface{}) {
+	p.onDisconnectCh <- service
+}
+
 func (p *processor) run() error {
 	w := app.NewWindow(
 		app.Size(unit.Px(1500), unit.Px(1500)),
@@ -78,9 +95,17 @@
 			p.ui.InviteQRGenerated(img)
 			w.Invalidate()
 		case code := <-p.inviteQrScannedCh:
-			go func() {
-				p.JoinNetworkAndConnect(code)
-			}()
+			// go func() {
+			fmt.Println("00000000")
+			p.JoinNetworkAndConnect(code)
+			fmt.Println("00000000")
+			// }()
+		case s := <-p.onConnectCh:
+			fmt.Println("--- 1111111111")
+			if err := p.app.Connect(s); err != nil {
+				panic(err)
+			}
+			fmt.Println("--- 1111111111")
 		}
 	}
 	return nil
@@ -148,8 +173,9 @@
 	if err != nil {
 		panic(err)
 	}
-	fmt.Printf("-- VPN CONFIG %s\n", string(config))
-
+	if err := p.app.StartVPN(config); err != nil {
+		panic(err)
+	}
 }
 
 func main() {
diff --git a/core/client/go.mod b/core/client/go.mod
index 112a042..76a8d55 100644
--- a/core/client/go.mod
+++ b/core/client/go.mod
@@ -5,6 +5,7 @@
 require (
 	gioui.org v0.0.0-20211201162354-9a5298914282
 	gioui.org/cmd v0.0.0-20211214104907-f5c9d2725c7b
+	github.com/sirupsen/logrus v1.8.1
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/slackhq/nebula v1.5.1
 	golang.org/x/crypto v0.0.0-20211202192323-5770296d904e