Chore: improve outbound architecture

This commit is contained in:
Dreamacro 2018-12-22 23:56:42 +08:00
parent a7cfc81885
commit cb118d4371
13 changed files with 105 additions and 239 deletions

26
adapters/outbound/base.go Normal file
View file

@ -0,0 +1,26 @@
package adapters
import (
"encoding/json"
C "github.com/Dreamacro/clash/constant"
)
type Base struct {
name string
tp C.AdapterType
}
func (b *Base) Name() string {
return b.name
}
func (b *Base) Type() C.AdapterType {
return b.tp
}
func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": b.Type().String(),
})
}

View file

@ -1,38 +1,16 @@
package adapters package adapters
import ( import (
"encoding/json"
"net" "net"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
// DirectAdapter is a directly connected adapter type Direct struct {
type DirectAdapter struct { *Base
conn net.Conn
} }
// Close is used to close connection func (d *Direct) Generator(metadata *C.Metadata) (net.Conn, error) {
func (d *DirectAdapter) Close() {
d.conn.Close()
}
// Conn is used to http request
func (d *DirectAdapter) Conn() net.Conn {
return d.conn
}
type Direct struct{}
func (d *Direct) Name() string {
return "DIRECT"
}
func (d *Direct) Type() C.AdapterType {
return C.Direct
}
func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
address := net.JoinHostPort(metadata.Host, metadata.Port) address := net.JoinHostPort(metadata.Host, metadata.Port)
if metadata.IP != nil { if metadata.IP != nil {
address = net.JoinHostPort(metadata.IP.String(), metadata.Port) address = net.JoinHostPort(metadata.IP.String(), metadata.Port)
@ -40,18 +18,17 @@ func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err er
c, err := net.DialTimeout("tcp", address, tcpTimeout) c, err := net.DialTimeout("tcp", address, tcpTimeout)
if err != nil { if err != nil {
return return nil, err
} }
tcpKeepAlive(c) tcpKeepAlive(c)
return &DirectAdapter{conn: c}, nil return c, nil
}
func (d *Direct) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": d.Type().String(),
})
} }
func NewDirect() *Direct { func NewDirect() *Direct {
return &Direct{} return &Direct{
Base: &Base{
name: "DIRECT",
tp: C.Direct,
},
}
} }

View file

