diff --git a/core/src/main/golang/main.go b/core/src/main/golang/main.go index dc46393..1ae3141 100644 --- a/core/src/main/golang/main.go +++ b/core/src/main/golang/main.go @@ -18,6 +18,9 @@ func main() { return } + // Redirect stderr to stdout + os.Stderr = os.Stdout + if cwd, err := os.Getwd(); err == nil { constant.SetHomeDir(cwd) } else { diff --git a/core/src/main/golang/server/profile.go b/core/src/main/golang/server/profile.go index fe1a394..b160552 100644 --- a/core/src/main/golang/server/profile.go +++ b/core/src/main/golang/server/profile.go @@ -13,8 +13,6 @@ func handleProfileDefault(client *net.UnixConn) { profile.LoadDefault() log.Infoln("Profile default loaded") - - client.Close() } func handleProfileReload(client *net.UnixConn) { @@ -49,6 +47,4 @@ func handleProfileReload(client *net.UnixConn) { } log.Infoln("Profile " + payload.Path + " loaded") - - client.Close() } diff --git a/core/src/main/golang/server/proxies.go b/core/src/main/golang/server/proxies.go new file mode 100644 index 0000000..669cdef --- /dev/null +++ b/core/src/main/golang/server/proxies.go @@ -0,0 +1,34 @@ +package server + +import ( + "encoding/json" + "net" + + "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/tunnel" +) + +func handleQueryProxies(client *net.UnixConn) { + proxies := tunnel.Instance().Proxies() + + var root struct { + proxies []map[string]interface{} `json:"proxies"` + } + + root.proxies = make([]map[string]interface{}, len(proxies)) + + for _, p := range proxies { + inner, err := p.MarshalJSON() + + if err != nil { + log.Warnln("MarshalJSON failure %s", err.Error()) + continue + } + + mapping := map[string]interface{}{} + json.Unmarshal(inner, &mapping) + + root.proxies = append(root.proxies, mapping) + } + +} diff --git a/core/src/main/golang/server/server.go b/core/src/main/golang/server/server.go index 96e9bf6..c66a4f1 100644 --- a/core/src/main/golang/server/server.go +++ b/core/src/main/golang/server/server.go @@ -13,6 +13,7 @@ const ( commandTunStop = 2 commandProfileDefault = 3 commandProfileReload = 4 + commandQueryProxies = 5 ) var handlers map[int]func(*net.UnixConn) = make(map[int]func(*net.UnixConn)) @@ -23,6 +24,7 @@ func init() { handlers[commandTunStop] = handleTunStop // tun.go handlers[commandProfileDefault] = handleProfileDefault // profile.go handlers[commandProfileReload] = handleProfileReload // profile.go + handlers[commandQueryProxies] = handleQueryProxies } // Start local control server @@ -64,4 +66,6 @@ func handleConnection(client *net.UnixConn) { } handler(client) + + client.Close() } diff --git a/core/src/main/golang/server/tun.go b/core/src/main/golang/server/tun.go index 929796b..6be593e 100644 --- a/core/src/main/golang/server/tun.go +++ b/core/src/main/golang/server/tun.go @@ -49,11 +49,5 @@ func handleTunStart(client *net.UnixConn) { } func handleTunStop(client *net.UnixConn) { - buf, _ := readCommandPacket(client) - - if tunCommandEnd != binary.BigEndian.Uint32(buf) { - log.Warnln("Invalid tun command end") - } - tun.StopTunProxy() } diff --git a/core/src/main/golang/tun/tun.go b/core/src/main/golang/tun/tun.go index ebe8bfb..c7133cf 100644 --- a/core/src/main/golang/tun/tun.go +++ b/core/src/main/golang/tun/tun.go @@ -52,6 +52,8 @@ func StartTunProxy(fd, mtu int) error { // StopTunProxy - stop func StopTunProxy() { + log.Infoln("Tun Device Stopped") + instance.close() instance = nil } diff --git a/core/src/main/java/com/github/kr328/clash/core/BaseClash.kt b/core/src/main/java/com/github/kr328/clash/core/BaseClash.kt new file mode 100644 index 0000000..02e8642 --- /dev/null +++ b/core/src/main/java/com/github/kr328/clash/core/BaseClash.kt @@ -0,0 +1,56 @@ +package com.github.kr328.clash.core + +import android.net.LocalSocket +import android.net.LocalSocketAddress +import java.io.DataInputStream +import java.io.DataOutputStream +import java.io.File +import java.io.IOException + +abstract class BaseClash(private val controllerPath: File) { + protected fun 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) + + runCatching { + socket.close() + } + + return result + } + + protected fun runControl(command: Int) { + return runControl(command) { _, _, _ -> } + } + + + protected fun DataOutputStream.writeString(string: String) { + this.writeInt(string.length) + this.writeBytes(string) + } + + protected fun DataInputStream.readString(): String { + val len = this.readInt() + val buffer = ByteArray(len) + + this.read(buffer) + + return String(buffer) + } +} \ No newline at end of file diff --git a/core/src/main/java/com/github/kr328/clash/core/Clash.kt b/core/src/main/java/com/github/kr328/clash/core/Clash.kt index 2eff840..5320c98 100644 --- a/core/src/main/java/com/github/kr328/clash/core/Clash.kt +++ b/core/src/main/java/com/github/kr328/clash/core/Clash.kt @@ -1,28 +1,29 @@ package com.github.kr328.clash.core import android.content.Context -import android.net.LocalSocket -import android.net.LocalSocketAddress import android.util.Log +import com.github.kr328.clash.core.Constants.TAG import org.json.JSONObject import java.io.* class Clash( context: Context, clashDir: File, - private val controllerPath: File, + controllerPath: File, listener: (ClashProcessStatus) -> Unit -) : ClashProcess(context, clashDir, controllerPath, listener) { +) : BaseClash(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 COMMAND_QUERY_PROXIES = 5 const val PING_REPLY = 233 } + val process = ClashProcess(context, clashDir, controllerPath, listener) + fun ping(): Boolean { try { return runControl(COMMAND_PING) { _, input, _ -> @@ -34,8 +35,6 @@ class Clash( return false } - @Throws(IOException::class) - @Synchronized fun loadProfile(file: File) { runControl(COMMAND_PROFILE_RELOAD) { _, input, output -> output.writeString(JSONObject().apply { @@ -49,7 +48,6 @@ class Clash( } } - @Throws(IOException::class) fun startTunDevice(fd: FileDescriptor, mtu: Int) { runControl(COMMAND_TUN_START) { socket, _, output -> socket.setFileDescriptorsForSend(arrayOf(fd)) @@ -60,49 +58,7 @@ class Clash( } } - private fun 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) + fun stopTunDevice() { + runControl(COMMAND_TUN_STOP) } } \ No newline at end of file diff --git a/core/src/main/java/com/github/kr328/clash/core/ClashProcess.kt b/core/src/main/java/com/github/kr328/clash/core/ClashProcess.kt index faedee7..13647f8 100644 --- a/core/src/main/java/com/github/kr328/clash/core/ClashProcess.kt +++ b/core/src/main/java/com/github/kr328/clash/core/ClashProcess.kt @@ -2,19 +2,18 @@ package com.github.kr328.clash.core import android.content.Context import android.util.Log +import com.github.kr328.clash.core.Constants.TAG import java.io.File import java.io.FileOutputStream import java.io.IOException import java.lang.Exception import kotlin.concurrent.thread -abstract class ClashProcess(private val context: Context, +class ClashProcess(private val context: Context, private val clashDir: File, private val controllerPath: File, private val listener: (ClashProcessStatus) -> Unit) { companion object { - const val TAG = "ClashForAndroid" - private const val CONTROLLER_STATUS_PREFIX = "[CONTROLLER]" private val PID_PATTERN = Regex("\\[PID]\\s*(\\d+)") diff --git a/core/src/main/java/com/github/kr328/clash/core/Constants.kt b/core/src/main/java/com/github/kr328/clash/core/Constants.kt new file mode 100644 index 0000000..e12fd69 --- /dev/null +++ b/core/src/main/java/com/github/kr328/clash/core/Constants.kt @@ -0,0 +1,5 @@ +package com.github.kr328.clash.core + +object Constants { + const val TAG = "ClashForAndroid" +} \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/ClashService.kt b/service/src/main/java/com/github/kr328/clash/service/ClashService.kt index ced3413..9d93f9f 100644 --- a/service/src/main/java/com/github/kr328/clash/service/ClashService.kt +++ b/service/src/main/java/com/github/kr328/clash/service/ClashService.kt @@ -22,7 +22,7 @@ import java.util.concurrent.Executors class ClashService : Service() { companion object { - private val TAG = "ClashForAndroid" + private const val TAG = "ClashForAndroid" private const val CLASH_STATUS_NOTIFICATION_CHANNEL = "clash_status_channel" private const val CLASH_STATUS_NOTIFICATION_ID = 413 @@ -100,7 +100,7 @@ class ClashService : Service() { } override fun stopTunDevice() { - clash.stop() + clash.stopTunDevice() handler.post(this@ClashService::updateNotification) } diff --git a/service/src/main/java/com/github/kr328/clash/service/TunService.kt b/service/src/main/java/com/github/kr328/clash/service/TunService.kt index f7e43e5..82e7744 100644 --- a/service/src/main/java/com/github/kr328/clash/service/TunService.kt +++ b/service/src/main/java/com/github/kr328/clash/service/TunService.kt @@ -82,8 +82,8 @@ class TunService : VpnService() { .addAddress(PRIVATE_VLAN6_CLIENT, 126) .addDefaultDns() .addDisallowedApplication(packageName) - .addRoute("0.0.0.0", 0) - .addRoute("::", 0) + .addDns() + .addBypassPrivateRoute() .setMtu(VPN_MTU) .setBlocking(false) .setMeteredCompat(false) @@ -129,4 +129,25 @@ class TunService : VpnService() { setMetered(isMetered) return this } + + private fun Builder.addBypassPrivateRoute(): Builder { + // IPv4 + resources.getStringArray(R.array.bypass_private_route).forEach { + val address = it.split("/") + Log.d(TAG, "$address") + addRoute(address[0], address[1].toInt()) + } + + // IPv6 + addRoute("::", 0) + + return this + } + + private fun Builder.addDns(): Builder { + resources.getStringArray(R.array.default_dns).forEach { + addDnsServer(it) + } + return this + } } \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/net/DefaultNetworkObserver.kt b/service/src/main/java/com/github/kr328/clash/service/net/DefaultNetworkObserver.kt index 3ea1e71..877e8cd 100644 --- a/service/src/main/java/com/github/kr328/clash/service/net/DefaultNetworkObserver.kt +++ b/service/src/main/java/com/github/kr328/clash/service/net/DefaultNetworkObserver.kt @@ -48,11 +48,10 @@ class DefaultNetworkObserver(val context: Context, val listener: (Network?) -> U fun register() { connectivity.registerNetworkCallback(NetworkRequest.Builder().build(), callback) - listener(connectivity.activeNetwork) - handler.postDelayed({ blocking = false - }, 1000) + listener(connectivity.activeNetwork) + }, 3000) } fun unregister() { diff --git a/service/src/main/res/drawable/ic_notification_icon.png b/service/src/main/res/drawable/ic_notification_icon.png new file mode 100644 index 0000000..9efd87e Binary files /dev/null and b/service/src/main/res/drawable/ic_notification_icon.png differ diff --git a/service/src/main/res/values/arrays.xml b/service/src/main/res/values/arrays.xml new file mode 100644 index 0000000..c859349 --- /dev/null +++ b/service/src/main/res/values/arrays.xml @@ -0,0 +1,169 @@ + + + + + 1.0.0.0/8 + 2.0.0.0/7 + 4.0.0.0/6 + 8.0.0.0/7 + 11.0.0.0/8 + 12.0.0.0/6 + 16.0.0.0/4 + 32.0.0.0/3 + 64.0.0.0/3 + 96.0.0.0/6 + 100.0.0.0/10 + 100.128.0.0/9 + 101.0.0.0/8 + 102.0.0.0/7 + 104.0.0.0/5 + 112.0.0.0/10 + 112.64.0.0/11 + 112.96.0.0/12 + 112.112.0.0/13 + 112.120.0.0/14 + 112.124.0.0/19 + 112.124.32.0/21 + 112.124.40.0/22 + 112.124.44.0/23 + 112.124.46.0/24 + 112.124.48.0/20 + 112.124.64.0/18 + 112.124.128.0/17 + 112.125.0.0/16 + 112.126.0.0/15 + 112.128.0.0/9 + 113.0.0.0/8 + 114.0.0.0/10 + 114.64.0.0/11 + 114.96.0.0/12 + 114.112.0.0/15 + 114.114.0.0/18 + 114.114.64.0/19 + 114.114.96.0/20 + 114.114.112.0/23 + 114.114.115.0/24 + 114.114.116.0/22 + 114.114.120.0/21 + 114.114.128.0/17 + 114.115.0.0/16 + 114.116.0.0/14 + 114.120.0.0/13 + 114.128.0.0/9 + 115.0.0.0/8 + 116.0.0.0/6 + 120.0.0.0/6 + 124.0.0.0/7 + 126.0.0.0/8 + 128.0.0.0/3 + 160.0.0.0/5 + 168.0.0.0/8 + 169.0.0.0/9 + 169.128.0.0/10 + 169.192.0.0/11 + 169.224.0.0/12 + 169.240.0.0/13 + 169.248.0.0/14 + 169.252.0.0/15 + 169.255.0.0/16 + 170.0.0.0/7 + 172.0.0.0/12 + 172.32.0.0/11 + 172.64.0.0/10 + 172.128.0.0/9 + 173.0.0.0/8 + 174.0.0.0/7 + 176.0.0.0/4 + 192.0.0.8/29 + 192.0.0.16/28 + 192.0.0.32/27 + 192.0.0.64/26 + 192.0.0.128/25 + 192.0.1.0/24 + 192.0.3.0/24 + 192.0.4.0/22 + 192.0.8.0/21 + 192.0.16.0/20 + 192.0.32.0/19 + 192.0.64.0/18 + 192.0.128.0/17 + 192.1.0.0/16 + 192.2.0.0/15 + 192.4.0.0/14 + 192.8.0.0/13 + 192.16.0.0/12 + 192.32.0.0/11 + 192.64.0.0/12 + 192.80.0.0/13 + 192.88.0.0/18 + 192.88.64.0/19 + 192.88.96.0/23 + 192.88.98.0/24 + 192.88.100.0/22 + 192.88.104.0/21 + 192.88.112.0/20 + 192.88.128.0/17 + 192.89.0.0/16 + 192.90.0.0/15 + 192.92.0.0/14 + 192.96.0.0/11 + 192.128.0.0/11 + 192.160.0.0/13 + 192.169.0.0/16 + 192.170.0.0/15 + 192.172.0.0/14 + 192.176.0.0/12 + 192.192.0.0/10 + 193.0.0.0/8 + 194.0.0.0/7 + 196.0.0.0/7 + 198.0.0.0/12 + 198.16.0.0/15 + 198.18.0.0/16 + 198.20.0.0/14 + 198.24.0.0/13 + 198.32.0.0/12 + 198.48.0.0/15 + 198.50.0.0/16 + 198.51.0.0/18 + 198.51.64.0/19 + 198.51.96.0/22 + 198.51.101.0/24 + 198.51.102.0/23 + 198.51.104.0/21 + 198.51.112.0/20 + 198.51.128.0/17 + 198.52.0.0/14 + 198.56.0.0/13 + 198.64.0.0/10 + 198.128.0.0/9 + 199.0.0.0/8 + 200.0.0.0/7 + 202.0.0.0/8 + 203.0.0.0/18 + 203.0.64.0/19 + 203.0.96.0/20 + 203.0.112.0/24 + 203.0.114.0/23 + 203.0.116.0/22 + 203.0.120.0/21 + 203.0.128.0/17 + 203.1.0.0/16 + 203.2.0.0/15 + 203.4.0.0/14 + 203.8.0.0/13 + 203.16.0.0/12 + 203.32.0.0/11 + 203.64.0.0/10 + 203.128.0.0/9 + 204.0.0.0/6 + 208.0.0.0/4 + + + + 119.29.29.29 + 223.5.5.5 + 1.1.1.1 + 8.8.8.8 + + \ No newline at end of file