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 }