@ -3,6 +3,7 @@ package adapters
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"net"
"sync" "sync"
"time" "time"
@ -15,7 +16,7 @@ type proxy struct {
} }
type Fallback struct { type Fallback struct {
name string *Base
proxies []*proxy proxies []*proxy
rawURL string rawURL string
interval time.Duration interval time.Duration
@ -29,14 +30,6 @@ type FallbackOption struct {
Interval int `proxy:"interval"` Interval int `proxy:"interval"`
} }
func (f *Fallback) Name() string {
return f.name
}
func (f *Fallback) Type() C.AdapterType {
return C.Fallback
}
func (f *Fallback) Now() string { func (f *Fallback) Now() string {
_, proxy := f.findNextValidProxy(0) _, proxy := f.findNextValidProxy(0)
if proxy != nil { if proxy != nil {
@ -45,7 +38,7 @@ func (f *Fallback) Now() string {
return f.proxies[0].RawProxy.Name() return f.proxies[0].RawProxy.Name()
} }
func (f *Fallback) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { func (f *Fallback) Generator(metadata *C.Metadata) (net.Conn, error) {
idx := 0 idx := 0
var proxy *proxy var proxy *proxy
for { for {
@ -53,13 +46,13 @@ func (f *Fallback) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err
if proxy == nil { if proxy == nil {
break break
} }
adapter, err = proxy.RawProxy.Generator(metadata) adapter, err := proxy.RawProxy.Generator(metadata)
if err != nil { if err != nil {
proxy.Valid = false proxy.Valid = false
idx++ idx++
continue continue
} }
return return adapter, err
} }
return f.proxies[0].RawProxy.Generator(metadata) return f.proxies[0].RawProxy.Generator(metadata)
} }
@ -138,7 +131,10 @@ func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) {
} }
Fallback := &Fallback{ Fallback := &Fallback{
name: option.Name, Base: &Base{
name: option.Name,
tp: C.Fallback,
},
proxies: warpperProxies, proxies: warpperProxies,
rawURL: option.URL, rawURL: option.URL,
interval: interval, interval: interval,

View file

@ -5,7 +5,6 @@ import (
"bytes" "bytes"
"crypto/tls" "crypto/tls"
"encoding/base64" "encoding/base64"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -16,23 +15,9 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
// HTTPAdapter is a proxy adapter
type HTTPAdapter struct {
conn net.Conn
}
// Close is used to close connection
func (ha *HTTPAdapter) Close() {
ha.conn.Close()
}
func (ha *HTTPAdapter) Conn() net.Conn {
return ha.conn
}
type Http struct { type Http struct {
*Base
addr string addr string
name string
user string user string
pass string pass string
tls bool tls bool
@ -50,15 +35,7 @@ type HttpOption struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (h *Http) Name() string { func (h *Http) Generator(metadata *C.Metadata) (net.Conn, error) {
return h.name
}
func (h *Http) Type() C.AdapterType {
return C.Http
}
func (h *Http) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
c, err := net.DialTimeout("tcp", h.addr, tcpTimeout) c, err := net.DialTimeout("tcp", h.addr, tcpTimeout)
if err == nil && h.tls { if err == nil && h.tls {
cc := tls.Client(c, h.tlsConfig) cc := tls.Client(c, h.tlsConfig)
@ -74,7 +51,7 @@ func (h *Http) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err erro
return nil, err return nil, err
} }
return &HTTPAdapter{conn: c}, nil return c, nil
} }
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
@ -118,12 +95,6 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode) return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode)
} }
func (h *Http) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": h.Type().String(),
})
}
func NewHttp(option HttpOption) *Http { func NewHttp(option HttpOption) *Http {
var tlsConfig *tls.Config var tlsConfig *tls.Config
if option.TLS { if option.TLS {
@ -137,8 +108,11 @@ func NewHttp(option HttpOption) *Http {
} }
return &Http{ return &Http{
Base: &Base{
name: option.Name,
tp: C.Http,
},
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
name: option.Name,
user: option.UserName, user: option.UserName,
pass: option.Password, pass: option.Password,
tls: option.TLS, tls: option.TLS,

View file

@ -1,7 +1,6 @@
package adapters package adapters
import ( import (
"encoding/json"
"io" "io"
"net" "net"
"time" "time"
@ -9,42 +8,21 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
// RejectAdapter is a reject connected adapter
type RejectAdapter struct {
conn net.Conn
}
// Close is used to close connection
func (r *RejectAdapter) Close() {}
// Conn is used to http request
func (r *RejectAdapter) Conn() net.Conn {
return r.conn
}
type Reject struct { type Reject struct {
*Base
} }
func (r *Reject) Name() string { func (r *Reject) Generator(metadata *C.Metadata) (net.Conn, error) {
return "REJECT" return &NopConn{}, nil
}
func (r *Reject) Type() C.AdapterType {
return C.Reject
}
func (r *Reject) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
return &RejectAdapter{conn: &NopConn{}}, nil
}
func (r *Reject) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": r.Type().String(),
})
} }
func NewReject() *Reject { func NewReject() *Reject {
return &Reject{} return &Reject{
Base: &Base{
name: "REJECT",
tp: C.Reject,
},
}
} }
type NopConn struct{} type NopConn struct{}

View file

@ -3,13 +3,14 @@ package adapters
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"net"
"sort" "sort"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
type Selector struct { type Selector struct {
name string *Base
selected C.Proxy selected C.Proxy
proxies map[string]C.Proxy proxies map[string]C.Proxy
} }
@ -19,15 +20,7 @@ type SelectorOption struct {
Proxies []string `proxy:"proxies"` Proxies []string `proxy:"proxies"`
} }
func (s *Selector) Name() string { func (s *Selector) Generator(metadata *C.Metadata) (net.Conn, error) {
return s.name
}
func (s *Selector) Type() C.AdapterType {
return C.Selector
}
func (s *Selector) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
return s.selected.Generator(metadata) return s.selected.Generator(metadata)
} }
@ -68,7 +61,10 @@ func NewSelector(name string, proxies []C.Proxy) (*Selector, error) {
} }
s := &Selector{ s := &Selector{
name: name, Base: &Base{
name: name,
tp: C.Selector,
},
proxies: mapping, proxies: mapping,
selected: proxies[0], selected: proxies[0],
} }

View file

