mirror of
https://github.com/yggdrasil-network/yggdrasil-android.git
synced 2025-04-28 06:05:08 +03:00
255 lines
No EOL
8.2 KiB
Kotlin
255 lines
No EOL
8.2 KiB
Kotlin
package eu.neilalexander.yggdrasil
|
|
|
|
import android.content.*
|
|
import android.net.VpnService
|
|
import android.os.ParcelFileDescriptor
|
|
import android.system.OsConstants
|
|
import android.util.Log
|
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
|
import mobile.Yggdrasil
|
|
import java.io.FileInputStream
|
|
import java.io.FileOutputStream
|
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
import kotlin.concurrent.thread
|
|
|
|
|
|
private const val TAG = "PacketTunnelProvider"
|
|
|
|
class PacketTunnelProvider: VpnService() {
|
|
companion object {
|
|
const val RECEIVER_INTENT = "eu.neilalexander.yggdrasil.PacketTunnelProvider.MESSAGE"
|
|
|
|
const val ACTION_START = "eu.neilalexander.yggdrasil.PacketTunnelProvider.START"
|
|
const val ACTION_STOP = "eu.neilalexander.yggdrasil.PacketTunnelProvider.STOP"
|
|
}
|
|
|
|
private var yggdrasil = Yggdrasil()
|
|
private var started = AtomicBoolean()
|
|
|
|
private lateinit var config: ConfigurationProxy
|
|
|
|
private var readerThread: Thread? = null
|
|
private var writerThread: Thread? = null
|
|
private var updateThread: Thread? = null
|
|
|
|
private var parcel: ParcelFileDescriptor? = null
|
|
private var readerStream: FileInputStream? = null
|
|
private var writerStream: FileOutputStream? = null
|
|
|
|
override fun onCreate() {
|
|
super.onCreate()
|
|
config = ConfigurationProxy(applicationContext)
|
|
}
|
|
|
|
override fun onDestroy() {
|
|
super.onDestroy()
|
|
stop()
|
|
}
|
|
|
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
if (intent == null) {
|
|
Log.d(TAG, "Intent is null")
|
|
return START_NOT_STICKY
|
|
}
|
|
return when (intent.action ?: ACTION_STOP) {
|
|
ACTION_STOP -> {
|
|
Log.d(TAG, "Stopping...")
|
|
stop(); START_NOT_STICKY
|
|
}
|
|
else -> {
|
|
Log.d(TAG, "Starting...")
|
|
start(); START_STICKY
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun start() {
|
|
if (!started.compareAndSet(false, true)) {
|
|
return
|
|
}
|
|
|
|
Log.d(TAG, config.getJSON().toString())
|
|
yggdrasil.startJSON(config.getJSONByteArray())
|
|
|
|
val address = yggdrasil.addressString
|
|
val builder = Builder()
|
|
.addAddress(address, 7)
|
|
.addRoute("200::", 7)
|
|
// We do this to trick the DNS-resolver into thinking that we have "regular" IPv6,
|
|
// and therefore we need to resolve AAAA DNS-records.
|
|
// See: https://android.googlesource.com/platform/bionic/+/refs/heads/master/libc/dns/net/getaddrinfo.c#1935
|
|
// and: https://android.googlesource.com/platform/bionic/+/refs/heads/master/libc/dns/net/getaddrinfo.c#365
|
|
// If we don't do this the DNS-resolver just doesn't do DNS-requests with record type AAAA,
|
|
// and we can't use DNS with Yggdrasil addresses.
|
|
.addRoute("2000::", 128)
|
|
.allowFamily(OsConstants.AF_INET)
|
|
.allowBypass()
|
|
.setBlocking(true)
|
|
.setMtu(yggdrasil.mtu.toInt())
|
|
.setSession("Yggdrasil")
|
|
// On Android API 29+ apps can opt-in/out to using metered networks.
|
|
// If we don't set metered status of VPN it is considered as metered.
|
|
// If we set it to false, then it will inherit this status from underlying network.
|
|
// See: https://developer.android.com/reference/android/net/VpnService.Builder#setMetered(boolean)
|
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
|
|
builder.setMetered(false)
|
|
}
|
|
|
|
val preferences = androidx.preference.PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
|
val serverString = preferences.getString(KEY_DNS_SERVERS, "")
|
|
if (serverString!!.isNotEmpty()) {
|
|
val servers = serverString.split(",")
|
|
if (servers.isNotEmpty()) {
|
|
servers.forEach {
|
|
Log.i(TAG, "Using DNS server $it")
|
|
builder.addDnsServer(it)
|
|
}
|
|
}
|
|
}
|
|
if (preferences.getBoolean(KEY_ENABLE_CHROME_FIX, false)) {
|
|
builder.addRoute("2001:4860:4860::8888", 128)
|
|
}
|
|
|
|
parcel = builder.establish()
|
|
val parcel = parcel
|
|
if (parcel == null || !parcel.fileDescriptor.valid()) {
|
|
stop()
|
|
return
|
|
}
|
|
|
|
readerStream = FileInputStream(parcel.fileDescriptor)
|
|
writerStream = FileOutputStream(parcel.fileDescriptor)
|
|
|
|
readerThread = thread {
|
|
reader()
|
|
}
|
|
writerThread = thread {
|
|
writer()
|
|
}
|
|
updateThread = thread {
|
|
updater()
|
|
}
|
|
|
|
val intent = Intent(RECEIVER_INTENT)
|
|
intent.putExtra("type", "state")
|
|
intent.putExtra("started", true)
|
|
intent.putExtra("ip", yggdrasil.addressString)
|
|
intent.putExtra("subnet", yggdrasil.subnetString)
|
|
intent.putExtra("coords", yggdrasil.coordsString)
|
|
intent.putExtra("peers", yggdrasil.peersJSON)
|
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
|
|
}
|
|
|
|
private fun stop() {
|
|
if (!started.compareAndSet(true, false)) {
|
|
return
|
|
}
|
|
|
|
yggdrasil.stop()
|
|
|
|
readerStream?.let {
|
|
it.close()
|
|
readerStream = null
|
|
}
|
|
writerStream?.let {
|
|
it.close()
|
|
writerStream = null
|
|
}
|
|
parcel?.let {
|
|
it.close()
|
|
parcel = null
|
|
}
|
|
|
|
readerThread?.let {
|
|
it.interrupt()
|
|
readerThread = null
|
|
}
|
|
writerThread?.let {
|
|
it.interrupt()
|
|
writerThread = null
|
|
}
|
|
updateThread?.let {
|
|
it.interrupt()
|
|
updateThread = null
|
|
}
|
|
|
|
val intent = Intent(RECEIVER_INTENT)
|
|
intent.putExtra("type", "state")
|
|
intent.putExtra("started", false)
|
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
|
|
|
|
stopSelf()
|
|
}
|
|
|
|
private fun updater() {
|
|
updates@ while (started.get()) {
|
|
val intent = Intent(RECEIVER_INTENT)
|
|
intent.putExtra("type", "state")
|
|
intent.putExtra("started", true)
|
|
intent.putExtra("ip", yggdrasil.addressString)
|
|
intent.putExtra("subnet", yggdrasil.subnetString)
|
|
intent.putExtra("coords", yggdrasil.coordsString)
|
|
intent.putExtra("peers", yggdrasil.peersJSON)
|
|
intent.putExtra("dht", yggdrasil.dhtjson)
|
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
|
|
if (Thread.currentThread().isInterrupted) {
|
|
break@updates
|
|
}
|
|
try {
|
|
Thread.sleep(1000)
|
|
} catch (e: InterruptedException) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun writer() {
|
|
val buf = ByteArray(65535)
|
|
writes@ while (started.get()) {
|
|
val writerStream = writerStream
|
|
val writerThread = writerThread
|
|
if (writerThread == null || writerStream == null) {
|
|
break@writes
|
|
}
|
|
if (Thread.currentThread().isInterrupted || !writerStream.fd.valid()) {
|
|
break@writes
|
|
}
|
|
try {
|
|
val len = yggdrasil.recvBuffer(buf)
|
|
if (len > 0) {
|
|
writerStream.write(buf, 0, len.toInt())
|
|
}
|
|
} catch (e: Exception) {
|
|
break@writes
|
|
}
|
|
}
|
|
writerStream?.let {
|
|
it.close()
|
|
writerStream = null
|
|
}
|
|
}
|
|
|
|
private fun reader() {
|
|
val b = ByteArray(65535)
|
|
reads@ while (started.get()) {
|
|
val readerStream = readerStream
|
|
val readerThread = readerThread
|
|
if (readerThread == null || readerStream == null) {
|
|
break@reads
|
|
}
|
|
if (Thread.currentThread().isInterrupted ||!readerStream.fd.valid()) {
|
|
break@reads
|
|
}
|
|
try {
|
|
val n = readerStream.read(b)
|
|
yggdrasil.sendBuffer(b, n.toLong())
|
|
} catch (e: Exception) {
|
|
break@reads
|
|
}
|
|
}
|
|
readerStream?.let {
|
|
it.close()
|
|
readerStream = null
|
|
}
|
|
}
|
|
} |