2023-04-23 19:57:54 +08:00
|
|
|
package outbound
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-04-26 15:57:25 +08:00
|
|
|
"errors"
|
2023-04-24 10:30:12 +08:00
|
|
|
"runtime"
|
2023-04-23 19:57:54 +08:00
|
|
|
|
2023-11-03 21:01:45 +08:00
|
|
|
CN "github.com/metacubex/mihomo/common/net"
|
|
|
|
"github.com/metacubex/mihomo/component/dialer"
|
|
|
|
"github.com/metacubex/mihomo/component/proxydialer"
|
|
|
|
"github.com/metacubex/mihomo/component/resolver"
|
|
|
|
C "github.com/metacubex/mihomo/constant"
|
2023-11-18 15:30:35 +08:00
|
|
|
"github.com/metacubex/mihomo/log"
|
2023-04-23 19:57:54 +08:00
|
|
|
|
|
|
|
mux "github.com/sagernet/sing-mux"
|
|
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
|
|
M "github.com/sagernet/sing/common/metadata"
|
|
|
|
)
|
|
|
|
|
|
|
|
type SingMux struct {
|
|
|
|
C.ProxyAdapter
|
2023-04-23 20:55:42 +08:00
|
|
|
base ProxyBase
|
|
|
|
client *mux.Client
|
2023-09-25 09:10:43 +08:00
|
|
|
dialer proxydialer.SingDialer
|
2023-04-23 20:55:42 +08:00
|
|
|
onlyTcp bool
|
2023-04-23 19:57:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
type SingMuxOption struct {
|
2023-11-18 00:07:07 +08:00
|
|
|
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"`
|
|
|
|
Statistic bool `proxy:"statistic,omitempty"`
|
|
|
|
OnlyTcp bool `proxy:"only-tcp,omitempty"`
|
|
|
|
BrutalOpts BrutalOption `proxy:"brutal-opts,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type BrutalOption struct {
|
|
|
|
Enabled bool `proxy:"enabled,omitempty"`
|
|
|
|
Up string `proxy:"up,omitempty"`
|
|
|
|
Down string `proxy:"down,omitempty"`
|
2023-04-23 19:57:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
type ProxyBase interface {
|
|
|
|
DialOptions(opts ...dialer.Option) []dialer.Option
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
|
|
|
options := s.base.DialOptions(opts...)
|
2023-09-25 09:10:43 +08:00
|
|
|
s.dialer.SetDialer(dialer.NewDialer(options...))
|
2023-09-29 08:51:13 +08:00
|
|
|
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
2023-04-23 19:57:54 +08:00
|
|
|
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...)
|
2023-09-25 09:10:43 +08:00
|
|
|
s.dialer.SetDialer(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 {
|
2023-06-14 15:51:13 +08:00
|
|
|
return s.ProxyAdapter.SupportUDP()
|
2023-04-23 21:50:42 +08:00
|
|
|
}
|
|
|
|
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-11-18 00:07:07 +08:00
|
|
|
if !mux.BrutalAvailable && option.BrutalOpts.Enabled {
|
|
|
|
return nil, errors.New("TCP Brutal is only supported on Linux-based systems")
|
|
|
|
}
|
2023-09-25 09:10:43 +08:00
|
|
|
singDialer := proxydialer.NewSingDialer(proxy, dialer.NewDialer(), option.Statistic)
|
2023-04-23 19:57:54 +08:00
|
|
|
client, err := mux.NewClient(mux.Options{
|
|
|
|
Dialer: singDialer,
|
2023-11-18 15:30:35 +08:00
|
|
|
Logger: log.SingLogger,
|
2023-04-23 19:57:54 +08:00
|
|
|
Protocol: option.Protocol,
|
|
|
|
MaxConnections: option.MaxConnections,
|
|
|
|
MinStreams: option.MinStreams,
|
|
|
|
MaxStreams: option.MaxStreams,
|
|
|
|
Padding: option.Padding,
|
2023-11-18 00:07:07 +08:00
|
|
|
Brutal: mux.BrutalOptions{
|
|
|
|
Enabled: option.BrutalOpts.Enabled,
|
|
|
|
SendBPS: StringToBps(option.BrutalOpts.Up),
|
|
|
|
ReceiveBPS: StringToBps(option.BrutalOpts.Down),
|
|
|
|
},
|
2023-04-23 19:57:54 +08:00
|
|
|
})
|
|
|
|
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
|
|
|
}
|