tun device work fine

This commit is contained in:
Kr328 committed 2019-11-19 14:36:17 +08:00
1 parent 6cecb23dd8
commit e8a3335e21
11 files changed
+165 -50

No files matched your search

@@ -3,5 +3,5 @@ package com.github.kr328.clash;
interface IClashService {
void loadProfile(String path);
void startTunDevice(int fd, int mtu);
void startTunDevice(in ParcelFileDescriptor fd, int mtu);
}
@@ -2,10 +2,14 @@ package com.github.kr328.clash
import android.app.Service
import android.content.Intent
import android.net.Uri
import android.os.IBinder
import android.os.Parcel
import android.os.ParcelFileDescriptor
import android.util.Log
import com.github.kr328.clash.core.Clash
import com.github.kr328.clash.core.ClashProcess
import java.io.FileDescriptor
import kotlin.concurrent.thread
class ClashService : Service() {
@@ -15,12 +19,12 @@ class ClashService : Service() {
private lateinit var clash: Clash
private inner class ClashServiceImpl : IClashService.Stub() {
override fun loadProfile(path: String?) {
override fun loadProfile(url: String?) {
clash.loadProfile(Uri.parse(url))
}
override fun startTunDevice(fd: Int, mtu: Int) {
override fun startTunDevice(fd: ParcelFileDescriptor, mtu: Int) {
clash.startTunDevice(fd.fileDescriptor, mtu)
}
}
@@ -31,6 +35,7 @@ class ClashService : Service() {
filesDir.resolve("clash"),
cacheDir.resolve("clash_controller"))
Log.d(TAG, "ping " + clash.ping())
thread {
clash.exec()
@@ -44,8 +49,6 @@ class ClashService : Service() {
}
override fun onDestroy() {
super.onDestroy()
}
}
@@ -8,13 +8,9 @@ import android.content.ServiceConnection
import android.net.VpnService
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import java.io.FileOutputStream
import java.lang.Exception
import java.lang.RuntimeException
import kotlin.concurrent.thread
class MainActivity : AppCompatActivity() {
@@ -23,7 +19,7 @@ class MainActivity : AppCompatActivity() {
private const val VPN_REQUEST = 234
}
private val connection = object: ServiceConnection {
private val connection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
Toast.makeText(this@MainActivity, "Disconnected", Toast.LENGTH_LONG).show()
}
@@ -34,11 +30,8 @@ class MainActivity : AppCompatActivity() {
clash.loadProfile(cacheDir.resolve("config.yaml").absolutePath)
// VpnService.prepare(this@MainActivity)?.apply {
// startActivityForResult(this, VPN_REQUEST)
// } ?: startService(Intent(this@MainActivity, TunService::class.java))
}
catch (e: Exception) {
} catch (e: Exception) {
runOnUiThread {
Toast.makeText(this@MainActivity, e.toString(), Toast.LENGTH_LONG).show()
}
@@ -57,23 +50,28 @@ class MainActivity : AppCompatActivity() {
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if ( requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK ) {
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
Toast.makeText(this, intent?.dataString, Toast.LENGTH_LONG).show()
thread {
FileOutputStream(cacheDir.resolve("config.yaml")).use {
contentResolver.openInputStream(data?.data ?: throw RuntimeException())?.apply { copyTo(it) }?.close()
}
bindService(Intent(this, ClashService::class.java), object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
Toast.makeText(this@MainActivity, "Disconnected", Toast.LENGTH_LONG).show()
}
//startService(Intent(this, ClashService::class.java))
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
IClashService.Stub.asInterface(service)?.loadProfile(data?.dataString)
bindService(Intent(this, ClashService::class.java), connection, Context.BIND_AUTO_CREATE)
VpnService.prepare(this@MainActivity)?.apply {
startActivityForResult(this, VPN_REQUEST)
} ?: startService(Intent(this@MainActivity, TunService::class.java))
}
}, Context.BIND_AUTO_CREATE)
}
}
else if ( requestCode == VPN_REQUEST && resultCode == Activity.RESULT_OK ) {
} else if (requestCode == VPN_REQUEST && resultCode == Activity.RESULT_OK) {
startService(Intent(this, TunService::class.java))
}
else {
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
@@ -18,7 +18,7 @@ class TunService : VpnService() {
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
IClashService.Stub.asInterface(service)?.startTunDevice(fileDescriptor.fd, 1500) ?: stopSelf()
IClashService.Stub.asInterface(service)?.startTunDevice(fileDescriptor, 1500) ?: stopSelf()
}
}
@@ -35,7 +35,7 @@ class TunService : VpnService() {
bindService(Intent(this, ClashService::class.java), connection, Context.BIND_AUTO_CREATE)
return Service.START_STICKY
return Service.START_NOT_STICKY
}
override fun onDestroy() {
+3 -3
View File
@@ -23,9 +23,9 @@ func main() {
} else {
return
}
if err := server.Start(os.Args[1]) ; err != nil {
fmt.Println("[CONTROLLER] ERROR={"+ err.Error() +"}")
if err := server.Start(os.Args[1]); err != nil {
fmt.Println("[CONTROLLER] ERROR={" + err.Error() + "}")
return
}
+6 -4
View File
@@ -52,10 +52,12 @@ func LoadDefault() {
Proxies: map[string]constant.Proxy{},
}
global, _ := adapters.NewSelector("GLOBAL", make([]constant.Proxy, 0))
reject := adapters.NewProxy(adapters.NewReject())
direct := adapters.NewProxy(adapters.NewDirect())
global, _ := adapters.NewSelector("GLOBAL", []constant.Proxy{direct})
defaultC.Proxies["DIRECT"] = adapters.NewProxy(adapters.NewDirect())
defaultC.Proxies["REJECT"] = adapters.NewProxy(adapters.NewReject())
defaultC.Proxies["DIRECT"] = direct
defaultC.Proxies["REJECT"] = reject
defaultC.Proxies["GLOBAL"] = adapters.NewProxy(global)
executor.ApplyConfig(defaultC, true)
@@ -67,7 +69,7 @@ func LoadFromFile(path string) error {
if err != nil {
return err
}
executor.ApplyConfig(cfg, true)
return nil
}
+4
View File
@@ -12,6 +12,8 @@ import (
func handleProfileDefault(client *net.UnixConn) {
profile.LoadDefault()
log.Infoln("Profile default loaded")
client.Close()
}
@@ -46,5 +48,7 @@ func handleProfileReload(client *net.UnixConn) {
binary.Write(client, binary.BigEndian, uint32(0))
}
log.Infoln("Profile " + payload.Path + " loaded")
client.Close()
}
+10 -6
View File
@@ -8,17 +8,21 @@ import (
)
const (
commandPing = 0
commandTunStart = 1
commandTunStop = 2
commandPing = 0
commandTunStart = 1
commandTunStop = 2
commandProfileDefault = 3
commandProfileReload = 4
)
var handlers map[int]func(*net.UnixConn) = make(map[int]func(*net.UnixConn))
func init() {
handlers[commandPing] = handlePing // ping.go
handlers[commandTunStart] = handleTunStart // tun.go
handlers[commandTunStop] = handleTunStop // tun.go
handlers[commandPing] = handlePing // ping.go
handlers[commandTunStart] = handleTunStart // tun.go
handlers[commandTunStop] = handleTunStop // tun.go
handlers[commandProfileDefault] = handleProfileDefault // profile.go
handlers[commandProfileReload] = handleProfileReload // profile.go
}
// Start local control server
+7 -4
View File
@@ -15,9 +15,8 @@ const (
func handleTunStart(client *net.UnixConn) {
buffer := make([]byte, unix.CmsgLen(4*1))
data := make([]byte, 8)
_, noob, _, _, err := client.ReadMsgUnix(data, buffer)
_, noob, _, _, err := client.ReadMsgUnix(nil, buffer)
if err != nil {
log.Warnln("Read tun socket failure, %s", err.Error())
return
@@ -35,9 +34,13 @@ func handleTunStart(client *net.UnixConn) {
return
}
mtu := binary.BigEndian.Uint32(data)
var mtu uint32
var end uint32
if binary.BigEndian.Uint32(data[4:]) != tunCommandEnd {
binary.Read(client, binary.BigEndian, &mtu)
binary.Read(client, binary.BigEndian, &end)
if end != tunCommandEnd {
log.Warnln("Invalid tun command end")
return
}
@@ -1,9 +1,108 @@
package com.github.kr328.clash.core
import android.content.Context
import java.io.File
import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.net.Uri
import android.os.ParcelFileDescriptor
import android.util.Log
import org.json.JSONObject
import java.io.*
class Clash(context: Context, clashDir: File, controllerPath: File) :
class Clash(private val context: Context, clashDir: File, private val controllerPath: File) :
ClashProcess(context, clashDir, controllerPath) {
companion object {
const val COMMAND_PING = 0
const val COMMAND_TUN_START = 1
const val COMMAND_TUN_STOP = 2
const val COMMAND_PROFILE_DEFAULT = 3
const val COMMAND_PROFILE_RELOAD = 4
const val PING_REPLY = 233
}
private val cacheFile = context.cacheDir.resolve("config.yaml")
fun ping(): Boolean {
try {
return runControl(COMMAND_PING) {_, input, _ ->
input.readInt() == PING_REPLY
}
}
catch (e: IOException) {
Log.w(TAG, "Clash ping failure", e)
}
return false
}
@Throws(IOException::class)
@Synchronized
fun loadProfile(url: Uri) {
synchronized(cacheFile) {
context.contentResolver.openInputStream(url)?.use {
it.copyTo(FileOutputStream(cacheFile))
}
runControl(COMMAND_PROFILE_RELOAD) {_, input, output ->
output.writeString(JSONObject().apply {
put("path", cacheFile.absolutePath)
}.toString())
val result = input.readString()
if (result.isNotEmpty()) {
throw IOException(JSONObject(result).getString("error"))
}
}
}
}
@Throws(IOException::class)
fun startTunDevice(fd: FileDescriptor, mtu: Int) {
runControl(COMMAND_TUN_START) { socket, _, output ->
socket.setFileDescriptorsForSend(arrayOf(fd))
socket.outputStream.write(0)
socket.outputStream.flush()
output.writeInt(mtu)
output.writeInt(0x243)
}
}
private fun <R>runControl(command: Int, block: (LocalSocket, DataInputStream, DataOutputStream) -> R): R {
val socket = LocalSocket()
socket.connect(LocalSocketAddress(controllerPath.absolutePath, LocalSocketAddress.Namespace.FILESYSTEM))
val input = DataInputStream(socket.inputStream)
val output = DataOutputStream(socket.outputStream)
output.writeInt(command)
val result = block(socket, input, output)
try {
input.close()
output.close()
socket.close()
}
catch (ignore: IOException) {
}
return result
}
private fun DataOutputStream.writeString(string: String) {
this.writeInt(string.length)
this.writeBytes(string)
}
private fun DataInputStream.readString(): String {
val len = this.readInt()
val buffer = ByteArray(len)
this.read(buffer)
return String(buffer)
}
}
@@ -5,9 +5,9 @@ import android.util.Log
import java.io.File
import java.io.IOException
abstract class ClashProcess(context: Context, clashDir: File, private val controllerPath: File) {
abstract class ClashProcess(context: Context, clashDir: File, controllerPath: File) {
companion object {
private const val TAG = "ClashForAndroid"
const val TAG = "ClashForAndroid"
private const val CLASH_COMMAND = "\"{CLASH}\" \"{CONTROLLER}\" 2>&1"
private val PID_PATTERN = Regex("PID=(\\d+)")
private const val CONTROLLER_STATUS_PREFIX = "[CONTROLLER]"
@@ -57,6 +57,8 @@ abstract class ClashProcess(context: Context, clashDir: File, private val contro
if ( error != null )
throw IOException("Controller: $error")
break
}
}