Add: url-test proxy group support

This commit is contained in:
Dreamacro 2018-06-16 21:34:13 +08:00
parent 961250f998
commit 6ffbb7c89e
6 changed files with 195 additions and 2 deletions

View file

@ -30,6 +30,10 @@ func (d *DirectAdapter) Conn() net.Conn {
type Direct struct { type Direct struct {
} }
func (d *Direct) Name() string {
return "Direct"
}
func (d *Direct) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { func (d *Direct) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
c, err := net.Dial("tcp", net.JoinHostPort(addr.String(), addr.Port)) c, err := net.Dial("tcp", net.JoinHostPort(addr.String(), addr.Port))
if err != nil { if err != nil {

View file

@ -27,6 +27,10 @@ func (r *RejectAdapter) Conn() net.Conn {
type Reject struct { type Reject struct {
} }
func (r *Reject) Name() string {
return "Reject"
}
func (r *Reject) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { func (r *Reject) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
return &RejectAdapter{}, nil return &RejectAdapter{}, nil
} }

View file

@ -35,9 +35,14 @@ func (ss *ShadowsocksAdapter) Conn() net.Conn {
type ShadowSocks struct { type ShadowSocks struct {
server string server string
name string
cipher core.Cipher cipher core.Cipher
} }
func (ss *ShadowSocks) Name() string {
return ss.name
}
func (ss *ShadowSocks) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { func (ss *ShadowSocks) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
c, err := net.Dial("tcp", ss.server) c, err := net.Dial("tcp", ss.server)
if err != nil { if err != nil {
@ -49,7 +54,7 @@ func (ss *ShadowSocks) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err erro
return &ShadowsocksAdapter{conn: c}, err return &ShadowsocksAdapter{conn: c}, err
} }
func NewShadowSocks(ssURL string) (*ShadowSocks, error) { func NewShadowSocks(name string, ssURL string) (*ShadowSocks, error) {
var key []byte var key []byte
server, cipher, password, _ := parseURL(ssURL) server, cipher, password, _ := parseURL(ssURL)
ciph, err := core.PickCipher(cipher, key, password) ciph, err := core.PickCipher(cipher, key, password)
@ -58,6 +63,7 @@ func NewShadowSocks(ssURL string) (*ShadowSocks, error) {
} }
return &ShadowSocks{ return &ShadowSocks{
server: server, server: server,
name: name,
cipher: ciph, cipher: ciph,
}, nil }, nil
} }

148
adapters/urltest.go Normal file
View file

@ -0,0 +1,148 @@
package adapters
import (
"fmt"
"net"
"net/http"
"net/url"
"sync"
"time"
C "github.com/Dreamacro/clash/constant"
)
type URLTest struct {
name string
proxys []C.Proxy
url *url.URL
rawURL string
addr *C.Addr
fast C.Proxy
delay time.Duration
}
func (u *URLTest) Name() string {
return u.name
}
func (u *URLTest) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
return u.fast.Generator(addr)
}
func (u *URLTest) loop() {
tick := time.Tick(u.delay)
go u.speedTest()
for range tick {
go u.speedTest()
}
}
func (u *URLTest) speedTest() {
wg := sync.WaitGroup{}
wg.Add(len(u.proxys))
c := make(chan interface{})
fast := selectFast(c)
timer := time.NewTimer(u.delay)
for _, p := range u.proxys {
go func(p C.Proxy) {
err := getUrl(p, u.addr, u.rawURL)
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 getUrl(proxy C.Proxy, addr *C.Addr, rawURL string) (err error) {
instance, err := proxy.Generator(addr)
if err != nil {
return
}
defer instance.Close()
transport := &http.Transport{
Dial: func(string, string) (net.Conn, error) {
return instance.Conn(), nil
},
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := http.Client{Transport: transport}
req, err := client.Get(rawURL)
if err != nil {
return
}
req.Body.Close()
return nil
}
func selectFast(in chan interface{}) chan interface{} {
out := make(chan interface{})
go func() {
p, open := <-in
if open {
out <- p
}
close(out)
for range in {
}
}()
return out
}
func NewURLTest(name string, proxys []C.Proxy, rawURL string, delay time.Duration) (*URLTest, error) {
u, err := url.Parse(rawURL)
if err != nil {
return nil, err
}
port := u.Port()
if port == "" {
if u.Scheme == "https" {
port = "443"
} else if u.Scheme == "http" {
port = "80"
} else {
return nil, fmt.Errorf("%s scheme not Support", rawURL)
}
}
addr := &C.Addr{
AddrType: C.AtypDomainName,
Host: u.Hostname(),
IP: nil,
Port: port,
}
urlTest := &URLTest{
name: name,
proxys: proxys[:],
rawURL: rawURL,
url: u,
addr: addr,
fast: proxys[0],
delay: delay,
}
go urlTest.loop()
return urlTest, nil
}

View file

@ -18,5 +18,6 @@ type ServerAdapter interface {
} }
type Proxy interface { type Proxy interface {
Name() string
Generator(addr *Addr) (ProxyAdapter, error) Generator(addr *Addr) (ProxyAdapter, error)
} }

View file

@ -2,8 +2,10 @@ package tunnel
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"sync" "sync"
"time"
"github.com/Dreamacro/clash/adapters" "github.com/Dreamacro/clash/adapters"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -43,6 +45,7 @@ func (t *Tunnel) UpdateConfig() (err error) {
proxysConfig := cfg.Section("Proxy") proxysConfig := cfg.Section("Proxy")
rulesConfig := cfg.Section("Rule") rulesConfig := cfg.Section("Rule")
groupsConfig := cfg.Section("Proxy Group")
// parse proxy // parse proxy
for _, key := range proxysConfig.Keys() { for _, key := range proxysConfig.Keys() {
@ -58,7 +61,7 @@ func (t *Tunnel) UpdateConfig() (err error) {
continue continue
} }
ssURL := fmt.Sprintf("ss://%s:%s@%s:%s", proxy[3], proxy[4], proxy[1], proxy[2]) ssURL := fmt.Sprintf("ss://%s:%s@%s:%s", proxy[3], proxy[4], proxy[1], proxy[2])
ss, err := adapters.NewShadowSocks(ssURL) ss, err := adapters.NewShadowSocks(key.Name(), ssURL)
if err != nil { if err != nil {
return err return err
} }
@ -91,6 +94,33 @@ func (t *Tunnel) UpdateConfig() (err error) {
} }
} }
// parse proxy groups
for _, key := range groupsConfig.Keys() {
rule := strings.Split(key.Value(), ",")
if len(rule) < 4 {
continue
}
rule = trimArr(rule)
switch rule[0] {
case "url-test":
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 := proxys[name]; ok {
ps = append(ps, p)
}
}
adapter, err := adapters.NewURLTest(key.Name(), ps, url, time.Duration(delay)*time.Second)
if err != nil {
return fmt.Errorf("Config error: %s", err.Error())
}
proxys[key.Name()] = adapter
}
}
t.configLock.Lock() t.configLock.Lock()
defer t.configLock.Unlock() defer t.configLock.Unlock()