mirror of
https://github.com/yggdrasil-network/crispa-android.git
synced 2024-11-09 20:11:01 +00:00
Make starting VPN service and running Yggdrasil (traffic hooks into Yggdrasil dummy interface)
This commit is contained in:
parent
4e750afca7
commit
28b72376a7
@ -2,7 +2,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="io.github.chronosx88.yggdrasil">
|
package="io.github.chronosx88.yggdrasil">
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@ -14,17 +15,12 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".YggdrasilTunService"
|
android:name=".YggdrasilTunService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true"></service>
|
|
||||||
<service
|
|
||||||
android:name=".YggdrasilService"
|
|
||||||
android:enabled="true"
|
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.net.VpnService"/>
|
<action android:name="android.net.VpnService"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<activity android:name=".MainActivity">
|
<activity android:name=".MainActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package io.github.chronosx88.yggdrasil
|
package io.github.chronosx88.yggdrasil
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private var isYggStarted = false
|
private var isYggStarted = false
|
||||||
|
|
||||||
@ -16,12 +18,18 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val connectButton = findViewById<Button>(R.id.connect_button)
|
val connectButton = findViewById<Button>(R.id.connect_button)
|
||||||
connectButton.setOnClickListener {
|
connectButton.setOnClickListener {
|
||||||
if(!isYggStarted) {
|
if(!isYggStarted) {
|
||||||
VpnService.prepare(this)
|
// Prepare to establish a VPN connection.
|
||||||
val intent = Intent(this, YggdrasilTunService::class.java)
|
// This method returns null if the VPN application is already prepared
|
||||||
startService(intent)
|
// or if the user has previously consented to the VPN application.
|
||||||
connectButton.text = "Disconnect"
|
// Otherwise, it returns an Intent to a system activity.
|
||||||
isYggStarted = true
|
val vpnIntent = VpnService.prepare(this)
|
||||||
|
if (vpnIntent != null) startActivityForResult(
|
||||||
|
vpnIntent,
|
||||||
|
0x0F
|
||||||
|
)
|
||||||
|
else onActivityResult(0x0F, Activity.RESULT_OK, null)
|
||||||
} else {
|
} else {
|
||||||
|
// FIXME fix this shit, this code doesn't stop service for some reasons
|
||||||
val intent = Intent(this, YggdrasilTunService::class.java)
|
val intent = Intent(this, YggdrasilTunService::class.java)
|
||||||
stopService(intent)
|
stopService(intent)
|
||||||
connectButton.text = "Connect"
|
connectButton.text = "Connect"
|
||||||
@ -29,4 +37,15 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
if (requestCode == 0x0F && resultCode == Activity.RESULT_OK) {
|
||||||
|
val intent = Intent(this, YggdrasilTunService::class.java)
|
||||||
|
startService(intent)
|
||||||
|
val connectButton = findViewById<Button>(R.id.connect_button)
|
||||||
|
connectButton.text = "Disconnect"
|
||||||
|
isYggStarted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,39 +13,6 @@ import java.lang.Runtime.getRuntime
|
|||||||
|
|
||||||
val gson = Gson()
|
val gson = Gson()
|
||||||
|
|
||||||
fun Context.execYgg(cmd: String) = getRuntime().exec(
|
|
||||||
"${yggBin.absolutePath} $cmd"
|
|
||||||
)
|
|
||||||
|
|
||||||
val Context.yggBin get() = File(filesDir, "yggdrasil-$YGGDRASIL_VERSION-linux-${CPU_ABI.let {
|
|
||||||
when {
|
|
||||||
it.contains("v7") -> "armhf"
|
|
||||||
it.contains("v8") -> "arm64"
|
|
||||||
else -> throw Exception("Unsupported ABI")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}")
|
|
||||||
|
|
||||||
@Throws(RuntimeException::class)
|
|
||||||
fun Context.getYggConfig(): Config {
|
|
||||||
val configFile = File(filesDir, "yggdrasil.conf")
|
|
||||||
if(!configFile.exists()) {
|
|
||||||
generateYggConfig()
|
|
||||||
}
|
|
||||||
val configStr = configFile.readText()
|
|
||||||
val configHjsonObject = JsonValue.readHjson(configStr)
|
|
||||||
return gson.fromJson(configHjsonObject.toString(Stringify.PLAIN), Config::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.generateYggConfig() {
|
|
||||||
val process = execYgg("-genconf > ${filesDir.absolutePath}/yggdrasil.conf")
|
|
||||||
process.waitFor()
|
|
||||||
val configBytes = process.inputStream.readBytes()
|
|
||||||
val configStr = String(configBytes)
|
|
||||||
val configFile = File(filesDir, "yggdrasil.conf")
|
|
||||||
configFile.writeText(configStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createNativeYggConfig(config: Config): NodeConfig {
|
fun createNativeYggConfig(config: Config): NodeConfig {
|
||||||
val nativeConfig = NodeConfig()
|
val nativeConfig = NodeConfig()
|
||||||
nativeConfig.adminListen = config.adminListen
|
nativeConfig.adminListen = config.adminListen
|
||||||
@ -64,36 +31,4 @@ fun Context.saveYggConfig(config: Config) {
|
|||||||
val configJson = gson.toJson(config)
|
val configJson = gson.toJson(config)
|
||||||
val configFile = File(filesDir, "yggdrasil.conf")
|
val configFile = File(filesDir, "yggdrasil.conf")
|
||||||
configFile.writeText(configJson)
|
configFile.writeText(configJson)
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.installBinary() {
|
|
||||||
val type = "yggdrasil-$YGGDRASIL_VERSION-linux-${CPU_ABI.let {
|
|
||||||
when{
|
|
||||||
it.contains("v8") -> "arm64"
|
|
||||||
it.contains("v7") -> "armhf"
|
|
||||||
else -> throw Exception("Unsupported ABI")
|
|
||||||
}
|
|
||||||
}}"
|
|
||||||
|
|
||||||
yggBin.apply {
|
|
||||||
delete()
|
|
||||||
createNewFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
val input = assets.open(type)
|
|
||||||
val output = yggBin.outputStream()
|
|
||||||
|
|
||||||
try {
|
|
||||||
input.copyTo(output)
|
|
||||||
} finally {
|
|
||||||
input.close(); output.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
yggBin.setExecutable(true)
|
|
||||||
|
|
||||||
val yggConfig = getYggConfig() // it generates config automatically
|
|
||||||
yggConfig.ifName = "tun0"
|
|
||||||
saveYggConfig(yggConfig)
|
|
||||||
|
|
||||||
Log.i("Utils", "# Binary installed successfully")
|
|
||||||
}
|
}
|
@ -1,44 +0,0 @@
|
|||||||
package io.github.chronosx88.yggdrasil
|
|
||||||
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.Intent
|
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
class YggdrasilService : Service() {
|
|
||||||
private val LOG_TAG: String = YggdrasilService::class.java.simpleName
|
|
||||||
|
|
||||||
override fun onBind(intent: Intent) = null
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
var daemon: Process? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun start() {
|
|
||||||
val process = execYgg("-useconffile ${filesDir.absolutePath}/yggdrasil.conf")
|
|
||||||
process.waitFor()
|
|
||||||
daemon = process
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stop() {
|
|
||||||
daemon?.destroy()
|
|
||||||
daemon = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
||||||
super.onStartCommand(intent, flags, startId)
|
|
||||||
when (intent?.action) {
|
|
||||||
"start" -> start()
|
|
||||||
"stop" -> stop()
|
|
||||||
"restart" -> {
|
|
||||||
stop(); start()
|
|
||||||
}
|
|
||||||
"exit" -> exitProcess(0)
|
|
||||||
}
|
|
||||||
return START_STICKY
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
stop()
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,7 +5,18 @@ import android.content.Intent
|
|||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import dummy.ConduitEndpoint
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import mobile.Mobile
|
||||||
import mobile.Yggdrasil
|
import mobile.Yggdrasil
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.experimental.or
|
||||||
|
|
||||||
|
|
||||||
class YggdrasilTunService : VpnService() {
|
class YggdrasilTunService : VpnService() {
|
||||||
@ -13,6 +24,11 @@ class YggdrasilTunService : VpnService() {
|
|||||||
private var isRunning: Boolean = false
|
private var isRunning: Boolean = false
|
||||||
}
|
}
|
||||||
private var tunInterface: ParcelFileDescriptor? = null
|
private var tunInterface: ParcelFileDescriptor? = null
|
||||||
|
private lateinit var yggConduitEndpoint: ConduitEndpoint
|
||||||
|
private var tunInputStream: InputStream? = null
|
||||||
|
private var tunOutputStream: OutputStream? = null
|
||||||
|
private lateinit var readCoroutine: CoroutineContext
|
||||||
|
private lateinit var writeCoroutine: CoroutineContext
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
super.onStartCommand(intent, flags, startId)
|
super.onStartCommand(intent, flags, startId)
|
||||||
@ -26,41 +42,85 @@ class YggdrasilTunService : VpnService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setupTunInterface() {
|
private fun setupTunInterface() {
|
||||||
if(!yggBin.exists()) {
|
|
||||||
installBinary()
|
|
||||||
}
|
|
||||||
|
|
||||||
val builder = Builder()
|
val builder = Builder()
|
||||||
|
val ygg = Yggdrasil()
|
||||||
|
var configJson = Mobile.generateConfigJSON()
|
||||||
|
val gson = Gson()
|
||||||
|
|
||||||
val yggTmp = Yggdrasil()
|
var config = gson.fromJson(String(configJson), Map::class.java).toMutableMap()
|
||||||
val tempYggConfig = getYggConfig()
|
config = fixConfig(config)
|
||||||
tempYggConfig.ifName = "none"
|
configJson = gson.toJson(config).toByteArray()
|
||||||
yggTmp.startJSON(Gson().toJson(tempYggConfig).toByteArray())
|
|
||||||
val address = yggTmp.addressString // hack for getting generic ipv6 string from NodeID
|
yggConduitEndpoint = ygg.startJSON(configJson)
|
||||||
|
val address = ygg.addressString // hack for getting generic ipv6 string from NodeID
|
||||||
|
|
||||||
tunInterface = builder
|
tunInterface = builder
|
||||||
.addAddress(address, 7)
|
.addAddress(address, 7)
|
||||||
.addRoute("0200::", 7)
|
.addRoute("0200::", 7)
|
||||||
.establish()
|
.establish()
|
||||||
|
|
||||||
createYggdrasilService()
|
tunInputStream = FileInputStream(tunInterface!!.fileDescriptor)
|
||||||
|
tunOutputStream = FileOutputStream(tunInterface!!.fileDescriptor)
|
||||||
|
readCoroutine = GlobalScope.launch {
|
||||||
|
// FIXME it will throw exception (bad file descriptor) when coroutine will be canceled
|
||||||
|
while (true) {
|
||||||
|
readPacketsFromTun()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeCoroutine = GlobalScope.launch {
|
||||||
|
while (true) {
|
||||||
|
writePacketsToTun()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createYggdrasilService() {
|
private fun fixConfig(config: MutableMap<Any?, Any?>): MutableMap<Any?, Any?> {
|
||||||
val intent = Intent(this, YggdrasilService::class.java)
|
config["Listen"] = ""
|
||||||
intent.action = "start"
|
config["AdminListen"] = "tcp://localhost:9001"
|
||||||
startService(intent)
|
config["IfName"] = "dummy"
|
||||||
|
(config["SessionFirewall"] as MutableMap<Any, Any>)["Enable"] = true
|
||||||
|
(config["SwitchOptions"] as MutableMap<Any, Any>)["MaxTotalQueueSize"] = 1048576
|
||||||
|
if (config["AutoStart"] == null) {
|
||||||
|
val tmpMap = emptyMap<String, Boolean>().toMutableMap()
|
||||||
|
tmpMap["WiFi"] = false
|
||||||
|
tmpMap["Mobile"] = false
|
||||||
|
config["AutoStart"] = tmpMap
|
||||||
|
}
|
||||||
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopYggdrasilService() {
|
private fun readPacketsFromTun() {
|
||||||
val intent = Intent(this, YggdrasilService::class.java)
|
if(tunInputStream != null) {
|
||||||
stopService(intent)
|
val buffer = ByteArray(1024)
|
||||||
|
tunInputStream!!.read(buffer)
|
||||||
|
if (!isBufferEmpty(buffer)) {
|
||||||
|
yggConduitEndpoint.send(buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isBufferEmpty(buffer: ByteArray): Boolean {
|
||||||
|
var sum: Byte = 0
|
||||||
|
for (i in buffer) {
|
||||||
|
sum.or(i)
|
||||||
|
}
|
||||||
|
return sum == 0.toByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writePacketsToTun() {
|
||||||
|
if(tunOutputStream != null) {
|
||||||
|
val buffer = yggConduitEndpoint.recv()
|
||||||
|
if (!isBufferEmpty(buffer)) {
|
||||||
|
tunOutputStream!!.write(buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRevoke() {
|
override fun onRevoke() {
|
||||||
super.onRevoke()
|
super.onRevoke()
|
||||||
isRunning = false
|
isRunning = false
|
||||||
stopYggdrasilService()
|
readCoroutine.cancel()
|
||||||
|
writeCoroutine.cancel()
|
||||||
tunInterface!!.close()
|
tunInterface!!.close()
|
||||||
tunInterface = null
|
tunInterface = null
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package io.github.chronosx88.yggdrasil.models.config
|
|||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
// FIXME This is old config scheme
|
||||||
data class Config (
|
data class Config (
|
||||||
@SerializedName("peers") var peers : List<String>,
|
@SerializedName("peers") var peers : List<String>,
|
||||||
@SerializedName("interfacePeers") var interfacePeers : Map<String, List<String>>,
|
@SerializedName("interfacePeers") var interfacePeers : Map<String, List<String>>,
|
||||||
|
Loading…
Reference in New Issue
Block a user