123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- package outbound
- import (
- "context"
- "crypto/tls"
- "errors"
- "fmt"
- "math"
- "net"
- "strconv"
- "time"
- "github.com/metacubex/mihomo/component/ca"
- "github.com/metacubex/mihomo/component/dialer"
- "github.com/metacubex/mihomo/component/proxydialer"
- "github.com/metacubex/mihomo/component/resolver"
- C "github.com/metacubex/mihomo/constant"
- "github.com/metacubex/mihomo/transport/tuic"
- "github.com/gofrs/uuid/v5"
- "github.com/metacubex/quic-go"
- M "github.com/sagernet/sing/common/metadata"
- "github.com/sagernet/sing/common/uot"
- )
- type Tuic struct {
- *Base
- option *TuicOption
- client *tuic.PoolClient
- }
- type TuicOption struct {
- BasicOption
- Name string `proxy:"name"`
- Server string `proxy:"server"`
- Port int `proxy:"port"`
- Token string `proxy:"token,omitempty"`
- UUID string `proxy:"uuid,omitempty"`
- Password string `proxy:"password,omitempty"`
- Ip string `proxy:"ip,omitempty"`
- HeartbeatInterval int `proxy:"heartbeat-interval,omitempty"`
- ALPN []string `proxy:"alpn,omitempty"`
- ReduceRtt bool `proxy:"reduce-rtt,omitempty"`
- RequestTimeout int `proxy:"request-timeout,omitempty"`
- UdpRelayMode string `proxy:"udp-relay-mode,omitempty"`
- CongestionController string `proxy:"congestion-controller,omitempty"`
- DisableSni bool `proxy:"disable-sni,omitempty"`
- MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"`
- FastOpen bool `proxy:"fast-open,omitempty"`
- MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
- CWND int `proxy:"cwnd,omitempty"`
- SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
- Fingerprint string `proxy:"fingerprint,omitempty"`
- CustomCA string `proxy:"ca,omitempty"`
- CustomCAString string `proxy:"ca-str,omitempty"`
- ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
- ReceiveWindow int `proxy:"recv-window,omitempty"`
- DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
- MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
- SNI string `proxy:"sni,omitempty"`
- UDPOverStream bool `proxy:"udp-over-stream,omitempty"`
- UDPOverStreamVersion int `proxy:"udp-over-stream-version,omitempty"`
- }
- // DialContext implements C.ProxyAdapter
- func (t *Tuic) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
- return t.DialContextWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
- }
- // DialContextWithDialer implements C.ProxyAdapter
- func (t *Tuic) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.Conn, error) {
- conn, err := t.client.DialContextWithDialer(ctx, metadata, dialer, t.dialWithDialer)
- if err != nil {
- return nil, err
- }
- return NewConn(conn, t), err
- }
- // ListenPacketContext implements C.ProxyAdapter
- func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
- return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
- }
- // ListenPacketWithDialer implements C.ProxyAdapter
- func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
- if t.option.UDPOverStream {
- uotDestination := uot.RequestDestination(uint8(t.option.UDPOverStreamVersion))
- uotMetadata := *metadata
- uotMetadata.Host = uotDestination.Fqdn
- uotMetadata.DstPort = uotDestination.Port
- c, err := t.DialContextWithDialer(ctx, dialer, &uotMetadata)
- if err != nil {
- return nil, err
- }
- // tuic uos 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
- }
- destination := M.SocksaddrFromNet(metadata.UDPAddr())
- if t.option.UDPOverStreamVersion == uot.LegacyVersion {
- return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), t), nil
- } else {
- return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), t), nil
- }
- }
- pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer)
- if err != nil {
- return nil, err
- }
- return newPacketConn(pc, t), nil
- }
- // SupportWithDialer implements C.ProxyAdapter
- func (t *Tuic) SupportWithDialer() C.NetWork {
- return C.ALLNet
- }
- func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error) {
- if len(t.option.DialerProxy) > 0 {
- dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
- if err != nil {
- return nil, nil, err
- }
- }
- udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", t.addr, t.prefer)
- if err != nil {
- return nil, nil, err
- }
- addr = udpAddr
- var pc net.PacketConn
- pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
- if err != nil {
- return nil, nil, err
- }
- transport = &quic.Transport{Conn: pc}
- transport.SetCreatedConn(true) // auto close conn
- transport.SetSingleUse(true) // auto close transport
- return
- }
- func NewTuic(option TuicOption) (*Tuic, error) {
- addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
- serverName := option.Server
- tlsConfig := &tls.Config{
- ServerName: serverName,
- InsecureSkipVerify: option.SkipCertVerify,
- MinVersion: tls.VersionTLS13,
- }
- if option.SNI != "" {
- tlsConfig.ServerName = option.SNI
- }
- var err error
- tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
- if err != nil {
- return nil, err
- }
- if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
- tlsConfig.NextProtos = option.ALPN
- } else {
- tlsConfig.NextProtos = []string{"h3"}
- }
- if option.RequestTimeout == 0 {
- option.RequestTimeout = 8000
- }
- if option.HeartbeatInterval <= 0 {
- option.HeartbeatInterval = 10000
- }
- udpRelayMode := tuic.QUIC
- if option.UdpRelayMode != "quic" {
- udpRelayMode = tuic.NATIVE
- }
- if option.MaxUdpRelayPacketSize == 0 {
- option.MaxUdpRelayPacketSize = 1252
- }
- if option.MaxOpenStreams == 0 {
- option.MaxOpenStreams = 100
- }
- if option.CWND == 0 {
- option.CWND = 32
- }
- packetOverHead := tuic.PacketOverHeadV4
- if len(option.Token) == 0 {
- packetOverHead = tuic.PacketOverHeadV5
- }
- if option.MaxDatagramFrameSize == 0 {
- option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + packetOverHead
- }
- if option.MaxDatagramFrameSize > 1400 {
- option.MaxDatagramFrameSize = 1400
- }
- option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - packetOverHead
- // ensure server's incoming stream can handle correctly, increase to 1.1x
- quicMaxOpenStreams := int64(option.MaxOpenStreams)
- quicMaxOpenStreams = quicMaxOpenStreams + int64(math.Ceil(float64(quicMaxOpenStreams)/10.0))
- quicConfig := &quic.Config{
- InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
- MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
- InitialConnectionReceiveWindow: uint64(option.ReceiveWindow),
- MaxConnectionReceiveWindow: uint64(option.ReceiveWindow),
- MaxIncomingStreams: quicMaxOpenStreams,
- MaxIncomingUniStreams: quicMaxOpenStreams,
- KeepAlivePeriod: time.Duration(option.HeartbeatInterval) * time.Millisecond,
- DisablePathMTUDiscovery: option.DisableMTUDiscovery,
- MaxDatagramFrameSize: int64(option.MaxDatagramFrameSize),
- EnableDatagrams: true,
- }
- if option.ReceiveWindowConn == 0 {
- quicConfig.InitialStreamReceiveWindow = tuic.DefaultStreamReceiveWindow / 10
- quicConfig.MaxStreamReceiveWindow = tuic.DefaultStreamReceiveWindow
- }
- if option.ReceiveWindow == 0 {
- quicConfig.InitialConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow / 10
- quicConfig.MaxConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow
- }
- if len(option.Ip) > 0 {
- addr = net.JoinHostPort(option.Ip, strconv.Itoa(option.Port))
- }
- if option.DisableSni {
- tlsConfig.ServerName = ""
- tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
- }
- switch option.UDPOverStreamVersion {
- case uot.Version, uot.LegacyVersion:
- case 0:
- option.UDPOverStreamVersion = uot.LegacyVersion
- default:
- return nil, fmt.Errorf("tuic %s unknown udp over stream protocol version: %d", addr, option.UDPOverStreamVersion)
- }
- t := &Tuic{
- Base: &Base{
- name: option.Name,
- addr: addr,
- tp: C.Tuic,
- udp: true,
- tfo: option.FastOpen,
- iface: option.Interface,
- rmark: option.RoutingMark,
- prefer: C.NewDNSPrefer(option.IPVersion),
- },
- option: &option,
- }
- clientMaxOpenStreams := int64(option.MaxOpenStreams)
- // to avoid tuic's "too many open streams", decrease to 0.9x
- if clientMaxOpenStreams == 100 {
- clientMaxOpenStreams = clientMaxOpenStreams - int64(math.Ceil(float64(clientMaxOpenStreams)/10.0))
- }
- if clientMaxOpenStreams < 1 {
- clientMaxOpenStreams = 1
- }
- if len(option.Token) > 0 {
- tkn := tuic.GenTKN(option.Token)
- clientOption := &tuic.ClientOptionV4{
- TlsConfig: tlsConfig,
- QuicConfig: quicConfig,
- Token: tkn,
- UdpRelayMode: udpRelayMode,
- CongestionController: option.CongestionController,
- ReduceRtt: option.ReduceRtt,
- RequestTimeout: time.Duration(option.RequestTimeout) * time.Millisecond,
- MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
- FastOpen: option.FastOpen,
- MaxOpenStreams: clientMaxOpenStreams,
- CWND: option.CWND,
- }
- t.client = tuic.NewPoolClientV4(clientOption)
- } else {
- maxUdpRelayPacketSize := option.MaxUdpRelayPacketSize
- if maxUdpRelayPacketSize > tuic.MaxFragSizeV5 {
- maxUdpRelayPacketSize = tuic.MaxFragSizeV5
- }
- clientOption := &tuic.ClientOptionV5{
- TlsConfig: tlsConfig,
- QuicConfig: quicConfig,
- Uuid: uuid.FromStringOrNil(option.UUID),
- Password: option.Password,
- UdpRelayMode: udpRelayMode,
- CongestionController: option.CongestionController,
- ReduceRtt: option.ReduceRtt,
- MaxUdpRelayPacketSize: maxUdpRelayPacketSize,
- MaxOpenStreams: clientMaxOpenStreams,
- CWND: option.CWND,
- }
- t.client = tuic.NewPoolClientV5(clientOption)
- }
- return t, nil
- }
|