Feature: add experimental provider

This commit is contained in:
Dreamacro
2019-12-08 12:17:24 +08:00
parent 4d7096f451
commit c427bc89ef
33 changed files with 1247 additions and 620 deletions

View File

@ -1,4 +1,4 @@
package adapters
package outbound
import (
"context"
@ -38,14 +38,16 @@ func (b *Base) SupportUDP() bool {
return b.udp
}
func (b *Base) Destroy() {}
func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": b.Type().String(),
})
}
func NewBase(name string, tp C.AdapterType, udp bool) *Base {
return &Base{name, tp, udp}
}
type conn struct {
net.Conn
chain C.Chain
@ -199,9 +201,3 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New(10), true}
}
// ProxyGroupOption contain the common options for all kind of ProxyGroup
type ProxyGroupOption struct {
Name string `proxy:"name"`
Proxies []string `proxy:"proxies"`
}

View File

@ -1,4 +1,4 @@
package adapters
package outbound
import (
"context"

View File

@ -1,146 +0,0 @@
package adapters
import (
"context"
"encoding/json"
"errors"
"net"
"sync/atomic"
"time"
"github.com/Dreamacro/clash/common/picker"
C "github.com/Dreamacro/clash/constant"
)
type Fallback struct {
*Base
proxies []C.Proxy
rawURL string
interval time.Duration
done chan struct{}
once int32
}
type FallbackOption struct {
Name string `proxy:"name"`
Proxies []string `proxy:"proxies"`
URL string `proxy:"url"`
Interval int `proxy:"interval"`
}
func (f *Fallback) Now() string {
proxy := f.findAliveProxy()
return proxy.Name()
}
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
}
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
proxy := f.findAliveProxy()
pc, addr, err := proxy.DialUDP(metadata)
if err == nil {
pc.AppendToChains(f)
}
return pc, addr, err
}
func (f *Fallback) SupportUDP() bool {
proxy := f.findAliveProxy()
return proxy.SupportUDP()
}
func (f *Fallback) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range f.proxies {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": f.Type().String(),
"now": f.Now(),
"all": all,
})
}
func (f *Fallback) Destroy() {
f.done <- struct{}{}
}
func (f *Fallback) loop() {
tick := time.NewTicker(f.interval)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go f.validTest(ctx)
Loop:
for {
select {
case <-tick.C:
go f.validTest(ctx)
case <-f.done:
break Loop
}
}
}
func (f *Fallback) findAliveProxy() C.Proxy {
for _, proxy := range f.proxies {
if proxy.Alive() {
return proxy
}
}
return f.proxies[0]
}
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)
for _, p := range f.proxies {
proxy := p
picker.Go(func() (interface{}, error) {
return proxy.URLTest(ctx, f.rawURL)
})
}
picker.Wait()
}
func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) {
_, err := urlToMetadata(option.URL)
if err != nil {
return nil, err
}
if len(proxies) < 1 {
return nil, errors.New("The number of proxies cannot be 0")
}
interval := time.Duration(option.Interval) * time.Second
Fallback := &Fallback{
Base: &Base{
name: option.Name,
tp: C.Fallback,
},
proxies: proxies,
rawURL: option.URL,
interval: interval,
done: make(chan struct{}),
once: 0,
}
go Fallback.loop()
return Fallback, nil
}

View File

