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 }