Shadowsocks protocol added

This commit is contained in:
aman 2022-04-26 23:49:20 +05:30
parent 59b4bf5267
commit 29656fb9a6
22 changed files with 791 additions and 664 deletions

View file

@ -6,18 +6,137 @@ package org.amnezia.vpn
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.content.pm.PackageManager
import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.net.Network
import android.net.ProxyInfo
import android.os.ParcelFileDescriptor
import android.os.*
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import com.wireguard.android.util.SharedLibraryLoader
import com.wireguard.config.*
import com.wireguard.crypto.Key
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.amnezia.vpn.shadowsocks.core.Core
import org.amnezia.vpn.shadowsocks.core.R
import org.amnezia.vpn.shadowsocks.core.VpnRequestActivity
import org.amnezia.vpn.shadowsocks.core.acl.Acl
import org.amnezia.vpn.shadowsocks.core.bg.*
import org.amnezia.vpn.shadowsocks.core.database.Profile
import org.amnezia.vpn.shadowsocks.core.database.ProfileManager
import org.amnezia.vpn.shadowsocks.core.net.ConcurrentLocalSocketListener
import org.amnezia.vpn.shadowsocks.core.net.DefaultNetworkListener
import org.amnezia.vpn.shadowsocks.core.net.Subnet
import org.amnezia.vpn.shadowsocks.core.preference.DataStore
import org.amnezia.vpn.shadowsocks.core.utils.Key.modeVpn
import org.amnezia.vpn.shadowsocks.core.utils.printLog
import org.json.JSONObject
import java.io.Closeable
import java.io.File
import java.io.FileDescriptor
import java.io.IOException
import android.net.VpnService as BaseVpnService
class VPNService : BaseVpnService(), LocalDnsService.Interface {
override val data = BaseService.Data(this)
override val tag: String get() = "VPNService"
// override fun createNotification(profileName: String): ServiceNotification =
// ServiceNotification(this, profileName, "service-vpn")
private var conn: ParcelFileDescriptor? = null
private var worker: ProtectWorker? = null
private var active = false
private var metered = false
private var underlyingNetwork: Network? = null
set(value) {
field = value
if (active && Build.VERSION.SDK_INT >= 22) setUnderlyingNetworks(underlyingNetworks)
}
private val underlyingNetworks
get() =
// clearing underlyingNetworks makes Android 9+ consider the network to be metered
if (Build.VERSION.SDK_INT >= 28 && metered) null else underlyingNetwork?.let {
arrayOf(
it
)
}
val handler = Handler(Looper.getMainLooper())
var runnable: Runnable = object : Runnable {
override fun run() {
if (mProtocol.equals("shadowsocks", true)) {
Log.e(tag, "run: -----------------: ${data.state}")
when (data.state) {
BaseService.State.Connected -> {
currentTunnelHandle = 1
isUp = true
}
BaseService.State.Stopped -> {
currentTunnelHandle = -1
isUp = false
}
else -> {
}
}
}
handler.postDelayed(this, 1000L) //wait 4 sec and run again
}
}
fun stopTest() {
handler.removeCallbacks(runnable)
}
fun startTest() {
handler.postDelayed(runnable, 0) //wait 0 ms and run
}
companion object {
private const val VPN_MTU = 1500
private const val PRIVATE_VLAN4_CLIENT = "172.19.0.1"
private const val PRIVATE_VLAN4_ROUTER = "172.19.0.2"
private const val PRIVATE_VLAN6_CLIENT = "fdfe:dcba:9876::1"
private const val PRIVATE_VLAN6_ROUTER = "fdfe:dcba:9876::2"
/**
* https://android.googlesource.com/platform/prebuilts/runtime/+/94fec32/appcompat/hiddenapi-light-greylist.txt#9466
*/
private val getInt = FileDescriptor::class.java.getDeclaredMethod("getInt$")
@JvmStatic
fun startService(c: Context) {
c.applicationContext.startService(
Intent(c.applicationContext, VPNService::class.java).apply {
putExtra("startOnly", true)
})
}
@JvmStatic
private external fun wgGetConfig(handle: Int): String?
@JvmStatic
private external fun wgGetSocketV4(handle: Int): Int
@JvmStatic
private external fun wgGetSocketV6(handle: Int): Int
@JvmStatic
private external fun wgTurnOff(handle: Int)
@JvmStatic
private external fun wgTurnOn(ifName: String, tunFd: Int, settings: String): Int
@JvmStatic
private external fun wgVersion(): String?
}
class VPNService : android.net.VpnService() {
private val tag = "VPNService"
private var mBinder: VPNServiceBinder = VPNServiceBinder(this)
private var mConfig: JSONObject? = null
private var mProtocol: String? = null
@ -26,7 +145,11 @@ class VPNService : android.net.VpnService() {
private var mbuilder: Builder = Builder()
private var mOpenVPNThreadv3: OpenVPNThreadv3? = null
private var currentTunnelHandle = -1
var currentTunnelHandle = -1
private var intent: Intent? = null
private var flags = 0
private var startId = 0
fun init() {
if (mAlreadyInitialised) {
@ -41,8 +164,16 @@ class VPNService : android.net.VpnService() {
mAlreadyInitialised = true
}
override fun onCreate() {
super.onCreate()
// Log.v(tag, "Aman: onCreate....................")
// Log.v(tag, "Aman: onCreate....................")
// Log.v(tag, "Aman: onCreate....................")
// NotificationUtil.show(this) // Go foreground
}
override fun onUnbind(intent: Intent?): Boolean {
Log.v(tag, "Got Unbind request")
Log.v(tag, "Aman: onUnbind....................")
if (!isUp) {
// If the Qt Client got closed while we were not connected
// we do not need to stay as a foreground service.
@ -55,9 +186,21 @@ class VPNService : android.net.VpnService() {
* EntryPoint for the Service, gets Called when AndroidController.cpp
* calles bindService. Returns the [VPNServiceBinder] so QT can send Requests to it.
*/
override fun onBind(intent: Intent?): IBinder? {
Log.v(tag, "Got Bind request")
init()
override fun onBind(intent: Intent): IBinder {
Log.v(tag, "Aman: onBind....................")
when (mProtocol) {
"shadowsocks" -> {
when (intent.action) {
SERVICE_INTERFACE -> super<BaseVpnService>.onBind(intent)
else -> super<LocalDnsService.Interface>.onBind(intent)
}
startTest()
}
else -> {
init()
}
}
return mBinder
}
@ -67,11 +210,16 @@ class VPNService : android.net.VpnService() {
* or from Booting the device and having "connect on boot" enabled.
*/
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.v(tag, "Aman: onStartCommand....................")
this.intent = intent
this.flags = flags
this.startId = startId
init()
intent?.let {
if (intent.getBooleanExtra("startOnly", false)) {
if (!isUp && intent.getBooleanExtra("startOnly", false)) {
Log.i(tag, "Start only!")
return super.onStartCommand(intent, flags, startId)
return START_REDELIVER_INTENT
// return super<LocalDnsService.Interface>.onStartCommand(intent, flags, startId)
}
}
// This start is from always-on
@ -81,18 +229,39 @@ class VPNService : android.net.VpnService() {
val lastConfString = prefs.getString("lastConf", "")
if (lastConfString.isNullOrEmpty()) {
// We have nothing to connect to -> Exit
Log.e(tag,"VPN service was triggered without defining a Server or having a tunnel")
return super.onStartCommand(intent, flags, startId)
Log.e(tag, "VPN service was triggered without defining a Server or having a tunnel")
return super<android.net.VpnService>.onStartCommand(intent, flags, startId)
}
this.mConfig = JSONObject(lastConfString)
}
return super.onStartCommand(intent, flags, startId)
mProtocol = mConfig!!.getString("protocol")
Log.e(tag, "mProtocol: $mProtocol")
if (mProtocol.equals("shadowsocks", true)) {
if (DataStore.serviceMode == modeVpn) {
if (prepare(this) != null) {
startActivity(
Intent(
this,
VpnRequestActivity::class.java
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} else {
Log.e(tag, "Else part enter")
// service?.startListeningForBandwidth(serviceCallback, 1000)
Log.e(tag, "test")
return super<LocalDnsService.Interface>.onStartCommand(intent, flags, startId)
}
}
stopRunner()
}
return START_REDELIVER_INTENT
}
// Invoked when the application is revoked.
// At this moment, the VPN interface is already deactivated by the system.
override fun onRevoke() {
Log.v(tag, "Aman: onRevoke....................")
this.turnOff()
super.onRevoke()
}
@ -145,6 +314,7 @@ class VPNService : android.net.VpnService() {
}
fun turnOn(json: JSONObject?): Int {
Log.v(tag, "Aman: turnOn....................")
if (!checkPermissions()) {
Log.e(tag, "turn on was called without no permissions present!")
isUp = false
@ -152,23 +322,31 @@ class VPNService : android.net.VpnService() {
}
Log.i(tag, "Permission okay")
mConfig = json!!
Log.i(tag, "Config: " + mConfig)
Log.i(tag, "Config: $mConfig")
mProtocol = mConfig!!.getString("protocol")
Log.i(tag, "Protocol: " + mProtocol)
Log.i(tag, "Protocol: $mProtocol")
when (mProtocol) {
"openvpn" -> startOpenVpn()
"wireguard" -> startWireGuard()
"shadowsocks" -> startShadowsocks()
"openvpn" -> {
startOpenVpn()
}
"wireguard" -> {
startWireGuard()
}
"shadowsocks" -> {
startShadowsocks()
startTest()
}
else -> {
Log.e(tag, "No protocol")
return 0
}
}
NotificationUtil.show(this) // Go foreground
NotificationUtil.show(this)
return 1
}
fun establish(): ParcelFileDescriptor? {
Log.v(tag, "Aman: establish....................")
mbuilder.allowFamily(OsConstants.AF_INET)
mbuilder.allowFamily(OsConstants.AF_INET6)
@ -208,7 +386,9 @@ class VPNService : android.net.VpnService() {
fun addHttpProxy(host: String, port: Int): Boolean {
val proxyInfo = ProxyInfo.buildDirectProxy(host, port)
Log.v(tag, "mbuilder.addHttpProxy($host, $port)")
mbuilder.setHttpProxy(proxyInfo)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mbuilder.setHttpProxy(proxyInfo)
}
return true
}
@ -218,19 +398,22 @@ class VPNService : android.net.VpnService() {
}
fun turnOff() {
Log.v(tag, "Try to disable tunnel")
Log.v(tag, "Aman: turnOff....................")
when (mProtocol) {
"wireguard" -> wgTurnOff(currentTunnelHandle)
"openvpn" -> ovpnTurnOff()
"shadowsocks" -> {
stopRunner(false)
stopTest()
}
else -> {
Log.e(tag, "No protocol")
}
}
currentTunnelHandle = -1
stopForeground(true)
isUp = false
stopSelf();
stopSelf()
}
@ -366,11 +549,89 @@ class VPNService : android.net.VpnService() {
private fun startShadowsocks() {
Log.e(tag, "startShadowsocks method enters")
if(mConfig != null) {
try {
if (mConfig != null) {
try {
Log.e(tag, "Config: $mConfig")
} catch(e: Exception) {
Log.e(tag, "Error in startShadowsocks: $e")
ProfileManager.clear()
val profile = Profile()
// val iter: Iterator<String> = mConfig!!.keys()
// while (iter.hasNext()) {
// val key = iter.next()
// try {
// val value: Any = mConfig!!.get(key)
// Log.i(tag, "startShadowsocks: $key : $value")
// } catch (e: JSONException) {
// // Something went wrong!
// }
// }
val shadowsocksConfig = mConfig?.getJSONObject("shadowsocks_config_data")
if (shadowsocksConfig?.has("name") == true) {
profile.name = shadowsocksConfig.getString("name")
} else {
profile.name = "amnezia"
}
if (shadowsocksConfig?.has("method") == true) {
profile.method = shadowsocksConfig.getString("method").toString()
}
if (shadowsocksConfig?.has("server") == true) {
profile.host = shadowsocksConfig.getString("server").toString()
}
if (shadowsocksConfig?.has("password") == true) {
profile.password = shadowsocksConfig.getString("password").toString()
}
if (shadowsocksConfig?.has("server_port") == true) {
profile.remotePort = shadowsocksConfig.getInt("server_port")
}
// if(mConfig?.has("local_port") == true) {
// profile. = mConfig?.getInt("local_port")
// }
// profile.name = "amnezia"
// profile.method = "chacha20-ietf-poly1305"
// profile.host = "de01-ss.sshocean.net"
// profile.password = "ZTZhN"
// profile.remotePort = 8388
profile.proxyApps = false
profile.bypass = false
profile.metered = false
profile.dirty = false
profile.ipv6 = true
DataStore.profileId = ProfileManager.createProfile(profile).id
val switchProfile = Core.switchProfile(DataStore.profileId)
Log.i(tag, "startShadowsocks: SwitchProfile: $switchProfile")
intent?.putExtra("startOnly", false)
onStartCommand(
intent,
flags,
startId
)
// startRunner()
// VpnManager.getInstance().run()
// VpnManager.getInstance()
// .setOnStatusChangeListener(object : VpnManager.OnStatusChangeListener {
// override fun onStatusChanged(state: BaseService.State) {
// when (state) {
// BaseService.State.Connected -> {
// isUp = true
// }
// BaseService.State.Stopped -> {
// isUp = false
// }
// else -> {}
// }
// }
//
// override fun onTrafficUpdated(profileId: Long, stats: TrafficStats) {
//
// }
// })
//// Core.startService()
} catch (e: Exception) {
Log.e(tag, "Error in startShadowsocks: $e")
}
} else {
Log.e(tag, "Invalid config file!!")
@ -386,19 +647,20 @@ class VPNService : android.net.VpnService() {
private fun startWireGuard() {
val wireguard_conf = buildWireugardConfig(mConfig!!)
Log.i(tag, "startWireGuard: wireguard_conf : $wireguard_conf")
if (currentTunnelHandle != -1) {
Log.e(tag, "Tunnel already up")
// Turn the tunnel down because this might be a switch
wgTurnOff(currentTunnelHandle)
}
val wgConfig: String = wireguard_conf!!.toWgUserspaceString()
val wgConfig: String = wireguard_conf.toWgUserspaceString()
val builder = Builder()
setupBuilder(wireguard_conf, builder)
builder.setSession("avpn0")
builder.setSession("Amnezia")
builder.establish().use { tun ->
if (tun == null) return
Log.i(tag, "Go backend " + wgVersion())
currentTunnelHandle = wgTurnOn("avpn0", tun.detachFd(), wgConfig)
currentTunnelHandle = wgTurnOn("Amnezia", tun.detachFd(), wgConfig)
}
if (currentTunnelHandle < 0) {
Log.e(tag, "Activation Error Code -> $currentTunnelHandle")
@ -417,31 +679,163 @@ class VPNService : android.net.VpnService() {
.apply()
}
companion object {
@JvmStatic
fun startService(c: Context) {
c.applicationContext.startService(
Intent(c.applicationContext, VPNService::class.java).apply {
putExtra("startOnly", true)
})
override suspend fun startProcesses() {
worker = ProtectWorker().apply { start() }
try {
Log.i(tag, "startProcesses: ------------------1")
super.startProcesses()
Log.i(tag, "startProcesses: ------------------2")
sendFd(startVpn())
Log.i(tag, "startProcesses: ------------------3")
} catch (e: Exception) {
e.printStackTrace()
}
@JvmStatic
private external fun wgGetConfig(handle: Int): String?
@JvmStatic
private external fun wgGetSocketV4(handle: Int): Int
@JvmStatic
private external fun wgGetSocketV6(handle: Int): Int
@JvmStatic
private external fun wgTurnOff(handle: Int)
@JvmStatic
private external fun wgTurnOn(ifName: String, tunFd: Int, settings: String): Int
@JvmStatic
private external fun wgVersion(): String?
}
override fun killProcesses(scope: CoroutineScope) {
super.killProcesses(scope)
active = false
scope.launch { DefaultNetworkListener.stop(this) }
worker?.shutdown(scope)
worker = null
conn?.close()
conn = null
}
private suspend fun startVpn(): FileDescriptor {
val profile = data.proxy!!.profile
Log.i(tag, "startVpn: -----------------------1")
val builder = Builder()
.setConfigureIntent(Core.configureIntent(this))
.setSession(profile.formattedName)
.setMtu(VPN_MTU)
.addAddress(PRIVATE_VLAN4_CLIENT, 30)
.addDnsServer(PRIVATE_VLAN4_ROUTER)
Log.i(tag, "startVpn: -----------------------2")
if (profile.ipv6) {
builder.addAddress(PRIVATE_VLAN6_CLIENT, 126)
builder.addRoute("::", 0)
}
Log.i(tag, "startVpn: -----------------------3")
val me = packageName
if (profile.proxyApps) {
profile.individual.split('\n')
.filter { it != me }
.forEach {
try {
if (profile.bypass) builder.addDisallowedApplication(it)
else builder.addAllowedApplication(it)
} catch (ex: PackageManager.NameNotFoundException) {
printLog(ex)
}
}
if (profile.bypass) {
builder.addDisallowedApplication(me)
}
} else {
builder.addDisallowedApplication(me)
}
Log.i(tag, "startVpn: -----------------------4")
when (profile.route) {
Acl.ALL, Acl.BYPASS_CHN, Acl.CUSTOM_RULES -> builder.addRoute("0.0.0.0", 0)
else -> {
resources.getStringArray(R.array.bypass_private_route).forEach {
val subnet = Subnet.fromString(it)!!
builder.addRoute(subnet.address.hostAddress, subnet.prefixSize)
}
builder.addRoute(PRIVATE_VLAN4_ROUTER, 32)
}
}
Log.i(tag, "startVpn: -----------------------5")
metered = profile.metered
active = true // possible race condition here?
Log.i(tag, "startVpn: -----------------------6")
builder.setUnderlyingNetworks(underlyingNetworks)
Log.i(tag, "startVpn: -----------------------7")
val conn = builder.establish() ?: throw NullConnectionException()
Log.i(tag, "startVpn: -----------------------8")
this.conn = conn
Log.i(tag, "startVpn: -----------------------9")
val cmd = arrayListOf(
File(applicationInfo.nativeLibraryDir, Executable.TUN2SOCKS).absolutePath,
"--netif-ipaddr", PRIVATE_VLAN4_ROUTER,
"--socks-server-addr", "${DataStore.listenAddress}:${DataStore.portProxy}",
"--tunmtu", VPN_MTU.toString(),
"--sock-path", "sock_path",
"--dnsgw", "127.0.0.1:${DataStore.portLocalDns}",
"--loglevel", "warning"
)
Log.i(tag, "startVpn: -----------------------10")
if (profile.ipv6) {
cmd += "--netif-ip6addr"
cmd += PRIVATE_VLAN6_ROUTER
}
Log.i(tag, "startVpn: -----------------------11")
cmd += "--enable-udprelay"
Log.i(tag, "startVpn: -----------------------12")
data.processes!!.start(cmd, onRestartCallback = {
try {
sendFd(conn.fileDescriptor)
} catch (e: ErrnoException) {
e.printStackTrace()
stopRunner(false, e.message)
}
})
Log.i(tag, "startVpn: -----------------------13")
return conn.fileDescriptor
}
private suspend fun sendFd(fd: FileDescriptor) {
var tries = 0
val path = File(Core.deviceStorage.noBackupFilesDir, "sock_path").absolutePath
while (true) try {
delay(50L shl tries)
LocalSocket().use { localSocket ->
localSocket.connect(
LocalSocketAddress(
path,
LocalSocketAddress.Namespace.FILESYSTEM
)
)
localSocket.setFileDescriptorsForSend(arrayOf(fd))
localSocket.outputStream.write(42)
}
return
} catch (e: IOException) {
if (tries > 5) throw e
tries += 1
}
}
private inner class ProtectWorker : ConcurrentLocalSocketListener(
"ShadowsocksVpnThread",
File(Core.deviceStorage.noBackupFilesDir, "protect_path")
) {
override fun acceptInternal(socket: LocalSocket) {
socket.inputStream.read()
val fd = socket.ancillaryFileDescriptors!!.single()!!
CloseableFd(fd).use {
socket.outputStream.write(if (underlyingNetwork.let { network ->
if (network != null && Build.VERSION.SDK_INT >= 23) try {
network.bindSocket(fd)
true
} catch (e: IOException) {
// suppress ENONET (Machine is not on the network)
if ((e.cause as? ErrnoException)?.errno != 64) printLog(e)
false
} else protect(getInt.invoke(fd) as Int)
}) 0 else 1)
}
}
}
inner class NullConnectionException : NullPointerException() {
override fun getLocalizedMessage() = getString(R.string.reboot_required)
}
class CloseableFd(val fd: FileDescriptor) : Closeable {
override fun close() = Os.close(fd)
}
}