@ -1,4 +1,4 @@
package adapters
package outbound
import (
"bufio"

View File

@ -1,172 +0,0 @@
package adapters
import (
"context"
"encoding/json"
"errors"
"net"
"sync"
"time"
"github.com/Dreamacro/clash/common/murmur3"
C "github.com/Dreamacro/clash/constant"
"golang.org/x/net/publicsuffix"
)
type LoadBalance struct {
*Base
proxies []C.Proxy
maxRetry int
rawURL string
interval time.Duration
done chan struct{}
}
func getKey(metadata *C.Metadata) string {
if metadata.Host != "" {
// ip host
if ip := net.ParseIP(metadata.Host); ip != nil {
return metadata.Host
}
if etld, err := publicsuffix.EffectiveTLDPlusOne(metadata.Host); err == nil {
return etld
}
}
if metadata.DstIP == nil {
return ""
}
return metadata.DstIP.String()
}
func jumpHash(key uint64, buckets int32) int32 {
var b, j int64
for j < int64(buckets) {
b = j
key = key*2862933555777941757 + 1
j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1)))
}
return int32(b)
}
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
defer func() {
if err == nil {
c.AppendToChains(lb)
}
}()
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
buckets := int32(len(lb.proxies))
for i := 0; i < lb.maxRetry; i, key = i+1, key+1 {
idx := jumpHash(key, buckets)
proxy := lb.proxies[idx]
if proxy.Alive() {
c, err = proxy.DialContext(ctx, metadata)
return
}
}
c, err = lb.proxies[0].DialContext(ctx, metadata)
return
}
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, addr net.Addr, err error) {
defer func() {
if err == nil {
pc.AppendToChains(lb)
}
}()
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
buckets := int32(len(lb.proxies))
for i := 0; i < lb.maxRetry; i, key = i+1, key+1 {
idx := jumpHash(key, buckets)
proxy := lb.proxies[idx]
if proxy.Alive() {
return proxy.DialUDP(metadata)
}
}
return lb.proxies[0].DialUDP(metadata)
}
func (lb *LoadBalance) SupportUDP() bool {
return true
}
func (lb *LoadBalance) Destroy() {
lb.done <- struct{}{}
}
func (lb *LoadBalance) validTest() {
wg := sync.WaitGroup{}
wg.Add(len(lb.proxies))
for _, p := range lb.proxies {
go func(p C.Proxy) {
p.URLTest(context.Background(), lb.rawURL)
wg.Done()
}(p)
}
wg.Wait()
}
func (lb *LoadBalance) loop() {
tick := time.NewTicker(lb.interval)
go lb.validTest()
Loop:
for {
select {
case <-tick.C:
go lb.validTest()
case <-lb.done:
break Loop
}
}
}
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range lb.proxies {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": lb.Type().String(),
"all": all,
})
}
type LoadBalanceOption struct {
Name string `proxy:"name"`
Proxies []string `proxy:"proxies"`
URL string `proxy:"url"`
Interval int `proxy:"interval"`
}
func NewLoadBalance(option LoadBalanceOption, proxies []C.Proxy) (*LoadBalance, error) {
if len(proxies) == 0 {
return nil, errors.New("Provide at least one proxy")
}
interval := time.Duration(option.Interval) * time.Second
lb := &LoadBalance{
Base: &Base{
name: option.Name,
tp: C.LoadBalance,
},
proxies: proxies,
maxRetry: 3,
rawURL: option.URL,
interval: interval,
done: make(chan struct{}),
}
go lb.loop()
return lb, nil
}

View File

@ -0,0 +1,64 @@
package outbound
import (
"fmt"
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant"
)
func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
proxyType, existType := mapping["type"].(string)
if !existType {
return nil, fmt.Errorf("Missing type")
}
var proxy C.ProxyAdapter
err := fmt.Errorf("Cannot parse")
switch proxyType {
case "ss":
ssOption := &ShadowSocksOption{}
err = decoder.Decode(mapping, ssOption)
if err != nil {
break
}
proxy, err = NewShadowSocks(*ssOption)
case "socks5":
socksOption := &Socks5Option{}
err = decoder.Decode(mapping, socksOption)
if err != nil {
break
}
proxy = NewSocks5(*socksOption)
case "http":
httpOption := &HttpOption{}
err = decoder.Decode(mapping, httpOption)
if err != nil {
break
}
proxy = NewHttp(*httpOption)
case "vmess":
vmessOption := &VmessOption{}
err = decoder.Decode(mapping, vmessOption)
if err != nil {
break
}
proxy, err = NewVmess(*vmessOption)
case "snell":
snellOption := &SnellOption{}
err = decoder.Decode(mapping, snellOption)
if err != nil {
break
}
proxy, err = NewSnell(*snellOption)
default:
return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType)
}
if err != nil {
return nil, err
}
return NewProxy(proxy), nil
}

View File

