diff --git a/adapters/remote/fallback.go b/adapters/remote/fallback.go new file mode 100644 index 00000000..5fe7a773 --- /dev/null +++ b/adapters/remote/fallback.go @@ -0,0 +1,128 @@ +package adapters + +import ( + "errors" + "sync" + "time" + + C "github.com/Dreamacro/clash/constant" +) + +type proxy struct { + RawProxy C.Proxy + Valid bool +} + +type Fallback struct { + name string + proxies []*proxy + rawURL string + delay time.Duration + done chan struct{} +} + +func (f *Fallback) Name() string { + return f.name +} + +func (f *Fallback) Type() C.AdapterType { + return C.Fallback +} + +func (f *Fallback) Now() string { + _, proxy := f.findNextValidProxy(0) + if proxy != nil { + return proxy.RawProxy.Name() + } + return f.proxies[0].RawProxy.Name() +} + +func (f *Fallback) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { + idx := 0 + var proxy *proxy + for { + idx, proxy = f.findNextValidProxy(idx) + if proxy == nil { + break + } + adapter, err = proxy.RawProxy.Generator(addr) + if err != nil { + proxy.Valid = false + idx++ + continue + } + return + } + return nil, errors.New("There are no valid proxy") +} + +func (f *Fallback) Close() { + f.done <- struct{}{} +} + +func (f *Fallback) loop() { + tick := time.NewTicker(f.delay) + go f.validTest() +Loop: + for { + select { + case <-tick.C: + go f.validTest() + case <-f.done: + break Loop + } + } +} + +func (f *Fallback) findNextValidProxy(start int) (int, *proxy) { + for i := start; i < len(f.proxies); i++ { + if f.proxies[i].Valid { + return i, f.proxies[i] + } + } + return -1, nil +} + +func (f *Fallback) validTest() { + wg := sync.WaitGroup{} + wg.Add(len(f.proxies)) + + for _, p := range f.proxies { + go func(p *proxy) { + _, err := DelayTest(p.RawProxy, f.rawURL) + p.Valid = err == nil + wg.Done() + }(p) + } + + wg.Wait() +} + +func NewFallback(name string, proxies []C.Proxy, rawURL string, delay time.Duration) (*Fallback, error) { + _, err := urlToAddr(rawURL) + if err != nil { + return nil, err + } + + if len(proxies) < 1 { + return nil, errors.New("The number of proxies cannot be 0") + } + + warpperProxies := make([]*proxy, len(proxies)) + for idx := range proxies { + warpperProxies[idx] = &proxy{ + RawProxy: proxies[idx], + Valid: true, + } + } + + Fallback := &Fallback{ + name: name, + proxies: warpperProxies, + rawURL: rawURL, + delay: delay, + done: make(chan struct{}), + } + go Fallback.loop() + return Fallback, nil +} diff --git a/config/config.go b/config/config.go index 0fb589a2..fc8e0a27 100644 --- a/config/config.go +++ b/config/config.go @@ -307,6 +307,25 @@ func (c *Config) parseProxies(cfg *ini.File) error { return fmt.Errorf("Selector create error: %s", err.Error()) } proxies[key.Name()] = selector + case "fallback": + if len(rule) < 4 { + return fmt.Errorf("URLTest need more than 4 param") + } + proxyNames := rule[1 : len(rule)-2] + delay, _ := strconv.Atoi(rule[len(rule)-1]) + url := rule[len(rule)-2] + var ps []C.Proxy + for _, name := range proxyNames { + if p, ok := proxies[name]; ok { + ps = append(ps, p) + } + } + + adapter, err := adapters.NewFallback(key.Name(), ps, url, time.Duration(delay)*time.Second) + if err != nil { + return fmt.Errorf("Config error: %s", err.Error()) + } + proxies[key.Name()] = adapter } } @@ -315,6 +334,15 @@ func (c *Config) parseProxies(cfg *ini.File) error { proxies["DIRECT"] = adapters.NewDirect() proxies["REJECT"] = adapters.NewReject() + // close old goroutine + for _, proxy := range c.proxies { + switch raw := proxy.(type) { + case *adapters.URLTest: + raw.Close() + case *adapters.Fallback: + raw.Close() + } + } c.proxies = proxies c.event <- &Event{Type: "proxies", Payload: proxies} return nil diff --git a/constant/adapters.go b/constant/adapters.go index a7fd1557..9f2e0406 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -7,6 +7,7 @@ import ( // Adapter Type const ( Direct AdapterType = iota + Fallback Reject Selector Shadowsocks @@ -38,6 +39,8 @@ func (at AdapterType) String() string { switch at { case Direct: return "Direct" + case Fallback: + return "Fallback" case Reject: return "Reject" case Selector: diff --git a/hub/proxies.go b/hub/proxies.go index 10a8e8cc..19bccca4 100644 --- a/hub/proxies.go +++ b/hub/proxies.go @@ -37,6 +37,11 @@ type URLTest struct { Now string `json:"now"` } +type Fallback struct { + Type string `json:"type"` + Now string `json:"now"` +} + func transformProxy(proxy C.Proxy) interface{} { t := proxy.Type() switch t { @@ -52,6 +57,11 @@ func transformProxy(proxy C.Proxy) interface{} { Type: t.String(), Now: proxy.(*A.URLTest).Now(), } + case C.Fallback: + return Fallback{ + Type: t.String(), + Now: proxy.(*A.Fallback).Now(), + } default: return SampleProxy{ Type: proxy.Type().String(),