github.com/metacubex/mihomo@v1.18.5/adapter/outbound/singmux.go (about)

     1  package outbound
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"runtime"
     7  
     8  	CN "github.com/metacubex/mihomo/common/net"
     9  	"github.com/metacubex/mihomo/component/dialer"
    10  	"github.com/metacubex/mihomo/component/proxydialer"
    11  	"github.com/metacubex/mihomo/component/resolver"
    12  	C "github.com/metacubex/mihomo/constant"
    13  	"github.com/metacubex/mihomo/log"
    14  
    15  	mux "github.com/sagernet/sing-mux"
    16  	E "github.com/sagernet/sing/common/exceptions"
    17  	M "github.com/sagernet/sing/common/metadata"
    18  )
    19  
    20  type SingMux struct {
    21  	C.ProxyAdapter
    22  	base    ProxyBase
    23  	client  *mux.Client
    24  	dialer  proxydialer.SingDialer
    25  	onlyTcp bool
    26  }
    27  
    28  type SingMuxOption struct {
    29  	Enabled        bool         `proxy:"enabled,omitempty"`
    30  	Protocol       string       `proxy:"protocol,omitempty"`
    31  	MaxConnections int          `proxy:"max-connections,omitempty"`
    32  	MinStreams     int          `proxy:"min-streams,omitempty"`
    33  	MaxStreams     int          `proxy:"max-streams,omitempty"`
    34  	Padding        bool         `proxy:"padding,omitempty"`
    35  	Statistic      bool         `proxy:"statistic,omitempty"`
    36  	OnlyTcp        bool         `proxy:"only-tcp,omitempty"`
    37  	BrutalOpts     BrutalOption `proxy:"brutal-opts,omitempty"`
    38  }
    39  
    40  type BrutalOption struct {
    41  	Enabled bool   `proxy:"enabled,omitempty"`
    42  	Up      string `proxy:"up,omitempty"`
    43  	Down    string `proxy:"down,omitempty"`
    44  }
    45  
    46  type ProxyBase interface {
    47  	DialOptions(opts ...dialer.Option) []dialer.Option
    48  }
    49  
    50  func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
    51  	options := s.base.DialOptions(opts...)
    52  	s.dialer.SetDialer(dialer.NewDialer(options...))
    53  	c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	return NewConn(CN.NewRefConn(c, s), s.ProxyAdapter), err
    58  }
    59  
    60  func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
    61  	if s.onlyTcp {
    62  		return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
    63  	}
    64  	options := s.base.DialOptions(opts...)
    65  	s.dialer.SetDialer(dialer.NewDialer(options...))
    66  
    67  	// sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr
    68  	if !metadata.Resolved() {
    69  		ip, err := resolver.ResolveIP(ctx, metadata.Host)
    70  		if err != nil {
    71  			return nil, errors.New("can't resolve ip")
    72  		}
    73  		metadata.DstIP = ip
    74  	}
    75  
    76  	pc, err := s.client.ListenPacket(ctx, M.SocksaddrFromNet(metadata.UDPAddr()))
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	if pc == nil {
    81  		return nil, E.New("packetConn is nil")
    82  	}
    83  	return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(pc), s), s.ProxyAdapter), nil
    84  }
    85  
    86  func (s *SingMux) SupportUDP() bool {
    87  	if s.onlyTcp {
    88  		return s.ProxyAdapter.SupportUDP()
    89  	}
    90  	return true
    91  }
    92  
    93  func (s *SingMux) SupportUOT() bool {
    94  	if s.onlyTcp {
    95  		return s.ProxyAdapter.SupportUOT()
    96  	}
    97  	return true
    98  }
    99  
   100  func closeSingMux(s *SingMux) {
   101  	_ = s.client.Close()
   102  }
   103  
   104  func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.ProxyAdapter, error) {
   105  	// TODO
   106  	// "TCP Brutal is only supported on Linux-based systems"
   107  
   108  	singDialer := proxydialer.NewSingDialer(proxy, dialer.NewDialer(), option.Statistic)
   109  	client, err := mux.NewClient(mux.Options{
   110  		Dialer:         singDialer,
   111  		Logger:         log.SingLogger,
   112  		Protocol:       option.Protocol,
   113  		MaxConnections: option.MaxConnections,
   114  		MinStreams:     option.MinStreams,
   115  		MaxStreams:     option.MaxStreams,
   116  		Padding:        option.Padding,
   117  		Brutal: mux.BrutalOptions{
   118  			Enabled:    option.BrutalOpts.Enabled,
   119  			SendBPS:    StringToBps(option.BrutalOpts.Up),
   120  			ReceiveBPS: StringToBps(option.BrutalOpts.Down),
   121  		},
   122  	})
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	outbound := &SingMux{
   127  		ProxyAdapter: proxy,
   128  		base:         base,
   129  		client:       client,
   130  		dialer:       singDialer,
   131  		onlyTcp:      option.OnlyTcp,
   132  	}
   133  	runtime.SetFinalizer(outbound, closeSingMux)
   134  	return outbound, nil
   135  }