@ -1,4 +1,4 @@
package adapters
package outbound
import (
"context"

View File

@ -1,87 +0,0 @@
package adapters
import (
"context"
"encoding/json"
"errors"
"net"
C "github.com/Dreamacro/clash/constant"
)
type Selector struct {
*Base
selected C.Proxy
proxies map[string]C.Proxy
proxyList []string
}
type SelectorOption struct {
Name string `proxy:"name"`
Proxies []string `proxy:"proxies"`
}
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := s.selected.DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(s)
}
return c, err
}
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
pc, addr, err := s.selected.DialUDP(metadata)
if err == nil {
pc.AppendToChains(s)
}
return pc, addr, err
}
func (s *Selector) SupportUDP() bool {
return s.selected.SupportUDP()
}
func (s *Selector) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"type": s.Type().String(),
"now": s.Now(),
"all": s.proxyList,
})
}
func (s *Selector) Now() string {
return s.selected.Name()
}
func (s *Selector) Set(name string) error {
proxy, exist := s.proxies[name]
if !exist {
return errors.New("Proxy does not exist")
}
s.selected = proxy
return nil
}
func NewSelector(name string, proxies []C.Proxy) (*Selector, error) {
if len(proxies) == 0 {
return nil, errors.New("Provide at least one proxy")
}
mapping := make(map[string]C.Proxy)
proxyList := make([]string, len(proxies))
for idx, proxy := range proxies {
mapping[proxy.Name()] = proxy
proxyList[idx] = proxy.Name()
}
s := &Selector{
Base: &Base{
name: name,
tp: C.Selector,
},
proxies: mapping,
selected: proxies[0],
proxyList: proxyList,
}
return s, nil
}

View File

@ -1,4 +1,4 @@
package adapters
package outbound
import (
"context"

View File

@ -1,4 +1,4 @@
package adapters
package outbound
import (
"context"

View File

@ -1,4 +1,4 @@
package adapters
package outbound
import (
"context"

View File

@ -1,162 +0,0 @@
package adapters
import (
"context"
"encoding/json"
"errors"
"net"
"sync/atomic"
"time"
"github.com/Dreamacro/clash/common/picker"
C "github.com/Dreamacro/clash/constant"
)
type URLTest struct {
*Base
proxies []C.Proxy
rawURL string
fast C.Proxy
interval time.Duration
done chan struct{}
once int32
}
type URLTestOption struct {
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()
}
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
for i := 0; i < 3; i++ {
c, err = u.fast.DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(u)
return
}
u.fallback()
}
return
}
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
pc, addr, err := u.fast.DialUDP(metadata)
if err == nil {
pc.AppendToChains(u)
}
return pc, addr, err
}
func (u *URLTest) SupportUDP() bool {
return u.fast.SupportUDP()
}
func (u *URLTest) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range u.proxies {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": u.Type().String(),
"now": u.Now(),
"all": all,
})
}
func (u *URLTest) Destroy() {
u.done <- struct{}{}
}
func (u *URLTest) loop() {
tick := time.NewTicker(u.interval)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go u.speedTest(ctx)
Loop:
for {
select {
case <-tick.C:
go u.speedTest(ctx)
case <-u.done:
break Loop
}
}
}
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
}
func (u *URLTest) speedTest(ctx context.Context) {
if !atomic.CompareAndSwapInt32(&u.once, 0, 1) {
return
}
defer atomic.StoreInt32(&u.once, 0)
ctx, cancel := context.WithTimeout(ctx, defaultURLTestTimeout)
defer cancel()
picker := picker.WithoutAutoCancel(ctx)
for _, p := range u.proxies {
proxy := p
picker.Go(func() (interface{}, error) {
_, err := proxy.URLTest(ctx, u.rawURL)
if err != nil {
return nil, err
}
return proxy, nil
})
}
fast := picker.WaitWithoutCancel()
if fast != nil {
u.fast = fast.(C.Proxy)
}
picker.Wait()
}
func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) {
_, err := urlToMetadata(option.URL)
if err != nil {
return nil, err
}
if len(proxies) < 1 {
return nil, errors.New("The number of proxies cannot be 0")
}
interval := time.Duration(option.Interval) * time.Second
urlTest := &URLTest{
Base: &Base{
name: option.Name,
tp: C.URLTest,
},
proxies: proxies[:],
rawURL: option.URL,
fast: proxies[0],
interval: interval,
done: make(chan struct{}),
once: 0,
}
go urlTest.loop()
return urlTest, nil
}

View File

@ -1,4 +1,4 @@
package adapters
package outbound
import (
"bytes"

View File

@ -1,4 +1,4 @@
package adapters
package outbound
import (
"context"