add url test

This commit is contained in:
Kr328 committed 2019-12-07 01:30:35 +08:00
1 parent a72c2d8abb
commit bda542d2e5
16 files changed
+270 -66

No files matched your search

@@ -1,6 +1,7 @@
package com.github.kr328.clash
import android.app.Application
import android.content.Context
import com.github.kr328.clash.core.utils.Log
import com.google.firebase.FirebaseApp
@@ -9,6 +10,14 @@ class MainApplication : Application() {
const val KEY_PROXY_MODE = "key_proxy_mode"
const val PROXY_MODE_VPN = "vpn"
const val PROXY_MODE_PROXY_ONLY = "proxy_only"
lateinit var instance: MainApplication
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
instance = this
}
override fun onCreate() {
@@ -2,7 +2,9 @@ package com.github.kr328.clash
import android.os.Bundle
import com.github.kr328.clash.adapter.ProxyAdapter
import com.github.kr328.clash.callback.IUrlTestCallback
import com.github.kr328.clash.core.model.ProxyPacket
import com.github.kr328.clash.core.utils.Log
import com.github.kr328.clash.model.ListProxy
import kotlinx.android.synthetic.main.activity_proxies.*
@@ -14,11 +16,7 @@ class ProxyActivity : BaseActivity() {
setSupportActionBar(activity_proxies_toolbar)
activity_proxies_list.also {
it.adapter = ProxyAdapter(this) { p, s ->
runClash { clash ->
clash.setSelectProxy(p, s)
}
}
it.adapter = ProxyAdapter(this, this::setProxySelected, this::urlTest)
it.layoutManager = (it.adapter!! as ProxyAdapter).getLayoutManager()
}
@@ -29,6 +27,51 @@ class ProxyActivity : BaseActivity() {
refreshList()
}
private fun setProxySelected(name: String, selected: String) {
runClash {
it.setSelectProxy(name, selected)
}
}
private fun urlTest(position: Int, size: Int) {
val adapter = (activity_proxies_list.adapter as ProxyAdapter)
runClash {
val proxies = adapter.elements.subList(position, position+size)
.filterIsInstance<ListProxy.ListProxyItem>()
.mapIndexed { index, proxy -> proxy.name to IndexedValue(index, proxy)}
.toMap()
for ((_, p) in proxies) {
Log.i("$p -> ${p.index}")
p.value.delay = -1
}
it.startUrlTest(proxies.keys.toTypedArray(), object: IUrlTestCallback.Stub() {
override fun onResult(proxy: String?, delay: Long) {
if ( proxy == null ) {
(adapter.elements[position] as ListProxy.ListProxyHeader).urlTest = false
runOnUiThread {
adapter.notifyItemRangeChanged(position, size)
}
return
}
val p = proxies[proxy] ?: return
p.value.delay = delay
runOnUiThread {
adapter.notifyItemChanged(position + p.index + 1)
}
}
})
}
}
private fun refreshList() {
if (!activity_proxies_swipe.isRefreshing)
activity_proxies_swipe.isRefreshing = true
@@ -13,7 +13,9 @@ import com.github.kr328.clash.core.model.ProxyPacket
import com.github.kr328.clash.model.ListProxy
import com.google.android.material.card.MaterialCardView
class ProxyAdapter(private val context: Context, val listener: (String, String) -> Unit) :
class ProxyAdapter(private val context: Context,
private val onSelect: (String, String) -> Unit,
private val onUrlTest: (Int, Int) -> Unit) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var elements: List<ListProxy> = emptyList()
var clickable: Boolean = false
@@ -108,7 +110,7 @@ class ProxyAdapter(private val context: Context, val listener: (String, String)
notifyItemChanged(old)
notifyItemChanged(position)
listener(element.header.name, element.name)
onSelect(element.header.name, element.name)
}
} else {
holder.card.setOnClickListener(null)
@@ -119,20 +121,45 @@ class ProxyAdapter(private val context: Context, val listener: (String, String)
holder.name.text = current.name
holder.type.text = current.type
holder.delay.text =
if (current.delay > 0) {
current.delay.toString()
} else {
if (current.header.type != ProxyPacket.Type.SELECT)
"N/A"
else
""
when {
current.header.urlTest && current.delay < 0 -> "..."
current.delay < 0 -> "N/A"
current.delay > 0 -> {
current.delay.toString()
}
else -> {
if (current.header.type != ProxyPacket.Type.SELECT)
"N/A"
else
""
}
}
}
private fun bindHeaderView(holder: HeaderHolder, current: ListProxy.ListProxyHeader) {
holder.name.text = current.name
holder.test.visibility =
if (current.type == ProxyPacket.Type.SELECT) View.VISIBLE else View.GONE
holder.test.setOnClickListener {
if ( current.urlTest )
return@setOnClickListener
val indexed = elements.withIndex()
.filter { it.value is ListProxy.ListProxyHeader }
.filterIsInstance<IndexedValue<ListProxy.ListProxyHeader>>()
val headerIndex = indexed.indexOfFirst {
it.value === current
}.takeIf { it >= 0 } ?: return@setOnClickListener
val header = indexed[headerIndex]
indexed[headerIndex].value.urlTest = true
val position = header.index
val size = (indexed.getOrNull(headerIndex + 1)?.index ?: elements.size) - header.index
notifyItemRangeChanged(position, size)
onUrlTest(position, size)
}
}
}
@@ -3,13 +3,13 @@ package com.github.kr328.clash.model
import com.github.kr328.clash.core.model.ProxyPacket
interface ListProxy {
data class ListProxyHeader(val name: String, val type: ProxyPacket.Type, var now: Int) :
data class ListProxyHeader(val name: String, val type: ProxyPacket.Type, var now: Int, var urlTest: Boolean = false) :
ListProxy
data class ListProxyItem(
val name: String,
val type: String,
val delay: Long,
var delay: Long,
val header: ListProxyHeader
) : ListProxy
}
@@ -0,0 +1,13 @@
package com.github.kr328.clash.utils
import android.util.TypedValue
import com.github.kr328.clash.MainApplication
val Int.dp: Float
get() {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this.toFloat(),
MainApplication.instance.resources.displayMetrics
)
}
+2 -2
View File
@@ -2,7 +2,7 @@
buildscript {
ext {
kotlin_version = '1.3.60'
kotlin_version = '1.3.61'
room_version = '2.2.2'
}
repositories {
@@ -14,7 +14,7 @@ buildscript {
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
classpath 'com.android.tools.build:gradle:3.5.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.3'
+1 -1
View File
@@ -2,7 +2,7 @@ apply plugin: 'java'
apply plugin: 'kotlin'
buildscript {
ext.kotlin_version = '1.3.60'
ext.kotlin_version = '1.3.61'
repositories {
mavenCentral()
}
+1 -26
View File
@@ -4,9 +4,7 @@ import (
"encoding/json"
"net"
A "github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
"github.com/kr328/cfa/profile"
)
@@ -54,31 +52,8 @@ func handleProfileReload(client *net.UnixConn) {
return
}
proxies := tunnel.Instance().Proxies()
for k, v := range payload.Selected {
p := proxies[k]
if p == nil {
response.InvalidSelected = append(response.InvalidSelected, k)
continue
}
proxy, ok := p.(*A.Proxy)
if !ok {
response.InvalidSelected = append(response.InvalidSelected, k)
continue
}
selector, ok := proxy.ProxyAdapter.(*A.Selector)
if !ok {
response.InvalidSelected = append(response.InvalidSelected, k)
continue
}
log.Infoln("Set selector " + k + " -> " + v)
selector.Set(v)
setProxySelect(k, v)
}
log.Infoln("Profile " + payload.Path + " loaded")
+86 -12
View File
@@ -1,9 +1,12 @@
package server
import (
"context"
"encoding/json"
"errors"
"net"
"strconv"
"time"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/proxy"
@@ -95,8 +98,6 @@ func handleQueryProxies(client *net.UnixConn) {
}
func handleSetProxy(client *net.UnixConn) {
proxies := tunnel.Instance().Proxies()
var Request struct {
Key string `json:"key"`
Value string `json:"value"`
@@ -119,28 +120,101 @@ func handleSetProxy(client *net.UnixConn) {
json.Unmarshal(buf, &Request)
p := proxies[Request.Key]
if err := setProxySelect(Request.Key, Request.Value); err != nil {
Response.Error = err.Error()
}
}
func handleUrlTest(client *net.UnixConn) {
var request struct {
URL string `json:"url"`
Timeout int `json:"timeout"`
Proxies []string `json:"proxies"`
}
request.Proxies = make([]string, 0)
buf, err := readCommandPacket(client)
if err != nil {
return
}
if json.Unmarshal(buf, &request) != nil {
return
}
type Response struct {
Name string `json:"name"`
Delay int `json:"delay"`
}
proxies := tunnel.Instance().Proxies()
channel := make(chan *Response, len(request.Proxies))
for _, p := range request.Proxies {
go func(p string) {
proxy := proxies[p]
if proxy == nil {
channel <- nil
return
}
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(request.Timeout)*time.Millisecond)
defer cancel()
delay, err := proxies[p].URLTest(ctx, request.URL)
if err != nil {
channel <- nil
return
}
channel <- &Response{
Name: p,
Delay: int(delay),
}
}(p)
}
for range request.Proxies {
response := <-channel
if response != nil {
buf, _ := json.Marshal(&response)
writeCommandPacket(client, buf)
}
}
writeCommandPacket(client, nil)
log.Infoln("URLTest exited")
}
func setProxySelect(name, selected string) error {
proxies := tunnel.Instance().Proxies()
p := proxies[name]
if p == nil {
Response.Error = "Unknown proxy " + Request.Key
return
return errors.New("Unknown proxy " + name)
}
proxy, ok := p.(*A.Proxy)
if !ok {
Response.Error = "Invalid proxy " + Request.Key
return
return errors.New("Invalid proxy " + name)
}
selector, ok := proxy.ProxyAdapter.(*A.Selector)
if !ok {
Response.Error = "Not selector"
return
return errors.New("Not selector")
}
log.Infoln("Set selector " + Request.Key + " -> " + Request.Value)
log.Infoln("Set selector " + name + " -> " + selected)
if err := selector.Set(Request.Value); err != nil {
Response.Error = err.Error()
if err := selector.Set(selected); err != nil {
return err
}
return nil
}
+2
View File
@@ -19,6 +19,7 @@ const (
commandPullBandwidth uint32 = 8
commandSetProxy uint32 = 9
commandQueryGeneral uint32 = 10
commandUrlTest uint32 = 11
)
var handlers map[uint32]func(*net.UnixConn) = make(map[uint32]func(*net.UnixConn))
@@ -35,6 +36,7 @@ func init() {
handlers[commandPullBandwidth] = handlePullBandwidthEvent // event.go
handlers[commandSetProxy] = handleSetProxy // proxies.go
handlers[commandQueryGeneral] = handleQueryGeneral // proxies.go
handlers[commandUrlTest] = handleUrlTest // proxies.go
}
// Start local control server
@@ -6,12 +6,11 @@ import com.github.kr328.clash.core.event.BandwidthEvent
import com.github.kr328.clash.core.event.LogEvent
import com.github.kr328.clash.core.event.ProcessEvent
import com.github.kr328.clash.core.event.SpeedEvent
import com.github.kr328.clash.core.model.GeneralPacket
import com.github.kr328.clash.core.model.LoadProfilePacket
import com.github.kr328.clash.core.model.RawProxyPacket
import com.github.kr328.clash.core.model.SetProxyPacket
import com.github.kr328.clash.core.model.*
import com.github.kr328.clash.core.utils.Log
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.stringify
import java.io.File
import java.io.FileDescriptor
import java.io.IOException
@@ -33,8 +32,12 @@ class Clash(
const val COMMAND_PULL_BANDWIDTH = 8
const val COMMAND_SET_PROXY = 9
const val COMMAND_QUERY_GENERAL = 10
const val COMMAND_URL_TEST = 11
const val PING_REPLY = 233
const val DEFAULT_URL_TEST_TIMEOUT = 5000
const val DEFAULT_URL_TEST_URL = "https://www.gstatic.com/generate_204"
}
val process = ClashProcess(context, clashDir, controllerPath, listener)
@@ -153,6 +156,27 @@ class Clash(
}
}
fun startUrlTest(proxies: List<String>, callback: (String, Long) -> Unit) {
runControl(COMMAND_URL_TEST) { _, input, output ->
output.writeString(Json(JsonConfiguration.Stable)
.stringify(UrlTestPacket.Request.serializer(),
UrlTestPacket.Request(proxies, DEFAULT_URL_TEST_TIMEOUT, DEFAULT_URL_TEST_URL)))
while ( true ) {
val data = input.readString()
if ( data.isEmpty() )
return@runControl
val response = Json(JsonConfiguration.Stable)
.parse(UrlTestPacket.Response.serializer(), data)
callback(response.name, response.delay)
}
}
Log.i("Url test exited")
}
fun startTunDevice(fd: FileDescriptor, mtu: Int) {
runControl(COMMAND_TUN_START) { socket, _, output ->
socket.setFileDescriptorsForSend(arrayOf(fd))
@@ -0,0 +1,11 @@
package com.github.kr328.clash.core.model
import kotlinx.serialization.Serializable
class UrlTestPacket {
@Serializable
data class Request(val proxies: List<String>, val timeout: Int, val url: String)
@Serializable
data class Response(val name: String, val delay: Long)
}
@@ -0,0 +1,5 @@
package com.github.kr328.clash.callback;
interface IUrlTestCallback {
void onResult(String proxy, long delay);
}
@@ -4,6 +4,7 @@ import com.github.kr328.clash.service.IClashEventObserver;
import com.github.kr328.clash.service.IClashEventService;
import com.github.kr328.clash.service.IClashProfileService;
import com.github.kr328.clash.service.IClashSettingService;
import com.github.kr328.clash.callback.IUrlTestCallback;
import com.github.kr328.clash.core.event.Event;
import com.github.kr328.clash.core.model.Packet;
@@ -26,4 +27,5 @@ interface IClashService {
// Query
ProxyPacket queryAllProxies();
GeneralPacket queryGeneral();
void startUrlTest(in String[] proxies, IUrlTestCallback callback);
}
@@ -5,7 +5,7 @@ import java.util.concurrent.Executors
class ClashEventService(private val master: Master) : IClashEventService.Stub() {
interface Master {
fun requireEvent(event: Int)
fun acquireEvent(event: Int)
fun releaseEvent(event: Int)
}
@@ -130,7 +130,7 @@ class ClashEventService(private val master: Master) : IClashEventService.Stub()
}.toSet()
val rel = EVENT_SET - req
req.forEach(master::requireEvent)
req.forEach(master::acquireEvent)
rel.forEach(master::releaseEvent)
}
}
@@ -6,6 +6,7 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.*
import com.github.kr328.clash.callback.IUrlTestCallback
import com.github.kr328.clash.core.Clash
import com.github.kr328.clash.core.ClashProcess
import com.github.kr328.clash.core.event.*
@@ -15,6 +16,7 @@ import com.github.kr328.clash.core.utils.Log
import java.io.File
import java.io.IOException
import java.util.concurrent.Executors
import kotlin.concurrent.thread
class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
ClashProfileService.Master, ClashEventPuller.Master {
@@ -66,6 +68,24 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
}
}
override fun startUrlTest(proxies: Array<out String>?, callback: IUrlTestCallback?) {
require(proxies != null && callback != null)
thread {
try {
clash.startUrlTest(proxies.toList()) { name, delay ->
callback.onResult(name, delay)
}
}
catch (e: Exception) {
Log.w("Url test failure", e)
}
callback.onResult(null, -1)
// Ignore exceptions
}
}
override fun start() {
try {
clash.process.start()
@@ -134,7 +154,7 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
}
}
override fun requireEvent(event: Int) {
override fun acquireEvent(event: Int) {
if (clash.process.getProcessStatus() == ProcessEvent.STOPPED)
return
@@ -146,7 +166,6 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
Event.EVENT_BANDWIDTH ->
puller.startBandwidthPull()
}
}
override fun releaseEvent(event: Int) {