mirror of
https://gitclone.com/github.com/MetaCubeX/Clash.Meta
synced 2025-02-24 05:02:14 +08:00
Feature: support relay (proxy chains) (#539)
This commit is contained in:
parent
70a19b999d
commit
c0a2473160
10
README.md
10
README.md
@ -255,6 +255,16 @@ proxies:
|
|||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
|
|
||||||
proxy-groups:
|
proxy-groups:
|
||||||
|
# relay chains the proxies. proxies shall not contain a proxy-group. No UDP support.
|
||||||
|
# Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet
|
||||||
|
- name: "relay"
|
||||||
|
type: relay
|
||||||
|
proxies:
|
||||||
|
- http
|
||||||
|
- vmess
|
||||||
|
- ss1
|
||||||
|
- ss2
|
||||||
|
|
||||||
# url-test select which proxy will be used by benchmarking speed to a URL.
|
# url-test select which proxy will be used by benchmarking speed to a URL.
|
||||||
- name: "auto"
|
- name: "auto"
|
||||||
type: url-test
|
type: url-test
|
||||||
|
@ -18,6 +18,7 @@ var (
|
|||||||
|
|
||||||
type Base struct {
|
type Base struct {
|
||||||
name string
|
name string
|
||||||
|
addr string
|
||||||
tp C.AdapterType
|
tp C.AdapterType
|
||||||
udp bool
|
udp bool
|
||||||
}
|
}
|
||||||
@ -30,6 +31,10 @@ func (b *Base) Type() C.AdapterType {
|
|||||||
return b.tp
|
return b.tp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
|
return c, errors.New("no support")
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
return nil, errors.New("no support")
|
return nil, errors.New("no support")
|
||||||
}
|
}
|
||||||
@ -44,8 +49,12 @@ func (b *Base) MarshalJSON() ([]byte, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBase(name string, tp C.AdapterType, udp bool) *Base {
|
func (b *Base) Addr() string {
|
||||||
return &Base{name, tp, udp}
|
return b.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base {
|
||||||
|
return &Base{name, addr, tp, udp}
|
||||||
}
|
}
|
||||||
|
|
||||||
type conn struct {
|
type conn struct {
|
||||||
@ -61,7 +70,7 @@ func (c *conn) AppendToChains(a C.ProxyAdapter) {
|
|||||||
c.chain = append(c.chain, a.Name())
|
c.chain = append(c.chain, a.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
||||||
return &conn{c, []string{a.Name()}}
|
return &conn{c, []string{a.Name()}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
return newConn(c, d), nil
|
return NewConn(c, d), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
|
@ -19,7 +19,6 @@ import (
|
|||||||
|
|
||||||
type Http struct {
|
type Http struct {
|
||||||
*Base
|
*Base
|
||||||
addr string
|
|
||||||
user string
|
user string
|
||||||
pass string
|
pass string
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
@ -35,23 +34,35 @@ type HttpOption struct {
|
|||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", h.addr)
|
if h.tlsConfig != nil {
|
||||||
if err == nil && h.tlsConfig != nil {
|
|
||||||
cc := tls.Client(c, h.tlsConfig)
|
cc := tls.Client(c, h.tlsConfig)
|
||||||
err = cc.Handshake()
|
err := cc.Handshake()
|
||||||
c = cc
|
c = cc
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := h.shakeHand(metadata, c); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", h.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
if err := h.shakeHand(metadata, c); err != nil {
|
|
||||||
|
c, err = h.StreamConn(c, metadata)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return newConn(c, h), nil
|
return NewConn(c, h), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
||||||
@ -113,9 +124,9 @@ func NewHttp(option HttpOption) *Http {
|
|||||||
return &Http{
|
return &Http{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Http,
|
tp: C.Http,
|
||||||
},
|
},
|
||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
|
||||||
user: option.UserName,
|
user: option.UserName,
|
||||||
pass: option.Password,
|
pass: option.Password,
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
|
@ -15,7 +15,7 @@ type Reject struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
return newConn(&NopConn{}, r), nil
|
return NewConn(&NopConn{}, r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
|
|
||||||
type ShadowSocks struct {
|
type ShadowSocks struct {
|
||||||
*Base
|
*Base
|
||||||
server string
|
|
||||||
cipher core.Cipher
|
cipher core.Cipher
|
||||||
|
|
||||||
// obfs
|
// obfs
|
||||||
@ -60,28 +59,34 @@ type v2rayObfsOption struct {
|
|||||||
Mux bool `obfs:"mux,omitempty"`
|
Mux bool `obfs:"mux,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", ss.server)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.server, err)
|
|
||||||
}
|
|
||||||
tcpKeepAlive(c)
|
|
||||||
switch ss.obfsMode {
|
switch ss.obfsMode {
|
||||||
case "tls":
|
case "tls":
|
||||||
c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
|
c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
|
||||||
case "http":
|
case "http":
|
||||||
_, port, _ := net.SplitHostPort(ss.server)
|
_, port, _ := net.SplitHostPort(ss.addr)
|
||||||
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
|
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
|
||||||
case "websocket":
|
case "websocket":
|
||||||
var err error
|
var err error
|
||||||
c, err = v2rayObfs.NewV2rayObfs(c, ss.v2rayOption)
|
c, err = v2rayObfs.NewV2rayObfs(c, ss.v2rayOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.server, err)
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c = ss.cipher.StreamConn(c)
|
c = ss.cipher.StreamConn(c)
|
||||||
_, err = c.Write(serializesSocksAddr(metadata))
|
_, err := c.Write(serializesSocksAddr(metadata))
|
||||||
return newConn(c, ss), err
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
c, err = ss.StreamConn(c, metadata)
|
||||||
|
return NewConn(c, ss), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
@ -90,7 +95,7 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := resolveUDPAddr("udp", ss.server)
|
addr, err := resolveUDPAddr("udp", ss.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -106,12 +111,12 @@ func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||||
server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
cipher := option.Cipher
|
cipher := option.Cipher
|
||||||
password := option.Password
|
password := option.Password
|
||||||
ciph, err := core.PickCipher(cipher, nil, password)
|
ciph, err := core.PickCipher(cipher, nil, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ss %s initialize error: %w", server, err)
|
return nil, fmt.Errorf("ss %s initialize error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var v2rayOption *v2rayObfs.Option
|
var v2rayOption *v2rayObfs.Option
|
||||||
@ -133,22 +138,22 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
if option.Plugin == "obfs" {
|
if option.Plugin == "obfs" {
|
||||||
opts := simpleObfsOption{Host: "bing.com"}
|
opts := simpleObfsOption{Host: "bing.com"}
|
||||||
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
|
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
|
||||||
return nil, fmt.Errorf("ss %s initialize obfs error: %w", server, err)
|
return nil, fmt.Errorf("ss %s initialize obfs error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Mode != "tls" && opts.Mode != "http" {
|
if opts.Mode != "tls" && opts.Mode != "http" {
|
||||||
return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode)
|
return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode)
|
||||||
}
|
}
|
||||||
obfsMode = opts.Mode
|
obfsMode = opts.Mode
|
||||||
obfsOption = &opts
|
obfsOption = &opts
|
||||||
} else if option.Plugin == "v2ray-plugin" {
|
} else if option.Plugin == "v2ray-plugin" {
|
||||||
opts := v2rayObfsOption{Host: "bing.com", Mux: true}
|
opts := v2rayObfsOption{Host: "bing.com", Mux: true}
|
||||||
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
|
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
|
||||||
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", server, err)
|
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Mode != "websocket" {
|
if opts.Mode != "websocket" {
|
||||||
return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode)
|
return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode)
|
||||||
}
|
}
|
||||||
obfsMode = opts.Mode
|
obfsMode = opts.Mode
|
||||||
|
|
||||||
@ -172,10 +177,10 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
return &ShadowSocks{
|
return &ShadowSocks{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
|
addr: addr,
|
||||||
tp: C.Shadowsocks,
|
tp: C.Shadowsocks,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
},
|
},
|
||||||
server: server,
|
|
||||||
cipher: ciph,
|
cipher: ciph,
|
||||||
|
|
||||||
obfsMode: obfsMode,
|
obfsMode: obfsMode,
|
||||||
|
@ -15,7 +15,6 @@ import (
|
|||||||
|
|
||||||
type Snell struct {
|
type Snell struct {
|
||||||
*Base
|
*Base
|
||||||
server string
|
|
||||||
psk []byte
|
psk []byte
|
||||||
obfsOption *simpleObfsOption
|
obfsOption *simpleObfsOption
|
||||||
}
|
}
|
||||||
@ -28,45 +27,51 @@ type SnellOption struct {
|
|||||||
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
|
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", s.server)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s connect error: %w", s.server, err)
|
|
||||||
}
|
|
||||||
tcpKeepAlive(c)
|
|
||||||
switch s.obfsOption.Mode {
|
switch s.obfsOption.Mode {
|
||||||
case "tls":
|
case "tls":
|
||||||
c = obfs.NewTLSObfs(c, s.obfsOption.Host)
|
c = obfs.NewTLSObfs(c, s.obfsOption.Host)
|
||||||
case "http":
|
case "http":
|
||||||
_, port, _ := net.SplitHostPort(s.server)
|
_, port, _ := net.SplitHostPort(s.addr)
|
||||||
c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port)
|
c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port)
|
||||||
}
|
}
|
||||||
c = snell.StreamConn(c, s.psk)
|
c = snell.StreamConn(c, s.psk)
|
||||||
port, _ := strconv.Atoi(metadata.DstPort)
|
port, _ := strconv.Atoi(metadata.DstPort)
|
||||||
err = snell.WriteHeader(c, metadata.String(), uint(port))
|
err := snell.WriteHeader(c, metadata.String(), uint(port))
|
||||||
return newConn(c, s), err
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", s.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
c, err = s.StreamConn(c, metadata)
|
||||||
|
return NewConn(c, s), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSnell(option SnellOption) (*Snell, error) {
|
func NewSnell(option SnellOption) (*Snell, error) {
|
||||||
server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
psk := []byte(option.Psk)
|
psk := []byte(option.Psk)
|
||||||
|
|
||||||
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
|
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
|
||||||
obfsOption := &simpleObfsOption{Host: "bing.com"}
|
obfsOption := &simpleObfsOption{Host: "bing.com"}
|
||||||
if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil {
|
if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil {
|
||||||
return nil, fmt.Errorf("snell %s initialize obfs error: %w", server, err)
|
return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if obfsOption.Mode != "tls" && obfsOption.Mode != "http" {
|
if obfsOption.Mode != "tls" && obfsOption.Mode != "http" {
|
||||||
return nil, fmt.Errorf("snell %s obfs mode error: %s", server, obfsOption.Mode)
|
return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Snell{
|
return &Snell{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
|
addr: addr,
|
||||||
tp: C.Snell,
|
tp: C.Snell,
|
||||||
},
|
},
|
||||||
server: server,
|
|
||||||
psk: psk,
|
psk: psk,
|
||||||
obfsOption: obfsOption,
|
obfsOption: obfsOption,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -17,7 +17,6 @@ import (
|
|||||||
|
|
||||||
type Socks5 struct {
|
type Socks5 struct {
|
||||||
*Base
|
*Base
|
||||||
addr string
|
|
||||||
user string
|
user string
|
||||||
pass string
|
pass string
|
||||||
tls bool
|
tls bool
|
||||||
@ -36,19 +35,16 @@ type Socks5Option struct {
|
|||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
if ss.tls {
|
||||||
|
|
||||||
if err == nil && ss.tls {
|
|
||||||
cc := tls.Client(c, ss.tlsConfig)
|
cc := tls.Client(c, ss.tlsConfig)
|
||||||
err = cc.Handshake()
|
err := cc.Handshake()
|
||||||
c = cc
|
c = cc
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
|
||||||
}
|
|
||||||
tcpKeepAlive(c)
|
|
||||||
var user *socks5.User
|
var user *socks5.User
|
||||||
if ss.user != "" {
|
if ss.user != "" {
|
||||||
user = &socks5.User{
|
user = &socks5.User{
|
||||||
@ -59,7 +55,22 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn
|
|||||||
if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
|
if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return newConn(c, ss), nil
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
c, err = ss.StreamConn(c, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewConn(c, ss), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
@ -127,10 +138,10 @@ func NewSocks5(option Socks5Option) *Socks5 {
|
|||||||
return &Socks5{
|
return &Socks5{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Socks5,
|
tp: C.Socks5,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
},
|
},
|
||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
|
||||||
user: option.UserName,
|
user: option.UserName,
|
||||||
pass: option.Password,
|
pass: option.Password,
|
||||||
tls: option.TLS,
|
tls: option.TLS,
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
type Trojan struct {
|
type Trojan struct {
|
||||||
*Base
|
*Base
|
||||||
server string
|
|
||||||
instance *trojan.Trojan
|
instance *trojan.Trojan
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,32 +28,41 @@ type TrojanOption struct {
|
|||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", t.server)
|
c, err := t.instance.StreamConn(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.server, err)
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
}
|
|
||||||
tcpKeepAlive(c)
|
|
||||||
c, err = t.instance.StreamConn(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.server, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
|
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
|
||||||
return newConn(c, t), err
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
c, err = t.StreamConn(c, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewConn(c, t), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
c, err := dialer.DialContext(ctx, "tcp", t.server)
|
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.server, err)
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
c, err = t.instance.StreamConn(c)
|
c, err = t.instance.StreamConn(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.server, err)
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||||
@ -73,7 +81,7 @@ func (t *Trojan) MarshalJSON() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||||
server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
|
|
||||||
tOption := &trojan.Option{
|
tOption := &trojan.Option{
|
||||||
Password: option.Password,
|
Password: option.Password,
|
||||||
@ -90,10 +98,10 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
return &Trojan{
|
return &Trojan{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
|
addr: addr,
|
||||||
tp: C.Trojan,
|
tp: C.Trojan,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
},
|
},
|
||||||
server: server,
|
|
||||||
instance: trojan.New(tOption),
|
instance: trojan.New(tOption),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ import (
|
|||||||
|
|
||||||
type Vmess struct {
|
type Vmess struct {
|
||||||
*Base
|
*Base
|
||||||
server string
|
|
||||||
client *vmess.Client
|
client *vmess.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,14 +34,19 @@ type VmessOption struct {
|
|||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
|
return v.client.New(c, parseVmessAddr(metadata))
|
||||||
|
}
|
||||||
|
|
||||||
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", v.server)
|
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error", v.server)
|
return nil, fmt.Errorf("%s connect error", v.addr)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
c, err = v.client.New(c, parseVmessAddr(metadata))
|
|
||||||
return newConn(c, v), err
|
c, err = v.StreamConn(c, metadata)
|
||||||
|
return NewConn(c, v), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
@ -57,9 +61,9 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
c, err := dialer.DialContext(ctx, "tcp", v.server)
|
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error", v.server)
|
return nil, fmt.Errorf("%s connect error", v.addr)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
c, err = v.client.New(c, parseVmessAddr(metadata))
|
c, err = v.client.New(c, parseVmessAddr(metadata))
|
||||||
@ -91,10 +95,10 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
return &Vmess{
|
return &Vmess{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Vmess,
|
tp: C.Vmess,
|
||||||
udp: true,
|
udp: true,
|
||||||
},
|
},
|
||||||
server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
|
||||||
client: client,
|
client: client,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ func (f *Fallback) findAliveProxy() C.Proxy {
|
|||||||
|
|
||||||
func NewFallback(name string, providers []provider.ProxyProvider) *Fallback {
|
func NewFallback(name string, providers []provider.ProxyProvider) *Fallback {
|
||||||
return &Fallback{
|
return &Fallback{
|
||||||
Base: outbound.NewBase(name, C.Fallback, false),
|
Base: outbound.NewBase(name, "", C.Fallback, false),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
providers: providers,
|
providers: providers,
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
|||||||
|
|
||||||
func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance {
|
func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance {
|
||||||
return &LoadBalance{
|
return &LoadBalance{
|
||||||
Base: outbound.NewBase(name, C.LoadBalance, false),
|
Base: outbound.NewBase(name, "", C.LoadBalance, false),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
maxRetry: 3,
|
maxRetry: 3,
|
||||||
providers: providers,
|
providers: providers,
|
||||||
|
@ -64,7 +64,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
|||||||
providers = append(providers, pd)
|
providers = append(providers, pd)
|
||||||
} else {
|
} else {
|
||||||
// select don't need health check
|
// select don't need health check
|
||||||
if groupOption.Type == "select" {
|
if groupOption.Type == "select" || groupOption.Type == "relay" {
|
||||||
hc := provider.NewHealthCheck(ps, "", 0)
|
hc := provider.NewHealthCheck(ps, "", 0)
|
||||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -108,6 +108,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
|||||||
group = NewFallback(groupName, providers)
|
group = NewFallback(groupName, providers)
|
||||||
case "load-balance":
|
case "load-balance":
|
||||||
group = NewLoadBalance(groupName, providers)
|
group = NewLoadBalance(groupName, providers)
|
||||||
|
case "relay":
|
||||||
|
group = NewRelay(groupName, providers)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
|
return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
|
||||||
}
|
}
|
||||||
|
84
adapters/outboundgroup/relay.go
Normal file
84
adapters/outboundgroup/relay.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package outboundgroup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapters/outbound"
|
||||||
|
"github.com/Dreamacro/clash/adapters/provider"
|
||||||
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Relay struct {
|
||||||
|
*outbound.Base
|
||||||
|
single *singledo.Single
|
||||||
|
providers []provider.ProxyProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
|
proxies := r.proxies()
|
||||||
|
if len(proxies) == 0 {
|
||||||
|
return nil, errors.New("Proxy does not exist")
|
||||||
|
}
|
||||||
|
first := proxies[0]
|
||||||
|
last := proxies[len(proxies)-1]
|
||||||
|
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", first.Addr())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
var currentMeta *C.Metadata
|
||||||
|
for _, proxy := range proxies[1:] {
|
||||||
|
currentMeta, err = addrToMetadata(proxy.Addr())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err = first.StreamConn(c, currentMeta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
first = proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err = last.StreamConn(c, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return outbound.NewConn(c, r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Relay) MarshalJSON() ([]byte, error) {
|
||||||
|
var all []string
|
||||||
|
for _, proxy := range r.proxies() {
|
||||||
|
all = append(all, proxy.Name())
|
||||||
|
}
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"type": r.Type().String(),
|
||||||
|
"all": all,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Relay) proxies() []C.Proxy {
|
||||||
|
elm, _, _ := r.single.Do(func() (interface{}, error) {
|
||||||
|
return getProvidersProxies(r.providers), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return elm.([]C.Proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRelay(name string, providers []provider.ProxyProvider) *Relay {
|
||||||
|
return &Relay{
|
||||||
|
Base: outbound.NewBase(name, "", C.Relay, false),
|
||||||
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
|
providers: providers,
|
||||||
|
}
|
||||||
|
}
|
@ -77,7 +77,7 @@ func (s *Selector) proxies() []C.Proxy {
|
|||||||
func NewSelector(name string, providers []provider.ProxyProvider) *Selector {
|
func NewSelector(name string, providers []provider.ProxyProvider) *Selector {
|
||||||
selected := providers[0].Proxies()[0]
|
selected := providers[0].Proxies()[0]
|
||||||
return &Selector{
|
return &Selector{
|
||||||
Base: outbound.NewBase(name, C.Selector, false),
|
Base: outbound.NewBase(name, "", C.Selector, false),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
providers: providers,
|
providers: providers,
|
||||||
selected: selected,
|
selected: selected,
|
||||||
|
@ -86,7 +86,7 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
|
|||||||
|
|
||||||
func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest {
|
func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest {
|
||||||
return &URLTest{
|
return &URLTest{
|
||||||
Base: outbound.NewBase(name, C.URLTest, false),
|
Base: outbound.NewBase(name, "", C.URLTest, false),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
fastSingle: singledo.NewSingle(time.Second * 10),
|
fastSingle: singledo.NewSingle(time.Second * 10),
|
||||||
providers: providers,
|
providers: providers,
|
||||||
|
53
adapters/outboundgroup/util.go
Normal file
53
adapters/outboundgroup/util.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package outboundgroup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
|
||||||
|
host, port, err := net.SplitHostPort(rawAddress)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("addrToMetadata failed: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(host)
|
||||||
|
if ip != nil {
|
||||||
|
if ip.To4() != nil {
|
||||||
|
addr = &C.Metadata{
|
||||||
|
AddrType: C.AtypIPv4,
|
||||||
|
Host: "",
|
||||||
|
DstIP: ip,
|
||||||
|
DstPort: port,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
addr = &C.Metadata{
|
||||||
|
AddrType: C.AtypIPv6,
|
||||||
|
Host: "",
|
||||||
|
DstIP: ip,
|
||||||
|
DstPort: port,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addr = &C.Metadata{
|
||||||
|
AddrType: C.AtypDomainName,
|
||||||
|
Host: host,
|
||||||
|
DstIP: nil,
|
||||||
|
DstPort: port,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcpKeepAlive(c net.Conn) {
|
||||||
|
if tcp, ok := c.(*net.TCPConn); ok {
|
||||||
|
tcp.SetKeepAlive(true)
|
||||||
|
tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ const (
|
|||||||
Vmess
|
Vmess
|
||||||
Trojan
|
Trojan
|
||||||
|
|
||||||
|
Relay
|
||||||
Selector
|
Selector
|
||||||
Fallback
|
Fallback
|
||||||
URLTest
|
URLTest
|
||||||
@ -62,10 +63,12 @@ type PacketConn interface {
|
|||||||
type ProxyAdapter interface {
|
type ProxyAdapter interface {
|
||||||
Name() string
|
Name() string
|
||||||
Type() AdapterType
|
Type() AdapterType
|
||||||
|
StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error)
|
||||||
DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
|
DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
|
||||||
DialUDP(metadata *Metadata) (PacketConn, error)
|
DialUDP(metadata *Metadata) (PacketConn, error)
|
||||||
SupportUDP() bool
|
SupportUDP() bool
|
||||||
MarshalJSON() ([]byte, error)
|
MarshalJSON() ([]byte, error)
|
||||||
|
Addr() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type DelayHistory struct {
|
type DelayHistory struct {
|
||||||
@ -105,6 +108,8 @@ func (at AdapterType) String() string {
|
|||||||
case Trojan:
|
case Trojan:
|
||||||
return "Trojan"
|
return "Trojan"
|
||||||
|
|
||||||
|
case Relay:
|
||||||
|
return "Relay"
|
||||||
case Selector:
|
case Selector:
|
||||||
return "Selector"
|
return "Selector"
|
||||||
case Fallback:
|
case Fallback:
|
||||||
|
Loading…
Reference in New Issue
Block a user