clash/adapters/outbound/urltest.go

165 lines
2.8 KiB
Go
Raw Normal View History

2018-06-16 13:34:13 +00:00
package adapters
import (
"context"
2018-11-21 05:47:46 +00:00
"encoding/json"
"errors"
2018-12-22 15:56:42 +00:00
"net"
2018-11-21 05:47:46 +00:00
"sort"
2018-06-16 13:34:13 +00:00
"sync"
"sync/atomic"
2018-06-16 13:34:13 +00:00
"time"
"github.com/Dreamacro/clash/common/picker"
2018-06-16 13:34:13 +00:00
C "github.com/Dreamacro/clash/constant"
)
type URLTest struct {
2018-12-22 15:56:42 +00:00
*Base
2018-10-06 07:13:44 +00:00
proxies []C.Proxy
rawURL string
fast C.Proxy
interval time.Duration
done chan struct{}
once int32
2018-06-16 13:34:13 +00:00
}
type URLTestOption struct {
2018-10-06 07:13:44 +00:00
Name string `proxy:"name"`
Proxies []string `proxy:"proxies"`
URL string `proxy:"url"`
Interval int `proxy:"interval"`
}
func (u *URLTest) Now() string {
return u.fast.Name()
}
2019-03-03 03:59:07 +00:00
func (u *URLTest) Dial(metadata *C.Metadata) (net.Conn, error) {
a, err := u.fast.Dial(metadata)
if err != nil {
u.fallback()
}
return a, err
2018-06-16 13:34:13 +00:00
}
2019-04-23 15:29:36 +00:00
func (u *URLTest) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
return u.fast.DialUDP(metadata)
}
func (u *URLTest) SupportUDP() bool {
return u.fast.SupportUDP()
}
2018-11-21 05:47:46 +00:00
func (u *URLTest) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range u.proxies {
all = append(all, proxy.Name())
}
sort.Strings(all)
return json.Marshal(map[string]interface{}{
"type": u.Type().String(),
"now": u.Now(),
"all": all,
})
}
func (u *URLTest) Destroy() {
u.done <- struct{}{}
}
2018-06-16 13:34:13 +00:00
func (u *URLTest) loop() {
2018-10-06 07:13:44 +00:00
tick := time.NewTicker(u.interval)
2018-06-16 13:34:13 +00:00
go u.speedTest()
Loop:
for {
select {
case <-tick.C:
go u.speedTest()
case <-u.done:
break Loop
}
2018-06-16 13:34:13 +00:00
}
}
func (u *URLTest) fallback() {
fast := u.proxies[0]
min := fast.LastDelay()
for _, proxy := range u.proxies[1:] {
if !proxy.Alive() {
continue
}
delay := proxy.LastDelay()
if delay < min {
fast = proxy
min = delay
}
}
u.fast = fast
}
2018-06-16 13:34:13 +00:00
func (u *URLTest) speedTest() {
if !atomic.CompareAndSwapInt32(&u.once, 0, 1) {
return
}
defer atomic.StoreInt32(&u.once, 0)
2018-06-16 13:34:13 +00:00
wg := sync.WaitGroup{}
2018-07-18 13:50:16 +00:00
wg.Add(len(u.proxies))
2018-06-16 13:34:13 +00:00
c := make(chan interface{})
fast := picker.SelectFast(context.Background(), c)
2018-10-06 07:13:44 +00:00
timer := time.NewTimer(u.interval)
2018-06-16 13:34:13 +00:00
2018-07-18 13:50:16 +00:00
for _, p := range u.proxies {
2018-06-16 13:34:13 +00:00
go func(p C.Proxy) {
_, err := p.URLTest(u.rawURL)
2018-06-16 13:34:13 +00:00
if err == nil {
c <- p
}
wg.Done()
}(p)
}
go func() {
wg.Wait()
close(c)
}()
select {
case <-timer.C:
// Wait for fast to return or close.
<-fast
case p, open := <-fast:
if open {
u.fast = p.(C.Proxy)
}
}
}
func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) {
_, err := urlToMetadata(option.URL)
2018-06-16 13:34:13 +00:00
if err != nil {
return nil, err
}
if len(proxies) < 1 {
return nil, errors.New("The number of proxies cannot be 0")
}
2018-06-16 13:34:13 +00:00
2018-10-06 07:13:44 +00:00
interval := time.Duration(option.Interval) * time.Second
2018-06-16 13:34:13 +00:00
urlTest := &URLTest{
2018-12-22 15:56:42 +00:00
Base: &Base{
name: option.Name,
tp: C.URLTest,
},
2018-10-06 07:13:44 +00:00
proxies: proxies[:],
rawURL: option.URL,
fast: proxies[0],
interval: interval,
done: make(chan struct{}),
once: 0,
2018-06-16 13:34:13 +00:00
}
go urlTest.loop()
return urlTest, nil
}