diff --git a/buildSrc/src/main/java/GolangBindTask.kt b/buildSrc/src/main/java/GolangBindTask.kt index 59d92c1..e091404 100644 --- a/buildSrc/src/main/java/GolangBindTask.kt +++ b/buildSrc/src/main/java/GolangBindTask.kt @@ -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() diff --git a/core/src/main/golang/bridge/init.go b/core/src/main/golang/bridge/init.go index 9b301f6..321b174 100644 --- a/core/src/main/golang/bridge/init.go +++ b/core/src/main/golang/bridge/init.go @@ -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() { diff --git a/core/src/main/golang/bridge/profiles.go b/core/src/main/golang/bridge/profiles.go index 5decb71..f10d66c 100644 --- a/core/src/main/golang/bridge/profiles.go +++ b/core/src/main/golang/bridge/profiles.go @@ -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) } diff --git a/core/src/main/golang/bridge/proxies.go b/core/src/main/golang/bridge/proxies.go index 6d23a73..71ca774 100644 --- a/core/src/main/golang/bridge/proxies.go +++ b/core/src/main/golang/bridge/proxies.go @@ -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)) - }() -} diff --git a/core/src/main/golang/clash b/core/src/main/golang/clash index d0ba150..7adba59 160000 --- a/core/src/main/golang/clash +++ b/core/src/main/golang/clash @@ -1 +1 @@ -Subproject commit d0ba150ed93122efe3516a89ad5c5ff21d323789 +Subproject commit 7adba597440b5d2d54677731c0df7a86b564e90f diff --git a/core/src/main/golang/profile/download.go b/core/src/main/golang/profile/download.go new file mode 100644 index 0000000..167624f --- /dev/null +++ b/core/src/main/golang/profile/download.go @@ -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) +} diff --git a/core/src/main/golang/profile/load.go b/core/src/main/golang/profile/load.go index 672bb2a..e12d0f6 100644 --- a/core/src/main/golang/profile/load.go +++ b/core/src/main/golang/profile/load.go @@ -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 -} 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 d3a1edb..a345eb1 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 @@ -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 { + fun saveProfile(data: ByteArray, output: String) { + Bridge.saveProfileAndCheck(data, output) + } + + fun queryProxyGroups(): List { enforceStarted() - val list = Bridge.queryAllProxies() - val result = mutableListOf() - - for (i in 0 until list.proxiesLength) { - val p = list.getProxiesElement(i) - val all = mutableListOf() - - 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 { + val future = CompletableFuture() + + Bridge.startUrlTest(name) { + future.complete(Unit) } + + return future } fun queryGeneral(): General { diff --git a/core/src/main/java/com/github/kr328/clash/core/model/Proxy.kt b/core/src/main/java/com/github/kr328/clash/core/model/Proxy.kt index 19cf097..edf7835 100644 --- a/core/src/main/java/com/github/kr328/clash/core/model/Proxy.kt +++ b/core/src/main/java/com/github/kr328/clash/core/model/Proxy.kt @@ -6,8 +6,6 @@ import kotlinx.serialization.Serializable data class Proxy( val name: String, val type: Type, - val now: String, - val all: List, val delay: Long ) { enum class Type { diff --git a/core/src/main/java/com/github/kr328/clash/core/model/ProxyGroup.kt b/core/src/main/java/com/github/kr328/clash/core/model/ProxyGroup.kt new file mode 100644 index 0000000..aa22ba8 --- /dev/null +++ b/core/src/main/java/com/github/kr328/clash/core/model/ProxyGroup.kt @@ -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 +) \ No newline at end of file diff --git a/core/src/main/java/com/github/kr328/clash/core/transact/ProxyCollections.kt b/core/src/main/java/com/github/kr328/clash/core/transact/ProxyCollections.kt new file mode 100644 index 0000000..ecbe667 --- /dev/null +++ b/core/src/main/java/com/github/kr328/clash/core/transact/ProxyCollections.kt @@ -0,0 +1,7 @@ +package com.github.kr328.clash.core.transact + +import bridge.* +import java.util.* + +class ProxyCollectionImpl : LinkedList(), ProxyCollection +class ProxyGroupCollectionImpl: LinkedList(), ProxyGroupCollection \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/ClashServiceImpl.kt b/service/src/main/java/com/github/kr328/clash/service/ClashServiceImpl.kt index df33882..8697572 100644 --- a/service/src/main/java/com/github/kr328/clash/service/ClashServiceImpl.kt +++ b/service/src/main/java/com/github/kr328/clash/service/ClashServiceImpl.kt @@ -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) } }