Add: fallback policy group

This commit is contained in:
Dreamacro 2018-09-26 00:34:15 +08:00
parent 3e68faecb2
commit 220e4f0608
4 changed files with 169 additions and 0 deletions

128
adapters/remote/fallback.go Normal file
View File

@ -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
}

View File

@ -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

View File

@ -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:

View File

@ -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(),