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

     1  package outbound
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"errors"
     7  	"fmt"
     8  	"math"
     9  	"net"
    10  	"strconv"
    11  	"time"
    12  
    13  	"github.com/metacubex/mihomo/component/ca"
    14  	"github.com/metacubex/mihomo/component/dialer"
    15  	"github.com/metacubex/mihomo/component/proxydialer"
    16  	"github.com/metacubex/mihomo/component/resolver"
    17  	C "github.com/metacubex/mihomo/constant"
    18  	"github.com/metacubex/mihomo/transport/tuic"
    19  
    20  	"github.com/gofrs/uuid/v5"
    21  	"github.com/metacubex/quic-go"
    22  	M "github.com/sagernet/sing/common/metadata"
    23  	"github.com/sagernet/sing/common/uot"
    24  )
    25  
    26  type Tuic struct {
    27  	*Base
    28  	option *TuicOption
    29  	client *tuic.PoolClient
    30  }
    31  
    32  type TuicOption struct {
    33  	BasicOption
    34  	Name                  string   `proxy:"name"`
    35  	Server                string   `proxy:"server"`
    36  	Port                  int      `proxy:"port"`
    37  	Token                 string   `proxy:"token,omitempty"`
    38  	UUID                  string   `proxy:"uuid,omitempty"`
    39  	Password              string   `proxy:"password,omitempty"`
    40  	Ip                    string   `proxy:"ip,omitempty"`
    41  	HeartbeatInterval     int      `proxy:"heartbeat-interval,omitempty"`
    42  	ALPN                  []string `proxy:"alpn,omitempty"`
    43  	ReduceRtt             bool     `proxy:"reduce-rtt,omitempty"`
    44  	RequestTimeout        int      `proxy:"request-timeout,omitempty"`
    45  	UdpRelayMode          string   `proxy:"udp-relay-mode,omitempty"`
    46  	CongestionController  string   `proxy:"congestion-controller,omitempty"`
    47  	DisableSni            bool     `proxy:"disable-sni,omitempty"`
    48  	MaxUdpRelayPacketSize int      `proxy:"max-udp-relay-packet-size,omitempty"`
    49  
    50  	FastOpen             bool   `proxy:"fast-open,omitempty"`
    51  	MaxOpenStreams       int    `proxy:"max-open-streams,omitempty"`
    52  	CWND                 int    `proxy:"cwnd,omitempty"`
    53  	SkipCertVerify       bool   `proxy:"skip-cert-verify,omitempty"`
    54  	Fingerprint          string `proxy:"fingerprint,omitempty"`
    55  	CustomCA             string `proxy:"ca,omitempty"`
    56  	CustomCAString       string `proxy:"ca-str,omitempty"`
    57  	ReceiveWindowConn    int    `proxy:"recv-window-conn,omitempty"`
    58  	ReceiveWindow        int    `proxy:"recv-window,omitempty"`
    59  	DisableMTUDiscovery  bool   `proxy:"disable-mtu-discovery,omitempty"`
    60  	MaxDatagramFrameSize int    `proxy:"max-datagram-frame-size,omitempty"`
    61  	SNI                  string `proxy:"sni,omitempty"`
    62  
    63  	UDPOverStream        bool `proxy:"udp-over-stream,omitempty"`
    64  	UDPOverStreamVersion int  `proxy:"udp-over-stream-version,omitempty"`
    65  }
    66  
    67  // DialContext implements C.ProxyAdapter
    68  func (t *Tuic) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
    69  	return t.DialContextWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
    70  }
    71  
    72  // DialContextWithDialer implements C.ProxyAdapter
    73  func (t *Tuic) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.Conn, error) {
    74  	conn, err := t.client.DialContextWithDialer(ctx, metadata, dialer, t.dialWithDialer)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	return NewConn(conn, t), err
    79  }
    80  
    81  // ListenPacketContext implements C.ProxyAdapter
    82  func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
    83  	return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
    84  }
    85  
    86  // ListenPacketWithDialer implements C.ProxyAdapter
    87  func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
    88  	if t.option.UDPOverStream {
    89  		uotDestination := uot.RequestDestination(uint8(t.option.UDPOverStreamVersion))
    90  		uotMetadata := *metadata
    91  		uotMetadata.Host = uotDestination.Fqdn
    92  		uotMetadata.DstPort = uotDestination.Port
    93  		c, err := t.DialContextWithDialer(ctx, dialer, &uotMetadata)
    94  		if err != nil {
    95  			return nil, err
    96  		}
    97  
    98  		// tuic uos use stream-oriented udp with a special address, so we need a net.UDPAddr
    99  		if !metadata.Resolved() {
   100  			ip, err := resolver.ResolveIP(ctx, metadata.Host)
   101  			if err != nil {
   102  				return nil, errors.New("can't resolve ip")
   103  			}
   104  			metadata.DstIP = ip
   105  		}
   106  
   107  		destination := M.SocksaddrFromNet(metadata.UDPAddr())
   108  		if t.option.UDPOverStreamVersion == uot.LegacyVersion {
   109  			return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), t), nil
   110  		} else {
   111  			return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), t), nil
   112  		}
   113  	}
   114  	pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	return newPacketConn(pc, t), nil
   119  }
   120  
   121  // SupportWithDialer implements C.ProxyAdapter
   122  func (t *Tuic) SupportWithDialer() C.NetWork {
   123  	return C.ALLNet
   124  }
   125  
   126  func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error) {
   127  	if len(t.option.DialerProxy) > 0 {
   128  		dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
   129  		if err != nil {
   130  			return nil, nil, err
   131  		}
   132  	}
   133  	udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", t.addr, t.prefer)
   134  	if err != nil {
   135  		return nil, nil, err
   136  	}
   137  	addr = udpAddr
   138  	var pc net.PacketConn
   139  	pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
   140  	if err != nil {
   141  		return nil, nil, err
   142  	}
   143  	transport = &quic.Transport{Conn: pc}
   144  	transport.SetCreatedConn(true) // auto close conn
   145  	transport.SetSingleUse(true)   // auto close transport
   146  	return
   147  }
   148  
   149  func NewTuic(option TuicOption) (*Tuic, error) {
   150  	addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
   151  	serverName := option.Server
   152  	tlsConfig := &tls.Config{
   153  		ServerName:         serverName,
   154  		InsecureSkipVerify: option.SkipCertVerify,
   155  		MinVersion:         tls.VersionTLS13,
   156  	}
   157  	if option.SNI != "" {
   158  		tlsConfig.ServerName = option.SNI
   159  	}
   160  
   161  	var err error
   162  	tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
   168  		tlsConfig.NextProtos = option.ALPN
   169  	} else {
   170  		tlsConfig.NextProtos = []string{"h3"}
   171  	}
   172  
   173  	if option.RequestTimeout == 0 {
   174  		option.RequestTimeout = 8000
   175  	}
   176  
   177  	if option.HeartbeatInterval <= 0 {
   178  		option.HeartbeatInterval = 10000
   179  	}
   180  
   181  	udpRelayMode := tuic.QUIC
   182  	if option.UdpRelayMode != "quic" {
   183  		udpRelayMode = tuic.NATIVE
   184  	}
   185  
   186  	if option.MaxUdpRelayPacketSize == 0 {
   187  		option.MaxUdpRelayPacketSize = 1252
   188  	}
   189  
   190  	if option.MaxOpenStreams == 0 {
   191  		option.MaxOpenStreams = 100
   192  	}
   193  
   194  	if option.CWND == 0 {
   195  		option.CWND = 32
   196  	}
   197  
   198  	packetOverHead := tuic.PacketOverHeadV4
   199  	if len(option.Token) == 0 {
   200  		packetOverHead = tuic.PacketOverHeadV5
   201  	}
   202  
   203  	if option.MaxDatagramFrameSize == 0 {
   204  		option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + packetOverHead
   205  	}
   206  
   207  	if option.MaxDatagramFrameSize > 1400 {
   208  		option.MaxDatagramFrameSize = 1400
   209  	}
   210  	option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - packetOverHead
   211  
   212  	// ensure server's incoming stream can handle correctly, increase to 1.1x
   213  	quicMaxOpenStreams := int64(option.MaxOpenStreams)
   214  	quicMaxOpenStreams = quicMaxOpenStreams + int64(math.Ceil(float64(quicMaxOpenStreams)/10.0))
   215  	quicConfig := &quic.Config{
   216  		InitialStreamReceiveWindow:     uint64(option.ReceiveWindowConn),
   217  		MaxStreamReceiveWindow:         uint64(option.ReceiveWindowConn),
   218  		InitialConnectionReceiveWindow: uint64(option.ReceiveWindow),
   219  		MaxConnectionReceiveWindow:     uint64(option.ReceiveWindow),
   220  		MaxIncomingStreams:             quicMaxOpenStreams,
   221  		MaxIncomingUniStreams:          quicMaxOpenStreams,
   222  		KeepAlivePeriod:                time.Duration(option.HeartbeatInterval) * time.Millisecond,
   223  		DisablePathMTUDiscovery:        option.DisableMTUDiscovery,
   224  		MaxDatagramFrameSize:           int64(option.MaxDatagramFrameSize),
   225  		EnableDatagrams:                true,
   226  	}
   227  	if option.ReceiveWindowConn == 0 {
   228  		quicConfig.InitialStreamReceiveWindow = tuic.DefaultStreamReceiveWindow / 10
   229  		quicConfig.MaxStreamReceiveWindow = tuic.DefaultStreamReceiveWindow
   230  	}
   231  	if option.ReceiveWindow == 0 {
   232  		quicConfig.InitialConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow / 10
   233  		quicConfig.MaxConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow
   234  	}
   235  
   236  	if len(option.Ip) > 0 {
   237  		addr = net.JoinHostPort(option.Ip, strconv.Itoa(option.Port))
   238  	}
   239  	if option.DisableSni {
   240  		tlsConfig.ServerName = ""
   241  		tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
   242  	}
   243  
   244  	switch option.UDPOverStreamVersion {
   245  	case uot.Version, uot.LegacyVersion:
   246  	case 0:
   247  		option.UDPOverStreamVersion = uot.LegacyVersion
   248  	default:
   249  		return nil, fmt.Errorf("tuic %s unknown udp over stream protocol version: %d", addr, option.UDPOverStreamVersion)
   250  	}
   251  
   252  	t := &Tuic{
   253  		Base: &Base{
   254  			name:   option.Name,
   255  			addr:   addr,
   256  			tp:     C.Tuic,
   257  			udp:    true,
   258  			tfo:    option.FastOpen,
   259  			iface:  option.Interface,
   260  			rmark:  option.RoutingMark,
   261  			prefer: C.NewDNSPrefer(option.IPVersion),
   262  		},
   263  		option: &option,
   264  	}
   265  
   266  	clientMaxOpenStreams := int64(option.MaxOpenStreams)
   267  
   268  	// to avoid tuic's "too many open streams", decrease to 0.9x
   269  	if clientMaxOpenStreams == 100 {
   270  		clientMaxOpenStreams = clientMaxOpenStreams - int64(math.Ceil(float64(clientMaxOpenStreams)/10.0))
   271  	}
   272  
   273  	if clientMaxOpenStreams < 1 {
   274  		clientMaxOpenStreams = 1
   275  	}
   276  
   277  	if len(option.Token) > 0 {
   278  		tkn := tuic.GenTKN(option.Token)
   279  		clientOption := &tuic.ClientOptionV4{
   280  			TlsConfig:             tlsConfig,
   281  			QuicConfig:            quicConfig,
   282  			Token:                 tkn,
   283  			UdpRelayMode:          udpRelayMode,
   284  			CongestionController:  option.CongestionController,
   285  			ReduceRtt:             option.ReduceRtt,
   286  			RequestTimeout:        time.Duration(option.RequestTimeout) * time.Millisecond,
   287  			MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
   288  			FastOpen:              option.FastOpen,
   289  			MaxOpenStreams:        clientMaxOpenStreams,
   290  			CWND:                  option.CWND,
   291  		}
   292  
   293  		t.client = tuic.NewPoolClientV4(clientOption)
   294  	} else {
   295  		maxUdpRelayPacketSize := option.MaxUdpRelayPacketSize
   296  		if maxUdpRelayPacketSize > tuic.MaxFragSizeV5 {
   297  			maxUdpRelayPacketSize = tuic.MaxFragSizeV5
   298  		}
   299  		clientOption := &tuic.ClientOptionV5{
   300  			TlsConfig:             tlsConfig,
   301  			QuicConfig:            quicConfig,
   302  			Uuid:                  uuid.FromStringOrNil(option.UUID),
   303  			Password:              option.Password,
   304  			UdpRelayMode:          udpRelayMode,
   305  			CongestionController:  option.CongestionController,
   306  			ReduceRtt:             option.ReduceRtt,
   307  			MaxUdpRelayPacketSize: maxUdpRelayPacketSize,
   308  			MaxOpenStreams:        clientMaxOpenStreams,
   309  			CWND:                  option.CWND,
   310  		}
   311  
   312  		t.client = tuic.NewPoolClientV5(clientOption)
   313  	}
   314  
   315  	return t, nil
   316  }