2023-04-23 19:57:54 +08:00
|
|
|
package outbound
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-04-26 15:57:25 +08:00
|
|
|
"errors"
|
2023-04-23 19:57:54 +08:00
|
|
|
"net"
|
2023-04-24 10:30:12 +08:00
|
|
|
"runtime"
|
2023-04-23 19:57:54 +08:00
|
|
|
|
2023-04-24 10:30:12 +08:00
|
|
|
CN "github.com/Dreamacro/clash/common/net"
|
2023-04-23 19:57:54 +08:00
|
|
|
"github.com/Dreamacro/clash/component/dialer"
|
|
|
|
"github.com/Dreamacro/clash/component/proxydialer"
|
2023-04-26 15:57:25 +08:00
|
|
|
"github.com/Dreamacro/clash/component/resolver"
|
2023-04-23 19:57:54 +08:00
|
|
|
C "github.com/Dreamacro/clash/constant"
|
|
|
|
|
|
|
|
mux "github.com/sagernet/sing-mux"
|
|
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
|
|
M "github.com/sagernet/sing/common/metadata"
|
|
|
|
N "github.com/sagernet/sing/common/network"
|
|
|
|
)
|
|
|
|
|
|
|
|
type SingMux struct {
|
|
|
|
C.ProxyAdapter
|
2023-04-23 20:55:42 +08:00
|
|
|
base ProxyBase
|
|
|
|
client *mux.Client
|
|
|
|
dialer *muxSingDialer
|
|
|
|
onlyTcp bool
|
2023-04-23 19:57:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
type SingMuxOption struct {
|
|
|
|
Enabled bool `proxy:"enabled,omitempty"`
|
|
|
|
Protocol string `proxy:"protocol,omitempty"`
|
|
|
|
MaxConnections int `proxy:"max-connections,omitempty"`
|
|
|
|
MinStreams int `proxy:"min-streams,omitempty"`
|
|
|
|
MaxStreams int `proxy:"max-streams,omitempty"`
|
|
|
|
Padding bool `proxy:"padding,omitempty"`
|
2023-04-23 20:55:42 +08:00
|
|
|
Statistic bool `proxy:"statistic,omitempty"`
|
|
|
|
OnlyTcp bool `proxy:"only-tcp,omitempty"`
|
2023-04-23 19:57:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
type ProxyBase interface {
|
|
|
|
DialOptions(opts ...dialer.Option) []dialer.Option
|
|
|
|
}
|
|
|
|
|
|
|
|
type muxSingDialer struct {
|
2023-04-23 20:55:42 +08:00
|
|
|
dialer dialer.Dialer
|
|
|
|
proxy C.ProxyAdapter
|
|
|
|
statistic bool
|
2023-04-23 19:57:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
var _ N.Dialer = (*muxSingDialer)(nil)
|
|
|
|
|
|
|
|
func (d *muxSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
2023-04-23 20:55:42 +08:00
|
|
|
var cDialer C.Dialer = proxydialer.New(d.proxy, d.dialer, d.statistic)
|
2023-04-23 19:57:54 +08:00
|
|
|
return cDialer.DialContext(ctx, network, destination.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *muxSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
2023-04-23 20:55:42 +08:00
|
|
|
var cDialer C.Dialer = proxydialer.New(d.proxy, d.dialer, d.statistic)
|
2023-04-23 19:57:54 +08:00
|
|
|
return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
|
|
|
options := s.base.DialOptions(opts...)
|
|
|
|
s.dialer.dialer = dialer.NewDialer(options...)
|
|
|
|
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddr(metadata.RemoteAddress()))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-04-24 10:30:12 +08:00
|
|
|
return NewConn(CN.NewRefConn(c, s), s.ProxyAdapter), err
|
2023-04-23 19:57:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
2023-04-23 20:55:42 +08:00
|
|
|
if s.onlyTcp {
|
|
|
|
return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
|
|
|
}
|
2023-04-23 19:57:54 +08:00
|
|
|
options := s.base.DialOptions(opts...)
|
|
|
|
s.dialer.dialer = dialer.NewDialer(options...)
|
2023-04-26 15:57:25 +08:00
|
|
|
|
|
|
|
// sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr
|
|
|
|
if !metadata.Resolved() {
|
|
|
|
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New("can't resolve ip")
|
|
|
|
}
|
|
|
|
metadata.DstIP = ip
|
|
|
|
}
|
|
|
|
|
|
|
|
pc, err := s.client.ListenPacket(ctx, M.SocksaddrFromNet(metadata.UDPAddr()))
|
2023-04-23 19:57:54 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if pc == nil {
|
|
|
|
return nil, E.New("packetConn is nil")
|
|
|
|
}
|
2023-05-11 15:34:28 +08:00
|
|
|
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(pc), s), s.ProxyAdapter), nil
|
2023-04-23 19:57:54 +08:00
|
|
|
}
|
|
|
|
|
2023-04-23 21:50:42 +08:00
|
|
|
func (s *SingMux) SupportUDP() bool {
|
|
|
|
if s.onlyTcp {
|
|
|
|
return s.ProxyAdapter.SupportUOT()
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SingMux) SupportUOT() bool {
|
|
|
|
if s.onlyTcp {
|
|
|
|
return s.ProxyAdapter.SupportUOT()
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2023-04-24 10:30:12 +08:00
|
|
|
func closeSingMux(s *SingMux) {
|
|
|
|
_ = s.client.Close()
|
|
|
|
}
|
|
|
|
|
2023-04-23 19:57:54 +08:00
|
|
|
func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.ProxyAdapter, error) {
|
2023-04-23 20:55:42 +08:00
|
|
|
singDialer := &muxSingDialer{dialer: dialer.NewDialer(), proxy: proxy, statistic: option.Statistic}
|
2023-04-23 19:57:54 +08:00
|
|
|
client, err := mux.NewClient(mux.Options{
|
|
|
|
Dialer: singDialer,
|
|
|
|
Protocol: option.Protocol,
|
|
|
|
MaxConnections: option.MaxConnections,
|
|
|
|
MinStreams: option.MinStreams,
|
|
|
|
MaxStreams: option.MaxStreams,
|
|
|
|
Padding: option.Padding,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-04-24 10:30:12 +08:00
|
|
|
outbound := &SingMux{
|
2023-04-23 19:57:54 +08:00
|
|
|
ProxyAdapter: proxy,
|
|
|
|
base: base,
|
|
|
|
client: client,
|
|
|
|
dialer: singDialer,
|
2023-04-23 20:55:42 +08:00
|
|
|
onlyTcp: option.OnlyTcp,
|
2023-04-24 10:30:12 +08:00
|
|
|
}
|
|
|
|
runtime.SetFinalizer(outbound, closeSingMux)
|
|
|
|
return outbound, nil
|
2023-04-23 19:57:54 +08:00
|
|
|
}
|