Add: url-test proxy group support
This commit is contained in:
parent
f60647430e
commit
e06d17994d
6 changed files with 195 additions and 2 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
148
adapters/urltest.go
Normal 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
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue