Clash.Meta/adapters/outbound/fallback.go

147 lines
2.8 KiB
Go
Raw Normal View History

2018-09-26 00:34:15 +08:00
package adapters
import (
2019-07-02 19:18:03 +08:00
"context"
2018-11-21 13:47:46 +08:00
"encoding/json"
2018-09-26 00:34:15 +08:00
"errors"
2018-12-22 23:56:42 +08:00
"net"
"sync/atomic"
2018-09-26 00:34:15 +08:00
"time"
"github.com/Dreamacro/clash/common/picker"
2018-09-26 00:34:15 +08:00
C "github.com/Dreamacro/clash/constant"
)
type Fallback struct {
2018-12-22 23:56:42 +08:00
*Base
proxies []C.Proxy
2018-10-06 15:13:44 +08:00
rawURL string
interval time.Duration
done chan struct{}
once int32
2018-09-26 00:34:15 +08:00
}
type FallbackOption struct {
2018-10-06 15:13:44 +08:00
Name string `proxy:"name"`
Proxies []string `proxy:"proxies"`
URL string `proxy:"url"`
Interval int `proxy:"interval"`
}
2018-09-26 00:34:15 +08:00
func (f *Fallback) Now() string {
proxy := f.findAliveProxy()
return proxy.Name()
2018-09-26 00:34:15 +08:00
}
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxy := f.findAliveProxy()
c, err := proxy.DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(f)
}
return c, err
2018-09-26 00:34:15 +08:00
}
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
2019-04-23 23:29:36 +08:00
proxy := f.findAliveProxy()
pc, addr, err := proxy.DialUDP(metadata)
if err == nil {
pc.AppendToChains(f)
}
return pc, addr, err
2019-04-23 23:29:36 +08:00
}
func (f *Fallback) SupportUDP() bool {
proxy := f.findAliveProxy()
return proxy.SupportUDP()
}
2018-11-21 13:47:46 +08:00
func (f *Fallback) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range f.proxies {
all = append(all, proxy.Name())
2018-11-21 13:47:46 +08:00
}
return json.Marshal(map[string]interface{}{
"type": f.Type().String(),
"now": f.Now(),
"all": all,
})
}
func (f *Fallback) Destroy() {
2018-09-26 00:34:15 +08:00
f.done <- struct{}{}
}
func (f *Fallback) loop() {
2018-10-06 15:13:44 +08:00
tick := time.NewTicker(f.interval)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go f.validTest(ctx)
2018-09-26 00:34:15 +08:00
Loop:
for {
select {
case <-tick.C:
go f.validTest(ctx)
2018-09-26 00:34:15 +08:00
case <-f.done:
break Loop
}
}
}
func (f *Fallback) findAliveProxy() C.Proxy {
for _, proxy := range f.proxies {
if proxy.Alive() {
return proxy
2018-09-26 00:34:15 +08:00
}
}
return f.proxies[0]
2018-09-26 00:34:15 +08:00
}
func (f *Fallback) validTest(ctx context.Context) {
if !atomic.CompareAndSwapInt32(&f.once, 0, 1) {
return
}
defer atomic.StoreInt32(&f.once, 0)
ctx, cancel := context.WithTimeout(ctx, defaultURLTestTimeout)
defer cancel()
picker := picker.WithoutAutoCancel(ctx)
2018-09-26 00:34:15 +08:00
for _, p := range f.proxies {
proxy := p
picker.Go(func() (interface{}, error) {
return proxy.URLTest(ctx, f.rawURL)
})
2018-09-26 00:34:15 +08:00
}
picker.Wait()
2018-09-26 00:34:15 +08:00
}
func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) {
_, err := urlToMetadata(option.URL)
2018-09-26 00:34:15 +08:00
if err != nil {
return nil, err
}
if len(proxies) < 1 {
return nil, errors.New("The number of proxies cannot be 0")
}
2018-10-06 15:13:44 +08:00
interval := time.Duration(option.Interval) * time.Second
2018-09-26 00:34:15 +08:00
Fallback := &Fallback{
2018-12-22 23:56:42 +08:00
Base: &Base{
name: option.Name,
tp: C.Fallback,
},
proxies: proxies,
2018-10-06 15:13:44 +08:00
rawURL: option.URL,
interval: interval,
done: make(chan struct{}),
once: 0,
2018-09-26 00:34:15 +08:00
}
go Fallback.loop()
return Fallback, nil
}