diff --git a/adapter/outbound/tuic.go b/adapter/outbound/tuic.go index af0d3b30..86b34dc8 100644 --- a/adapter/outbound/tuic.go +++ b/adapter/outbound/tuic.go @@ -49,6 +49,7 @@ type TuicOption struct { 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"` @@ -188,6 +189,10 @@ func NewTuic(option TuicOption) (*Tuic, error) { option.MaxOpenStreams = 100 } + if option.CWND == 0 { + option.CWND = 32 + } + packetOverHead := tuic.PacketOverHeadV4 if len(option.Token) == 0 { packetOverHead = tuic.PacketOverHeadV5 @@ -272,6 +277,7 @@ func NewTuic(option TuicOption) (*Tuic, error) { MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize, FastOpen: option.FastOpen, MaxOpenStreams: clientMaxOpenStreams, + CWND: option.CWND, } t.client = tuic.NewPoolClientV4(clientOption) @@ -286,6 +292,7 @@ func NewTuic(option TuicOption) (*Tuic, error) { ReduceRtt: option.ReduceRtt, MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize, MaxOpenStreams: clientMaxOpenStreams, + CWND: option.CWND, } t.client = tuic.NewPoolClientV5(clientOption) diff --git a/docs/config.yaml b/docs/config.yaml index a8207917..1a86497d 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -64,7 +64,7 @@ hosts: profile: # 存储 select 选择记录 store-selected: false - + # 持久化 fake-ip store-fake-ip: true @@ -93,10 +93,10 @@ tun: #- 1000 # exclude-uid-range: # 排除路由的的用户范围 # - 1000-99999 - + # Android 用户和应用规则仅在 Android 下被支持 # 并且需要 auto-route - + # include-android-user: # 限制被路由的 Android 用户 # - 0 # - 10 @@ -126,10 +126,9 @@ sniffer: sniff: # TLS 默认如果不配置 ports 默认嗅探 443 TLS: # ports: [443, 8443] - + # 默认嗅探 80 HTTP: # 需要嗅探的端口 - ports: [80, 8080-8880] # 可覆盖 sniffer.override-destination override-destination: true @@ -144,7 +143,7 @@ sniffer: - tls - http # 强制对此域名进行嗅探 - + # 仅对白名单中的端口进行嗅探,默认为 443,80 # 已废弃,若 sniffer.sniff 配置则此项无效 port-whitelist: @@ -152,7 +151,6 @@ sniffer: - "443" # - 8000-9999 - tunnels: # one line config - tcp/udp,127.0.0.1:6553,114.114.114.114:53,proxy - tcp,127.0.0.1:6666,rds.mysql.com:3306,vpn @@ -162,7 +160,6 @@ tunnels: # one line config target: target.com proxy: proxy - # DNS配置 dns: enable: false # 关闭将使用系统 DNS @@ -177,18 +174,18 @@ dns: - 8.8.8.8 - tls://1.12.12.12:853 - tls://223.5.5.5:853 - - system # append DNS server from system configuration. If not found, it would print an error log and skip. + - system # append DNS server from system configuration. If not found, it would print an error log and skip. enhanced-mode: fake-ip # or redir-host - + fake-ip-range: 198.18.0.1/16 # fake-ip 池设置 - + # use-hosts: true # 查询 hosts - + # 配置不使用fake-ip的域名 # fake-ip-filter: # - '*.lan' # - localhost.ptlogin2.qq.com - + # DNS主要域名配置 # 支持 UDP,TCP,DoT,DoH,DoQ # 这部分为主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS @@ -202,20 +199,20 @@ dns: - dhcp://en0 # dns from dhcp - quic://dns.adguard.com:784 # DNS over QUIC # - '8.8.8.8#en0' # 兼容指定DNS出口网卡 - + # 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN,非必要配置 # 当不是 CN,则使用 fallback 中的 DNS 查询结果 # 确保配置 fallback 时能够正常查询 # fallback: # - tcp://1.1.1.1 # - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询,ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡 - + # 专用于节点域名解析的 DNS 服务器,非必要配置项 # 配置服务器若查询失败将使用 nameserver,非并发查询 # proxy-server-nameserver: # - https://dns.google/dns-query # - tls://one.one.one.one - + # 配置 fallback 使用条件 # fallback-filter: # geoip: true # 配置是否使用 geoip @@ -230,7 +227,7 @@ dns: # - '+.google.com' # - '+.facebook.com' # - '+.youtube.com' - + # 配置查询域名使用的 DNS 服务器 nameserver-policy: # 'www.baidu.com': '114.114.114.114' @@ -241,7 +238,7 @@ dns: "geosite:category-ads-all": rcode://success "www.baidu.com,+.google.cn": [223.5.5.5, https://dns.alidns.com/dns-query] ## global,dns 为 rule-providers 中的名为 global 和 dns 规则订阅, - ## 且 behavior 必须为 domain/classical,当为 classical 时仅会生效域名类规则 + ## 且 behavior 必须为 domain/classical,当为 classical 时仅会生效域名类规则 # "rule-set:global,dns": 8.8.8.8 proxies: # socks5 @@ -256,7 +253,7 @@ proxies: # socks5 # skip-cert-verify: true # udp: true # ip-version: ipv6 - + # http - name: "http" type: http @@ -269,7 +266,7 @@ proxies: # socks5 # sni: custom.com # fingerprint: xxxx # 同 experimental.fingerprints 使用 sha256 指纹,配置协议独立的指纹,将忽略 experimental.fingerprints # ip-version: dual - + # Snell # Beware that there's currently no UDP support yet - name: "snell" @@ -281,7 +278,7 @@ proxies: # socks5 # obfs-opts: # mode: http # or tls # host: bing.com - + # Shadowsocks # cipher支持: # aes-128-gcm aes-192-gcm aes-256-gcm @@ -313,7 +310,7 @@ proxies: # socks5 # padding: false # Enable padding. Requires sing-box server version 1.3-beta9 or later. # statistic: false # 控制是否将底层连接显示在面板中,方便打断底层连接 # only-tcp: false # 如果设置为true, smux的设置将不会对udp生效,udp连接会直接走底层协议 - + - name: "ss2" type: ss server: server @@ -324,7 +321,7 @@ proxies: # socks5 plugin-opts: mode: tls # or http # host: bing.com - + - name: "ss3" type: ss server: server @@ -344,7 +341,7 @@ proxies: # socks5 # mux: true # headers: # custom: value - + - name: "ss4-shadow-tls" type: ss server: server @@ -364,20 +361,22 @@ proxies: # socks5 port: 443 cipher: chacha20-ietf-poly1305 password: [YOUR_SS_PASSWORD] - client-fingerprint: chrome # One of: chrome, ios, firefox or safari - # 可以是chrome, ios, firefox, safari中的一个 + client-fingerprint: + chrome # One of: chrome, ios, firefox or safari + # 可以是chrome, ios, firefox, safari中的一个 plugin: restls plugin-opts: - host: "www.microsoft.com" # Must be a TLS 1.3 server - # 应当是一个TLS 1.3 服务器 - password: [YOUR_RESTLS_PASSWORD] - version-hint: "tls13" - # Control your post-handshake traffic through restls-script - # Hide proxy behaviors like "tls in tls". - # see https://github.com/3andne/restls/blob/main/Restls-Script:%20Hide%20Your%20Proxy%20Traffic%20Behavior.md - # 用restls剧本来控制握手后的行为,隐藏"tls in tls"等特征 - # 详情:https://github.com/3andne/restls/blob/main/Restls-Script:%20%E9%9A%90%E8%97%8F%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%90%86%E8%A1%8C%E4%B8%BA.md - restls-script: "300?100<1,400~100,350~100,600~100,300~200,300~100" + host: + "www.microsoft.com" # Must be a TLS 1.3 server + # 应当是一个TLS 1.3 服务器 + password: [YOUR_RESTLS_PASSWORD] + version-hint: "tls13" + # Control your post-handshake traffic through restls-script + # Hide proxy behaviors like "tls in tls". + # see https://github.com/3andne/restls/blob/main/Restls-Script:%20Hide%20Your%20Proxy%20Traffic%20Behavior.md + # 用restls剧本来控制握手后的行为,隐藏"tls in tls"等特征 + # 详情:https://github.com/3andne/restls/blob/main/Restls-Script:%20%E9%9A%90%E8%97%8F%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%90%86%E8%A1%8C%E4%B8%BA.md + restls-script: "300?100<1,400~100,350~100,600~100,300~200,300~100" - name: "ss-restls-tls12" type: ss @@ -385,16 +384,18 @@ proxies: # socks5 port: 443 cipher: chacha20-ietf-poly1305 password: [YOUR_SS_PASSWORD] - client-fingerprint: chrome # One of: chrome, ios, firefox or safari - # 可以是chrome, ios, firefox, safari中的一个 + client-fingerprint: + chrome # One of: chrome, ios, firefox or safari + # 可以是chrome, ios, firefox, safari中的一个 plugin: restls plugin-opts: - host: "vscode.dev" # Must be a TLS 1.2 server - # 应当是一个TLS 1.2 服务器 - password: [YOUR_RESTLS_PASSWORD] - version-hint: "tls12" - restls-script: "1000?100<1,500~100,350~100,600~100,400~200" - + host: + "vscode.dev" # Must be a TLS 1.2 server + # 应当是一个TLS 1.2 服务器 + password: [YOUR_RESTLS_PASSWORD] + version-hint: "tls12" + restls-script: "1000?100<1,500~100,350~100,600~100,400~200" + # vmess # cipher支持 auto/aes-128-gcm/chacha20-poly1305/none - name: "vmess" @@ -417,7 +418,7 @@ proxies: # socks5 # Host: v2ray.com # max-early-data: 2048 # early-data-header-name: Sec-WebSocket-Protocol - + - name: "vmess-h2" type: vmess server: server @@ -433,7 +434,7 @@ proxies: # socks5 - http.example.com - http-alt.example.com path: / - + - name: "vmess-http" type: vmess server: server @@ -452,7 +453,7 @@ proxies: # socks5 # Connection: # - keep-alive # ip-version: ipv4 # 设置使用 IP 类型偏好,可选:ipv4,ipv6,dual,默认值:dual - + - name: vmess-grpc server: server port: 443 @@ -468,7 +469,7 @@ proxies: # socks5 grpc-opts: grpc-service-name: "example" # ip-version: ipv4 - + # vless - name: "vless-tcp" type: vless @@ -481,7 +482,7 @@ proxies: # socks5 # skip-cert-verify: true # fingerprint: xxxx # client-fingerprint: random # Available: "chrome","firefox","safari","random","none" - + - name: "vless-vision" type: vless server: server @@ -494,7 +495,7 @@ proxies: # socks5 client-fingerprint: chrome # fingerprint: xxxx # skip-cert-verify: true - + - name: "vless-reality-vision" type: vless server: server @@ -509,7 +510,7 @@ proxies: # socks5 public-key: xxx short-id: xxx # optional client-fingerprint: chrome # cannot be empty - + - name: "vless-reality-grpc" type: vless server: server @@ -527,7 +528,7 @@ proxies: # socks5 reality-opts: public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE short-id: 10f897e26c4b9478 - + - name: "vless-ws" type: vless server: server @@ -544,7 +545,7 @@ proxies: # socks5 path: "/" headers: Host: example.com - + # Trojan - name: "trojan" type: trojan @@ -559,7 +560,7 @@ proxies: # socks5 # - h2 # - http/1.1 # skip-cert-verify: true - + - name: trojan-grpc server: server port: 443 @@ -572,7 +573,7 @@ proxies: # socks5 udp: true grpc-opts: grpc-service-name: "example" - + - name: trojan-ws server: server port: 443 @@ -587,7 +588,7 @@ proxies: # socks5 # path: /path # headers: # Host: example.com - + - name: "trojan-xtls" type: trojan server: server @@ -599,7 +600,7 @@ proxies: # socks5 # sni: example.com # aka server name # skip-cert-verify: true # fingerprint: xxxx - + #hysteria - name: "hysteria" type: hysteria @@ -626,7 +627,7 @@ proxies: # socks5 # disable_mtu_discovery: false # fingerprint: xxxx # fast-open: true # 支持 TCP 快速打开,默认为 false - + # wireguard - name: "wg" type: wireguard @@ -655,7 +656,7 @@ proxies: # socks5 # # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM= # allowed_ips: ['0.0.0.0/0'] # reserved: [209,98,59] - + # tuic - name: tuic server: www.example.com @@ -674,12 +675,13 @@ proxies: # socks5 request-timeout: 8000 udp-relay-mode: native # Available: "native", "quic". Default: "native" # congestion-controller: bbr # Available: "cubic", "new_reno", "bbr". Default: "cubic" + # cwnd: 10 # default: 32 # max-udp-relay-packet-size: 1500 # fast-open: true # skip-cert-verify: true # max-open-streams: 20 # default 100, too many open streams may hurt performance # sni: example.com - + # ShadowsocksR # The supported ciphers (encryption methods): all stream ciphers in ss # The supported obfses: @@ -711,7 +713,7 @@ proxy-groups: - vmess - ss1 - ss2 - + # url-test 将按照 url 测试结果使用延迟最低节点 - name: "auto" type: url-test @@ -723,7 +725,7 @@ proxy-groups: # lazy: true url: "https://cp.cloudflare.com/generate_204" interval: 300 - + # fallback 将按照 url 测试结果按照节点顺序选择 - name: "fallback-auto" type: fallback @@ -733,7 +735,7 @@ proxy-groups: - vmess1 url: "https://cp.cloudflare.com/generate_204" interval: 300 - + # load-balance 将按照算法随机选择节点 - name: "load-balance" type: load-balance @@ -744,7 +746,7 @@ proxy-groups: url: "https://cp.cloudflare.com/generate_204" interval: 300 # strategy: consistent-hashing # 可选 round-robin 和 sticky-sessions - + # select 用户自行选择节点 - name: Proxy type: select @@ -754,7 +756,7 @@ proxy-groups: - ss2 - vmess1 - auto - + # 配置指定 interface-name 和 fwmark 的 DIRECT - name: en1 type: select @@ -762,7 +764,7 @@ proxy-groups: routing-mark: 6667 proxies: - DIRECT - + - name: UseProvider type: select filter: "HK|TW" # 正则表达式,过滤 provider1 中节点名包含 HK 或 TW @@ -778,7 +780,7 @@ proxy-providers: type: http url: "url" interval: 3600 - path: ./provider1.yaml # 默认只允许存储在 clash 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 + path: ./provider1.yaml # 默认只允许存储在 clash 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 health-check: enable: true interval: 600 @@ -795,7 +797,7 @@ rule-providers: rule1: behavior: classical # domain ipcidr interval: 259200 - path: /path/to/save/file.yaml # 默认只允许存储在 clash 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 + path: /path/to/save/file.yaml # 默认只允许存储在 clash 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 type: http url: "url" rule2: @@ -846,14 +848,14 @@ listeners: # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理 # udp: false # 默认 true - + - name: http-in-1 type: http port: 10809 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) - + - name: mixed-in-1 type: mixed # HTTP(S) 和 SOCKS 代理混合 port: 10810 @@ -861,14 +863,14 @@ listeners: # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) # udp: false # 默认 true - + - name: reidr-in-1 type: redir port: 10811 listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) - + - name: tproxy-in-1 type: tproxy port: 10812 @@ -876,7 +878,7 @@ listeners: # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) # udp: false # 默认 true - + - name: shadowsocks-in-1 type: shadowsocks port: 10813 @@ -885,7 +887,7 @@ listeners: # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) password: vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg= cipher: 2022-blake3-aes-256-gcm - + - name: vmess-in-1 type: vmess port: 10814 @@ -896,7 +898,7 @@ listeners: - username: 1 uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68 alterId: 1 - + - name: tuic-in-1 type: tuic port: 10815 @@ -916,7 +918,7 @@ listeners: # alpn: # - h3 # max-udp-relay-packet-size: 1500 - + - name: tunnel-in-1 type: tunnel port: 10816 @@ -925,7 +927,7 @@ listeners: # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) network: [tcp, udp] target: target.com - + - name: tun-in-1 type: tun # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules @@ -956,10 +958,10 @@ listeners: # - 1000 # exclude_uid_range: # 排除路由的的用户范围 # - 1000-99999 - + # Android 用户和应用规则仅在 Android 下被支持 # 并且需要 auto_route - + # include_android_user: # 限制被路由的 Android 用户 # - 0 # - 10 @@ -967,7 +969,6 @@ listeners: # - com.android.chrome # exclude_package: # 排除被路由的 Android 应用包名 # - com.android.captiveportallogin - # 入口配置与 Listener 等价,传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理 # shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理) # ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456 diff --git a/transport/tuic/common/congestion.go b/transport/tuic/common/congestion.go index e2f7d867..8b8018b5 100644 --- a/transport/tuic/common/congestion.go +++ b/transport/tuic/common/congestion.go @@ -4,6 +4,7 @@ import ( "github.com/Dreamacro/clash/transport/tuic/congestion" "github.com/metacubex/quic-go" + c "github.com/metacubex/quic-go/congestion" ) const ( @@ -11,7 +12,8 @@ const ( DefaultConnectionReceiveWindow = 67108864 // 64 MB/s ) -func SetCongestionController(quicConn quic.Connection, cc string) { +func SetCongestionController(quicConn quic.Connection, cc string, cwnd int) { + CWND := c.ByteCount(cwnd) switch cc { case "cubic": quicConn.SetCongestionControl( @@ -36,7 +38,7 @@ func SetCongestionController(quicConn quic.Connection, cc string) { congestion.NewBBRSender( congestion.DefaultClock{}, congestion.GetInitialPacketSize(quicConn.RemoteAddr()), - congestion.InitialCongestionWindow*congestion.InitialMaxDatagramSize, + CWND*congestion.InitialMaxDatagramSize, congestion.DefaultBBRMaxCongestionWindow*congestion.InitialMaxDatagramSize, ), ) diff --git a/transport/tuic/v4/client.go b/transport/tuic/v4/client.go index 7e5ed7e0..e1a334e5 100644 --- a/transport/tuic/v4/client.go +++ b/transport/tuic/v4/client.go @@ -36,6 +36,7 @@ type ClientOption struct { MaxUdpRelayPacketSize int FastOpen bool MaxOpenStreams int64 + CWND int } type clientImpl struct { @@ -91,7 +92,7 @@ func (t *clientImpl) getQuicConn(ctx context.Context, dialer C.Dialer, dialFn co return nil, err } - common.SetCongestionController(quicConn, t.CongestionController) + common.SetCongestionController(quicConn, t.CongestionController, t.CWND) go func() { _ = t.sendAuthentication(quicConn) diff --git a/transport/tuic/v4/server.go b/transport/tuic/v4/server.go index 017494ea..37b311b0 100644 --- a/transport/tuic/v4/server.go +++ b/transport/tuic/v4/server.go @@ -33,6 +33,7 @@ type ServerOption struct { CongestionController string AuthenticationTimeout time.Duration MaxUdpRelayPacketSize int + CWND int } type Server struct { @@ -57,7 +58,7 @@ func (s *Server) Serve() error { if err != nil { return err } - common.SetCongestionController(conn, s.CongestionController) + common.SetCongestionController(conn, s.CongestionController, s.CWND) h := &serverHandler{ Server: s, quicConn: conn, diff --git a/transport/tuic/v5/client.go b/transport/tuic/v5/client.go index 7bc1c360..cb1d538c 100644 --- a/transport/tuic/v5/client.go +++ b/transport/tuic/v5/client.go @@ -33,6 +33,7 @@ type ClientOption struct { ReduceRtt bool MaxUdpRelayPacketSize int MaxOpenStreams int64 + CWND int } type clientImpl struct { @@ -88,7 +89,7 @@ func (t *clientImpl) getQuicConn(ctx context.Context, dialer C.Dialer, dialFn co return nil, err } - common.SetCongestionController(quicConn, t.CongestionController) + common.SetCongestionController(quicConn, t.CongestionController, t.CWND) go func() { _ = t.sendAuthentication(quicConn) diff --git a/transport/tuic/v5/server.go b/transport/tuic/v5/server.go index 26965436..7b21ee6c 100644 --- a/transport/tuic/v5/server.go +++ b/transport/tuic/v5/server.go @@ -6,6 +6,7 @@ import ( "context" "crypto/tls" "fmt" + "net" "sync" "sync/atomic" @@ -32,6 +33,7 @@ type ServerOption struct { CongestionController string AuthenticationTimeout time.Duration MaxUdpRelayPacketSize int + CWND int } type Server struct { @@ -56,7 +58,7 @@ func (s *Server) Serve() error { if err != nil { return err } - common.SetCongestionController(conn, s.CongestionController) + common.SetCongestionController(conn, s.CongestionController, s.CWND) h := &serverHandler{ Server: s, quicConn: conn,