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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="io.github.chronosx88.yggdrasil">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
@ -14,17 +15,12 @@
|
||||
<service
|
||||
android:name=".YggdrasilTunService"
|
||||
android:enabled="true"
|
||||
android:exported="true"></service>
|
||||
<service
|
||||
android:name=".YggdrasilService"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
@ -1,11 +1,13 @@
|
||||
package io.github.chronosx88.yggdrasil
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.VpnService
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private var isYggStarted = false
|
||||
|
||||
@ -16,12 +18,18 @@ class MainActivity : AppCompatActivity() {
|
||||
val connectButton = findViewById<Button>(R.id.connect_button)
|
||||
connectButton.setOnClickListener {
|
||||
if(!isYggStarted) {
|
||||
VpnService.prepare(this)
|
||||
val intent = Intent(this, YggdrasilTunService::class.java)
|
||||
startService(intent)
|
||||
connectButton.text = "Disconnect"
|
||||
isYggStarted = true
|
||||
// Prepare to establish a VPN connection.
|
||||
// This method returns null if the VPN application is already prepared
|
||||
// or if the user has previously consented to the VPN application.
|
||||
// Otherwise, it returns an Intent to a system activity.
|
||||
val vpnIntent = VpnService.prepare(this)
|
||||
if (vpnIntent != null) startActivityForResult(
|
||||
vpnIntent,
|
||||
0x0F
|
||||
)
|
||||
else onActivityResult(0x0F, Activity.RESULT_OK, null)
|
||||
} else {
|
||||
// FIXME fix this shit, this code doesn't stop service for some reasons
|
||||
val intent = Intent(this, YggdrasilTunService::class.java)
|
||||
stopService(intent)
|
||||
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()
|
||||
|
||||
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 {
|
||||
val nativeConfig = NodeConfig()
|
||||
nativeConfig.adminListen = config.adminListen
|
||||
@ -65,35 +32,3 @@ fun Context.saveYggConfig(config: Config) {
|
||||
val configFile = File(filesDir, "yggdrasil.conf")
|
||||
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.os.ParcelFileDescriptor
|
||||
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 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() {
|
||||
@ -13,6 +24,11 @@ class YggdrasilTunService : VpnService() {
|
||||
private var isRunning: Boolean = false
|
||||
}
|
||||
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 {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
@ -26,41 +42,85 @@ class YggdrasilTunService : VpnService() {
|
||||
}
|
||||
|
||||
private fun setupTunInterface() {
|
||||
if(!yggBin.exists()) {
|
||||
installBinary()
|
||||
}
|
||||
|
||||
val builder = Builder()
|
||||
val ygg = Yggdrasil()
|
||||
var configJson = Mobile.generateConfigJSON()
|
||||
val gson = Gson()
|
||||
|
||||
val yggTmp = Yggdrasil()
|
||||
val tempYggConfig = getYggConfig()
|
||||
tempYggConfig.ifName = "none"
|
||||
yggTmp.startJSON(Gson().toJson(tempYggConfig).toByteArray())
|
||||
val address = yggTmp.addressString // hack for getting generic ipv6 string from NodeID
|
||||
var config = gson.fromJson(String(configJson), Map::class.java).toMutableMap()
|
||||
config = fixConfig(config)
|
||||
configJson = gson.toJson(config).toByteArray()
|
||||
|
||||
yggConduitEndpoint = ygg.startJSON(configJson)
|
||||
val address = ygg.addressString // hack for getting generic ipv6 string from NodeID
|
||||
|
||||
tunInterface = builder
|
||||
.addAddress(address, 7)
|
||||
.addRoute("0200::", 7)
|
||||
.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() {
|
||||
val intent = Intent(this, YggdrasilService::class.java)
|
||||
intent.action = "start"
|
||||
startService(intent)
|
||||
private fun fixConfig(config: MutableMap<Any?, Any?>): MutableMap<Any?, Any?> {
|
||||
config["Listen"] = ""
|
||||
config["AdminListen"] = "tcp://localhost:9001"
|
||||
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() {
|
||||
val intent = Intent(this, YggdrasilService::class.java)
|
||||
stopService(intent)
|
||||
private fun readPacketsFromTun() {
|
||||
if(tunInputStream != null) {
|
||||
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() {
|
||||
super.onRevoke()
|
||||
isRunning = false
|
||||
stopYggdrasilService()
|
||||
readCoroutine.cancel()
|
||||
writeCoroutine.cancel()
|
||||
tunInterface!!.close()
|
||||
tunInterface = null
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package io.github.chronosx88.yggdrasil.models.config
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
// FIXME This is old config scheme
|
||||
data class Config (
|
||||
@SerializedName("peers") var peers : List<String>,
|
||||
@SerializedName("interfacePeers") var interfacePeers : Map<String, List<String>>,
|
||||
|
Loading…
Reference in New Issue
Block a user