[WIP] refactor clash bridge
This commit is contained in:
12 files changed
+293
-153
No files matched your search
@@ -84,6 +84,7 @@ open class GolangBindTask : DefaultTask() {
|
||||
else
|
||||
environment.put("PATH", System.getenv("PATH") + ":" + goPath.resolve("bin"))
|
||||
|
||||
goPath.resolve("src/github.com/kr328").deleteRecursively()
|
||||
goBindPath.deleteRecursively()
|
||||
goBindPath.mkdirs()
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package bridge
|
||||
|
||||
import "github.com/Dreamacro/clash/constant"
|
||||
import (
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/kr328/cfa/profile"
|
||||
)
|
||||
|
||||
import "github.com/kr328/cfa/profile"
|
||||
|
||||
import "github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
func SetBaseDir(home string) {
|
||||
constant.SetHomeDir(home)
|
||||
func LoadMMDB(data []byte) {
|
||||
mmdb.LoadFromBytes(data)
|
||||
}
|
||||
|
||||
func Reset() {
|
||||
|
||||
@@ -2,10 +2,14 @@ package bridge
|
||||
|
||||
import "github.com/kr328/cfa/profile"
|
||||
|
||||
func LoadProfileFile(path string) error {
|
||||
return profile.LoadFromFile(path)
|
||||
func LoadProfileFile(path, baseDir string) error {
|
||||
return profile.LoadFromFile(path, baseDir)
|
||||
}
|
||||
|
||||
func CheckProfileValid(profileData string) error {
|
||||
return profile.CheckValid(profileData)
|
||||
func DownloadProfileAndCheck(url, output string) error {
|
||||
return profile.DownloadAndCheck(url, output)
|
||||
}
|
||||
|
||||
func SaveProfileAndCheck(data []byte, output string) error {
|
||||
return profile.SaveAndCheck(data, output)
|
||||
}
|
||||
@@ -1,74 +1,150 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
type Proxy struct {
|
||||
type ProxyItem struct {
|
||||
Name string
|
||||
Type string `json:"type"`
|
||||
All []string `json:"all"`
|
||||
Now string `json:"now"`
|
||||
Type string
|
||||
Delay int
|
||||
}
|
||||
|
||||
type ProxyList struct {
|
||||
proxies []*Proxy
|
||||
type ProxyGroupItem struct {
|
||||
Name string
|
||||
Type string
|
||||
Current string
|
||||
Delay int
|
||||
|
||||
providers []provider.ProxyProvider
|
||||
}
|
||||
|
||||
type ProxyGroupCollection interface {
|
||||
Add(proxy *ProxyGroupItem) bool
|
||||
}
|
||||
|
||||
type ProxyCollection interface {
|
||||
Add(proxy *ProxyItem) bool
|
||||
}
|
||||
|
||||
type UrlTestCallback interface {
|
||||
OnResult(name string, delay int)
|
||||
Done()
|
||||
}
|
||||
|
||||
func (p Proxy) GetAllLength() int {
|
||||
return len(p.All)
|
||||
}
|
||||
|
||||
func (p Proxy) GetAllElement(index int) string {
|
||||
return p.All[index]
|
||||
}
|
||||
|
||||
func (pl *ProxyList) GetProxiesLength() int {
|
||||
return len(pl.proxies)
|
||||
}
|
||||
|
||||
func (pl *ProxyList) GetProxiesElement(index int) *Proxy {
|
||||
return pl.proxies[index]
|
||||
}
|
||||
|
||||
func QueryAllProxies() (*ProxyList, error) {
|
||||
ps := tunnel.Instance().Proxies()
|
||||
result := make([]*Proxy, len(ps))
|
||||
currentIndex := 0
|
||||
|
||||
for k, v := range ps {
|
||||
current := &Proxy{
|
||||
Name: k,
|
||||
Type: v.Type().String(),
|
||||
All: []string{},
|
||||
Delay: int(v.LastDelay()),
|
||||
func (p *ProxyGroupItem) QueryAllProxies(collection ProxyCollection) {
|
||||
for _, v := range p.providers {
|
||||
for _, p := range v.Proxies() {
|
||||
collection.Add(
|
||||
&ProxyItem{
|
||||
Name: p.Name(),
|
||||
Type: p.Type().String(),
|
||||
Delay: int(p.LastDelay()),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
data, err := v.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json.Unmarshal(data, current)
|
||||
|
||||
result[currentIndex] = current
|
||||
|
||||
currentIndex++
|
||||
}
|
||||
}
|
||||
|
||||
return &ProxyList{proxies: result}, nil
|
||||
func StartUrlTest(group string, callback UrlTestCallback) {
|
||||
go func() {
|
||||
defer callback.Done()
|
||||
|
||||
p := tunnel.Instance().Proxies()[group]
|
||||
|
||||
pa, ok := p.(*outbound.Proxy)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var providers []provider.ProxyProvider
|
||||
|
||||
switch group := pa.ProxyAdapter.(type) {
|
||||
case *outboundgroup.Fallback:
|
||||
providers = group.GetProviders()
|
||||
case *outboundgroup.URLTest:
|
||||
providers = group.GetProviders()
|
||||
case *outboundgroup.LoadBalance:
|
||||
providers = group.GetProviders()
|
||||
case *outboundgroup.Selector:
|
||||
providers = group.GetProviders()
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(providers))
|
||||
|
||||
for _, v := range providers {
|
||||
go func(p provider.ProxyProvider) {
|
||||
p.HealthCheck()
|
||||
wg.Done()
|
||||
}(v)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}()
|
||||
}
|
||||
|
||||
func QueryAllProxyGroups(collection ProxyGroupCollection) {
|
||||
ps := tunnel.Instance().Proxies()
|
||||
|
||||
for _, p := range ps {
|
||||
pa, ok := p.(*outbound.Proxy)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
switch group := pa.ProxyAdapter.(type) {
|
||||
case *outboundgroup.Fallback:
|
||||
collection.Add(
|
||||
&ProxyGroupItem{
|
||||
Name: group.Name(),
|
||||
Type: group.Type().String(),
|
||||
Current: group.Now(),
|
||||
Delay: int(p.LastDelay()),
|
||||
providers: group.GetProviders(),
|
||||
},
|
||||
)
|
||||
case *outboundgroup.URLTest:
|
||||
collection.Add(
|
||||
&ProxyGroupItem{
|
||||
Name: group.Name(),
|
||||
Type: group.Type().String(),
|
||||
Current: group.Now(),
|
||||
Delay: int(p.LastDelay()),
|
||||
providers: group.GetProviders(),
|
||||
},
|
||||
)
|
||||
case *outboundgroup.LoadBalance:
|
||||
collection.Add(
|
||||
&ProxyGroupItem{
|
||||
Name: group.Name(),
|
||||
Type: group.Type().String(),
|
||||
Current: "",
|
||||
Delay: int(p.LastDelay()),
|
||||
providers: group.GetProviders(),
|
||||
},
|
||||
)
|
||||
case *outboundgroup.Selector:
|
||||
collection.Add(
|
||||
&ProxyGroupItem{
|
||||
Name: group.Name(),
|
||||
Type: group.Type().String(),
|
||||
Current: group.Now(),
|
||||
Delay: int(p.LastDelay()),
|
||||
providers: group.GetProviders(),
|
||||
},
|
||||
)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SetSelectedProxy(name, proxy string) bool {
|
||||
@@ -95,25 +171,3 @@ func SetSelectedProxy(name, proxy string) bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func StartUrlTest(name, url string, timeout int, callback UrlTestCallback) {
|
||||
go func() {
|
||||
p := tunnel.Instance().Proxies()[name]
|
||||
|
||||
if p == nil {
|
||||
callback.OnResult(name, -1)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
|
||||
defer cancel()
|
||||
|
||||
delay, err := p.URLTest(ctx, url)
|
||||
if ctx.Err() != nil || err != nil {
|
||||
callback.OnResult(name, -1)
|
||||
return
|
||||
}
|
||||
|
||||
callback.OnResult(name, int(delay))
|
||||
}()
|
||||
}
|
||||
Submodule core/src/main/golang/clash updated: d0ba150ed9...7adba59744.
@@ -0,0 +1,73 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/inbound"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
const defaultFileMode = 0600
|
||||
|
||||
var client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
if network != "tcp" && network != "tcp4" && network != "tcp6" {
|
||||
return nil, errors.New("Unsupported network type " + network)
|
||||
}
|
||||
|
||||
client, server := net.Pipe()
|
||||
|
||||
tunnel.Instance().Add(inbound.NewSocket(socks5.ParseAddr(address), server, constant.HTTP, constant.TCP))
|
||||
|
||||
go func() {
|
||||
if ctx == nil || ctx.Done() == nil {
|
||||
return
|
||||
}
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
client.Close()
|
||||
server.Close()
|
||||
}()
|
||||
|
||||
return client, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func DownloadAndCheck(url, output string) error {
|
||||
response, err := client.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
data, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = config.Parse(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(output, data, defaultFileMode)
|
||||
}
|
||||
|
||||
func SaveAndCheck(data []byte, output string) error {
|
||||
_, err := config.Parse(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(output, data, defaultFileMode)
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
"github.com/kr328/cfa/tun"
|
||||
@@ -40,12 +42,29 @@ func LoadDefault() {
|
||||
}
|
||||
|
||||
// LoadFromFile - load file
|
||||
func LoadFromFile(path string) error {
|
||||
cfg, err := executor.ParseWithPath(path)
|
||||
func LoadFromFile(path, baseDir string) error {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawCfg, err := config.UnmarshalRawConfig(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawCfg.ExternalController = ""
|
||||
rawCfg.ExternalUI = ""
|
||||
|
||||
fallbackBaseDir := constant.Path.HomeDir()
|
||||
constant.SetHomeDir(baseDir)
|
||||
|
||||
cfg, err := config.ParseRawConfig(rawCfg)
|
||||
if err != nil {
|
||||
constant.SetHomeDir(fallbackBaseDir)
|
||||
return err
|
||||
}
|
||||
|
||||
executor.ApplyConfig(cfg, true)
|
||||
|
||||
if dns.DefaultResolver == nil && cfg.DNS.Enable {
|
||||
@@ -94,8 +113,3 @@ func LoadFromFile(path string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckValid(data string) error {
|
||||
_, err := config.Parse([]byte(data))
|
||||
return err
|
||||
}
|
||||
@@ -9,9 +9,12 @@ import com.github.kr328.clash.core.event.ProcessEvent
|
||||
import com.github.kr328.clash.core.event.TrafficEvent
|
||||
import com.github.kr328.clash.core.model.General
|
||||
import com.github.kr328.clash.core.model.Proxy
|
||||
import java.io.FileOutputStream
|
||||
import java.lang.Exception
|
||||
import com.github.kr328.clash.core.model.ProxyGroup
|
||||
import com.github.kr328.clash.core.transact.ProxyCollectionImpl
|
||||
import com.github.kr328.clash.core.transact.ProxyGroupCollectionImpl
|
||||
import java.io.InputStream
|
||||
import java.lang.IllegalStateException
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class Clash(
|
||||
context: Context,
|
||||
@@ -33,21 +36,10 @@ class Clash(
|
||||
private var currentProcess = ProcessEvent.STOPPED
|
||||
|
||||
init {
|
||||
val home = context.filesDir.resolve(CLASH_DIR)
|
||||
val countryDatabase = home.resolve("Country.mmdb")
|
||||
val country = context.assets.open("Country.mmdb")
|
||||
.use(InputStream::readBytes)
|
||||
|
||||
home.resolve("ui").mkdirs()
|
||||
|
||||
if ( context.packageManager.getPackageInfo(context.packageName, 0).lastUpdateTime >
|
||||
countryDatabase.lastModified() ) {
|
||||
FileOutputStream(countryDatabase).use { output ->
|
||||
context.assets.open("Country.mmdb").use { input ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bridge.setBaseDir(home.absolutePath)
|
||||
Bridge.loadMMDB(country)
|
||||
}
|
||||
|
||||
fun getCurrentProcessStatus(): ProcessEvent {
|
||||
@@ -90,58 +82,50 @@ class Clash(
|
||||
Bridge.stopTunDevice()
|
||||
}
|
||||
|
||||
fun loadProfile(path: String) {
|
||||
fun loadProfile(path: String, baseDir: String) {
|
||||
enforceStarted()
|
||||
|
||||
Bridge.loadProfileFile(path)
|
||||
Bridge.loadProfileFile(path, baseDir)
|
||||
}
|
||||
|
||||
fun checkProfileValid(data: String): String? {
|
||||
return try {
|
||||
Bridge.checkProfileValid(data)
|
||||
null
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.message
|
||||
}
|
||||
fun downloadProfile(url: String, output: String) {
|
||||
Bridge.downloadProfileAndCheck(url, output)
|
||||
}
|
||||
|
||||
fun queryProxies(): List<Proxy> {
|
||||
fun saveProfile(data: ByteArray, output: String) {
|
||||
Bridge.saveProfileAndCheck(data, output)
|
||||
}
|
||||
|
||||
fun queryProxyGroups(): List<ProxyGroup> {
|
||||
enforceStarted()
|
||||
|
||||
val list = Bridge.queryAllProxies()
|
||||
val result = mutableListOf<Proxy>()
|
||||
|
||||
for (i in 0 until list.proxiesLength) {
|
||||
val p = list.getProxiesElement(i)
|
||||
val all = mutableListOf<String>()
|
||||
|
||||
for (index in 0 until p.allLength) {
|
||||
all.add(p.getAllElement(index))
|
||||
return ProxyGroupCollectionImpl().also { Bridge.queryAllProxyGroups(it) }
|
||||
.filterNotNull()
|
||||
.map { group ->
|
||||
ProxyGroup(group.name,
|
||||
Proxy.Type.fromString(group.type),
|
||||
group.delay,
|
||||
group.current,
|
||||
ProxyCollectionImpl().also { pc ->
|
||||
group.queryAllProxies(pc)
|
||||
}.filterNotNull().map {
|
||||
Proxy(it.name, Proxy.Type.fromString(it.type), it.delay)
|
||||
})
|
||||
}
|
||||
|
||||
result.add(
|
||||
Proxy(
|
||||
p.name,
|
||||
Proxy.Type.fromString(p.type),
|
||||
p.now,
|
||||
all,
|
||||
if ( p.delay < Short.MAX_VALUE ) p.delay else 0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun setSelectedProxy(name: String, selected: String): Boolean {
|
||||
return Bridge.setSelectedProxy(name, selected)
|
||||
}
|
||||
|
||||
fun startUrlTest(name: String, callback: (String, Long) -> Unit) {
|
||||
Bridge.startUrlTest(name, DEFAULT_URL_TEST_URL, DEFAULT_URL_TEST_TIMEOUT.toLong()) { n, delay ->
|
||||
callback(n, delay)
|
||||
fun startUrlTest(name: String): CompletableFuture<Unit> {
|
||||
val future = CompletableFuture<Unit>()
|
||||
|
||||
Bridge.startUrlTest(name) {
|
||||
future.complete(Unit)
|
||||
}
|
||||
|
||||
return future
|
||||
}
|
||||
|
||||
fun queryGeneral(): General {
|
||||
|
||||
@@ -6,8 +6,6 @@ import kotlinx.serialization.Serializable
|
||||
data class Proxy(
|
||||
val name: String,
|
||||
val type: Type,
|
||||
val now: String,
|
||||
val all: List<String>,
|
||||
val delay: Long
|
||||
) {
|
||||
enum class Type {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.github.kr328.clash.core.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ProxyGroup(
|
||||
val name: String,
|
||||
val type: Proxy.Type,
|
||||
val delay: Long,
|
||||
val current: String,
|
||||
val proxies: List<Proxy>
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.github.kr328.clash.core.transact
|
||||
|
||||
import bridge.*
|
||||
import java.util.*
|
||||
|
||||
class ProxyCollectionImpl : LinkedList<ProxyItem?>(), ProxyCollection
|
||||
class ProxyGroupCollectionImpl: LinkedList<ProxyGroupItem?>(), ProxyGroupCollection
|
||||
@@ -48,7 +48,7 @@ class ClashServiceImpl(clashService: ClashService) : IClashService.Stub() {
|
||||
|
||||
override fun queryAllProxies(): CompressedProxyList {
|
||||
return try {
|
||||
clash.queryProxies().compress()
|
||||
clash.queryProxyGroups().compress()
|
||||
}
|
||||
catch (e: Exception) {
|
||||
Log.w("Query proxies", e)
|
||||
@@ -65,14 +65,7 @@ class ClashServiceImpl(clashService: ClashService) : IClashService.Stub() {
|
||||
val count = AtomicInteger(proxies.size)
|
||||
|
||||
proxies.forEach {
|
||||
clash.startUrlTest(it) { n, d ->
|
||||
callback.onResult(n, d)
|
||||
|
||||
count.getAndDecrement()
|
||||
|
||||
if ( count.get() == 0 )
|
||||
callback.onResult(null, 0)
|
||||
}
|
||||
clash.startUrlTest(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user