[WIP] refactor clash bridge

This commit is contained in:
Kr328 committed 2020-01-12 23:43:52 +08:00
1 parent 1eb6f59167
commit 19ddb86430
12 files changed
+293 -153

No files matched your search

+1
View File
@@ -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()
+7 -7
View File
@@ -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() {
+8 -4
View File
@@ -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)
}
+125 -71
View File
@@ -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))
}()
}
+73
View File
@@ -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)
}
+21 -7
View File
@@ -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)
}
}