Run check config on app boot, fix early error reporting on android

This commit is contained in:
Nate Brown 2020-08-17 11:56:15 -05:00
parent 50d50f690b
commit 1a4cbceda0
10 changed files with 144 additions and 45 deletions

View File

@ -2,11 +2,12 @@ package net.defined.mobile_nebula
import android.app.Activity
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.net.VpnService
import android.os.*
import androidx.annotation.NonNull;
import androidx.annotation.NonNull
import com.google.gson.Gson
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
@ -28,9 +29,15 @@ class MainActivity: FlutterActivity() {
private var activeSiteId: String? = null
companion object {
private var appContext: Context? = null
fun getContext(): Context? { return appContext }
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
appContext = context
//TODO: Initializing in the constructor leads to a context lacking info we need, figure out the right way to do this
sites = Sites(context, flutterEngine)
sites = Sites(flutterEngine)
// Bind against our service to detect which site is running on app boot
val intent = Intent(this, NebulaVpnService::class.java)
@ -153,7 +160,7 @@ class MainActivity: FlutterActivity() {
intent.putExtra("id", siteContainer.site.id)
onActivityResult(VPN_START_CODE, Activity.RESULT_OK, intent)
}
result.success(null)
}

View File

@ -43,28 +43,37 @@ class NebulaVpnService : VpnService() {
return Service.START_NOT_STICKY
}
startVpn(intent?.getStringExtra("path"), intent?.getStringExtra("id"))
return super.onStartCommand(intent, flags, startId)
}
private fun startVpn(path: String?, id: String?) {
val path = intent?.getStringExtra("path")
val id = intent?.getStringExtra("id")
if (running) {
return announceExit(id, "Trying to run nebula but it is already running")
announceExit(id, "Trying to run nebula but it is already running")
//TODO: can we signal failure?
return super.onStartCommand(intent, flags, startId)
}
//TODO: if we fail to start, android will attempt a restart lacking all the intent data we need.
// Link active site config in Main to avoid this
site = Site(File(path))
var ipNet: CIDR
if (site!!.cert == null) {
return announceExit(id, "Site is missing a certificate")
announceExit(id, "Site is missing a certificate")
//TODO: can we signal failure?
return super.onStartCommand(intent, flags, startId)
}
// We don't actually start here. In order to properly capture boot errors we wait until an IPC connection is made
return super.onStartCommand(intent, flags, startId)
}
private fun startVpn() {
var ipNet: CIDR
try {
ipNet = mobileNebula.MobileNebula.parseCIDR(site!!.cert!!.cert.details.ips[0])
} catch (err: Exception) {
return announceExit(id, err.message ?: "$err")
return announceExit(site!!.id, err.message ?: "$err")
}
val builder = Builder()
@ -91,7 +100,7 @@ class NebulaVpnService : VpnService() {
} catch (e: Exception) {
Log.e(TAG, "Got an error $e")
vpnInterface?.close()
announceExit(id, e.message)
announceExit(site!!.id, e.message)
return stopSelf()
}
@ -130,7 +139,7 @@ class NebulaVpnService : VpnService() {
//TODO: how do we limit what can talk to us?
//TODO: Make sure replyTo is actually a messenger
when (msg.what) {
MSG_REGISTER_CLIENT -> mClients.add(msg.replyTo)
MSG_REGISTER_CLIENT -> register(msg)
MSG_UNREGISTER_CLIENT -> mClients.remove(msg.replyTo)
MSG_IS_RUNNING -> isRunning()
MSG_LIST_HOSTMAP -> listHostmap(msg)
@ -142,11 +151,29 @@ class NebulaVpnService : VpnService() {
}
}
private fun register(msg: Message) {
mClients.add(msg.replyTo)
if (!running) {
startVpn()
}
}
private fun protect(msg: Message): Boolean {
if (nebula != null) {
return false
}
msg.replyTo.send(Message.obtain(null, msg.what))
return true
}
private fun isRunning() {
sendSimple(MSG_IS_RUNNING, if (running) 1 else 0)
}
private fun listHostmap(msg: Message) {
if (protect(msg)) { return }
val res = nebula!!.listHostmap(msg.what == MSG_LIST_PENDING_HOSTMAP)
var m = Message.obtain(null, msg.what)
m.data.putString("data", res)
@ -154,6 +181,8 @@ class NebulaVpnService : VpnService() {
}
private fun getHostInfo(msg: Message) {
if (protect(msg)) { return }
val res = nebula!!.getHostInfoByVpnIp(msg.data.getString("vpnIp"), msg.data.getBoolean("pending"))
var m = Message.obtain(null, msg.what)
m.data.putString("data", res)
@ -161,6 +190,8 @@ class NebulaVpnService : VpnService() {
}
private fun setRemoteForTunnel(msg: Message) {
if (protect(msg)) { return }
val res = nebula!!.setRemoteForTunnel(msg.data.getString("vpnIp"), msg.data.getString("addr"))
var m = Message.obtain(null, msg.what)
m.data.putString("data", res)
@ -168,6 +199,8 @@ class NebulaVpnService : VpnService() {
}
private fun closeTunnel(msg: Message) {
if (protect(msg)) { return }
val res = nebula!!.closeTunnel(msg.data.getString("vpnIp"))
var m = Message.obtain(null, msg.what)
m.data.putBoolean("data", res)

View File

@ -15,7 +15,7 @@ data class SiteContainer(
val updater: SiteUpdater
)
class Sites(private var context: Context, private var engine: FlutterEngine) {
class Sites(private var engine: FlutterEngine) {
private var sites: HashMap<String, SiteContainer> = HashMap()
init {
@ -23,6 +23,7 @@ class Sites(private var context: Context, private var engine: FlutterEngine) {
}
fun refreshSites(activeSite: String? = null) {
val context = MainActivity.getContext()!!
val sitesDir = context.filesDir.resolve("sites")
if (!sitesDir.isDirectory) {
sitesDir.delete()
@ -57,7 +58,7 @@ class Sites(private var context: Context, private var engine: FlutterEngine) {
fun deleteSite(id: String) {
sites.remove(id)
val siteDir = context.filesDir.resolve("sites").resolve(id)
val siteDir = MainActivity.getContext()!!.filesDir.resolve("sites").resolve(id)
siteDir.deleteRecursively()
//TODO: make sure you stop the vpn
//TODO: make sure you relink the active site if this is the active site
@ -77,7 +78,6 @@ class SiteUpdater(private var site: Site, engine: FlutterEngine): EventChannel.S
site.connected = connected
site.status = status
val d = mapOf("connected" to site.connected, "status" to site.status)
if (err != null) {
eventSink?.error("", err, d)
} else {
@ -209,6 +209,14 @@ class Site {
ca = arrayOf()
errors.add("Error while loading certificate authorities: ${err.message}")
}
if (errors.isEmpty()) {
try {
mobileNebula.MobileNebula.testConfig(config, getKey(MainActivity.getContext()!!))
} catch (err: Exception) {
errors.add("Config test error: ${err.message}")
}
}
}
fun getKey(context: Context): String? {

View File

@ -173,9 +173,9 @@ struct Site: Codable {
init(proto: NETunnelProviderProtocol) throws {
let dict = proto.providerConfiguration
let rawConfig = dict?["config"] as? Data ?? Data()
let config = dict?["config"] as? Data ?? Data()
let decoder = JSONDecoder()
let incoming = try decoder.decode(IncomingSite.self, from: rawConfig)
let incoming = try decoder.decode(IncomingSite.self, from: config)
self.init(incoming: incoming)
}
@ -241,6 +241,22 @@ struct Site: Codable {
logVerbosity = incoming.logVerbosity ?? "info"
mtu = incoming.mtu ?? 1300
logFile = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.net.defined.mobileNebula")?.appendingPathComponent(id).appendingPathExtension("log").path
if (errors.isEmpty) {
do {
let encoder = JSONEncoder()
let rawConfig = try encoder.encode(incoming)
let key = try getKey()
let strConfig = String(data: rawConfig, encoding: .utf8)
var err: NSError?
MobileNebulaTestConfig(strConfig, key, &err)
if (err != nil) {
throw err!
}
} catch {
errors.append("Config test error: \(error.localizedDescription)")
}
}
}
// Gets the private key from the keystore, we don't always need it in memory

View File

@ -529,7 +529,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 0.0.31;
MARKETING_VERSION = 0.0.33;
PRODUCT_BUNDLE_IDENTIFIER = net.defined.mobileNebula;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -559,7 +559,7 @@
INFOPLIST_FILE = NebulaNetworkExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 0.0.31;
MARKETING_VERSION = 0.0.33;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = "";
@ -594,7 +594,7 @@
INFOPLIST_FILE = NebulaNetworkExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 0.0.31;
MARKETING_VERSION = 0.0.33;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = net.defined.mobileNebula.NebulaNetworkExtension;
@ -626,7 +626,7 @@
INFOPLIST_FILE = NebulaNetworkExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 0.0.31;
MARKETING_VERSION = 0.0.33;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = net.defined.mobileNebula.NebulaNetworkExtension;
@ -768,7 +768,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 0.0.31;
MARKETING_VERSION = 0.0.33;
PRODUCT_BUNDLE_IDENTIFIER = net.defined.mobileNebula;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -801,7 +801,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 0.0.31;
MARKETING_VERSION = 0.0.33;
PRODUCT_BUNDLE_IDENTIFIER = net.defined.mobileNebula;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";

View File

@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@ -38,8 +36,8 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@ -61,8 +59,6 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"

View File

@ -3,17 +3,17 @@ package mobileNebula
type config struct {
PKI configPKI `yaml:"pki"`
StaticHostmap map[string][]string `yaml:"static_host_map"`
Lighthouse configLighthouse `yaml:"lighthouse"`
Listen configListen `yaml:"listen"`
Punchy configPunchy `yaml:"punchy"`
Cipher string `yaml:"cipher"`
LocalRange string `yaml:"local_range"`
SSHD configSSHD `yaml:"sshd"`
Tun configTun `yaml:"tun"`
Logging configLogging `yaml:"logging"`
Stats configStats `yaml:"stats"`
Handshakes configHandshakes `yaml:"handshakes"`
Firewall configFirewall `yaml:"firewall"`
Lighthouse configLighthouse `yaml:"lighthouse"`
Listen configListen `yaml:"listen"`
Punchy configPunchy `yaml:"punchy"`
Cipher string `yaml:"cipher"`
LocalRange string `yaml:"local_range"`
SSHD configSSHD `yaml:"sshd"`
Tun configTun `yaml:"tun"`
Logging configLogging `yaml:"logging"`
Stats configStats `yaml:"stats"`
Handshakes configHandshakes `yaml:"handshakes"`
Firewall configFirewall `yaml:"firewall"`
}
func newConfig() *config {

View File

@ -45,8 +45,10 @@ func NewNebula(configData string, key string, logFile string, tunFd int) (*Nebul
if err != nil {
switch v := err.(type) {
case nebula.ContextualError:
return nil, v.RealError
v.Log(l)
return nil, v.Unwrap()
default:
l.WithError(err).Error("Failed to start")
return nil, err
}
}
@ -81,7 +83,7 @@ func (n *Nebula) ListHostmap(pending bool) (string, error) {
}
func (n *Nebula) GetHostInfoByVpnIp(vpnIp string, pending bool) (string, error) {
b, err := json.Marshal(n.c.GetHostInfoByVpnIp(stringIpToInt(vpnIp), pending))
b, err := json.Marshal(n.c.GetHostInfoByVpnIP(stringIpToInt(vpnIp), pending))
if err != nil {
return "", err
}
@ -113,4 +115,4 @@ func stringIpToInt(ip string) uint32 {
return binary.BigEndian.Uint32(n[12:16])
}
return binary.BigEndian.Uint32(n)
}
}

View File

@ -95,6 +95,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/vishvananda/netlink v1.0.1-0.20190522153524-00009fb8606a h1:Bt1IVPhiCDMqwGrc2nnbIN4QKvJGx6SK2NzWBmW00ao=
github.com/vishvananda/netlink v1.0.1-0.20190522153524-00009fb8606a/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
@ -156,3 +157,4 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,6 +1,7 @@
package mobileNebula
import (
"bytes"
"crypto/rand"
"encoding/json"
"fmt"
@ -9,6 +10,7 @@ import (
"strings"
"time"
"github.com/sirupsen/logrus"
"github.com/slackhq/nebula"
"github.com/slackhq/nebula/cert"
"golang.org/x/crypto/curve25519"
@ -108,6 +110,39 @@ func RenderConfig(configData string, key string) (string, error) {
return string(finalConfig), nil
}
func TestConfig(configData string, key string) error {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
yamlConfig, err := RenderConfig(configData, key)
if err != nil {
return err
}
config := nebula.NewConfig()
err = config.LoadString(yamlConfig)
if err != nil {
return fmt.Errorf("failed to load config: %s", err)
}
// We don't want to leak the config into the system logs
l := logrus.New()
l.SetOutput(bytes.NewBuffer([]byte{}))
_, err = nebula.Main(config, true, "", l, nil)
if err != nil {
switch v := err.(type) {
case nebula.ContextualError:
return v.Unwrap()
default:
return err
}
}
return nil
}
func GetConfigSetting(configData string, setting string) string {
config := nebula.NewConfig()
config.LoadString(configData)