Make starting VPN service and running Yggdrasil (traffic hooks into Yggdrasil dummy interface)

This commit is contained in:
ChronosX88 2020-02-02 22:34:58 +04:00
parent 4e750afca7
commit 28b72376a7
Signed by: ChronosXYZ
GPG Key ID: 085A69A82C8C511A
6 changed files with 105 additions and 138 deletions

View File

@ -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" />

View File

@ -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
}
}
}

View File

@ -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")
}

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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>>,