@ -7,30 +7,16 @@ import (
"net" "net"
"strconv" "strconv"
"github.com/Dreamacro/clash/component/simple-obfs" obfs "github.com/Dreamacro/clash/component/simple-obfs"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/core" "github.com/Dreamacro/go-shadowsocks2/core"
"github.com/Dreamacro/go-shadowsocks2/socks" "github.com/Dreamacro/go-shadowsocks2/socks"
) )
// ShadowsocksAdapter is a shadowsocks adapter
type ShadowsocksAdapter struct {
conn net.Conn
}
// Close is used to close connection
func (ss *ShadowsocksAdapter) Close() {
ss.conn.Close()
}
func (ss *ShadowsocksAdapter) Conn() net.Conn {
return ss.conn
}
type ShadowSocks struct { type ShadowSocks struct {
*Base
server string server string
name string
obfs string obfs string
obfsHost string obfsHost string
cipher core.Cipher cipher core.Cipher
@ -46,15 +32,7 @@ type ShadowSocksOption struct {
ObfsHost string `proxy:"obfs-host,omitempty"` ObfsHost string `proxy:"obfs-host,omitempty"`
} }
func (ss *ShadowSocks) Name() string { func (ss *ShadowSocks) Generator(metadata *C.Metadata) (net.Conn, error) {
return ss.name
}
func (ss *ShadowSocks) Type() C.AdapterType {
return C.Shadowsocks
}
func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
c, err := net.DialTimeout("tcp", ss.server, tcpTimeout) c, err := net.DialTimeout("tcp", ss.server, tcpTimeout)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error", ss.server) return nil, fmt.Errorf("%s connect error", ss.server)
@ -69,7 +47,7 @@ func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter,
} }
c = ss.cipher.StreamConn(c) c = ss.cipher.StreamConn(c)
_, err = c.Write(serializesSocksAddr(metadata)) _, err = c.Write(serializesSocksAddr(metadata))
return &ShadowsocksAdapter{conn: c}, err return c, err
} }
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) { func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
@ -94,8 +72,11 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
} }
return &ShadowSocks{ return &ShadowSocks{
Base: &Base{
name: option.Name,
tp: C.Shadowsocks,
},
server: server, server: server,
name: option.Name,
cipher: ciph, cipher: ciph,
obfs: obfs, obfs: obfs,
obfsHost: obfsHost, obfsHost: obfsHost,

View file

@ -3,7 +3,6 @@ package adapters
import ( import (
"bytes" "bytes"
"crypto/tls" "crypto/tls"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -15,23 +14,9 @@ import (
"github.com/Dreamacro/go-shadowsocks2/socks" "github.com/Dreamacro/go-shadowsocks2/socks"
) )
// Socks5Adapter is a shadowsocks adapter
type Socks5Adapter struct {
conn net.Conn
}
// Close is used to close connection
func (ss *Socks5Adapter) Close() {
ss.conn.Close()
}
func (ss *Socks5Adapter) Conn() net.Conn {
return ss.conn
}
type Socks5 struct { type Socks5 struct {
*Base
addr string addr string
name string
user string user string
pass string pass string
tls bool tls bool
@ -49,15 +34,7 @@ type Socks5Option struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (ss *Socks5) Name() string { func (ss *Socks5) Generator(metadata *C.Metadata) (net.Conn, error) {
return ss.name
}
func (ss *Socks5) Type() C.AdapterType {
return C.Socks5
}
func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout) c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout)
if err == nil && ss.tls { if err == nil && ss.tls {
@ -73,13 +50,7 @@ func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err e
if err := ss.shakeHand(metadata, c); err != nil { if err := ss.shakeHand(metadata, c); err != nil {
return nil, err return nil, err
} }
return &Socks5Adapter{conn: c}, nil return c, nil
}
func (ss *Socks5) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ss.Type().String(),
})
} }
func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
@ -154,8 +125,11 @@ func NewSocks5(option Socks5Option) *Socks5 {
} }
return &Socks5{ return &Socks5{
Base: &Base{
name: option.Name,
tp: C.Socks5,
},
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
name: option.Name,
user: option.UserName, user: option.UserName,
pass: option.Password, pass: option.Password,
tls: option.TLS, tls: option.TLS,

View file

@ -3,6 +3,7 @@ package adapters
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"net"
"sort" "sort"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -12,7 +13,7 @@ import (
) )
type URLTest struct { type URLTest struct {
name string *Base
proxies []C.Proxy proxies []C.Proxy
rawURL string rawURL string
fast C.Proxy fast C.Proxy
@ -28,19 +29,11 @@ type URLTestOption struct {
Interval int `proxy:"interval"` Interval int `proxy:"interval"`
} }
func (u *URLTest) Name() string {
return u.name
}
func (u *URLTest) Type() C.AdapterType {
return C.URLTest
}
func (u *URLTest) Now() string { func (u *URLTest) Now() string {
return u.fast.Name() return u.fast.Name()
} }
func (u *URLTest) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { func (u *URLTest) Generator(metadata *C.Metadata) (net.Conn, error) {
a, err := u.fast.Generator(metadata) a, err := u.fast.Generator(metadata)
if err != nil { if err != nil {
go u.speedTest() go u.speedTest()
@ -128,7 +121,10 @@ func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) {
interval := time.Duration(option.Interval) * time.Second interval := time.Duration(option.Interval) * time.Second
urlTest := &URLTest{ urlTest := &URLTest{
name: option.Name, Base: &Base{
name: option.Name,
tp: C.URLTest,
},
proxies: proxies[:], proxies: proxies[:],
rawURL: option.URL, rawURL: option.URL,
fast: proxies[0], fast: proxies[0],

View file

@ -36,7 +36,7 @@ func DelayTest(proxy C.Proxy, url string) (t int16, err error) {
defer instance.Close() defer instance.Close()
transport := &http.Transport{ transport := &http.Transport{
Dial: func(string, string) (net.Conn, error) { Dial: func(string, string) (net.Conn, error) {
return instance.Conn(), nil return instance, nil
}, },
// from http.DefaultTransport // from http.DefaultTransport
MaxIdleConns: 100, MaxIdleConns: 100,

View file

@ -1,7 +1,6 @@
package adapters package adapters
import ( import (
"encoding/json"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
@ -11,22 +10,8 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
// VmessAdapter is a vmess adapter
type VmessAdapter struct {
conn net.Conn
}
// Close is used to close connection
func (v *VmessAdapter) Close() {
v.conn.Close()
}
func (v *VmessAdapter) Conn() net.Conn {
return v.conn
}
type Vmess struct { type Vmess struct {
name string *Base
server string server string
client *vmess.Client client *vmess.Client
} }
@ -45,28 +30,14 @@ type VmessOption struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (v *Vmess) Name() string { func (v *Vmess) Generator(metadata *C.Metadata) (net.Conn, error) {
return v.name
}
func (v *Vmess) Type() C.AdapterType {
return C.Vmess
}
func (v *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
c, err := net.DialTimeout("tcp", v.server, tcpTimeout) c, err := net.DialTimeout("tcp", v.server, tcpTimeout)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error", v.server) return nil, fmt.Errorf("%s connect error", v.server)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
c, err = v.client.New(c, parseVmessAddr(metadata)) c, err = v.client.New(c, parseVmessAddr(metadata))
return &VmessAdapter{conn: c}, err return c, err
}
func (v *Vmess) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"type": v.Type().String(),
})
} }
func NewVmess(option VmessOption) (*Vmess, error) { func NewVmess(option VmessOption) (*Vmess, error) {
@ -89,7 +60,10 @@ func NewVmess(option VmessOption) (*Vmess, error) {
} }
return &Vmess{ return &Vmess{
name: option.Name, Base: &Base{
name: option.Name,
tp: C.Vmess,
},
server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
client: client, client: client,
}, nil }, nil

View file

@ -17,11 +17,6 @@ const (
Vmess Vmess
) )
type ProxyAdapter interface {
Conn() net.Conn
Close()
}
type ServerAdapter interface { type ServerAdapter interface {
Metadata() *Metadata Metadata() *Metadata
Close() Close()
@ -30,7 +25,7 @@ type ServerAdapter interface {
type Proxy interface { type Proxy interface {
Name() string Name() string
Type() AdapterType Type() AdapterType
Generator(metadata *Metadata) (ProxyAdapter, error) Generator(metadata *Metadata) (net.Conn, error)
MarshalJSON() ([]byte, error) MarshalJSON() ([]byte, error)
} }

View file

@ -9,8 +9,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/Dreamacro/clash/adapters/inbound" adapters "github.com/Dreamacro/clash/adapters/inbound"
C "github.com/Dreamacro/clash/constant"
) )
const ( const (
@ -22,8 +21,8 @@ const (
var bufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }} var bufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }}
func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) { func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
conn := newTrafficTrack(proxy.Conn(), t.traffic) conn := newTrafficTrack(outbound, t.traffic)
req := request.R req := request.R
host := req.Host host := req.Host
keepalive := true keepalive := true
@ -76,8 +75,8 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter)
} }
} }
func (t *Tunnel) handleSOCKS(request *adapters.SocketAdapter, proxy C.ProxyAdapter) { func (t *Tunnel) handleSOCKS(request *adapters.SocketAdapter, outbound net.Conn) {
conn := newTrafficTrack(proxy.Conn(), t.traffic) conn := newTrafficTrack(outbound, t.traffic)
relay(request.Conn(), conn) relay(request.Conn(), conn)
} }