mihomo/adapter/outbound/tuic.go

263 lines
8.2 KiB
Go
Raw Normal View History

2022-11-25 08:08:14 +08:00
package outbound
import (
"context"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"encoding/pem"
"fmt"
"math"
2022-11-25 08:08:14 +08:00
"net"
"os"
"strconv"
"time"
"github.com/metacubex/quic-go"
2022-11-25 08:08:14 +08:00
"github.com/Dreamacro/clash/component/dialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/tuic"
)
type Tuic struct {
*Base
2022-11-26 23:53:59 +08:00
client *tuic.PoolClient
2022-11-25 08:08:14 +08:00
}
type TuicOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Token string `proxy:"token"`
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"`
2022-11-25 08:08:14 +08:00
FastOpen bool `proxy:"fast-open,omitempty"`
MaxOpenStreams int `proxy:"max-open-streams,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"`
2022-11-25 08:08:14 +08:00
}
// DialContext implements C.ProxyAdapter
func (t *Tuic) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
2022-12-22 09:53:11 +08:00
return t.DialContextWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
2022-11-25 08:08:14 +08:00
}
2022-12-20 00:11:02 +08:00
// DialContextWithDialer implements C.ProxyAdapter
func (t *Tuic) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.Conn, error) {
2022-12-22 09:53:11 +08:00
conn, err := t.client.DialContextWithDialer(ctx, metadata, dialer, t.dialWithDialer)
2022-12-20 00:11:02 +08:00
if err != nil {
return nil, err
}
return NewConn(conn, t), err
}
2022-11-25 08:08:14 +08:00
// ListenPacketContext implements C.ProxyAdapter
func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
2022-12-22 09:53:11 +08:00
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
2022-11-25 08:08:14 +08:00
}
2022-12-20 00:11:02 +08:00
// ListenPacketWithDialer implements C.ProxyAdapter
func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
2022-12-22 09:53:11 +08:00
pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer)
2022-12-20 00:11:02 +08:00
if err != nil {
return nil, err
}
return newPacketConn(pc, t), nil
}
// SupportWithDialer implements C.ProxyAdapter
func (t *Tuic) SupportWithDialer() bool {
return true
}
2022-11-26 23:53:59 +08:00
func (t *Tuic) dial(ctx context.Context, opts ...dialer.Option) (pc net.PacketConn, addr net.Addr, err error) {
2022-12-20 00:11:02 +08:00
return t.dialWithDialer(ctx, dialer.NewDialer(opts...))
}
func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (pc net.PacketConn, addr net.Addr, err error) {
2022-12-11 09:25:46 +08:00
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", t.addr, t.prefer)
2022-11-26 23:53:59 +08:00
if err != nil {
return nil, nil, err
}
2022-12-11 09:25:46 +08:00
addr = udpAddr
2022-12-20 00:11:02 +08:00
pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
2022-11-26 23:53:59 +08:00
if err != nil {
return nil, nil, err
}
return
}
2022-11-25 08:08:14 +08:00
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,
}
2023-02-19 16:20:30 +08:00
if option.SNI != "" {
tlsConfig.ServerName = option.SNI
}
2022-11-25 08:08:14 +08:00
var bs []byte
var err error
if len(option.CustomCA) > 0 {
bs, err = os.ReadFile(option.CustomCA)
if err != nil {
2022-11-25 10:45:06 +08:00
return nil, fmt.Errorf("tuic %s load ca error: %w", addr, err)
2022-11-25 08:08:14 +08:00
}
} else if option.CustomCAString != "" {
bs = []byte(option.CustomCAString)
}
if len(bs) > 0 {
block, _ := pem.Decode(bs)
if block == nil {
return nil, fmt.Errorf("CA cert is not PEM")
}
fpBytes := sha256.Sum256(block.Bytes)
if len(option.Fingerprint) == 0 {
option.Fingerprint = hex.EncodeToString(fpBytes[:])
}
}
if len(option.Fingerprint) != 0 {
var err error
tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
if err != nil {
return nil, err
}
} else {
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
2022-11-25 08:08:14 +08:00
}
if len(option.ALPN) > 0 {
tlsConfig.NextProtos = option.ALPN
} else {
tlsConfig.NextProtos = []string{"h3"}
}
if option.RequestTimeout == 0 {
option.RequestTimeout = 8000
}
if option.HeartbeatInterval <= 0 {
option.HeartbeatInterval = 10000
}
if option.UdpRelayMode != "quic" {
option.UdpRelayMode = "native"
}
if option.MaxUdpRelayPacketSize == 0 {
option.MaxUdpRelayPacketSize = 1252
}
if option.MaxOpenStreams == 0 {
option.MaxOpenStreams = 100
}
if option.MaxDatagramFrameSize == 0 {
option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + tuic.PacketOverHead
}
if option.MaxDatagramFrameSize > 1400 {
option.MaxDatagramFrameSize = 1400
}
option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - tuic.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))
2022-11-25 08:08:14 +08:00
quicConfig := &quic.Config{
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
InitialConnectionReceiveWindow: uint64(option.ReceiveWindow),
MaxConnectionReceiveWindow: uint64(option.ReceiveWindow),
MaxIncomingStreams: quicMaxOpenStreams,
MaxIncomingUniStreams: quicMaxOpenStreams,
2022-11-25 08:08:14 +08:00
KeepAlivePeriod: time.Duration(option.HeartbeatInterval) * time.Millisecond,
DisablePathMTUDiscovery: option.DisableMTUDiscovery,
MaxDatagramFrameSize: int64(option.MaxDatagramFrameSize),
2022-11-25 08:08:14 +08:00
EnableDatagrams: true,
}
if option.ReceiveWindowConn == 0 {
quicConfig.InitialStreamReceiveWindow = tuic.DefaultStreamReceiveWindow / 10
quicConfig.MaxStreamReceiveWindow = tuic.DefaultStreamReceiveWindow
2022-11-25 08:08:14 +08:00
}
if option.ReceiveWindow == 0 {
quicConfig.InitialConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow / 10
quicConfig.MaxConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow
2022-11-25 08:08:14 +08:00
}
if len(option.Ip) > 0 {
addr = net.JoinHostPort(option.Ip, strconv.Itoa(option.Port))
}
host := option.Server
if option.DisableSni {
host = ""
tlsConfig.ServerName = ""
}
tkn := tuic.GenTKN(option.Token)
t := &Tuic{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Tuic,
udp: true,
tfo: option.FastOpen,
iface: option.Interface,
2023-02-24 14:02:20 +08:00
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
}
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
}
2022-11-26 23:53:59 +08:00
clientOption := &tuic.ClientOption{
TlsConfig: tlsConfig,
QuicConfig: quicConfig,
Host: host,
Token: tkn,
UdpRelayMode: option.UdpRelayMode,
CongestionController: option.CongestionController,
ReduceRtt: option.ReduceRtt,
2022-11-28 17:09:25 +08:00
RequestTimeout: time.Duration(option.RequestTimeout) * time.Millisecond,
2022-11-26 23:53:59 +08:00
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
FastOpen: option.FastOpen,
MaxOpenStreams: clientMaxOpenStreams,
}
t.client = tuic.NewPoolClient(clientOption)
return t, nil
2022-11-25 08:08:14 +08:00
}